On Github Ocramius / doctrine-orm-zf2-tutorial
Note: these slides are combined with a webcast that you can find on this page .
Doctrine core team
Zf2 contributor
Modules developer time waster
BjyAuthorize, AssetManager, ZeffMu, ZfrRest, OcraDiCompiler, OcraServiceManager, OcraCachedViewResolver, DoctrineModule, DoctrineORMModule, DoctrineMongoODMModule, VersionEyeModule
An incubator for persistence-oriented libraries
Doctrine ORM is an Object Relational Mapper
It is inspired by Hibernate and the JPA (JSR-317)
It is based on a DBAL (DataBase Abstraction Layer)
Allows developers to save and load POPO with SQL
Also consider that Doctrine ORM abstract the underlying SQL dialect of the DB platform you are using, so it's possible for you to switch DB vendor from a day to anotherAn ORM gives you the impression that you are working with a "virtual" database (graph) composed by objects
Simply put:
DoctrineModule basic common functionality
DoctrineORMModule ORM/SQL Connection
DoctrineMongoODMModule ODM/MongoDB Connection
DoctrineModule is a set of functionality common to all ZF2-Doctrine integration layers
php composer.phar require doctrine/doctrine-orm-module:0.7.*
php composer.phar require zendframework/zend-developer-tools:dev-master
cp vendor/zendframework/zend-developer-tools/config/zenddevelopertools.local.php.dist config/autoload/zdt.local.php
config/application.config.php
return array( 'modules' => array( 'ZendDeveloperTools', 'DoctrineModule', 'DoctrineORMModule', 'Application', ), // [...] );
You should see:
module/Application/src/Application/Entity/User.php
namespace Application\Entity; use Doctrine\ORM\Mapping as ORM; /** @ORM\Entity */ class User { /** * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") * @ORM\Column(type="integer") */ protected $id; /** @ORM\Column(type="string") */ protected $fullName; // getters/setters }
module/Application/config/module.config.php
return array( 'doctrine' => array( 'driver' => array( 'application_entities' => array( 'class' =>'Doctrine\ORM\Mapping\Driver\AnnotationDriver', 'cache' => 'array', 'paths' => array(__DIR__ . '/../src/Application/Entity') ), 'orm_default' => array( 'drivers' => array( 'Application\Entity' => 'application_entities' ) ))), // [...]
You should see:
config/autoload/doctrine.local.php
return array( 'doctrine' => array( 'connection' => array( 'orm_default' => array( 'driverClass' =>'Doctrine\DBAL\Driver\PDOMySql\Driver', 'params' => array( 'host' => 'localhost', 'port' => '3306', 'user' => 'username', 'password' => 'password', 'dbname' => 'database', )))));
./vendor/bin/doctrine-module orm:validate-schema
./vendor/bin/doctrine-module orm:schema-tool:create
module/Application/src/Application/Controller/IndexController.php
public function indexAction() { $objectManager = $this ->getServiceLocator() ->get('Doctrine\ORM\EntityManager'); $user = new \Application\Entity\User(); $user->setFullName('Marco Pivetta'); $objectManager->persist($user); $objectManager->flush(); die(var_dump($user->getId())); // yes, I'm lazy }
$user = new User(); $user->setFullName('Marco Pivetta'); $objectManager->persist($user); // $user is now "managed" $objectManager->flush(); // commit changes to db var_dump($user->getId()); // 1
$user1 = new User(); $user1->setFullName('Marco Pivetta'); $objectManager->persist($user1); $user2 = new User(); $user2->setFullName('Michaël Gallego'); $objectManager->persist($user2); $user3 = new User(); $user3->setFullName('Kyle Spraggs'); $objectManager->persist($user3); $objectManager->flush();
$user1 = $objectManager->find('Application\Entity\User', 1); var_dump($user1->getFullName()); // Marco Pivetta $user2 = $objectManager ->getRepository('Application\Entity\User') ->findOneBy(array('fullName' => 'Michaël Gallego')); var_dump($user2->getFullName()); // Michaël Gallego
$user = $objectManager->find('Application\Entity\User', 1); $user->setFullName('Guilherme Blanco'); $objectManager->flush();
$user = $objectManager->find('Application\Entity\User', 1); $objectManager->remove($user); $objectManager->flush();
/** @ORM\Entity */ class User { // like before /** @ORM\ManyToOne(targetEntity="Address") */ protected $address; /** @ORM\ManyToMany(targetEntity="Project") */ protected $projects; public function __construct() { $this->projects = new ArrayCollection(); } // getters/setters }
/** @ORM\Entity */ class Address { /** @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /** @ORM\Column(type="string") */ protected $city; /** @ORM\Column(type="string") */ protected $country; // getters/setters etc. }
/** @ORM\Entity */ class Project { /** @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /** @ORM\Column(type="string") */ protected $name; // getters/setters }
$user = new User(); $user->setFullName('Marco Pivetta'); $objectManager->persist($user); $address = new Address(); $address->setCity('Frankfurt') $address->setCountry('Germany'); $objectManager->persist($address); $project = new Project(); $project->setName('Doctrine ORM'); $objectManager->persist($project); $user->setAddress($address); $user->getProjects()->add($project); $objectManager->flush();
$user = $objectManager->find('Application\Entity\User', 1); var_dump($user->getAddress()->getCity()); // Frankfurt var_dump($user->getProjects()->first()->getName()) // Doctrine ORM
More tutorials at http://marco-pivetta.com/doctrine2-orm-tutorial
See what your entities look like in a graph:
use Doctrine\Common\Collections\ArrayCollection; use DoctrineModule\Paginator\Adapter\Collection as Adapter; use Zend\Paginator\Paginator; // Create a Doctrine Collection $collection = new ArrayCollection(range(1, 101)); // Create the paginator itself $paginator = new Paginator(new Adapter($collection)); $paginator ->setCurrentPageNumber(1) ->setItemCountPerPage(5);
use DoctrineORMModule\Paginator\Adapter\DoctrinePaginator; use Doctrine\ORM\Tools\Pagination\Paginator as ORMPaginator; use Zend\Paginator\Paginator; // Create a Doctrine Collection $query = $em->createQuery('SELECT f FROM Foo f JOIN f.bar b'); // Create the paginator itself $paginator = new Paginator( new DoctrinePaginator(new ORMPaginator($query)) ); $paginator ->setCurrentPageNumber(1) ->setItemCountPerPage(5);
$repository = $objectManager ->getRepository('Application\Entity\User'); $validator = new \DoctrineModule\Validator\ObjectExists(array( 'object_repository' => $repository, 'fields' => array('email') )); var_dump($validator->isValid('test@example.com')); var_dump($validator->isValid(array( 'email' => 'test@example.com' )));
$zendCache = new \Zend\Cache\Storage\Adapter\Memory(); $cache = new \DoctrineModule\Cache\ZendStorageCache($zendCache);
$doctrineCache = new \Doctrine\Common\Cache\ArrayCache(); $options = new \Zend\Cache\Storage\Adapter\AdapterOptions(); $cache = new \DoctrineModule\Cache\DoctrineCacheStorage( $options, $doctrineCache );
use DoctrineModule\Stdlib\Hydrator\DoctrineObject; $hydrator = new DoctrineObject( $objectManager, 'Application\Entity\City' ); $city = new City(); $data = array('name' => 'Frankfurt'); $city = $hydrator->hydrate($data, $city); echo $city->getName(); // prints "Frankfurt" $dataArray = $hydrator->extract($city); echo $dataArray['name']; // prints "Frankfurt"
use DoctrineModule\Stdlib\Hydrator\DoctrineObject; $hydrator = new DoctrineObject( $objectManager, 'Application\Entity\City' ); $city = new City(); $data = array('country' => 123); $city = $hydrator->hydrate($data, $city); var_dump($city->getCountry()); // prints class Country#1 (1) { // protected $name => string(5) "Germany" // }
$form->add(array( 'type' => 'DoctrineModule\Form\Element\ObjectSelect', 'name' => 'user', 'options' => array( 'object_manager' => $objectManager, 'target_class' => 'Module\Entity\User', 'property' => 'fullName', 'is_method' => true, 'find_method' => array( 'name' => 'findBy', 'params' => array( 'criteria' => array('active' => 1), 'orderBy' => array('lastName' => 'ASC'), ), ), ), ));
Everything works with MongoDB ODM too!
CouchDB ODM/PHPCR ODM/OrientDB ODM
Think of entities as value-objects
Don't add logic to entities (hard to change later!)
Keep entities aware only of themselves + relations
If you stick with using only doctrine/common API, users of your modules can switch between ORM/ MongoDB ODM/CouchDB ODM/ PHPCR ODM/OrientDB ODM
Prefer
Doctrine\Common\Persistence\ObjectManager
over
Doctrine\ORM\EntityManager
Prefer
Doctrine\Common\Persistence\ObjectRepository
over
Doctrine\ORM\EntityRepository
Doctrine comes with a powerful collections API
OOP API for array-like data structures
Collections provide a Criteria API
Allows you to filter virtually any kind of data structure
use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Collections\ArrayCollection; $collection = new ArrayCollection(array($user1, $user2, $user3)); $criteria = new Criteria(); $criteria->andWhere( $criteria->expr()->gt( 'lastLogin', new \DateTime('-1 day') ) ); $recentVisitors = $collection->matching($criteria);
$recentVisitors = $em ->getRepository('Application\Entity\Users') ->matching($criteria);
Works in ORM Repositories, Collections, etc...
Abstracts the problem of "searching"
Same criteria for different storages (ORM, ODM, Memory, ElasticSearch, cache...)
Allows you to define your own RecentUsersCriteria or InactiveUsersCriteria...
If you fetch the entity manager from within your services, replacing it will become very hard: Inject it instead!
'factories' => array( 'my_service' => function ($sl) { $objectManager = $sl->get('Doctrine\ORM\EntityManager'); return new MyService($objectManager); } ),
class MyService { public function __construct(ObjectManager $objectManager) { // [...] } }
Filtering data when saved to DB
Validating data when saved to DB
Saving files when records are saved to DB
Using DB-level errors to check input validity
An ObjectManager works under the assumption that managed objects are valid!
Assign values to your entities only when data is valid!
You can find these slides on GitHub at https://github.com/Ocramius/doctrine-orm-zf2-tutorial