Last active
March 2, 2021 20:22
-
-
Save BacLuc/4476549b47a470b86f5d1fa84157c824 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 | |
/** | |
* Created by PhpStorm. | |
* User: lucius | |
* Date: 19.10.16 | |
* Time: 16:49 | |
*/ | |
namespace Concrete\Package\BasicTablePackage\Src\DiscriminatorEntry; | |
use Doctrine\Common\Annotations\AnnotationReader; | |
use Doctrine\Common\Annotations\SimpleAnnotationReader; | |
/** | |
* Class Annotation | |
* @package Concrete\Package\BasicTablePackage\Src\DiscriminatorEntry | |
*/ | |
class Annotation | |
{ | |
/** | |
* @var SimpleAnnotationReader | |
*/ | |
public static $reader; | |
public static function getAnnotationsForClass( $className ) { | |
$class = new \ReflectionClass( $className ); | |
return Annotation::$reader->getClassAnnotations( $class ); | |
} | |
} | |
//set the reader. Externally, because you cannot set a static property as a return value of a function | |
Annotation::$reader = new AnnotationReader(); | |
//the function with the default namespace does not exist anymore | |
//Annotation::$reader->setDefaultAnnotationNamespace( __NAMESPACE__ . "" ); |
This file contains hidden or 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 | |
/** | |
* Created by PhpStorm. | |
* User: lucius | |
* Date: 19.10.16 | |
* Time: 16:48 | |
*/ | |
namespace Concrete\Package\BasicTablePackage\Src\DiscriminatorEntry; | |
/** | |
* Class DiscriminatorEntry | |
* package Concrete\Package\BasicTablePackage\Src\DiscriminatorEntry | |
* added Target Annotation that it only should have effect on class annotations | |
* if you want to use an attribute, you need to define it, and the type, else you have an exception | |
* @Annotation | |
* @Target({"CLASS"}) | |
* @Attributes({ | |
* @Attribute("value", type = "string"), | |
* }) | |
*/ | |
class DiscriminatorEntry | |
{ | |
private $value; | |
public function __construct( array $data ) { | |
$this->value = $data['value']; | |
} | |
public function getValue() { | |
return $this->value; | |
} | |
} |
This file contains hidden or 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 | |
namespace Concrete\Package\BasicTablePackage\Src\DiscriminatorEntry; | |
use Concrete\Package\BasicTablePackage\Src\BaseEntity; | |
/** | |
* This Listener listens to the loadClassMetadata event. Upon this event | |
* it hooks into Doctrine to update discriminator maps. Adding entries | |
* to the discriminator map at parent level is just not nice. We turn this | |
* around with this mechanism. In the subclass you will be able to give an | |
* entry for the discriminator map. In this listener we will retrieve the | |
* load metadata event to update the parent with a good discriminator map, | |
* collecting all entries from the subclasses. | |
*/ | |
class DiscriminatorListener implements \Doctrine\Common\EventSubscriber { | |
// The driver of Doctrine, can be used to find all loaded classes | |
private $driver; | |
// The *temporary* map used for one run, when computing everything | |
private $map; | |
// The cached map, this holds the results after a computation, also for other classes | |
private $cachedMap; | |
const ENTRY_ANNOTATION = 'Concrete\Package\BasicTablePackage\Src\DiscriminatorEntry\DiscriminatorEntry'; | |
public function getSubscribedEvents() { | |
return Array( \Doctrine\ORM\Events::loadClassMetadata ); | |
} | |
public function __construct( \Doctrine\ORM\EntityManager $db ) { | |
$this->driver = $db->getConfiguration()->getMetadataDriverImpl(); | |
$this->cachedMap = Array(); | |
} | |
/** | |
* | |
* @param $class | |
* @return array | |
*/ | |
public static function getSubClasses($class){ | |
$subclasses = array(); | |
foreach(get_declared_classes() as $potentialSubclass) | |
{ | |
$reflection = new \ReflectionClass($potentialSubclass); | |
if($reflection ->isSubclassOf($class)){ | |
$subclasses[] = $potentialSubclass; | |
} | |
} | |
return $subclasses; | |
} | |
public function loadClassMetadata( \Doctrine\ORM\Event\LoadClassMetadataEventArgs $event ) { | |
// Reset the temporary calculation map and get the classname | |
$this->map = Array(); | |
$class = $event->getClassMetadata()->name; | |
//because it now has some problem with not importet annotations, limit it to the entities i have control over | |
$reflection = new \ReflectionClass($class); | |
if(!$reflection->isSubclassOf("Concrete\\Package\\BasicTablePackage\\Src\\BaseEntity")){ | |
return; | |
} | |
// Did we already calculate the map for this element? | |
if( array_key_exists( $class, $this->cachedMap ) ) { | |
$this->overrideMetadata( $event, $class ); | |
return; | |
} | |
// Do we have to process this class? | |
if( count( $event->getClassMetadata()->discriminatorMap ) < 2 | |
&& $this->extractEntry( $class ) ) { | |
// Now build the whole map | |
$this->checkFamily( $class ); | |
} else { | |
// Nothing to do… | |
return; | |
} | |
// Create the lookup entries | |
$dMap = array_flip( $this->map ); | |
foreach( $this->map as $cName => $discr ) { | |
$this->cachedMap[$cName]['map'] = $dMap; | |
$this->cachedMap[$cName]['discr'] = $this->map[$cName]; | |
} | |
// Override the data for this class | |
$this->overrideMetadata( $event, $class ); | |
} | |
private function overrideMetadata( \Doctrine\ORM\Event\LoadClassMetadataEventArgs $event, $class ) { | |
// Set the discriminator map and value | |
$event->getClassMetadata()->discriminatorMap = $this->cachedMap[$class]['map']; | |
$event->getClassMetadata()->discriminatorValue = $this->cachedMap[$class]['discr']; | |
// If we are the top-most parent, set subclasses! | |
if( isset( $this->cachedMap[$class]['isParent'] ) && $this->cachedMap[$class]['isParent'] === true ) { | |
$subclasses = $this->cachedMap[$class]['map']; | |
unset( $subclasses[$this->cachedMap[$class]['discr']] ); | |
if(!is_array($subclasses)){ | |
$subclasses = array(); | |
} | |
$event->getClassMetadata()->subClasses = array_values( $subclasses ); | |
} | |
} | |
private function checkFamily( $class ) { | |
$rc = new \ReflectionClass( $class ); | |
$parent = $rc->getParentClass()->name; | |
if( $parent !== null) { //if no parent class is there, its null, not false | |
// Also check all the children of our parent | |
$this->checkFamily( $parent ); | |
} else { | |
// This is the top-most parent, used in overrideMetadata | |
$this->cachedMap[$class]['isParent'] = true; | |
// Find all the children of this class | |
$this->checkChildren( $class ); | |
} | |
} | |
private function checkChildren( $class ) { | |
/* | |
Because $this->driver->getAllClassNames() did not work, implemented own method to get subclasses | |
attention, getSubClasses returns all Child Classes, not only the direct Child classes | |
*/ | |
foreach( static::getSubClasses($class) as $name ) { | |
$cRc = new \ReflectionClass( $name ); | |
$cParent = $cRc->getParentClass()->name; | |
// Haven't done this class yet? Go for it. | |
//removed the check if it is a direct child. It does not really matter (and didn't work somehow) | |
if( !array_key_exists( $name, $this->map ) && $this->extractEntry( $name ) ) { | |
$this->checkChildren( $name ); | |
} | |
} | |
} | |
private function extractEntry( $class ) | |
{ | |
$annotations = Annotation::getAnnotationsForClass($class); | |
/* | |
* getAnnotationsForClass gives back an array like this. There is no key called class | |
* Array | |
( | |
[0] => Doctrine\ORM\Mapping\Entity Object | |
( | |
[repositoryClass] => | |
[readOnly] => | |
) | |
[1] => Concrete\Package\BasicTablePackage\Src\DiscriminatorEntry\DiscriminatorEntry Object | |
( | |
[value:Concrete\Package\BasicTablePackage\Src\DiscriminatorEntry\DiscriminatorEntry:private] => Concrete\Package\BaclucPersonPackage\Src\PostalAddress | |
) | |
[2] => Doctrine\ORM\Mapping\Table Object | |
( | |
[name] => bacluc_postal_address | |
[schema] => | |
[indexes] => | |
[uniqueConstraints] => | |
[options] => Array | |
( | |
) | |
) | |
) | |
* */ | |
$success = false; | |
foreach($annotations as $key => $annotation){ | |
if(get_class($annotation) == self::ENTRY_ANNOTATION){ | |
//TODO check for duplicates | |
$this->map[$class] = $annotation->getValue(); | |
$success = true; | |
} | |
} | |
// if (is_array($annotations['class'])) { | |
// if (array_key_exists(self::ENTRY_ANNOTATION, $annotations['class'])) { | |
// $value = $annotations['class'][self::ENTRY_ANNOTATION]->value; | |
// | |
// if (in_array($value, $this->map)) { | |
// throw new \Exception("Found duplicate discriminator map entry '" . $value . "' in " . $class); | |
// } | |
// | |
// $this->map[$class] = $value; | |
// $success = true; | |
// } | |
// } | |
return $success; | |
} | |
} |
This file contains hidden or 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 Controller extends Package{ | |
/** | |
* @param EntityManager $em | |
* Because Doctrine itself requires on the topmost entity a discriminatormap with all subentities, | |
* we add here a EventListener when Doctrine Parses the Annotations. | |
* This DiscriminatorListener scans the Annotations of Child Classes of BaseEntity for | |
* @DiscriminatorEntry(value="Namespace\Classname") and adds them to the Discriminator Map, | |
* So that you don't have to define the Cildren in the topmost parent class. | |
*/ | |
public static function addDiscriminatorListenerToEm(EntityManager $em){ | |
if(!$em->DiscriminatorListenerAttached) { | |
$em->getEventManager()->addEventSubscriber(new DiscriminatorListener($em)); | |
$em->DiscriminatorListenerAttached = true; | |
} | |
} | |
/** | |
* @return EntityManager | |
* @overrides Package::getEntityManager | |
* if the Package is installed, this function calls static::addDiscriminatorListenerToEm on the EntityManager | |
* To add support for @DiscriminatorEntry Annotation | |
* Only after Installation, because else the Classes to Support this are not found | |
*/ | |
public function getEntityManager() | |
{ | |
$em = parent::getEntityManager(); // TODO: Change the autogenerated stub | |
if(parent::isPackageInstalled()) { | |
static::addDiscriminatorListenerToEm($em); | |
} | |
return $em; | |
} | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Tested https://gist.github.com/jasperkuperus/03302fefe6e4722ab650 in the environment of Concrete5 Packages.
Don't know if Doctrine changed so much between 2.x and 2.4 Concrete5 is using, but i had to make several changes.