(And we love <marquee/>)
ProxyManager, BjyAuthorize, AssetManager, ZeffMu, ZfrRest, OcraServiceManager, OcraCachedViewResolver, DoctrineModule, DoctrineORMModule, DoctrineMongoODMModule, VersionEyeModule
namespace Album\Model;
class Album
{
protected $id;
protected $artist;
protected $title;
public function exchangeArray($data)
{
$this->id = $data['id'];
$this->artist = $data['artist'];
$this->title = $data['title'];
}
// ...
}
namespace Album\Model;
use Zend\Db\TableGateway\TableGateway;
class AlbumTable {
public function __construct(TableGateway $gateway) {
$this->gw = $gateway;
}
public function getAlbum($id) {
return $this->gw->select(array('id' => (int) $id));
}
public function saveAlbum(Album $album) {
$this->gw->insert($album->toArray()); // (or update!)
}
public function deleteAlbum($id) {
$this->gw->delete(array('id' => (int) $id));
}
}
public function getServiceConfig() {
return array(
'factories' => array(
'Album\Model\AlbumTable' => function($sm) {
return new AlbumTable($sm->get('AlbumTableGateway'));
},
'AlbumTableGateway' => function ($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype
->setArrayObjectPrototype(new Album());
return new TableGateway(
'album', $dbAdapter,
null, $resultSetPrototype
);
},
),
);
}
class AlbumController extends SomeBaseController {
public function __construct(AlbumTable $table) {
$this->table = $table;
}
public function registerNewAlbum() {
$data = $this->getValidatedData(); // I'm lazy
$album = new Album();
$album->exchangeArray($data);
$this->table->saveAlbum($album);
return array('album' => $album);
}
}
Do we need to do it for all tables?
How do we load an album's artist(s)?
Does AlbumTable depend on ArtistTable?
Does the controller depend on AlbumTable and ArtistTable?
How do we avoid useless queries when saving data?
I don't know for sure
Decide for yoursef
An incubator for persistence-oriented libraries
It is inspired by Hibernate and the JPA (JSR-317)
It is based on a DBAL (DataBase Abstraction Layer)
Allows saving and loading POPO with an SQL DB
But most importantly...
Remember this guy?
An ORM gives you the impression that you are working with a "virtual" database (graph) composed by objects
Simply put:
(yes, you still need to know how a RDBMS works!)
DoctrineModule
basic common functionality
DoctrineORMModule
ORM/SQL Connection
DoctrineMongoODMModule
ODM/MongoDB Connection
php composer.phar require doctrine/doctrine-orm-module:0.8.*
php composer.phar require zendframework/zend-developer-tools:0.*
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
}
(stone throwing here)
Annotations are actually OK for non-reusable code
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',
'encoding' => 'utf8',
)))));
php public/index.php orm:validate-schema
php public/index.php 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
}
Not familiar with doctrine?
$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
Generated via ZendDeveloperTools
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);
$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);
$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
... because the community never sleeps ...
Think of entities as value-objects + ID
Don't add logic to entities (it's data!)
Keep entities aware only of themselves + associations
Stick with the doctrine/common interfaces
Unlock ORM/ MongoDB ODM/CouchDB ODM/ PHPCR ODM/OrientDB ODM portability!
Doctrine\ORM\EntityManager
Doctrine\Common\Persistence\ObjectManager
Doctrine\ORM\EntityRepository
Doctrine\Common\Persistence\ObjectRepository
Doctrine comes with a powerful collections API
OOP API for (PHP-)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);
Works also with repositories!
$recentVisitors = $objectManager
->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
objects...
If you fetch the object manager from within your services, replacing it will become very
difficult:
Inject it instead!
class MyService
{
public function __construct(ObjectManager $objectManager)
{
// [...]
}
}
'factories' => array(
'my_service' => function ($sl) {
$objectManager = $sl->get('Doctrine\ORM\EntityManager');
return new MyService($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!
Send me a tweet @Ocramius!
You can find these slides on GitHub at https://github.com/Ocramius/doctrine-orm-and-zendframework-2