CRUD con Zend Framework 2

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>

El resultado final:
Crud con Zend Framework 2
Crud con Zend Framework 2 modificar usuario

Más información:
Documentación oficial de Zend Framework 2

Victor

Autor: Victor

Desarrollador web - Formador online - Blogger

Compartir este post

14 Comentarios

  1. hola, muchas gracias por el tutorial, me ayudo mucho, pero teng un problema cuando intento insertar un usuario me sale este error

    SQLSTATE[HY093]: Invalid parameter number: parameter was not defined,

    me he dado cuenta que es por la contraseña, osea si creo el usuario sin contraseña se guarda normalmente

    Responder
  2. Hola, hay algún ejemplo para implementar un option select y foreing key entre dos tablas diferentes?

    Responder
  3. Hola!, muy buen articulo, soy nuevo en esto y me ha servido un montón, pero hay algo que me quedo grande jeje quizás es muy tonto, pero, donde le das los datos para conectarse a la base de datos?

    Responder
  4. Hola, podrias publicar el codigo para descargarlo porfavor.

    Responder
    • Este ejemplo lo hice hace tiempo, intentaré recuperar el código y subirlo a github. Un saludo.

      Responder
  5. Tengo solo una duda al ejecutar el constructor de la clase TableGateway, exactamente en esta seccion de codigo:

    return parent::__construct(‘usuarios’, $this->dbAdapter, $databaseSchema,$selectResultPrototype);

    Entiendo que el primer parametro es la tabla “‘usuarios”, pero que pasa si tengo una tabla que usa inner joins, entonces mi pregunta es ¿como haria para incluir 2 o 3 tablas?

    Responder
  6. Hola, me aprendido mucho en el tutorial, pero ahora cuando voy a insertar un usuario me sale este error:

    Additional information:
    Zend\I18n\Exception\ExtensionNotLoadedException

    File:
    C:\xampp\htdocs\zend2\vendor\zendframework\zend-i18n\src\Filter\AbstractLocale.php:24
    Message:
    Zend\I18n\Filter component requires the intl PHP extension

    Responder
  7. Muchas gracias, me has ayudado tanto en el desarrollo de una aplicación web

    Responder

Poner un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *