-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Value objects (Based on #634) #835
Changes from all commits
b4b9709
02d34bb
32988b3
0204a8b
011776f
879ab6e
9613f1d
38b041d
c67ac8a
30897c3
41c937b
fd8b5bd
20fb827
4f6c150
f86abd8
97836ef
d4e6618
ece62d6
5586ddd
0cd6061
2b2f489
17e0a7b
9ad376c
fb3a06b
2a73a6f
0ee7b68
e5cab1d
928c32d
fbb7b5a
f7f7c46
4f585a3
9464194
7020f41
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
Separating Concerns using Embeddables | ||
------------------------------------- | ||
|
||
Embeddables are classes which are not entities themself, but are embedded | ||
in entities and can also be queried in DQL. You'll mostly want to use them | ||
to reduce duplication or separating concerns. | ||
|
||
For the purposes of this tutorial, we will assume that you have a ``User`` | ||
class in your application and you would like to store an address in | ||
the ``User`` class. We will model the ``Address`` class as an embeddable | ||
instead of simply adding the respective columns to the ``User`` class. | ||
|
||
.. configuration-block:: | ||
|
||
.. code-block:: php | ||
|
||
<?php | ||
|
||
/** @Entity */ | ||
class User | ||
{ | ||
/** @Embedded(class = "Address") */ | ||
private $address; | ||
} | ||
|
||
/** @Embeddable */ | ||
class Address | ||
{ | ||
/** @Column(type = "string") */ | ||
private $street; | ||
|
||
/** @Column(type = "string") */ | ||
private $postalCode; | ||
|
||
/** @Column(type = "string") */ | ||
private $city; | ||
|
||
/** @Column(type = "string") */ | ||
private $country; | ||
} | ||
|
||
.. code-block:: xml | ||
|
||
<doctrine-mapping> | ||
<entity name="User"> | ||
<embedded name="address" class="Address" /> | ||
</entity> | ||
|
||
<embeddable name="Address"> | ||
<field name="street" type="string" /> | ||
<field name="postalCode" type="string" /> | ||
<field name="city" type="string" /> | ||
<field name="country" type="string" /> | ||
</embeddable> | ||
</doctrine-mapping> | ||
|
||
.. code-block:: yaml | ||
|
||
User: | ||
type: entity | ||
embedded: | ||
address: | ||
class: Address | ||
|
||
Address: | ||
type: embeddable | ||
fields: | ||
street: { type: string } | ||
postalCode: { type: string } | ||
city: { type: string } | ||
country: { type: string } | ||
|
||
In terms of your database schema, Doctrine will automatically inline all | ||
columns from the ``Address`` class into the table of the ``User`` class, | ||
just as if you had declared them directly there. | ||
|
||
You can also use mapped fields of embedded classes in DQL queries, just | ||
as if they were declared in the ``User`` class: | ||
|
||
.. code-block:: sql | ||
|
||
SELECT u FROM User u WHERE u.address.city = :myCity | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -260,6 +260,13 @@ class ClassMetadataInfo implements ClassMetadata | |
*/ | ||
public $isMappedSuperclass = false; | ||
|
||
/** | ||
* READ-ONLY: Whether this class describes the mapping of an embeddable class. | ||
* | ||
* @var boolean | ||
*/ | ||
public $isEmbeddedClass = false; | ||
|
||
/** | ||
* READ-ONLY: The names of the parent classes (ancestors). | ||
* | ||
|
@@ -274,6 +281,13 @@ class ClassMetadataInfo implements ClassMetadata | |
*/ | ||
public $subClasses = array(); | ||
|
||
/** | ||
* READ-ONLY: The names of all embedded classes based on properties. | ||
* | ||
* @var array | ||
*/ | ||
public $embeddedClasses = array(); | ||
|
||
/** | ||
* READ-ONLY: The named queries allowed to be called directly from Repository. | ||
* | ||
|
@@ -799,6 +813,7 @@ public function __sleep() | |
'columnNames', //TODO: Not really needed. Can use fieldMappings[$fieldName]['columnName'] | ||
'fieldMappings', | ||
'fieldNames', | ||
'embeddedClasses', | ||
'identifier', | ||
'isIdentifierComposite', // TODO: REMOVE | ||
'name', | ||
|
@@ -907,6 +922,18 @@ public function wakeupReflection($reflService) | |
$this->reflClass = $reflService->getClass($this->name); | ||
|
||
foreach ($this->fieldMappings as $field => $mapping) { | ||
if (isset($mapping['declaredField'])) { | ||
$declaringClass = isset($this->embeddedClasses[$mapping['declaredField']]['declared']) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. <?php
$declaringClass = isset($this->embeddedClasses[$mapping['declaredField']]['declared'])
? $this->embeddedClasses[$mapping['declaredField']]['declared']
: $this->name; |
||
? $this->embeddedClasses[$mapping['declaredField']]['declared'] : $this->name; | ||
|
||
$this->reflFields[$field] = new ReflectionEmbeddedProperty( | ||
$reflService->getAccessibleProperty($declaringClass, $mapping['declaredField']), | ||
$reflService->getAccessibleProperty($this->embeddedClasses[$mapping['declaredField']]['class'], $mapping['originalField']), | ||
$this->embeddedClasses[$mapping['declaredField']]['class'] | ||
); | ||
continue; | ||
} | ||
|
||
$this->reflFields[$field] = isset($mapping['declared']) | ||
? $reflService->getAccessibleProperty($mapping['declared'], $field) | ||
: $reflService->getAccessibleProperty($this->name, $field); | ||
|
@@ -948,8 +975,12 @@ public function initializeReflection($reflService) | |
*/ | ||
public function validateIdentifier() | ||
{ | ||
if ($this->isMappedSuperclass || $this->isEmbeddedClass) { | ||
return; | ||
} | ||
|
||
// Verify & complete identifier mapping | ||
if ( ! $this->identifier && ! $this->isMappedSuperclass) { | ||
if ( ! $this->identifier) { | ||
throw MappingException::identifierRequired($this->name); | ||
} | ||
|
||
|
@@ -2150,6 +2181,11 @@ public function isInheritedAssociation($fieldName) | |
return isset($this->associationMappings[$fieldName]['inherited']); | ||
} | ||
|
||
public function isInheritedEmbeddedClass($fieldName) | ||
{ | ||
return isset($this->embeddedClasses[$fieldName]['inherited']); | ||
} | ||
|
||
/** | ||
* Sets the name of the primary table the class is mapped to. | ||
* | ||
|
@@ -2229,9 +2265,8 @@ private function _isInheritanceType($type) | |
public function mapField(array $mapping) | ||
{ | ||
$this->_validateAndCompleteFieldMapping($mapping); | ||
if (isset($this->fieldMappings[$mapping['fieldName']]) || isset($this->associationMappings[$mapping['fieldName']])) { | ||
throw MappingException::duplicateFieldMapping($this->name, $mapping['fieldName']); | ||
} | ||
$this->assertFieldNotMapped($mapping['fieldName']); | ||
|
||
$this->fieldMappings[$mapping['fieldName']] = $mapping; | ||
} | ||
|
||
|
@@ -2479,9 +2514,7 @@ protected function _storeAssociationMapping(array $assocMapping) | |
{ | ||
$sourceFieldName = $assocMapping['fieldName']; | ||
|
||
if (isset($this->fieldMappings[$sourceFieldName]) || isset($this->associationMappings[$sourceFieldName])) { | ||
throw MappingException::duplicateFieldMapping($this->name, $sourceFieldName); | ||
} | ||
$this->assertFieldNotMapped($sourceFieldName); | ||
|
||
$this->associationMappings[$sourceFieldName] = $assocMapping; | ||
} | ||
|
@@ -3116,4 +3149,55 @@ public function getMetadataValue($name) { | |
|
||
return null; | ||
} | ||
|
||
/** | ||
* Map Embedded Class | ||
* | ||
* @array $mapping | ||
* @return void | ||
*/ | ||
public function mapEmbedded(array $mapping) | ||
{ | ||
$this->assertFieldNotMapped($mapping['fieldName']); | ||
|
||
$this->embeddedClasses[$mapping['fieldName']] = array( | ||
'class' => $this->fullyQualifiedClassName($mapping['class']), | ||
'columnPrefix' => $mapping['columnPrefix'], | ||
); | ||
} | ||
|
||
/** | ||
* Inline the embeddable class | ||
* | ||
* @param string $property | ||
* @param ClassMetadataInfo $embeddable | ||
*/ | ||
public function inlineEmbeddable($property, ClassMetadataInfo $embeddable) | ||
{ | ||
foreach ($embeddable->fieldMappings as $fieldMapping) { | ||
$fieldMapping['declaredField'] = $property; | ||
$fieldMapping['originalField'] = $fieldMapping['fieldName']; | ||
$fieldMapping['fieldName'] = $property . "." . $fieldMapping['fieldName']; | ||
|
||
$fieldMapping['columnName'] = ! empty($this->embeddedClasses[$property]['columnPrefix']) | ||
? $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. souldn't this case be handled by the naming strategy too ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
: $this->namingStrategy->embeddedFieldToColumnName($property, $fieldMapping['columnName'], $this->reflClass->name, $embeddable->reflClass->name); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wrong indentation There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
$this->mapField($fieldMapping); | ||
} | ||
} | ||
|
||
/** | ||
* @param string $fieldName | ||
* @throws MappingException | ||
*/ | ||
private function assertFieldNotMapped($fieldName) | ||
{ | ||
if (isset($this->fieldMappings[$fieldName]) || | ||
isset($this->associationMappings[$fieldName]) || | ||
isset($this->embeddedClasses[$fieldName])) { | ||
|
||
throw MappingException::duplicateFieldMapping($this->name, $fieldName); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing line break between IFs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, this looks the same like the code 10 lines above (you need to open the entire file).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but AFAIK this is wrong from a CS perspective. See
doLoadMetadata()
. But there are still no concrete CS specifications ;)