Created
June 19, 2011 11:11
-
-
Save beberlei/1034079 to your computer and use it in GitHub Desktop.
Doctrine 2.2 Traits Preview
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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."); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* @Entity | |
*/ | |
class Article | |
{ | |
use ActiveEntity,Timestampable; | |
/** @Id @Column(type="integer") @GeneratedValue */ | |
private $id; | |
/** @Column */ | |
private $headline; | |
/** @Column(type="text") */ | |
private $body; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
@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.
Awesome!
// ActiveEntity.php
static public function findAll()
{
$class = get_called_class();
return ActiveEntityRegistry::getClassManager($class)->getRepository($class)->findAll($criteria);
}
$criteria is not defined.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.