php - Come dovrebbe essere strutturato un modello in MVC?




oop model-view-controller architecture (5)

Più spesso la maggior parte delle applicazioni avrà una parte di dati, di visualizzazione e di elaborazione e inseriamo semplicemente tutti quelli nelle lettere M , V e C

Model ( M ) -> Ha gli attributi che mantengono lo stato di applicazione e non sa nulla di V e C

Visualizza ( V ) -> Ha un formato di visualizzazione per l'applicazione e conosce solo il modello how-to-digest su di esso e non si preoccupa di C

Controller ( C ) ----> Ha una parte di applicazione di elaborazione e funge da cablaggio tra M e V e dipende da M , V differenza di M e V

Complessivamente c'è una separazione di preoccupazione tra ciascuno. In futuro qualsiasi modifica o miglioramento può essere aggiunto molto facilmente.

Sto solo facendo conoscenza del framework MVC e spesso mi chiedo quanto codice debba andare nel modello. Tendo ad avere una classe di accesso ai dati che ha metodi come questo:

public function CheckUsername($connection, $username)
{
    try
    {
        $data = array();
        $data['Username'] = $username;

        //// SQL
        $sql = "SELECT Username FROM" . $this->usersTableName . " WHERE Username = :Username";

        //// Execute statement
        return $this->ExecuteObject($connection, $sql, $data);
    }
    catch(Exception $e)
    {
        throw $e;
    }
}

I miei modelli tendono ad essere una classe di entità mappata alla tabella del database.

L'oggetto del modello deve avere tutte le proprietà mappate del database e il codice sopra o è OK separare quel codice che effettivamente funziona il database?

Finirò per avere quattro strati?


Tutto ciò che è logico aziendale appartiene a un modello, sia che si tratti di una query di database, di calcoli, di una chiamata REST, ecc.

Puoi avere l'accesso ai dati nel modello stesso, il pattern MVC non ti impedisce di farlo. Puoi rivestirlo di zucchero con servizi, mappatori e cosa no, ma la definizione attuale di un modello è un livello che gestisce la logica di business, niente di più, niente di meno. Può essere una classe, una funzione o un modulo completo con un oggetto di miliardi se è quello che vuoi.

È sempre più semplice avere un oggetto separato che esegua effettivamente le query del database anziché eseguirle direttamente nel modello: questo sarà particolarmente utile quando si esegue il test delle unità (a causa della facilità di iniettare una dipendenza del database fittizio nel modello):

class Database {
   protected $_conn;

   public function __construct($connection) {
       $this->_conn = $connection;
   }

   public function ExecuteObject($sql, $data) {
       // stuff
   }
}

abstract class Model {
   protected $_db;

   public function __construct(Database $db) {
       $this->_db = $db;
   }
}

class User extends Model {
   public function CheckUsername($username) {
       // ...
       $sql = "SELECT Username FROM" . $this->usersTableName . " WHERE ...";
       return $this->_db->ExecuteObject($sql, $data);
   }
}

$db = new Database($conn);
$model = new User($db);
$model->CheckUsername('foo');

Inoltre, in PHP, raramente devi catturare / rilanciare le eccezioni perché il backtrace è preservato, specialmente in un caso come il tuo esempio. Fai in modo che l'eccezione sia lanciata e catturala nel controller.


In Web- "MVC" puoi fare tutto ciò che vuoi.

Il concetto originale (1) descriveva il modello come la logica aziendale. Dovrebbe rappresentare lo stato dell'applicazione e rafforzare la coerenza dei dati. Questo approccio è spesso descritto come "modello grasso".

La maggior parte dei framework PHP segue un approccio più superficiale, in cui il modello è solo un'interfaccia di database. Ma per lo meno questi modelli dovrebbero ancora convalidare i dati in entrata e le relazioni.

In entrambi i casi, non sei molto lontano se separi la roba SQL o le chiamate al database in un altro livello. In questo modo devi solo occuparti dei dati / comportamenti reali, non con l'effettiva API di archiviazione. (È comunque irragionevole esagerare. Ad esempio, non sarà mai possibile sostituire un back-end del database con una filestoria se non è stato progettato in anticipo.)


Nel mio caso, ho una classe di database che gestisce tutte le interazioni dirette del database come l'interrogazione, il recupero e così via. Quindi se dovessi cambiare il mio database da MySQL a PostgreSQL non ci saranno problemi. Quindi aggiungere quel livello in più può essere utile.

Ogni tabella può avere una propria classe e avere i suoi metodi specifici, ma per ottenere effettivamente i dati, consente alla classe del database di gestirli:

File Database.php

class Database {
    private static $connection;
    private static $current_query;
    ...

    public static function query($sql) {
        if (!self::$connection){
            self::open_connection();
        }
        self::$current_query = $sql;
        $result = mysql_query($sql,self::$connection);

        if (!$result){
            self::close_connection();
            // throw custom error
            // The query failed for some reason. here is query :: self::$current_query
            $error = new Error(2,"There is an Error in the query.\n<b>Query:</b>\n{$sql}\n");
            $error->handleError();
        }
        return $result;
    }
 ....

    public static function find_by_sql($sql){
        if (!is_string($sql))
            return false;

        $result_set = self::query($sql);
        $obj_arr = array();
        while ($row = self::fetch_array($result_set))
        {
            $obj_arr[] = self::instantiate($row);
        }
        return $obj_arr;
    }
}

Oggetto tabella classL

class DomainPeer extends Database {

    public static function getDomainInfoList() {
        $sql = 'SELECT ';
        $sql .='d.`id`,';
        $sql .='d.`name`,';
        $sql .='d.`shortName`,';
        $sql .='d.`created_at`,';
        $sql .='d.`updated_at`,';
        $sql .='count(q.id) as queries ';
        $sql .='FROM `domains` d ';
        $sql .='LEFT JOIN queries q on q.domainId = d.id ';
        $sql .='GROUP BY d.id';
        return self::find_by_sql($sql);
    }

    ....
}

Spero che questo esempio ti aiuti a creare una buona struttura.


Alcune linee guida per l'escape di caratteri speciali nelle istruzioni SQL.

Non usare MySQL , questa estensione è deprecata, usa MySQLi o PDO .

MySQLi

Per eseguire manualmente l'escape di caratteri speciali in una stringa, puoi utilizzare la funzione mysqli_real_escape_string . La funzione non funzionerà correttamente a meno che il set di caratteri corretto sia impostato con mysqli_set_charset .

Esempio:

$mysqli = new mysqli( 'host', 'user', 'password', 'database' );
$mysqli->set_charset( 'charset');

$string = $mysqli->real_escape_string( $string );
$mysqli->query( "INSERT INTO table (column) VALUES ('$string')" );

Per l'escape automatico dei valori con istruzioni preparate, utilizzare mysqli_prepare e mysqli_stmt_bind_param dove devono essere forniti i tipi per le variabili di collegamento corrispondenti per una conversione appropriata:

Esempio:

$stmt = $mysqli->prepare( "INSERT INTO table ( column1, column2 ) VALUES (?,?)" );

$stmt->bind_param( "is", $integer, $string );

$stmt->execute();

Non importa se usi istruzioni preparate o mysqli_real_escape_string, devi sempre conoscere il tipo di dati di input con cui stai lavorando.

Pertanto, se si utilizza un'istruzione preparata, è necessario specificare i tipi di variabili per la funzione mysqli_stmt_bind_param.

E l'uso di mysqli_real_escape_string è per, come dice il nome, l'escape di caratteri speciali in una stringa, quindi non renderà gli interi sicuri. Lo scopo di questa funzione è di impedire la rottura delle stringhe nelle istruzioni SQL e il danno al database che potrebbe causare. mysqli_real_escape_string è una funzione utile se usata correttamente, specialmente se combinata con sprintf.

Esempio:

$string = "x' OR name LIKE '%John%";
$integer = '5 OR id != 0';

$query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string( $string ), $integer );

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 5

$integer = '99999999999999999999';
$query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string( $string ), $integer );

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 2147483647






php oop model-view-controller architecture model