Skip to content

Instantly share code, notes, and snippets.

@beberlei
Created June 19, 2011 11:11
Show Gist options
  • Star 32 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save beberlei/1034079 to your computer and use it in GitHub Desktop.
Save beberlei/1034079 to your computer and use it in GitHub Desktop.
Doctrine 2.2 Traits Preview
<?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();
}
}
<?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);
}
}
}
<?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.");
}
}
}
<?php
/**
* @Entity
*/
class Article
{
use ActiveEntity,Timestampable;
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column */
private $headline;
/** @Column(type="text") */
private $body;
}
<?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";
<?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();
}
}
<?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
Copy link

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.

@beberlei
Copy link
Author

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

@henrikbjorn
Copy link

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

@beberlei
Copy link
Author

@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.

@beberlei
Copy link
Author

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

@dustinwhittle
Copy link

Awesome!

Copy link

ghost commented Sep 12, 2014

// 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