Pages

Date 15 octobre 2012

Jobeet ZF2 - Jour 3 - Le modèle de données

Après avoir préparé le squelette de projet et présenté les différentes exigences du projet Jobeet, nous allons enfin ouvrir notre éditeur de code. Nous allons définir le modèle de données de Jobeet, mettre en place des intéractions avec la base de données et construire le premier module de l'application.
Contrairement au framework Symfony, qui génère beaucoup de chose à notre place, nous allons devoir écrire un peu de code PHP.

Modification du 17/10/2012:
J'ai supprimer du script SQL la clé étrangère id_user de la table Job. Pour les prochains cours, nous nous concentrerons uniquement sur les tables Job et Category. Les utilisateurs reviendront plus tard.


Le modèle relationnel

Les différentes histoires d'utilisateurs, que nous avons vu dans le jour 2, nous présentent les objets principaux de notre projet: les emplois, les catégories et les utilisateurs.

Diagramme

La base de données

Pour stocker les emplois, les catégories et les utilisateurs, nous avons besoin d'une base de données relationnelle. Zend Framework 2 permet de se connecter à différents moteur de base de données (MySQL, PostgreSQL, SQLite, ...).

Pour notre projet, nous allons utiliser MySQL. Créons maintenant notre base de  données.

mysqladmin -uroot -p create jobeet
Enter password: your-root-password ## Votre mot de passe ne sera pas visile

Nous allons créer un utilisateur mysql qui aura uniquement accès à la base jobeet.
Connectons-nous à MySQL et créons notre utilisateur:

mysql -uroot -p jobeet   # Le mot de passe de l'utilisateur MySQL root vous sera demandé

GRANT ALL PRIVILEGES 
       ON jobeet.* 
       TO 'jobeet_user'@'localhost'
       IDENTIFIED BY 'votre_password' 
       WITH GRANT OPTION;

Explication:

  • Ligne 1: Nous donnons tous les droits
  • Ligne 2: sur toutes les tables de la base de données jobeet
  • Ligne 3: pour l'utilisateur jobeet_user  (vous pouvez choisir un autre nom d'utilisateur ici).
  • Ligne 4: L'utilisateur aura comme mot de passe 'votre_passeword' (à définir ici')
    • Attention: Le mot de passe de l'utilisateur sera crypté par MySQL, et ne sera pas visible en clair
  • Ligne 5: L'utilisateur pourra créer d'autres utilisateurs avec l'option GRANT


Les tables SQL

Les données seront stockées dans notre base MySQL. Créeons maintenant les différentes tables dont nous avons besoin.

CREATE  TABLE IF NOT EXISTS `category`
(
  `id_category` INT NOT NULL AUTO_INCREMENT ,
  `name` VARCHAR(100) NULL ,
  PRIMARY KEY (`id_category`)
) ENGINE = InnoDB;
CREATE  TABLE IF NOT EXISTS `job`
(
  `id_job` INT NOT NULL AUTO_INCREMENT ,
  `id_category` INT NOT NULL ,
  `type` VARCHAR(255) NULL ,
  `company` VARCHAR(255) NOT NULL ,
  `logo` VARCHAR(255) NULL ,
  `url` VARCHAR(255) NULL ,
  `position` VARCHAR(255) NOT NULL ,
  `location` VARCHAR(255) NOT NULL ,
  `description` TEXT NOT NULL ,
  `how_to_play` TEXT NOT NULL ,
  `is_public` TINYINT(1) NOT NULL ,
  `is_activated` TINYINT(1) NOT NULL ,
  `email` VARCHAR(255) NOT NULL ,
  `created_at` TIMESTAMP NOT NULL ,
  `updated_at` TIMESTAMP NOT NULL ,
  PRIMARY KEY (`id_job`) ,
  CONSTRAINT `fk_job_category` FOREIGN KEY (`id_category`)
    REFERENCES `category` (`id_category`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION
) ENGINE = InnoDB;

CREATE INDEX `fk_job_category2` ON `job` (`id_category` ASC);

La base de données est prête!

Grâce à ZF2, nous n'avons pas besoin d'écrire directement nos requêtes SQL pour récupérer des enregistrements de la base de données. Mais contrairement à Symfony , qui permet de générer vos modèles automatiquement, nous allons devoir écrire nous-même le code de nos objets.

Voyons comment Zend Framework 2 nous permet de "communiquer" avec MySQL.

Un peu de code

Nous allons mettre tout notre code dans un nouveau Module. Celui-ci contiendra nos modèles, nos controlleurs, nos formulaires, nos vues et de la configuration.

Pour charger et configurer un module, Zend Framework 2 à un ModuleManager.
Ce ModuleManager va chercher le fichier Module.php présent à la racine du module (module/Front) et s'attend à trouver une classe /Front/Module. Autrement dit, les classes à l'intérieur d'un module donné auront un namespace (espace de nom) du nom du module (qui est le nom du répertoire du module).

Editons le fichier Module.php

namespace Front;

class Module
{
    public function getAutoloaderConfig()
    {
        return array(
            'Zend\Loader\ClassMapAutoloader' => array(
                __DIR__ . '/autoload_classmap.php',
            ),
            'Zend\Loader\StandardAutoloader' => array(
                'namespaces' => array(
                    __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
                ),
            ),
        );
    }

    public function getConfig()
    {
        return include __DIR__ . '/config/module.config.php';
    }
}

Le ModuleManager appelera les méthodes getAutoloaderConfig() et getConfig() automatiquement pour nous.

Autochargement des fichiers

Notre méthode getAutoloaderConfig() retourne un tableau qui est compatible avec l'AutoloaderFactory de Zend Framework 2. Nous avons besoin d'un fichier autoload_classmap.php (voir la documentation officielle pour ClassmapAutoloader et pour StandardAutoloader pour plus d'information).

En développement, nous n'avons pas besoin de remplir ce fichier autoload_classmap.php.
Nous allons donc lui faire retourné un tableau vide
// dans module/Front/autoload_classmap.php:
    return array();

Configuration

Une fois l'autoloader enregistré, jettons au coup d'oeil à la méthode getConfig() dans Front/Module. Cette méthode charge simplement le fichier /config/module.config.php
Créons le fichier de configuration suivant pour le module Front
// module/Front/config/module.config.php:
return array(
    'controllers' => array(
        'invokables' => array(
            'Front\Controller\Category' => 'Front\Controller\CategoryController',
            // on ajoutera ici les différents controlleurs
        ),
    ),
    // On indique ici où sont les vues du module
    'view_manager' => array(
        'template_path_stack' => array(
            'front' => __DIR__ . '/../view',
        ),
    ),
);

Le ServiceManager passera les informations de configuration aux composants adéquats.

Les fichiers Model

Plusieurs composants du framework permettent d’implémenter la logique métier:

  • Une approche est d'avoir des classes Model représentant chaque entités de votre application puis d'utiliser des objets de mapping pour charger et enregistrer les entités dans la base de données: cette approche implémente le design pattern Table Data Gateway.
  • Une autre approche est d'utiliser un ORM, tel Doctrine ou Propel: nous verrons cette approche hors tutoriel

Nous utiliserons donc la première approche. Nous allons créer une classe CategoryTable, étendant Zend\Db\TableGateway\TableGateway. Chaque objet sera un objet Category (appelé aussi Entité)

Voici le code de la classe Category:
// module/Front/src/Front/Model/Category.php:
namespace Front\Model;

class Category
{
    public $idCategory;
    public $name;
    
    public function exchangeArray($data)
    {
        $this->idCategory = (isset($data['id_category'])) ? $data['id_category'] : null;
        $this->name = (isset($data['name'])) ? $data['name'] : null;
    }
}
Notre entité Category est une simple classe PHP. Nous y ajoutons une méthode exchangeArray(). Cette méthode est nécessaire pour faire fonctionner cette classe Category avec AbstractTableGateway: Elle copie simplement les données du tableau passé en paramètre aux propriété de notre entité.

Ensuite, nous devons étendre Zend\Db\TableGateway\AbstractTableGateway pour créer notre propre classe CategoryTable dans le répertoire Model de notre module.

namespace Front\Model;

use Zend\Db\Adapter\Adapter;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\TableGateway\AbstractTableGateway;

class CategoryTable extends AbstractTableGateway
{
    // Table name in database
    protected $table ='category';

    public function __construct(Adapter $adapter)
    {
        $this->adapter = $adapter;
        $this->resultSetPrototype = new ResultSet();
        $this->resultSetPrototype->setArrayObjectPrototype(new Category());
        $this->initialize();
    }

    public function fetchAll()
    {
        $resultSet = $this->select();
        return $resultSet;
    }

    public function getCategory($id)
    {
        $id  = (int)$id;
        $rowset = $this->select(array('id_category' => $id));
        $row = $rowset->current();

        if (!$row) {
            throw new \Exception("Could not find row $id");
        }

        return $row;
    }

    public function saveCategory(Category $category)
    {
        $data = array(
            'id_category' => $category->idCategory,
            'name'  => $category->name
        );

        $id = (int)$category->id_category;

        if ($id == 0) {
            $this->insert($data);
        } elseif ($this->getCategory($id)) {
            $this->update(
                $data,
                array(
                    'id_category' => $id,
                )
            );
        } else {
            throw new \Exception('Form id does not exist');
        }
    }

    public function deleteCategory($id)
    {
        $this->delete(array('id_category' => $id));
    }
}
Quelques explications sur le code que nous avons écrit.
D'abord, nous indiquons le nom de la table dans la propriété protégée $table.
Ensuite, le constructeur: il accepte en paramètre en Zend\Db\Adapter\Adapter.
Nous indiquons ensuite comment le ResultSet doit être créé, et qu'il doit utiliser l'objet Category. Les classes TableGateway utilise le pattern Prototype pour la création de resultset et d'entités: cela signifie qu'au lieu d’instancier un objet quand on en a besoin, le système clone un objet précédemment instancié.
Les autres méthodes sont des méthodes d'aide, que nous utiliserons plus tard pour interagir avec la base de données: fetchAll() renvoie toutes les catégories de la base sous forme de ResultSet, getCategory() retourne un enregistrement de type Category, saveCategory() crée ou met à jour un enregistrement et deleteCategory supprime complètement l'enregistrement. Le code de ces méthodes est pour vous, je l'espère, assez explicite.

Utilisation du ServiceManager

Afin de toujours utiliser la même instance de notre CategoryTable, nous allons utiliser le ServiceManager pour définir comment en créer une. Cela se fait assez facilement dans la classe Module de notre module: nous allons créer une méthode getServiceConfig() qui sera automatiquement appelé par le ModuleManager. Nous serons ainsi capable de récupérer cette instance dans nos controleurs quand nous en aurons besoin.
// module/Front/Module.php:
namespace Front;

// Ajouter ici:
use Front\Model\CategoryTable;

class Module
{
    [...]

    // Ajouter cette méthode après getAutoloaderConfig() et getConfig():
    public function getServiceConfig()
    {
        return array(
            'factories' => array(
                'Front\Model\CategoryTable' =>  function($sm) {
                    $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
                    $table     = new CategoryTable($dbAdapter);
                    return $table;
                },
            ),
        );
    }
}

Nous arrivons à la fin du jour 3 de ce tutoriel Jobeet Pour Zend Framework 2.

Avant d'accèder au jour 4, je vous laisse écrire (comme exercice) les classes Job, JobTable, User et UserTable.
Avant d'accéder au jour 4, ajoutons les classes Job et JobTable
// module/Front/src/Front/Model/Job.php
namespace Front\Model;

class Job
{
    public $idJob;
    public $idCategory;
    public $idUser;
    public $type;
    public $company;
    public $logo;
    public $url;
    public $position;
    public $location;
    public $description;
    public $howToPlay;
    public $isPublic;
    public $isActivated;
    public $email;
    public $createdAt;
    public $updatedAt;
    
    public function exchangeArray($data)
    {
        $this->idJob = (isset($data['id_job'])) ? $data['id_job'] : null;
        $this->idCategory = (isset($data['id_category'])) ? $data['id_category'] : null;
        $this->idUser = (isset($data['id_user'])) ? $data['id_user'] : null;
        $this->type = (isset($data['type'])) ? $data['type'] : null;
        $this->company = (isset($data['company'])) ? $data['company'] : null;
        $this->logo = (isset($data['logo'])) ? $data['logo'] : null;
        $this->url = (isset($data['url'])) ? $data['url'] : null;
        $this->position = (isset($data['position'])) ? $data['position'] : null;
        $this->location = (isset($data['location'])) ? $data['location'] : null;
        $this->description = (isset($data['description'])) ? $data['description'] : null;
        $this->howToPlay = (isset($data['how_to_play'])) ? $data['how_to_play'] : null;
        $this->isPublic = (isset($data['is_public'])) ? $data['is_public'] : null;
        $this->isActivated = (isset($data['is_activated'])) ? $data['is_activated'] : null;
        $this->email = (isset($data['email'])) ? $data['email'] : null;
        $this->createdAt = (isset($data['created_at'])) ? $data['created_at'] : null;
        $this->updatedAt = (isset($data['updated_at'])) ? $data['updated_at'] : null;
    }
}


// module/Front/src/Front/Model/JobTable.php
namespace Front\Model;

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

class JobTable extends AbstractTableGateway
{
    protected $table ='job';

    public function __construct(Adapter $adapter)
    {
        $this->adapter = $adapter;
        $this->resultSetPrototype = new ResultSet();
        $this->resultSetPrototype->setArrayObjectPrototype(new Job());
        $this->initialize();
    }

    public function fetchAll()
    {
        $resultSet = $this->select();
        return $resultSet;
    }
    
    public function getJob($id)
    {
        $id  = (int)$id;
        $rowset = $this->select(array('id_job' => $id));
        $row = $rowset->current();

        if (!$row) {
            throw new \Exception("Could not find row $id");
        }

        return $row;
    }

    public function saveJob(Job $job)
    {
        $data = array(
            'id_job' => $job->idJob,
            'id_category' => $job->idCategory,
            'id_user' => $job->idUser,
            'type' => $job->type,
            'company' => $job->company,
            'logo' => $job->logo,
            'url' => $job->url,
            'position' => $job->position,
            'location' => $job->location,
            'description' => $job->description,
            'howToPlay' => $job->howToPlay,
            'isPublic' => $job->isPublic,
            'isActivated' => $job->isActivated,
            'email' => $job->email,
            'createdAt' => $job->createdAt
        );

        $id = (int)$job->idJob;

        if ($id == 0) {
            $this->insert($data);
        } elseif ($this->getJob($id)) {
            $this->update(
                $data,
                array(
                    'id_job' => $id,
                )
            );
        } else {
            throw new \Exception('Form id does not exist');
        }
    }

    public function deleteJob($id)
    {
        $this->delete(array('id_job' => $id));
    }
}

N'oubliez pas d'ajouter la classe JobTable dans le fichier Module.php
// module/Front/Module.php:
namespace Front;

// Ajouter ici:
use Front\Model\CategoryTable;

class Module
{
    [...]

    public function getServiceConfig()
    {
        return array(
            'factories' => array(
                'Front\Model\CategoryTable' =>  function($sm) {
                    $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
                    $table     = new CategoryTable($dbAdapter);
                    return $table;
                },
                // On ajoute JobTable ici
                'Front\Model\JobTable' =>  function($sm) {
                    $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
                    $table     = new JobTable($dbAdapter);
                    return $table;
                },
            ),
        );
    }
}

Télécharger les source du jour 3 sur mon compte Github

Jour 4 - Controller et Vue


Il faudra que je reprenne ce tutoriel un de ces jours. On m'a fait remarqué la forte ressemble avec un article qui m'a servi de suppport. Cette article étant un de mes débuts de blogger, je vois qu'il n'est pas évident de "s'inspirer de..." sans "plagier". En tout cas, merci à Philippe Peso, pour l'excellent tuto de Rob Allen. Pour le lire, ca se passe sur developpez.com.
21 commentaires:
  1. C'est dommage de ne pas utiliser les rows et faire le save et delete au niveau de l'instance Table.
    Ton Category pourrait étendre Row et ça éviterais que tu fasses tout ce qui est accesseurs.

    En gros c'est le même soucis que la doc pour ZF1 pour commencer, on indique de créer de simple classe qui sont des modèles, alors qu'il est possibles d'avoir des rows

    RépondreSupprimer
    Réponses
    1. Pour le moment, je vais rester simple.
      Mais j'ai prévu de faire des améliorations (plus tard) dans les tutos (notamment sur le RowGateway et sur l'implémentation de Doctrine 2)

      Supprimer
  2. Bonjour,

    Sympa le tuto. Dis, est-ce qu'une vidéo est prévue pour ça ?
    Mon seul souci, c'est le vocabulaire technique.

    Si quelqu'un a une astuce pour que je me sente moins perdue ?

    Merci

    RépondreSupprimer
    Réponses
    1. Bonjour,

      Non, malheureusement, pas de vidéos prévues pour les tutos.

      Sur quoi es-tu perdu ?

      Supprimer
  3. Petite relecture (juste que le tuto soit encore meilleur... ;-) ):

    * Pour la requête de création de la table 'job', comme le champ 'id_user" n'est pas défini, il faut le supprimer de la clé primaire aussi, de même qu'il ne faut pas définir la contrainte 'fk_job_users1' (en attendant de créer la table 'users').
    * Pour le fichier 'autoload_classmap.php', le commentaire reprenant le chemin ne devrait-il pas être: 'module/*Front*/autoload_classmap.php'?
    * Pour le fichier 'module.config.php', le commentaire reprenant le chemin ne devrait-il pas être: 'module/*Front*/config/module.config.php'?
    * Pour la classe 'Category', les attributs ne devraient-ils pas être 'public' (comme pour la classe 'Job')?
    * Dans la classe 'CategoryTable', dans la méthode 'getCategory', tu as: '$rowset = $this->select(array('id' => $id));'. Ce ne serait pas plutôt '$rowset = $this->select(array('id_category' => $id));'?
    * Dans la classe 'CategoryTable', dans la méthode 'deleteCategory', tu as: '$this->delete(array('id' => $id));'. Ce ne serait pas plutôt '$this->delete(array('id_category' => $id));'?
    * Dans l'explication de la classe 'CategoryTable': Nous indiquons ensuite comment le *Resultet* (=> ResultSet?) doit être créé, ...
    * Dans la classe 'JobTable', dans la méthode 'deleteJob', tu as: '$this->delete(array('id' => $id));'. Ce ne serait pas plutôt '$this->delete(array('id_job' => $id));'?

    RépondreSupprimer
    Réponses
    1. Il me semble que les attributs devraient être 'private' partout plutôt que 'public'. (enfin, ça me parait plus sûr en tout cas)

      Supprimer
    2. Merci Sébastien pour ta relecture et tes retours (que je viens de prendre en compte).

      @Loïc, tu as tout à fait raison: les attributs devraient être soit protected, soit private.
      Cependant, le tuto concerne ZF2 uniquement, et pas les "bonnes pratiques" de développement. Je considère que les lecteurs ont un minimum de connaissance du PHP et du développement objet...
      Et puis c'est un gain de temps (pour moi) pour l'écriture du tuto et du code associé: pas besoin d'écrire les accesseurs de consultation / modification.

      Supprimer
  4. La 2ieme contrainte est à supprimer également dans le "CREATE TABLE IF NOT EXISTS `job`". Et aussi le id_suer dans le segment "PRIMARY KEY (`id_job`, `id_user`) ," de la table JOB

    RépondreSupprimer
  5. bonjour;

    et merci pour vos tuto je suis impatient pour les nouveau tuto surtou ceux zf2,j'ai une question:

    je souhaite aporter des modification sur le module album et l'exporter mai je ne trouve pas le "NOM DE LA TABLE" afin de la changer ex:"produit" a la place de "album".
    Dans votre tuto vous avez mis [protected $table ='category';] dans "categoryTable",ayant suivi le tuto de zend framework [protected $tableGateway;]....
    j'ai chercher dans albumTable ,album et vu que le module s'appel album c'est pas facile d'avoir 2km d'erreur....

    merci de bien vouloir me repondre.

    RépondreSupprimer
  6. Bonjour Yacine,

    Si tu as suivi le tuto Album sur le site de ZF2, il te faut regarder dans Module.php du mobule Album. Dans la méthode getServiceConfig(), tu devrais trouver ton bonheur: ils définissent des fabriques pour Album\Model\AlbumTable et AlbumTableGateway. Le nom de la table est dans le constructeur de TableGateway


    'AlbumTableGateway' => function ($sm) {
    $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
    $resultSetPrototype = new ResultSet();
    $resultSetPrototype->setArrayObjectPrototype(new Album());
    return new TableGateway('album', $dbAdapter, null, $resultSetPrototype);
    },

    RépondreSupprimer
    Réponses
    1. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa je me sent bet surtout que j'avais modifier ce passage et a d'autre endroit et ça n'avais po marcher.....merci

      Supprimer
    2. Content d'avoir pu t'aider :-)
      Ca m'est arrivé aussi en suivant le tuto officiel et en commençant le mien...
      c'est sûr que c'est pas l'endroit où l'on s'attend à trouver l'info (c'est pour ça, entre autre, que je ne gère pas les modèles / tables comme eux)

      Supprimer
  7. bonsoir,
    merci pour le tuto :)
    pour lister la liste des catégories j'ai rencontrer cette erreur:

    RépondreSupprimer
  8. bonsoir,
    merci pour le tuto :)
    pour lister la liste des catégories j'ai rencontrer cette erreur:

    <get('Zend\Db\Adapter...')
    #1 [internal function]: Application\{closure}(Object(Zend\ServiceManager\ServiceManager), 'applicationmode...', 'Application\Mod...')
    #2 C:\wamp\www\zendy2\vendor\zendframework\zendframework\library\Zend\ServiceManager\ServiceManager.php(853): call_user_func(Object(Closure), Object(Zend\ServiceManager\ServiceManager), 'applicationmode...', 'Application\Mod...')
    #3 C:\wamp\www\zendy2\vendor\zendframework\zendframework\library\Zend\ServiceManager\ServiceManager.php(985): Zend\ServiceManager\ServiceManager->createServiceViaCallback(Object(Closure), 'applicationmode...', 'Application\Mod...')
    #4 C:\wamp\www\zendy2\vendor\zendframework\zendframework\library\Zend\ServiceManager\ServiceManager.php(598): Zend\ServiceManager\ServiceManager->createFromFactory('applicationmode...', 'Application\Mod...')
    #5 C:\wamp\www\zendy2\vendor\zendframework\zendframework\library\Zend\ServiceManager\ServiceManager.php(557): Zend\ServiceManager\ServiceManager->doCreate('Application\Mod...', 'applicationmode...')
    #6 C:\wamp\www\zendy2\vendor\zendframework\zendframework\library\Zend\ServiceManager\ServiceManager.php(481): Zend\ServiceManager\ServiceManager->create(Array)
    #7 C:\wamp\www\zendy2\module\Application\src\Application\Controller\IndexController.php(26): Zend\ServiceManager\ServiceManager->get('Application\Mod...')
    #8 C:\wamp\www\zendy2\module\Application\src\Application\Controller\IndexController.php(33): Application\Controller\IndexController->getCategoryTable()
    #9 C:\wamp\www\zendy2\vendor\zendframework\zendframework\library\Zend\Mvc\Controller\AbstractActionController.php(83): Application\Controller\IndexController->indexAction()
    #10 [internal function]: Zend\Mvc\Controller\AbstractActionController->onDispatch(Object(Zend\Mvc\MvcEvent))
    #11 C:\wamp\www\zendy2\vendor\zendframework\zendframework\library\Zend\EventManager\EventManager.php(468): call_user_func(Array, Object(Zend\Mvc\MvcEvent))
    #12 C:\wamp\www\zendy2\vendor\zendframework\zendframework\library\Zend\EventManager\EventManager.php(207): Zend\EventManager\EventManager->triggerListeners('dispatch', Object(Zend\Mvc\MvcEvent), Object(Closure))
    #13 C:\wamp\www\zendy2\vendor\zendframework\zendframework\library\Zend\Mvc\Controller\AbstractController.php(117): Zend\EventManager\EventManager->trigger('dispatch', Object(Zend\Mvc\MvcEvent), Object(Closure))
    #14 C:\wamp\www\zendy2\vendor\zendframework\zendframework\library\Zend\Mvc\DispatchListener.php(114): Zend\Mvc\Controller\AbstractController->dispatch(Object(Zend\Http\PhpEnvironment\Request), Object(Zend\Http\PhpEnvironment\Response))
    #15 [internal function]: Zend\Mvc\DispatchListener->onDispatch(Object(Zend\Mvc\MvcEvent))
    #16 C:\wamp\www\zendy2\vendor\zendframework\zendframework\library\Zend\EventManager\EventManager.php(468): call_user_func(Array, Object(Zend\Mvc\MvcEvent))
    #17 C:\wamp\www\zendy2\vendor\zendframework\zendframework\library\Zend\EventManager\EventManager.php(207): Zend\EventManager\EventManager->triggerListeners('dispatch', Object(Zend\Mvc\MvcEvent), Object(Closure))
    #18 C:\wamp\www\zendy2\vendor\zendframework\zendframework\library\Zend\Mvc\Application.php(309): Zend\EventManager\EventManager->trigger('dispatch', Object(Zend\Mvc\MvcEvent), Object(Closure))
    #19 C:\wamp\www\zendy2\public\index.php(17): Zend\Mvc\Application->run()
    #20 {main}

    >>

    RépondreSupprimer
  9. pourqoui le backgrounde de tuto est en noir, c'est assez deficile de lire ce tuto

    RépondreSupprimer
  10. Bonjour,

    J'ai une question au niveau des appels de Base de données :

    Pour le cas d'un appel d'une procedure stocké du type :

    SET @p0='.$username.'; SET @p1='.$unique_code.';
    CALL `FiltreNumeroUnique`(@p0, @p1, @p2);
    SELECT @p2 AS `p_shipment_number`;

    Tu ferrais comment ?

    RépondreSupprimer
    Réponses
    1. Bonjour,

      De mémoire, je le ferais comme ça (ou qui s'en approche)

      // 1 - D'abord, l'appel à la procédure stockée
      $stmt = $this->adapter->createStatement();
      $stmt->prepare('CALL FiltreNumeroUnique(?, ? ,@p2);');
      $stmt->getResource()->bindParam(1, $username);
      $stmt->getResource()->bindParam(2, $unique_code);
      $result = $this->getResultSetPrototype()->initialize($stmt->execute());

      // 2 - Ensuite, tu récupéres ton parametre en sortie
      $stmtResult = $this->adapter->createStatement();
      $stmtResult->prepare("SELECT @p2 AS p_shipment_number");
      $result = $stmtResult->execute();
      $output = $result->current();
      return $output['p_shipment_number'];

      Supprimer
    2. ça marche bien, enfin presque, pour deux cas :
      VIDE ou quand il y a plusieurs valeurs.
      Je modifie dans la l'appel ou je fais des filtres ?

      Supprimer
    3. bonsoir,

      Désolé, je ne comprend pas :-)
      Qu'est-ce qui est vide ? le retour @p2 ou tes parametres username et unque_code ?

      qu'entends-tu par "plusieurs valeurs" ? plusieurs valeur en retour ?

      $stmtResult->prepare("SELECT @p2 AS p_shipment_number, @p3 AS valeur1, @p4 as autre_valeur");

      $result = $stmtResult->execute();
      $output = $result->current();
      return $output // p-e comme ca ? avec output = ['p_shipment_number' =>unevaleur, 'valeur1'=>autrevalur, ...) ?



      Supprimer
    4. Ce n'est pas le code, il est bon, c'est ma procédure stocké qui ne va pas.
      Une procédure ne peut ramène qu'une valeur.
      Désolè du dérangement et encore merci pour ton aise.

      Supprimer
  11. Merci d'avance de ta réponse.

    RépondreSupprimer