public
Last active

Doctrine 2.2 Traits Preview

  • Download Gist
ActiveEntity.php
PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
<?php
 
use Doctrine\ORM\EntityManager,
Doctrine\ORM\Configuration,
Doctrine\ORM\Mapping\ClassMetadata;
 
/**
* Active Entity trait
*
* Limitations: a class can only ever be assocaited with ONE active entity manager. Multiple entity managers
* per entity class are not supported.
*/
trait ActiveEntity
{
private $doctrineEntityManager;
private $doctrineClassMetadata;
 
public function setDoctrine($em, $classMetadata)
{
$this->doctrineEntityManager = $em;
$this->doctrineClassMetadata = $classMetadata;
}
 
private function set($field, $args)
{
if (isset($this->doctrineClassMetadata->fieldMappings[$field])) {
$this->$field = $args[0];
} else if (isset($this->doctrineClassMetadata->associationMappings[$field]) &&
$this->doctrineClassMetadata->associationMappings[$field]['type'] & ClassMetadata::TO_ONE) {
 
$assoc = $this->doctrineClassMetadata->associationMappings[$field];
if (!($args[0] instanceof $assoc['targetEntity'])) {
throw new \InvalidArgumentException(
"Expected entity of type '".$assoc['targetEntity']."'"
);
}
 
if ($assoc['type'] & ClassMetadata::ONE_TO_ONE && !$assoc['isOwning']) {
$setter = "set".$assoc['mappedBy'];
$args[0]->$setter($this);
}
 
$this->$field = $args[0];
} else {
throw new \BadMethodCallException("no field with name '".$field."' exists on '".$this->doctrineClassMetadata->name."'");
}
}
private function get($field)
{
if ( (isset($this->doctrineClassMetadata->fieldMappings[$field]) && $this->doctrineClassMetadata->fieldMappings[$field]['type'] != "boolean") ||
isset($this->doctrineClassMetadata->associationMappings[$field])) {
return $this->$field;
} else {
throw new \BadMethodCallException("no field with name '".$field."' exists on '".$this->doctrineClassMetadata->name."'");
}
}
private function add($field, $args)
{
if (isset($this->doctrineClassMetadata->associationMappings[$field]) &&
$this->doctrineClassMetadata->associationMappings[$field]['type'] & ClassMetadata::TO_MANY) {
 
$assoc = $this->doctrineClassMetadata->associationMappings[$field];
if (!($args[0] instanceof $assoc['targetEntity'])) {
throw new \InvalidArgumentException(
"Expected entity of type '".$assoc['targetEntity']."'"
);
}
 
// add this object on the owning side aswell, for obvious infinite recursion
// reasons this is only done when called on the inverse side.
if (!$assoc['isOwning']) {
$setter = (($assoc['type'] & ClassMetadata::MANY_TO_MANY) ? "add" : "set").$assoc['mappedBy'];
$args[0]->$setter($this);
}
 
$this->$field->add($args[0]);
} else {
throw new \BadMethodCallException("There is no method ".$method." on ".$this->doctrineClassMetadata->name);
}
}
private function is($field)
{
if ( isset($this->doctrineClassMetadata->fieldMappings[$field]) && $this->doctrineClassMetadata->fieldMappings[$field]['type'] == "boolean") {
return $this->$field;
} else {
throw new \BadMethodCallException("There is no method ".$method." on ".$this->doctrineClassMetadata->name);
}
}
private function initializeDoctrine()
{
$this->doctrineEntityManager = ActiveEntityRegistry::getClassManager($className = get_class($this));
$this->doctrineClassMetadata = $this->doctrineEntityManager->getClassMetadata($className);
}
/**
* @param string $method
* @param array $args
* @return mixed
*/
public function __call($method, $args)
{
// this happens if you call new on the entity.
if ($this->doctrineClassMetadata === null) {
$this->initializeDoctrine();
}
$command = substr($method, 0, 3);
$field = lcfirst(substr($method, 3));
if ($command == "set") {
$this->set($field, $args);
} else if ($command == "get") {
return $this->get($field);
} else if ($command == "add") {
$this->add($field, $args);
} else if (substr($command, 0, 2) == "is") {
$this->is($field, $args);
} else {
throw new \BadMethodCallException("There is no method ".$method." on ".$this->doctrineClassMetadata->name);
}
}
public function persist()
{
$this->doctrineEntityManager->persist($this);
}
public function remove()
{
$this->doctrineEntityManager->remove($this);
}
static public function create(array $data = array())
{
$instance = new static();
$instance->initializeDoctrine();
foreach ($data AS $k => $v) {
$instance->set($k, array($v));
}
return $instance;
}
static public function createQueryBuilder($rootAlias = 'r')
{
$class = get_called_class();
return ActiveEntityRegistry::getClassManager($class)->createQueryBuilder($rootAlias);
}
static public function find($id)
{
$class = get_called_class();
return ActiveEntityRegistry::getClassManager($class)->find($class, $id);
}
static public function findOneBy(array $criteria = array())
{
$class = get_called_class();
return ActiveEntityRegistry::getClassManager($class)->getRepository($class)->findOneBy($criteria);
}
static public function findBy(array $criteria = array(), $orderBy = null, $limit = null, $offset = null)
{
$class = get_called_class();
return ActiveEntityRegistry::getClassManager($class)->getRepository($class)->findBy($criteria, $orderBy, $limit, $offset);
}
static public function findAll()
{
$class = get_called_class();
return ActiveEntityRegistry::getClassManager($class)->getRepository($class)->findAll($criteria);
}
static public function expr()
{
$class = get_called_class();
return ActiveEntityRegistry::getClassManager($class)->getExpressionBuilder();
}
}
ActiveEntityListener.php
PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<?php
 
class ActiveEntityListener
{
public function postLoad($args)
{
$entity = $args->getEntity();
$em = $args->getEntityManager();
 
$metadata = $em->getClassMetadata(get_class($entity));
if (in_array("ActiveEntity", $metadata->reflClass->getTraitNames())) {
$entity->setDoctrine($em, $metadata);
}
}
}
ActiveEntityRegistry.php
PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
<?php
 
use Doctrine\ORM\EntityManager;
 
class ActiveEntityRegistry
{
/**
* @var array
*/
private static $managers = array();
private static $defaultManager = array();
static public function setClassManager($class, EntityManager $manager)
{
self::$managers[$class] = $manager;
}
static public function setDefaultManager(EntityManager $manager)
{
self::$defaultManager = $manager;
}
static public function getClassManager($class)
{
if (isset(self::$managers[$class])) {
return self::$managers[$class];
} else if (self::$defaultManager) {
return self::$defaultManager;
} else {
throw new \BadMethodCallException("ActiveEntity is not yet connected to an EntityManager.");
}
}
}
Article.php
PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<?php
 
/**
* @Entity
*/
class Article
{
use ActiveEntity,Timestampable;
 
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column */
private $headline;
/** @Column(type="text") */
private $body;
}
SerializableEntity.php
PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
<?php
 
use Doctrine\Common\Util\Inflector;
 
trait SerializableEntity
{
static private function serializeEntity($entity)
{
$className = get_class($entity);
$em = ActiveEntityRegistry::getClassManager($className);
$class = $em->getClassMetadata($className);
$data = array();
foreach ($class->fieldMappings as $field => $mapping) {
$value = $class->reflFields[$field]->getValue($entity);
$field = Inflector::tableize($field);
if ($value instanceof \DateTime) {
$data[$field] = $value->format(\DateTime::ATOM);
} else if (is_object($value)) {
$data[$field] = (string)$value;
} else {
$data[$field] = $value;
}
}
foreach ($class->associationMappings as $field => $mapping) {
$key = Inflector::tableize($field);
if ($mapping['isCascadeDetach']) {
$data[$key] = self::serializeEntity( $class->reflFields[$field]->getValue($entity) );
} else if ($mapping['isOwningSide'] && $mapping['type'] & ClassMetadata::TO_ONE) {
// if its not detached to but there is an owning side to one entity at least reflect the identifier.
$data[$key] = $em->getUnitOfWork()->getEntityIdentifier( $class->reflFields[$field]->getValue($entity) );
}
}
return $data;
}
public function toArray()
{
return self::serializeEntity($this);
}
public function toJson()
{
return json_encode($this->toArray());
}
public function toDOMDocument()
{
$arrToXml = function($node, $data) use (&$arrToXml) {
foreach ($data AS $k => $v) {
$child = $node->ownerDocument->createElement($k);
$node->appendChild($child);
if (is_array($v)) {
$arrToXml($child, $v);
} else {
$child->appendChild($node->ownerDocument->createTextNode($v));
}
}
};
$className = get_class($this);
$em = ActiveEntityRegistry::getClassManager($className);
$class = $em->getClassMetadata($className);
$dom = new \DOMDocument('1.0', 'UTF-8');
$root = $dom->createElement(Inflector::tableize($class->reflClass->getShortName()));
$dom->appendChild($root);
$arrToXml($root, $this->toArray());
return $dom;
}
public function toXml($formatOutput = false)
{
$dom = $this->toDOMDocument();
$dom->formatOutput = $formatOutput;
return $dom->saveXML();
}
}
Timestampable.php
PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
<?php
trait Timestampable
{
/** @Column(type="datetime") */
private $created;
/** @Column(type="datetime") */
private $updated;
 
/** @PrePersist */
public function onPrePersist()
{
$this->created = new \DateTime("now");
$this->updated = new \DateTime("now");
}
 
/** @PreUpdate */
public function onPreUpdate()
{
$this->updated = new \DateTime("now");
}
 
public function getCreated()
{
return $this->created;
}
 
public function getUpdated()
{
return $this->updated;
}
}
sample.php
PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
<?php
 
$lib = 'path/to/doctrine2/';
require $lib . 'lib/Doctrine/ORM/Tools/Setup.php';
 
Setup::registerAutoloadGit($lib);
 
$cache = new \Doctrine\Common\Cache\ArrayCache;
 
$config = Setup::createAnnotationMetadataConfiguration(array(), true);
$config->setSQLLogger(new Doctrine\DBAL\Logging\EchoSQLLogger());
 
$connectionOptions = array(
'driver' => 'pdo_sqlite',
'memory' => true,
);
 
$evm = new \Doctrine\Common\EventManager();
$evm->addEventListener(array('postLoad'), new ActiveEntityListener);
 
$em = EntityManager::create($connectionOptions, $config, $evm);
ActiveEntityRegistry::setDefaultManager($em);
 
$schemaTool = new \Doctrine\ORM\Tools\SchemaTool($em);
$schemaTool->createSchema(array(
$em->getClassMetadata("Article")
));
 
$article = new Article();
$article->setHeadline("foo");
$article->setBody("barz!");
 
$other = Article::create(array('headline' => 'foo', 'body' => 'omg!?'));
 
$article->persist();
$other->persist();
 
$em->flush();
$em->clear();
 
$article = Article::find(1);
$article->remove();
 
$em->flush();
 
$articles = Article::findBy(array('headline' => 'foo'));
echo count($articles) . " articles\n";
 
$articles = Article::createQueryBuilder('r')->where(
Article::expr()->like("r.body", '%omg%')
);
echo count($articles) . " articles\n";

ActiveEntity as a trait and injecting it with an EventListener is smart, the only this is that when creating entites the EntityManager wont be availible.

@henrikbjorn that is why therere is initializeDoctrine() grabbing the EM from a globally static location. You can't get around having this with ActiveRecord :-)

Ohh so __call is always called on traits when a new object is created?

@henrikbjorn no, only on the first intercept of __call. The metadata is only necessary to check if the get/set/add/is method really exists.

@henrikbjorn yes or if you do ClassName::create(..); then its done aswell directly during construction.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.