In questo articolo vedremo come installare e configurare Doctrine 2 con Zend Framework 2.

  1. Installazione di Doctrine 2 su Zend Framework 2
  2. Configurazione della connessione
  3. Registrare i moduli nell’applicazione
  4. Configurare Doctrine driver nel modulo custom
  5. Generare un’entità da console
  6. Istanziare l’entità
  7. Configurazione multi DB

Installazione di Doctrine 2 su Zend Framework 2

Per installare Doctrine 2 sarà necessario aggiungere 1 linea di configurazione nel composer.json.
Beberlei/DoctrineExtensions non è obbligatorio ma potrebbe tornare utile in quanto contiene diverse estensioni utili,
come per esempio la possibilità di utilizzare le funzioni MATCH.. AGAINST e CONCAT_WS.

composer.json

  "beberlei/DoctrineExtensions": "dev-master",
  "doctrine/doctrine-orm-module": "0.*",

Eseguiamo l’aggiornamento di composer per scaricare e registrare questi moduli nella nostra applicazione.

  php composer.phar update

Configurazione della connessione

Per configurare la connessione di Doctrine 2 sarà necessario creare un file di configurazione all’interno della directory autoload del nostro progetto. Possiamo chiamare il file doctrine.local.php.
Il nome del file non è casuale, il suffisso local sta ad indicare che questa configurazione è valida nel nostro ambiente locale, la configurazione di produzione potrà quindi essere contenuta in un file doctrine.global.php o
doctrine.production.php.

config/autoload/doctrine.local.php

return array(
    'doctrine' => array(
        'connection' => array(
            'orm_default' => array(
                'doctrine_type_mappings' => array('enum' => 'string'),
                'driverClass' => 'Doctrine\DBAL\Driver\PDOMySql\Driver',
                'params' => array(
                    'host' => 'xxxx',
                    'port' => '3306',
                    'user'     => 'xxxx',
                    'password' => 'xxxx',
                    'dbname' => 'xxxx',
                )
            ),
        )
    )
);

Nella configurazione di qui sopra saranno ovviamente da sostituire le ‘xxxx’ con dei valori validi. Inoltre ho inserito la chiave ‘doctrine_type_mappings’ con il valore array(‘enum’ => ‘string’), che indica che i
tipi di campo enum saranno trattati come stringhe.

Al fine poter illustrare degli esempi più chiari possibili, suggerisco di creare la seguente tabella su un nuovo database (nome del db a vostra scelta). Questo schema è lo stesso del modulo ZfcUser.
Creeremo una entità di Doctrine su questa tabella.

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";

Registrare i moduli nell’applicazione

Ricordiamoci di registrare i moduli di Doctrine 2 (ed eventuali altri moduli necessari) nell’applicazione

return array(
    'modules' => array(
        'Application',
        'MyAwesomeModuleModule', // qui
        'DoctrineModule',, // qui
        'DoctrineORMModule', // e qui
    ),
    'module_listener_options' => array(
        'module_paths' => array(
            './module',
            './vendor'
            ),
        'config_glob_paths' => array('config/autoload/{,*.}{global,local}.php')
        )
    );

Configurare Doctrine driver nel modulo custom

Aprite il file config/module.config.php del vostro modulo custom (MyAwesomeModule nel mio caso) ed inserite
la seguente configurazione per usare Doctrine utilizzando la configurazione con annotation.

Inserire il corretto namespace in cima a questo file è indispensabile in quanto solo così avremo la possibilità di usare la keyword __NAMESPACE__.

namespace MyAwesomeModule;

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

Generare un’entità da console

Dalla console di GIT eseguite i seguenti comandi, il primo genererà la classe dell’entità con tante proprietà quanti sono i nomi dei campi,
il secondo genererà setters ed i getters per le proprietà, assicuratevi di modificare correttamente il namespace del modulo.

$ ./vendor/bin/doctrine-module orm:convert-mapping --from-database --namespace="MyAwesomeModule\Entity\\" annotation module/MyAwesomeModule/src

$ ./vendor/bin/doctrine-module orm:generate-entities module\MyAwesomeModule\src

Istanziare l’entità

Arrivati a questo punto dovremmo avere all’interno di MyAwesomeModule/Entity la classe User.php. Per testare il suo corretto funzionamento,
in una qualsiasi action eseguiamo il seguente codice

    $u = new \MyAwesomeModule\Entity\User();
    var_dump($u); exit;
    
    /* Se tutto è a posto otterremo
    object(MyAwesomeModule\Entity\User)#511 (8) { 
    ["userId":"MyAwesomeModule\Entity\User":private]=> NULL ["username":"MyAwesomeModule\Entity\User":private]=> NULL 
    ["email":"MyAwesomeModule\Entity\User":private]=> NULL ["displayName":"MyAwesomeModule\Entity\User":private]=> NULL 
    ["password":"MyAwesomeModule\Entity\User":private]=> NULL ["company":"MyAwesomeModule\Entity\User":private]=> NULL 
    ["locale":"MyAwesomeModule\Entity\User":private]=> NULL ["state":"MyAwesomeModule\Entity\User":private]=> NULL } */

Nel caso volessimo recuperare l’entity manager dal service manager, useremo la chiave doctrine.entitymanager.orm_another

    $em = $sm->get('doctrine.entitymanager.orm_another');

Configurazione multi DB

Per prima cosa provvediamo a creare un nuovo modulo nella nostra applicazione, assicurandoci che sia registrato per ultimo nel file config/application.config.php. Creando il modulo con ZFtool questo dovrebbe essere registrato automaticamente.

Quindi inseriamo una nuova configurazione nel file doctrine.global.php all’interno della directory autoload.

return array(
    'doctrine' => array(
        'connection' => array(
        
            [....]
                
            'orm_another' => array(                
                'doctrine_type_mappings' => array('enum' => 'string'),
                'driverClass' => 'Doctrine\DBAL\Driver\PDOMySql\Driver',
                'params' => array(
                    'host'     => 'xxxxx',
                    'port'     => '3306',
                    'user'     => 'xxxxx',
                    'password' => 'xxxxx',
                    'dbname'   => 'xxxxx',
                )
            ),
        
        )
    )
);

Per evitare che il file di configurazione del modulo module.config.php diventi eccessivamente pesante,
ho scelto di dividerlo in 3 parti, questo grazie all’implementazione delle interfacce ControllerProviderInterface, ConfigProviderInterface e ServiceProviderInterface.

Per configurare Doctrine multiDB sarà inoltre necessario implementare le interfacce InitProviderInterface e BootstrapListenerInterface,
per poter usufruire dei metodi init e onBootstrap.

File Module.php

namespace MyAwesomeModule;

use Zend\ModuleManager\Feature\AutoloaderProviderInterface;
use Zend\ModuleManager\Feature\ControllerProviderInterface;
use Zend\ModuleManager\Feature\BootstrapListenerInterface;
use Zend\ModuleManager\Feature\ServiceProviderInterface;
use Zend\ModuleManager\Feature\ConfigProviderInterface;
use Zend\ModuleManager\Feature\InitProviderInterface;
use Zend\ModuleManager\ModuleManagerInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Loader\AutoloaderFactory;
use Zend\Loader\StandardAutoloader;
use Zend\EventManager\EventInterface;

use Doctrine\ORM\Tools\Console\ConsoleRunner;
use Symfony\Component\Console\Helper\DialogHelper;
use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper;
use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper;
use Doctrine\DBAL\Migrations\Tools\Console\Command\DiffCommand;
use Doctrine\DBAL\Migrations\Tools\Console\Command\ExecuteCommand;
use Doctrine\DBAL\Migrations\Tools\Console\Command\GenerateCommand;
use Doctrine\DBAL\Migrations\Tools\Console\Command\MigrateCommand;
use Doctrine\DBAL\Migrations\Tools\Console\Command\StatusCommand;
use Doctrine\DBAL\Migrations\Tools\Console\Command\VersionCommand;

class Module implements
    AutoloaderProviderInterface,
    ControllerProviderInterface,
    BootstrapListenerInterface,
    ServiceProviderInterface,
    ConfigProviderInterface,
    InitProviderInterface
{    
    public function init(ModuleManagerInterface $manager)
    {
        $events = $manager->getEventManager();
        // Initialize logger collector once the profiler is initialized itself
        $events->attach('profiler_init', function() use ($manager) {
            $manager->getEvent()->getParam('ServiceManager')
                ->get('doctrine.sql_logger_collector.orm_another'); // moduificare nome
        });
    }
    
    public function onBootstrap(EventInterface $e)
    {
        /* @var $app \Zend\Mvc\ApplicationInterface */
        $app    = $e->getTarget();
        $events = $app->getEventManager()->getSharedManager();

        // Attach to helper set event and load the entity manager helper.
        $events->attach('doctrine', 'loadCli.post', function(EventInterface $e) {
            /* @var $cli \Symfony\Component\Console\Application */
            $cli = $e->getTarget();
    
            ConsoleRunner::addCommands($cli);
    
            if (class_exists('Doctrine\\DBAL\\Migrations\\Version')) {
                $cli->addCommands(array(
                    new DiffCommand(),
                    new ExecuteCommand(),
                    new GenerateCommand(),
                    new MigrateCommand(),
                    new StatusCommand(),
                    new VersionCommand(),
                ));
            }
    
            /* @var $sm ServiceLocatorInterface */
            $sm = $e->getParam('ServiceManager');
            /* @var $em \Doctrine\ORM\EntityManager */
            $em = $sm->get('doctrine.entitymanager.orm_another');
            $helperSet = $cli->getHelperSet();
            $helperSet->set(new DialogHelper(), 'dialog');
            $helperSet->set(new ConnectionHelper($em->getConnection()), 'db');
            $helperSet->set(new EntityManagerHelper($em), 'em');
        });
    }
    
    public function getConfig()
    {
        return include __DIR__ . '/config/module.config.php';
    }
    
    public function getControllerConfig()
    {
        return include __DIR__ . '/config/controllers.config.php';
    }

    public function getServiceConfig()
    {
        return include __DIR__ . '/config/services.config.php';
    }

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

Di seguito invece la configurazione necessaria nel file module.config.php

namespace MyAwesomeModule;

return array(
    'doctrine' => array(
        'driver' => array(
            __NAMESPACE__ . '_entities' => array(
                'class' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
                'cache' => 'array',
                'paths' => array(
                    __DIR__ . '/../src/' . __NAMESPACE__ . '/Entity'
                ),
            ),
            
            // Lasciamo invariata questa chiave (orm_default), è utilizzata dalla CLI
            'orm_another' => array(
                'class'   => 'Doctrine\ORM\Mapping\Driver\DriverChain',
                'drivers' => array(
                    __NAMESPACE__ . '\Entity' => __NAMESPACE__ . '_entities'
                )
            )
        ),
            
        'configuration' => array(
            'orm_another' =>  array(
                'metadata_cache'    => 'array',
                'query_cache'       => 'array',
                'result_cache'      => 'array',
                'driver'            => 'orm_another',
                'generate_proxies'  => true,
                'proxy_dir'         => 'data/DoctrineORMModule/Proxy',
                'proxy_namespace'   => 'DoctrineORMModule\Proxy',
                'filters'           => array()
            ),
        ),
            
        'entitymanager' => array(
            'orm_another' => array(
                'connection'    => 'orm_another',
                'configuration' => 'orm_another'
            )
        ),
            
        'eventmanager' => array(
            'orm_another' => array()
        ),
            
        'sql_logger_collector' => array(
            'orm_another' => array()
        ),
            
        'entity_resolver' => array(
            'orm_another' => array()
        ),
    ),
);

Infine la configurazione necessaria del service manager, file services.config.php

namespace MyAwesomeModule;

return array(
    'factories' => array(
        'doctrine.authenticationadapter.orm_another'  => new \DoctrineModule\Service\Authentication\AdapterFactory('orm_another'),
        'doctrine.authenticationstorage.orm_another'  => new \DoctrineModule\Service\Authentication\StorageFactory('orm_another'),
        'doctrine.authenticationservice.orm_another'  => new \DoctrineModule\Service\Authentication\AuthenticationServiceFactory('orm_another'),
        'doctrine.connection.orm_another'             => new \DoctrineORMModule\Service\DBALConnectionFactory('orm_another'),
        'doctrine.configuration.orm_another'          => new \DoctrineORMModule\Service\ConfigurationFactory('orm_another'),
        'doctrine.entitymanager.orm_another'          => new \DoctrineORMModule\Service\EntityManagerFactory('orm_another'),
        'doctrine.driver.orm_another'                 => new \DoctrineModule\Service\DriverFactory('orm_another'),
        'doctrine.eventmanager.orm_another'           => new \DoctrineModule\Service\EventManagerFactory('orm_another'),
        'doctrine.entity_resolver.orm_another'        => new \DoctrineORMModule\Service\EntityResolverFactory('orm_another'),
        'doctrine.sql_logger_collector.orm_another'   => new \DoctrineORMModule\Service\SQLLoggerCollectorFactory('orm_another'),
        'doctrine.mapping_collector.orm_another'      => function (\Zend\ServiceManager\ServiceLocatorInterface $sl) {
            $em = $sl->get('doctrine.entitymanager.orm_another');
            return new \DoctrineORMModule\Collector\MappingCollector($em->getMetadataFactory(), 'orm_another_mappings');
        },
        'DoctrineORMModule\Form\Annotation\AnnotationBuilder' => function(\Zend\ServiceManager\ServiceLocatorInterface $sl) {
            return new \DoctrineORMModule\Form\Annotation\AnnotationBuilder($sl->get('doctrine.entitymanager.orm_another'));
        },
    ),
);

Se tutto è stato configurato correttamente potremo usare la console di doctrine in modo che i comandi utilizzino questa connessione che abbiamo appena configurato, proviamo quindi a generare le entità seguendo le indicazioni date qui.

NOTA: la configurazione dell’entity manager attiva da CLI è relativa all’ultima configurazione registrata in doctrine.{ambiente}.php in autoload, quindi nel caso si volesse utilizzare la console con un’altra connessione sarà necessario commentare eventuali connessioni successive.


Dubbi? Lasciate un commento!

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

5 Comments

  1. Ciao Sergio,
    complimenti per l’ottimo lavoro che hai fatto ma ho un problema nella generazione delle classi entità, il problema probabilmente deriva dal fatto che ho tabelle con chiave esterne di questo tipo:
    CREATE TABLE `pippo` (
    `pippo_id` int(11) NOT NULL AUTO_INCREMENT,
    `alias` varchar(75) COLLATE latin1_german1_ci NOT NULL,
    `type_id` int(11) NOT NULL,
    PRIMARY KEY (`pippo_id`),
    KEY `fk_pippo_type1` (`type_id`),
    CONSTRAINT `fk_pippo_type10` FOREIGN KEY (`type_id`) REFERENCES `servers2_type` (`type_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
    CONSTRAINT `pippofk_1` FOREIGN KEY (`type_id`) REFERENCES `type` (`type_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
    )

    in sostanza la chiave esterna si chiama come la primary key della tabella a cui fà riferimento, su altri DB dove non ho utilizzato questa convenzione e non ho trovato problemi.
    C’è qualche modo per risolverlo? premetto che non posso modificare il DB.

    Grazie in anticipo

    • Ciao Giovanni, premettendo che anche se ho sempre parlato di generazione delle entità non è il metodo consigliato di lavorare con Doctrine 2 ( scriverò qualcosa sull’argomento… ), non c’è nessun problema ad utilizzare chiavi esterne con lo stesso nome.

      Potresti descrivere più in dettaglio il problema? Quale errore ti viene restituito dalla console?

      Ciao

      • [Doctrine\ORM\Mapping\MappingException]
        Property “Type” in “Entity\Pippo” was already declared, but it must be declared only once

        Premetto che non sono molto esperto con ZF2 stò cercando di addentrarmi adesso.
        Sono molto interessato al modo migliore di poter utilizzare Docrine 2, aspetto la nuova guida.
        Grazie.

        • Hai risolto? In quale passaggio ti da questo errore?

          • sono riuscito a capire proprio adesso il problema. Era sul database avevo 2 chiavi esterne uguali, quindi quando andava a creare la proprietà di referenza alla tabella andava in errore.
            E’ un problema che mi si è creato con mysql workbech.
            Scusa se ti scoccio ancora, ma sapresti dirmi quando potrò leggere la tua guida su Docrine 2? oppure potresti indicarmi dove posso trovare una guida su come lavorare nel modo giusto con doctrine2 e zf2?

            Grazie.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>