/**
  * {@inheritDoc}
  */
 public function createMetadata($reference)
 {
     // clear the method arrays
     $this->getters = [];
     $this->setters = [];
     $this->adders = [];
     $ref = new \ReflectionClass($reference);
     $this->findClayMethods($ref);
     $entityMetadata = new EntityMetadata($ref->getName());
     /*
     For each setter (or adder) that has a getter (e.g. a property that is fully accessible) check if it has a
     relationship to another entity class, otherwise detect the property type
     */
     foreach ($this->setters as $property => $setterMethod) {
         if (empty($this->getters[$property])) {
             // can't continue without a getter
             continue;
         }
         /** @var \ReflectionMethod $setterMethod */
         /** @var \ReflectionParameter $valueParam */
         $valueParam = $setterMethod->getParameters()[0];
         if ($valueParam->isArray()) {
             // this is a collection, check for an adder so we can check type on the collection elements
             if (!empty($this->adders[$property])) {
                 $setterMethod = $this->adders[$property];
                 $arguments = $setterMethod->getParameters();
                 // take the last argument, as the value parameter will be #2 for adders that apply to associative arrays
                 $valueParam = end($arguments);
             }
         }
         $class = $valueParam->getClass();
         // ignore properties that have relationships with other entities
         if (empty($class) || empty($class->getNamespaceName())) {
             /** @var \ReflectionMethod $getterMethod */
             $getterMethod = $this->getters[$property];
             $getter = $getterMethod->getName();
             $setter = $setterMethod->getName();
             if ($valueParam->isArray()) {
                 $type = EntityMetadata::FIELD_TYPE_ARRAY;
             } else {
                 // detect type
                 $type = $this->detectPropertyType($this->owningClass[$property], $getter, $setter, $property);
             }
             $fieldMetadata = [EntityMetadata::METADATA_FIELD_TYPE => $type, EntityMetadata::METADATA_FIELD_GETTER => $getter, EntityMetadata::METADATA_FIELD_SETTER => $setter];
             // underscore the property name
             $property = $this->toSplitCase($property);
             $entityMetadata->addFieldMetadata($property, $fieldMetadata);
         }
     }
     return $entityMetadata;
 }
 protected function addIncludes(TokenSequencerInterface $query, $includeRelationships = null)
 {
     $includeRelationships = is_null($includeRelationships) ? $this->includeRelationshipsByDefault : $includeRelationships;
     if (!empty($includeRelationships)) {
         if (!is_array($includeRelationships) || isset($includeRelationships[0])) {
             $includeRelationships = ["this" => $includeRelationships];
         }
         $allRelationships = ["this" => $this->entityMetadata->getRelationships()];
         $relationships = [];
         $parents = [];
         // add all the relationship metadata we need into the allRelationships array
         foreach (array_keys($includeRelationships) as $alias) {
             if ($alias == "this") {
                 // already added the subject entity's relationships
                 continue;
             }
             foreach ($allRelationships as $subset) {
                 if (!empty($subset[$alias])) {
                     $relationshipEntity = $subset[$alias][EntityMetadata::METADATA_ENTITY];
                     $relationshipMetadata = $this->metadataProvider->getEntityMetadata($relationshipEntity);
                     $allRelationships[$alias] = $relationshipMetadata->getRelationships();
                     break;
                 }
             }
         }
         foreach ($includeRelationships as $alias => $includes) {
             if (empty($allRelationships[$alias])) {
                 continue;
             }
             $thisRelationships = $allRelationships[$alias];
             // if we have an array of includes, filter thisRelationships
             if (is_array($includes)) {
                 $thisRelationships = array_intersect_key($allRelationships[$alias], array_flip($includes));
             }
             // record the parent of each of the child aliases, so we can reference them when we include the entity
             foreach (array_keys($thisRelationships) as $childAlias) {
                 $parents[$childAlias] = $alias;
             }
             // add thisRelationships to the final list
             $relationships = array_replace($relationships, $thisRelationships);
         }
         // get the metadata for each entity and include it on the query
         foreach ($relationships as $alias => $relationship) {
             $metadata = $this->metadataProvider->getEntityMetadata($relationship[EntityMetadata::METADATA_ENTITY]);
             if ($alias == $metadata->getEntity()) {
                 $alias = "";
             }
             $parent = "";
             if (!empty($parents[$alias]) && $parents[$alias] != "this") {
                 $parent = $parents[$alias];
             }
             $query->includeEntity($metadata, $alias, $parent);
         }
     }
 }
 /**
  * @dataProvider relationshipProvider
  *
  * @param array $relationships
  * @param array $expectedPatterns
  * @param array $unexpectedPatterns
  * @param string $intermediaryCollection
  */
 public function testProcessingRelationships(array $relationships, array $expectedPatterns, array $unexpectedPatterns = [], $intermediaryCollection = "")
 {
     $outputDir = vfsStream::url($this->testDir);
     $this->metadata->shouldReceive("getFields")->andReturn(["id" => [EntityMetadata::METADATA_FIELD_TYPE => EntityMetadata::FIELD_TYPE_INT]]);
     $this->metadata->shouldReceive("getPrimaryKey")->andReturn("id");
     $this->metadata->shouldReceive("getRelationships")->andReturn($relationships);
     if (!empty($intermediaryCollection)) {
         // setup intermediary metadata
         $this->intermediaryMetadata->shouldReceive("getCollection")->andReturn($intermediaryCollection);
     }
     $generator = new MigrationGenerator($this->metadataProvider, $outputDir);
     $fileName = $generator->generateMigrationFor("Test\\Entity");
     $this->assertFileExists($fileName);
     $fileContents = file_get_contents($fileName);
     foreach ($expectedPatterns as $pattern) {
         $this->assertRegExp($pattern, $fileContents);
     }
     foreach ($unexpectedPatterns as $pattern) {
         $this->assertNotRegExp($pattern, $fileContents);
     }
 }
 public function includeEntity(EntityMetadata $childMetadata, $collectionAlias = "", $parent = "", TokenSequencerInterface $additionalFilters = null)
 {
     // check we're dealing with a query
     if (!$this->isQuery()) {
         throw new TokenParseException("Cannot include an entity on an expression sequence.");
     }
     // get data on the child entity
     $childEntity = $childMetadata->getEntity();
     $childCollection = $childMetadata->getCollection();
     $childAlias = empty($collectionAlias) ? $childCollection : $collectionAlias;
     // determine which entity to join on (the parent)
     $parentMetadata = $this->getEntityMetadata();
     if (!empty($parent)) {
         if (empty($this->includes[$parent])) {
             throw new TokenParseException("Cannot include the entity '{$childEntity}'. The parent entity '{$parent}' has not yet been included");
         }
         /** @var EntityMetadata $parentMetadata */
         $parentMetadata = $this->includes[$parent];
         if ($parent == $parentMetadata->getEntity()) {
             $parent = $parentMetadata->getCollection();
         }
     }
     // make sure we have a parent that we can use as a table name or alias
     if (empty($parent) || $parent == $parentMetadata->getEntity()) {
         $parent = $parentMetadata->getCollection();
     }
     // get the relationship between the child and parent
     $relationshipAlias = empty($collectionAlias) ? $childEntity : $collectionAlias;
     $relationship = $parentMetadata->getRelationship($relationshipAlias);
     if (empty($relationship)) {
         throw new TokenParseException("The parent entity '{$parentMetadata->getEntity()}' has no relationship defined for '{$relationshipAlias}'");
     }
     // find the fields we will need to use in the join condition
     // we need the primary key and/or the parent field defined in the relationship
     $parentKey = $parentMetadata->getPrimaryKey();
     $parentField = $parent . "." . (empty($relationship[EntityMetadata::METADATA_RELATIONSHIP_OUR_FIELD]) ? $parentKey : $relationship[EntityMetadata::METADATA_RELATIONSHIP_OUR_FIELD]);
     // we need the primary key and/or the child field defined in the relationship
     $childKey = $childMetadata->getPrimaryKey();
     $childField = $childAlias . "." . (empty($relationship[EntityMetadata::METADATA_RELATIONSHIP_THEIR_FIELD]) ? $childKey : $relationship[EntityMetadata::METADATA_RELATIONSHIP_THEIR_FIELD]);
     // many to many relationships require an extra join, so treat them differently
     if ($relationship[EntityMetadata::METADATA_RELATIONSHIP_TYPE] != EntityMetadata::RELATIONSHIP_TYPE_MANY_TO_MANY) {
         // create the join condition
         $onClause = new TokenSequencer($this->tokenFactory);
         $onClause->ref($parentField)->op("=")->ref($childField);
         // if we have additional join filters, add them now
         if (!empty($additionalFilters)) {
             $onClause->andL()->mergeSequence($additionalFilters->getSequence());
         }
         // create the join
         $this->join($childCollection, $onClause, $collectionAlias);
     } else {
         // many to many. Requires intermediary join table
         $joinTable = $relationship[EntityMetadata::METADATA_RELATIONSHIP_JOIN_TABLE];
         // create the join condition from the parent to the intermediate
         $parentToMany = new TokenSequencer($this->tokenFactory);
         $parentToMany->ref($parentField)->op("=")->ref("{$joinTable}.{$parent}_id");
         // create the join condition from the intermediate to the child, including any additional join filters
         $manyToChild = new TokenSequencer($this->tokenFactory);
         $manyToChild->ref("{$joinTable}.{$childAlias}_id")->op("=")->ref($childField);
         if (!empty($additionalFilters)) {
             $manyToChild->andL()->mergeSequence($additionalFilters->getSequence());
         }
         // create the joins
         $this->join($joinTable, $parentToMany)->join($childCollection, $manyToChild, $collectionAlias);
     }
     // add the include
     $this->includes[$childAlias] = $childMetadata;
     return $this;
 }
 /**
  * @dataProvider pkMetadataProvider
  *
  * @param $fieldMetadata
  * @param $expectedMetadata
  */
 public function testPublicKeyMetadata($fieldMetadata, $expectedMetadata)
 {
     $pk = "id";
     $metadata = new EntityMetadata("blah");
     $metadata->setPrimaryKey($pk);
     if (!is_null($fieldMetadata)) {
         $metadata->addFieldMetadata($pk, $fieldMetadata);
     }
     $pkMetadata = $metadata->getPrimaryKeyMetadata();
     foreach ($expectedMetadata as $field => $value) {
         $this->assertEquals($value, $pkMetadata[$field]);
     }
 }