Beispiel #1
0
    /**
     * @param ClassMetadata $class
     * @param string $hydratorClassName
     * @param string $fileName
     */
    private function generateHydratorClass(ClassMetadata $class, $hydratorClassName, $fileName)
    {
        $code = '';
        foreach ($class->fieldMappings as $fieldName => $mapping) {
            if (isset($mapping['alsoLoadFields'])) {
                foreach ($mapping['alsoLoadFields'] as $name) {
                    $code .= sprintf(<<<EOF

        /** @AlsoLoad("{$name}") */
        if (!array_key_exists('%1\$s', \$data) && array_key_exists('{$name}', \$data)) {
            \$data['%1\$s'] = \$data['{$name}'];
        }

EOF
, $mapping['name']);
                }
            }
            if ($mapping['type'] === 'date') {
                $code .= sprintf(<<<EOF

        /** @Field(type="date") */
        if (isset(\$data['%1\$s'])) {
            \$value = \$data['%1\$s'];
            %3\$s
            \$this->class->reflFields['%2\$s']->setValue(\$document, clone \$return);
            \$hydratedData['%2\$s'] = \$return;
        }

EOF
, $mapping['name'], $mapping['fieldName'], Type::getType($mapping['type'])->closureToPHP());
            } elseif (!isset($mapping['association'])) {
                $code .= sprintf(<<<EOF

        /** @Field(type="{$mapping['type']}") */
        if (isset(\$data['%1\$s'])) {
            \$value = \$data['%1\$s'];
            %3\$s
            \$this->class->reflFields['%2\$s']->setValue(\$document, \$return);
            \$hydratedData['%2\$s'] = \$return;
        }

EOF
, $mapping['name'], $mapping['fieldName'], Type::getType($mapping['type'])->closureToPHP());
            } elseif ($mapping['association'] === ClassMetadata::REFERENCE_ONE && $mapping['isOwningSide']) {
                $code .= sprintf(<<<EOF

        /** @ReferenceOne */
        if (isset(\$data['%1\$s'])) {
            \$reference = \$data['%1\$s'];
            if (isset(\$this->class->fieldMappings['%2\$s']['simple']) && \$this->class->fieldMappings['%2\$s']['simple']) {
                \$className = \$this->class->fieldMappings['%2\$s']['targetDocument'];
                \$mongoId = \$reference;
            } else {
                \$className = \$this->unitOfWork->getClassNameForAssociation(\$this->class->fieldMappings['%2\$s'], \$reference);
                \$mongoId = \$reference['\$id'];
            }
            \$targetMetadata = \$this->dm->getClassMetadata(\$className);
            \$id = \$targetMetadata->getPHPIdentifierValue(\$mongoId);
            \$return = \$this->dm->getReference(\$className, \$id);
            \$this->class->reflFields['%2\$s']->setValue(\$document, \$return);
            \$hydratedData['%2\$s'] = \$return;
        }

EOF
, $mapping['name'], $mapping['fieldName']);
            } elseif ($mapping['association'] === ClassMetadata::REFERENCE_ONE && $mapping['isInverseSide']) {
                if (isset($mapping['repositoryMethod']) && $mapping['repositoryMethod']) {
                    $code .= sprintf(<<<EOF

        \$className = \$this->class->fieldMappings['%2\$s']['targetDocument'];
        \$return = \$this->dm->getRepository(\$className)->%3\$s(\$document);
        \$this->class->reflFields['%2\$s']->setValue(\$document, \$return);
        \$hydratedData['%2\$s'] = \$return;

EOF
, $mapping['name'], $mapping['fieldName'], $mapping['repositoryMethod']);
                } else {
                    $code .= sprintf(<<<EOF

        \$mapping = \$this->class->fieldMappings['%2\$s'];
        \$className = \$mapping['targetDocument'];
        \$targetClass = \$this->dm->getClassMetadata(\$mapping['targetDocument']);
        \$mappedByMapping = \$targetClass->fieldMappings[\$mapping['mappedBy']];
        \$mappedByFieldName = isset(\$mappedByMapping['simple']) && \$mappedByMapping['simple'] ? \$mapping['mappedBy'] : \$mapping['mappedBy'].'.\$id';
        \$criteria = array_merge(
            array(\$mappedByFieldName => \$data['_id']),
            isset(\$this->class->fieldMappings['%2\$s']['criteria']) ? \$this->class->fieldMappings['%2\$s']['criteria'] : array()
        );
        \$sort = isset(\$this->class->fieldMappings['%2\$s']['sort']) ? \$this->class->fieldMappings['%2\$s']['sort'] : array();
        \$return = \$this->unitOfWork->getDocumentPersister(\$className)->load(\$criteria, null, array(), 0, \$sort);
        \$this->class->reflFields['%2\$s']->setValue(\$document, \$return);
        \$hydratedData['%2\$s'] = \$return;

EOF
, $mapping['name'], $mapping['fieldName']);
                }
            } elseif ($mapping['association'] === ClassMetadata::REFERENCE_MANY || $mapping['association'] === ClassMetadata::EMBED_MANY) {
                $code .= sprintf(<<<EOF

        /** @Many */
        \$mongoData = isset(\$data['%1\$s']) ? \$data['%1\$s'] : null;
        \$return = new \\CosmoW\\ODM\\Riak\\PersistentCollection(new \\Doctrine\\Common\\Collections\\ArrayCollection(), \$this->dm, \$this->unitOfWork);
        \$return->setHints(\$hints);
        \$return->setOwner(\$document, \$this->class->fieldMappings['%2\$s']);
        \$return->setInitialized(false);
        if (\$mongoData) {
            \$return->setMongoData(\$mongoData);
        }
        \$this->class->reflFields['%2\$s']->setValue(\$document, \$return);
        \$hydratedData['%2\$s'] = \$return;

EOF
, $mapping['name'], $mapping['fieldName']);
            } elseif ($mapping['association'] === ClassMetadata::EMBED_ONE) {
                $code .= sprintf(<<<EOF

        /** @EmbedOne */
        if (isset(\$data['%1\$s'])) {
            \$embeddedDocument = \$data['%1\$s'];
            \$className = \$this->unitOfWork->getClassNameForAssociation(\$this->class->fieldMappings['%2\$s'], \$embeddedDocument);
            \$embeddedMetadata = \$this->dm->getClassMetadata(\$className);
            \$return = \$embeddedMetadata->newInstance();

            \$this->unitOfWork->setParentAssociation(\$return, \$this->class->fieldMappings['%2\$s'], \$document, '%1\$s');

            \$embeddedData = \$this->dm->getHydratorFactory()->hydrate(\$return, \$embeddedDocument, \$hints);
            \$embeddedId = \$embeddedMetadata->identifier && isset(\$embeddedData[\$embeddedMetadata->identifier]) ? \$embeddedData[\$embeddedMetadata->identifier] : null;

            \$this->unitOfWork->registerManaged(\$return, \$embeddedId, \$embeddedData);

            \$this->class->reflFields['%2\$s']->setValue(\$document, \$return);
            \$hydratedData['%2\$s'] = \$return;
        }

EOF
, $mapping['name'], $mapping['fieldName']);
            }
        }
        $className = $class->name;
        $namespace = $this->hydratorNamespace;
        $code = sprintf(<<<EOF
<?php

namespace {$namespace};

use CosmoW\\ODM\\Riak\\DocumentManager;
use CosmoW\\ODM\\Riak\\Mapping\\ClassMetadata;
use CosmoW\\ODM\\Riak\\Hydrator\\HydratorInterface;
use CosmoW\\ODM\\Riak\\UnitOfWork;

/**
 * THIS CLASS WAS GENERATED BY THE DOCTRINE ODM. DO NOT EDIT THIS FILE.
 */
class {$hydratorClassName} implements HydratorInterface
{
    private \$dm;
    private \$unitOfWork;
    private \$class;

    public function __construct(DocumentManager \$dm, UnitOfWork \$uow, ClassMetadata \$class)
    {
        \$this->dm = \$dm;
        \$this->unitOfWork = \$uow;
        \$this->class = \$class;
    }

    public function hydrate(\$document, \$data, array \$hints = array())
    {
        \$hydratedData = array();
%s        return \$hydratedData;
    }
}
EOF
, $code);
        if ($fileName === false) {
            if (!class_exists($namespace . '\\' . $hydratorClassName)) {
                eval(substr($code, 5));
            }
        } else {
            $parentDirectory = dirname($fileName);
            if (!is_dir($parentDirectory) && false === @mkdir($parentDirectory, 0775, true)) {
                throw HydratorException::hydratorDirectoryNotWritable();
            }
            if (!is_writable($parentDirectory)) {
                throw HydratorException::hydratorDirectoryNotWritable();
            }
            $tmpFileName = $fileName . '.' . uniqid('', true);
            file_put_contents($tmpFileName, $code);
            rename($tmpFileName, $fileName);
            chmod($fileName, 0664);
        }
    }
Beispiel #2
0
 private function generateDocumentStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null)
 {
     // Add/remove methods should use the singular form of the field name
     $formattedFieldName = in_array($type, array('add', 'remove')) ? Inflector::singularize($fieldName) : $fieldName;
     $methodName = $type . Inflector::classify($formattedFieldName);
     $variableName = Inflector::camelize($formattedFieldName);
     if ($this->hasMethod($methodName, $metadata)) {
         return;
     }
     $description = ucfirst($type) . ' ' . $variableName;
     $types = Type::getTypesMap();
     $methodTypeHint = $typeHint && !isset($types[$typeHint]) ? '\\' . $typeHint . ' ' : null;
     $variableType = $typeHint ? $typeHint . ' ' : null;
     $replacements = array('<description>' => $description, '<methodTypeHint>' => $methodTypeHint, '<variableType>' => $variableType, '<variableName>' => $variableName, '<methodName>' => $methodName, '<fieldName>' => $fieldName, '<variableDefault>' => $defaultValue !== null ? ' = ' . $defaultValue : '');
     $templateVar = sprintf('%sMethodTemplate', $type);
     $method = str_replace(array_keys($replacements), array_values($replacements), self::${$templateVar});
     return $this->prefixCodeWithSpaces($method);
 }
Beispiel #3
0
 /**
  * Casts the identifier to its database type.
  *
  * @param mixed $id
  * @return mixed $id
  */
 public function getDatabaseIdentifierValue($id)
 {
     $idType = $this->fieldMappings[$this->identifier]['type'];
     return Type::getType($idType)->convertToDatabaseValue($id);
 }
Beispiel #4
0
 /**
  * Prepares the query criteria or new document object.
  *
  * PHP field names and types will be converted to those used by Riak.
  *
  * @param array $query
  * @return array
  */
 public function prepareQueryOrNewObj(array $query)
 {
     $preparedQuery = array();
     foreach ($query as $key => $value) {
         // Recursively prepare logical query clauses
         if (in_array($key, array('$and', '$or', '$nor')) && is_array($value)) {
             foreach ($value as $k2 => $v2) {
                 $preparedQuery[$key][$k2] = $this->prepareQueryOrNewObj($v2);
             }
             continue;
         }
         if (isset($key[0]) && $key[0] === '$' && is_array($value)) {
             $preparedQuery[$key] = $this->prepareQueryOrNewObj($value);
             continue;
         }
         list($key, $value) = $this->prepareQueryElement($key, $value, null, true);
         $preparedQuery[$key] = is_array($value) ? array_map('CosmoW\\ODM\\Riak\\Types\\Type::convertPHPToDatabaseValue', $value) : Type::convertPHPToDatabaseValue($value);
     }
     return $preparedQuery;
 }
Beispiel #5
0
 /**
  * Used to do the common work of computeChangeSet and recomputeSingleDocumentChangeSet
  *
  * @param \CosmoW\ODM\Riak\Mapping\ClassMetadata $class
  * @param object $document
  * @param boolean $recompute
  */
 private function computeOrRecomputeChangeSet(ClassMetadata $class, $document, $recompute = false)
 {
     $oid = spl_object_hash($document);
     $actualData = $this->getDocumentActualData($document);
     $isNewDocument = !isset($this->originalDocumentData[$oid]);
     if ($isNewDocument) {
         // Document is either NEW or MANAGED but not yet fully persisted (only has an id).
         // These result in an INSERT.
         $this->originalDocumentData[$oid] = $actualData;
         $changeSet = array();
         foreach ($actualData as $propName => $actualValue) {
             /* At this PersistentCollection shouldn't be here, probably it
              * was cloned and its ownership must be fixed
              */
             if ($actualValue instanceof PersistentCollection && $actualValue->getOwner() !== $document) {
                 $actualData[$propName] = $this->fixPersistentCollectionOwnership($actualValue, $document, $class, $propName);
                 $actualValue = $actualData[$propName];
             }
             $changeSet[$propName] = array(null, $actualValue);
         }
         $this->documentChangeSets[$oid] = $changeSet;
     } else {
         // Document is "fully" MANAGED: it was already fully persisted before
         // and we have a copy of the original data
         $originalData = $this->originalDocumentData[$oid];
         $isChangeTrackingNotify = $class->isChangeTrackingNotify();
         if ($isChangeTrackingNotify && !$recompute && isset($this->documentChangeSets[$oid])) {
             $changeSet = $this->documentChangeSets[$oid];
         } else {
             $changeSet = array();
         }
         foreach ($actualData as $propName => $actualValue) {
             // skip not saved fields
             if (isset($class->fieldMappings[$propName]['notSaved']) && $class->fieldMappings[$propName]['notSaved'] === true) {
                 continue;
             }
             $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null;
             // skip if value has not changed
             if ($orgValue === $actualValue) {
                 // but consider dirty GridFSFile instances as changed
                 if (!(isset($class->fieldMappings[$propName]['file']) && $actualValue->isDirty())) {
                     continue;
                 }
             }
             // if relationship is a embed-one, schedule orphan removal to trigger cascade remove operations
             if (isset($class->fieldMappings[$propName]['embedded']) && $class->fieldMappings[$propName]['type'] === 'one') {
                 if ($orgValue !== null) {
                     $this->scheduleOrphanRemoval($orgValue);
                 }
                 $changeSet[$propName] = array($orgValue, $actualValue);
                 continue;
             }
             // if owning side of reference-one relationship
             if (isset($class->fieldMappings[$propName]['reference']) && $class->fieldMappings[$propName]['type'] === 'one' && $class->fieldMappings[$propName]['isOwningSide']) {
                 if ($orgValue !== null && $class->fieldMappings[$propName]['orphanRemoval']) {
                     $this->scheduleOrphanRemoval($orgValue);
                 }
                 $changeSet[$propName] = array($orgValue, $actualValue);
                 continue;
             }
             if ($isChangeTrackingNotify) {
                 continue;
             }
             // ignore inverse side of reference-many relationship
             if (isset($class->fieldMappings[$propName]['reference']) && $class->fieldMappings[$propName]['type'] === 'many' && $class->fieldMappings[$propName]['isInverseSide']) {
                 continue;
             }
             // Persistent collection was exchanged with the "originally"
             // created one. This can only mean it was cloned and replaced
             // on another document.
             if ($actualValue instanceof PersistentCollection && $actualValue->getOwner() !== $document) {
                 $actualValue = $this->fixPersistentCollectionOwnership($actualValue, $document, $class, $propName);
             }
             // if embed-many or reference-many relationship
             if (isset($class->fieldMappings[$propName]['type']) && $class->fieldMappings[$propName]['type'] === 'many') {
                 $changeSet[$propName] = array($orgValue, $actualValue);
                 /* If original collection was exchanged with a non-empty value
                  * and $set will be issued, there is no need to $unset it first
                  */
                 if ($actualValue && $actualValue->isDirty() && CollectionHelper::usesSet($class->fieldMappings[$propName]['strategy'])) {
                     continue;
                 }
                 if ($orgValue instanceof PersistentCollection) {
                     $this->scheduleCollectionDeletion($orgValue);
                 }
                 continue;
             }
             // skip equivalent date values
             if (isset($class->fieldMappings[$propName]['type']) && $class->fieldMappings[$propName]['type'] === 'date') {
                 $dateType = Type::getType('date');
                 $dbOrgValue = $dateType->convertToDatabaseValue($orgValue);
                 $dbActualValue = $dateType->convertToDatabaseValue($actualValue);
                 if ($dbOrgValue instanceof \MongoDate && $dbActualValue instanceof \MongoDate && $dbOrgValue == $dbActualValue) {
                     continue;
                 }
             }
             // regular field
             $changeSet[$propName] = array($orgValue, $actualValue);
         }
         if ($changeSet) {
             $this->documentChangeSets[$oid] = $recompute && isset($this->documentChangeSets[$oid]) ? $changeSet + $this->documentChangeSets[$oid] : $changeSet;
             $this->originalDocumentData[$oid] = $actualData;
             $this->scheduleForUpdate($document);
         }
     }
     // Look for changes in associations of the document
     $associationMappings = array_filter($class->associationMappings, function ($assoc) {
         return empty($assoc['notSaved']);
     });
     foreach ($associationMappings as $mapping) {
         $value = $class->reflFields[$mapping['fieldName']]->getValue($document);
         if ($value === null) {
             continue;
         }
         $this->computeAssociationChanges($document, $mapping, $value);
         if (isset($mapping['reference'])) {
             continue;
         }
         $values = $mapping['type'] === ClassMetadata::ONE ? array($value) : $value->unwrap();
         foreach ($values as $obj) {
             $oid2 = spl_object_hash($obj);
             if (isset($this->documentChangeSets[$oid2])) {
                 $this->documentChangeSets[$oid][$mapping['fieldName']] = array($value, $value);
                 if (!$isNewDocument) {
                     $this->scheduleForUpdate($document);
                 }
                 break;
             }
         }
     }
 }
Beispiel #6
0
 /**
  * Returns the embedded document to be stored in Riak.
  *
  * The return value will usually be an associative array with string keys
  * corresponding to field names on the embedded document. An object may be
  * returned if the document is empty, to ensure that a BSON object will be
  * stored in lieu of an array.
  *
  * If $includeNestedCollections is true, nested collections will be included
  * in this prepared value and the option will cascade to all embedded
  * associations. If any nested PersistentCollections (embed or reference)
  * within this value were previously scheduled for deletion or update, they
  * will also be unscheduled.
  *
  * @param array $embeddedMapping
  * @param object $embeddedDocument
  * @param boolean $includeNestedCollections
  * @return array|object
  * @throws \UnexpectedValueException if an unsupported associating mapping is found
  */
 public function prepareEmbeddedDocumentValue(array $embeddedMapping, $embeddedDocument, $includeNestedCollections = false)
 {
     $embeddedDocumentValue = array();
     $class = $this->dm->getClassMetadata(get_class($embeddedDocument));
     foreach ($class->fieldMappings as $mapping) {
         // Skip notSaved fields
         if (!empty($mapping['notSaved'])) {
             continue;
         }
         // Inline ClassMetadataInfo::getFieldValue()
         $rawValue = $class->reflFields[$mapping['fieldName']]->getValue($embeddedDocument);
         $value = null;
         if ($rawValue !== null) {
             switch (isset($mapping['association']) ? $mapping['association'] : null) {
                 // @Field, @String, @Date, etc.
                 case null:
                     $value = Type::getType($mapping['type'])->convertToDatabaseValue($rawValue);
                     break;
                 case ClassMetadata::EMBED_ONE:
                 case ClassMetadata::REFERENCE_ONE:
                     // Nested collections should only be included for embedded relationships
                     $value = $this->prepareAssociatedDocumentValue($mapping, $rawValue, $includeNestedCollections && isset($mapping['embedded']));
                     break;
                 case ClassMetadata::EMBED_MANY:
                 case ClassMetadata::REFERENCE_MANY:
                     // Skip PersistentCollections already scheduled for deletion
                     if (!$includeNestedCollections && $rawValue instanceof PersistentCollection && $this->uow->isCollectionScheduledForDeletion($rawValue)) {
                         break;
                     }
                     $value = $this->prepareAssociatedCollectionValue($rawValue, $includeNestedCollections);
                     break;
                 default:
                     throw new \UnexpectedValueException('Unsupported mapping association: ' . $mapping['association']);
             }
         }
         // Omit non-nullable fields that would have a null value
         if ($value === null && $mapping['nullable'] === false) {
             continue;
         }
         $embeddedDocumentValue[$mapping['name']] = $value;
     }
     /* Add a discriminator value if the embedded document is not mapped
      * explicitly to a targetDocument class.
      */
     if (!isset($embeddedMapping['targetDocument'])) {
         $discriminatorField = $embeddedMapping['discriminatorField'];
         $discriminatorValue = isset($embeddedMapping['discriminatorMap']) ? array_search($class->name, $embeddedMapping['discriminatorMap']) : $class->name;
         /* If the discriminator value was not found in the map, use the full
          * class name. In the future, it may be preferable to throw an
          * exception here (perhaps based on some strictness option).
          *
          * @see DocumentManager::createDBRef()
          */
         if ($discriminatorValue === false) {
             $discriminatorValue = $class->name;
         }
         $embeddedDocumentValue[$discriminatorField] = $discriminatorValue;
     }
     /* If the class has a discriminator (field and value), use it. A child
      * class that is not defined in the discriminator map may only have a
      * discriminator field and no value, so default to the full class name.
      */
     if (isset($class->discriminatorField)) {
         $embeddedDocumentValue[$class->discriminatorField] = isset($class->discriminatorValue) ? $class->discriminatorValue : $class->name;
     }
     // Ensure empty embedded documents are stored as BSON objects
     if (empty($embeddedDocumentValue)) {
         return (object) $embeddedDocumentValue;
     }
     /* @todo Consider always casting the return value to an object, or
      * building $embeddedDocumentValue as an object instead of an array, to
      * handle the edge case where all database field names are sequential,
      * numeric keys.
      */
     return $embeddedDocumentValue;
 }