Chris@0: . Chris@0: */ Chris@0: Chris@0: namespace Doctrine\Common\Persistence; Chris@0: Chris@0: use Doctrine\Common\Collections\ArrayCollection; Chris@0: use Doctrine\Common\Collections\Collection; Chris@0: use Doctrine\Common\Persistence\Mapping\ClassMetadata; Chris@0: Chris@0: /** Chris@0: * PersistentObject base class that implements getter/setter methods for all mapped fields and associations Chris@0: * by overriding __call. Chris@0: * Chris@0: * This class is a forward compatible implementation of the PersistentObject trait. Chris@0: * Chris@0: * Limitations: Chris@0: * Chris@0: * 1. All persistent objects have to be associated with a single ObjectManager, multiple Chris@0: * ObjectManagers are not supported. You can set the ObjectManager with `PersistentObject#setObjectManager()`. Chris@0: * 2. Setters and getters only work if a ClassMetadata instance was injected into the PersistentObject. Chris@0: * This is either done on `postLoad` of an object or by accessing the global object manager. Chris@0: * 3. There are no hooks for setters/getters. Just implement the method yourself instead of relying on __call(). Chris@0: * 4. Slower than handcoded implementations: An average of 7 method calls per access to a field and 11 for an association. Chris@0: * 5. Only the inverse side associations get autoset on the owning side as well. Setting objects on the owning side Chris@0: * will not set the inverse side associations. Chris@0: * Chris@0: * @example Chris@0: * Chris@0: * PersistentObject::setObjectManager($em); Chris@0: * Chris@0: * class Foo extends PersistentObject Chris@0: * { Chris@0: * private $id; Chris@0: * } Chris@0: * Chris@0: * $foo = new Foo(); Chris@0: * $foo->getId(); // method exists through __call Chris@0: * Chris@0: * @author Benjamin Eberlei Chris@0: */ Chris@0: abstract class PersistentObject implements ObjectManagerAware Chris@0: { Chris@0: /** Chris@0: * @var ObjectManager|null Chris@0: */ Chris@0: private static $objectManager = null; Chris@0: Chris@0: /** Chris@0: * @var ClassMetadata|null Chris@0: */ Chris@0: private $cm = null; Chris@0: Chris@0: /** Chris@0: * Sets the object manager responsible for all persistent object base classes. Chris@0: * Chris@0: * @param ObjectManager|null $objectManager Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: static public function setObjectManager(ObjectManager $objectManager = null) Chris@0: { Chris@0: self::$objectManager = $objectManager; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return ObjectManager|null Chris@0: */ Chris@0: static public function getObjectManager() Chris@0: { Chris@0: return self::$objectManager; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Injects the Doctrine Object Manager. Chris@0: * Chris@0: * @param ObjectManager $objectManager Chris@0: * @param ClassMetadata $classMetadata Chris@0: * Chris@0: * @return void Chris@0: * Chris@0: * @throws \RuntimeException Chris@0: */ Chris@0: public function injectObjectManager(ObjectManager $objectManager, ClassMetadata $classMetadata) Chris@0: { Chris@0: if ($objectManager !== self::$objectManager) { Chris@0: throw new \RuntimeException("Trying to use PersistentObject with different ObjectManager instances. " . Chris@0: "Was PersistentObject::setObjectManager() called?"); Chris@0: } Chris@0: Chris@0: $this->cm = $classMetadata; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets a persistent fields value. Chris@0: * Chris@0: * @param string $field Chris@0: * @param array $args Chris@0: * Chris@0: * @return void Chris@0: * Chris@0: * @throws \BadMethodCallException When no persistent field exists by that name. Chris@0: * @throws \InvalidArgumentException When the wrong target object type is passed to an association. Chris@0: */ Chris@0: private function set($field, $args) Chris@0: { Chris@0: if ($this->cm->hasField($field) && !$this->cm->isIdentifier($field)) { Chris@0: $this->$field = $args[0]; Chris@0: } else if ($this->cm->hasAssociation($field) && $this->cm->isSingleValuedAssociation($field)) { Chris@0: $targetClass = $this->cm->getAssociationTargetClass($field); Chris@0: if (!($args[0] instanceof $targetClass) && $args[0] !== null) { Chris@0: throw new \InvalidArgumentException("Expected persistent object of type '".$targetClass."'"); Chris@0: } Chris@0: $this->$field = $args[0]; Chris@0: $this->completeOwningSide($field, $targetClass, $args[0]); Chris@0: } else { Chris@0: throw new \BadMethodCallException("no field with name '".$field."' exists on '".$this->cm->getName()."'"); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets a persistent field value. Chris@0: * Chris@0: * @param string $field Chris@0: * Chris@0: * @return mixed Chris@0: * Chris@0: * @throws \BadMethodCallException When no persistent field exists by that name. Chris@0: */ Chris@0: private function get($field) Chris@0: { Chris@0: if ( $this->cm->hasField($field) || $this->cm->hasAssociation($field) ) { Chris@0: return $this->$field; Chris@0: } Chris@12: Chris@12: throw new \BadMethodCallException("no field with name '".$field."' exists on '".$this->cm->getName()."'"); Chris@0: } Chris@0: Chris@0: /** Chris@0: * If this is an inverse side association, completes the owning side. Chris@0: * Chris@0: * @param string $field Chris@0: * @param ClassMetadata $targetClass Chris@0: * @param object $targetObject Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: private function completeOwningSide($field, $targetClass, $targetObject) Chris@0: { Chris@0: // add this object on the owning side as well, for obvious infinite recursion Chris@0: // reasons this is only done when called on the inverse side. Chris@0: if ($this->cm->isAssociationInverseSide($field)) { Chris@0: $mappedByField = $this->cm->getAssociationMappedByTargetField($field); Chris@0: $targetMetadata = self::$objectManager->getClassMetadata($targetClass); Chris@0: Chris@0: $setter = ($targetMetadata->isCollectionValuedAssociation($mappedByField) ? "add" : "set").$mappedByField; Chris@0: $targetObject->$setter($this); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds an object to a collection. Chris@0: * Chris@0: * @param string $field Chris@0: * @param array $args Chris@0: * Chris@0: * @return void Chris@0: * Chris@0: * @throws \BadMethodCallException Chris@0: * @throws \InvalidArgumentException Chris@0: */ Chris@0: private function add($field, $args) Chris@0: { Chris@0: if ($this->cm->hasAssociation($field) && $this->cm->isCollectionValuedAssociation($field)) { Chris@0: $targetClass = $this->cm->getAssociationTargetClass($field); Chris@0: if (!($args[0] instanceof $targetClass)) { Chris@0: throw new \InvalidArgumentException("Expected persistent object of type '".$targetClass."'"); Chris@0: } Chris@0: if (!($this->$field instanceof Collection)) { Chris@0: $this->$field = new ArrayCollection($this->$field ?: []); Chris@0: } Chris@0: $this->$field->add($args[0]); Chris@0: $this->completeOwningSide($field, $targetClass, $args[0]); Chris@0: } else { Chris@0: throw new \BadMethodCallException("There is no method add".$field."() on ".$this->cm->getName()); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Initializes Doctrine Metadata for this class. Chris@0: * Chris@0: * @return void Chris@0: * Chris@0: * @throws \RuntimeException Chris@0: */ Chris@0: private function initializeDoctrine() Chris@0: { Chris@0: if ($this->cm !== null) { Chris@0: return; Chris@0: } Chris@0: Chris@0: if (!self::$objectManager) { Chris@0: throw new \RuntimeException("No runtime object manager set. Call PersistentObject#setObjectManager()."); Chris@0: } Chris@0: Chris@0: $this->cm = self::$objectManager->getClassMetadata(get_class($this)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Magic methods. Chris@0: * Chris@0: * @param string $method Chris@0: * @param array $args Chris@0: * Chris@0: * @return mixed Chris@0: * Chris@0: * @throws \BadMethodCallException Chris@0: */ Chris@0: public function __call($method, $args) Chris@0: { Chris@12: $this->initializeDoctrine(); Chris@12: Chris@0: $command = substr($method, 0, 3); Chris@0: $field = lcfirst(substr($method, 3)); Chris@0: if ($command == "set") { Chris@0: $this->set($field, $args); Chris@0: } else if ($command == "get") { Chris@0: return $this->get($field); Chris@0: } else if ($command == "add") { Chris@0: $this->add($field, $args); Chris@0: } else { Chris@0: throw new \BadMethodCallException("There is no method ".$method." on ".$this->cm->getName()); Chris@0: } Chris@0: } Chris@0: }