Vamos a ver como hacer un CRUD con Zend Framework 2 (Create,Read,Update,Delete) de una tabla de la BD pruebas llamada usuarios con las columnas id,email,password,nombre,apellido. Se van ha abordar los siguientes temas:
- Todos los puntos anteriores
- Consultas y operaciones a la base de datos
- Mensajes Flash
- Cifrado de datos Bcrypt
Vamos a crear un controlador llamado CrudController.php el cual tenemos que añadir al apartado invokables del fichero config del modulo.
También añadiremos una nueva ruta para ese controlador.
El fichero config quedará asi:
<?php return array( 'controllers'=>array( 'invokables'=>array( 'Modulo\Controller\Pruebas'=>'Modulo\Controller\PruebasController', 'Modulo\Controller\Crud'=>'Modulo\Controller\CrudController' ), ), 'router'=>array( 'routes'=>array( 'modulo'=>array( 'type'=>'Segment', 'options'=>array( 'route' => '/modulo[/[:action]]', 'constraints' => array( 'action' => '[a-zA-Z][a-zA-Z0-9_-]*', ), 'defaults' => array( 'controller' => 'Modulo\Controller\Pruebas', 'action' => 'index' ), ), ), //Nueva ruta para el nuevo controlador 'crud'=>array( 'type'=>'Segment', 'options'=>array( 'route' => '/crud[/[:action][/:id]]', 'constraints' => array( 'action' => '[a-zA-Z][a-zA-Z0-9_-]*', ), 'defaults' => array( 'controller' => 'Modulo\Controller\Crud', 'action' => 'index' ), ), ), ), ), //Cargamos el view manager 'view_manager' => array( 'display_not_found_reason' => true, 'display_exceptions' => true, 'doctype' => 'HTML5', 'not_found_template' => 'error/404', 'exception_template' => 'error/index', 'template_map' => array( 'numpaginacion' => __DIR__ . '/../view/layout/numpaginacion.phtml', 'layout/layout' => __DIR__ . '/../view/layout/layout.phtml', 'modulo/index/index' => __DIR__ . '/../view/pruebas/index/index.phtml', 'error/404' => __DIR__ . '/../view/error/404.phtml', 'error/index' => __DIR__ . '/../view/error/index.phtml', ), 'template_path_stack' => array( 'modulo' => __DIR__ . '/../view', ), ), );
El modelo, consultas y operaciones con la base de datos.
Las consultas a la base de datos se pueden hacer sin mayores problemas en los controladores, pero lo correcto en la arquitectura MVC es tener métodos aislados que se encarguen de interactuar con la base de datos.
Module/src/Modulo/Model/Entity/UsuariosModel.php
<?php namespace Modulo\Model\Entity; /* * Usamos el componente tablegateway que nos permite hacer consultas * utilizando una capa de abstracción, aremos las consultas sobre * una tabla que indicamos en el constructor */ use Zend\Db\TableGateway\TableGateway; /* * Usamos el componente Dd\Adapter que nos permite hacer consultas * convencionales en formato SQL así como para servir de conexión * para el componente SQL que nos provee de una capa de abstracción * mas potente que la que da tablagateway */ use Zend\Db\Adapter\Adapter; /* Usamos el componente SQL que nos permite realizar consultas utilizando métodos. */ use Zend\Db\Sql\Sql; /* Igual que el anterior pero solamente con la cláusula select */ use Zend\Db\Sql\Select; /* * Nos da algunas herramientas para trabajar con el resulset de las consultas, puede ser prescindible */ use Zend\Db\ResultSet\ResultSet; class UsuariosModel extends TableGateway{ private $dbAdapter; public function __construct(Adapter $adapter = null, $databaseSchema = null, ResultSet $selectResultPrototype = null){ //Conseguimos el adaptador $this->dbAdapter=$adapter; /*Al estar utilizando TableGateway necesitamos montar el constructor de la clase padre al que le pasamos como parámetros principales la tabla de la base de datos que corresponde a este modelo y le pasamos el adaptador de conexión */ return parent::__construct('usuarios', $this->dbAdapter, $databaseSchema,$selectResultPrototype); } //CREAMOS LOS METODOS DEL MODELO PARA EL CRUD public function getUsuarios(){ //Las consultas se pueden hacer de 4 formas: /* Utilizando una query en modo ejecución directamente desde el adaptador: $consulta=$this->dbAdapter->query("SELECT * FROM usuarios",Adapter::QUERY_MODE_EXECUTE); $datos=$consulta->toArray(); */ /* Creando una sentencia en el adaptado y ejecutándola: $consulta=$this->dbAdapter->createStatement("SELECT * FROM usuarios"); $datos= $consulta->execute(); */ /* Usando el componente SQL le pasamos el adaptador y utilizamos los métodos que nos ofrece: $sql = new Sql($this->dbAdapter); $select = $sql->select(); $select->from('usuarios'); Aquí le indicamos que convierta las llamadas a los métodos en una sentencia sql y que la ejecute $statement = $sql->prepareStatementForSqlObject($select); $datos=$statement->execute(); */ //Utilizando las sentencias básicas de table gateway: $select=$this->select(); $datos=$select->toArray(); return $datos; /*Todos estos métodos permiten hacer consultas mucho mas complejas de lo que está aquí documentado, para más información acceder a la documentación oficial.*/ } public function getUnUsuario($id){ $sql = new Sql($this->dbAdapter); $select = $sql->select(); $select->columns(array('email','password','nombre', 'apellido')) ->from('usuarios') ->where(array('id' => $id)); $selectString = $sql->getSqlStringForSqlObject($select); $execute = $this->dbAdapter->query($selectString, Adapter::QUERY_MODE_EXECUTE); $result=$execute->toArray(); return $result[0]; } public function addUsuario($email,$password,$nombre,$apellido){ $consulta=$this->dbAdapter->query("SELECT count(email) as count FROM usuarios WHERE email='$email'",Adapter::QUERY_MODE_EXECUTE); $datos=$consulta->toArray(); if($datos[0]["count"]==0){ $insert=$this->insert(array( "email" => $email, "password" => $password, "nombre" => $nombre, "apellido" => $apellido )); }else{ $insert=false; } return $insert; } public function deleteUsuario($id){ $delete=$this->delete(array("id"=>$id)); return $delete; } public function updateUsuario($id,$email,$password,$nombre,$apellidos){ $update=$this->update(array( "email" => $email, "password" => $password, "nombre" => $nombre, "apellido" => $apellidos ), array("id"=>$id)); return $update; } }
El controlador, requests, mensajes flash, encriptación bcrypt.
<?php namespace Modulo\Controller; use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\ViewModel; //Componentes de validación use Zend\Validator; use Zend\I18n\Validator as I18nValidator; //Adaptador de la db use Zend\Db\Adapter\Adapter; //Componente para cifrar contraseñas use Zend\Crypt\Password\Bcrypt; //Incluir modelos use Modulo\Model\Entity\UsuariosModel; //Incluir formularios use Modulo\Form\AddUsuario; class CrudController extends AbstractActionController{ private $dbAdapter; public function __construct(){ } public function indexAction(){ /*Siempre que necesitemos trabajar con el modelo hay que hacer esto solamente le estamos pasando al modelo la conexión */ $this->dbAdapter=$this->getServiceLocator()->get('Zend\Db\Adapter'); $usuarios=new UsuariosModel($this->dbAdapter); $lista=$usuarios->getUsuarios(); return new ViewModel( array( "lista"=>$lista )); } public function addAction(){ $form=new AddUsuario("form"); $vista=array("form"=>$form); if($this->getRequest()->isPost()) { $form->setData($this->getRequest()->getPost()); if($form->isValid()){ //Cargamos el modelo $this->dbAdapter=$this->getServiceLocator()->get('Zend\Db\Adapter'); $usuarios=new UsuariosModel($this->dbAdapter); //Recogemos los datos del formulario $email=$this->request->getPost("email"); /* Ciframos la contraseña para la maxima seguridad le aplicamos un salt y hacemos el hash del hash 5 veces (por defecto vienen mas de 10 pero es mas lento) */ $bcrypt = new Bcrypt(array( 'salt' => 'aleatorio_salt_pruebas_victor', 'cost' => 5)); $securePass = $bcrypt->create($this->request->getPost("password")); $password=$securePass; $nombre=$this->request->getPost("nombre"); $apellido=$this->request->getPost("apellido"); //Insertamos en la bd $insert=$usuarios->addUsuario($email, $password, $nombre, $apellido); //Mensajes flash $this->flashMessenger()->addMenssage("mensaje"); if($insert==true){ $this->flashMessenger()->setNamespace("add_correcto")->addMessage("Usuario añadido correctamente"); return $this->redirect()->toUrl($this->getRequest()->getBaseUrl().'/crud/'); }else{ $this->flashMessenger()->setNamespace("duplicado")->addMessage("Usuario duplicado mete otro"); return $this->redirect()->refresh(); } }else{ $err=$form->getMessages(); $vista=array("form"=>$form,'url'=>$this->getRequest()->getBaseUrl(),"error"=>$err); } } return new ViewModel($vista); } public function verAction(){ $id=$this->params()->fromRoute("id",null); $this->dbAdapter=$this->getServiceLocator()->get('Zend\Db\Adapter'); $usuarios=new UsuariosModel($this->dbAdapter); $usuario=$usuarios->getUnUsuario($id); if($usuario){ return new ViewModel( array( "id" => $id, "usuario" => $usuario )); }else{ return $this->redirect()->toUrl($this->getRequest()->getBaseUrl().'/crud/'); } } public function modificarAction(){ $id=$this->params()->fromRoute("id",null); $this->dbAdapter=$this->getServiceLocator()->get('Zend\Db\Adapter'); $usuarios=new UsuariosModel($this->dbAdapter); $usuario=$usuarios->getUnUsuario($id); $form=new AddUsuario("form"); $form->setData($usuario); $vista=array("form"=>$form); if($this->getRequest()->isPost()) { $form->setData($this->getRequest()->getPost()); if($form->isValid()){ //Recogemos los datos del formulario $email=$this->request->getPost("email"); $bcrypt = new Bcrypt(array( 'salt' => 'aleatorio_salt_pruebas_victor', 'cost' => 5)); $securePass = $bcrypt->create($this->request->getPost("password")); $password=$securePass; $nombre=$this->request->getPost("nombre"); $apellido=$this->request->getPost("apellido"); //Insertamos en la bd $update=$usuarios->updateUsuario($id,$email, $password, $nombre, $apellido); if($update==true){ $this->flashMessenger()->setNamespace("add_correcto")->addMessage("Usuario modificado correctamente"); return $this->redirect()->toUrl($this->getRequest()->getBaseUrl().'/crud/'); }else{ $this->flashMessenger()->setNamespace("duplicado")->addMessage("El usuario se ha modificado"); return $this->redirect()->refresh(); } }else{ $err=$form->getMessages(); $vista=array("form"=>$form,'url'=>$this->getRequest()->getBaseUrl(),"error"=>$err); } } return new ViewModel($vista); } public function eliminarAction(){ $id=$this->params()->fromRoute("id",null); $this->dbAdapter=$this->getServiceLocator()->get('Zend\Db\Adapter'); $usuarios=new UsuariosModel($this->dbAdapter); $delete=$usuarios->deleteUsuario($id); if($delete==true){ $this->flashMessenger()->setNamespace("eliminado")->addMessage("Usuario eliminado correctamente"); }else{ $this->flashMessenger()->setNamespace("eliminado")->addMessage("El usuario no a podido ser eliminado"); } return $this->redirect()->toUrl($this->getRequest()->getBaseUrl().'/crud/'); } }
Formulario para añadir y modificar.
<?php namespace Modulo\Form; use Zend\Form\Element; use Zend\Form\Form; use Zend\Form\Factory; class AddUsuario extends Form { public function __construct($name = null) { parent::__construct($name); $this->setInputFilter(new \Modulo\Form\AddUsuarioValidator()); $this->setAttributes(array( //'action' => $this->url.'/modulo/recibirformulario', 'action'=>"", 'method' => 'post' )); $this->add(array( 'name' => 'email', 'options' => array( 'label' => 'Correo: ', ), 'attributes' => array( 'type' => 'email', 'class' => 'input form-control', 'required'=>'required' ) )); $this->add(array( 'name' => 'password', 'options' => array( 'label' => 'Contraseña: ', ), 'attributes' => array( 'type' => 'password', 'class' => 'input form-control', 'required'=>'required' ) )); $this->add(array( 'name' => 'nombre', 'options' => array( 'label' => 'Nombre: ', ), 'attributes' => array( 'type' => 'text', 'class' => 'input form-control', 'required'=>'required' ) )); $this->add(array( 'name' => 'apellido', 'options' => array( 'label' => 'Apellidos: ', ), 'attributes' => array( 'type' => 'text', 'class' => 'input form-control', 'required'=>'required' ) )); $this->add(array( 'name' => 'submit', 'attributes' => array( 'type' => 'submit', 'value' => 'Enviar', 'title' => 'Enviar', 'class' => 'btn btn-success' ), )); } } ?>
Validar el formulario
<?php namespace Modulo\Form; use Zend\Form\Form; use Zend\InputFilter\InputFilter; use Zend\Validator; use Zend\I18n\Validator as I18nValidator; class AddUsuarioValidator extends InputFilter{ public function __construct(){ $this->add(array( 'name' => 'email', 'required' => true, 'filters' => array( array('name' => 'StringTrim'), ), 'validators' => array( array( 'name'=>'EmailAddress', 'options'=> array( 'messages' => array( \Zend\Validator\EmailAddress::INVALID_HOSTNAME=>'Email incorrecto', ), ), ), ))); $this->add(array( 'name' => 'nombre', 'required' => true, 'filters' => array( array('name' => 'HtmlEntities'), array('name' => 'StripTags'), array('name' => 'StringTrim'), ), 'validators' => array( array( 'name' => 'Alpha', 'options' => array( 'allowWhiteSpace'=>false, 'messages' => array( I18nValidator\Alpha::INVALID=>'El nombre solo puede estar formado por letras', I18nValidator\Alpha::NOT_ALPHA=>'El nombre solo puede estar formado por letras', I18nValidator\Alpha::STRING_EMPTY=>'El nombre no puede estar vacio', //I18nValidator\Alpha::NOT_ALNUM=>'Tu nombre esta mal', ), ), ), ))); $this->add(array( 'name' => 'apellido', 'required' => true, 'filters' => array( array('name' => 'HtmlEntities'), array('name' => 'StripTags'), array('name' => 'StringTrim'), ), 'validators' => array( array( 'name' => 'Alpha', 'options' => array( 'allowWhiteSpace'=>true, 'messages' => array( I18nValidator\Alpha::INVALID=>'Los apellido solo pueden estar formado por letras', I18nValidator\Alpha::NOT_ALPHA=>'Los apellido solo pueden estar formado por letras', I18nValidator\Alpha::STRING_EMPTY=>'Los apellido no pueden estar vacios', //I18nValidator\Alpha::NOT_ALNUM=>'Tu nombre esta mal', ), ), ), ))); $this->add(array( 'name' => 'password', 'required' => true, 'filters' => array( array('name' => 'HtmlEntities'), array('name' => 'StringTrim'), ) )); } }
Las vistas
Index.phtml
<h1>Crud en Zend Framework 2</h1> <?php $flash=$this->flashMessenger() ->setMessageOpenFormat('<div%s>') ->setMessageSeparatorString('') ->setMessageCloseString('</div>'); ?> <table class="table"> <tr> <td colspan="7"> <a class="btn btn-success" href="<?=$this->basePath()?>/crud/add">Añadir usuario</a> <?php echo $flash->render('add_correcto',array('btn', 'btn-success'));?> <?php echo $flash->render('eliminado',array('btn', 'btn-success'));?> </td> </tr> <tr> <th>ID</th> <th>Correo</th> <th>Contraseña</th> <th>Nombre y apellidos</th> <th>Ver</th> <th>Modificar</th> <th>Eliminar</th> </tr> <?php foreach($lista as $usuario){ ?> <tr style="background-color:<?php echo $this->cycle(array("#F0F0F0","#FFFFFF"))->next()?>"> <td><?=$usuario["id"]?></td> <td><?=$usuario["email"]?></td> <td><?=$usuario["password"]?></td> <td><?=$usuario["nombre"]." ".$usuario["apellido"]?></td> <td><a class="btn btn-success" href="<?=$this->basePath()?>/crud/ver/<?=$usuario["id"]?>">Ver</a></td> <td><a class="btn btn-primary" href="<?=$this->basePath()?>/crud/modificar/<?=$usuario["id"]?>">Modificar</a></td> <td><a class="btn btn-danger" href="<?=$this->basePath()?>/crud/eliminar/<?=$usuario["id"]?>">Eliminar</a></td> </tr> <?php } ?> </table>
Add.phtml
<h1>Añadir usuario - Crud en Zend Framework 2</h1> <hr/> <?php $flash=$this->flashMessenger() ->setMessageOpenFormat('<div%s>') ->setMessageSeparatorString('') ->setMessageCloseString('</div>'); ?> <?php $this->headTitle("Añadir usuarios"); $form = $this->form; $form->prepare(); $formLabel = $this->plugin('formLabel'); echo $this->form()->openTag($form); echo $flash->render('duplicado',array('btn', 'btn-danger')); ?> <div class="form_element"> <?php $email = $form->get('email'); echo $formLabel->openTag().$email->getOption('label')." "; echo $this->formInput($email); //echo $this->formElementErrors($email); if(isset($error["email"])){ echo $error["email"]['emailAddressInvalidHostname']; } echo $formLabel->closeTag(); ?> </div> <div class="form_element"> <?php $pass = $form->get('password'); echo $formLabel->openTag().$pass->getOption('label')." "; echo $this->formInput($pass); echo $this->formElementErrors($pass); echo $formLabel->closeTag(); ?> </div> <div class="form_element"> <?php $name = $form->get('nombre'); echo $formLabel->openTag().$name->getOption('label')." "; echo $this->formInput($name); echo $this->formElementErrors($name); echo $formLabel->closeTag(); ?> </div> <div class="form_element"> <?php $apellido = $form->get('apellido'); echo $formLabel->openTag().$apellido->getOption('label')." "; echo $this->formInput($apellido); echo $this->formElementErrors($apellido); echo $formLabel->closeTag(); ?> </div> <?php echo $this->formElement($form->get('submit')); echo $this->form()->closeTag(); ?>
Modificar.phtml
<h1>Modificar usuario - Crud en Zend Framework 2</h1> <hr/> <?php $flash=$this->flashMessenger() ->setMessageOpenFormat('<div%s>') ->setMessageSeparatorString('') ->setMessageCloseString('</div>'); ?> <?php $this->headTitle("Modificar usuarios"); $form = $this->form; $form->prepare(); $formLabel = $this->plugin('formLabel'); echo $this->form()->openTag($form); echo $flash->render('duplicado',array('btn', 'btn-danger')); ?> <div class="form_element"> <?php $email = $form->get('email'); echo $formLabel->openTag().$email->getOption('label')." "; echo $this->formInput($email); //echo $this->formElementErrors($email); if(isset($error["email"])){ echo $error["email"]['emailAddressInvalidHostname']; } echo $formLabel->closeTag(); ?> </div> <div class="form_element"> <?php $pass = $form->get('password'); echo $formLabel->openTag().$pass->getOption('label')." "; echo $this->formInput($pass); echo $this->formElementErrors($pass); echo $formLabel->closeTag(); ?> </div> <div class="form_element"> <?php $name = $form->get('nombre'); echo $formLabel->openTag().$name->getOption('label')." "; echo $this->formInput($name); echo $this->formElementErrors($name); echo $formLabel->closeTag(); ?> </div> <div class="form_element"> <?php $apellido = $form->get('apellido'); echo $formLabel->openTag().$apellido->getOption('label')." "; echo $this->formInput($apellido); echo $this->formElementErrors($apellido); echo $formLabel->closeTag(); ?> </div> <?php echo $this->formElement($form->get('submit')); echo $this->form()->closeTag(); ?>
ver.phtml
<h1>Crud en Zend Framework 2</h1> <h3>Viendo usuario <?=$id?></h3> <?php echo $usuario["email"]." - ".$usuario["nombre"]." - ".$usuario["apellido"]; ?> <hr/> <a class="btn btn-default" href="<?=$this->basePath("crud")?>">Volver</a>
Más información:
Documentación oficial de Zend Framework 2