Skip to content

Embed URL

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
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

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
Owner

@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

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

@beberlei
Owner

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

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

@dustinwhittle

Awesome!

@DmitryPHP

// 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
Something went wrong with that request. Please try again.