L'objectif avec ce tutoriel n'est pas de vous initier à Zend Framework 2. Le site officiel propose pour cela un excellent tutoriel.

Ici nous allons voir comment créer une nouvelle application et y intégrer immédiatement les modules "de base" tels que Doctrine 2 et ZfcUser afin d'avoir rapidement une application capable de:

  • gérer des utilisateurs
  • des groupes (à venir)
  • l'internationalisation (à venir)
  • avoir ORM fonctionnel avec Doctrine 2
    • Le tout en respectant l'architecture MVC, pas de requêtes dans les contrôleurs ici !! :)

Voyez cet article comme un Zend Skeletor avancé.

Pré-requis

Avoir installé Git et mis php.exe dans le PATH

Les modules suivant doivent être actifs sur votre serveur web

  • rewrite_module (Apache)
  • php_openssl (PHP)

Une connaissance de Git et de l'ORM Doctrine 2 est préférable pour démarrer

Installation du squelette ZF2

En ligne de commande

Nous allons créer et nous positionner dans un nouveau répertoire qui contiendra le projet ZF2, ici je l'appelle monProjet

mkdir monProjet
cd monProjet

Puis, cloner le dépôt Git de l'application squelette

git clone git://github.com/zendframework/ZendSkeletonApplication.git

Nous avons récupéré un dossier ZendSkeletonApplication, nous allons utiliser le composer.phar qu'il contient pour installer les dépendances

cd ZendSkeletonApplication
php composer.phar install

Nous pouvons maintenant créer un alias apache sur le dossier public et vérifier avec notre navigateur que nous arrivons bien à afficher la page de bienvenue.

Ajouter Doctrine2 et ZfcUser

Nous allons maintenant ajouter les modules Doctrine 2 ORM pour la représentation objet de la base, ZfcUser pour la gestion des utilisateurs et ZendDeveloperTools pour la petite barre d'outils bien pratique en développement.

Ajoutez dans le fichier composer.json les dépendances suivantes

{
    "name": "zendframework/skeleton-application",
    "description": "Skeleton Application for ZF2",
    "license": "BSD-3-Clause",
    "keywords": [
        "framework",
        "zf2"
    ],
    "homepage": "http://framework.zend.com/",
    "require": {
        "php": ">=5.3.3",
        "zendframework/zendframework": "2.2.*",
        "doctrine/doctrine-orm-module": "0.*",
        "zf-commons/zfc-user": "*",
        "zf-commons/zfc-user-doctrine-orm": "0.1.*",
        "zendframework/zend-developer-tools": "dev-master"
    }
}

La première ligne pour Doctrine, les deux suivantes pour ZfcUser et son utilisation au travers de Doctrine, la dernière pour la barre d'outils ZendDeveloperTools

Installez les maintenant avec composer

php composer.phar update

Activez les modules en ajoutant les lignes suivantes dans /config/application.config.php

'modules' => array(
    'Application',
    'DoctrineModule',
    'DoctrineORMModule',
    'ZfcBase',
    'ZfcUser',
    'ZfcUserDoctrineORM',
    'ZendDeveloperTools'
),

Les paramètres de connexion pour Doctrine dans /config/autoload/local.php

return array(
    // ...
    'doctrine' => array(
        'connection' => array(
            'orm_default' => array(
                'driverClass' => 'Doctrine\DBAL\Driver\PDOMySql\Driver',
                'params' => array(
                    'host'     => 'localhost',
                    'port'     => '3306',
                    'user'     => 'root',
                    'password' => '',
                    'dbname'   => 'maBase',
                    'charset'  => 'utf8'
                )
            )
        )
    ),
);

Dans le fichier module.config.php de votre ou vos modules ZF2, ajoutez la configuration qui permettra à Doctrine de savoir dans quel répertoire rechercher ses entités. Chez moi ce répertoire s'appelle Model

Ajoutez également un namespace en début de fichier, au nom de votre module

namespace Application;

return array(
    // Autre configuration...

    // Doctrine
    'doctrine' => array(
        'driver' => array(
            __NAMESPACE__ . '_driver' => array(
                'class' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
                'cache' => 'array',
                'paths' => array(__DIR__ . '/../src/' . __NAMESPACE__ . '/Model')
            ),
            'orm_default' => array(
                'drivers' => array(
                    __NAMESPACE__ . '\Model' => __NAMESPACE__ . '_driver'
                )
            )
        )
    )
);

Et enfin copier les fichiers suivants

  • /vendor/zf-commons/zfc-user/config/zfcuser.global.php.dist
  • /vendor/zendframework/zend-developer-tools/config/zenddevelopertools.local.php.dist

dans

  • /config/autoload/zfcuser.global.php
  • /config/autoload/zenddevelopertools.local.php

Il s'agit respectivement des fichiers de paramètres pour ZfcUser et ZendDeveloperTools que nous allons pouvoir adapter

Créer la table User

Le script de création de la table se trouve dans /vendor/zf-commons/zfc-user/data/schema.sql

Voici sa version MySQL actuelle:

CREATE TABLE `user`
(
    `user_id`       INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `username`      VARCHAR(255) DEFAULT NULL UNIQUE,
    `email`         VARCHAR(255) DEFAULT NULL UNIQUE,
    `display_name`  VARCHAR(50) DEFAULT NULL,
    `password`      VARCHAR(128) NOT NULL,
    `state`         SMALLINT UNSIGNED
) ENGINE=InnoDB CHARSET="utf8";

Créer une première entité

Dans la suite de ce tutoriel, je créerais l'entité User correspondant à ce que ZfcUser attend, ainsi que les Services et Factory autour de cette classe.

Tous seront positionnés dans le module Application, je n'ai pas d'intérêts à créer un nouveau module pour cette couche

Voici donc la classe User, utilisant la table user de ZfcUser (oui ça fait trois fois user :) ), à positionner dans module/Application/Model/User.php

<?php
/**
 * User
 * @author Bidoum
 *
 */
namespace Application\Model;
use Doctrine\ORM\Mapping as ORM;
use ZfcUser\Entity\UserInterface;

/**
 * Représentation d'un utilisateur
 *
 * @ORM\Entity
 * @ORM\Table(name="user")
 *
 * @author
 */
class User implements UserInterface, ProviderInterface
{
	/*********************************
	 * ATTRIBUTS
	*********************************/
	
	/**
	 * @var int L'identifiant utilisateur
	 * @ORM\Id
	 * @ORM\Column(type="integer", name="user_id")
	 * @ORM\GeneratedValue(strategy="AUTO")
	 */
	protected $_id;
	/**
	 * @var string Le login
	 * @ORM\Column(type="string", length=255, unique=true, nullable=true, name="username")
	 */
	protected $_username;
	/**
	 * @var string L'email
	 * @ORM\Column(type="string", unique=true,  length=255, name="email")
	 */
	protected $_email;
	/**
	 * @var string Le nom affiché
	 * @ORM\Column(type="string", length=50, nullable=true, name="display_name")
	 */
	protected $_displayName;
	/**
	 * @var string Le mot de passe
	 * @ORM\Column(type="string", length=128, name="password")
	 */
	protected $_password;
	/**
	 * @var int Statut du compte
	 * @ORM\Column(type="integer", name="state")
	 */
	protected $_state;

	/*********************************
	 * ACCESSEURS
	*********************************/
	
	/*********** GETTERS ************/
	
	/**
	 * Obtient l'identifiant utilisateur
	 * @return int
	 */
	public function getId()
	{
		return $this->_id;
	}
	
	/**
	 * Obtient le login
	 * @return string
	 */
	public function getUsername()
	{
		return $this->_username;
	}
	
	/**
	 * Obtient l'email
	 * @return string
	 */
	public function getEmail()
	{
		return $this->_email;
	}
	
	/**
	 * Obtient le nom affiché
	 * @return string
	 */
	public function getDisplayName()
	{
		return $this->_displayName;
	}
	
	/**
	 * Obtient le mot de passe
	 * @return string
	 */
	public function getPassword()
	{
		return $this->_password;
	}	
	
	/**
	 * Obtient le statut du compte
	 * @return int
	 */
	public function getState()
	{
		return $this->_state;
	}
	
	
	/*********** SETTERS ************/
	
	/**
	 * Définit l'id utilisateur
	 * @param int $id L'identifiant
	 * @return User
	 */
	public function setId($id)
	{
		$this->_id = (int) $id;
		return $this;
	}
	
	/**
	 * Définit le login
	 * @param string $username Le login
	 * @return User
	 */
	public function setUsername($username)
	{
		$this->_username = $username;
		return $this;
	}
	
	/**
	 * Définit l'email
	 * @param string $email L'email
	 * @return User
	 */
	public function setEmail($email)
	{
		$this->_email = $email;
		return $this;
	}
	
	/**
	 * Définit le nom complet
	 * @param string $displayName Le nom complet
	 * @return User
	 */
	public function setDisplayName($displayName)
	{
		$this->_displayName = $displayName;
		return $this;
	}
	
	/**
	 * Définit le mot de passe
	 * @param string $password Le mot de passe
	 * @return User
	 */
	public function setPassword($password)
	{
		$this->_password = $password;
		return $this;
	}
	
	/**
	 * Définit l'état
	 * @param int $state L'etat
	 * @return User
	 */
	public function setState($state)
	{
		$this->_state = $state;
		return $this;
	}
	
	/*********************************
	 * CONSTRUCTEUR / DESTRUCTEUR
	*********************************/
	
	/**
	 * Constructeur
	 */
	public function __construct()
	{
		
	}

	/*********************************
	 * METHODES
	*********************************/
	
	/************ PUBLIC ************/
		
	/*********** PROTECTED **********/
	
	/************ PRIVATE ***********/
}

Créer un AbstractService

Plutôt que d'utiliser l'EntityManager de Doctrine directement dans le contrôleur comme on peut le voir sur divers tutoriaux, je préfère respecter l'architecture du MVC et séparer la récupération des données du contrôleur.

Je met donc en place une couche service, chargée de récupérer les données et retourner des entités métier.

Tous mes services hériteront d'une classe abstraite qui contiendra l'EntityManager.

Voici donc la classe AbstractService à mettre dans module/Application/src/Application/Service/AbstractService.php

<?php 
namespace Application\Service;

/**
 * Service layer - Abstract class
 *
 * @package Application\Service
 * @author Bidoum
 *
 */
abstract class AbstractService{
	
	/**
	 * @var \Doctrine\ORM\EntityManager L'entity manager
	 */
	private $_em;
	/**
	 * @var \Doctrine\ORM\EntityRepository Le repository
	 */
	private $_rep;
		
	/**
	 * Constructeur
	 * @param \Doctrine\ORM\EntityManager $em L'Entity manager
	 * @param \Doctrine\ORM\EntityRepository $rep Le repository
	 */
	public function __construct(\Doctrine\ORM\EntityManager $em, \Doctrine\ORM\EntityRepository $rep)
	{
		$this->_em = $em;
		$this->_rep = $rep;
	}
	
	/**
	 * Obtient l'entity manager
	 * @return \Doctrine\ORM\EntityManager
	 */
	protected function getEm()
	{
		return $this->_em;
	}
	
	/**
	 * Obtient le repository
	 * @return \Doctrine\ORM\EntityRepository
	 */
	protected function getRep()
	{
		return $this->_rep;
	}
	
}
?>

Créer un premier service

Je vais créer ici un premier service qui me permettra de manipuler des utilisateurs, à placer comme précédemment dans module/Application/src/Application/Service/UserService.php

<?php
/**
 * Service
 * @package Application\Service
 * @author Bidoum
 *
 */
namespace Application\Service;

/**
 * Service
 * 
 * @package Application\Service
 * @author auget
 *
 */
class UserService extends \Application\Service\AbstractService
{
	/**
	 * Obtient un utilisateur par son email
	 * @param string email
	 * @return Application\Model\User
	 */
	public function getByEmail($email)
	{
		$qb = $this->getEm()->createQueryBuilder();
	
		$qb->select(array('u'))
			->from('Application\Model\User', 'u')
			->where(
				$qb->expr()->eq('u._email', '?1')
			)
			->setParameters(array(1 => $email))
		;
	
		$query = $qb->getQuery();
	
		return $query->getSingleResult();
	}
}

 Afin de faciliter l'instanciation de ce service, nous allons créer un factory

A placer comme dans module/Application/src/Application/Service/Factory/UserServiceFactory.php

<?php
namespace Application\Service\Factory;

use Application\Service\UserService;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class UserServiceFactory implements FactoryInterface
{
	/**
	 * (non-PHPdoc)
	 * @see \Zend\ServiceManager\FactoryInterface::createService()
	 * @return Application\Service\ApplicationService
	 */
	public function createService(ServiceLocatorInterface $serviceLocator)
	{
		$entityManager = $serviceLocator->get('Doctrine\ORM\EntityManager');
		$userRepository = $entityManager->getRepository('Application\Model\User');
		
		return new UserService($entityManager, $userRepository);
	} 
}

Et enfin, nous allons ajouter ce service dans la configuration du module, à nouveau dans module.config.php

'service_manager' => array(
	'factories' => array(
		'Application\Service\UserService' => 'Application\Service\Factory\UserServiceFactory'
	)
),

Ce service pourra être appelé depuis un contrôleur avec l'instruction

$userService = $this->getServiceLocator()->get('Application\Service\UserService');

Nous pourrons alors récupérer un utilisateur en appelant la méthode créé précédemment

$user = $userService->getByEmail('bidoum @home.fr');

 

Problèmes connus

openssl

[RuntimeException]
You must enable the openssl extension to download files via https

Il suffit d'activer l'extension PHP correspondante: php_openssl, dans php.ini (Attention, avec Wamp il y en a deux)