Skip to content

Instantly share code, notes, and snippets.

@chvonrohr
Last active February 15, 2016 09:07

Revisions

  1. chvonrohr revised this gist Apr 24, 2015. 2 changed files with 31 additions and 18 deletions.
    44 changes: 27 additions & 17 deletions CopyService.php
    Original file line number Diff line number Diff line change
    @@ -43,8 +43,8 @@ public function copy($object) {
    $properties = $this->reflectionService->getClassPropertyNames($className);

    foreach ($properties as $propertyName) {
    //$tags = $this->reflectionService->getPropertyTagsValues($className, $propertyName);
    $propertyAnnotation = $this->reflectionService->getPropertyAnnotation($className, $propertyName, 'Frontal\Cockpit\Annotations\Copy');

    if (!$propertyAnnotation) { // ignore if property has no "copy" annotation
    continue;
    }
    @@ -54,13 +54,16 @@ public function copy($object) {
    $setter = 'set' . ucfirst($propertyName);

    $originalValue = $object->$getter();

    if ($copyMethod == 'reference') { // copy as reference

    // copy as reference
    if ($copyMethod == 'reference') {
    $copiedValue = $this->copyAsReference($originalValue);

    } else { // clone value itself (if its a model, it will copied again by annotations)
    // clone value itself (if its a model, it will copied again by annotations)
    } else {
    $copiedValue = $this->copyAsClone($originalValue);
    }

    if ($copiedValue != NULL) {
    $copy->$setter($copiedValue);
    }
    @@ -70,37 +73,44 @@ public function copy($object) {
    }

    protected function copyAsReference($value) {
    $objectManager = $this->objectManager;
    if ($value instanceof Doctrine\Common\Collections\ArrayCollection) { //TODO: more general class? traversable?
    // collection; copy collection and attach items to this new collection
    // if 1:n mapping is used, items are detached from their old collection - this is
    // a limitation of this type of reference
    $newStorage = $this->objectManager->get('Doctrine\Common\Collections\ArrayCollection');
    // collection
    if ($value instanceof \Doctrine\ORM\PersistentCollection) {
    $newStorage = new \Doctrine\Common\Collections\ArrayCollection();
    foreach ($value as $item) {
    $newStorage->attach($item);
    }
    return $newStorage;
    //} else if ($value instanceof Tx_Locus_Domain_Model_AbstractDomainModelObject) {
    // 1:1 mapping as reference; return object itself
    // return $value;

    // model
    } else if ($value instanceof Doctrine\ORM\ProxyInterface) {
    return $value;

    // other object
    } else if (is_object($value)) {
    // fallback case for class copying - value objects and such
    return $value;

    } else {
    // this case is very unlikely: means someone wished to copy hard type as a reference - so return a copy instead
    return $value;
    }
    }

    protected function copyAsClone($value) {
    if ($value instanceof Doctrine\Common\Collections\ArrayCollection) {
    // collection; copy storage and copy items, return new storage
    $newStorage = $this->objectManager->get('Doctrine\Common\Collections\ArrayCollection');
    // collection
    if ($value instanceof \Doctrine\ORM\PersistentCollection) {
    $newStorage = new \Doctrine\Common\Collections\ArrayCollection();
    foreach ($value as $item) {
    $newItem = $this->copy($item);
    $newStorage->attach($newItem);
    $newStorage->add($newItem);
    }
    return $newStorage;

    // model
    } else if ($value instanceof \Doctrine\ORM\ProxyInterface) {
    return $this->copy($value);

    // other object
    } else if (is_object($value)) {
    // fallback case for class copying - value objects and such
    return clone $value;
    5 changes: 4 additions & 1 deletion Example.php
    Original file line number Diff line number Diff line change
    @@ -25,21 +25,24 @@ class Example {

    /**
    * @var \Foo\Bar\Domain\Model\ExampleChild
    * @ORM\OneOne(cascade={"persist", "remove"})
    * @CP\Copy
    * => copy related object
    */
    protected $child;

    /**
    * @var \Doctrine\Common\Collections\ArrayCollection<\Foo\Bar\Domain\Model\ExampleChild>
    * @ORM\OneToMany(mappedBy="project",cascade={"persist", "remove"})
    * @CP\Copy
    * => copy related objects
    */
    protected $childs;

    /**
    * Important: Set cascade=persist
    * @var \Doctrine\Common\Collections\ArrayCollection<\Foo\Bar\Domain\Model\MmChilds>
    * @ORM\ManyToMany
    * @ORM\ManyToMany(mappedBy="example",cascade={"persist", "remove"})
    * @CP\Copy(type="reference")
    * => copy reference to existing objects
    */
  2. chvonrohr revised this gist Apr 23, 2015. 1 changed file with 40 additions and 0 deletions.
    40 changes: 40 additions & 0 deletions ExampleRepository.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,40 @@
    <?php
    namespace Foo\Bar\Domain\Repository;

    use TYPO3\Flow\Annotations as Flow;
    use TYPO3\Flow\Persistence\Repository;

    /**
    * @Flow\Scope("singleton")
    */
    class ExampleRepository extends Repository {

    /**
    * @var \TYPO3\Flow\Object\ObjectManagerInterface
    * @Flow\Inject
    */
    protected $objectManager;

    /**
    * clone existing example
    *
    * @param \Foo\Bar\Domain\Model\Example $example
    * @return \Foo\Bar\Domain\Model\Example cloned example
    */
    public function copy($example) {

    // clone project
    $copyService = $this->objectManager->get("Foo\Bar\Service\CopyService");
    $clonedExample = $copyService->copy($example);

    // set title "xxx (copy)"
    $clonedExample->setTitle( $clonedExample->getTitle() . " (Copy)");

    // persist
    $this->add($clonedExample);
    $this->persistenceManager->persistAll();

    return $clonedExample;
    }

    }
  3. chvonrohr revised this gist Apr 23, 2015. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions CopyService.php
    Original file line number Diff line number Diff line change
    @@ -23,10 +23,10 @@ class CopyService {
    protected $reflectionService;

    /**
    * @var \TYPO3\Flow\Object\ObjectManagerInterface
    * @var \TYPO3\Flow\Object\ObjectManagerInterface
    * @Flow\Inject
    */
    protected $objectManager;
    */
    protected $objectManager;


    /**
  4. chvonrohr revised this gist Apr 23, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Copy.php
    Original file line number Diff line number Diff line change
    @@ -8,7 +8,7 @@
    final class Copy {

    /**
    * Ember type
    * type of copy {empty, 'reference'}
    * @var string
    */
    public $type;
  5. chvonrohr created this gist Apr 23, 2015.
    16 changes: 16 additions & 0 deletions Copy.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,16 @@
    <?php
    namespace Foo\Bar\Annotations;

    /**
    * @Annotation
    * @Target("PROPERTY")
    */
    final class Copy {

    /**
    * Ember type
    * @var string
    */
    public $type;

    }
    115 changes: 115 additions & 0 deletions CopyService.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,115 @@
    <?php
    namespace Foo\Bar\Service;


    use TYPO3\Flow\Annotations as Flow;

    /**
    * @Flow\Scope("singleton")
    */
    class CopyService {


    /**
    * @var \Foo\Cockpit\Service\RecursionService
    * @Flow\Inject
    */
    public $recursionService;

    /**
    * @var \TYPO3\Flow\Reflection\ReflectionService
    * @Flow\Inject
    */
    protected $reflectionService;

    /**
    * @var \TYPO3\Flow\Object\ObjectManagerInterface
    * @Flow\Inject
    */
    protected $objectManager;


    /**
    * Copy a single object based on field annotations about how to copy the object
    *
    * @return $copy
    */
    public function copy($object) {

    $className = get_class($object);
    $this->recursionService->in();
    $this->recursionService->check($className);
    $copy = $this->objectManager->get($className);
    $properties = $this->reflectionService->getClassPropertyNames($className);

    foreach ($properties as $propertyName) {
    //$tags = $this->reflectionService->getPropertyTagsValues($className, $propertyName);
    $propertyAnnotation = $this->reflectionService->getPropertyAnnotation($className, $propertyName, 'Frontal\Cockpit\Annotations\Copy');
    if (!$propertyAnnotation) { // ignore if property has no "copy" annotation
    continue;
    }

    $copyMethod = $propertyAnnotation->type;
    $getter = 'get' . ucfirst($propertyName);
    $setter = 'set' . ucfirst($propertyName);

    $originalValue = $object->$getter();

    if ($copyMethod == 'reference') { // copy as reference
    $copiedValue = $this->copyAsReference($originalValue);

    } else { // clone value itself (if its a model, it will copied again by annotations)
    $copiedValue = $this->copyAsClone($originalValue);
    }
    if ($copiedValue != NULL) {
    $copy->$setter($copiedValue);
    }
    }
    $this->recursionService->out();
    return $copy;
    }

    protected function copyAsReference($value) {
    $objectManager = $this->objectManager;
    if ($value instanceof Doctrine\Common\Collections\ArrayCollection) { //TODO: more general class? traversable?
    // collection; copy collection and attach items to this new collection
    // if 1:n mapping is used, items are detached from their old collection - this is
    // a limitation of this type of reference
    $newStorage = $this->objectManager->get('Doctrine\Common\Collections\ArrayCollection');
    foreach ($value as $item) {
    $newStorage->attach($item);
    }
    return $newStorage;
    //} else if ($value instanceof Tx_Locus_Domain_Model_AbstractDomainModelObject) {
    // 1:1 mapping as reference; return object itself
    // return $value;
    } else if (is_object($value)) {
    // fallback case for class copying - value objects and such
    return $value;
    } else {
    // this case is very unlikely: means someone wished to copy hard type as a reference - so return a copy instead
    return $value;
    }
    }

    protected function copyAsClone($value) {
    if ($value instanceof Doctrine\Common\Collections\ArrayCollection) {
    // collection; copy storage and copy items, return new storage
    $newStorage = $this->objectManager->get('Doctrine\Common\Collections\ArrayCollection');
    foreach ($value as $item) {
    $newItem = $this->copy($item);
    $newStorage->attach($newItem);
    }
    return $newStorage;
    } else if (is_object($value)) {
    // fallback case for class copying - value objects and such
    return clone $value;
    } else {
    // value is probably a string
    return $value;
    }
    }

    }

    ?>
    53 changes: 53 additions & 0 deletions Example.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,53 @@
    <?php
    namespace Foo\Bar\Domain\Model;

    use TYPO3\Flow\Annotations as Flow;
    use Foo\Bar\Annotations as CP;

    /**
    * @Flow\Entity
    */
    class Example {

    /**
    * @var string
    * @CP\Copy
    * => copy content
    */
    protected $title;

    /**
    * @var \Foo\Bar\Domain\Model\ExampleCategory
    * @CP\Copy(type="reference")
    * => keep reference to relation in copy
    */
    protected $category;

    /**
    * @var \Foo\Bar\Domain\Model\ExampleChild
    * @CP\Copy
    * => copy related object
    */
    protected $child;

    /**
    * @var \Doctrine\Common\Collections\ArrayCollection<\Foo\Bar\Domain\Model\ExampleChild>
    * @CP\Copy
    * => copy related objects
    */
    protected $childs;

    /**
    * @var \Doctrine\Common\Collections\ArrayCollection<\Foo\Bar\Domain\Model\MmChilds>
    * @ORM\ManyToMany
    * @CP\Copy(type="reference")
    * => copy reference to existing objects
    */
    protected $mmChilds;


    // ...

    }
    ?>

    212 changes: 212 additions & 0 deletions RecursionService.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,212 @@
    <?php
    namespace Foo\Bar\Service;

    use TYPO3\Flow\Annotations as Flow;

    /**
    * @Flow\Scope("singleton")
    */
    class RecursionService {

    /**
    * @var string
    */
    private $_exceptionMessage = 'Recursion problem occurred';

    /**
    * @var int
    */
    private $_level = 0;

    /**
    * @var int
    */
    private $_maxLevel = 16;

    /**
    * @var int
    */
    private $_maxEncounters = 1;

    /**
    * @var array
    */
    private $_encountered = array();

    /**
    * @var boolean
    */
    private $_autoReset = FALSE;

    /**
    * Set the message used to prepend Exceptions
    * @param string $msg
    */
    public function setExceptionMessage($msg) {
    $this->_exceptionMessage = $msg;
    }

    /**
    * Get the message used to prepend Exceptions
    * @return string
    */
    public function getExceptionMessage() {
    return $this->_exceptionMessage;
    }

    /**
    * Set automatic resetting of encounters and level (TRUE/FALSE)
    * @param boolean $reset
    */
    public function setAutoReset($reset) {
    $this->_autoReset = $reset;
    }

    /**
    * Set the maximum allowed number of times a particular identifier may be encountered before an Exception is thrown
    * @param unknown_type $max
    */
    public function setMaxEncounters($max) {
    $this->_maxEncounters = $max;
    }

    /**
    * Get the maximum allowed number of encounters
    * @return int
    */
    public function getMaxEncounters() {
    return $this->_maxEncounters;
    }

    /**
    * Set the maximum allowed recursion level
    * @param int $level
    */
    public function setMaxLevel($level) {
    $this->_maxLevel = $level;
    }

    /**
    * Get the maximum allowed recursion level
    * @return int
    */
    public function getMaxLevel() {
    return $this->_maxLevel;
    }

    /**
    * Get the current recursion level
    * @return int
    */
    public function getLevel() {
    return $this->_level;
    }

    /**
    * Get the identifier last encountered
    * @return mixed
    */
    public function getLastEncounter() {
    return array_pop($this->_encountered);
    }

    /**
    * Increase recursion level (start of implementer function)
    */
    public function in() {
    $this->_level++;
    }

    /**
    * Decrease recursion level (end of implementer function)
    */
    public function out() {
    $this->_level--;
    }

    /**
    * Encounter $data (usually a string), call this when new values are read in your recursive function
    * @param mixed $data
    */
    public function encounter($data) {
    array_push($this->_encountered, $data);
    $this->check();
    }

    /**
    * Check the current recursion level and encounter status. Call in each iteration of your function
    * @param string $exitMsg
    */
    public function check($exitMsg='<no message>') {
    $level = $this->getLevel();
    $maxEnc = $this->getMaxEncounters();
    $message = $this->getExceptionMessage();
    if ($this->failsOnLevel()) {
    $msg = "{$message} at level {$level} with message: {$exitMsg}";
    throw new Exception($msg);
    }
    if ($this->failsOnMaxEncounters()) {
    $msg = "{$message} at encounter {$maxEnc} of {$maxEnc} allowed with message: {$exitMsg}";
    $this->throwException($msg);
    }
    return TRUE;
    }

    /**
    * Reset all counters
    */
    public function reset() {
    $this->_level = 0;
    $this->_encountered = array();
    }

    /**
    * Throw an Exception - wrapper; check for auto-reset and reset if needed
    * @param string $message
    * @throws Exception
    */
    private function throwException($message) {
    if ($this->_autoReset === TRUE) {
    $this->reset();
    }
    throw new Exception($message);
    }

    /**
    * Check if the current iteration violates level restraints
    * @return boolean
    */
    private function failsOnLevel() {
    $level = $this->getLevel();
    $max = $this->getMaxLevel();
    return (bool) ($level >= $max);
    }

    /**
    * Check if the current iteration violates encounter restraints
    * @return boolean
    */
    private function failsOnMaxEncounters() {
    $lastEncounter = $this->getLastEncounter();
    $occurrences = $this->countEncounters($lastEncounter);
    $max = $this->getMaxEncounters();
    return (bool) ($occurrences > $max);
    }

    /**
    * Count number of times the identifier $encounter has been encountered
    * @param mixed $encounter
    * @return int
    */
    private function countEncounters($encounter) {
    $num = 0;
    foreach ($this->_encountered as $encountered) {
    if ($encountered === $encounter) {
    $num++;
    }
    }
    return (int) $num;
    }

    }
    ?>