lunes, 6 de octubre de 2014

Finalizando el objeto Menus y el uso de getServiceLocator

En la entrada anterior expliqué tanto el listado como la creación de registros de nuestro módulo "Menús", para finalizarlo tan solo debemos añadir las acciones con sus correspondientes vistas a la edición y el borrado.

Previo a incluir los nuevos métodos en nuestro controlador, no podemos olvidar que hemos de espeficar a las rutas de acceso de nuestro módulo esperará un parámetro ID. Para ello modificaremos el fichero module.config.php del módulo Menus:

Contenido del fichero /module/Menus/config/module.config.php:

<?php
return array(
    'controllers' => array(
        'invokables' => array(
            'Menus\Controller\Index' => 'Menus\Controller\Index',
            'Menus\Controller\Menus' => 'Menus\Controller\MenusController',
        ),
    ),
    'router' => array(
        'routes' => array(
            'menus' => array(
                'type'    => 'Literal',
                'options' => array(
                    // Change this to something specific to your module
                    'route'    => '/menus',
                    'defaults' => array(
                        // Change this value to reflect the namespace in which
                        // the controllers for your module are found
                        '__NAMESPACE__' => 'Menus\Controller',
                        'controller'    => 'Index',
                        'action'        => 'index',
                    ),
                ),
                'may_terminate' => true,
                'child_routes' => array(
                    // This route is a sane default when developing a module;
                    // as you solidify the routes for your module, however,
                    // you may want to remove it and replace it with more
                    // specific routes.
                    'default' => array(
                        'type'    => 'Segment',
                        'options' => array(
                            'route'    => '/[:controller[/:action[/:id]]]',
                            'constraints' => array(
                                'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
                                'action'     => '[a-zA-Z][a-zA-Z0-9_-]*',
                                'id'     => '[0-9]*',
                            ),
                            'defaults' => array(
                            ),
                        ),
                    ),
                ),
            ),
        ),
    ),
    'view_manager' => array(
        'template_path_stack' => array(
            'Menus' => __DIR__ . '/../view',
        ),
    ),
);

Vamos ahora sí a por faena y vamos a incluir en el controlador ambas acciones:

Añadir los siguientes métodos en el fichero /module/Menus/src/Menus/Controller/MenusController.php:
    public function editAction() {
        $form = new MenuForm();
        $form->setInputFilter(new MenuFilter());
        $viewVars = array (
            'title' => 'Edit Menu',
            'form' => $form
        );
        if (! $this->request->isPost ()) {
            $id = ( int ) $this->params ()->fromRoute ( 'id' );
            $adapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');
            $menusTable = new MenusTable($adapter);
            $menuDb = $menusTable->getMenuById ( $id );
            $form->setData ( $menuDb );
            return new ViewModel ( $viewVars );
        } else {
            $post = $this->request->getPost ();
            $id = ( int ) $post->id_menu;
            $form->setData ( $post );
            if (! $form->isValid ()) {
                $viewVars ['error'] = true;
                return new ViewModel ( $viewVars );
            }
        }
        // Edit menu
        try {
            $formData = $form->getData ();
            $menu = (isset ( $formData ['menu'] )) ? $formData ['menu'] : null;
            $name = (isset ( $formData ['name'] )) ? $formData ['name'] : null;
            $label = (isset ( $formData ['label'] )) ? $formData ['label'] : null;
            $module = (isset ( $formData ['module'] )) ? $formData ['module'] : null;
            $controller = (isset ( $formData ['controller'] )) ? $formData ['controller'] : null;
            $action = (isset ( $formData ['action'] )) ? $formData ['action'] : null;
            $order = (isset ( $formData ['order'] )) ? $formData ['order'] : null;
    
            $adapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');
            $menusTable = new MenusTable($adapter);
            $menusTable->updateMenu ( $id, $menu, $name, $label, $module, $controller, $action, $order );
            return $this->redirect ()->toRoute ( NULL, array (
                'controller' => 'menus',
                'action' => 'index'
            ) );
        } catch ( \Exception $e ) {
            $viewVars ['error'] = true;
            $viewVars ['message'] = $e->getMessage ();
            return new ViewModel ( $viewVars );
        }
    }
    
    public function deleteAction() {
        try {
            $id = ( int ) $this->params ()->fromRoute ( 'id' );

            $adapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');
            $menusTable = new MenusTable($adapter);
            $menusTable->deleteMenu( $id );
            return $this->redirect ()->toRoute ( NULL, array (
                'controller' => 'Menus',
                'action' => 'index'
            ) );
        } catch ( \Exception $e ) {
            $viewVars ['title'] = 'Delete Menu';
            $viewVars ['message'] = $e->getMessage ();
            return new ViewModel ( $viewVars );
        }
    }

Por cada acción necesitaremos una vista asociada, así pues hemos de crear ambas, edit.phtml y delete.phtml.

Contenido del archivo /module/Menus/view/menus/menus/edit.phtml :
<section class="edit">
<h2><?php echo $this->translate($this->title); ?></h2>

<?php if ($this->error){ ?>
<p class="error">
    <?php echo $this->translate('There were one or more isues with your submission. Please correct them as indicated below.'); ?>
    <?php if(isset($this->message) && $this->message != '') { ?>
     <br>
     <?php echo $this->message; ?>
     <?php } ?>
</p>
<?php } ?>

<?php 
$form = $this->form;
$form->prepare();
$form->setAttribute('action', $this->url(NULL, array('controller'=>'menus', 'action' => 'edit')));
$form->setAttribute('method', 'post');

echo $this->form()->openTag($form);
?>
    <div class="form-group">
<?php echo $this->formLabel($form->get('menu')); ?>
<?php 
    echo $this->formElement($form->get('menu'));
    echo $this->formElementErrors($form->get('menu'));
?>
 </div>
 <div class="form-group">
<?php echo $this->formLabel($form->get('name')); ?>
<?php 
    echo $this->formElement($form->get('name'));
    echo $this->formElementErrors($form->get('name'));
?>
 </div>
 <div class="form-group">
<?php echo $this->formLabel($form->get('label')); ?>
<?php 
    echo $this->formElement($form->get('label'));
    echo $this->formElementErrors($form->get('label'));
?>
 </div>
 <div class="form-group">
<?php echo $this->formLabel($form->get('module')); ?>
<?php 
    echo $this->formElement($form->get('module'));
    echo $this->formElementErrors($form->get('module'));
?>
 </div>
 <div class="form-group">
<?php echo $this->formLabel($form->get('controller')); ?>
<?php 
    echo $this->formElement($form->get('controller'));
    echo $this->formElementErrors($form->get('controller'));
?>
 </div>
 <div class="form-group">
<?php echo $this->formLabel($form->get('action')); ?>
<?php 
    echo $this->formElement($form->get('action'));
    echo $this->formElementErrors($form->get('action'));
?>
 </div>
 <div class="form-group">
<?php echo $this->formLabel($form->get('order')); ?>
<?php 
    echo $this->formElement($form->get('order'));
    echo $this->formElementErrors($form->get('order'));
?>
 </div>   
    <div class="form-group">
<?php 
    echo $this->formElement($form->get('submit'));
    echo $this->formElementErrors($form->get('submit'));
?>&nbsp;
<a class="btn btn-default" 
   href="<?php echo $this->url('menus/default', array('controller' => 'menus', 'action' => 'index'), null); ?>"><?php echo $this->translate('Return back');?></a>
 </div>

<?php echo $this->formElement($form->get('id_menu')); ?>
<?php echo $this->form()->closeTag() ?>
</section>

Contenido del archivo /module/Menus/view/menus/menus/delete.phtml :
<section class="delete-confirm">
<h3><?php echo $this->translate($this->title); ?></h3>
<p class="error">
    <?php echo $this->translate('There were one or more isues with your submission. Please correct them as indicated below.'); ?>
    <?php if(isset($this->message) && $this->message != '') { ?>
     <br>
     <?php echo $this->message; ?>
     <?php } ?>
</p>

<p><a href="<?php echo $this->url('menus/default', array('controller' => 'groups'), null); ?>"><?php echo $this->translate('Return back');?></a></p>
</section>

Para finalizar debemos actualizar el modelo de la tabla Menus para incluir la actualización y borrado de registros, así como la búsqueda de un registro por identificador.

Añadir los siguientes métodos en el fichero /module/Menus/src/Menus/Model/MenusTable.php:

    public function getMenuById($id) {
        $id  = (int) $id;
        $rowset = $this->select(array('id_menu' => $id));
        $row = $rowset->current();
        if (!$row) {
            throw new \Exception("Could not find row $id");
        }
        return $row;
    }
    
    public function updateMenu($id, $menu, $name, $label, $module, $controller, $action, $order){
        $row = $this->getMenuById($id);
        $data['menu'] = $menu;
        $data['name'] = $name;
        $data['label'] = $label;
        $data['module'] = $module;
        $data['controller'] = $controller;
        $data['action'] = $action;
        $data['order'] = $order;
        if ($row) {
            $this->update($data, array('id_menu' => $id));
        } else {
            throw new \Exception('Menu ID does not exist');
        }
    }
    
    public function deleteMenu($id){
        $id = (int) $id;
        $rowset = $this->select(array('id_menu' => $id));
        $row = $rowset->current();
        if (!$row) {
            throw new \Exception("Could not find row $id");
        }
        $this->delete(array('id_menu' => $id));
    }

Podemos probar los scripts y validar tanto la edición como el borrado de entradas.

El uso de getServiceLocator

Puede ser interesante encapsular las llamadas tanto al modelo como al formulario mediante el uso de getServiceLocator.
Si editamos el fichero /module/Module.php y incluimos el siguiente método a la clase Module:

    public function getServiceConfig(){
        return array(
            'abstract_factories' => array(),
            'aliases' => array(),
            'factories' => array(
                'MenusTable' => function ($sm) {
                    return new MenusTable( $sm->get('\Zend\Db\Adapter\Adapter') );
                },
                // Forms
                'MenuForm' => function ($sm) {
                    $form = new \Menus\Form\MenuForm();
                    $form->SetInputFilter($sm->get('MenuFilter'));
                    return $form;
                },
                //Filters
                'MenuFilter' => function ($sm) {
                    return new \Menus\Form\MenuFilter();
                },
            ),
            'invokables' => array(),
            'services' => array(),
            'shared' => array(),
        );
   }

En nuestro código podremos sustituir todas las llamadas a MenusTables y MenuForm usando getServiceLocator.

En el caso de MenusTable el código:
        $adapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');
        $menusTable = new MenusTable($adapter);

Podremos sustituirlo por:
        $menusTable = $this->getServiceLocator ()->get ( 'MenusTable' );

En el caso de la llamada al formulario:
        $form = new MenuForm();
        $form->setInputFilter(new MenuFilter());

Podremos sustituirlo por:
        $form = $this->getServiceLocator ()->get ( 'MenuForm' );

El código queda más limpio.

viernes, 3 de octubre de 2014

Accediendo a la base de datos con ZF2

Zend Framework nos proporciona un potente conjunto de funciones para la interacción con nuestra base de datos. En mi caso uso MySQL y para acceder a él trabajaremos con el controlador PDO (Objetos de datos para PHP).
Con los componentes Zend\Db\Resultset y Zend\Db\Sql nos permitirán hacer consultas a nuestra base de datos.
Mientras que con Resultset podremos crear la SQL directamente con Sql dispondremos de una capa de abstracción que mediante el uso de sus métodos crearemos la consulta.
Otra forma de acceder a los datos contenidos en nuestra base de datos es medianet Zend\Db\TableGateway, usaremos este método cuando queramos trabajar con "factory's". Creando una clase que derive de TableGateway nos permitirá invocar sus métodos nativos para interaccionar con los datos de la base de datos.

Antes de plantearnos nada hemos de definir la cadena de conexión con nuestro sistema gestor, para ello es en el fichero local.php de la carpeta autoload de nuestra aplicación donde deberán estar ubicada esta definición. Este fichero no existe, pero existe otro de nombre local.php.dist que podemos renombrar y usar.

En nuestro caso y como ejemplo hemos creado una base de datos llamada myapp, con un usuario myAppUsername con privilegios de acceso y una contraseña myAppPassword.

El esquema de nuestra base de datos es:

  /* Tabla menus */
  CREATE TABLE `menus` (
    `id_menu` int(10) unsigned NOT NULL,
    `menu` varchar(255) NOT NULL DEFAULT 'navigation',
    `name` varchar(255) NOT NULL,
    `label` varchar(255) NOT NULL,
    `module` varchar(255) NOT NULL,
    `controller` varchar(255) NOT NULL,
    `action` varchar(255) NOT NULL,
    `order` smallint(6) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

  /* Insertar menus */
  INSERT INTO `menus` (`id_menu`, `menu`, `name`, `label`, `module`, `controller`, `action`, `order`) VALUES
  (1, 'topnav', 'Home', 'Home', 'Application', 'Index', 'index', 0),
  (2, 'admin', 'Menus', 'Menus', 'Menus', 'Menus', 'index', 0); 

Contenido del fichero /config/autoload/local.php:

<?php

return array(
  'db' => array(
   'driver' => 'Pdo',
   'dsn'  => 'mysql:dbname=myapp;host=localhost',
   'username' => 'myAppUsername',
   'password' => 'myAppPassword',
   'driver_options' => array(
    PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8\''
   ), 
  ),
  'service_manager' => array(
   'factories' => array(
    'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory'
   ),
  ),
);

El componente TableGateway es el que encuentro es el más extendido para la interacción con los datos de nuestra base datos.
Lo más correcto es crear una clase que nos proporcione métodos para la interacción con los datos. Al igual que las clases formularios estarán ubicadas en su carpeta src/ModuleName/Form las clases encargadas del acceso a la base de datos las ubicaremos en la carpeta src/ModuleName/Model. Así pues crearemos la carpeta Model en /module/Menus/src/Menus/, y dentro crearemos nuestra clase en el fichero MenusTable.php.

Contenido del fichero /module/Menus/src/Menus/Model/MenusTable.php:

<?php
namespace Menus\Model;

use Zend\Db\TableGateway\TableGateway;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Sql\Select;
use Zend\Db\ResultSet\ResultSet;

class MenusTable extends TableGateway
{
    protected $table = 'menus';

    public function __construct(Adapter $adapter = null, $databaseSchema = null, ResultSet $selectResultPrototype = null)
    {
        return parent::__construct($this->table, $adapter, $databaseSchema, $selectResultPrototype);
    }
    
    public function saveMenu($menu, $name, $label, $module, $controller, $action, $order){
        $data['menu'] = $menu;
        $data['name'] = $name;
        $data['label'] = $label;
        $data['module'] = $module;
        $data['controller'] = $controller;
        $data['action'] = $action;
        $data['order'] = $order;
        
        $this->insert($data);
        return $this->lastInsertValue;
    }
}

Una vez definido la clase modelo ya estamos capacitados a interaccionar con la base de datos desde nuestro controlador. Así editaremos nuestro fichero MenusController.php y añadiremos la funcionalidad de creación de un nuevo registro.

Contenido del fichero /module/Menus/src/Menus/Controller/MenusController.php:

<?php
namespace Menus\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Menus\Form\MenuForm;
use Menus\Form\MenuFilter;
use Menus\Model\MenusTable;

class MenusController extends AbstractActionController {
    /**
     * Crea un nuevo registro
     *
     * @return \Zend\View\Model\ViewModel
     */
    public function createAction() {
        $form = new MenuForm();
        $form->setInputFilter(new MenuFilter());
        $viewVars = array (
            'title' => 'Add Menu',
            'form' => $form
        );
        if (! $this->request->isPost ()) {
            return new ViewModel ( $viewVars );
        }
        $post = $this->request->getPost ();
        $form->setData ( $post );
        if (! $form->isValid ()) {
            $viewVars ['error'] = true;
            return new ViewModel ( $viewVars );
        }
        // Create menu
        try {
            $formData = $form->getData();
            $adapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');
            $MenusTable = new MenusTable($adapter);
            $menu = (isset ( $formData ['menu'] )) ? $formData ['menu'] : null;
            $name = (isset ( $formData ['name'] )) ? $formData ['name'] : null;
            $label = (isset ( $formData ['label'] )) ? $formData ['label'] : null;
            $module = (isset ( $formData ['module'] )) ? $formData ['module'] : null;
            $controller = (isset ( $formData ['controller'] )) ? $formData ['controller'] : null;
            $action = (isset ( $formData ['action'] )) ? $formData ['action'] : null;
            $order = (isset ( $formData ['order'] )) ? $formData ['order'] : null;
            $MenusTable->saveMenu ( $menu, $name, $label, $module, $controller, $action, $order );
            return $this->redirect()->toRoute(NULL, array(
                'controller' => 'menus',
                'action' => 'index'
            ));
        } catch (\Exception $e) {
            $viewVars['error'] = true;
            $viewVars['message'] = $e->getMessage();
        }
        return new ViewModel ( $viewVars );
    }
}

En este métod podemos acceder de dos formas, mediante la llamada directa desde URL, en ese caso $this->request->isPost () será true, con lo que directamente saldremos del método y mostraremos el formulario de entrada de datos, o, el acceso es mediante el action del formulario, en ese caso $this->request->isPost () devolverá un valor false y seguirá ejecutando las instrucciones del método.
En el caso de que nos encontremos que es el formulario envía los datos mediante el método post, debemos validarlos mediante $form->isValid (), que nos devolverá un valor false en caso de que no cumpla con las reglas establecidas.
El siguiente paso será recuperar los datos enviados en el request y pasarlos a nuestra clase modelo para que realice una inserción de un nuevo registro.
Para recuperar valores del post en nuestro controlador siempre podemos usar:

$data = $this->request->getPost();
$variable = $data['variable']; 

Para finalizar este ejemplo deberemos crear el método indexAction dentro de nuestro controlador, así como la vista al mismo asociado.

Antes editaremos nuestra clase modelo y añadiremos la función fetchAll.
Añadiremos un nuevo método al fichero /module/Menus/src/Menus/Model/MenusTable.php:

    public function fetchAll($where = array(), $order = array('order asc')){
        $rowset = $this->select(function (Select $select) use ($where, $order){
            if( count($where) > 0 ){
                $select->where ( $where );
            }
            if( count($order) > 0 ){
                $select->order ( $order );
            }
        } );
        return $rowset;
    }

En nuestro controlador añadiremos el método indexAction.
Añadiremos un nuevo método al fichero /module/Menus/src/Menus/Controller/MenusController.php:

    public function indexAction()
    {
        $adapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');
        $menusTable = new MenusTable($adapter);
        $menus = $menusTable->fetchAll(array(), array(
            'menu',
            'order'
        ));
        $viewVars = array(
            'title' => 'Menus list',
            'menus' => $menus
        );
        return new ViewModel($viewVars);
    } 
Ahora tan solo nos falta definir la nueva vista asociada a la acción de crear un nuevo registro en el controlador. Para ello crearemos un fichero llamado index.phtml en /module/Menus/view/menus/.
Contenido del fichero /module/Menus/view/menus/index.phtml:

<section class="Menus">
<?php if( isset($this->menus) ){ ?>
 <table class="table table-striped table-bordered table-condensed">
 <tr>
  <th><?php echo $this->translate('Menu'); ?></th>
  <th><?php echo $this->translate('Menu Name'); ?></th>
  <th><?php echo $this->translate('Label'); ?></th>
  <th><?php echo $this->translate('Module'); ?></th>
  <th><?php echo $this->translate('controller'); ?></th>
  <th><?php echo $this->translate('action'); ?></th>
  <th><?php echo $this->translate('order'); ?></th>
  <th width="300px">&nbsp;</th> 
 </tr>
 <?php  
  foreach ($this->menus as $menu){ ?>
 <tr>
  <td><?php echo $menu['menu']; ?></td>
  <td><?php echo $menu['name']; ?></td>
  <td><?php echo $menu['label']; ?></td>
  <td><?php echo $menu['module']; ?></td>
  <td><?php echo $menu['controller']; ?></td>
  <td><?php echo $menu['action']; ?></td>
  <td><?php echo $menu['order']; ?></td>
  <td><a href="<?php echo $this->url('menus/default', array('controller' => 'menus', 'action' => 'edit', 'id' => $menu['id_menu']), null); ?>"><?php echo $this->translate('Edit'); ?></a>
   | <a href="<?php echo $this->url('menus/default', array('controller' => 'menus', 'action' => 'delete', 'id' => $menu['id_menu']), null); ?>"><?php echo $this->translate('Delete');?></a>   
   </td>
 </tr>
 <?php
  } ?> 
 </table>
<?php } ?> 
 <p><a class="btn btn-primary" href="<?php echo $this->url('menus/default', array('controller' => 'menus', 'action' => 'create'), null); ?>"><?php echo $this->translate('Add new menu');?></a></p>
</section>

Sólo nos resta comprobar el resultado de la creación de nuevas entradas de menú, para ello navegaremos a la dirección http://localhost/myApp/menus/menus e iremos insertando nuevos registros:


Dejaremos para más adelante la edición y el borrado de entradas.

jueves, 2 de octubre de 2014

Creando formularios con Zf2

Toda aplicación necesita un mecanismo de intercambio de información mediante formularios. Zend Framework dota de una potente capa de abstracción para realizar dicha tarea.
Es recomendable consultar la documentación oficial de Zend al respecto: Introduction to Zend\Form

Generalmente organizaremos nuestros formularios en una carpeta para dicho propósito, dicha carpeta estará ubicada en el fuente de nuestro módulo, en el caso de ser el módulo "Menus", para seguir con los ejemplos anteriores, la carpeta la podríamos llamar "Form" y la ubicariamos en /module/Menus/src/Menus/.

El objetivo es crear un formulario para la inserción de los campos de las entradas de menú, para ello a parte del formulario crearemos su controlador y vistas asociadas.

Nuestro formulario lo crearemos en el fichero MenuForm.php:
Contenido del fichero /module/Menus/src/Menus/Form/MenuForm.php:

<?php
// filename : /module/Menus/src/Menus/Form/MenuForm.php
namespace Menus\Form;

use Zend\Form\Form;

class MenuForm extends Form
{

    public function __construct($name = null)
    {
        parent::__construct('Menus');
        $this->setAttribute('method', 'post');
        $this->setAttribute('enctype', 'multipart/form-data');
        
        $this->add(array(
            'name' => 'id_menu',
            'attributes' => array(
                'type' => 'hidden'
            )
        ));
        
        $this->add(array(
            'name' => 'menu',
            'attributes' => array(
                'type' => 'text',
                'required' => 'required',
                'class' => 'form-control'
            ),
            'options' => array(
                'label' => 'Menu Type'
            )
        ));
        
        $this->add(array(
            'name' => 'name',
            'attributes' => array(
                'type' => 'text',
                'required' => 'required',
                'class' => 'form-control'
            ),
            'options' => array(
                'label' => 'Menu Name'
            )
        ));
        
        $this->add(array(
            'name' => 'label',
            'attributes' => array(
                'type' => 'text',
                'required' => 'required',
                'class' => 'form-control'
            ),
            'options' => array(
                'label' => 'Label'
            )
        ));
        
        $this->add(array(
            'name' => 'module',
            'attributes' => array(
                'type' => 'text',
                'required' => 'required',
                'class' => 'form-control'
            ),
            'options' => array(
                'label' => 'Module'
            )
        ));
        
        $this->add(array(
            'name' => 'controller',
            'attributes' => array(
                'type' => 'text',
                'required' => 'required',
                'class' => 'form-control'
            ),
            'options' => array(
                'label' => 'Controller'
            )
        ));
        
        $this->add(array(
            'name' => 'action',
            'attributes' => array(
                'type' => 'text',
                'required' => 'required',
                'class' => 'form-control'
            ),
            'options' => array(
                'label' => 'Action'
            )
        ));
        
        $this->add(array(
            'name' => 'order',
            'attributes' => array(
                'type' => 'text',
                'required' => 'required',
                'class' => 'form-control'
            ),
            'options' => array(
                'label' => 'Order'
            )
        ));
        
        $this->add(array(
            'name' => 'submit',
            'attributes' => array(
                'type' => 'submit',
                'value' => 'Submit',
                'class' => 'btn btn-success'
            )
        ));
    }
}

Zend Framework nos proporciona una serie de funciones incorporadas para el tratamiento de los datos en formularios, eso es especialmente interesante para evitar accesos no deseados a nuestra base de datos, además de usar una metodología ordenada que permite separar los datos de su definición.

Procederemos a crear un nuevo fichero php llamado MenuFilter.php en /module/Menus/src/Menus/Form/.
Contenido del fichero /module/Menus/src/Menus/Form/MenuFilter.php.

<?php
namespace Menus\Form;

use Zend\InputFilter\InputFilter;

class MenuFilter extends InputFilter
{

    public function __construct()
    {
        $this->add(array(
            'name' => 'menu',
            'required' => true,
            'filters' => array(
                array(
                    'name' => 'StripTags'
                )
            ),
            'validators' => array(
                array(
                    'name' => 'StringLength',
                    'options' => array(
                        'encoding' => 'UTF-8',
                        'min' => 2,
                        'max' => 255
                    )
                )
            )
        ));
        
        $this->add(array(
            'name' => 'name',
            'required' => true,
            'filters' => array(
                array(
                    'name' => 'StripTags'
                )
            ),
            'validators' => array(
                array(
                    'name' => 'StringLength',
                    'options' => array(
                        'encoding' => 'UTF-8',
                        'min' => 2,
                        'max' => 255
                    )
                )
            )
        ));
        
        $this->add(array(
            'name' => 'label',
            'required' => true,
            'filters' => array(
                array(
                    'name' => 'StripTags'
                )
            ),
            'validators' => array(
                array(
                    'name' => 'StringLength',
                    'options' => array(
                        'encoding' => 'UTF-8',
                        'min' => 2,
                        'max' => 255
                    )
                )
            )
        ));
        
        $this->add(array(
            'name' => 'module',
            'required' => true,
            'filters' => array(
                array(
                    'name' => 'StripTags'
                )
            ),
            'validators' => array(
                array(
                    'name' => 'StringLength',
                    'options' => array(
                        'encoding' => 'UTF-8',
                        'min' => 2,
                        'max' => 255
                    )
                )
            )
        ));
        
        $this->add(array(
            'name' => 'controller',
            'required' => true,
            'filters' => array(
                array(
                    'name' => 'StripTags'
                )
            ),
            'validators' => array(
                array(
                    'name' => 'StringLength',
                    'options' => array(
                        'encoding' => 'UTF-8',
                        'min' => 2,
                        'max' => 255
                    )
                )
            )
        ));
        
        $this->add(array(
            'name' => 'action',
            'required' => true,
            'filters' => array(
                array(
                    'name' => 'StripTags'
                )
            ),
            'validators' => array(
                array(
                    'name' => 'StringLength',
                    'options' => array(
                        'encoding' => 'UTF-8',
                        'min' => 2,
                        'max' => 255
                    )
                )
            )
        ));

        $this->add(array(
            'name' => 'order',
            'required' => true,
            'filters' => array(
                array(
                'name'                   => 'Digits',
                'break_chain_on_failure' => true, 
                )
            ),
            'validators' => array(
                array(
                    'name' => 'Between',
                    'options' => array(
                        'min' => 1,
                        'max' => 99,
                    ), 
                )
            )
        ));
    }
}
Recordemos que el controlador invocará los datos del formulario y pasará en forma de variables a la vista, que será la encargada de generar el código HTML pertinente.
Nuestro controlador lo llamaremos MenusController.php y lo ubicaremos en la carpeta  /module/Menus/src/Menus/Controller.
Contenido del fichero /module/Menus/src/Menus/Controller/MenusController.php:

<?php
namespace Menus\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Menus\Form\MenuForm;
use Menus\Form\MenuFilter;

class MenusController extends AbstractActionController {
    /**
     * Crea un nuevo registro
     *
     * @return \Zend\View\Model\ViewModel
     */
    public function createAction() {
        $form = new MenuForm();
        $form->setInputFilter(new MenuFilter());
        $viewVars = array (
            'title' => 'Add Menu',
            'form' => $form
        );
        if (! $this->request->isPost ()) {
            return new ViewModel ( $viewVars );
        }
        $post = $this->request->getPost ();
        $form->setData ( $post );
        if (! $form->isValid ()) {
            $viewVars ['error'] = true;
            return new ViewModel ( $viewVars );
        }
        // Create menu
        try {
            // ...
        } catch (\Exception $e) {
            $viewVars['error'] = true;
            $viewVars['message'] = $e->getMessage();
        }
        return new ViewModel ( $viewVars );
    }
}   

Es una buena práctica gestionar los datos en el controlador y pasarlos como variables a la vista, en este caso estamos usando el array viewVars para incluir en él todos los parámetros. Posteriormente en la vista podremos acceder fácilmente usando la cláusual this.

Cada acción susceptible de ser invocada en el controlador debe tener una vista asociada. La vista que transformará el contenido en HTML se llamará create.phtml y estará ubicada en la carpeta "menus" (en minúsculas), que no existe, nuestro siguiente paso será crearla en /module/Menus/view.
Contenido del fichero  /module/Menus/view/menus/create.phtml:

<section class="create">
 <h2><?php echo $this->translate($this->title); ?></h2>

<?php if ($this->error){ ?>
<p class="error">
    <?php echo $this->translate('There were one or more isues with your submission. Please correct them as indicated below.'); ?>
    <?php if(isset($this->message) && $this->message != '') { ?>
     <br>
     <?php echo $this->message; ?>
     <?php } ?>
</p>
<?php } ?>

<?php
$form = $this->form;
$form->prepare();
$form->setAttribute('action', $this->url(NULL, array(
    'controller' => 'menus',
    'action' => 'create'
)));
$form->setAttribute('method', 'post');
echo $this->form()->openTag($form);
?>
 <div class="form-group">
<?php echo $this->formLabel($form->get('menu')); ?>
<?php
echo $this->formElement($form->get('menu'));
echo $this->formElementErrors($form->get('menu'));
?>
 </div>
 <div class="form-group">
<?php echo $this->formLabel($form->get('name')); ?>
<?php
echo $this->formElement($form->get('name'));
echo $this->formElementErrors($form->get('name'));
?>
 </div>
 <div class="form-group">
<?php echo $this->formLabel($form->get('label')); ?>
<?php
echo $this->formElement($form->get('label'));
echo $this->formElementErrors($form->get('label'));
?>
 </div>
 <div class="form-group">
<?php echo $this->formLabel($form->get('module')); ?>
<?php
echo $this->formElement($form->get('module'));
echo $this->formElementErrors($form->get('module'));
?>
 </div>
 <div class="form-group">
<?php echo $this->formLabel($form->get('controller')); ?>
<?php
echo $this->formElement($form->get('controller'));
echo $this->formElementErrors($form->get('controller'));
?>
 </div>
 <div class="form-group">
<?php echo $this->formLabel($form->get('action')); ?>
<?php
echo $this->formElement($form->get('action'));
echo $this->formElementErrors($form->get('action'));
?>
 </div>
 <div class="form-group">
<?php echo $this->formLabel($form->get('order')); ?>
<?php
echo $this->formElement($form->get('order'));
echo $this->formElementErrors($form->get('order'));
?>
 </div>
 <div class="form-group">
<?php
echo $this->formElement($form->get('submit'));
echo $this->formElementErrors($form->get('submit'));
?>&nbsp;
<a class="btn btn-default"
   href="<?php echo $this->url('menus/default', array('controller' => 'menus', 'action' => 'index'), null); ?>"><?php echo $this->translate('Return back');?></a>
 </div>
<?php echo $this->form()->closeTag()?>
</section>
Como detalle comentar que tenemos acceso directo a las variables que desde el controlador hemos pasado a la vista: p. ej.: $this->title.

Finalmente hemos de incluir nuestro nuevo controlador en la definición de las rutas de nuestro módulo.
Contenido del fichero /module/Menus/config/module.config.php:

<?php
return array(
    'controllers' => array(
        'invokables' => array(
            'Menus\Controller\Index' => 'Menus\Controller\Index',
            'Menus\Controller\Menus' => 'Menus\Controller\MenusController',
        ),
    ),
    'router' => array(
        'routes' => array(
            'menus' => array(
                'type'    => 'Literal',
                'options' => array(
                    'route'    => '/menus',
                    'defaults' => array(
                        '__NAMESPACE__' => 'Menus\Controller',
                        'controller'    => 'Index',
                        'action'        => 'index',
                    ),
                ),
                'may_terminate' => true,
                'child_routes' => array(
                    'default' => array(
                        'type'    => 'Segment',
                        'options' => array(
                            'route'    => '/[:controller[/:action]]',
                            'constraints' => array(
                                'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
                                'action'     => '[a-zA-Z][a-zA-Z0-9_-]*',
                            ),
                            'defaults' => array(
                            ),
                        ),
                    ),
                ),
            ),
        ),
    ),
    'view_manager' => array(
        'template_path_stack' => array(
            'Menus' => __DIR__ . '/../view',
        ),
    ),
);

Ya podemos comprobar la salida de nuestro formulario creado consultando la página http://localhost/myApp/menus/menus/create:


miércoles, 1 de octubre de 2014

Creando un módulo con Zend Framework 2

La arquitectura Modelo-Controlador-Vista intenta separar la capa de datos (Modelo) de su representación (Vista), y la capa que gestiona las relaciones entre ambas son los Controladores. Dicho de forma sencilla, nuestra aplicación sólo ejecutará acciones de los controladores, las cuales instanciaran modelos de datps de los que extraerán datos que posteriormente pasarán a las vistas para su representación.

La estructura básica de una URL en ZF2 es:
http://app/módulo/controlador/acción/parámetros

Recordemos que el uso de módulos nos permite segmentar el código, haciendo que sea más fácil de mantener y distribuir.
En la estructura de un módulo encontraremos tres carpetas imprescindibles:
  • config: donde se ubicarán los ficheros de configuración del módulo.
  • src: aquí encontraremos los controladores del módulo.
  • view: las vistas y layouts que nos determinaran la salida HTML.
Posteriormente podremos añadir otras carpetas de más interés como diccionarios, plugins, etc.

La creación de un módulo es tan sencilla como la creación de una aplicación, descargaremos el esqueleto base de un módulo desde el repositorio git de Zend Framework, como ejemplo crearemos un módulo llamado Menus:

cd /path_project/module
git clone git://github.com/zendframework/ZendSkeletonModule.git Menus

Este paso nos permitirá crear toda la estructura de directorios básica. Por defecto el esqueleto básico nos trae un controlador llamado ZendSkeletonModule con sus vistas asociadas. Para poder hacer accesible el nuevo módulo creado debemos incluirlo en el fichero /config/application.config.php de nuestra aplicación:

<?php
return array(
    // This should be an array of module namespaces used in the application.
    'modules' => array(
        'Application', 'Menus',
    ),

    // These are various options for the listeners attached to the ModuleManager
    'module_listener_options' => array(
        'module_paths' => array(
            './module',
            './vendor','./module',
        ),

        'config_glob_paths' => array(
            'config/autoload/{,*.}{global,local}.php',
        ),
    ),
);

En el módulo descargado desde git hemos de realizar algunas correcciones para adaptarlo al nombre que le hemos dado.
Fichero /module/Menus/Module.php:
...
namespace Menus;
..

Fichero /module/Menus/config/module.config.php sustituir cada ocurrencia de "ZendSkeletonModule" por "Menus" al igual que "module-specific-root" por "menus". Así mismo redefiniremos el controlador por "Index" en lugar de "SkeletonController":

<?php
return array(
    'controllers' => array(
        'invokables' => array(
            'Menus\Controller\Index' => 'Menus\Controller\Index',
        ),
    ),
    'router' => array(
        'routes' => array(
            'module-name-here' => array(
                'type'    => 'Literal',
                'options' => array(
                    // Change this to something specific to your module
                    'route'    => '/menus',
                    'defaults' => array(
                        // Change this value to reflect the namespace in which
                        // the controllers for your module are found
                        '__NAMESPACE__' => 'Menus\Controller',
                        'controller'    => 'Index',
                        'action'        => 'index',
                    ),
                ),
                'may_terminate' => true,
                'child_routes' => array(
                    // This route is a sane default when developing a module;
                    // as you solidify the routes for your module, however,
                    // you may want to remove it and replace it with more
                    // specific routes.
                    'default' => array(
                        'type'    => 'Segment',
                        'options' => array(
                            'route'    => '/[:controller[/:action]]',
                            'constraints' => array(
                                'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
                                'action'     => '[a-zA-Z][a-zA-Z0-9_-]*',
                            ),
                            'defaults' => array(
                            ),
                        ),
                    ),
                ),
            ),
        ),
    ),
    'view_manager' => array(
        'template_path_stack' => array(
            'Menus' => __DIR__ . '/../view',
        ),
    ),
);

Renombraremos la carpeta /module/Menus/src/ZendSkeletonModule por /module/Menus/src/Menus.
Renombraremos la carpeta /module/Menus/view/zend-skeleton-module por /module/Menus/view/menus.
Renombraremos la carpeta  /module/Menus/view/menus/skeleton por /module/Menus/view/menus/index.
Finalmente renombraremos el fichero /module/Menus/src/Menus/Controller/SkeletonController.php por /module/Menus/src/Menus/Controller/Index.php

Una vez hecho podemos invocar cualquiera de las acciones por defecto que han sido creadas en nuestro módulo:
http://localhost/myApp/menus
http://localhost/myApp/menus/index/foo


Ya hemos creado nuestro primer módulo en Zf2, el siguiente paso es dotarlo de funcionalidad.