From 3529ea8ae8d3c9198e362b49fda94fe5b4885349 Mon Sep 17 00:00:00 2001 From: Hikariii Date: Mon, 21 Mar 2016 08:34:17 +0100 Subject: [PATCH 1/3] added readonly property for SELECT-only (virtual) columns --- .../ORM/Mapping/Builder/FieldBuilder.php | 14 +++++++++++++ .../ORM/Mapping/ClassMetadataInfo.php | 21 +++++++++++++++++++ lib/Doctrine/ORM/Mapping/Column.php | 5 +++++ .../ORM/Mapping/Driver/AnnotationDriver.php | 3 ++- lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 4 ++++ .../ORM/Mapping/Driver/YamlDriver.php | 4 ++++ .../Entity/BasicEntityPersister.php | 8 +++++++ .../ORM/Tools/Export/Driver/XmlExporter.php | 4 ++++ 8 files changed, 62 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Mapping/Builder/FieldBuilder.php b/lib/Doctrine/ORM/Mapping/Builder/FieldBuilder.php index 5a896678117..8c5d12ba5d9 100644 --- a/lib/Doctrine/ORM/Mapping/Builder/FieldBuilder.php +++ b/lib/Doctrine/ORM/Mapping/Builder/FieldBuilder.php @@ -153,6 +153,20 @@ public function scale($s) return $this; } + /** + * Sets readOnly. + * + * @param bool $flag + * + * @return FieldBuilder + */ + public function readOnly($flag = true) + { + $this->mapping['readOnly'] = (bool) $flag; + + return $this; + } + /** * Sets field as primary key. * diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 90db1f8a6fa..e0395eb4083 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -380,6 +380,9 @@ class ClassMetadataInfo implements ClassMetadata * - nullable (boolean, optional) * Whether the column is nullable. Defaults to FALSE. * + * - readOnly (boolean, optional) + * Whether the column is readOnly. Defaults to FALSE. + * * - columnDefinition (string, optional, schema-only) * The SQL fragment that is used when generating the DDL for the column. * @@ -1203,6 +1206,24 @@ public function isNullable($fieldName) return false; } + /** + * Checks if the field is readOnly. + * + * @param string $fieldName The field name. + * + * @return boolean TRUE if the field is readOnly, FALSE otherwise. + */ + public function isReadOnly($fieldName) + { + $mapping = $this->getFieldMapping($fieldName); + + if ($mapping !== false) { + return isset($mapping['readOnly']) && $mapping['readOnly'] == true; + } + + return false; + } + /** * Gets a column name for a field name. * If the column name for the field cannot be found, the given field name diff --git a/lib/Doctrine/ORM/Mapping/Column.php b/lib/Doctrine/ORM/Mapping/Column.php index 70337323f6f..cd2f17387e4 100644 --- a/lib/Doctrine/ORM/Mapping/Column.php +++ b/lib/Doctrine/ORM/Mapping/Column.php @@ -64,6 +64,11 @@ final class Column implements Annotation */ public $nullable = false; + /** + * @var boolean + */ + public $readOnly = false; + /** * @var array */ diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index 9dd64bbd33a..4ace289cd34 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -617,7 +617,8 @@ private function columnToArray($fieldName, Column $column) 'length' => $column->length, 'unique' => $column->unique, 'nullable' => $column->nullable, - 'precision' => $column->precision + 'precision' => $column->precision, + 'readOnly' => $column->readOnly ); if ($column->options) { diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index 00de4f2ea11..761eab6af0e 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -748,6 +748,10 @@ private function columnToArray(SimpleXMLElement $fieldMapping) $mapping['nullable'] = $this->evaluateBoolean($fieldMapping['nullable']); } + if (isset($fieldMapping['readOnly'])) { + $mapping['readOnly'] = $this->evaluateBoolean($fieldMapping['readOnly']); + } + if (isset($fieldMapping['version']) && $fieldMapping['version']) { $mapping['version'] = $this->evaluateBoolean($fieldMapping['version']); } diff --git a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php index fe406785f58..937fd4fe779 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php @@ -750,6 +750,10 @@ private function columnToArray($fieldName, $column) $mapping['nullable'] = $column['nullable']; } + if (isset($column['readOnly'])) { + $mapping['readOnly'] = $column['readOnly']; + } + if (isset($column['version']) && $column['version']) { $mapping['version'] = $column['version']; } diff --git a/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php index 3b2faf878d7..ebc81a53376 100644 --- a/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php @@ -619,6 +619,10 @@ protected function prepareUpdateData($entity) continue; } + if ($this->class->hasField($field) && $this->class->isReadOnly($field)) { + continue; + } + $newVal = $change[1]; if ( ! isset($this->class->associationMappings[$field])) { @@ -1430,6 +1434,10 @@ protected function getInsertColumnList() continue; } + if ($this->class->hasField($name) && $this->class->isReadOnly($name)) { + continue; + } + if (isset($this->class->associationMappings[$name])) { $assoc = $this->class->associationMappings[$name]; diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php index ba41d99862f..2396f8f9f8c 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php @@ -222,6 +222,10 @@ public function exportClassMetadata(ClassMetadataInfo $metadata) if (isset($field['nullable'])) { $fieldXml->addAttribute('nullable', $field['nullable'] ? 'true' : 'false'); } + + if (isset($field['readOnly'])) { + $fieldXml->addAttribute('readOnly', $field['readOnly'] ? 'true' : 'false'); + } } } From 639de30a4af035ea8eb9275444f72781646823a3 Mon Sep 17 00:00:00 2001 From: Hikariii Date: Mon, 21 Mar 2016 10:48:37 +0100 Subject: [PATCH 2/3] testing entity persister on readOnly columns --- .../Tests/Models/ReadOnlyColumn/Item.php | 32 ++++++++ ...BasicEntityPersisterReadOnlyColumnTest.php | 82 +++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 tests/Doctrine/Tests/Models/ReadOnlyColumn/Item.php create mode 100644 tests/Doctrine/Tests/ORM/Persisters/BasicEntityPersisterReadOnlyColumnTest.php diff --git a/tests/Doctrine/Tests/Models/ReadOnlyColumn/Item.php b/tests/Doctrine/Tests/Models/ReadOnlyColumn/Item.php new file mode 100644 index 00000000000..1fb22f7bd3f --- /dev/null +++ b/tests/Doctrine/Tests/Models/ReadOnlyColumn/Item.php @@ -0,0 +1,32 @@ +_em = $this->_getTestEntityManager(); + + $this->_persister = new BasicEntityPersister($this->_em, $this->_em->getClassMetadata(Item::class)); + } + + public function testGetSelectSQLUsesReadOnlyColumn() + { + $method = new \ReflectionMethod($this->_persister, 'getSelectSQL'); + $method->setAccessible(true); + + $sql = $method->invoke($this->_persister, new Criteria()); + + $this->assertGreaterThan(0, stripos($sql, '.generatedString')); + } + + public function testGetInsertSQLUsesReadOnlyColumn() + { + $method = new \ReflectionMethod($this->_persister, 'getInsertSQL'); + $method->setAccessible(true); + + $sql = $method->invoke($this->_persister); + + $this->assertEquals('INSERT INTO readonly_column (label, content) VALUES (?, ?)', $sql); + } + + public function testGetUpdateSQLUsesReadOnlyColumn() + { + $item = new Item(); + $this->_em->persist($item); + $this->_em->flush(); + + $item->label = 'foo'; + $item->generatedString = 'bar'; + + $this->_em->getUnitOfWork()->computeChangeSets(); + + $method = new \ReflectionMethod($this->_persister, 'update'); + $method->setAccessible(true); + $method->invoke($this->_persister, $item); + + $updates = $this->_em->getConnection()->getExecuteUpdates(); + $lastUpdate = end($updates); + + $this->assertNotEmpty($lastUpdate); + + $sql = $lastUpdate['query']; + $updateParams = $lastUpdate['params']; + + $this->assertContains('label', $sql, '', true); + $this->assertContains('foo', $updateParams, '', true, true, true); + + $this->assertNotContains('generatedString', $sql, '', true); + $this->assertNotContains('bar', $updateParams, '', true, true, true); + } +} From 576d0aa3b2a444240dd77135d44735ea170aeb9b Mon Sep 17 00:00:00 2001 From: Hikariii Date: Mon, 21 Mar 2016 11:09:02 +0100 Subject: [PATCH 3/3] tests fixed for older php versions --- .../ORM/Persisters/BasicEntityPersisterReadOnlyColumnTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Persisters/BasicEntityPersisterReadOnlyColumnTest.php b/tests/Doctrine/Tests/ORM/Persisters/BasicEntityPersisterReadOnlyColumnTest.php index c6d64b53f0e..2bdbfcdc09c 100644 --- a/tests/Doctrine/Tests/ORM/Persisters/BasicEntityPersisterReadOnlyColumnTest.php +++ b/tests/Doctrine/Tests/ORM/Persisters/BasicEntityPersisterReadOnlyColumnTest.php @@ -27,7 +27,7 @@ protected function setUp() $this->_em = $this->_getTestEntityManager(); - $this->_persister = new BasicEntityPersister($this->_em, $this->_em->getClassMetadata(Item::class)); + $this->_persister = new BasicEntityPersister($this->_em, $this->_em->getClassMetadata('Doctrine\Tests\Models\ReadOnlyColumn\Item')); } public function testGetSelectSQLUsesReadOnlyColumn()