2012
01.01

Dalsza część tutoriala będzie korzystała z systemu kontroli wersji GIT. Nie musicie się z nim zapoznawać, żeby pomyślnie ukończyć tutorial. Polecenia podane są „na tacy”. Wystarczy przepisać.

Pobieramy Doctrine 2

Aby móc z niego korzystać musimy go najpierw pobrać. Ja wykorzystam repozytorium GIT.
Przechodzimy do katalogu głównego naszej aplikacji i wydajemy następujące polecenia:

git submodule add git://github.com/doctrine/doctrine2.git vendor/Doctrine
cd vendor/Doctrine/
git checkout 2.1.x
git submodule init
git submodule update

Pobiorą one Doctrine 2 (ORM, Common oraz DBAL).

Autoloader Doctrine 2

Doctrine 2 ma swój własny autoloader. Musimy go więc zarejestrować. Zarejestrujemy go obok autoloadera Zenda.

Uwaga! Na potrzeby własnego hostingu przemianowałem folder public na htdocs

W pliku htdocs/index.php zaraz po chdir(dirname(__DIR__)); rejestrujemy autoloader Doctrine 2

require_once (getenv('D2_PATH') ? : 'vendor/Doctrine/lib') . '/Doctrine/ORM/Tools/Setup.php';
Doctrine\ORM\Tools\Setup::registerAutoloadGit(getenv('D2_PATH') ? : 'vendor/Doctrine/');

Docelowo plik htdocs/index.php wygląda tak:

<?php
// Usunąć na serwerze produkcyjnym
error_log(E_ALL);
ini_set("display_errors", "On");

chdir(dirname(__DIR__));

// Doctrine autoloader
require_once (getenv('D2_PATH') ? : 'vendor/Doctrine/lib') . '/Doctrine/ORM/Tools/Setup.php';
Doctrine\ORM\Tools\Setup::registerAutoloadGit(getenv('D2_PATH') ? : 'vendor/Doctrine/');

// Zend Framework autoloader
require_once (getenv('ZF2_PATH') ? : 'vendor/ZendFramework/library') . '/Zend/Loader/AutoloaderFactory.php';
Zend\Loader\AutoloaderFactory::factory(array('Zend\Loader\StandardAutoloader' => array()));

$appConfig = include 'config/application.config.php';

$listenerOptions = new Zend\Module\Listener\ListenerOptions($appConfig['module_listener_options']);
$defaultListeners = new Zend\Module\Listener\DefaultListenerAggregate($listenerOptions);
$defaultListeners->getConfigListener()->addConfigGlobPath('config/autoload/*.config.php');

$moduleManager = new Zend\Module\Manager($appConfig['modules']);
$moduleManager->events()->attachAggregate($defaultListeners);
$moduleManager->loadModules();

// Create application, bootstrap, and run
$bootstrap = new Zend\Mvc\Bootstrap($defaultListeners->getConfigListener()->getMergedConfig());
$application = new Zend\Mvc\Application;
$bootstrap->bootstrap($application);
$application->run()->send();

Modele

Doctrine 2 potrzebuje od nas 3 rzeczy jeśli chodzi o modele obiektów:

  1. Entities – Klas dla PHP
  2. Yaml – Modeli z metadanymi opisujących mapowanie obiektów i relacji na bazę danych
  3. Proxy – Klas pośredniczących (Proxy)

Mapowanie nie musi być zapisane w Yamlu. Ja wybrałem jednak tę metodę, ponieważ mapowanie w Yamlu wydaje mi się najbardziej przejrzyste.

Tworzymy więc następującą strukturę katalogów:

models/
   Entities/
   Proxies/
   Yaml/

Pliki konfiguracyjne

Do pliku config/autoload/local.config.php dodajemy konfigurację bazy danych dla Doctrine. Dla uproszczenia zdecydowałem się użyć bazy danych SQLite.

'doctrine' => array(
        'connection' => array (
            'driver' => 'pdo_sqlite',
            'path' => 'database.sqlite'
         )
    )

Po zmianach mój plik config/autoload/local.config.php wygląda tak:

<?php
return array(
    'doctrine' => array(
        'connection' => array (
            'driver' => 'pdo_sqlite',
            'path' => 'database.sqlite'
         )
    )
);

Bootstrap

Moduły to logicznie wyodrębnione części naszej aplikacji. Może to być np. „Frontend” oraz „Backend” lub „CMS” i „CRM”.
W przykładowej aplikacji ZF2 domyślnym modułem jest „Application”. Plik „Module.php” to bootstrapper naszego modułu.
Do naszego bootstrappera module/Application/Module.php dodajemy nową funkcję, która skonfiguruje oraz zarejestruje Entity Managera w rejestrze ZF2, aby można było go później użyć w dowolnym momencie.

public function initializeDoctrine ($e) {
        // YAML metadata
        $metaConfig = \Doctrine\ORM\Tools\Setup::createYAMLMetadataConfiguration(array('models/Yaml'), true, 'models/Proxies');
        // Proxies
        $metaConfig->setProxyDir('models/Proxies');
        $metaConfig->setProxyNamespace('Proxies');
        // Cache
        $metaConfig->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache);
        // Class loaders
        $classLoader = new \Doctrine\Common\ClassLoader('Entities', 'models');
        $classLoader->register();
        $classLoader = new \Doctrine\Common\ClassLoader('Proxies', 'models');
        $classLoader->register();
        // Get application config
        $config = $e->getParam('config');
        // Create and register Entity Manager
        $em = \Doctrine\ORM\EntityManager::create($config['doctrine']['connection']->toArray(), $metaConfig);
        // Register Entity Manager
        \Zend\Registry::set('em', $em);
    }

Następnie dodajemy wyzwalacz naszej metody. Wyzwalacz dodajemy do zdarzenia bootstrap. Jest ono wywoływane jeśli Router skieruje żądanie do naszego modułu.
W metodzie init na końcu dodajemy linię

$events->attach('bootstrap', 'bootstrap', array($this, 'initializeDoctrine'));

Po zmianach mój plik module/Application/Module.php wygląda tak:

<?php

namespace Application;

use Zend\Module\Manager,
    Zend\EventManager\StaticEventManager,
    Zend\Module\Consumer\AutoloaderProvider;

class Module implements AutoloaderProvider
{
    protected $view;
    protected $viewListener;

    public function init(Manager $moduleManager)
    {
        $events = StaticEventManager::getInstance();
        $events->attach('bootstrap', 'bootstrap', array($this, 'initializeView'), 100);
        // Init Doctrine
        $events->attach('bootstrap', 'bootstrap', array($this, 'initializeDoctrine'));
    }

    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';
    }
   
    public function initializeView($e)
    {
        $app          = $e->getParam('application');
        $locator      = $app->getLocator();
        $config       = $e->getParam('config');
        $view         = $this->getView($app);
        $viewListener = $this->getViewListener($view, $config);
        $app->events()->attachAggregate($viewListener);
        $events       = StaticEventManager::getInstance();
        $viewListener->registerStaticListeners($events, $locator);
    }
   
    public function initializeDoctrine ($e) {
        // YAML metadata
        $metaConfig = \Doctrine\ORM\Tools\Setup::createYAMLMetadataConfiguration(array('models/Yaml'), true, 'models/Proxies');
        // Proxies
        $metaConfig->setProxyNamespace('Proxies');
        // Cache
        $metaConfig->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache);
        // Class loaders
        $classLoader = new \Doctrine\Common\ClassLoader('Entities', 'models');
        $classLoader->register();
        $classLoader = new \Doctrine\Common\ClassLoader('Proxies', 'models');
        $classLoader->register();
        // Get application config
        $config = $e->getParam('config');
        // Create and register Entity Manager
        $em = \Doctrine\ORM\EntityManager::create($config['doctrine']['connection']->toArray(), $metaConfig);
        // Register Entity Manager
        \Zend\Registry::set('em', $em);
    }

    protected function getViewListener($view, $config)
    {
        if ($this->viewListener instanceof View\Listener) {
            return $this->viewListener;
        }

        $viewListener       = new View\Listener($view, $config->layout);
        $viewListener->setDisplayExceptionsFlag($config->display_exceptions);

        $this->viewListener = $viewListener;
        return $viewListener;
    }

    protected function getView($app)
    {
        if ($this->view) {
            return $this->view;
        }

        $locator = $app->getLocator();
        $view    = $locator->get('view');
        $url     = $view->plugin('url');
        $url->setRouter($app->getRouter());

        $view->plugin('headTitle')->setSeparator(' - ')
                                  ->setAutoEscape(false)
                                  ->append('ZF2 Skeleton Application');

        $basePath = $app->getRequest()->detectBaseUrl();

        $view->plugin('headLink')->appendStylesheet($basePath . 'css/bootstrap.min.css');

        $html5js = '<script src="' . $basePath . 'js/html5.js"></script>';
        $view->plugin('placeHolder')->__invoke('html5js')->set($html5js);
        $favicon = '<link rel="shortcut icon" href="' . $basePath . 'images/favicon.ico">';
        $view->plugin('placeHolder')->__invoke('favicon')->set($favicon);

        $this->view = $view;
        return $view;
    }
}

Gotowe

Mamy już zintegrowanego Doctrine 2 z Zend Framework 2. W następnym artykule skonfigurujemy narzędzia konsolowe w Doctrine 2.

Repozytorium

Gotowy kod dla tej części tutoriala znajdziecie tutaj.

Możecie również pobrać gotowe repozytorium:

git clone -b cz2 git://github.com/paweliwanowski/zf2-doctrine2-tutorial.git
cd zf2-doctrine2-tutorial/
git submodule init
git submodule update

Podobne artykuły:

Do tej pory 4 comments

Dodaj własny komentarz
  1. Czekamy z niecierpliwością na kolejny odcinek.

    Przydałby się też artykuł nt. używania modułów, bo to w zf2 trochę inaczej niż w jedynce.

  2. Witaj,

    Dziękuję za tutorial ale już tyle czasu minęło a nie ma dalszych części…

    Mam nadzieje, że niedługo uda Ci się napisać kolejną część!

    I jeszcze jedno pytanie. Co myślisz o: https://github.com/doctrine/DoctrineModule?

    Pozdrawiam.

  3. Cześć,

    Nie sprawdzałem jakości kodu, ale jeśli jest takie rozwiązanie na oficjalnych repo Doctrine to uważam, że jest warte użycia.

    Odnośnie nowych części to nie wiem jak to będzie. Zmieniłem trochę kierunek rozwoju. Pracuję teraz w B2B jako konsultant salesforce.com. Ze względu na delegacje, przeprowadzkę, certyfikaty nie miałem czasu na nic… Może ruszymy dalej z ZF2, Doctrine2 oraz dodamy salesforce.com. Wszystko rozwiąże się z początkiem sierpnia.

    Pozdrawiam

  4. Jeśli chcesz na prawdę skopiować pliki do podanej lokalizacji musisz użyć atrybutu: -f. Czyli w całości będzie to wyglądać tak: git submodule add git://github.com/doctrine/doctrine2.git vendor/Doctrine