Principalmente para desarrollar interfaces de usuario. Para usarlo, necesita crear un tipo de Modelo que represente el estado completo del programa, un tipo de Mensaje que describa eventos ambientales externos a los que el programa debe responder cambiando su estado, una función de actualización que cree un nuevo estado del programa del estado y mensaje antiguos, y una función de visualización que, en función del estado del programa, calcula los impactos requeridos en el entorno externo, que generan eventos del tipo Mensaje. El patrón es muy conveniente, pero tiene un pequeño inconveniente: no permite describir qué eventos tienen sentido para estados específicos del programa.
Un problema similar surge (y se resuelve) cuando se utiliza el patrón State OO.
El lenguaje Elm es simple, pero muy estricto: verifica que la función de actualización maneje de alguna manera todas las combinaciones posibles de modelo-estado y eventos de mensaje. Por lo tanto, hay que escribir código adicional, aunque trivial, que normalmente deja el modelo sin cambios. Quiero demostrar cómo se puede evitar esto en lenguajes más complejos: Idris, Scala, C++ y Haskell.
Todo el código que se muestra aquí está disponible en GitHub para experimentación. Echemos un vistazo a los lugares más interesantes.
MUV es un constructor. Acepta parámetros: modelo: el estado inicial del programa, actualizador: una función para actualizar el estado ante un evento externo y vista: una función para crear una vista externa. Tenga en cuenta que el tipo de funciones de actualización y visualización depende del valor del modelo (usando la función msg de los parámetros de tipo).
Ahora veamos cómo iniciar esta aplicación.
MuvRun: (modelo de aplicaciónTipo msgTipo IO) -> IO a muvRun (vista del actualizador del modelo MUV) = hacer mensaje<- view model
muvRun (MUV (updater model msg) updater view)
Elegimos una operación de entrada/salida como una representación externa (vista) (en Idris, como en Haskell, las operaciones de entrada/salida son valores de primera clase; para que se ejecuten, se deben tomar acciones adicionales, generalmente devolviendo dicha operación). de la función principal).
Brevemente sobre IO
Al realizar una operación de tipo (IO a), se produce algún impacto en el mundo exterior, posiblemente vacío, y se devuelve un valor de tipo a al programa, pero las funciones de la biblioteca estándar están diseñadas de tal manera que puede sólo se procesará generando un nuevo valor de tipo IO b. De esta forma se separan las funciones puras de las funciones con efectos secundarios. Esto es inusual para muchos programadores, pero ayuda a escribir código más confiable.
Ahora describamos los tipos de entidades con las que vamos a trabajar.
Modelo de datos = Cerrado sesión | Datos de cadena conectados MsgOuted = Datos de cadena de inicio de sesión MsgIned = Cerrar sesión | Saludo total msgType: Modelo -> Tipo msgType Cerrado = MsgOuted msgType (Iniciado _) = MsgIned
Aquí describimos un tipo de modelo que refleja la presencia de dos estados de interfaz: el usuario no ha iniciado sesión y el usuario con un nombre de tipo String sí ha iniciado sesión.
A continuación describimos dos diferentes tipos de mensajes relevantes para diferentes variantes del modelo: si estamos desconectados, solo podemos iniciar sesión con un nombre determinado, y si ya estamos conectados, podemos cerrar sesión o saludar. Idris es un lenguaje fuertemente tipado que no permitirá la posibilidad de mezclar diferentes tipos.
Y finalmente, una función que establece la correspondencia del valor del modelo con el tipo de mensaje.
La función se declara total, es decir, no debería fallar ni congelarse, el compilador intentará monitorear esto. msgType se llama en el momento de la compilación, y su totalidad significa que la compilación no se congelará debido a nuestro error, aunque no puede garantizar que la ejecución de esta función agote los recursos del sistema.
También se garantiza que no ejecutará "rm -rf /" porque no hay IO en su firma.
Describamos el actualizador:
Actualizador total: (m:Model) -> (msgType m) -> Actualizador de modelo Cerrado (nombre de inicio de sesión) = Actualizador de nombre registrado (nombre de inicio de sesión) Cerrar sesión = Actualizador desconectado (nombre de inicio de sesión) Saludo = Nombre de inicio de sesión
Creo que la lógica de esta función es clara. Me gustaría señalar una vez más la totalidad: significa que el compilador de Idris comprobará que hemos considerado todas las alternativas permitidas por el sistema de tipos. Elm también realiza esta verificación, pero no puede saber que no podemos cerrar sesión si aún no hemos iniciado sesión, y requerirá un procesamiento explícito de la condición.
Actualizador Cerrado Cerrar sesión = ???
Idris encontrará discrepancias de tipos en una comprobación innecesaria.
Ahora pasemos a la vista; como es habitual en la interfaz de usuario, esta será la parte más difícil del código.
Total loginPage: IO MsgOuted loginPage = do putStr "Iniciar sesión: " map Iniciar sesión getLine total genMsg: String -> MsgIned genMsg "" = Cerrar sesión genMsg _ = Saludar a total workPage: String -> IO MsgIned workPage nombre = do putStr ("Hola, " ++ nombre ++ "\n") putStr "Ingrese una cadena vacía para cerrar sesión o no vacía para saludar\n" mapa genMsg getLine vista total: (m: Modelo) -> IO (msgType m) vista Cerrado de sesión = vista de página de inicio de sesión (nombre de inicio de sesión ) = nombre de la página de trabajo
La vista debe crear una operación de E/S que devuelva mensajes, cuyo tipo nuevamente depende del valor del modelo. Tenemos dos opciones: loginPage, que imprime un mensaje "Iniciar sesión:", lee una cadena del teclado y la envuelve en un mensaje de inicio de sesión, y página de trabajo con un parámetro de nombre de usuario, que imprime un saludo y devuelve mensajes diferentes (pero del mismo tipo). - MsgIned) dependiendo de si el usuario ingresa una cadena vacía o no vacía. view devuelve una de estas operaciones dependiendo del valor del modelo, y el compilador verifica su tipo, aunque sea diferente.
Ahora podemos crear y ejecutar nuestra aplicación.
Aplicación: Modelo de aplicación Main.msgType Aplicación IO = MUV Vista del actualizador desconectado principal: IO () principal = aplicación muvRun
Cabe señalar aquí un punto sutil: la función muvRun devuelve IO un, donde no se especificó a y el valor main es de tipo E/S(), Dónde ()
es el nombre de un tipo generalmente llamado Unidad, que tiene un valor único, también escrito como una tupla vacía ()
. Pero el compilador puede manejar esto fácilmente. sustituyendo a() en su lugar.
Clase abstracta sellada Clase de caso MsgLogined Login (nombre: Cadena) extiende la clase abstracta sellada MsgLogouted Clase de caso MsgLogined Logout() extiende la clase de caso MsgLogined Greet() extiende la clase abstracta MsgLogined Ver (def run(): Msg) Clase abstracta sellada Modelo (tipo Mensaje def view(): Ver) clase de caso Cerrado de sesión() extiende el modelo (tipo Mensaje = MsgLogined anulación def view(): Ver....) clase de caso Cerrado de sesión (nombre: Cadena) extiende el modelo (tipo Mensaje = MsgLogined anulación def view( ) : Vista .... )
Los tipos algebraicos en Scala se modelan mediante herencia. El tipo corresponde a algunos clase abstracta sellada, y cada constructor heredado de él clase de caso. Intentaremos usarlos exactamente como tipos algebraicos, describiendo todas las variables como pertenecientes al padre. clase abstracta sellada.
Las clases MsgLogined y MsgLogouted dentro de nuestro programa no tienen un ancestro común. La función de vista tenía que distribuirse entre diferentes clases del modelo para tener acceso a un tipo específico de mensaje. Esto tiene sus ventajas, que los partidarios de OO apreciarán: el código está agrupado de acuerdo con la lógica empresarial, todo lo relacionado con un caso de uso está cerca. Pero prefiero separar la vista en una función separada, cuyo desarrollo podría transferirse a otra persona.
Ahora implementemos el actualizador.
Actualizador de objetos (def actualización(modelo: Modelo)(msg: modelo.Mensaje): Modelo = (coincidencia de modelo (caso Cerrar sesión() => coincidencia de mensaje (caso Iniciar sesión(nombre) => Iniciar sesión(nombre)) caso Iniciar sesión(nombre) => coincidencia de mensaje ( caso Cerrar sesión() => Cerrar sesión() caso Saludar() => modelo ) ) ) )
Aquí utilizamos tipos dependientes de la ruta para describir el tipo del segundo argumento a partir del valor del primero. Para que Scala acepte tales dependencias, las funciones deben describirse en forma curry, es decir, como una función del primer argumento, que devuelve una función del segundo argumento. Desafortunadamente, Scala no realiza muchas comprobaciones de tipos en este momento para las cuales el compilador tenga suficiente información.
Ahora demos una implementación completa del modelo y veamos.
La clase de caso Logoouted() extiende el modelo ( tipo Mensaje = MsgLogouted anula def view() : Vista = nueva Vista ( anula def run() = ( println("Ingrese nombre ") val nombre = scala.io.StdIn.readLine() Iniciar sesión (nombre) ) ) clase de caso Iniciado sesión (nombre: Cadena) extiende el modelo (tipo Mensaje = MsgLogined override def view() : Vista = nueva Vista ( override def run() = ( println(s"Hola, $nombre") println ( "Cadena vacía para cerrar sesión, no vacía para saludar".) scala.io.StdIn.readLine() match ( case "" => Cerrar sesión() case _ => Greet() ) ) ) vista de clase abstracta ( def run() : Msg) Visor de objetos (def vista(modelo: Modelo): Vista = (model.view()))
El tipo de retorno de una función de vista depende de la instancia de su argumento. Pero para la implementación se recurre al modelo.
La aplicación creada así se inicia así:
Objeto principal ( import scala.annotation.tailrec @tailrec def proceso(m: Modelo) ( val msg = Viewer.view(m).run() proceso(Updater.update(m)(msg)) ) def principal(args: Matriz) = (proceso (cerrado sesión ())))
Por tanto, el código del sistema de ejecución no sabe nada sobre la estructura interna de los modelos y tipos de mensajes, pero el compilador puede comprobar que el mensaje coincide con el modelo actual.
Aquí no necesitábamos todas las capacidades proporcionadas por los tipos dependientes de la ruta. Aparecerán propiedades interesantes si trabajamos en paralelo con varias instancias de sistemas Model-Updater-View, por ejemplo, al simular un mundo de múltiples agentes (la vista entonces representaría la influencia del agente en el mundo y la recepción de retroalimentación). En este caso, el compilador comprobó que el mensaje fue procesado exactamente por el agente al que estaba destinado, a pesar de que todos los agentes son del mismo tipo.
Los tipos algebraicos se pueden implementar de la misma manera que en Scala: una clase abstracta corresponde a un tipo y los descendientes concretos corresponden a constructores (llamémoslos "clases constructoras", para no confundirlos con los constructores ordinarios de C++) del sistema algebraico. tipo.
C++ admite tipos dependientes de la ruta, pero el compilador no puede usar el tipo en abstracto sin conocer el tipo real al que está asociado. Por lo tanto, es imposible implementar Model-Updater-View con su ayuda.
Pero C++ tiene un poderoso sistema de plantillas. La dependencia del tipo del valor del modelo se puede ocultar en un parámetro de plantilla de una versión especializada del sistema ejecutivo.
Procesador de estructura (procesador constante virtual *next() const = 0;); plantilla
Describimos un sistema de ejecución abstracto, con un único método para hacer lo que sea necesario y devolver un nuevo sistema de ejecución adecuado para la siguiente iteración. La versión específica tiene un parámetro de plantilla y estará especializada para cada "clase de constructor" del modelo. Es importante aquí que todas las propiedades del tipo CurModel se verifiquen durante la especialización de la plantilla con un parámetro de tipo específico, y en el momento de compilar la plantilla en sí, no es necesario describirlas (aunque es posible usando conceptos u otras formas de implementar clases de tipos). Scala también tiene un sistema bastante potente de tipos parametrizados, pero comprueba las propiedades de los tipos de parámetros durante la compilación del tipo parametrizado. Allí, la implementación de dicho patrón es difícil, pero posible gracias al soporte de clases de tipos.
Describamos el modelo.
Modelo de estructura ( virtual ~Model() (); virtual const Procesador *procesador() const = 0; ); struct Iniciado sesión: modelo público ( struct Mensaje ( const modelo virtual * proceso (const iniciado sesión * m) const = 0; virtual ~Message() (); struct Cerrar sesión: mensaje público ( const Modelo * proceso (const iniciado sesión * m) const; struct Saludo: Mensaje público ( const Modelo * proceso (const Iniciado sesión * m) const; ); const std::string nombre;
(este);
); );
Modelo constante * Cerrado de sesión::Inicio de sesión::proceso(const Cerrado de sesión * m) const (eliminar m; devolver nuevo Cerrado de sesión(nombre); ); const Modelo * Iniciado sesión::Cerrar sesión::proceso(const Iniciar sesión * m) const ( eliminar m; devolver nuevo Cerrar sesión(); ); const Modelo * Iniciado sesión::Saludo::proceso(const Iniciado sesión * m) const ( return m; );
Ahora juntemos todo lo relacionado con la vista, incluidas las entidades internas de los modelos.
Plantilla
Y finalmente, escribamos principal.
Int main(int argc, char ** argv) (const Procesador * p = nuevo ProcessorImpl
Parte similar del código.
clase abstracta Vista ( def run(): Mensaje ) clase abstracta Procesador ( def next(): Procesador; ) clase abstracta sellada Modelo ( def procesador(): Procesador ) clase abstracta sellada LoginedMessage clase de caso Logout() extiende la clase de caso LoginedMessage Greet( ) extiende la clase de caso LoginedMessage Logined (nombre de valor: Cadena) extiende el Modelo ( anula el procesador def(): Procesador = nuevo ProcessorImpl(this) ) clase abstracta sellada Clase de caso LogoutedMessage Inicio de sesión (nombre: Cadena) extiende la clase de caso LoginedMessage Logouted() extiende el Modelo ( anular def procesador(): Procesador = nuevo ProcessorImpl(this) ) objeto Principal ( import scala.annotation.tailrec @tailrec def proceso(p: Procesador) ( proceso(p.next()) ) def principal(args: Matriz) = ( proceso (nuevo procesadorImpl (cerrado sesión ()))))
Clase ProcessorImpl(modelo: M)(actualizador implícito: (M, Mensaje) => Modelo, vista: M => Ver) extiende el Procesador ( def next(): Procesador = ( val v = vista(modelo) val msg = v. ejecutar() val nuevoModelo = actualizador(modelo,msg) nuevoModelo.procesador() ) )
Aquí vemos nuevos parámetros misteriosos. (actualizador implícito: (M, Mensaje) => Modelo, vista: M => Ver). La palabra clave implícita significa que al llamar a esta función (más precisamente, al constructor de la clase), el compilador buscará objetos de tipos adecuados marcados como implícitos en el contexto y los pasará como parámetros apropiados. Este es un concepto bastante complejo, una de cuyas aplicaciones es la implementación de clases de tipos. Aquí le prometen al compilador que para implementaciones específicas del modelo y mensaje, nosotros proporcionaremos todas las funciones necesarias. Ahora cumplamos esta promesa.
Actualizadores de objetos (implícito def logoutedUpdater(modelo: Logoouted, msg: LogoutedMessage): Modelo = ((modelo, msg) match ( case (Logouted(), Login(name)) => Logined(name) ) ) implícito def viewLogouted(modelo : Cerrado de sesión) = nueva Vista ( anular def run() : LogoutedMessage = ( println("Ingrese nombre ") val nombre = scala.io.StdIn.readLine() Iniciar sesión(nombre) ) ) implícito def loginedUpdater(modelo: Iniciado sesión, msg : LoginedMessage): Modelo = ( (modelo, mensaje) coincidencia ( caso (Iniciado sesión (nombre), Cerrar sesión ()) => Cerrar sesión () case (Iniciar sesión (nombre), Saludar ()) => modelo)) implícito def viewLogined ( modelo: Iniciado sesión) = nueva Vista (val nombre = model.name override def run() : LoginedMessage = ( println(s"Hola, $nombre") println("Cadena vacía para cerrar sesión, no vacía para saludar.") scala.io .StdIn.readLine() coincidencia ( case "" => Cerrar sesión() case _ => Greet() ) ) ) importar actualizadores._
Modelo de datos = forall m. (Actualizable m, Visualizable m) => Modelo m clase Actualizable m donde datos Mensaje m:: * actualización:: m -> (Mensaje m) -> Modelo clase (Actualizable m) => Visible m donde vista:: m -> (Ver (Mensaje m)) datos Cerrado sesión = Datos cerrados sesión Cerrado = Cerrado sesión Cadena
Intenté separar el actualizador y la vista en la medida de lo posible, así que creé dos clases de tipos diferentes, pero hasta ahora no ha funcionado bien.
La implementación del actualizador es simple.
Instancia actualizable Cerrado sesión donde datos Mensaje Cerrado sesión = Actualización de cadena de inicio de sesión Cerrado sesión (nombre de inicio de sesión) = Modelo (nombre de inicio de sesión) instancia Actualizable Cerrado sesión donde datos Mensaje Cerrado sesión = Cerrar sesión | Saludo actualización m Cerrar sesión = Modelo Cerrar sesión actualizar m Saludo = Modelo m
Tuve que arreglar IO como Vista. Los intentos de hacerlo más abstracto complicaron mucho todo y aumentaron el acoplamiento del código: el tipo de modelo debe saber qué Vista vamos a utilizar.
Importar System.IO tipo Ver a = IO una instancia Visible Cerrar sesión donde ver Cerrar sesión = do putStr "Iniciar sesión: " hFlush stdout fmap Iniciar sesión getLine instancia Visible Cerrar sesión donde ver (Nombre de inicio de sesión) = do putStr $ "Hola " ++ nombre ++ " !\n" hSalida estándar de descarga l<- getLine
pure $ if l == ""
then
Logout
else
Greeting
Bueno, el entorno ejecutable difiere poco del similar en Idris.
RunMUV::Model -> IO a runMUV (Modelo m) = hacer mensaje<- view m runMUV $ update m msg main:: IO () main = runMUV (Model Logouted)
Editar el archivo URL.py aplicaciones cuenta:
desde django.conf.urls importar URL desde . importar vistas urlpatterns = [# vista de inicio de sesión anterior # url(r"^iniciar sesión/$", vistas.user_login, nombre="iniciar sesión"),# URL de inicio de sesión/cierre de sesión url(r"^login/$" , "django.contrib.auth.views.login", nombre="iniciar sesión" ), url(r"^cerrar sesión/$" , "django.contrib.auth.views.cerrar sesión", nombre="cerrar sesión" ), url(r"^cerrar sesión-luego-iniciar sesión/$" , "django.contrib.auth.views.logout_then_login", nombre="logout_then_login" ), ]Hemos comentado la plantilla de URL para la vista. inicio de sesión de usuario, creado anteriormente para usar la vista acceso Django.
Cree un nuevo directorio en el directorio de plantillas de la aplicación. cuenta y nómbralo registro. Cree un nuevo archivo en un nuevo directorio, asígnele un nombre iniciar sesión.html
(% extiende "base.html" %) (% título del bloque %) Iniciar sesión (% endblock %) (% contenido del bloque %)
Su nombre de usuario y contraseña no coinciden. Inténtelo de nuevo.
(%demás%)Por favor, utilice el siguiente formulario para iniciar sesión:
(% terminara si %)Esta plantilla de inicio de sesión es muy similar a la creada anteriormente. Usos de Django Formulario de autenticación, situado en django.contrib.auth.forms. Este formulario intenta autenticar al usuario y genera un error de validación si el nombre de usuario era incorrecto. En este caso, podemos buscar errores usando el comando (% if form.errors %). Tenga en cuenta que hemos agregado un elemento oculto. para enviar el valor de una variable llamada próximo.
Parámetro próximo debe ser una URL. Si se especifica este parámetro, una vez que el usuario inicia sesión, se le redirige a la URL especificada.
Ahora crea una plantilla logged_out.html dentro del directorio de plantillas registro y pega el siguiente código en él:
(% extiende "base.html" %) (% título del bloque %) Cerrado sesión (% endblock %) (% contenido del bloque %)
Ha sido desconectado exitosamente. Puedes iniciar sesión nuevamente.
(%bloque final%)Esta es la plantilla que se mostrará después de que el usuario inicie sesión.
Después de agregar las plantillas de URL y las plantillas para las vistas de entrada y salida, el sitio está listo para iniciar sesión utilizando las vistas de autenticación de Django.
Tenga en cuenta que la presentación cerrar sesión_luego_iniciar sesión, incluido en nuestro configuración de URL, no necesita una plantilla ya que redirige a iniciar sesión ver.
Ahora creemos una nueva vista para mostrar un panel para el usuario, de modo que sepamos cuándo el usuario inicia sesión en su cuenta. Abre el archivo vistas.py aplicaciones cuenta y agrégale el siguiente código:
desde django.contrib.auth.decorators importar login_required @login_required panel def (solicitud): return render(solicitud, "cuenta/dashboard.html", ("sección": "tablero"))Agregamos un decorador a nuestra vista. Necesario iniciar sesión marco de autenticación. Decorador Necesario iniciar sesión comprueba si el usuario actual está autenticado. Si el usuario está autenticado, se ejecutará el envío; Si el usuario no está autenticado, será redirigido a la página de inicio de sesión.
También definimos una variable sección. Usaremos esta variable para rastrear qué sección del sitio está viendo el usuario.
Ahora necesita crear una plantilla para la vista del panel. Crea un nuevo archivo dentro de plantillas/cuenta plantillas/cuenta/ y nómbralo tablero.html :
(% extiende "base.html" %) (% título del bloque %) Panel de control (% endblock %) (% contenido del bloque %)
Bienvenido a su panel de control.
(%bloque final%)Luego agregue el siguiente patrón de URL para este archivo de cambio URL.py aplicaciones cuenta:
Patrones de URL = [ # ... url(r"^$", vistas.tablero, nombre="tablero"), ]
Ahora edite el archivo configuración.py:
de django.core.urlresolvers importar Reverse_lazy LOGIN_REDIRECT_URL = Reverse_lazy("tablero") LOGIN_URL = Reverse_lazy("iniciar sesión") LOGOUT_URL = Reverse_lazy("cerrar sesión")Ahora vamos a agregar enlaces de inicio y cierre de sesión a nuestra plantilla básica.
Para hacer esto, es necesario determinar si el usuario actual ha iniciado sesión o no para mostrar un enlace correspondiente al estado del usuario actual. El usuario actual se especifica en Solicitud HTTP objeto de la clase intermedia de autenticación. Se puede acceder usando solicitud.usuario. La solicitud encontrará un objeto de usuario, incluso si el usuario no está autenticado. Usuario no autenticado, especificado en la solicitud como instancia Usuario anónimo. La mejor manera de verificar el estado de autenticación del usuario actual es llamar solicitud.usuario.is_authenticado()
Editar en la plantilla base.html
Como puede ver, el menú del sitio se muestra solo para usuarios autenticados. También revisamos la sección actual para agregar el atributo de clase seleccionado al elemento correspondiente
Abra http://127.0.0.1:8000/account/login/ en su navegador. Deberías ver una página de inicio de sesión. Ingrese un nombre de usuario y contraseña válidos. Verá lo siguiente:
Puedes ver que la sección Mi panel está resaltada con CSS ya que tiene una clase. seleccionado. Dado que el usuario ha sido autenticado, el nombre de usuario se muestra en el lado derecho del encabezado. Haga clic en el enlace Cerrar sesión. Verá la siguiente página:
En esta página puede ver que el usuario ha cerrado sesión y por lo tanto ya no se muestra el menú del sitio web. El enlace en el lado derecho del encabezado ahora muestra Acceso.
Si ve la página de cierre de sesión del sitio de administración de Django en lugar de su propia página de cierre de sesión, verifique la configuración de INSTALLED_APPS y asegúrese de que django.contrib.admin es despues cuenta. Ambas plantillas están en la misma ruta relativa y el cargador de plantillas de Django utilizará la primera que encuentre.
Muchas personas comienzan a escribir un proyecto para trabajar con una sola tarea, sin implicar que pueda convertirse en un sistema de gestión multiusuario, por ejemplo, contenido o, Dios no lo quiera, producción. Y todo parece genial y genial, todo funciona, hasta que empiezas a comprender que el código que se escribe consiste enteramente en muletas y código duro. El código se mezcla con diseño, consultas y muletas, a veces incluso ilegible. Surge un problema urgente: al agregar nuevas funciones, hay que jugar con este código durante mucho tiempo, recordando "¿qué estaba escrito allí?" y maldicete en el pasado.Es posible que incluso hayas oído hablar de patrones de diseño e incluso hayas hojeado estos maravillosos libros:
Este artículo será útil principalmente para principiantes. En cualquier caso, espero que en un par de horas pueda hacerse una idea de la implementación del patrón MVC, que subyace a todos los marcos web modernos, y también obtener "alimento" para una mayor reflexión sobre "cómo hazlo." Al final del artículo hay una selección de enlaces útiles que también le ayudarán a comprender en qué consisten los frameworks web (además de MVC) y cómo funcionan.
Es poco probable que los programadores PHP experimentados encuentren algo nuevo en este artículo, ¡pero sus comentarios y comentarios sobre el texto principal serán de gran ayuda! Porque Sin teoría la práctica es imposible, y sin práctica la teoría es inútil, luego primero habrá un poco de teoría y luego pasaremos a la práctica. Si ya está familiarizado con el concepto MVC, puede omitir la sección teórica e ir directamente a la práctica.
Veamos el diagrama conceptual del patrón MVC (en mi opinión, este es el diagrama más exitoso que he visto):
En la arquitectura MVC, el modelo proporciona los datos y las reglas de lógica empresarial, la vista es responsable de la interfaz de usuario y el controlador proporciona la interacción entre el modelo y la vista.
Un flujo típico de una aplicación MVC se puede describir de la siguiente manera:
Vista- se utiliza para configurar la visualización externa de los datos recibidos del controlador y del modelo.
Las vistas contienen marcado HTML y pequeñas inserciones de código PHP para recorrer, formatear y mostrar datos.
No debe acceder directamente a la base de datos. Esto es lo que deberían hacer los modelos.
No debería funcionar con datos obtenidos a partir de una solicitud de usuario. Esta tarea debe ser realizada por el controlador.
Puede acceder directamente a las propiedades y métodos de un controlador o modelos para obtener datos listos para la salida.
Las vistas generalmente se dividen en una plantilla común, que contiene marcas comunes a todas las páginas (por ejemplo, un encabezado y pie de página) y partes de la plantilla que se utilizan para mostrar la salida de datos del modelo o mostrar formularios de entrada de datos.
Controlador- el pegamento que conecta modelos, vistas y otros componentes en una aplicación funcional. El responsable del tratamiento es responsable de procesar las solicitudes de los usuarios. El controlador no debe contener consultas SQL. Es mejor mantenerlos en modelos. El controlador no debe contener HTML ni otro tipo de marcado. Vale la pena ponerlo a la vista.
En una aplicación MVC bien diseñada, los controladores suelen ser muy delgados y contienen sólo unas pocas docenas de líneas de código. No se puede decir lo mismo de los Inspectores Gordos Estúpidos (SFC) en CMS Joomla. La lógica del controlador es bastante típica y la mayor parte se transfiere a clases base.
Los modelos, por el contrario, son muy gruesos y contienen la mayor parte del código relacionado con el procesamiento de datos, porque La estructura de datos y la lógica empresarial que contiene suelen ser bastante específicas de una aplicación en particular.
Espero que ya hayas notado que diferentes sitios pueden tener formatos completamente diferentes para construir la barra de direcciones. Cada formato puede mostrar la arquitectura de una aplicación web. Aunque no siempre es así, en la mayoría de los casos es un hecho claro.
Consideremos dos opciones para la barra de direcciones, que muestran algo de texto y un perfil de usuario.
Primera opción:
Segunda opción:
Puede ver el enfoque de múltiples puntos de contacto en los foros de phpBB. La navegación por el foro se realiza mediante un script. verforo.php, ver tema vía vertopic.php etc. El segundo enfoque, con acceso a través de un único archivo de script físico, se puede ver en mi CMS MODX favorito, donde todos los accesos pasan por index.php.
Estos dos enfoques son completamente diferentes. El primero es típico del patrón Page Controller y el segundo enfoque lo implementa el patrón Front Controller. El controlador de página es bueno para sitios con una lógica bastante simple. A su vez, el controlador de solicitudes consolida todas las actividades de procesamiento de solicitudes en un solo lugar, lo que le brinda capacidades adicionales que pueden permitirle implementar tareas más complejas que las que normalmente resuelve el controlador de páginas. No entraré en detalles de la implementación del controlador de página, solo diré que en la parte práctica será el controlador de solicitudes (algo similar) el que se desarrollará.
Por ejemplo, para una página normal que muestra un formulario de contacto, la URL podría verse así:
http://www.example.com/contacts.php?action=feedback
Código de procesamiento aproximado en este caso:
cambiar ($_GET ["acción"]) (caso "acerca de": require_once ("acerca de.php"); // página "Acerca de nosotros" romper ; caso "contactos": require_once ("contactos.php");// página "Contactos" romper ; caso "comentarios": require_once ("comentarios.php");
// página "Comentarios"
romper ;
predeterminado: require_once ("página404.php"); // salto de página "404"; )
Creo que casi todo el mundo ha hecho esto antes.
Con un motor de enrutamiento de URL, puede configurar su aplicación para aceptar solicitudes como esta para mostrar la misma información:
http://www.example.com/contacts/feedback
Ahora tenemos suficientes conocimientos teóricos para pasar a la práctica.
2. Practica index.php Primero, creemos la siguiente estructura de archivos y carpetas: De cara al futuro, diré que las clases principales Modelo, Vista y Controlador se almacenarán en la carpeta principal. Sus hijos se almacenarán en los directorios de controladores, modelos y vistas. Archivo
Iremos de forma secuencial; Abramos el archivo index.php y rellénelo con el siguiente código:
ini_set("display_errors", 1); require_once "aplicación/bootstrap.php";
No debería haber ninguna pregunta aquí.
A continuación, pasemos inmediatamente a la driza. De cara al futuro, diré que las clases principales Modelo, Vista y Controlador se almacenarán en la carpeta principal.:
require_once "núcleo/model.php"; require_once "núcleo/view.php"; require_once "núcleo/controlador.php"; require_once "núcleo/ruta.php"; Ruta::inicio(); //iniciar el enrutador
Las primeras tres líneas incluirán archivos del kernel que actualmente no existen. Las últimas líneas incluyen el archivo con la clase de enrutador y lo lanzan para su ejecución llamando al método de inicio estático.
Colocaremos la ruta en un archivo separado. ruta.php al directorio principal. En este archivo describiremos la clase Ruta, que ejecutará métodos de controlador, que a su vez generarán la vista de página.
Contenido del archivo route.php
ruta de clase ( estático inicio de función() ( // controlador y acción predeterminada$controller_name = "Principal"; $nombre_acción = "índice"; $rutas = explotar("/" , $_SERVER ["REQUEST_URI" ]); //obtiene el nombre del controlador if (!empty ($rutas)) ( $nombre_controlador = $rutas ; ) //obtiene el nombre de la acción if (!empty ($rutas )) ( $nombre_acción = $rutas ; ) // agregar prefijos$nombre_modelo = "Modelo_".$nombre_controlador; $nombre_controlador = "Controlador_".$nombre_controlador; $nombre_acción = "acción_" .$nombre_acción ; // conecta el archivo con la clase de modelo (puede que no haya un archivo de modelo)$model_file = strtolower($model_name)..php"; $model_path = "aplicación/modelos/" .$model_file; if (file_exists($model_path)) (incluye "aplicación/modelos/".$model_file;) // conecta el archivo con la clase de controlador$controller_file = strtolower($controller_name )..php"; $controller_path = "aplicación/controladores/" .$controller_file; if (file_exists($controller_path)) (incluye "aplicación/controladores/".$controller_file;) else ( /* sería correcto lanzar una excepción aquí, pero para simplificarlo, redireccionaremos inmediatamente a la página 404 */ Ruta::ErrorPage404(); ) // crear un controlador$controlador = nuevo $nombre_controlador; $acción = $nombre_acción; si (method_exists($controlador, $acción)) ( // llama a la acción del controlador$controlador ->$acción(); ) demás ( // también sería más prudente lanzar una excepción aquí Ruta::ErrorPage404(); ) ) función PáginaError404() ($host = "http://" .$_SERVER ["HTTP_HOST" ]."/" ; encabezado("HTTP/1.1 404 no encontrado"); encabezado("Estado: 404 No encontrado"); encabezado("Ubicación:".$host. "404"); ) )
El elemento de matriz global $_SERVER["REQUEST_URI"] contiene la dirección completa a la que se comunicó el usuario.
Por ejemplo: ejemplo.ru/contacts/feedback
Usando la función explotar La dirección se divide en componentes. Como resultado, obtenemos el nombre del controlador, en el ejemplo dado, este es controlador contactos y el nombre de la acción, en nuestro caso - comentario.
A continuación, se conectan el archivo del modelo (puede que falte el modelo) y el archivo del controlador, si lo hay, y finalmente, se crea una instancia del controlador y se llama a la acción, nuevamente, si se describió en la clase del controlador.
Así, al dirigirse a, por ejemplo, la dirección:
ejemplo.com/portfolio
o
ejemplo.com/portfolio/index
El enrutador realizará las siguientes acciones:
Permítanme recordarles que contendrán clases base, que ahora comenzaremos a escribir.
Contenido del archivo modelo.php
modelo de clase ( público función obtener_datos() (
}
}
La clase modelo contiene un único método de recuperación de datos vacío, que se anulará en las clases descendientes. Cuando creemos clases descendientes todo quedará más claro.
Contenido del archivo ver.php
vista de clase (
//público $template_view; // aquí puede especificar la vista general predeterminada.
función generar ( $content_view, $template_view, $datos = nulo)
{
/* if(is_array($data)) ( // convierte elementos de la matriz en variables extract($data); ) */ incluya "aplicación/vistas/".$template_view; ) )
No es difícil adivinar que el método generar destinado a formar una vista. Se le pasan los siguientes parámetros:
En nuestro caso, la plantilla general contendrá encabezado, menú, barra lateral y pie de página, y el contenido de la página estará contenido en un formulario separado. Nuevamente, esto se hace por simplicidad.
Contenido del archivo controlador.php
controlador de clase ( modelo $ público; vista pública $; función __construcción() ($this ->vista = nueva Vista(); ) ) )
Método índice_acción- esta es la acción llamada por defecto; la anularemos al implementar clases descendientes.
En la figura anterior, el archivo está resaltado por separado. plantilla_vista.php es una plantilla que contiene marcas comunes a todas las páginas. En el caso más sencillo podría verse así:
<html lang="ru" >
<cabeza >
<metacharset="utf-8"> <título > hogartítulo >
cabeza >
<cuerpo >
$vista_contenido; ?> cuerpo >
HTML >
Para darle al sitio un aspecto presentable, creamos una plantilla CSS y la integramos en nuestro sitio cambiando la estructura del marcado HTML y conectando archivos CSS y JavaScript:
<enlace rel ="hoja de estilo" tipo ="text/css" href ="/css/style.css" />
<script src="/js/jquery-1.6.2.js" tipo="texto/javascript" >guión >
Al final del artículo, en la sección “Resultado”, hay un enlace a un repositorio de GitHub con un proyecto en el que se han dado pasos para integrar una plantilla sencilla.
Revisamos el archivo de vista general anteriormente. Considere el archivo de contenido vista_principal.php:
<h1 >¡Bienvenido!h1 >
<p>
<img src="/images/office-small.jpg" align="izquierda" >
<un href = "/" > EQUIPO OLOLOSHAun >- un equipo de especialistas de primer nivel en el campo del desarrollo de sitios web con muchos años de experiencia en la colección de máscaras mexicanas, estatuas de bronce y piedra de la India y Ceilán, bajorrelieves y esculturas creadas por maestros de África Ecuatorial hace cinco o seis siglos. ..p>
Contiene marcado simple sin llamadas PHP.
Para mostrar la página principal, puede utilizar una de las siguientes direcciones:
) ) La clase de controlador del modelo está contenida en el archivo, aquí está su código:
controlador_cartera.php
función __construcción() ( clase Controller_Portfolio extiende el controlador ( función índice_acción() ($this ->modelo = nuevo Model_Portfolio(); $this ->vista = nueva Vista(); )
$datos = $this ->modelo->get_data(); $this ->view->generate("portfolio_view.php", "template_view.php", $datos); ) ) a una variable datos se escribe la matriz devuelta por el método obtener datos
que vimos antes. generar Luego, esta variable se pasa como parámetro del método.
, que también contiene: el nombre del archivo con la plantilla general y el nombre del archivo que contiene la vista con el contenido de la página. La vista que contiene el contenido de la página está en el archivo..
Todos los proyectos de la siguiente tabla son ficticios, así que ni siquiera intentes seguir los enlaces proporcionados. | Año | Proyecto | " .$fila ["Año" ]." | " .$fila ["Sitio" ]." | " .$fila ["Descripción" ]." | " ; }