Me estoy familiarizando con el marco MVC y a menudo me pregunto cuánto código debería incluir el modelo. Tiendo a tener una clase de acceso a datos que tiene métodos como este:
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;
}
}
Mis modelos tienden a ser una clase de entidad que se asigna a la tabla de la base de datos.
¿Debería el objeto modelo tener todas las propiedades asignadas de la base de datos, así como el código anterior o está bien separar ese código que realmente funciona la base de datos?
¿Terminaré teniendo cuatro capas?
php
oop
model-view-controller
architecture
model
Dietpixel
fuente
fuente
Exception
no tiene mucho valor de documentación. Personalmente, si seguía ese camino, elegiría PHPDoc@exception
o algún mecanismo similar, por lo que se muestra en la documentación generada.Respuestas:
Lo primero que debo aclarar es: el modelo es una capa .
Segundo: hay una diferencia entre el MVC clásico y lo que usamos en el desarrollo web. Aquí hay una respuesta un poco más antigua que escribí, que describe brevemente cómo son diferentes.
Qué modelo NO es:
El modelo no es una clase ni ningún objeto individual. Es un error muy común de cometer (también lo hice, aunque la respuesta original fue escrita cuando comencé a aprender lo contrario) , porque la mayoría de los marcos perpetúan esta idea errónea.
Tampoco es una técnica de mapeo relacional de objetos (ORM) ni una abstracción de tablas de bases de datos. Cualquiera que le diga lo contrario probablemente intente 'vender' otro ORM nuevo o un marco completo.
Qué modelo es:
En la adaptación adecuada de MVC, la M contiene toda la lógica de negocio del dominio y la Capa de modelo se compone principalmente de tres tipos de estructuras:
Objetos de dominio
Aquí es donde definiría cómo validar los datos antes de enviar una factura, o calcular el costo total de un pedido. Al mismo tiempo, los objetos de dominio desconocen por completo el almacenamiento, ni desde dónde (base de datos SQL, API REST, archivo de texto, etc.) ni siquiera si se guardan o recuperan.
Mapeadores de datos
Estos objetos solo son responsables del almacenamiento. Si almacena información en una base de datos, este sería el lugar donde vive el SQL. O tal vez use un archivo XML para almacenar datos, y sus Data Mappers están analizando desde y hacia archivos XML.
Servicios
Puede pensar en ellos como "Objetos de dominio de nivel superior", pero en lugar de la lógica empresarial, los Servicios son responsables de la interacción entre Objetos de dominio y Mapeadores . Estas estructuras terminan creando una interfaz "pública" para interactuar con la lógica comercial del dominio. Puede evitarlos, pero con la penalidad de filtrar cierta lógica de dominio en los Controladores .
Hay una respuesta relacionada con este tema en la pregunta de implementación de ACL : puede ser útil.
La comunicación entre la capa del modelo y otras partes de la tríada MVC solo debe realizarse a través de los Servicios . La separación clara tiene algunos beneficios adicionales:
¿Cómo interactuar con una modelo?
Obtener acceso a instancias de servicio
Para las instancias de Vista y Controlador (lo que se podría llamar: "capa de interfaz de usuario") para tener acceso a estos servicios, hay dos enfoques generales:
Como puede sospechar, el contenedor DI es una solución mucho más elegante (aunque no es la más fácil para un principiante). Las dos bibliotecas, que recomiendo considerar para esta funcionalidad, serían el componente de inyección de dependencia independiente de Syfmony o Auryn .
Tanto las soluciones que usan una fábrica como un contenedor DI también le permitirían compartir las instancias de varios servidores que se compartirán entre el controlador seleccionado y la vista para un ciclo de solicitud-respuesta dado.
Alteración del estado del modelo.
Ahora que puede acceder a la capa del modelo en los controladores, debe comenzar a usarlos:
Sus controladores tienen una tarea muy clara: tomar la entrada del usuario y, en base a esta entrada, cambiar el estado actual de la lógica empresarial. En este ejemplo, los estados que se cambian entre "usuario anónimo" y "usuario conectado".
El controlador no es responsable de validar la entrada del usuario, porque eso es parte de las reglas de negocio y el controlador definitivamente no está llamando a consultas SQL, como lo que vería aquí o aquí (por favor, no las odie, están equivocadas, no son malas).
Mostrando al usuario el cambio de estado.
Ok, el usuario ha iniciado sesión (o ha fallado). ¿Ahora que? Dicho usuario todavía no lo sabe. Por lo tanto, debe producir una respuesta y esa es la responsabilidad de una vista.
En este caso, la vista produjo una de dos posibles respuestas, en función del estado actual de la capa del modelo. Para un caso de uso diferente, tendría la vista seleccionando diferentes plantillas para representar, en base a algo como "artículo seleccionado actualmente".
La capa de presentación en realidad puede ser bastante elaborada, como se describe aquí: Comprender las vistas MVC en PHP .
¡Pero solo estoy haciendo una API REST!
Por supuesto, hay situaciones, cuando esto es una exageración.
MVC es solo una solución concreta para el principio de separación de preocupaciones . MVC separa la interfaz de usuario de la lógica de negocios y, en la interfaz de usuario, separa el manejo de la entrada del usuario y la presentación. Esto es crucial Aunque a menudo la gente lo describe como una "tríada", en realidad no se compone de tres partes independientes. La estructura es más como esta:
Significa que, cuando la lógica de su capa de presentación es casi inexistente, el enfoque pragmático es mantenerlos como una sola capa. También puede simplificar sustancialmente algunos aspectos de la capa del modelo.
Con este enfoque, el ejemplo de inicio de sesión (para una API) se puede escribir como:
Si bien esto no es sostenible, cuando tiene una lógica complicada para representar un cuerpo de respuesta, esta simplificación es muy útil para escenarios más triviales. Pero tenga en cuenta que este enfoque se convertirá en una pesadilla cuando intente utilizarlo en bases de código grandes con una lógica de presentación compleja.
¿Cómo construir el modelo?
Como no hay una sola clase de "Modelo" (como se explicó anteriormente), realmente no "construye el modelo". En su lugar, comienza por hacer Servicios , que pueden realizar ciertos métodos. Y luego implementar objetos de dominio y mapeadores .
Un ejemplo de un método de servicio:
En los dos enfoques anteriores había este método de inicio de sesión para el servicio de identificación. ¿Cómo se vería realmente? Estoy usando una versión ligeramente modificada de la misma funcionalidad de una biblioteca que escribí ... porque soy vago:
Como puede ver, en este nivel de abstracción, no hay indicación de dónde se obtuvieron los datos. Puede ser una base de datos, pero también puede ser solo un objeto simulado para fines de prueba. Incluso los mapeadores de datos, que realmente se utilizan para ello, están ocultos en los
private
métodos de este servicio.Formas de crear mapeadores
Para implementar una abstracción de persistencia, en los enfoques más flexibles es crear mapeadores de datos personalizados .
De: libro de PoEAA
En la práctica, se implementan para la interacción con clases o superclases específicas. Digamos que tiene
Customer
yAdmin
en su código (ambos heredan de unaUser
superclase). Ambos probablemente terminarían teniendo un mapeador coincidente separado, ya que contienen diferentes campos. Pero también terminará con operaciones compartidas y de uso común. Por ejemplo: actualizar el "último visto en línea" . Y en lugar de hacer que los mapeadores existentes sean más complicados, el enfoque más pragmático es tener un "Mapeador de usuarios" general, que solo actualiza esa marca de tiempo.Algunos comentarios adicionales:
Tablas de base de datos y modelo
Si bien a veces hay una relación directa 1: 1: 1 entre una tabla de base de datos, un Objeto de dominio y un Mapeador , en proyectos más grandes puede ser menos común de lo que espera:
La información utilizada por un solo Objeto de dominio puede asignarse desde diferentes tablas, mientras que el objeto en sí no tiene persistencia en la base de datos.
Ejemplo: si está generando un informe mensual. Esto recolectaría información de diferentes tablas, pero no hay una
MonthlyReport
tabla mágica en la base de datos.Un solo Mapper puede afectar múltiples tablas.
Ejemplo: cuando está almacenando datos del
User
objeto, este Objeto de dominio podría contener una colección de otros objetos de dominio:Group
instancias. Si los modifica y almacenaUser
, el Data Mapper tendrá que actualizar y / o insertar entradas en varias tablas.Los datos de un solo objeto de dominio se almacenan en más de una tabla.
Ejemplo: en sistemas grandes (piense: una red social de tamaño mediano), podría ser pragmático almacenar los datos de autenticación del usuario y los datos a los que se accede con frecuencia por separado de grandes cantidades de contenido, lo que rara vez se requiere. En ese caso, es posible que aún tenga una sola
User
clase, pero la información que contiene dependerá de si se obtuvieron todos los detalles.Por cada objeto de dominio puede haber más de un mapeador
Ejemplo: tiene un sitio de noticias con un código compartido basado tanto para el público como para el software de administración. Pero, si bien ambas interfaces usan la misma
Article
clase, la administración necesita mucha más información. En este caso, tendría dos mapeadores separados: "interno" y "externo". Cada uno realiza diferentes consultas, o incluso utiliza diferentes bases de datos (como en maestro o esclavo).Una vista no es una plantilla
Ver instancias en MVC (si no está utilizando la variación MVP del patrón) es responsable de la lógica de presentación. Esto significa que cada vista generalmente hará malabares con al menos algunas plantillas. Adquiere datos de la capa modelo y luego, en función de la información recibida, elige una plantilla y establece valores.
Uno de los beneficios que obtiene de esto es la reutilización. Si crea una
ListView
clase, entonces, con un código bien escrito, puede tener la misma clase entregando la presentación de la lista de usuarios y los comentarios debajo de un artículo. Porque ambos tienen la misma lógica de presentación. Simplemente cambia de plantilla.Puede usar plantillas PHP nativas o usar un motor de plantillas de terceros. También puede haber algunas bibliotecas de terceros, que pueden reemplazar completamente las instancias de View .
¿Qué pasa con la versión anterior de la respuesta?
El único cambio importante es que, lo que se llama Modelo en la versión anterior, es en realidad un Servicio . El resto de la "analogía de la biblioteca" se mantiene bastante bien.
La única falla que veo es que esta sería una biblioteca realmente extraña, porque le devolvería información del libro, pero no le permitiría tocar el libro en sí, porque de lo contrario la abstracción comenzaría a "filtrarse". Podría tener que pensar en una analogía más adecuada.
¿Cuál es la relación entre las instancias de View y Controller ?
La estructura MVC se compone de dos capas: ui y modelo. Las estructuras principales en la capa de IU son vistas y controlador.
Cuando se trata de sitios web que utilizan el patrón de diseño MVC, la mejor manera es tener una relación 1: 1 entre las vistas y los controladores. Cada vista representa una página completa en su sitio web y tiene un controlador dedicado para manejar todas las solicitudes entrantes para esa vista en particular.
Por ejemplo, para representar un artículo abierto, debería tener
\Application\Controller\Document
y\Application\View\Document
. Esto contendría toda la funcionalidad principal para la capa de interfaz de usuario, cuando se trata de tratar con artículos (por supuesto, puede tener algunos componentes XHR que no están directamente relacionados con los artículos) .fuente
Todo lo que es lógica de negocios pertenece a un modelo, ya sea una consulta de base de datos, cálculos, una llamada REST, etc.
Puede tener acceso a los datos en el modelo mismo, el patrón MVC no le impide hacerlo. Puede endulzarlo con servicios, mapeadores y demás, pero la definición real de un modelo es una capa que maneja la lógica empresarial, nada más y nada menos. Puede ser una clase, una función o un módulo completo con millones de objetos si eso es lo que quieres.
Siempre es más fácil tener un objeto separado que realmente ejecute las consultas de la base de datos en lugar de ejecutarlas directamente en el modelo: esto será especialmente útil cuando se realicen pruebas unitarias (debido a la facilidad de inyectar una dependencia de base de datos simulada en su modelo):
Además, en PHP, rara vez necesita capturar / volver a generar excepciones porque se conserva la traza inversa, especialmente en un caso como su ejemplo. Simplemente deje que se lance la excepción y en su lugar póngala en el controlador.
fuente
User
clase básicamente extiende el modelo, pero no es un objeto. El usuario debe ser un objeto y tiene propiedades como: id, name ... Estás implementandoUser
class es un ayudante.User
representa un objeto, y debe tener propiedades de un Usuario, no métodos comoCheckUsername
, ¿qué debe hacer si desea crear un nuevoUser
objeto?new User($db)
En la Web- "MVC" puedes hacer lo que quieras.
El concepto original (1) describió el modelo como la lógica empresarial. Debe representar el estado de la aplicación y aplicar cierta coherencia de datos. Ese enfoque a menudo se describe como "modelo gordo".
La mayoría de los frameworks PHP siguen un enfoque más superficial, donde el modelo es solo una interfaz de base de datos. Pero al menos estos modelos aún deberían validar los datos y las relaciones entrantes.
De cualquier manera, no está muy lejos si separa las cosas de SQL o las llamadas a la base de datos en otra capa. De esta manera, solo necesita preocuparse por los datos / comportamientos reales, no con la API de almacenamiento real. (Sin embargo, no es razonable exagerar. Por ejemplo, nunca podrá reemplazar un backend de base de datos con un almacenamiento de archivos si no se diseñó de antemano).
fuente
Con mayor frecuencia, la mayoría de las aplicaciones tendrán una parte de datos, visualización y procesamiento, y solo ponemos todas esas en las letras
M
,V
yC
.Modelo (
M
) -> Tiene los atributos que mantienen el estado de aplicación y no sabe nada sobreV
yC
.Ver (
V
) -> Tiene un formato de visualización para la aplicación y solo conoce el modelo de cómo digerir y no se molestaC
.Controlador (
C
) ----> Tiene parte de procesamiento de la aplicación y actúa como cableado entre M y V y depende de ambosM
, aV
diferencia deM
yV
.En total, existe una separación de preocupación entre cada uno. En el futuro, cualquier cambio o mejora se puede agregar muy fácilmente.
fuente
En mi caso, tengo una clase de base de datos que maneja todas las interacciones directas de la base de datos, como las consultas, la obtención y demás. Entonces, si tuviera que cambiar mi base de datos de MySQL a PostgreSQL , no habría ningún problema. Por lo tanto, agregar esa capa adicional puede ser útil.
Cada tabla puede tener su propia clase y sus métodos específicos, pero para obtener realmente los datos, permite que la clase de la base de datos lo maneje:
Expediente
Database.php
Tabla objeto clase L
Espero que este ejemplo te ayude a crear una buena estructura.
fuente
Database
en el ejemplo no es una clase. Es solo un contenedor para funciones. Además, ¿cómo puede tener una "clase de objeto de tabla" sin un objeto?