From 507c8f45b47200b7160e69aa8e0939882b872609 Mon Sep 17 00:00:00 2001 From: Vitali Date: Sun, 20 Nov 2011 17:01:56 +0300 Subject: [PATCH 01/15] Register DoctrineAnnotations on call to OrmTestCase::createAnnotationDriver() --- tests/Doctrine/Tests/OrmTestCase.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Doctrine/Tests/OrmTestCase.php b/tests/Doctrine/Tests/OrmTestCase.php index fa9938d09..2a7b37dc9 100644 --- a/tests/Doctrine/Tests/OrmTestCase.php +++ b/tests/Doctrine/Tests/OrmTestCase.php @@ -52,6 +52,8 @@ abstract class OrmTestCase extends DoctrineTestCase $reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\'); } } + \Doctrine\Common\Annotations\AnnotationRegistry::registerFile( + __DIR__ . "/../../../lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php"); return new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader, (array)$paths); } From 10e74040af68794112e92aab83b8dde972a6f6d3 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 27 Nov 2011 12:59:17 +0100 Subject: [PATCH 02/15] PSR-0 compliance --- lib/Doctrine/ORM/Mapping/Annotation.php | 24 ++ .../ORM/Mapping/ChangeTrackingPolicy.php | 30 ++ lib/Doctrine/ORM/Mapping/Column.php | 46 ++ .../ORM/Mapping/DiscriminatorColumn.php | 36 ++ lib/Doctrine/ORM/Mapping/DiscriminatorMap.php | 30 ++ .../Mapping/Driver/DoctrineAnnotations.php | 395 ------------------ .../ORM/Mapping/ElementCollection.php | 31 ++ lib/Doctrine/ORM/Mapping/Entity.php | 32 ++ lib/Doctrine/ORM/Mapping/GeneratedValue.php | 30 ++ .../ORM/Mapping/HasLifecycleCallbacks.php | 28 ++ lib/Doctrine/ORM/Mapping/Id.php | 28 ++ lib/Doctrine/ORM/Mapping/Index.php | 32 ++ lib/Doctrine/ORM/Mapping/InheritanceType.php | 30 ++ lib/Doctrine/ORM/Mapping/JoinColumn.php | 42 ++ lib/Doctrine/ORM/Mapping/JoinColumns.php | 30 ++ lib/Doctrine/ORM/Mapping/JoinTable.php | 36 ++ lib/Doctrine/ORM/Mapping/ManyToMany.php | 40 ++ lib/Doctrine/ORM/Mapping/ManyToOne.php | 36 ++ lib/Doctrine/ORM/Mapping/MappedSuperclass.php | 30 ++ lib/Doctrine/ORM/Mapping/NamedQueries.php | 30 ++ lib/Doctrine/ORM/Mapping/NamedQuery.php | 32 ++ lib/Doctrine/ORM/Mapping/OneToMany.php | 40 ++ lib/Doctrine/ORM/Mapping/OneToOne.php | 40 ++ lib/Doctrine/ORM/Mapping/OrderBy.php | 30 ++ lib/Doctrine/ORM/Mapping/PostLoad.php | 28 ++ lib/Doctrine/ORM/Mapping/PostPersist.php | 28 ++ lib/Doctrine/ORM/Mapping/PostRemove.php | 28 ++ lib/Doctrine/ORM/Mapping/PostUpdate.php | 28 ++ lib/Doctrine/ORM/Mapping/PreFlush.php | 28 ++ lib/Doctrine/ORM/Mapping/PrePersist.php | 28 ++ lib/Doctrine/ORM/Mapping/PreRemove.php | 28 ++ lib/Doctrine/ORM/Mapping/PreUpdate.php | 28 ++ .../ORM/Mapping/SequenceGenerator.php | 34 ++ lib/Doctrine/ORM/Mapping/Table.php | 36 ++ lib/Doctrine/ORM/Mapping/UniqueConstraint.php | 32 ++ lib/Doctrine/ORM/Mapping/Version.php | 28 ++ 36 files changed, 1117 insertions(+), 395 deletions(-) create mode 100644 lib/Doctrine/ORM/Mapping/Annotation.php create mode 100644 lib/Doctrine/ORM/Mapping/ChangeTrackingPolicy.php create mode 100644 lib/Doctrine/ORM/Mapping/Column.php create mode 100644 lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php create mode 100644 lib/Doctrine/ORM/Mapping/DiscriminatorMap.php create mode 100644 lib/Doctrine/ORM/Mapping/ElementCollection.php create mode 100644 lib/Doctrine/ORM/Mapping/Entity.php create mode 100644 lib/Doctrine/ORM/Mapping/GeneratedValue.php create mode 100644 lib/Doctrine/ORM/Mapping/HasLifecycleCallbacks.php create mode 100644 lib/Doctrine/ORM/Mapping/Id.php create mode 100644 lib/Doctrine/ORM/Mapping/Index.php create mode 100644 lib/Doctrine/ORM/Mapping/InheritanceType.php create mode 100644 lib/Doctrine/ORM/Mapping/JoinColumn.php create mode 100644 lib/Doctrine/ORM/Mapping/JoinColumns.php create mode 100644 lib/Doctrine/ORM/Mapping/JoinTable.php create mode 100644 lib/Doctrine/ORM/Mapping/ManyToMany.php create mode 100644 lib/Doctrine/ORM/Mapping/ManyToOne.php create mode 100644 lib/Doctrine/ORM/Mapping/MappedSuperclass.php create mode 100644 lib/Doctrine/ORM/Mapping/NamedQueries.php create mode 100644 lib/Doctrine/ORM/Mapping/NamedQuery.php create mode 100644 lib/Doctrine/ORM/Mapping/OneToMany.php create mode 100644 lib/Doctrine/ORM/Mapping/OneToOne.php create mode 100644 lib/Doctrine/ORM/Mapping/OrderBy.php create mode 100644 lib/Doctrine/ORM/Mapping/PostLoad.php create mode 100644 lib/Doctrine/ORM/Mapping/PostPersist.php create mode 100644 lib/Doctrine/ORM/Mapping/PostRemove.php create mode 100644 lib/Doctrine/ORM/Mapping/PostUpdate.php create mode 100644 lib/Doctrine/ORM/Mapping/PreFlush.php create mode 100644 lib/Doctrine/ORM/Mapping/PrePersist.php create mode 100644 lib/Doctrine/ORM/Mapping/PreRemove.php create mode 100644 lib/Doctrine/ORM/Mapping/PreUpdate.php create mode 100644 lib/Doctrine/ORM/Mapping/SequenceGenerator.php create mode 100644 lib/Doctrine/ORM/Mapping/Table.php create mode 100644 lib/Doctrine/ORM/Mapping/UniqueConstraint.php create mode 100644 lib/Doctrine/ORM/Mapping/Version.php diff --git a/lib/Doctrine/ORM/Mapping/Annotation.php b/lib/Doctrine/ORM/Mapping/Annotation.php new file mode 100644 index 000000000..dd8f4efee --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/Annotation.php @@ -0,0 +1,24 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +interface Annotation +{ +} diff --git a/lib/Doctrine/ORM/Mapping/ChangeTrackingPolicy.php b/lib/Doctrine/ORM/Mapping/ChangeTrackingPolicy.php new file mode 100644 index 000000000..a9cc3725a --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/ChangeTrackingPolicy.php @@ -0,0 +1,30 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("CLASS") + */ +final class ChangeTrackingPolicy implements Annotation +{ + /** @var string */ + public $value; +} diff --git a/lib/Doctrine/ORM/Mapping/Column.php b/lib/Doctrine/ORM/Mapping/Column.php new file mode 100644 index 000000000..5f7dd7f64 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/Column.php @@ -0,0 +1,46 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("PROPERTY") + */ +final class Column implements Annotation +{ + /** @var string */ + public $name; + /** @var mixed */ + public $type = 'string'; + /** @var integer */ + public $length; + /** @var integer */ + public $precision = 0; // The precision for a decimal (exact numeric) column (Applies only for decimal column) + /** @var integer */ + public $scale = 0; // The scale for a decimal (exact numeric) column (Applies only for decimal column) + /** @var boolean */ + public $unique = false; + /** @var boolean */ + public $nullable = false; + /** @var array */ + public $options = array(); + /** @var string */ + public $columnDefinition; +} diff --git a/lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php b/lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php new file mode 100644 index 000000000..aec011538 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php @@ -0,0 +1,36 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("CLASS") + */ +final class DiscriminatorColumn implements Annotation +{ + /** @var string */ + public $name; + /** @var string */ + public $type; + /** @var integer */ + public $length; + /** @var mixed */ + public $fieldName; // field name used in non-object hydration (array/scalar) +} diff --git a/lib/Doctrine/ORM/Mapping/DiscriminatorMap.php b/lib/Doctrine/ORM/Mapping/DiscriminatorMap.php new file mode 100644 index 000000000..8505cf92f --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/DiscriminatorMap.php @@ -0,0 +1,30 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("CLASS") + */ +final class DiscriminatorMap implements Annotation +{ + /** @var array */ + public $value; +} diff --git a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php index 7f25ecbb1..e69de29bb 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php @@ -1,395 +0,0 @@ -. - */ - -namespace Doctrine\ORM\Mapping; - -interface Annotation {} - - -/* Annotations */ - -/** - * @Annotation - * @Target("CLASS") - */ -final class Entity implements Annotation { - /** @var string */ - public $repositoryClass; - /** @var boolean */ - public $readOnly = false; -} - -/** - * @Annotation - * @Target("CLASS") - */ -final class MappedSuperclass implements Annotation { - /** @var string */ - public $repositoryClass; -} - -/** - * @Annotation - * @Target("CLASS") - */ -final class InheritanceType implements Annotation { - /** @var string */ - public $value; -} - -/** - * @Annotation - * @Target("CLASS") - */ -final class DiscriminatorColumn implements Annotation { - /** @var string */ - public $name; - /** @var string */ - public $type; - /** @var integer */ - public $length; - /** @var mixed */ - public $fieldName; // field name used in non-object hydration (array/scalar) -} - -/** - * @Annotation - * @Target("CLASS") - */ -final class DiscriminatorMap implements Annotation { - /** @var array */ - public $value; -} - -/** - * @Annotation - * @Target("PROPERTY") - */ -final class Id implements Annotation {} - -/** - * @Annotation - * @Target("PROPERTY") - */ -final class GeneratedValue implements Annotation { - /** @var string */ - public $strategy = 'AUTO'; -} - -/** - * @Annotation - * @Target("PROPERTY") - */ -final class Version implements Annotation {} - -/** - * @Annotation - * @Target({"PROPERTY","ANNOTATION"}) - */ -final class JoinColumn implements Annotation { - /** @var string */ - public $name; - /** @var string */ - public $referencedColumnName = 'id'; - /** @var boolean */ - public $unique = false; - /** @var boolean */ - public $nullable = true; - /** @var mixed */ - public $onDelete; - /** @var string */ - public $columnDefinition; - /** @var string */ - public $fieldName; // field name used in non-object hydration (array/scalar) -} - -/** - * @Annotation - * @Target("PROPERTY") - */ -final class JoinColumns implements Annotation { - /** @var array */ - public $value; -} - -/** - * @Annotation - * @Target("PROPERTY") - */ -final class Column implements Annotation { - /** @var string */ - public $name; - /** @var mixed */ - public $type = 'string'; - /** @var integer */ - public $length; - /** @var integer */ - public $precision = 0; // The precision for a decimal (exact numeric) column (Applies only for decimal column) - /** @var integer */ - public $scale = 0; // The scale for a decimal (exact numeric) column (Applies only for decimal column) - /** @var boolean */ - public $unique = false; - /** @var boolean */ - public $nullable = false; - /** @var array */ - public $options = array(); - /** @var string */ - public $columnDefinition; -} - -/** - * @Annotation - * @Target("PROPERTY") - */ -final class OneToOne implements Annotation { - /** @var string */ - public $targetEntity; - /** @var string */ - public $mappedBy; - /** @var string */ - public $inversedBy; - /** @var array */ - public $cascade; - /** @var string */ - public $fetch = 'LAZY'; - /** @var boolean */ - public $orphanRemoval = false; -} - -/** - * @Annotation - * @Target("PROPERTY") - */ -final class OneToMany implements Annotation { - /** @var string */ - public $mappedBy; - /** @var string */ - public $targetEntity; - /** @var array */ - public $cascade; - /** @var string */ - public $fetch = 'LAZY'; - /** @var boolean */ - public $orphanRemoval = false; - /** @var string */ - public $indexBy; -} - -/** - * @Annotation - * @Target("PROPERTY") - */ -final class ManyToOne implements Annotation { - /** @var string */ - public $targetEntity; - /** @var array */ - public $cascade; - /** @var string */ - public $fetch = 'LAZY'; - /** @var string */ - public $inversedBy; -} - -/** - * @Annotation - * @Target("PROPERTY") - */ -final class ManyToMany implements Annotation { - /** @var string */ - public $targetEntity; - /** @var string */ - public $mappedBy; - /** @var string */ - public $inversedBy; - /** @var array */ - public $cascade; - /** @var string */ - public $fetch = 'LAZY'; - /** @var string */ - public $indexBy; -} - -/** - * @Annotation - * @Target("ALL") - * @todo check available targets - */ -final class ElementCollection implements Annotation { - /** @var string */ - public $tableName; -} - -/** - * @Annotation - * @Target("CLASS") - */ -final class Table implements Annotation { - /** @var string */ - public $name; - /** @var string */ - public $schema; - /** @var array */ - public $indexes; - /** @var array */ - public $uniqueConstraints; -} - -/** - * @Annotation - * @Target("ANNOTATION") - */ -final class UniqueConstraint implements Annotation { - /** @var string */ - public $name; - /** @var array */ - public $columns; -} - -/** - * @Annotation - * @Target("ANNOTATION") - */ -final class Index implements Annotation { - /** @var string */ - public $name; - /** @var array */ - public $columns; -} - -/** - * @Annotation - * @Target("PROPERTY") - */ -final class JoinTable implements Annotation { - /** @var string */ - public $name; - /** @var string */ - public $schema; - /** @var array */ - public $joinColumns = array(); - /** @var array */ - public $inverseJoinColumns = array(); -} - -/** - * @Annotation - * @Target("PROPERTY") - */ -final class SequenceGenerator implements Annotation { - /** @var string */ - public $sequenceName; - /** @var integer */ - public $allocationSize = 1; - /** @var integer */ - public $initialValue = 1; -} - -/** - * @Annotation - * @Target("CLASS") - */ -final class ChangeTrackingPolicy implements Annotation { - /** @var string */ - public $value; -} - -/** - * @Annotation - * @Target("PROPERTY") - */ -final class OrderBy implements Annotation { - /** @var array */ - public $value; -} - -/** - * @Annotation - * @Target("CLASS") - */ -final class NamedQueries implements Annotation { - /** @var array */ - public $value; -} - -/** - * @Annotation - * @Target("ANNOTATION") - */ -final class NamedQuery implements Annotation { - /** @var string */ - public $name; - /** @var string */ - public $query; -} - -/* Annotations for lifecycle callbacks */ - -/** - * @Annotation - * @Target("CLASS") - */ -final class HasLifecycleCallbacks implements Annotation {} - -/** - * @Annotation - * @Target("METHOD") - */ -final class PrePersist implements Annotation {} - -/** - * @Annotation - * @Target("METHOD") - */ -final class PostPersist implements Annotation {} - -/** - * @Annotation - * @Target("METHOD") - */ -final class PreUpdate implements Annotation {} - -/** - * @Annotation - * @Target("METHOD") - */ -final class PostUpdate implements Annotation {} - -/** - * @Annotation - * @Target("METHOD") - */ -final class PreRemove implements Annotation {} - -/** - * @Annotation - * @Target("METHOD") - */ -final class PostRemove implements Annotation {} - -/** - * @Annotation - * @Target("METHOD") - */ -final class PostLoad implements Annotation {} - -/** - * @Annotation - * @Target("METHOD") - */ -final class PreFlush implements Annotation {} diff --git a/lib/Doctrine/ORM/Mapping/ElementCollection.php b/lib/Doctrine/ORM/Mapping/ElementCollection.php new file mode 100644 index 000000000..f12717419 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/ElementCollection.php @@ -0,0 +1,31 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("ALL") + * @todo check available targets + */ +final class ElementCollection implements Annotation +{ + /** @var string */ + public $tableName; +} diff --git a/lib/Doctrine/ORM/Mapping/Entity.php b/lib/Doctrine/ORM/Mapping/Entity.php new file mode 100644 index 000000000..075a5ecec --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/Entity.php @@ -0,0 +1,32 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("CLASS") + */ +final class Entity implements Annotation +{ + /** @var string */ + public $repositoryClass; + /** @var boolean */ + public $readOnly = false; +} diff --git a/lib/Doctrine/ORM/Mapping/GeneratedValue.php b/lib/Doctrine/ORM/Mapping/GeneratedValue.php new file mode 100644 index 000000000..8558d21c3 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/GeneratedValue.php @@ -0,0 +1,30 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("PROPERTY") + */ +final class GeneratedValue implements Annotation +{ + /** @var string */ + public $strategy = 'AUTO'; +} diff --git a/lib/Doctrine/ORM/Mapping/HasLifecycleCallbacks.php b/lib/Doctrine/ORM/Mapping/HasLifecycleCallbacks.php new file mode 100644 index 000000000..a65fbb522 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/HasLifecycleCallbacks.php @@ -0,0 +1,28 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("CLASS") + */ +final class HasLifecycleCallbacks implements Annotation +{ +} diff --git a/lib/Doctrine/ORM/Mapping/Id.php b/lib/Doctrine/ORM/Mapping/Id.php new file mode 100644 index 000000000..a670e3ddb --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/Id.php @@ -0,0 +1,28 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("PROPERTY") + */ +final class Id implements Annotation +{ +} diff --git a/lib/Doctrine/ORM/Mapping/Index.php b/lib/Doctrine/ORM/Mapping/Index.php new file mode 100644 index 000000000..e0a2db36c --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/Index.php @@ -0,0 +1,32 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("ANNOTATION") + */ +final class Index implements Annotation +{ + /** @var string */ + public $name; + /** @var array */ + public $columns; +} diff --git a/lib/Doctrine/ORM/Mapping/InheritanceType.php b/lib/Doctrine/ORM/Mapping/InheritanceType.php new file mode 100644 index 000000000..009f3abf3 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/InheritanceType.php @@ -0,0 +1,30 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("CLASS") + */ +final class InheritanceType implements Annotation +{ + /** @var string */ + public $value; +} diff --git a/lib/Doctrine/ORM/Mapping/JoinColumn.php b/lib/Doctrine/ORM/Mapping/JoinColumn.php new file mode 100644 index 000000000..bf5c51d07 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/JoinColumn.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target({"PROPERTY","ANNOTATION"}) + */ +final class JoinColumn implements Annotation +{ + /** @var string */ + public $name; + /** @var string */ + public $referencedColumnName = 'id'; + /** @var boolean */ + public $unique = false; + /** @var boolean */ + public $nullable = true; + /** @var mixed */ + public $onDelete; + /** @var string */ + public $columnDefinition; + /** @var string */ + public $fieldName; // field name used in non-object hydration (array/scalar) +} diff --git a/lib/Doctrine/ORM/Mapping/JoinColumns.php b/lib/Doctrine/ORM/Mapping/JoinColumns.php new file mode 100644 index 000000000..7cf663bc7 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/JoinColumns.php @@ -0,0 +1,30 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("PROPERTY") + */ +final class JoinColumns implements Annotation +{ + /** @var array */ + public $value; +} diff --git a/lib/Doctrine/ORM/Mapping/JoinTable.php b/lib/Doctrine/ORM/Mapping/JoinTable.php new file mode 100644 index 000000000..75f8e271b --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/JoinTable.php @@ -0,0 +1,36 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("PROPERTY") + */ +final class JoinTable implements Annotation +{ + /** @var string */ + public $name; + /** @var string */ + public $schema; + /** @var array */ + public $joinColumns = array(); + /** @var array */ + public $inverseJoinColumns = array(); +} diff --git a/lib/Doctrine/ORM/Mapping/ManyToMany.php b/lib/Doctrine/ORM/Mapping/ManyToMany.php new file mode 100644 index 000000000..1e2ae06c0 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/ManyToMany.php @@ -0,0 +1,40 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("PROPERTY") + */ +final class ManyToMany implements Annotation +{ + /** @var string */ + public $targetEntity; + /** @var string */ + public $mappedBy; + /** @var string */ + public $inversedBy; + /** @var array */ + public $cascade; + /** @var string */ + public $fetch = 'LAZY'; + /** @var string */ + public $indexBy; +} diff --git a/lib/Doctrine/ORM/Mapping/ManyToOne.php b/lib/Doctrine/ORM/Mapping/ManyToOne.php new file mode 100644 index 000000000..1bc83769c --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/ManyToOne.php @@ -0,0 +1,36 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("PROPERTY") + */ +final class ManyToOne implements Annotation +{ + /** @var string */ + public $targetEntity; + /** @var array */ + public $cascade; + /** @var string */ + public $fetch = 'LAZY'; + /** @var string */ + public $inversedBy; +} diff --git a/lib/Doctrine/ORM/Mapping/MappedSuperclass.php b/lib/Doctrine/ORM/Mapping/MappedSuperclass.php new file mode 100644 index 000000000..639d21642 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/MappedSuperclass.php @@ -0,0 +1,30 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("CLASS") + */ +final class MappedSuperclass implements Annotation +{ + /** @var string */ + public $repositoryClass; +} diff --git a/lib/Doctrine/ORM/Mapping/NamedQueries.php b/lib/Doctrine/ORM/Mapping/NamedQueries.php new file mode 100644 index 000000000..04e0ebfaa --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/NamedQueries.php @@ -0,0 +1,30 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("CLASS") + */ +final class NamedQueries implements Annotation +{ + /** @var array */ + public $value; +} diff --git a/lib/Doctrine/ORM/Mapping/NamedQuery.php b/lib/Doctrine/ORM/Mapping/NamedQuery.php new file mode 100644 index 000000000..656a4eabf --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/NamedQuery.php @@ -0,0 +1,32 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("ANNOTATION") + */ +final class NamedQuery implements Annotation +{ + /** @var string */ + public $name; + /** @var string */ + public $query; +} diff --git a/lib/Doctrine/ORM/Mapping/OneToMany.php b/lib/Doctrine/ORM/Mapping/OneToMany.php new file mode 100644 index 000000000..b4d8ad3f8 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/OneToMany.php @@ -0,0 +1,40 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("PROPERTY") + */ +final class OneToMany implements Annotation +{ + /** @var string */ + public $mappedBy; + /** @var string */ + public $targetEntity; + /** @var array */ + public $cascade; + /** @var string */ + public $fetch = 'LAZY'; + /** @var boolean */ + public $orphanRemoval = false; + /** @var string */ + public $indexBy; +} diff --git a/lib/Doctrine/ORM/Mapping/OneToOne.php b/lib/Doctrine/ORM/Mapping/OneToOne.php new file mode 100644 index 000000000..4baa12196 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/OneToOne.php @@ -0,0 +1,40 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("PROPERTY") + */ +final class OneToOne implements Annotation +{ + /** @var string */ + public $targetEntity; + /** @var string */ + public $mappedBy; + /** @var string */ + public $inversedBy; + /** @var array */ + public $cascade; + /** @var string */ + public $fetch = 'LAZY'; + /** @var boolean */ + public $orphanRemoval = false; +} diff --git a/lib/Doctrine/ORM/Mapping/OrderBy.php b/lib/Doctrine/ORM/Mapping/OrderBy.php new file mode 100644 index 000000000..c28f11042 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/OrderBy.php @@ -0,0 +1,30 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("PROPERTY") + */ +final class OrderBy implements Annotation +{ + /** @var array */ + public $value; +} diff --git a/lib/Doctrine/ORM/Mapping/PostLoad.php b/lib/Doctrine/ORM/Mapping/PostLoad.php new file mode 100644 index 000000000..169bb4904 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/PostLoad.php @@ -0,0 +1,28 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("METHOD") + */ +final class PostLoad implements Annotation +{ +} diff --git a/lib/Doctrine/ORM/Mapping/PostPersist.php b/lib/Doctrine/ORM/Mapping/PostPersist.php new file mode 100644 index 000000000..5b5baed12 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/PostPersist.php @@ -0,0 +1,28 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("METHOD") + */ +final class PostPersist implements Annotation +{ +} diff --git a/lib/Doctrine/ORM/Mapping/PostRemove.php b/lib/Doctrine/ORM/Mapping/PostRemove.php new file mode 100644 index 000000000..4798e26e8 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/PostRemove.php @@ -0,0 +1,28 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("METHOD") + */ +final class PostRemove implements Annotation +{ +} diff --git a/lib/Doctrine/ORM/Mapping/PostUpdate.php b/lib/Doctrine/ORM/Mapping/PostUpdate.php new file mode 100644 index 000000000..f7e753973 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/PostUpdate.php @@ -0,0 +1,28 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("METHOD") + */ +final class PostUpdate implements Annotation +{ +} diff --git a/lib/Doctrine/ORM/Mapping/PreFlush.php b/lib/Doctrine/ORM/Mapping/PreFlush.php new file mode 100644 index 000000000..f5eb08636 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/PreFlush.php @@ -0,0 +1,28 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("METHOD") + */ +final class PreFlush implements Annotation +{ +} diff --git a/lib/Doctrine/ORM/Mapping/PrePersist.php b/lib/Doctrine/ORM/Mapping/PrePersist.php new file mode 100644 index 000000000..c3827006e --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/PrePersist.php @@ -0,0 +1,28 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("METHOD") + */ +final class PrePersist implements Annotation +{ +} diff --git a/lib/Doctrine/ORM/Mapping/PreRemove.php b/lib/Doctrine/ORM/Mapping/PreRemove.php new file mode 100644 index 000000000..adcb837d7 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/PreRemove.php @@ -0,0 +1,28 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("METHOD") + */ +final class PreRemove implements Annotation +{ +} diff --git a/lib/Doctrine/ORM/Mapping/PreUpdate.php b/lib/Doctrine/ORM/Mapping/PreUpdate.php new file mode 100644 index 000000000..2afd38120 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/PreUpdate.php @@ -0,0 +1,28 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("METHOD") + */ +final class PreUpdate implements Annotation +{ +} diff --git a/lib/Doctrine/ORM/Mapping/SequenceGenerator.php b/lib/Doctrine/ORM/Mapping/SequenceGenerator.php new file mode 100644 index 000000000..be3d700f9 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/SequenceGenerator.php @@ -0,0 +1,34 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("PROPERTY") + */ +final class SequenceGenerator implements Annotation +{ + /** @var string */ + public $sequenceName; + /** @var integer */ + public $allocationSize = 1; + /** @var integer */ + public $initialValue = 1; +} diff --git a/lib/Doctrine/ORM/Mapping/Table.php b/lib/Doctrine/ORM/Mapping/Table.php new file mode 100644 index 000000000..a68e3673b --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/Table.php @@ -0,0 +1,36 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("CLASS") + */ +final class Table implements Annotation +{ + /** @var string */ + public $name; + /** @var string */ + public $schema; + /** @var array */ + public $indexes; + /** @var array */ + public $uniqueConstraints; +} diff --git a/lib/Doctrine/ORM/Mapping/UniqueConstraint.php b/lib/Doctrine/ORM/Mapping/UniqueConstraint.php new file mode 100644 index 000000000..db9f8fd41 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/UniqueConstraint.php @@ -0,0 +1,32 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("ANNOTATION") + */ +final class UniqueConstraint implements Annotation +{ + /** @var string */ + public $name; + /** @var array */ + public $columns; +} diff --git a/lib/Doctrine/ORM/Mapping/Version.php b/lib/Doctrine/ORM/Mapping/Version.php new file mode 100644 index 000000000..313e7519c --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/Version.php @@ -0,0 +1,28 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("PROPERTY") + */ +final class Version implements Annotation +{ +} From b8ac2fb416430010fc32829cc954427434b7ff50 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 27 Nov 2011 18:16:18 +0100 Subject: [PATCH 03/15] Add requires to new files for BC --- .../Mapping/Driver/DoctrineAnnotations.php | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php index e69de29bb..290fc6529 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php @@ -0,0 +1,54 @@ +. + */ + +require_once __DIR__.'/../Annotation.php'; +require_once __DIR__.'/../Entity.php'; +require_once __DIR__.'/../MappedSuperclass.php'; +require_once __DIR__.'/../InheritanceType.php'; +require_once __DIR__.'/../DiscriminatorColumn.php'; +require_once __DIR__.'/../DiscriminatorMap.php'; +require_once __DIR__.'/../Id.php'; +require_once __DIR__.'/../GeneratedValue.php'; +require_once __DIR__.'/../Version.php'; +require_once __DIR__.'/../JoinColumn.php'; +require_once __DIR__.'/../JoinColumns.php'; +require_once __DIR__.'/../Column.php'; +require_once __DIR__.'/../OneToOne.php'; +require_once __DIR__.'/../OneToMany.php'; +require_once __DIR__.'/../ManyToOne.php'; +require_once __DIR__.'/../ManyToMany.php'; +require_once __DIR__.'/../ElementCollection.php'; +require_once __DIR__.'/../Table.php'; +require_once __DIR__.'/../UniqueConstraint.php'; +require_once __DIR__.'/../Index.php'; +require_once __DIR__.'/../JoinTable.php'; +require_once __DIR__.'/../SequenceGenerator.php'; +require_once __DIR__.'/../ChangeTrackingPolicy.php'; +require_once __DIR__.'/../OrderBy.php'; +require_once __DIR__.'/../NamedQueries.php'; +require_once __DIR__.'/../NamedQuery.php'; +require_once __DIR__.'/../HasLifecycleCallbacks.php'; +require_once __DIR__.'/../PrePersist.php'; +require_once __DIR__.'/../PostPersist.php'; +require_once __DIR__.'/../PreUpdate.php'; +require_once __DIR__.'/../PostUpdate.php'; +require_once __DIR__.'/../PreRemove.php'; +require_once __DIR__.'/../PostRemove.php'; +require_once __DIR__.'/../PostLoad.php'; +require_once __DIR__.'/../PreFlush.php'; From f2f32ca70f99c279174e2c8719290335e9251fc4 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 23 Nov 2011 20:35:00 +0100 Subject: [PATCH 04/15] DDC-1509 - Fix regression in doMerge() introduced with the DDC-1383 bugfix --- lib/Doctrine/ORM/UnitOfWork.php | 13 +- .../ORM/Functional/Ticket/DDC1509Test.php | 146 ++++++++++++++++++ 2 files changed, 151 insertions(+), 8 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1509Test.php diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 0a04d05ae..99491d965 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1700,21 +1700,18 @@ class UnitOfWork implements PropertyChangedListener // do not merge fields marked lazy that have not been fetched. continue; } else if ( ! $assoc2['isCascadeMerge']) { - if ($this->getEntityState($other, self::STATE_DETACHED) == self::STATE_MANAGED) { - $prop->setValue($managedCopy, $other); - } else { - + if ($this->getEntityState($other, self::STATE_DETACHED) !== self::STATE_MANAGED) { $targetClass = $this->em->getClassMetadata($assoc2['targetEntity']); $relatedId = $targetClass->getIdentifierValues($other); if ($targetClass->subClasses) { - $entity = $this->em->find($targetClass->name, $relatedId); + $other = $this->em->find($targetClass->name, $relatedId); } else { - $proxy = $this->em->getProxyFactory()->getProxy($assoc2['targetEntity'], $relatedId); - $prop->setValue($managedCopy, $proxy); - $this->registerManaged($proxy, $relatedId, array()); + $other = $this->em->getProxyFactory()->getProxy($assoc2['targetEntity'], $relatedId); + $this->registerManaged($other, $relatedId, array()); } } + $prop->setValue($managedCopy, $other); } } else { $mergeCol = $prop->getValue($entity); diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1509Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1509Test.php new file mode 100644 index 000000000..7f7e7d575 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1509Test.php @@ -0,0 +1,146 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1509AbstractFile'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1509File'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1509Picture'), + )); + } catch (\Exception $ignored) { + + } + } + + public function testFailingCase() + { + $file = new DDC1509File; + $thumbnail = new DDC1509File; + + $picture = new DDC1509Picture; + $picture->setFile($file); + $picture->setThumbnail($thumbnail); + + + /* @var $em \Doctrine\ORM\EntityManager */ + $em = $this->_em; + $em->persist($picture); + $em->flush(); + $em->clear(); + + $id = $picture->getPictureId(); + + $pic = $em->merge($picture); + /* @var $pic DDC1509Picture */ + + $this->assertNotNull($pic->getThumbnail()); + $this->assertNotNull($pic->getFile()); + } + +} + +/** + * @Entity + */ +class DDC1509Picture +{ + + /** + * @Column(type="integer") + * @Id + * @GeneratedValue(strategy="AUTO") + */ + private $id; + + /** + * @ManyToOne(targetEntity="DDC1509AbstractFile", cascade={"persist", "remove"}) + */ + private $thumbnail; + + /** + * @ManyToOne(targetEntity="DDC1509AbstractFile", cascade={"persist", "remove"}) + */ + private $file; + + /** + * Get pictureId + */ + public function getPictureId() + { + return $this->id; + } + + /** + * Set file + */ + public function setFile($value = null) + { + $this->file = $value; + } + + /** + * Get file + */ + public function getFile() + { + return $this->file; + } + + public function getThumbnail() + { + return $this->thumbnail; + } + + public function setThumbnail($thumbnail) + { + $this->thumbnail = $thumbnail; + } + +} + +/** + * @Entity + * @InheritanceType("SINGLE_TABLE") + * @DiscriminatorColumn(name="discr", type="string") + * @DiscriminatorMap({"file" = "DDC1509File"}) + */ +class DDC1509AbstractFile +{ + + /** + * @Column(type="integer") + * @Id + * @GeneratedValue(strategy="AUTO") + */ + public $id; + + /** + * Get fileId + */ + public function getFileId() + { + return $this->id; + } + +} + +/** + * @Entity + */ +class DDC1509File extends DDC1509AbstractFile +{ + +} From 0c12d3ed5a60fda8d879d1d40b029ed83d0d24f5 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 28 Nov 2011 11:16:23 +0100 Subject: [PATCH 05/15] DDC-1512 - Make ClassMetadataFactory::isTransient() entity namespace aware. --- .../ORM/Mapping/ClassMetadataFactory.php | 6 +++ .../ORM/Mapping/ClassMetadataFactoryTest.php | 46 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 19d319dbe..9bc3ca054 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -521,6 +521,12 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface $this->initialize(); } + // Check for namespace alias + if (strpos($class, ':') !== false) { + list($namespaceAlias, $simpleClassName) = explode(':', $class); + $class = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName; + } + return $this->driver->isTransient($class); } } diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php index dbb82f054..00b8f5f16 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php @@ -9,6 +9,7 @@ use Doctrine\Tests\Mocks\ConnectionMock; use Doctrine\Tests\Mocks\DriverMock; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\Common\EventManager; +use Doctrine\ORM\Mapping\ClassMetadataFactory; require_once __DIR__ . '/../../TestInit.php'; @@ -81,6 +82,51 @@ class ClassMetadataFactoryTest extends \Doctrine\Tests\OrmTestCase $this->assertFalse($h2); $this->assertTrue($h1); } + + /** + * @group DDC-1512 + */ + public function testIsTransient() + { + $cmf = new ClassMetadataFactory(); + $driver = $this->getMock('Doctrine\ORM\Mapping\Driver\Driver'); + $driver->expects($this->at(0)) + ->method('isTransient') + ->with($this->equalTo('Doctrine\Tests\Models\CMS\CmsUser')) + ->will($this->returnValue(true)); + $driver->expects($this->at(1)) + ->method('isTransient') + ->with($this->equalTo('Doctrine\Tests\Models\CMS\CmsArticle')) + ->will($this->returnValue(false)); + + $em = $this->_createEntityManager($driver); + + $this->assertTrue($em->getMetadataFactory()->isTransient('Doctrine\Tests\Models\CMS\CmsUser')); + $this->assertFalse($em->getMetadataFactory()->isTransient('Doctrine\Tests\Models\CMS\CmsArticle')); + } + + /** + * @group DDC-1512 + */ + public function testIsTransientEntityNamespace() + { + $cmf = new ClassMetadataFactory(); + $driver = $this->getMock('Doctrine\ORM\Mapping\Driver\Driver'); + $driver->expects($this->at(0)) + ->method('isTransient') + ->with($this->equalTo('Doctrine\Tests\Models\CMS\CmsUser')) + ->will($this->returnValue(true)); + $driver->expects($this->at(1)) + ->method('isTransient') + ->with($this->equalTo('Doctrine\Tests\Models\CMS\CmsArticle')) + ->will($this->returnValue(false)); + + $em = $this->_createEntityManager($driver); + $em->getConfiguration()->addEntityNamespace('CMS', 'Doctrine\Tests\Models\CMS'); + + $this->assertTrue($em->getMetadataFactory()->isTransient('CMS:CmsUser')); + $this->assertFalse($em->getMetadataFactory()->isTransient('CMS:CmsArticle')); + } protected function _createEntityManager($metadataDriver) { From 24f6b74427b04cb3bf038b3bee4f5062d391529b Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Tue, 29 Nov 2011 10:36:32 -0500 Subject: [PATCH 06/15] Refactored UnitOfWork::createEntity, improving its performance. --- .../ORM/Internal/Hydration/ObjectHydrator.php | 18 +- .../Hydration/SimpleObjectHydrator.php | 26 +- lib/Doctrine/ORM/UnitOfWork.php | 467 +++++++++++------- 3 files changed, 313 insertions(+), 198 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 896de0caa..ced83b126 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -23,6 +23,8 @@ use PDO, Doctrine\ORM\Mapping\ClassMetadata, Doctrine\ORM\PersistentCollection, Doctrine\ORM\Query, + Doctrine\ORM\Event\LifecycleEventArgs, + Doctrine\ORM\Events, Doctrine\Common\Collections\ArrayCollection, Doctrine\Common\Collections\Collection; @@ -235,7 +237,21 @@ class ObjectHydrator extends AbstractHydrator } $this->_hints['fetchAlias'] = $dqlAlias; - return $this->_uow->createEntity($className, $data, $this->_hints); + + $entity = $this->_uow->createEntity($className, $data, $this->_hints); + + //TODO: These should be invoked later, after hydration, because associations may not yet be loaded here. + if (isset($this->_ce[$className]->lifecycleCallbacks[Events::postLoad])) { + $this->_ce[$className]->invokeLifecycleCallbacks(Events::postLoad, $entity); + } + + $evm = $this->_em->getEventManager(); + + if ($evm->hasListeners(Events::postLoad)) { + $evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em)); + } + + return $entity; } private function _getEntityFromIdentityMap($className, array $data) diff --git a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php index c045c1edf..a4770f28e 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php @@ -19,10 +19,12 @@ namespace Doctrine\ORM\Internal\Hydration; -use \PDO; -use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\DBAL\Types\Type; -use Doctrine\ORM\Query; +use \PDO, + Doctrine\DBAL\Types\Type, + Doctrine\ORM\Mapping\ClassMetadata, + Doctrine\ORM\Event\LifecycleEventArgs, + Doctrine\ORM\Events, + Doctrine\ORM\Query; class SimpleObjectHydrator extends AbstractHydrator { @@ -125,7 +127,21 @@ class SimpleObjectHydrator extends AbstractHydrator $this->registerManaged($this->class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data); } - $result[] = $this->_em->getUnitOfWork()->createEntity($entityName, $data, $this->_hints); + $uow = $this->_em->getUnitOfWork(); + $entity = $uow->createEntity($entityName, $data, $this->_hints); + + //TODO: These should be invoked later, after hydration, because associations may not yet be loaded here. + if (isset($this->class->lifecycleCallbacks[Events::postLoad])) { + $this->class->invokeLifecycleCallbacks(Events::postLoad, $entity); + } + + $evm = $this->_em->getEventManager(); + + if ($evm->hasListeners(Events::postLoad)) { + $evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em)); + } + + $result[] = $entity; } /** diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 99491d965..64f0608e7 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1790,6 +1790,7 @@ class UnitOfWork implements PropertyChangedListener public function detach($entity) { $visited = array(); + $this->doDetach($entity, $visited); } @@ -1803,6 +1804,7 @@ class UnitOfWork implements PropertyChangedListener private function doDetach($entity, array &$visited, $noCascade = false) { $oid = spl_object_hash($entity); + if (isset($visited[$oid])) { return; // Prevent infinite recursion } @@ -1814,16 +1816,22 @@ class UnitOfWork implements PropertyChangedListener if ($this->isInIdentityMap($entity)) { $this->removeFromIdentityMap($entity); } - unset($this->entityInsertions[$oid], $this->entityUpdates[$oid], - $this->entityDeletions[$oid], $this->entityIdentifiers[$oid], - $this->entityStates[$oid], $this->originalEntityData[$oid]); + + unset( + $this->entityInsertions[$oid], + $this->entityUpdates[$oid], + $this->entityDeletions[$oid], + $this->entityIdentifiers[$oid], + $this->entityStates[$oid], + $this->originalEntityData[$oid] + ); break; case self::STATE_NEW: case self::STATE_DETACHED: return; } - if (!$noCascade) { + if ( ! $noCascade) { $this->cascadeDetach($entity, $visited); } } @@ -1838,6 +1846,7 @@ class UnitOfWork implements PropertyChangedListener public function refresh($entity) { $visited = array(); + $this->doRefresh($entity, $visited); } @@ -1851,6 +1860,7 @@ class UnitOfWork implements PropertyChangedListener private function doRefresh($entity, array &$visited) { $oid = spl_object_hash($entity); + if (isset($visited[$oid])) { return; // Prevent infinite recursion } @@ -1858,14 +1868,15 @@ class UnitOfWork implements PropertyChangedListener $visited[$oid] = $entity; // mark visited $class = $this->em->getClassMetadata(get_class($entity)); - if ($this->getEntityState($entity) == self::STATE_MANAGED) { - $this->getEntityPersister($class->name)->refresh( - array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]), - $entity - ); - } else { + + if ($this->getEntityState($entity) !== self::STATE_MANAGED) { throw new InvalidArgumentException("Entity is not MANAGED."); } + + $this->getEntityPersister($class->name)->refresh( + array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]), + $entity + ); $this->cascadeRefresh($entity, $visited); } @@ -1879,21 +1890,33 @@ class UnitOfWork implements PropertyChangedListener private function cascadeRefresh($entity, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); + foreach ($class->associationMappings as $assoc) { if ( ! $assoc['isCascadeRefresh']) { continue; } + $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); - if ($relatedEntities instanceof Collection) { - if ($relatedEntities instanceof PersistentCollection) { + + switch (true) { + case ($relatedEntities instanceof PersistentCollection): // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); - } - foreach ($relatedEntities as $relatedEntity) { - $this->doRefresh($relatedEntity, $visited); - } - } else if ($relatedEntities !== null) { - $this->doRefresh($relatedEntities, $visited); + // break; is commented intentionally! + + case ($relatedEntities instanceof Collection): + case (is_array($relatedEntities)): + foreach ($relatedEntities as $relatedEntity) { + $this->doRefresh($relatedEntity, $visited); + } + break; + + case ($relatedEntities !== null): + $this->doRefresh($relatedEntities, $visited); + break; + + default: + // Do nothing } } } @@ -1907,21 +1930,33 @@ class UnitOfWork implements PropertyChangedListener private function cascadeDetach($entity, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); + foreach ($class->associationMappings as $assoc) { if ( ! $assoc['isCascadeDetach']) { continue; } + $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); - if ($relatedEntities instanceof Collection) { - if ($relatedEntities instanceof PersistentCollection) { + + switch (true) { + case ($relatedEntities instanceof PersistentCollection): // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); - } - foreach ($relatedEntities as $relatedEntity) { - $this->doDetach($relatedEntity, $visited); - } - } else if ($relatedEntities !== null) { - $this->doDetach($relatedEntities, $visited); + // break; is commented intentionally! + + case ($relatedEntities instanceof Collection): + case (is_array($relatedEntities)): + foreach ($relatedEntities as $relatedEntity) { + $this->doDetach($relatedEntity, $visited); + } + break; + + case ($relatedEntities !== null): + $this->doDetach($relatedEntities, $visited); + break; + + default: + // Do nothing } } } @@ -1969,22 +2004,33 @@ class UnitOfWork implements PropertyChangedListener private function cascadePersist($entity, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); + foreach ($class->associationMappings as $assoc) { if ( ! $assoc['isCascadePersist']) { continue; } $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); - if (($relatedEntities instanceof Collection || is_array($relatedEntities))) { - if ($relatedEntities instanceof PersistentCollection) { + + switch (true) { + case ($relatedEntities instanceof PersistentCollection): // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); - } - foreach ($relatedEntities as $relatedEntity) { - $this->doPersist($relatedEntity, $visited); - } - } else if ($relatedEntities !== null) { - $this->doPersist($relatedEntities, $visited); + // break; is commented intentionally! + + case ($relatedEntities instanceof Collection): + case (is_array($relatedEntities)): + foreach ($relatedEntities as $relatedEntity) { + $this->doPersist($relatedEntity, $visited); + } + break; + + case ($relatedEntities !== null): + $this->doPersist($relatedEntities, $visited); + break; + + default: + // Do nothing } } } @@ -2010,13 +2056,21 @@ class UnitOfWork implements PropertyChangedListener $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); - if ($relatedEntities instanceof Collection || is_array($relatedEntities)) { - // If its a PersistentCollection initialization is intended! No unwrap! - foreach ($relatedEntities as $relatedEntity) { - $this->doRemove($relatedEntity, $visited); - } - } else if ($relatedEntities !== null) { - $this->doRemove($relatedEntities, $visited); + switch (true) { + case ($relatedEntities instanceof Collection): + case (is_array($relatedEntities)): + // If its a PersistentCollection initialization is intended! No unwrap! + foreach ($relatedEntities as $relatedEntity) { + $this->doRemove($relatedEntity, $visited); + } + break; + + case ($relatedEntities !== null): + $this->doRemove($relatedEntities, $visited); + break; + + default: + // Do nothing } } } @@ -2037,29 +2091,40 @@ class UnitOfWork implements PropertyChangedListener $entityName = get_class($entity); $class = $this->em->getClassMetadata($entityName); - if ($lockMode == \Doctrine\DBAL\LockMode::OPTIMISTIC) { - if (!$class->isVersioned) { - throw OptimisticLockException::notVersioned($entityName); - } + switch ($lockMode) { + case \Doctrine\DBAL\LockMode::OPTIMISTIC; + if ( ! $class->isVersioned) { + throw OptimisticLockException::notVersioned($entityName); + } - if ($lockVersion != null) { + if ($lockVersion === null) { + return; + } + $entityVersion = $class->reflFields[$class->versionField]->getValue($entity); + if ($entityVersion != $lockVersion) { throw OptimisticLockException::lockFailedVersionMissmatch($entity, $lockVersion, $entityVersion); } - } - } else if (in_array($lockMode, array(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE))) { + + break; + + case \Doctrine\DBAL\LockMode::PESSIMISTIC_READ: + case \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE: + if (!$this->em->getConnection()->isTransactionActive()) { + throw TransactionRequiredException::transactionRequired(); + } - if (!$this->em->getConnection()->isTransactionActive()) { - throw TransactionRequiredException::transactionRequired(); - } + $oid = spl_object_hash($entity); - $oid = spl_object_hash($entity); - - $this->getEntityPersister($class->name)->lock( - array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]), - $lockMode - ); + $this->getEntityPersister($class->name)->lock( + array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]), + $lockMode + ); + break; + + default: + // Do nothing } } @@ -2187,6 +2252,7 @@ class UnitOfWork implements PropertyChangedListener if ($class->isIdentifierComposite) { $id = array(); + foreach ($class->identifier as $fieldName) { if (isset($class->associationMappings[$fieldName])) { $id[$fieldName] = $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']]; @@ -2194,28 +2260,26 @@ class UnitOfWork implements PropertyChangedListener $id[$fieldName] = $data[$fieldName]; } } + $idHash = implode(' ', $id); } else { if (isset($class->associationMappings[$class->identifier[0]])) { $idHash = $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']]; } else { - /*echo $className; - \Doctrine\Common\Util\Debug::dump($data); - \Doctrine\Common\Util\Debug::dump($class->identifier); - ob_end_flush(); - ob_start();*/ - $idHash = $data[$class->identifier[0]]; } + $id = array($class->identifier[0] => $idHash); } if (isset($this->identityMap[$class->rootEntityName][$idHash])) { $entity = $this->identityMap[$class->rootEntityName][$idHash]; $oid = spl_object_hash($entity); + if ($entity instanceof Proxy && ! $entity->__isInitialized__) { $entity->__isInitialized__ = true; $overrideLocalValues = true; + if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } @@ -2239,156 +2303,175 @@ class UnitOfWork implements PropertyChangedListener } else { $entity = $this->newInstance($class); $oid = spl_object_hash($entity); + $this->entityIdentifiers[$oid] = $id; $this->entityStates[$oid] = self::STATE_MANAGED; $this->originalEntityData[$oid] = $data; $this->identityMap[$class->rootEntityName][$idHash] = $entity; + if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } + $overrideLocalValues = true; } - if ($overrideLocalValues) { - foreach ($data as $field => $value) { - if (isset($class->fieldMappings[$field])) { - $class->reflFields[$field]->setValue($entity, $value); - } + if ( ! $overrideLocalValues) { + return $entity; + } + + foreach ($data as $field => $value) { + if (isset($class->fieldMappings[$field])) { + $class->reflFields[$field]->setValue($entity, $value); + } + } + + // Loading the entity right here, if its in the eager loading map get rid of it there. + unset($this->eagerLoadingEntities[$class->rootEntityName][$idHash]); + + if (isset($this->eagerLoadingEntities[$class->rootEntityName]) && ! $this->eagerLoadingEntities[$class->rootEntityName]) { + unset($this->eagerLoadingEntities[$class->rootEntityName]); + } + + // Properly initialize any unfetched associations, if partial objects are not allowed. + if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) { + return $entity; + } + + foreach ($class->associationMappings as $field => $assoc) { + // Check if the association is not among the fetch-joined associations already. + if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) { + continue; } - // Loading the entity right here, if its in the eager loading map get rid of it there. - unset($this->eagerLoadingEntities[$class->rootEntityName][$idHash]); + $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); - if (isset($this->eagerLoadingEntities[$class->rootEntityName]) && - ! $this->eagerLoadingEntities[$class->rootEntityName]) { - unset($this->eagerLoadingEntities[$class->rootEntityName]); - } + switch (true) { + case ($assoc['type'] & ClassMetadata::TO_ONE): + if ( ! $assoc['isOwningSide']) { + // Inverse side of x-to-one can never be lazy + $class->reflFields[$field]->setValue($entity, $this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc, $entity)); + + continue 2; + } + + $associatedId = array(); + + // TODO: Is this even computed right in all cases of composite keys? + foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) { + $joinColumnValue = isset($data[$srcColumn]) ? $data[$srcColumn] : null; + + if ($joinColumnValue !== null) { + if ($targetClass->containsForeignIdentifier) { + $associatedId[$targetClass->getFieldForColumn($targetColumn)] = $joinColumnValue; + } else { + $associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue; + } + } + } + + if ( ! $associatedId) { + // Foreign key is NULL + $class->reflFields[$field]->setValue($entity, null); + $this->originalEntityData[$oid][$field] = null; - // Properly initialize any unfetched associations, if partial objects are not allowed. - if ( ! isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) { - foreach ($class->associationMappings as $field => $assoc) { - // Check if the association is not among the fetch-joined associations already. - if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) { continue; } - $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); - - if ($assoc['type'] & ClassMetadata::TO_ONE) { - if ($assoc['isOwningSide']) { - $associatedId = array(); - // TODO: Is this even computed right in all cases of composite keys? - foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) { - $joinColumnValue = isset($data[$srcColumn]) ? $data[$srcColumn] : null; - if ($joinColumnValue !== null) { - if ($targetClass->containsForeignIdentifier) { - $associatedId[$targetClass->getFieldForColumn($targetColumn)] = $joinColumnValue; - } else { - $associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue; - } - } - } - if ( ! $associatedId) { - // Foreign key is NULL - $class->reflFields[$field]->setValue($entity, null); - $this->originalEntityData[$oid][$field] = null; - } else { - if (!isset($hints['fetchMode'][$class->name][$field])) { - $hints['fetchMode'][$class->name][$field] = $assoc['fetch']; - } - - // Foreign key is set - // Check identity map first - // FIXME: Can break easily with composite keys if join column values are in - // wrong order. The correct order is the one in ClassMetadata#identifier. - $relatedIdHash = implode(' ', $associatedId); - if (isset($this->identityMap[$targetClass->rootEntityName][$relatedIdHash])) { - $newValue = $this->identityMap[$targetClass->rootEntityName][$relatedIdHash]; - - // if this is an uninitialized proxy, we are deferring eager loads, - // this association is marked as eager fetch, and its an uninitialized proxy (wtf!) - // then we cann append this entity for eager loading! - if ($hints['fetchMode'][$class->name][$field] == ClassMetadata::FETCH_EAGER && - isset($hints['deferEagerLoad']) && - !$targetClass->isIdentifierComposite && - $newValue instanceof Proxy && - $newValue->__isInitialized__ === false) { - - $this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId); - } - } else { - if ($targetClass->subClasses) { - // If it might be a subtype, it can not be lazy. There isn't even - // a way to solve this with deferred eager loading, which means putting - // an entity with subclasses at a *-to-one location is really bad! (performance-wise) - $newValue = $this->getEntityPersister($assoc['targetEntity']) - ->loadOneToOneEntity($assoc, $entity, $associatedId); - } else { - // Deferred eager load only works for single identifier classes - - if ($hints['fetchMode'][$class->name][$field] == ClassMetadata::FETCH_EAGER) { - if (isset($hints['deferEagerLoad']) && !$targetClass->isIdentifierComposite) { - // TODO: Is there a faster approach? - $this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId); - - $newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId); - } else { - // TODO: This is very imperformant, ignore it? - $newValue = $this->em->find($assoc['targetEntity'], $associatedId); - } - } else { - $newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId); - } - // PERF: Inlined & optimized code from UnitOfWork#registerManaged() - $newValueOid = spl_object_hash($newValue); - $this->entityIdentifiers[$newValueOid] = $associatedId; - $this->identityMap[$targetClass->rootEntityName][$relatedIdHash] = $newValue; - $this->entityStates[$newValueOid] = self::STATE_MANAGED; - // make sure that when an proxy is then finally loaded, $this->originalEntityData is set also! - } - } - $this->originalEntityData[$oid][$field] = $newValue; - $class->reflFields[$field]->setValue($entity, $newValue); - - if ($assoc['inversedBy'] && $assoc['type'] & ClassMetadata::ONE_TO_ONE) { - $inverseAssoc = $targetClass->associationMappings[$assoc['inversedBy']]; - $targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($newValue, $entity); - } - } - } else { - // Inverse side of x-to-one can never be lazy - $class->reflFields[$field]->setValue($entity, $this->getEntityPersister($assoc['targetEntity']) - ->loadOneToOneEntity($assoc, $entity)); - } - } else { - // Inject collection - $pColl = new PersistentCollection($this->em, $targetClass, new ArrayCollection); - $pColl->setOwner($entity, $assoc); - - $reflField = $class->reflFields[$field]; - $reflField->setValue($entity, $pColl); - - if ($assoc['fetch'] == ClassMetadata::FETCH_EAGER) { - $this->loadCollection($pColl); - $pColl->takeSnapshot(); - } else { - $pColl->setInitialized(false); - } - $this->originalEntityData[$oid][$field] = $pColl; + if ( ! isset($hints['fetchMode'][$class->name][$field])) { + $hints['fetchMode'][$class->name][$field] = $assoc['fetch']; } - } + + // Foreign key is set + // Check identity map first + // FIXME: Can break easily with composite keys if join column values are in + // wrong order. The correct order is the one in ClassMetadata#identifier. + $relatedIdHash = implode(' ', $associatedId); + + switch (true) { + case (isset($this->identityMap[$targetClass->rootEntityName][$relatedIdHash])): + $newValue = $this->identityMap[$targetClass->rootEntityName][$relatedIdHash]; + + // if this is an uninitialized proxy, we are deferring eager loads, + // this association is marked as eager fetch, and its an uninitialized proxy (wtf!) + // then we cann append this entity for eager loading! + if ($hints['fetchMode'][$class->name][$field] == ClassMetadata::FETCH_EAGER && + isset($hints['deferEagerLoad']) && + !$targetClass->isIdentifierComposite && + $newValue instanceof Proxy && + $newValue->__isInitialized__ === false) { + + $this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId); + } + + break; + + case ($targetClass->subClasses): + // If it might be a subtype, it can not be lazy. There isn't even + // a way to solve this with deferred eager loading, which means putting + // an entity with subclasses at a *-to-one location is really bad! (performance-wise) + $newValue = $this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc, $entity, $associatedId); + break; + + default: + switch (true) { + // We are negating the condition here. Other cases will assume it is valid! + case ($hints['fetchMode'][$class->name][$field] !== ClassMetadata::FETCH_EAGER): + $newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId); + break; + + // Deferred eager load only works for single identifier classes + case (isset($hints['deferEagerLoad']) && ! $targetClass->isIdentifierComposite): + // TODO: Is there a faster approach? + $this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId); + + $newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId); + break; + + default: + // TODO: This is very imperformant, ignore it? + $newValue = $this->em->find($assoc['targetEntity'], $associatedId); + break; + } + + // PERF: Inlined & optimized code from UnitOfWork#registerManaged() + $newValueOid = spl_object_hash($newValue); + $this->entityIdentifiers[$newValueOid] = $associatedId; + $this->identityMap[$targetClass->rootEntityName][$relatedIdHash] = $newValue; + $this->entityStates[$newValueOid] = self::STATE_MANAGED; + // make sure that when an proxy is then finally loaded, $this->originalEntityData is set also! + break; + } + + $this->originalEntityData[$oid][$field] = $newValue; + $class->reflFields[$field]->setValue($entity, $newValue); + + if ($assoc['inversedBy'] && $assoc['type'] & ClassMetadata::ONE_TO_ONE) { + $inverseAssoc = $targetClass->associationMappings[$assoc['inversedBy']]; + $targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($newValue, $entity); + } + + break; + + default: + // Inject collection + $pColl = new PersistentCollection($this->em, $targetClass, new ArrayCollection); + $pColl->setOwner($entity, $assoc); + $pColl->setInitialized(false); + + $reflField = $class->reflFields[$field]; + $reflField->setValue($entity, $pColl); + + if ($assoc['fetch'] == ClassMetadata::FETCH_EAGER) { + $this->loadCollection($pColl); + $pColl->takeSnapshot(); + } + + $this->originalEntityData[$oid][$field] = $pColl; + break; } } - //TODO: These should be invoked later, after hydration, because associations may not yet be loaded here. - if (isset($class->lifecycleCallbacks[Events::postLoad])) { - $class->invokeLifecycleCallbacks(Events::postLoad, $entity); - } - - if ($this->evm->hasListeners(Events::postLoad)) { - $this->evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->em)); - } - return $entity; } From 356f5874bf81ca4e37681c233e24cc84d16e7a39 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Tue, 29 Nov 2011 11:29:17 -0500 Subject: [PATCH 07/15] Added support to removeElement remove items without initializing the PersistentCollection. --- lib/Doctrine/ORM/PersistentCollection.php | 224 ++++++++++++------ .../AbstractCollectionPersister.php | 10 + .../ORM/Persisters/ManyToManyPersister.php | 181 +++++++------- .../ORM/Persisters/OneToManyPersister.php | 142 +++++------ .../Functional/ExtraLazyCollectionTest.php | 114 ++++++++- 5 files changed, 439 insertions(+), 232 deletions(-) diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index 06617ff9b..53bff7066 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -21,6 +21,7 @@ namespace Doctrine\ORM; use Doctrine\ORM\Mapping\ClassMetadata, Doctrine\Common\Collections\Collection, + Doctrine\Common\Collections\ArrayCollection, Closure; /** @@ -114,8 +115,8 @@ final class PersistentCollection implements Collection */ public function __construct(EntityManager $em, $class, $coll) { - $this->coll = $coll; - $this->em = $em; + $this->coll = $coll; + $this->em = $em; $this->typeClass = $class; } @@ -129,8 +130,8 @@ final class PersistentCollection implements Collection */ public function setOwner($entity, array $assoc) { - $this->owner = $entity; - $this->association = $assoc; + $this->owner = $entity; + $this->association = $assoc; $this->backRefFieldName = $assoc['inversedBy'] ?: $assoc['mappedBy']; } @@ -160,16 +161,18 @@ final class PersistentCollection implements Collection public function hydrateAdd($element) { $this->coll->add($element); + // If _backRefFieldName is set and its a one-to-many association, // we need to set the back reference. if ($this->backRefFieldName && $this->association['type'] == ClassMetadata::ONE_TO_MANY) { // Set back reference to owner - $this->typeClass->reflFields[$this->backRefFieldName] - ->setValue($element, $this->owner); + $this->typeClass->reflFields[$this->backRefFieldName]->setValue( + $element, $this->owner + ); + $this->em->getUnitOfWork()->setOriginalEntityProperty( - spl_object_hash($element), - $this->backRefFieldName, - $this->owner); + spl_object_hash($element), $this->backRefFieldName, $this->owner + ); } } @@ -183,12 +186,14 @@ final class PersistentCollection implements Collection public function hydrateSet($key, $element) { $this->coll->set($key, $element); + // If _backRefFieldName is set, then the association is bidirectional // and we need to set the back reference. if ($this->backRefFieldName && $this->association['type'] == ClassMetadata::ONE_TO_MANY) { // Set back reference to owner - $this->typeClass->reflFields[$this->backRefFieldName] - ->setValue($element, $this->owner); + $this->typeClass->reflFields[$this->backRefFieldName]->setValue( + $element, $this->owner + ); } } @@ -198,23 +203,31 @@ final class PersistentCollection implements Collection */ public function initialize() { - if ( ! $this->initialized && $this->association) { - if ($this->isDirty) { - // Has NEW objects added through add(). Remember them. - $newObjects = $this->coll->toArray(); - } - $this->coll->clear(); - $this->em->getUnitOfWork()->loadCollection($this); - $this->takeSnapshot(); - // Reattach NEW objects added through add(), if any. - if (isset($newObjects)) { - foreach ($newObjects as $obj) { - $this->coll->add($obj); - } - $this->isDirty = true; - } - $this->initialized = true; + if ($this->initialized || ! $this->association) { + return; } + + // Has NEW objects added through add(). Remember them. + $newObjects = array(); + + if ($this->isDirty) { + $newObjects = $this->coll->toArray(); + } + + $this->coll->clear(); + $this->em->getUnitOfWork()->loadCollection($this); + $this->takeSnapshot(); + + // Reattach NEW objects added through add(), if any. + if ($newObjects) { + foreach ($newObjects as $obj) { + $this->coll->add($obj); + } + + $this->isDirty = true; + } + + $this->initialized = true; } /** @@ -224,7 +237,7 @@ final class PersistentCollection implements Collection public function takeSnapshot() { $this->snapshot = $this->coll->toArray(); - $this->isDirty = false; + $this->isDirty = false; } /** @@ -246,8 +259,11 @@ final class PersistentCollection implements Collection */ public function getDeleteDiff() { - return array_udiff_assoc($this->snapshot, $this->coll->toArray(), - function($a, $b) {return $a === $b ? 0 : 1;}); + return array_udiff_assoc( + $this->snapshot, + $this->coll->toArray(), + function($a, $b) { return $a === $b ? 0 : 1; } + ); } /** @@ -258,8 +274,11 @@ final class PersistentCollection implements Collection */ public function getInsertDiff() { - return array_udiff_assoc($this->coll->toArray(), $this->snapshot, - function($a, $b) {return $a === $b ? 0 : 1;}); + return array_udiff_assoc( + $this->coll->toArray(), + $this->snapshot, + function($a, $b) { return $a === $b ? 0 : 1; } + ); } /** @@ -277,12 +296,17 @@ final class PersistentCollection implements Collection */ private function changed() { - if ( ! $this->isDirty) { - $this->isDirty = true; - if ($this->association !== null && $this->association['isOwningSide'] && $this->association['type'] == ClassMetadata::MANY_TO_MANY && - $this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) { - $this->em->getUnitOfWork()->scheduleForDirtyCheck($this->owner); - } + if ($this->isDirty) { + return; + } + + $this->isDirty = true; + + if ($this->association !== null && + $this->association['isOwningSide'] && + $this->association['type'] == ClassMetadata::MANY_TO_MANY && + $this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) { + $this->em->getUnitOfWork()->scheduleForDirtyCheck($this->owner); } } @@ -331,6 +355,7 @@ final class PersistentCollection implements Collection public function first() { $this->initialize(); + return $this->coll->first(); } @@ -338,6 +363,7 @@ final class PersistentCollection implements Collection public function last() { $this->initialize(); + return $this->coll->last(); } @@ -351,13 +377,19 @@ final class PersistentCollection implements Collection // not used we can issue a straight SQL delete/update on the // association (table). Without initializing the collection. $this->initialize(); + $removed = $this->coll->remove($key); - if ($removed) { - $this->changed(); - if ($this->association !== null && $this->association['type'] == ClassMetadata::ONE_TO_MANY && - $this->association['orphanRemoval']) { - $this->em->getUnitOfWork()->scheduleOrphanRemoval($removed); - } + + if ( ! $removed) { + return $removed; + } + + $this->changed(); + + if ($this->association !== null && + $this->association['type'] == ClassMetadata::ONE_TO_MANY && + $this->association['orphanRemoval']) { + $this->em->getUnitOfWork()->scheduleOrphanRemoval($removed); } return $removed; @@ -368,25 +400,36 @@ final class PersistentCollection implements Collection */ public function removeElement($element) { - // TODO: Assuming the identity of entities in a collection is always based - // on their primary key (there is no equals/hashCode in PHP), - // if the collection is not initialized, we could issue a straight - // SQL DELETE/UPDATE on the association (table) without initializing - // the collection. - /*if ( ! $this->initialized) { - $this->em->getUnitOfWork()->getCollectionPersister($this->association) - ->deleteRows($this, $element); - }*/ + if ( ! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { + if ($this->coll->contains($element)) { + return $this->coll->removeElement($element); + } + + $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association); + + if ($persister->removeElement($this, $element)) { + return $element; + } + + return null; + } $this->initialize(); + $removed = $this->coll->removeElement($element); - if ($removed) { - $this->changed(); - if ($this->association !== null && $this->association['type'] == ClassMetadata::ONE_TO_MANY && - $this->association['orphanRemoval']) { - $this->em->getUnitOfWork()->scheduleOrphanRemoval($element); - } + + if ( ! $removed) { + return $removed; } + + $this->changed(); + + if ($this->association !== null && + $this->association['type'] == ClassMetadata::ONE_TO_MANY && + $this->association['orphanRemoval']) { + $this->em->getUnitOfWork()->scheduleOrphanRemoval($element); + } + return $removed; } @@ -396,6 +439,7 @@ final class PersistentCollection implements Collection public function containsKey($key) { $this->initialize(); + return $this->coll->containsKey($key); } @@ -404,14 +448,14 @@ final class PersistentCollection implements Collection */ public function contains($element) { - if (!$this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { - return $this->coll->contains($element) || - $this->em->getUnitOfWork() - ->getCollectionPersister($this->association) - ->contains($this, $element); + if ( ! $this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { + $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association); + + return $this->coll->contains($element) || $persister->contains($this, $element); } $this->initialize(); + return $this->coll->contains($element); } @@ -421,6 +465,7 @@ final class PersistentCollection implements Collection public function exists(Closure $p) { $this->initialize(); + return $this->coll->exists($p); } @@ -430,6 +475,7 @@ final class PersistentCollection implements Collection public function indexOf($element) { $this->initialize(); + return $this->coll->indexOf($element); } @@ -439,6 +485,7 @@ final class PersistentCollection implements Collection public function get($key) { $this->initialize(); + return $this->coll->get($key); } @@ -448,6 +495,7 @@ final class PersistentCollection implements Collection public function getKeys() { $this->initialize(); + return $this->coll->getKeys(); } @@ -457,6 +505,7 @@ final class PersistentCollection implements Collection public function getValues() { $this->initialize(); + return $this->coll->getValues(); } @@ -465,13 +514,14 @@ final class PersistentCollection implements Collection */ public function count() { - if (!$this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { - return $this->em->getUnitOfWork() - ->getCollectionPersister($this->association) - ->count($this) + ($this->isDirty ? $this->coll->count() : 0); + if ( ! $this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { + $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association); + + return $persister->count($this) + ($this->isDirty ? $this->coll->count() : 0); } $this->initialize(); + return $this->coll->count(); } @@ -481,7 +531,9 @@ final class PersistentCollection implements Collection public function set($key, $value) { $this->initialize(); + $this->coll->set($key, $value); + $this->changed(); } @@ -491,7 +543,9 @@ final class PersistentCollection implements Collection public function add($value) { $this->coll->add($value); + $this->changed(); + return true; } @@ -501,6 +555,7 @@ final class PersistentCollection implements Collection public function isEmpty() { $this->initialize(); + return $this->coll->isEmpty(); } @@ -510,6 +565,7 @@ final class PersistentCollection implements Collection public function getIterator() { $this->initialize(); + return $this->coll->getIterator(); } @@ -519,6 +575,7 @@ final class PersistentCollection implements Collection public function map(Closure $func) { $this->initialize(); + return $this->coll->map($func); } @@ -528,6 +585,7 @@ final class PersistentCollection implements Collection public function filter(Closure $p) { $this->initialize(); + return $this->coll->filter($p); } @@ -537,6 +595,7 @@ final class PersistentCollection implements Collection public function forAll(Closure $p) { $this->initialize(); + return $this->coll->forAll($p); } @@ -546,6 +605,7 @@ final class PersistentCollection implements Collection public function partition(Closure $p) { $this->initialize(); + return $this->coll->partition($p); } @@ -555,6 +615,7 @@ final class PersistentCollection implements Collection public function toArray() { $this->initialize(); + return $this->coll->toArray(); } @@ -566,19 +627,28 @@ final class PersistentCollection implements Collection if ($this->initialized && $this->isEmpty()) { return; } + + $uow = $this->em->getUnitOfWork(); + if ($this->association['type'] == ClassMetadata::ONE_TO_MANY && $this->association['orphanRemoval']) { // we need to initialize here, as orphan removal acts like implicit cascadeRemove, // hence for event listeners we need the objects in memory. $this->initialize(); + foreach ($this->coll as $element) { - $this->em->getUnitOfWork()->scheduleOrphanRemoval($element); + $uow->scheduleOrphanRemoval($element); } } + $this->coll->clear(); + $this->initialized = true; // direct call, {@link initialize()} is too expensive + if ($this->association['isOwningSide']) { $this->changed(); - $this->em->getUnitOfWork()->scheduleCollectionDeletion($this); + + $uow->scheduleCollectionDeletion($this); + $this->takeSnapshot(); } } @@ -622,6 +692,7 @@ final class PersistentCollection implements Collection if ( ! isset($offset)) { return $this->add($value); } + return $this->set($offset, $value); } @@ -656,6 +727,8 @@ final class PersistentCollection implements Collection /** * Retrieves the wrapped Collection instance. + * + * @return Doctrine\Common\Collections\Collection */ public function unwrap() { @@ -671,20 +744,19 @@ final class PersistentCollection implements Collection * * @param int $offset * @param int $length + * * @return array */ public function slice($offset, $length = null) { - if ( ! $this->initialized && - ! $this->isDirty && - $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { + if ( ! $this->initialized && ! $this->isDirty && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { + $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association); - return $this->em->getUnitOfWork() - ->getCollectionPersister($this->association) - ->slice($this, $offset, $length); + return $persister->slice($this, $offset, $length); } $this->initialize(); + return $this->coll->slice($offset, $length); } } diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index 250f7c0ef..a4fc4b92d 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -151,6 +151,16 @@ abstract class AbstractCollectionPersister throw new \BadMethodCallException("Checking for existance of a key is not supported by this CollectionPersister."); } + public function removeElement(PersistentCollection $coll, $element) + { + throw new \BadMethodCallException("Removing an element is not supported by this CollectionPersister."); + } + + public function removeKey(PersistentCollection $coll, $key) + { + throw new \BadMethodCallException("Removing a key is not supported by this CollectionPersister."); + } + public function get(PersistentCollection $coll, $index) { throw new \BadMethodCallException("Selecting a collection by index is not supported by this CollectionPersister."); diff --git a/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php index 863d350dc..e94cb3443 100644 --- a/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php +++ b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php @@ -27,8 +27,9 @@ use Doctrine\ORM\PersistentCollection, /** * Persister for many-to-many collections. * - * @author Roman Borschel - * @since 2.0 + * @author Roman Borschel + * @author Guilherme Blanco + * @since 2.0 */ class ManyToManyPersister extends AbstractCollectionPersister { @@ -79,8 +80,10 @@ class ManyToManyPersister extends AbstractCollectionPersister $columns = $mapping['joinTableColumns']; $class = $this->_em->getClassMetadata(get_class($coll->getOwner())); - return 'INSERT INTO ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform()) - . ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')'; + $joinTable = $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform()); + + return 'INSERT INTO ' . $joinTable . ' (' . implode(', ', $columns) . ')' + . ' VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')'; } /** @@ -118,27 +121,21 @@ class ManyToManyPersister extends AbstractCollectionPersister } foreach ($mapping['joinTableColumns'] as $joinTableColumn) { - if (isset($mapping['relationToSourceKeyColumns'][$joinTableColumn])) { - if ($isComposite) { - if ($class1->containsForeignIdentifier) { - $params[] = $identifier1[$class1->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])]; - } else { - $params[] = $identifier1[$class1->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]]; - } - } else { - $params[] = array_pop($identifier1); - } - } else { - if ($isComposite) { - if ($class2->containsForeignIdentifier) { - $params[] = $identifier2[$class2->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])]; - } else { - $params[] = $identifier2[$class2->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]]; - } - } else { - $params[] = array_pop($identifier2); - } + $isRelationToSource = isset($mapping['relationToSourceKeyColumns'][$joinTableColumn]); + + if ( ! $isComposite) { + $params[] = $isRelationToSource ? array_pop($identifier1) : array_pop($identifier2); + + continue; } + + if ($isRelationToSource) { + $params[] = $identifier1[$class1->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])]; + + continue; + } + + $params[] = $identifier2[$class2->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])]; } return $params; @@ -151,19 +148,11 @@ class ManyToManyPersister extends AbstractCollectionPersister */ protected function _getDeleteSQL(PersistentCollection $coll) { - $mapping = $coll->getMapping(); - $class = $this->_em->getClassMetadata(get_class($coll->getOwner())); - $joinTable = $mapping['joinTable']; - $whereClause = ''; - - foreach ($mapping['relationToSourceKeyColumns'] as $relationColumn => $srcColumn) { - if ($whereClause !== '') $whereClause .= ' AND '; - - $whereClause .= $relationColumn . ' = ?'; - } + $class = $this->_em->getClassMetadata(get_class($coll->getOwner())); + $mapping = $coll->getMapping(); return 'DELETE FROM ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform()) - . ' WHERE ' . $whereClause; + . ' WHERE ' . implode(' = ? AND ', array_keys($mapping['relationToSourceKeyColumns'])) . ' = ?'; } /** @@ -175,20 +164,24 @@ class ManyToManyPersister extends AbstractCollectionPersister */ protected function _getDeleteSQLParameters(PersistentCollection $coll) { - $params = array(); - $mapping = $coll->getMapping(); $identifier = $this->_uow->getEntityIdentifier($coll->getOwner()); + $mapping = $coll->getMapping(); + $params = array(); - if (count($mapping['relationToSourceKeyColumns']) > 1) { - $sourceClass = $this->_em->getClassMetadata(get_class($mapping->getOwner())); + // Optimization for single column identifier + if (count($mapping['relationToSourceKeyColumns']) === 1) { + $params[] = array_pop($identifier); - foreach ($mapping['relationToSourceKeyColumns'] as $relColumn => $srcColumn) { - $params[] = $identifier[$sourceClass->fieldNames[$srcColumn]]; - } - } else { - $params[] = array_pop($identifier); + return $params; } - + + // Composite identifier + $sourceClass = $this->_em->getClassMetadata(get_class($mapping->getOwner())); + + foreach ($mapping['relationToSourceKeyColumns'] as $relColumn => $srcColumn) { + $params[] = $identifier[$sourceClass->fieldNames[$srcColumn]]; + } + return $params; } @@ -197,7 +190,6 @@ class ManyToManyPersister extends AbstractCollectionPersister */ public function count(PersistentCollection $coll) { - $params = array(); $mapping = $coll->getMapping(); $class = $this->_em->getClassMetadata($mapping['sourceEntity']); $id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); @@ -209,25 +201,24 @@ class ManyToManyPersister extends AbstractCollectionPersister $joinColumns = $mapping['relationToTargetKeyColumns']; } - $whereClause = ''; + $whereClauses = array(); + $params = array(); foreach ($mapping['joinTableColumns'] as $joinTableColumn) { - if (isset($joinColumns[$joinTableColumn])) { - if ($whereClause !== '') { - $whereClause .= ' AND '; - } - - $whereClause .= "$joinTableColumn = ?"; - - $params[] = ($class->containsForeignIdentifier) - ? $id[$class->getFieldForColumn($joinColumns[$joinTableColumn])] - : $id[$class->fieldNames[$joinColumns[$joinTableColumn]]]; + if ( ! isset($joinColumns[$joinTableColumn])) { + continue; } + + $whereClauses[] = $joinTableColumn . ' = ?'; + + $params[] = ($class->containsForeignIdentifier) + ? $id[$class->getFieldForColumn($joinColumns[$joinTableColumn])] + : $id[$class->fieldNames[$joinColumns[$joinTableColumn]]]; } $sql = 'SELECT COUNT(*)' . ' FROM ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform()) - . ' WHERE ' . $whereClause; + . ' WHERE ' . implode(' AND ', $whereClauses); return $this->_conn->fetchColumn($sql, $params); } @@ -248,6 +239,7 @@ class ManyToManyPersister extends AbstractCollectionPersister /** * @param PersistentCollection $coll * @param object $element + * @return boolean */ public function contains(PersistentCollection $coll, $element) { @@ -258,9 +250,44 @@ class ManyToManyPersister extends AbstractCollectionPersister return false; } - $params = array(); - $mapping = $coll->getMapping(); + list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element); + + $sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses); + return (bool) $this->_conn->fetchColumn($sql, $params); + } + + /** + * @param PersistentCollection $coll + * @param object $element + * @return boolean + */ + public function removeElement(PersistentCollection $coll, $element) + { + $uow = $this->_em->getUnitOfWork(); + + // shortcut for new entities + if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) == UnitOfWork::STATE_NEW) { + return false; + } + + list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element); + + $sql = 'DELETE FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses); + + return (bool) $this->_conn->executeUpdate($sql, $params); + } + + /** + * @param Doctrine\ORM\PersistentCollection $coll + * @param object $element + * @return array + */ + private function getJoinTableRestrictions(PersistentCollection $coll, $element) + { + $uow = $this->_em->getUnitOfWork(); + $mapping = $coll->getMapping(); + if ( ! $mapping['isOwningSide']) { $sourceClass = $this->_em->getClassMetadata($mapping['targetEntity']); $targetClass = $this->_em->getClassMetadata($mapping['sourceEntity']); @@ -275,36 +302,26 @@ class ManyToManyPersister extends AbstractCollectionPersister $targetId = $uow->getEntityIdentifier($element); } - $whereClause = ''; + $quotedJoinTable = $sourceClass->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform()); + $whereClauses = array(); + $params = array(); foreach ($mapping['joinTableColumns'] as $joinTableColumn) { - if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) { - if ($whereClause !== '') { - $whereClause .= ' AND '; - } - - $whereClause .= $joinTableColumn . ' = ?'; + $whereClauses[] = $joinTableColumn . ' = ?'; + if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) { $params[] = ($targetClass->containsForeignIdentifier) ? $targetId[$targetClass->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])] : $targetId[$targetClass->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]]; - } else if (isset($mapping['relationToSourceKeyColumns'][$joinTableColumn])) { - if ($whereClause !== '') { - $whereClause .= ' AND '; - } - - $whereClause .= $joinTableColumn . ' = ?'; - - $params[] = ($sourceClass->containsForeignIdentifier) - ? $sourceId[$sourceClass->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])] - : $sourceId[$sourceClass->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]]; + continue; } + + // relationToSourceKeyColumns + $params[] = ($sourceClass->containsForeignIdentifier) + ? $sourceId[$sourceClass->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])] + : $sourceId[$sourceClass->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]]; } - $sql = 'SELECT 1' - . ' FROM ' . $sourceClass->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform()) - . ' WHERE ' . $whereClause; - - return (bool) $this->_conn->fetchColumn($sql, $params); + return array($quotedJoinTable, $whereClauses, $params); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Persisters/OneToManyPersister.php b/lib/Doctrine/ORM/Persisters/OneToManyPersister.php index e9fcf06c5..7a7f29750 100644 --- a/lib/Doctrine/ORM/Persisters/OneToManyPersister.php +++ b/lib/Doctrine/ORM/Persisters/OneToManyPersister.php @@ -27,13 +27,9 @@ use Doctrine\ORM\PersistentCollection, /** * Persister for one-to-many collections. * - * IMPORTANT: - * This persister is only used for uni-directional one-to-many mappings on a foreign key - * (which are not yet supported). So currently this persister is not used. - * - * @since 2.0 - * @author Roman Borschel - * @todo Remove + * @author Roman Borschel + * @author Guilherme Blanco + * @since 2.0 */ class OneToManyPersister extends AbstractCollectionPersister { @@ -48,24 +44,19 @@ class OneToManyPersister extends AbstractCollectionPersister protected function _getDeleteRowSQL(PersistentCollection $coll) { $mapping = $coll->getMapping(); - $targetClass = $this->_em->getClassMetadata($mapping->getTargetEntityName()); - $table = $targetClass->getTableName(); + $class = $this->_em->getClassMetadata($mapping['targetEntity']); + + return 'DELETE FROM ' . $class->getQuotedTableName($this->_conn->getDatabasePlatform()) + . ' WHERE ' . implode('= ? AND ', $class->getIdentifierColumnNames()) . ' = ?'; + } - $ownerMapping = $targetClass->getAssociationMapping($mapping['mappedBy']); - - $setClause = ''; - foreach ($ownerMapping->sourceToTargetKeyColumns as $sourceCol => $targetCol) { - if ($setClause != '') $setClause .= ', '; - $setClause .= "$sourceCol = NULL"; - } - - $whereClause = ''; - foreach ($targetClass->getIdentifierColumnNames() as $idColumn) { - if ($whereClause != '') $whereClause .= ' AND '; - $whereClause .= "$idColumn = ?"; - } - - return array("UPDATE $table SET $setClause WHERE $whereClause", $this->_uow->getEntityIdentifier($element)); + /** + * {@inheritdoc} + * + */ + protected function _getDeleteRowSQLParameters(PersistentCollection $coll, $element) + { + return array_values($this->_uow->getEntityIdentifier($element)); } protected function _getInsertRowSQL(PersistentCollection $coll) @@ -73,6 +64,16 @@ class OneToManyPersister extends AbstractCollectionPersister return "UPDATE xxx SET foreign_key = yyy WHERE foreign_key = zzz"; } + /** + * Gets the SQL parameters for the corresponding SQL statement to insert the given + * element of the given collection into the database. + * + * @param PersistentCollection $coll + * @param mixed $element + */ + protected function _getInsertRowSQLParameters(PersistentCollection $coll, $element) + {} + /* Not used for OneToManyPersister */ protected function _getUpdateRowSQL(PersistentCollection $coll) { @@ -98,52 +99,31 @@ class OneToManyPersister extends AbstractCollectionPersister protected function _getDeleteSQLParameters(PersistentCollection $coll) {} - /** - * Gets the SQL parameters for the corresponding SQL statement to insert the given - * element of the given collection into the database. - * - * @param PersistentCollection $coll - * @param mixed $element - */ - protected function _getInsertRowSQLParameters(PersistentCollection $coll, $element) - {} - - /** - * Gets the SQL parameters for the corresponding SQL statement to delete the given - * element from the given collection. - * - * @param PersistentCollection $coll - * @param mixed $element - */ - protected function _getDeleteRowSQLParameters(PersistentCollection $coll, $element) - {} - /** * {@inheritdoc} */ public function count(PersistentCollection $coll) { - $mapping = $coll->getMapping(); + $mapping = $coll->getMapping(); $targetClass = $this->_em->getClassMetadata($mapping['targetEntity']); $sourceClass = $this->_em->getClassMetadata($mapping['sourceEntity']); + $id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); - $params = array(); - $id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); - - $where = ''; + $whereClauses = array(); + $params = array(); + foreach ($targetClass->associationMappings[$mapping['mappedBy']]['joinColumns'] AS $joinColumn) { - if ($where != '') { - $where .= ' AND '; - } - $where .= $joinColumn['name'] . " = ?"; - if ($targetClass->containsForeignIdentifier) { - $params[] = $id[$sourceClass->getFieldForColumn($joinColumn['referencedColumnName'])]; - } else { - $params[] = $id[$sourceClass->fieldNames[$joinColumn['referencedColumnName']]]; - } + $whereClauses[] = $joinColumn['name'] . ' = ?'; + + $params[] = ($targetClass->containsForeignIdentifier) + ? $id[$sourceClass->getFieldForColumn($joinColumn['referencedColumnName'])] + : $id[$sourceClass->fieldNames[$joinColumn['referencedColumnName']]]; } - $sql = "SELECT count(*) FROM " . $targetClass->getQuotedTableName($this->_conn->getDatabasePlatform()) . " WHERE " . $where; + $sql = 'SELECT count(*)' + . ' FROM ' . $targetClass->getQuotedTableName($this->_conn->getDatabasePlatform()) + . ' WHERE ' . implode(' AND ', $whereClauses); + return $this->_conn->fetchColumn($sql, $params); } @@ -155,31 +135,57 @@ class OneToManyPersister extends AbstractCollectionPersister */ public function slice(PersistentCollection $coll, $offset, $length = null) { - $mapping = $coll->getMapping(); - return $this->_em->getUnitOfWork() - ->getEntityPersister($mapping['targetEntity']) - ->getOneToManyCollection($mapping, $coll->getOwner(), $offset, $length); + $mapping = $coll->getMapping(); + $uow = $this->_em->getUnitOfWork(); + $persister = $uow->getEntityPersister($mapping['targetEntity']); + + return $persister->getOneToManyCollection($mapping, $coll->getOwner(), $offset, $length); } /** * @param PersistentCollection $coll * @param object $element + * @return boolean */ public function contains(PersistentCollection $coll, $element) { $mapping = $coll->getMapping(); - $uow = $this->_em->getUnitOfWork(); + $uow = $this->_em->getUnitOfWork(); // shortcut for new entities if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) == UnitOfWork::STATE_NEW) { return false; } - // only works with single id identifier entities. Will throw an exception in Entity Persisters - // if that is not the case for the 'mappedBy' field. + $persister = $uow->getEntityPersister($mapping['targetEntity']); + + // only works with single id identifier entities. Will throw an + // exception in Entity Persisters if that is not the case for the + // 'mappedBy' field. $id = current( $uow->getEntityIdentifier($coll->getOwner()) ); - return $uow->getEntityPersister($mapping['targetEntity']) - ->exists($element, array($mapping['mappedBy'] => $id)); + return $persister->exists($element, array($mapping['mappedBy'] => $id)); + } + + /** + * @param PersistentCollection $coll + * @param object $element + * @return boolean + */ + public function removeElement(PersistentCollection $coll, $element) + { + $uow = $this->_em->getUnitOfWork(); + + // shortcut for new entities + if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) == UnitOfWork::STATE_NEW) { + return false; + } + + $mapping = $coll->getMapping(); + $class = $this->_em->getClassMetadata($mapping['targetEntity']); + $sql = 'DELETE FROM ' . $class->getQuotedTableName($this->_conn->getDatabasePlatform()) + . ' WHERE ' . implode('= ? AND ', $class->getIdentifierColumnNames()) . ' = ?'; + + return (bool) $this->_conn->executeUpdate($sql, $this->_getDeleteRowSQLParameters($coll, $element)); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php b/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php index 351c1e25b..1bf704a89 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php @@ -25,11 +25,10 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); $class->associationMappings['groups']['fetch'] = ClassMetadataInfo::FETCH_EXTRA_LAZY; $class->associationMappings['articles']['fetch'] = ClassMetadataInfo::FETCH_EXTRA_LAZY; - + $class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsGroup'); $class->associationMappings['users']['fetch'] = ClassMetadataInfo::FETCH_EXTRA_LAZY; - $this->loadFixture(); } @@ -256,17 +255,18 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); $this->assertFalse($user->groups->isInitialized(), "Pre-Condition: Collection is not initialized."); - $group = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId); - + $group = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId); $queryCount = $this->getCurrentQueryCount(); + $this->assertTrue($user->groups->contains($group)); - $this->assertEquals($queryCount+1, $this->getCurrentQueryCount(), "Checking for contains of managed entity should cause one query to be executed."); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Checking for contains of managed entity should cause one query to be executed."); $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); $group = new \Doctrine\Tests\Models\CMS\CmsGroup(); $group->name = "A New group!"; $queryCount = $this->getCurrentQueryCount(); + $this->assertFalse($user->groups->contains($group)); $this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Checking for contains of new entity should cause no query to be executed."); $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); @@ -275,8 +275,9 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->flush(); $queryCount = $this->getCurrentQueryCount(); + $this->assertFalse($user->groups->contains($group)); - $this->assertEquals($queryCount+1, $this->getCurrentQueryCount(), "Checking for contains of managed entity should cause one query to be executed."); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Checking for contains of managed entity should cause one query to be executed."); $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); } @@ -304,6 +305,107 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); } + /** + * + */ + public function testRemoveElementOneToMany() + { + $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); + $this->assertFalse($user->articles->isInitialized(), "Pre-Condition: Collection is not initialized."); + + $article = $this->_em->find('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId); + $queryCount = $this->getCurrentQueryCount(); + + $user->articles->removeElement($article); + + $this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized."); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + + $article = new \Doctrine\Tests\Models\CMS\CmsArticle(); + $article->topic = "Testnew"; + $article->text = "blub"; + + $queryCount = $this->getCurrentQueryCount(); + + $user->articles->removeElement($article); + + $this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Removing a new entity should cause no query to be executed."); + + $this->_em->persist($article); + $this->_em->flush(); + + $queryCount = $this->getCurrentQueryCount(); + + $user->articles->removeElement($article); + + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a managed entity should cause one query to be executed."); + $this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized."); + } + + /** + * + */ + public function testRemoveElementManyToMany() + { + $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); + $this->assertFalse($user->groups->isInitialized(), "Pre-Condition: Collection is not initialized."); + + $group = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId); + $queryCount = $this->getCurrentQueryCount(); + + $user->groups->removeElement($group); + + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a managed entity should cause one query to be executed."); + $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); + + $group = new \Doctrine\Tests\Models\CMS\CmsGroup(); + $group->name = "A New group!"; + + $queryCount = $this->getCurrentQueryCount(); + + $user->groups->removeElement($group); + + $this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Removing new entity should cause no query to be executed."); + $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); + + $this->_em->persist($group); + $this->_em->flush(); + + $queryCount = $this->getCurrentQueryCount(); + + $user->groups->removeElement($group); + + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a managed entity should cause one query to be executed."); + $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); + } + + /** + * + */ + public function testRemoveElementManyToManyInverse() + { + $group = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId); + $this->assertFalse($group->users->isInitialized(), "Pre-Condition: Collection is not initialized."); + + $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); + $queryCount = $this->getCurrentQueryCount(); + + $group->users->removeElement($user); + + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a managed entity should cause one query to be executed."); + $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); + + $newUser = new \Doctrine\Tests\Models\CMS\CmsUser(); + $newUser->name = "A New group!"; + + $queryCount = $this->getCurrentQueryCount(); + + $group->users->removeElement($newUser); + + $this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Removing a new entity should cause no query to be executed."); + $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); + } + /** * @group DDC-1399 */ From 5b73f1bd826858dcee43dacdd6bcdc8ee53f7639 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Wed, 30 Nov 2011 09:57:54 -0500 Subject: [PATCH 08/15] Improved code readability. Improved performance. --- .../ORM/Internal/CommitOrderCalculator.php | 12 +- .../ORM/Internal/Hydration/ObjectHydrator.php | 5 - lib/Doctrine/ORM/PersistentCollection.php | 14 +- lib/Doctrine/ORM/Query/Lexer.php | 96 ++--- lib/Doctrine/ORM/Query/Parser.php | 337 ++++++++++-------- 5 files changed, 250 insertions(+), 214 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php b/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php index 8997b1ea5..aad34e993 100644 --- a/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php +++ b/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php @@ -23,8 +23,9 @@ namespace Doctrine\ORM\Internal; * The CommitOrderCalculator is used by the UnitOfWork to sort out the * correct order in which changes to entities need to be persisted. * - * @since 2.0 - * @author Roman Borschel + * @since 2.0 + * @author Roman Borschel + * @author Guilherme Blanco */ class CommitOrderCalculator { @@ -60,10 +61,9 @@ class CommitOrderCalculator { // Check whether we need to do anything. 0 or 1 node is easy. $nodeCount = count($this->_classes); - if ($nodeCount == 0) { - return array(); - } else if ($nodeCount == 1) { - return array_values($this->_classes); + + if ($nodeCount <= 1) { + return ($nodeCount == 1) ? array_values($this->_classes) : array(); } // Init diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index ced83b126..36dbfa46a 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -36,11 +36,6 @@ use PDO, * @author Guilherme Blanco * * @internal Highly performance-sensitive code. - * - * @todo General behavior is "wrong" if you define an alias to selected IdentificationVariable. - * Example: SELECT u AS user FROM User u - * The result should contains an array where each array index is an array: array('user' => [User object]) - * Problem must be solved somehow by removing the isMixed in ResultSetMapping */ class ObjectHydrator extends AbstractHydrator { diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index 53bff7066..d8a53f2f1 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -164,7 +164,7 @@ final class PersistentCollection implements Collection // If _backRefFieldName is set and its a one-to-many association, // we need to set the back reference. - if ($this->backRefFieldName && $this->association['type'] == ClassMetadata::ONE_TO_MANY) { + if ($this->backRefFieldName && $this->association['type'] === ClassMetadata::ONE_TO_MANY) { // Set back reference to owner $this->typeClass->reflFields[$this->backRefFieldName]->setValue( $element, $this->owner @@ -189,7 +189,7 @@ final class PersistentCollection implements Collection // If _backRefFieldName is set, then the association is bidirectional // and we need to set the back reference. - if ($this->backRefFieldName && $this->association['type'] == ClassMetadata::ONE_TO_MANY) { + if ($this->backRefFieldName && $this->association['type'] === ClassMetadata::ONE_TO_MANY) { // Set back reference to owner $this->typeClass->reflFields[$this->backRefFieldName]->setValue( $element, $this->owner @@ -304,7 +304,7 @@ final class PersistentCollection implements Collection if ($this->association !== null && $this->association['isOwningSide'] && - $this->association['type'] == ClassMetadata::MANY_TO_MANY && + $this->association['type'] === ClassMetadata::MANY_TO_MANY && $this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) { $this->em->getUnitOfWork()->scheduleForDirtyCheck($this->owner); } @@ -425,7 +425,7 @@ final class PersistentCollection implements Collection $this->changed(); if ($this->association !== null && - $this->association['type'] == ClassMetadata::ONE_TO_MANY && + $this->association['type'] === ClassMetadata::ONE_TO_MANY && $this->association['orphanRemoval']) { $this->em->getUnitOfWork()->scheduleOrphanRemoval($element); } @@ -448,7 +448,7 @@ final class PersistentCollection implements Collection */ public function contains($element) { - if ( ! $this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { + if ( ! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association); return $this->coll->contains($element) || $persister->contains($this, $element); @@ -514,7 +514,7 @@ final class PersistentCollection implements Collection */ public function count() { - if ( ! $this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { + if ( ! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association); return $persister->count($this) + ($this->isDirty ? $this->coll->count() : 0); @@ -630,7 +630,7 @@ final class PersistentCollection implements Collection $uow = $this->em->getUnitOfWork(); - if ($this->association['type'] == ClassMetadata::ONE_TO_MANY && $this->association['orphanRemoval']) { + if ($this->association['type'] === ClassMetadata::ONE_TO_MANY && $this->association['orphanRemoval']) { // we need to initialize here, as orphan removal acts like implicit cascadeRemove, // hence for event listeners we need the objects in memory. $this->initialize(); diff --git a/lib/Doctrine/ORM/Query/Lexer.php b/lib/Doctrine/ORM/Query/Lexer.php index 00fcffd8c..1eef6afeb 100644 --- a/lib/Doctrine/ORM/Query/Lexer.php +++ b/lib/Doctrine/ORM/Query/Lexer.php @@ -49,7 +49,7 @@ class Lexer extends \Doctrine\Common\Lexer const T_PLUS = 17; const T_OPEN_CURLY_BRACE = 18; const T_CLOSE_CURLY_BRACE = 19; - + // All tokens that are also identifiers should be >= 100 const T_IDENTIFIER = 100; const T_ALL = 101; @@ -133,7 +133,7 @@ class Lexer extends \Doctrine\Common\Lexer '\?[0-9]*|:[a-z]{1}[a-z0-9_]{0,}' ); } - + /** * @inheritdoc */ @@ -149,50 +149,58 @@ class Lexer extends \Doctrine\Common\Lexer { $type = self::T_NONE; - // Recognizing numeric values - if (is_numeric($value)) { - return (strpos($value, '.') !== false || stripos($value, 'e') !== false) - ? self::T_FLOAT : self::T_INTEGER; - } - - // Differentiate between quoted names, identifiers, input parameters and symbols - if ($value[0] === "'") { - $value = str_replace("''", "'", substr($value, 1, strlen($value) - 2)); - return self::T_STRING; - } else if (ctype_alpha($value[0]) || $value[0] === '_') { - $name = 'Doctrine\ORM\Query\Lexer::T_' . strtoupper($value); - - if (defined($name)) { - $type = constant($name); - - if ($type > 100) { - return $type; + switch (true) { + // Recognize numeric values + case (is_numeric($value)): + if (strpos($value, '.') !== false || stripos($value, 'e') !== false) { + return self::T_FLOAT; } - } - return self::T_IDENTIFIER; - } else if ($value[0] === '?' || $value[0] === ':') { - return self::T_INPUT_PARAMETER; - } else { - switch ($value) { - case '.': return self::T_DOT; - case ',': return self::T_COMMA; - case '(': return self::T_OPEN_PARENTHESIS; - case ')': return self::T_CLOSE_PARENTHESIS; - case '=': return self::T_EQUALS; - case '>': return self::T_GREATER_THAN; - case '<': return self::T_LOWER_THAN; - case '+': return self::T_PLUS; - case '-': return self::T_MINUS; - case '*': return self::T_MULTIPLY; - case '/': return self::T_DIVIDE; - case '!': return self::T_NEGATE; - case '{': return self::T_OPEN_CURLY_BRACE; - case '}': return self::T_CLOSE_CURLY_BRACE; - default: - // Do nothing - break; - } + return self::T_INTEGER; + + // Recognize quoted strings + case ($value[0] === "'"): + $value = str_replace("''", "'", substr($value, 1, strlen($value) - 2)); + + return self::T_STRING; + + // Recognize identifiers + case (ctype_alpha($value[0]) || $value[0] === '_'): + $name = 'Doctrine\ORM\Query\Lexer::T_' . strtoupper($value); + + if (defined($name)) { + $type = constant($name); + + if ($type > 100) { + return $type; + } + } + + return self::T_IDENTIFIER; + + // Recognize input parameters + case ($value[0] === '?' || $value[0] === ':'): + return self::T_INPUT_PARAMETER; + + // Recognize symbols + case ($value === '.'): return self::T_DOT; + case ($value === ','): return self::T_COMMA; + case ($value === '('): return self::T_OPEN_PARENTHESIS; + case ($value === ')'): return self::T_CLOSE_PARENTHESIS; + case ($value === '='): return self::T_EQUALS; + case ($value === '>'): return self::T_GREATER_THAN; + case ($value === '<'): return self::T_LOWER_THAN; + case ($value === '+'): return self::T_PLUS; + case ($value === '-'): return self::T_MINUS; + case ($value === '*'): return self::T_MULTIPLY; + case ($value === '/'): return self::T_DIVIDE; + case ($value === '!'): return self::T_NEGATE; + case ($value === '{'): return self::T_OPEN_CURLY_BRACE; + case ($value === '}'): return self::T_CLOSE_CURLY_BRACE; + + // Default + default: + // Do nothing } return $type; diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 1a34963f2..a3725eb8d 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -677,13 +677,10 @@ class Parser } // Build the error message - $semanticalError = 'Invalid PathExpression. '; - - if (count($expectedStringTypes) == 1) { - $semanticalError .= 'Must be a ' . $expectedStringTypes[0] . '.'; - } else { - $semanticalError .= implode(' or ', $expectedStringTypes) . ' expected.'; - } + $semanticalError = 'Invalid PathExpression. '; + $semanticalError .= (count($expectedStringTypes) == 1) + ? 'Must be a ' . $expectedStringTypes[0] . '.' + : implode(' or ', $expectedStringTypes) . ' expected.'; $this->semanticalError($semanticalError, $deferredItem['token']); } @@ -1866,53 +1863,78 @@ class Parser $expression = null; $identVariable = null; $peek = $this->_lexer->glimpse(); + $lookaheadType = $this->_lexer->lookahead['type']; - if ($this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT) { + switch (true) { // ScalarExpression (u.name) - $expression = $this->ScalarExpression(); - } else if ($this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS) { - // IdentificationVariable (u) - $expression = $identVariable = $this->IdentificationVariable(); - } else if (in_array($this->_lexer->lookahead['type'], array(Lexer::T_CASE, Lexer::T_COALESCE, Lexer::T_NULLIF))) { - // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...)) - $expression = $this->CaseExpression(); - } else if ($this->_isFunction()) { - // DQL Function (SUM(u.value) or SUM(u.value) + 1) - $this->_lexer->peek(); // "(" - - $lookaheadType = $this->_lexer->lookahead['type']; - $beyond = $this->_peekBeyondClosingParenthesis(); - - if ($this->_isMathOperator($beyond)) { - // SUM(u.id) + COUNT(u.id) + case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT): $expression = $this->ScalarExpression(); - } else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { - // COUNT(u.id) - $expression = $this->AggregateExpression(); - } else { - // SUM(u.id) - $expression = $this->FunctionDeclaration(); - } - } else if ($this->_lexer->lookahead['type'] === Lexer::T_PARTIAL) { + break; + + // IdentificationVariable (u) + case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS): + $expression = $identVariable = $this->IdentificationVariable(); + break; + + // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...)) + case ($lookaheadType === Lexer::T_CASE): + case ($lookaheadType === Lexer::T_COALESCE): + case ($lookaheadType === Lexer::T_NULLIF): + $expression = $this->CaseExpression(); + break; + + // DQL Function (SUM(u.value) or SUM(u.value) + 1) + case ($this->_isFunction()): + $this->_lexer->peek(); // "(" + + switch (true) { + case ($this->_isMathOperator($this->_peekBeyondClosingParenthesis())): + // SUM(u.id) + COUNT(u.id) + $expression = $this->ScalarExpression(); + break; + + case ($this->_isAggregateFunction($lookaheadType)): + // COUNT(u.id) + $expression = $this->AggregateExpression(); + break; + + default: + // IDENTITY(u) + $expression = $this->FunctionDeclaration(); + break; + } + + break; + // PartialObjectExpression (PARTIAL u.{id, name}) - $expression = $this->PartialObjectExpression(); - $identVariable = $expression->identificationVariable; - } else if ($this->_lexer->lookahead['type'] === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT) { + case ($lookaheadType === Lexer::T_PARTIAL): + $expression = $this->PartialObjectExpression(); + $identVariable = $expression->identificationVariable; + break; + // Subselect - $this->match(Lexer::T_OPEN_PARENTHESIS); - $expression = $this->Subselect(); - $this->match(Lexer::T_CLOSE_PARENTHESIS); - } else if (in_array($this->_lexer->lookahead['type'], array(Lexer::T_OPEN_PARENTHESIS, Lexer::T_INTEGER, Lexer::T_FLOAT, Lexer::T_STRING))) { + case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT): + $this->match(Lexer::T_OPEN_PARENTHESIS); + $expression = $this->Subselect(); + $this->match(Lexer::T_CLOSE_PARENTHESIS); + break; + // Shortcut: ScalarExpression => SimpleArithmeticExpression - $expression = $this->SimpleArithmeticExpression(); - } else if (in_array($this->_lexer->lookahead['type'], array(Lexer::T_PLUS, Lexer::T_MINUS))) { - // SimpleArithmeticExpression : (- u.value ) or ( + u.value ) - $expression = $this->SimpleArithmeticExpression(); - } else { - $this->syntaxError( - 'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression', - $this->_lexer->lookahead - ); + case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS): + case ($lookaheadType === Lexer::T_INTEGER): + case ($lookaheadType === Lexer::T_STRING): + case ($lookaheadType === Lexer::T_FLOAT): + // SimpleArithmeticExpression : (- u.value ) or ( + u.value ) + case ($lookaheadType === Lexer::T_MINUS): + case ($lookaheadType === Lexer::T_PLUS): + $expression = $this->SimpleArithmeticExpression(); + break; + + default: + $this->syntaxError( + 'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression', + $this->_lexer->lookahead + ); } // [["AS"] ["HIDDEN"] AliasResultVariable] @@ -1965,25 +1987,41 @@ class Parser { $peek = $this->_lexer->glimpse(); - if ($peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) { - // SingleValuedPathExpression | IdentificationVariable - $expression = ($peek['value'] == '.') - ? $this->StateFieldPathExpression() - : $this->IdentificationVariable(); + switch ($this->_lexer->lookahead['type']) { + case Lexer::T_IDENTIFIER: + switch (true) { + case ($peek['type'] === Lexer::T_DOT): + $expression = $this->StateFieldPathExpression(); + + return new AST\SimpleSelectExpression($expression); + + case ($peek['type'] !== Lexer::T_OPEN_PARENTHESIS): + $expression = $this->IdentificationVariable(); + + return new AST\SimpleSelectExpression($expression); + + default: + // Do nothing + } + break; + + case Lexer::T_OPEN_PARENTHESIS: + if ($peek['type'] !== Lexer::T_SELECT) { + // Shortcut: ScalarExpression => SimpleArithmeticExpression + $expression = $this->SimpleArithmeticExpression(); + + return new AST\SimpleSelectExpression($expression); + } - return new AST\SimpleSelectExpression($expression); - } else if ($this->_lexer->lookahead['value'] == '(') { - if ($peek['type'] == Lexer::T_SELECT) { // Subselect $this->match(Lexer::T_OPEN_PARENTHESIS); $expression = $this->Subselect(); $this->match(Lexer::T_CLOSE_PARENTHESIS); - } else { - // Shortcut: ScalarExpression => SimpleArithmeticExpression - $expression = $this->SimpleArithmeticExpression(); - } - return new AST\SimpleSelectExpression($expression); + return new AST\SimpleSelectExpression($expression); + + default: + // Do nothing } $this->_lexer->peek(); @@ -2099,27 +2137,26 @@ class Parser { $condPrimary = new AST\ConditionalPrimary; - if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { - // Peek beyond the matching closing paranthesis ')' - $peek = $this->_peekBeyondClosingParenthesis(); - - if (in_array($peek['value'], array("=", "<", "<=", "<>", ">", ">=", "!=")) || - $peek['type'] === Lexer::T_NOT || - $peek['type'] === Lexer::T_BETWEEN || - $peek['type'] === Lexer::T_LIKE || - $peek['type'] === Lexer::T_IN || - $peek['type'] === Lexer::T_IS || - $peek['type'] === Lexer::T_EXISTS) { - $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression(); - } else { - $this->match(Lexer::T_OPEN_PARENTHESIS); - $condPrimary->conditionalExpression = $this->ConditionalExpression(); - $this->match(Lexer::T_CLOSE_PARENTHESIS); - } - } else { + if ( ! $this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression(); + + return $condPrimary; } + // Peek beyond the matching closing paranthesis ')' + $peek = $this->_peekBeyondClosingParenthesis(); + + if (in_array($peek['value'], array("=", "<", "<=", "<>", ">", ">=", "!=")) || + in_array($peek['type'], array(Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS))) { + $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression(); + + return $condPrimary; + } + + $this->match(Lexer::T_OPEN_PARENTHESIS); + $condPrimary->conditionalExpression = $this->ConditionalExpression(); + $this->match(Lexer::T_CLOSE_PARENTHESIS); + return $condPrimary; } @@ -2132,10 +2169,10 @@ class Parser */ public function SimpleConditionalExpression() { + $token = $this->_lexer->lookahead; + if ($this->_lexer->isNextToken(Lexer::T_NOT)) { $token = $this->_lexer->glimpse(); - } else { - $token = $this->_lexer->lookahead; } if ($token['type'] === Lexer::T_EXISTS) { @@ -2464,9 +2501,9 @@ class Parser } return $this->FunctionDeclaration(); - } else { - return $this->Literal(); } + + return $this->Literal(); } } @@ -2498,30 +2535,46 @@ class Parser */ public function StringPrimary() { - if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) { - $peek = $this->_lexer->glimpse(); + $lookaheadType = $this->_lexer->lookahead['type']; + + switch ($lookaheadType) { + case Lexer::T_IDENTIFIER: + $peek = $this->_lexer->glimpse(); + + if ($peek['value'] == '.') { + return $this->StateFieldPathExpression(); + } + + if ($peek['value'] == '(') { + // do NOT directly go to FunctionsReturningString() because it doesnt check for custom functions. + return $this->FunctionDeclaration(); + } - if ($peek['value'] == '.') { - return $this->StateFieldPathExpression(); - } else if ($peek['value'] == '(') { - // do NOT directly go to FunctionsReturningString() because it doesnt check for custom functions. - return $this->FunctionDeclaration(); - } else { $this->syntaxError("'.' or '('"); - } - } else if ($this->_lexer->isNextToken(Lexer::T_STRING)) { - $this->match(Lexer::T_STRING); + break; - return $this->_lexer->token['value']; - } else if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { - return $this->InputParameter(); - } else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) { - return $this->AggregateExpression(); - } else if (in_array($this->_lexer->lookahead['type'], array(Lexer::T_CASE, Lexer::T_COALESCE, Lexer::T_NULLIF))) { - return $this->CaseExpression(); + case Lexer::T_STRING: + $this->match(Lexer::T_STRING); + + return $this->_lexer->token['value']; + + case Lexer::T_INPUT_PARAMETER: + return $this->InputParameter(); + + case Lexer::T_CASE: + case Lexer::T_COALESCE: + case Lexer::T_NULLIF: + return $this->CaseExpression(); + + default: + if ($this->_isAggregateFunction($lookaheadType)) { + return $this->AggregateExpression(); + } } - $this->syntaxError('StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression'); + $this->syntaxError( + 'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression' + ); } /** @@ -2564,40 +2617,28 @@ class Parser */ public function AggregateExpression() { + $lookaheadType = $this->_lexer->lookahead['type']; $isDistinct = false; - $functionName = ''; - if ($this->_lexer->isNextToken(Lexer::T_COUNT)) { - $this->match(Lexer::T_COUNT); - $functionName = $this->_lexer->token['value']; - $this->match(Lexer::T_OPEN_PARENTHESIS); - - if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) { - $this->match(Lexer::T_DISTINCT); - $isDistinct = true; - } - - $pathExp = $this->SingleValuedPathExpression(); - $this->match(Lexer::T_CLOSE_PARENTHESIS); - } else { - if ($this->_lexer->isNextToken(Lexer::T_AVG)) { - $this->match(Lexer::T_AVG); - } else if ($this->_lexer->isNextToken(Lexer::T_MAX)) { - $this->match(Lexer::T_MAX); - } else if ($this->_lexer->isNextToken(Lexer::T_MIN)) { - $this->match(Lexer::T_MIN); - } else if ($this->_lexer->isNextToken(Lexer::T_SUM)) { - $this->match(Lexer::T_SUM); - } else { - $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT'); - } - - $functionName = $this->_lexer->token['value']; - $this->match(Lexer::T_OPEN_PARENTHESIS); - $pathExp = $this->SimpleArithmeticExpression(); - $this->match(Lexer::T_CLOSE_PARENTHESIS); + if ( ! in_array($lookaheadType, array(Lexer::T_COUNT, Lexer::T_AVG, Lexer::T_MAX, Lexer::T_MIN, Lexer::T_SUM))) { + $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT'); } + $this->match($lookaheadType); + $functionName = $this->_lexer->token['value']; + $this->match(Lexer::T_OPEN_PARENTHESIS); + + if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) { + $this->match(Lexer::T_DISTINCT); + $isDistinct = true; + } + + $pathExp = ($lookaheadType === Lexer::T_COUNT) + ? $this->SingleValuedPathExpression() + : $this->SimpleArithmeticExpression(); + + $this->match(Lexer::T_CLOSE_PARENTHESIS); + return new AST\AggregateExpression($functionName, $pathExp, $isDistinct); } @@ -2608,24 +2649,19 @@ class Parser */ public function QuantifiedExpression() { - $type = ''; + $lookaheadType = $this->_lexer->lookahead['type']; + $value = $this->_lexer->lookahead['value']; - if ($this->_lexer->isNextToken(Lexer::T_ALL)) { - $this->match(Lexer::T_ALL); - $type = 'ALL'; - } else if ($this->_lexer->isNextToken(Lexer::T_ANY)) { - $this->match(Lexer::T_ANY); - $type = 'ANY'; - } else if ($this->_lexer->isNextToken(Lexer::T_SOME)) { - $this->match(Lexer::T_SOME); - $type = 'SOME'; - } else { + if ( ! in_array($lookaheadType, array(Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME))) { $this->syntaxError('ALL, ANY or SOME'); } + $this->match($lookaheadType); $this->match(Lexer::T_OPEN_PARENTHESIS); + $qExpr = new AST\QuantifiedExpression($this->Subselect()); - $qExpr->type = $type; + $qExpr->type = $value; + $this->match(Lexer::T_CLOSE_PARENTHESIS); return $qExpr; @@ -2666,14 +2702,11 @@ class Parser { $peek = $this->_lexer->glimpse(); - $leftExpr = $this->ArithmeticExpression(); - $operator = $this->ComparisonOperator(); - - if ($this->_isNextAllAnySome()) { - $rightExpr = $this->QuantifiedExpression(); - } else { - $rightExpr = $this->ArithmeticExpression(); - } + $leftExpr = $this->ArithmeticExpression(); + $operator = $this->ComparisonOperator(); + $rightExpr = ($this->_isNextAllAnySome()) + ? $this->QuantifiedExpression() + : $this->ArithmeticExpression(); return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr); } From 5e3e8b39571491ece27d755d625fce100ab6ca39 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Thu, 1 Dec 2011 10:00:26 -0500 Subject: [PATCH 09/15] More refactorings and optimizations. --- UPGRADE_TO_2_2 | 38 ++++- lib/Doctrine/ORM/AbstractQuery.php | 110 ++++++++------- lib/Doctrine/ORM/EntityManager.php | 111 +++++++++------ lib/Doctrine/ORM/EntityRepository.php | 73 +++++----- lib/Doctrine/ORM/Mapping/ClassMetadata.php | 60 ++++---- lib/Doctrine/ORM/NativeQuery.php | 23 +-- lib/Doctrine/ORM/Query.php | 59 +++++--- lib/Doctrine/ORM/QueryBuilder.php | 31 ++-- lib/Doctrine/ORM/UnitOfWork.php | 156 +++++++++++---------- 9 files changed, 388 insertions(+), 273 deletions(-) diff --git a/UPGRADE_TO_2_2 b/UPGRADE_TO_2_2 index 757413029..5b01fab04 100644 --- a/UPGRADE_TO_2_2 +++ b/UPGRADE_TO_2_2 @@ -1,6 +1,6 @@ # ResultCache implementation rewritten -The result cache is completly rewritten and now works on the database result level, not inside the ORM AbstractQuery +The result cache is completely rewritten and now works on the database result level, not inside the ORM AbstractQuery anymore. This means that for result cached queries the hydration will now always be performed again, regardless of the hydration mode. Affected areas are: @@ -12,20 +12,24 @@ The API is backwards compatible however most of the getter methods on the `Abstr deprecated in favor of calling AbstractQuery#getQueryCacheProfile(). This method returns a `Doctrine\DBAL\Cache\QueryCacheProfile` instance with access to result cache driver, lifetime and cache key. + # EntityManager#getPartialReference() creates read-only entity Entities returned from EntityManager#getPartialReference() are now marked as read-only if they haven't been in the identity map before. This means objects of this kind never lead to changes in the UnitOfWork. + # Fields omitted in a partial DQL query or a native query are never updated Fields of an entity that are not returned from a partial DQL Query or native SQL query will never be updated through an UPDATE statement. + # Removed support for onUpdate in @JoinColumn -The onUpdate foreign key handling makes absolutly no sense in an ORM. Additionally Oracle doesn't even support it. Support for it is removed. +The onUpdate foreign key handling makes absolutely no sense in an ORM. Additionally Oracle doesn't even support it. Support for it is removed. + # Changes in Annotation Handling @@ -41,4 +45,32 @@ from 2.0 have to configure the annotation driver if they don't use `Configuratio $driver = new AnnotationDriver($reader, (array)$paths); - $config->setMetadataDriverImpl($driver); \ No newline at end of file + $config->setMetadataDriverImpl($driver); + + +# Scalar mappings can now be ommitted from DQL result + +You are now allowed to mark scalar SELECT expressions as HIDDEN an they are not hydrated anymore. +Example: + +SELECT u, SUM(a.id) AS HIDDEN numArticles FROM User u LEFT JOIN u.Articles a ORDER BY numArticles DESC HAVING numArticles > 10 + +Your result will be a collection of Users, and not an array with key 0 as User object instance and "numArticles" as the number of articles per user + + +# Map entities as scalars in DQL result + +When hydrating to array or even a mixed result in object hydrator, previously you had the 0 index holding you entity instance. +You are now allowed to alias this, providing more flexibility for you code. +Example: + +SELECT u AS user FROM User u + +Will now return a collection of arrays with index "user" pointing to the User object instance. + + +# Performance optimizations + +Thousands of lines were completely reviewed and optimized for best performance. +Removed redundancy and improved code readability made now internal Doctrine code easier to understand. +Also, Doctrine 2.2 now is around 10-15% faster than 2.1. \ No newline at end of file diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 72eab194b..9a812f446 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -144,7 +144,7 @@ abstract class AbstractQuery { return $this->_params; } - + /** * Get all defined parameter types. * @@ -163,7 +163,11 @@ abstract class AbstractQuery */ public function getParameter($key) { - return isset($this->_params[$key]) ? $this->_params[$key] : null; + if (isset($this->_params[$key])) { + return $this->_params[$key]; + } + + return null; } /** @@ -174,7 +178,11 @@ abstract class AbstractQuery */ public function getParameterType($key) { - return isset($this->_paramTypes[$key]) ? $this->_paramTypes[$key] : null; + if (isset($this->_paramTypes[$key])) { + return $this->_paramTypes[$key]; + } + + return null; } /** @@ -199,14 +207,14 @@ abstract class AbstractQuery public function setParameter($key, $value, $type = null) { $key = trim($key, ':'); - + if ($type === null) { $type = Query\ParameterTypeInferer::inferType($value); } - + $this->_paramTypes[$key] = $type; $this->_params[$key] = $value; - + return $this; } @@ -220,12 +228,9 @@ abstract class AbstractQuery public function setParameters(array $params, array $types = array()) { foreach ($params as $key => $value) { - if (isset($types[$key])) { - $this->setParameter($key, $value, $types[$key]); - } else { - $this->setParameter($key, $value); - } + $this->setParameter($key, $value, isset($types[$key]) ? $types[$key] : null); } + return $this; } @@ -238,6 +243,7 @@ abstract class AbstractQuery public function setResultSetMapping(Query\ResultSetMapping $rsm) { $this->_resultSetMapping = $rsm; + return $this; } @@ -252,11 +258,11 @@ abstract class AbstractQuery if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) { throw ORMException::invalidResultCacheDriver(); } - if ($this->_queryCacheProfile) { - $this->_queryCacheProfile = $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver); - } else { - $this->_queryCacheProfile = new QueryCacheProfile(0, null, $resultCacheDriver); - } + + $this->_queryCacheProfile = $this->_queryCacheProfile + ? $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver) + : new QueryCacheProfile(0, null, $resultCacheDriver); + return $this; } @@ -270,9 +276,9 @@ abstract class AbstractQuery { if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) { return $this->_queryCacheProfile->getResultCacheDriver(); - } else { - return $this->_em->getConfiguration()->getResultCacheImpl(); } + + return $this->_em->getConfiguration()->getResultCacheImpl(); } /** @@ -289,9 +295,12 @@ abstract class AbstractQuery if ($bool) { $this->setResultCacheLifetime($lifetime); $this->setResultCacheId($resultCacheId); - } else { - $this->_queryCacheProfile = null; + + return $this; } + + $this->_queryCacheProfile = null; + return $this; } @@ -303,16 +312,12 @@ abstract class AbstractQuery */ public function setResultCacheLifetime($lifetime) { - if ($lifetime === null) { - $lifetime = 0; - } else { - $lifetime = (int)$lifetime; - } - if ($this->_queryCacheProfile) { - $this->_queryCacheProfile = $this->_queryCacheProfile->setLifetime($lifetime); - } else { - $this->_queryCacheProfile = new QueryCacheProfile($lifetime); - } + $lifetime = ($lifetime !== null) ? (int) $lifetime : 0; + + $this->_queryCacheProfile = $this->_queryCacheProfile + ? $this->_queryCacheProfile->setLifetime($lifetime) + : new QueryCacheProfile($lifetime); + return $this; } @@ -336,6 +341,7 @@ abstract class AbstractQuery public function expireResultCache($expire = true) { $this->_expireResultCache = $expire; + return $this; } @@ -374,6 +380,7 @@ abstract class AbstractQuery } $this->_hints['fetchMode'][$class][$assocName] = $fetchMode; + return $this; } @@ -387,6 +394,7 @@ abstract class AbstractQuery public function setHydrationMode($hydrationMode) { $this->_hydrationMode = $hydrationMode; + return $this; } @@ -451,14 +459,15 @@ abstract class AbstractQuery return null; } - if (is_array($result)) { - if (count($result) > 1) { - throw new NonUniqueResultException; - } - return array_shift($result); + if ( ! is_array($result)) { + return $result; } - return $result; + if (count($result) > 1) { + throw new NonUniqueResultException; + } + + return array_shift($result); } /** @@ -482,14 +491,15 @@ abstract class AbstractQuery throw new NoResultException; } - if (is_array($result)) { - if (count($result) > 1) { - throw new NonUniqueResultException; - } - return array_shift($result); + if ( ! is_array($result)) { + return $result; } - return $result; + if (count($result) > 1) { + throw new NonUniqueResultException; + } + + return array_shift($result); } /** @@ -515,6 +525,7 @@ abstract class AbstractQuery public function setHint($name, $value) { $this->_hints[$name] = $value; + return $this; } @@ -531,7 +542,7 @@ abstract class AbstractQuery /** * Return the key value map of query hints that are currently set. - * + * * @return array */ public function getHints() @@ -588,8 +599,8 @@ abstract class AbstractQuery } return $this->_em->getHydrator($this->_hydrationMode)->hydrateAll( - $stmt, $this->_resultSetMapping, $this->_hints - ); + $stmt, $this->_resultSetMapping, $this->_hints + ); } /** @@ -602,11 +613,10 @@ abstract class AbstractQuery */ public function setResultCacheId($id) { - if ($this->_queryCacheProfile) { - $this->_queryCacheProfile = $this->_queryCacheProfile->setCacheKey($id); - } else { - $this->_queryCacheProfile = new QueryCacheProfile(0, $id); - } + $this->_queryCacheProfile = $this->_queryCacheProfile + ? $this->_queryCacheProfile->setCacheKey($id) + : new QueryCacheProfile(0, $id); + return $this; } diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 8bb7a5896..f09bacb89 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -130,10 +130,12 @@ class EntityManager implements ObjectManager $this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl()); $this->unitOfWork = new UnitOfWork($this); - $this->proxyFactory = new ProxyFactory($this, - $config->getProxyDir(), - $config->getProxyNamespace(), - $config->getAutoGenerateProxyClasses()); + $this->proxyFactory = new ProxyFactory( + $this, + $config->getProxyDir(), + $config->getProxyNamespace(), + $config->getAutoGenerateProxyClasses() + ); } /** @@ -175,6 +177,7 @@ class EntityManager implements ObjectManager if ($this->expressionBuilder === null) { $this->expressionBuilder = new Query\Expr; } + return $this->expressionBuilder; } @@ -267,9 +270,11 @@ class EntityManager implements ObjectManager public function createQuery($dql = "") { $query = new Query($this); + if ( ! empty($dql)) { $query->setDql($dql); } + return $query; } @@ -296,6 +301,7 @@ class EntityManager implements ObjectManager $query = new NativeQuery($this); $query->setSql($sql); $query->setResultSetMapping($rsm); + return $query; } @@ -308,6 +314,7 @@ class EntityManager implements ObjectManager public function createNamedNativeQuery($name) { list($sql, $rsm) = $this->config->getNamedNativeQuery($name); + return $this->createNativeQuery($sql, $rsm); } @@ -336,6 +343,7 @@ class EntityManager implements ObjectManager public function flush($entity = null) { $this->errorIfClosed(); + $this->unitOfWork->commit($entity); } @@ -371,16 +379,18 @@ class EntityManager implements ObjectManager if ($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) { return ($entity instanceof $class->name) ? $entity : null; } + if ($class->subClasses) { - $entity = $this->find($entityName, $identifier); - } else { - if ( ! is_array($identifier)) { - $identifier = array($class->identifier[0] => $identifier); - } - $entity = $this->proxyFactory->getProxy($class->name, $identifier); - $this->unitOfWork->registerManaged($entity, $identifier, array()); + return $this->find($entityName, $identifier); } + if ( ! is_array($identifier)) { + $identifier = array($class->identifier[0] => $identifier); + } + + $entity = $this->proxyFactory->getProxy($class->name, $identifier); + $this->unitOfWork->registerManaged($entity, $identifier, array()); + return $entity; } @@ -411,6 +421,7 @@ class EntityManager implements ObjectManager if ($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) { return ($entity instanceof $class->name) ? $entity : null; } + if ( ! is_array($identifier)) { $identifier = array($class->identifier[0] => $identifier); } @@ -461,7 +472,9 @@ class EntityManager implements ObjectManager if ( ! is_object($entity)) { throw new \InvalidArgumentException(gettype($entity)); } + $this->errorIfClosed(); + $this->unitOfWork->persist($entity); } @@ -478,7 +491,9 @@ class EntityManager implements ObjectManager if ( ! is_object($entity)) { throw new \InvalidArgumentException(gettype($entity)); } + $this->errorIfClosed(); + $this->unitOfWork->remove($entity); } @@ -493,7 +508,9 @@ class EntityManager implements ObjectManager if ( ! is_object($entity)) { throw new \InvalidArgumentException(gettype($entity)); } + $this->errorIfClosed(); + $this->unitOfWork->refresh($entity); } @@ -511,6 +528,7 @@ class EntityManager implements ObjectManager if ( ! is_object($entity)) { throw new \InvalidArgumentException(gettype($entity)); } + $this->unitOfWork->detach($entity); } @@ -527,7 +545,9 @@ class EntityManager implements ObjectManager if ( ! is_object($entity)) { throw new \InvalidArgumentException(gettype($entity)); } + $this->errorIfClosed(); + return $this->unitOfWork->merge($entity); } @@ -567,20 +587,20 @@ class EntityManager implements ObjectManager public function getRepository($entityName) { $entityName = ltrim($entityName, '\\'); + if (isset($this->repositories[$entityName])) { return $this->repositories[$entityName]; } $metadata = $this->getClassMetadata($entityName); - $customRepositoryClassName = $metadata->customRepositoryClassName; + $repositoryClassName = $metadata->customRepositoryClassName; - if ($customRepositoryClassName !== null) { - $repository = new $customRepositoryClassName($this, $metadata); - } else { - $repositoryClass = $this->config->getDefaultRepositoryClassName(); - $repository = new $repositoryClass($this, $metadata); + if ($repositoryClassName === null) { + $repositoryClassName = $this->config->getDefaultRepositoryClassName(); } + $repository = new $repositoryClassName($this, $metadata); + $this->repositories[$entityName] = $repository; return $repository; @@ -594,9 +614,9 @@ class EntityManager implements ObjectManager */ public function contains($entity) { - return $this->unitOfWork->isScheduledForInsert($entity) || - $this->unitOfWork->isInIdentityMap($entity) && - ! $this->unitOfWork->isScheduledForDelete($entity); + return $this->unitOfWork->isScheduledForInsert($entity) + || $this->unitOfWork->isInIdentityMap($entity) + && ! $this->unitOfWork->isScheduledForDelete($entity); } /** @@ -679,29 +699,27 @@ class EntityManager implements ObjectManager { switch ($hydrationMode) { case Query::HYDRATE_OBJECT: - $hydrator = new Internal\Hydration\ObjectHydrator($this); - break; + return new Internal\Hydration\ObjectHydrator($this); + case Query::HYDRATE_ARRAY: - $hydrator = new Internal\Hydration\ArrayHydrator($this); - break; + return new Internal\Hydration\ArrayHydrator($this); + case Query::HYDRATE_SCALAR: - $hydrator = new Internal\Hydration\ScalarHydrator($this); - break; + return new Internal\Hydration\ScalarHydrator($this); + case Query::HYDRATE_SINGLE_SCALAR: - $hydrator = new Internal\Hydration\SingleScalarHydrator($this); - break; + return new Internal\Hydration\SingleScalarHydrator($this); + case Query::HYDRATE_SIMPLEOBJECT: - $hydrator = new Internal\Hydration\SimpleObjectHydrator($this); - break; + return new Internal\Hydration\SimpleObjectHydrator($this); + default: if ($class = $this->config->getCustomHydrationMode($hydrationMode)) { - $hydrator = new $class($this); - break; + return new $class($this); } - throw ORMException::invalidHydrationMode($hydrationMode); } - return $hydrator; + throw ORMException::invalidHydrationMode($hydrationMode); } /** @@ -737,18 +755,25 @@ class EntityManager implements ObjectManager */ public static function create($conn, Configuration $config, EventManager $eventManager = null) { - if (!$config->getMetadataDriverImpl()) { + if ( ! $config->getMetadataDriverImpl()) { throw ORMException::missingMappingDriverImpl(); } - if (is_array($conn)) { - $conn = \Doctrine\DBAL\DriverManager::getConnection($conn, $config, ($eventManager ?: new EventManager())); - } else if ($conn instanceof Connection) { - if ($eventManager !== null && $conn->getEventManager() !== $eventManager) { - throw ORMException::mismatchedEventManager(); - } - } else { - throw new \InvalidArgumentException("Invalid argument: " . $conn); + switch (true) { + case (is_array($conn)): + $conn = \Doctrine\DBAL\DriverManager::getConnection( + $conn, $config, ($eventManager ?: new EventManager()) + ); + break; + + case ($conn instanceof Connection): + if ($eventManager !== null && $conn->getEventManager() !== $eventManager) { + throw ORMException::mismatchedEventManager(); + } + break; + + default: + throw new \InvalidArgumentException("Invalid argument: " . $conn); } return new EntityManager($conn, $config, $conn->getEventManager()); diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php index a4c239001..f74448141 100644 --- a/lib/Doctrine/ORM/EntityRepository.php +++ b/lib/Doctrine/ORM/EntityRepository.php @@ -109,11 +109,11 @@ class EntityRepository implements ObjectRepository { // Check identity map first if ($entity = $this->_em->getUnitOfWork()->tryGetById($id, $this->_class->rootEntityName)) { - if (!($entity instanceof $this->_class->name)) { + if ( ! ($entity instanceof $this->_class->name)) { return null; } - - if ($lockMode != LockMode::NONE) { + + if ($lockMode !== LockMode::NONE) { $this->_em->lock($entity, $lockMode, $lockVersion); } @@ -126,23 +126,27 @@ class EntityRepository implements ObjectRepository $id = array_combine($this->_class->identifier, $value); } - if ($lockMode == LockMode::NONE) { - return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id); - } else if ($lockMode == LockMode::OPTIMISTIC) { - if (!$this->_class->isVersioned) { - throw OptimisticLockException::notVersioned($this->_entityName); - } - $entity = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id); + switch ($lockMode) { + case LockMode::NONE: + return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id); - $this->_em->getUnitOfWork()->lock($entity, $lockMode, $lockVersion); + case LockMode::OPTIMISTIC: + if ( ! $this->_class->isVersioned) { + throw OptimisticLockException::notVersioned($this->_entityName); + } - return $entity; - } else { - if (!$this->_em->getConnection()->isTransactionActive()) { - throw TransactionRequiredException::transactionRequired(); - } - - return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id, null, null, array(), $lockMode); + $entity = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id); + + $this->_em->getUnitOfWork()->lock($entity, $lockMode, $lockVersion); + + return $entity; + + default: + if ( ! $this->_em->getConnection()->isTransactionActive()) { + throw TransactionRequiredException::transactionRequired(); + } + + return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id, null, null, array(), $lockMode); } } @@ -191,30 +195,35 @@ class EntityRepository implements ObjectRepository */ public function __call($method, $arguments) { - if (substr($method, 0, 6) == 'findBy') { - $by = substr($method, 6, strlen($method)); - $method = 'findBy'; - } else if (substr($method, 0, 9) == 'findOneBy') { - $by = substr($method, 9, strlen($method)); - $method = 'findOneBy'; - } else { - throw new \BadMethodCallException( - "Undefined method '$method'. The method name must start with ". - "either findBy or findOneBy!" - ); + switch (true) { + case (substr($method, 0, 6) == 'findBy'): + $by = substr($method, 6, strlen($method)); + $method = 'findBy'; + break; + + case (substr($method, 0, 9) == 'findOneBy'): + $by = substr($method, 9, strlen($method)); + $method = 'findOneBy'; + break; + + default: + throw new \BadMethodCallException( + "Undefined method '$method'. The method name must start with ". + "either findBy or findOneBy!" + ); } if (empty($arguments)) { - throw ORMException::findByRequiresParameter($method.$by); + throw ORMException::findByRequiresParameter($method . $by); } $fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by)); if ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName)) { return $this->$method(array($fieldName => $arguments[0])); - } else { - throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by); } + + throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by); } /** diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadata.php b/lib/Doctrine/ORM/Mapping/ClassMetadata.php index 9caa79e11..808e58552 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadata.php @@ -24,7 +24,7 @@ use ReflectionClass, ReflectionProperty; /** * A ClassMetadata instance holds all the object-relational mapping metadata * of an entity and it's associations. - * + * * Once populated, ClassMetadata instances are usually cached in a serialized form. * * IMPORTANT NOTE: @@ -47,10 +47,10 @@ class ClassMetadata extends ClassMetadataInfo * @var array */ public $reflFields = array(); - + /** * The prototype from which new instances of the mapped class are created. - * + * * @var object */ private $_prototype; @@ -103,13 +103,13 @@ class ClassMetadata extends ClassMetadataInfo } return $this->reflFields[$this->identifier[0]]; } - + /** * Validates & completes the given field mapping. * * @param array $mapping The field mapping to validated & complete. * @return array The validated and completed field mapping. - * + * * @throws MappingException */ protected function _validateAndCompleteFieldMapping(array &$mapping) @@ -124,7 +124,7 @@ class ClassMetadata extends ClassMetadataInfo /** * Extracts the identifier values of an entity of this class. - * + * * For composite identifiers, the identifier values are returned as an array * with the same order as the field order in {@link identifier}. * @@ -135,20 +135,25 @@ class ClassMetadata extends ClassMetadataInfo { if ($this->isIdentifierComposite) { $id = array(); + foreach ($this->identifier as $idField) { $value = $this->reflFields[$idField]->getValue($entity); + if ($value !== null) { $id[$idField] = $value; } } + return $id; - } else { - $value = $this->reflFields[$this->identifier[0]]->getValue($entity); - if ($value !== null) { - return array($this->identifier[0] => $value); - } - return array(); } + + $value = $this->reflFields[$this->identifier[0]]->getValue($entity); + + if ($value !== null) { + return array($this->identifier[0] => $value); + } + + return array(); } /** @@ -215,18 +220,18 @@ class ClassMetadata extends ClassMetadataInfo { return __CLASS__ . '@' . spl_object_hash($this); } - + /** * Determines which fields get serialized. * * It is only serialized what is necessary for best unserialization performance. * That means any metadata properties that are not set or empty or simply have * their default value are NOT serialized. - * + * * Parts that are also NOT serialized because they can not be properly unserialized: * - reflClass (ReflectionClass) * - reflFields (ReflectionProperty array) - * + * * @return array The names of all the fields that should be serialized. */ public function __sleep() @@ -301,7 +306,7 @@ class ClassMetadata extends ClassMetadataInfo /** * Restores some state that can not be serialized/unserialized. - * + * * @return void */ public function __wakeup() @@ -310,30 +315,27 @@ class ClassMetadata extends ClassMetadataInfo $this->reflClass = new ReflectionClass($this->name); foreach ($this->fieldMappings as $field => $mapping) { - if (isset($mapping['declared'])) { - $reflField = new ReflectionProperty($mapping['declared'], $field); - } else { - $reflField = $this->reflClass->getProperty($field); - } + $reflField = isset($mapping['declared']) + ? new ReflectionProperty($mapping['declared'], $field) + : $this->reflClass->getProperty($field); + $reflField->setAccessible(true); $this->reflFields[$field] = $reflField; } foreach ($this->associationMappings as $field => $mapping) { - if (isset($mapping['declared'])) { - $reflField = new ReflectionProperty($mapping['declared'], $field); - } else { - $reflField = $this->reflClass->getProperty($field); - } + $reflField = isset($mapping['declared']) + ? new ReflectionProperty($mapping['declared'], $field) + : $this->reflClass->getProperty($field); $reflField->setAccessible(true); $this->reflFields[$field] = $reflField; } } - + /** * Creates a new instance of the mapped class, without invoking the constructor. - * + * * @return object */ public function newInstance() @@ -341,6 +343,7 @@ class ClassMetadata extends ClassMetadataInfo if ($this->_prototype === null) { $this->_prototype = unserialize(sprintf('O:%d:"%s":0:{}', strlen($this->name), $this->name)); } + return clone $this->_prototype; } @@ -354,6 +357,7 @@ class ClassMetadata extends ClassMetadataInfo ($this->reflClass->getMethod($callback)->getModifiers() & \ReflectionMethod::IS_PUBLIC) == 0) { throw MappingException::lifecycleCallbackMethodNotFound($this->name, $callback); } + return parent::addLifecycleCallback($callback, $event); } } diff --git a/lib/Doctrine/ORM/NativeQuery.php b/lib/Doctrine/ORM/NativeQuery.php index dea223fa3..2ab87441a 100644 --- a/lib/Doctrine/ORM/NativeQuery.php +++ b/lib/Doctrine/ORM/NativeQuery.php @@ -1,4 +1,4 @@ -_sql = $sql; + return $this; } @@ -58,16 +59,18 @@ final class NativeQuery extends AbstractQuery protected function _doExecute() { $params = $this->_params; - $types = $this->_paramTypes; - if ($params) { - if (is_int(key($params))) { - ksort($params); - ksort($types); - $params = array_values($params); - $types = array_values($types); - } + $types = $this->_paramTypes; + + if ($params && is_int(key($params))) { + ksort($params); + ksort($types); + + $params = array_values($params); + $types = array_values($types); } - return $this->_em->getConnection()->executeQuery($this->_sql, $params, $types, $this->_queryCacheProfile); + return $this->_em->getConnection()->executeQuery( + $this->_sql, $params, $types, $this->_queryCacheProfile + ); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index 7fbafce7b..0aa22df41 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -202,26 +202,30 @@ final class Query extends AbstractQuery return $this->_parserResult; } - // Check query cache. - if ($this->_useQueryCache && ($queryCache = $this->getQueryCacheDriver())) { - $hash = $this->_getQueryCacheId(); - $cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash); + $this->_state = self::STATE_CLEAN; - if ($cached === false) { - // Cache miss. - $parser = new Parser($this); - $this->_parserResult = $parser->parse(); - $queryCache->save($hash, $this->_parserResult, $this->_queryCacheTTL); - } else { - // Cache hit. - $this->_parserResult = $cached; - } - } else { + // Check query cache. + if ( ! ($this->_useQueryCache && ($queryCache = $this->getQueryCacheDriver()))) { $parser = new Parser($this); $this->_parserResult = $parser->parse(); + + return $this->_parserResult; } - $this->_state = self::STATE_CLEAN; + $hash = $this->_getQueryCacheId(); + $cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash); + + if ($cached !== false) { + // Cache hit. + $this->_parserResult = $cached; + + return $this->_parserResult; + } + + // Cache miss. + $parser = new Parser($this); + $this->_parserResult = $parser->parse(); + $queryCache->save($hash, $this->_parserResult, $this->_queryCacheTTL); return $this->_parserResult; } @@ -232,6 +236,7 @@ final class Query extends AbstractQuery protected function _doExecute() { $executor = $this->_parse()->getSqlExecutor(); + if ($this->_queryCacheProfile) { $executor->setQueryCacheProfile($this->_queryCacheProfile); } @@ -339,6 +344,7 @@ final class Query extends AbstractQuery public function setQueryCacheDriver($queryCache) { $this->_queryCache = $queryCache; + return $this; } @@ -351,6 +357,7 @@ final class Query extends AbstractQuery public function useQueryCache($bool) { $this->_useQueryCache = $bool; + return $this; } @@ -364,9 +371,9 @@ final class Query extends AbstractQuery { if ($this->_queryCache) { return $this->_queryCache; - } else { - return $this->_em->getConfiguration()->getQueryCacheImpl(); } + + return $this->_em->getConfiguration()->getQueryCacheImpl(); } /** @@ -380,6 +387,7 @@ final class Query extends AbstractQuery if ($timeToLive !== null) { $timeToLive = (int) $timeToLive; } + $this->_queryCacheTTL = $timeToLive; return $this; @@ -424,6 +432,7 @@ final class Query extends AbstractQuery public function free() { parent::free(); + $this->_dql = null; $this->_state = self::STATE_CLEAN; } @@ -440,6 +449,7 @@ final class Query extends AbstractQuery $this->_dql = $dqlQuery; $this->_state = self::STATE_DIRTY; } + return $this; } @@ -489,6 +499,7 @@ final class Query extends AbstractQuery { $this->_firstResult = $firstResult; $this->_state = self::STATE_DIRTY; + return $this; } @@ -513,6 +524,7 @@ final class Query extends AbstractQuery { $this->_maxResults = $maxResults; $this->_state = self::STATE_DIRTY; + return $this; } @@ -538,6 +550,7 @@ final class Query extends AbstractQuery public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT) { $this->setHint(self::HINT_INTERNAL_ITERATION, true); + return parent::iterate($params, $hydrationMode); } @@ -547,6 +560,7 @@ final class Query extends AbstractQuery public function setHint($name, $value) { $this->_state = self::STATE_DIRTY; + return parent::setHint($name, $value); } @@ -556,6 +570,7 @@ final class Query extends AbstractQuery public function setHydrationMode($hydrationMode) { $this->_state = self::STATE_DIRTY; + return parent::setHydrationMode($hydrationMode); } @@ -568,13 +583,14 @@ final class Query extends AbstractQuery */ public function setLockMode($lockMode) { - if ($lockMode == LockMode::PESSIMISTIC_READ || $lockMode == LockMode::PESSIMISTIC_WRITE) { - if (!$this->_em->getConnection()->isTransactionActive()) { + if ($lockMode === LockMode::PESSIMISTIC_READ || $lockMode === LockMode::PESSIMISTIC_WRITE) { + if ( ! $this->_em->getConnection()->isTransactionActive()) { throw TransactionRequiredException::transactionRequired(); } } $this->setHint(self::HINT_LOCK_MODE, $lockMode); + return $this; } @@ -586,9 +602,11 @@ final class Query extends AbstractQuery public function getLockMode() { $lockMode = $this->getHint(self::HINT_LOCK_MODE); - if (!$lockMode) { + + if ( ! $lockMode) { return LockMode::NONE; } + return $lockMode; } @@ -618,6 +636,7 @@ final class Query extends AbstractQuery public function __clone() { parent::__clone(); + $this->_state = self::STATE_DIRTY; } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/QueryBuilder.php b/lib/Doctrine/ORM/QueryBuilder.php index d033eaff2..21143214b 100644 --- a/lib/Doctrine/ORM/QueryBuilder.php +++ b/lib/Doctrine/ORM/QueryBuilder.php @@ -355,12 +355,9 @@ class QueryBuilder public function setParameters(array $params, array $types = array()) { foreach ($params as $key => $value) { - if (isset($types[$key])) { - $this->setParameter($key, $value, $types[$key]); - } else { - $this->setParameter($key, $value); - } + $this->setParameter($key, $value, isset($types[$key]) ? $types[$key] : null); } + return $this; } @@ -394,6 +391,7 @@ class QueryBuilder public function setFirstResult($firstResult) { $this->_firstResult = $firstResult; + return $this; } @@ -417,6 +415,7 @@ class QueryBuilder public function setMaxResults($maxResults) { $this->_maxResults = $maxResults; + return $this; } @@ -652,13 +651,16 @@ class QueryBuilder public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null) { $rootAlias = substr($join, 0, strpos($join, '.')); - if (!in_array($rootAlias, $this->getRootAliases())) { + + if ( ! in_array($rootAlias, $this->getRootAliases())) { $rootAlias = $this->getRootAlias(); } - return $this->add('join', array( - $rootAlias => new Expr\Join(Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition, $indexBy) - ), true); + $join = new Expr\Join( + Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition, $indexBy + ); + + return $this->add('join', array($rootAlias => $join), true); } /** @@ -685,13 +687,16 @@ class QueryBuilder public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null) { $rootAlias = substr($join, 0, strpos($join, '.')); - if (!in_array($rootAlias, $this->getRootAliases())) { + + if ( ! in_array($rootAlias, $this->getRootAliases())) { $rootAlias = $this->getRootAlias(); } - return $this->add('join', array( - $rootAlias => new Expr\Join(Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy) - ), true); + $join = new Expr\Join( + Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy + ); + + return $this->add('join', array($rootAlias => $join), true); } /** diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 64f0608e7..25d45fcd0 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1790,7 +1790,7 @@ class UnitOfWork implements PropertyChangedListener public function detach($entity) { $visited = array(); - + $this->doDetach($entity, $visited); } @@ -1804,7 +1804,7 @@ class UnitOfWork implements PropertyChangedListener private function doDetach($entity, array &$visited, $noCascade = false) { $oid = spl_object_hash($entity); - + if (isset($visited[$oid])) { return; // Prevent infinite recursion } @@ -1816,13 +1816,13 @@ class UnitOfWork implements PropertyChangedListener if ($this->isInIdentityMap($entity)) { $this->removeFromIdentityMap($entity); } - + unset( - $this->entityInsertions[$oid], + $this->entityInsertions[$oid], $this->entityUpdates[$oid], - $this->entityDeletions[$oid], + $this->entityDeletions[$oid], $this->entityIdentifiers[$oid], - $this->entityStates[$oid], + $this->entityStates[$oid], $this->originalEntityData[$oid] ); break; @@ -1846,7 +1846,7 @@ class UnitOfWork implements PropertyChangedListener public function refresh($entity) { $visited = array(); - + $this->doRefresh($entity, $visited); } @@ -1860,7 +1860,7 @@ class UnitOfWork implements PropertyChangedListener private function doRefresh($entity, array &$visited) { $oid = spl_object_hash($entity); - + if (isset($visited[$oid])) { return; // Prevent infinite recursion } @@ -1868,11 +1868,11 @@ class UnitOfWork implements PropertyChangedListener $visited[$oid] = $entity; // mark visited $class = $this->em->getClassMetadata(get_class($entity)); - + if ($this->getEntityState($entity) !== self::STATE_MANAGED) { throw new InvalidArgumentException("Entity is not MANAGED."); } - + $this->getEntityPersister($class->name)->refresh( array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]), $entity @@ -1890,31 +1890,32 @@ class UnitOfWork implements PropertyChangedListener private function cascadeRefresh($entity, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); - - foreach ($class->associationMappings as $assoc) { - if ( ! $assoc['isCascadeRefresh']) { - continue; - } - + + $associationMappings = array_filter( + $class->associationMappings, + function ($assoc) { return $assoc['isCascadeRefresh']; } + ); + + foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); - + switch (true) { case ($relatedEntities instanceof PersistentCollection): // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); // break; is commented intentionally! - + case ($relatedEntities instanceof Collection): case (is_array($relatedEntities)): foreach ($relatedEntities as $relatedEntity) { $this->doRefresh($relatedEntity, $visited); } break; - + case ($relatedEntities !== null): $this->doRefresh($relatedEntities, $visited); break; - + default: // Do nothing } @@ -1930,31 +1931,32 @@ class UnitOfWork implements PropertyChangedListener private function cascadeDetach($entity, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); - - foreach ($class->associationMappings as $assoc) { - if ( ! $assoc['isCascadeDetach']) { - continue; - } - + + $associationMappings = array_filter( + $class->associationMappings, + function ($assoc) { return $assoc['isCascadeDetach']; } + ); + + foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); - + switch (true) { case ($relatedEntities instanceof PersistentCollection): // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); // break; is commented intentionally! - + case ($relatedEntities instanceof Collection): case (is_array($relatedEntities)): foreach ($relatedEntities as $relatedEntity) { $this->doDetach($relatedEntity, $visited); } break; - + case ($relatedEntities !== null): $this->doDetach($relatedEntities, $visited); break; - + default: // Do nothing } @@ -1971,11 +1973,15 @@ class UnitOfWork implements PropertyChangedListener private function cascadeMerge($entity, $managedCopy, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); - foreach ($class->associationMappings as $assoc) { - if ( ! $assoc['isCascadeMerge']) { - continue; - } + + $associationMappings = array_filter( + $class->associationMappings, + function ($assoc) { return $assoc['isCascadeMerge']; } + ); + + foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); + if ($relatedEntities instanceof Collection) { if ($relatedEntities === $class->reflFields[$assoc['fieldName']]->getValue($managedCopy)) { continue; @@ -1985,6 +1991,7 @@ class UnitOfWork implements PropertyChangedListener // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); } + foreach ($relatedEntities as $relatedEntity) { $this->doMerge($relatedEntity, $visited, $managedCopy, $assoc); } @@ -2004,31 +2011,32 @@ class UnitOfWork implements PropertyChangedListener private function cascadePersist($entity, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); - - foreach ($class->associationMappings as $assoc) { - if ( ! $assoc['isCascadePersist']) { - continue; - } + $associationMappings = array_filter( + $class->associationMappings, + function ($assoc) { return $assoc['isCascadePersist']; } + ); + + foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); - + switch (true) { case ($relatedEntities instanceof PersistentCollection): // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); // break; is commented intentionally! - + case ($relatedEntities instanceof Collection): case (is_array($relatedEntities)): foreach ($relatedEntities as $relatedEntity) { $this->doPersist($relatedEntity, $visited); } break; - + case ($relatedEntities !== null): $this->doPersist($relatedEntities, $visited); break; - + default: // Do nothing } @@ -2045,11 +2053,12 @@ class UnitOfWork implements PropertyChangedListener { $class = $this->em->getClassMetadata(get_class($entity)); - foreach ($class->associationMappings as $assoc) { - if ( ! $assoc['isCascadeRemove']) { - continue; - } + $associationMappings = array_filter( + $class->associationMappings, + function ($assoc) { return $assoc['isCascadeRemove']; } + ); + foreach ($associationMappings as $assoc) { if ($entity instanceof Proxy && !$entity->__isInitialized__) { $entity->__load(); } @@ -2064,11 +2073,11 @@ class UnitOfWork implements PropertyChangedListener $this->doRemove($relatedEntity, $visited); } break; - + case ($relatedEntities !== null): $this->doRemove($relatedEntities, $visited); break; - + default: // Do nothing } @@ -2100,15 +2109,15 @@ class UnitOfWork implements PropertyChangedListener if ($lockVersion === null) { return; } - + $entityVersion = $class->reflFields[$class->versionField]->getValue($entity); - + if ($entityVersion != $lockVersion) { throw OptimisticLockException::lockFailedVersionMissmatch($entity, $lockVersion, $entityVersion); } - + break; - + case \Doctrine\DBAL\LockMode::PESSIMISTIC_READ: case \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE: if (!$this->em->getConnection()->isTransactionActive()) { @@ -2122,7 +2131,7 @@ class UnitOfWork implements PropertyChangedListener $lockMode ); break; - + default: // Do nothing } @@ -2138,6 +2147,7 @@ class UnitOfWork implements PropertyChangedListener if ($this->commitOrderCalculator === null) { $this->commitOrderCalculator = new Internal\CommitOrderCalculator; } + return $this->commitOrderCalculator; } @@ -2226,9 +2236,11 @@ class UnitOfWork implements PropertyChangedListener private function newInstance($class) { $entity = $class->newInstance(); + if ($entity instanceof \Doctrine\Common\Persistence\ObjectManagerAware) { $entity->injectObjectManager($this->em, $class); } + return $entity; } @@ -2252,34 +2264,30 @@ class UnitOfWork implements PropertyChangedListener if ($class->isIdentifierComposite) { $id = array(); - + foreach ($class->identifier as $fieldName) { - if (isset($class->associationMappings[$fieldName])) { - $id[$fieldName] = $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']]; - } else { - $id[$fieldName] = $data[$fieldName]; - } + $id[$fieldName] = isset($class->associationMappings[$fieldName]) + ? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']] + : $data[$fieldName]; } - + $idHash = implode(' ', $id); } else { - if (isset($class->associationMappings[$class->identifier[0]])) { - $idHash = $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']]; - } else { - $idHash = $data[$class->identifier[0]]; - } - + $idHash = isset($class->associationMappings[$class->identifier[0]]) + ? $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']] + : $data[$class->identifier[0]]; + $id = array($class->identifier[0] => $idHash); } if (isset($this->identityMap[$class->rootEntityName][$idHash])) { $entity = $this->identityMap[$class->rootEntityName][$idHash]; $oid = spl_object_hash($entity); - + if ($entity instanceof Proxy && ! $entity->__isInitialized__) { $entity->__isInitialized__ = true; $overrideLocalValues = true; - + if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } @@ -2303,23 +2311,23 @@ class UnitOfWork implements PropertyChangedListener } else { $entity = $this->newInstance($class); $oid = spl_object_hash($entity); - + $this->entityIdentifiers[$oid] = $id; $this->entityStates[$oid] = self::STATE_MANAGED; $this->originalEntityData[$oid] = $data; $this->identityMap[$class->rootEntityName][$idHash] = $entity; - + if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } - + $overrideLocalValues = true; } if ( ! $overrideLocalValues) { return $entity; } - + foreach ($data as $field => $value) { if (isset($class->fieldMappings[$field])) { $class->reflFields[$field]->setValue($entity, $value); @@ -2337,7 +2345,7 @@ class UnitOfWork implements PropertyChangedListener if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) { return $entity; } - + foreach ($class->associationMappings as $field => $assoc) { // Check if the association is not among the fetch-joined associations already. if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) { From 619a31913a4f5952248a0b909a25c2020619c29f Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Thu, 1 Dec 2011 21:18:39 +0100 Subject: [PATCH 10/15] DDC-1517 - Fix EntityRepository#find() and EntityManager#getReference() breaking on invalid or missing identifiers. --- lib/Doctrine/ORM/EntityManager.php | 26 +++++++++++++------ lib/Doctrine/ORM/EntityRepository.php | 25 +++++++++++------- lib/Doctrine/ORM/ORMException.php | 5 ++++ .../Functional/CompositePrimaryKeyTest.php | 6 +++++ 4 files changed, 44 insertions(+), 18 deletions(-) diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index f09bacb89..9033c178c 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -368,28 +368,38 @@ class EntityManager implements ObjectManager * without actually loading it, if the entity is not yet loaded. * * @param string $entityName The name of the entity type. - * @param mixed $identifier The entity identifier. + * @param mixed $id The entity identifier. * @return object The entity reference. */ - public function getReference($entityName, $identifier) + public function getReference($entityName, $id) { $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\')); + if ( ! is_array($id)) { + $id = array($class->identifier[0] => $id); + } + $sortedId = array(); + foreach ($class->identifier as $identifier) { + if (!isset($id[$identifier])) { + throw ORMException::missingIdentifierField($class->name, $identifier); + } + $sortedId[$identifier] = $id[$identifier]; + } // Check identity map first, if its already in there just return it. - if ($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) { + if ($entity = $this->unitOfWork->tryGetById($sortedId, $class->rootEntityName)) { return ($entity instanceof $class->name) ? $entity : null; } if ($class->subClasses) { - return $this->find($entityName, $identifier); + return $this->find($entityName, $sortedId); } - if ( ! is_array($identifier)) { - $identifier = array($class->identifier[0] => $identifier); + if ( ! is_array($sortedId)) { + $sortedId = array($class->identifier[0] => $sortedId); } - $entity = $this->proxyFactory->getProxy($class->name, $identifier); - $this->unitOfWork->registerManaged($entity, $identifier, array()); + $entity = $this->proxyFactory->getProxy($class->name, $sortedId); + $this->unitOfWork->registerManaged($entity, $sortedId, array()); return $entity; } diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php index f74448141..3261d7e61 100644 --- a/lib/Doctrine/ORM/EntityRepository.php +++ b/lib/Doctrine/ORM/EntityRepository.php @@ -107,8 +107,19 @@ class EntityRepository implements ObjectRepository */ public function find($id, $lockMode = LockMode::NONE, $lockVersion = null) { + if ( ! is_array($id)) { + $id = array($this->_class->identifier[0] => $id); + } + $sortedId = array(); + foreach ($this->_class->identifier as $identifier) { + if (!isset($id[$identifier])) { + throw ORMException::missingIdentifierField($this->_class->name, $identifier); + } + $sortedId[$identifier] = $id[$identifier]; + } + // Check identity map first - if ($entity = $this->_em->getUnitOfWork()->tryGetById($id, $this->_class->rootEntityName)) { + if ($entity = $this->_em->getUnitOfWork()->tryGetById($sortedId, $this->_class->rootEntityName)) { if ( ! ($entity instanceof $this->_class->name)) { return null; } @@ -120,22 +131,16 @@ class EntityRepository implements ObjectRepository return $entity; // Hit! } - if ( ! is_array($id) || count($id) <= 1) { - // @todo FIXME: Not correct. Relies on specific order. - $value = is_array($id) ? array_values($id) : array($id); - $id = array_combine($this->_class->identifier, $value); - } - switch ($lockMode) { case LockMode::NONE: - return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id); + return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($sortedId); case LockMode::OPTIMISTIC: if ( ! $this->_class->isVersioned) { throw OptimisticLockException::notVersioned($this->_entityName); } - $entity = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id); + $entity = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($sortedId); $this->_em->getUnitOfWork()->lock($entity, $lockMode, $lockVersion); @@ -146,7 +151,7 @@ class EntityRepository implements ObjectRepository throw TransactionRequiredException::transactionRequired(); } - return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id, null, null, array(), $lockMode); + return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($sortedId, null, null, array(), $lockMode); } } diff --git a/lib/Doctrine/ORM/ORMException.php b/lib/Doctrine/ORM/ORMException.php index f15e0fbe1..0e36217d9 100644 --- a/lib/Doctrine/ORM/ORMException.php +++ b/lib/Doctrine/ORM/ORMException.php @@ -144,4 +144,9 @@ class ORMException extends Exception return new self("Invalid repository class '".$className."'. ". "it must be a Doctrine\ORM\EntityRepository."); } + + public static function missingIdentifierField($className, $fieldName) + { + return new self("The identifier $fieldName is missing for a query of " . $className); + } } diff --git a/tests/Doctrine/Tests/ORM/Functional/CompositePrimaryKeyTest.php b/tests/Doctrine/Tests/ORM/Functional/CompositePrimaryKeyTest.php index ee762d345..15f7e0bda 100644 --- a/tests/Doctrine/Tests/ORM/Functional/CompositePrimaryKeyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/CompositePrimaryKeyTest.php @@ -92,4 +92,10 @@ class CompositePrimaryKeyTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(1, count($tours)); } + + public function testSpecifiyUnknownIdentifierPrimaryKeyFails() + { + $this->setExpectedException('Doctrine\ORM\ORMException', 'The identifier long is missing for a query of Doctrine\Tests\Models\Navigation\NavPointOfInterest'); + $poi = $this->_em->find('Doctrine\Tests\Models\Navigation\NavPointOfInterest', array('key1' => 100)); + } } \ No newline at end of file From 2642daa43851878688d01625f272ff5874cac7b2 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Thu, 1 Dec 2011 23:52:35 -0500 Subject: [PATCH 11/15] Fixed DDC-1236: GROUP BY now supports ResultVariable and IdentificationVariable. Composite PK is also supported. If you are willing to group by an aggregate function or a function itself, just place it in SELECT expression then refer to it in the GROUP BY clause. If you are not willing to have the function being part of your resultset, just mark the column as HIDDEN and you are done. --- lib/Doctrine/ORM/Query/Parser.php | 20 ++--- lib/Doctrine/ORM/Query/SqlWalker.php | 76 ++++++++++++++----- lib/Doctrine/ORM/Query/TreeWalker.php | 10 ++- lib/Doctrine/ORM/Query/TreeWalkerAdapter.php | 22 ++++-- lib/Doctrine/ORM/Query/TreeWalkerChain.php | 33 +++++--- .../ORM/Query/SelectSqlGenerationTest.php | 49 +++++++++--- 6 files changed, 152 insertions(+), 58 deletions(-) diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index a3725eb8d..ff3656b62 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -2,7 +2,7 @@ /* * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHARNTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT @@ -1305,7 +1305,7 @@ class Parser } /** - * GroupByItem ::= IdentificationVariable | SingleValuedPathExpression + * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression * * @return string | \Doctrine\ORM\Query\AST\PathExpression */ @@ -1314,18 +1314,20 @@ class Parser // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression $glimpse = $this->_lexer->glimpse(); - if ($glimpse['type'] == Lexer::T_DOT) { + if ($glimpse['type'] === Lexer::T_DOT) { return $this->SingleValuedPathExpression(); } - $token = $this->_lexer->lookahead; - $identVariable = $this->IdentificationVariable(); + // Still need to decide between IdentificationVariable or ResultVariable + $lookaheadValue = $this->_lexer->lookahead['value']; - if ( ! isset($this->_queryComponents[$identVariable])) { - $this->semanticalError('Cannot group by undefined identification variable.'); + if ( ! isset($this->_queryComponents[$lookaheadValue])) { + $this->semanticalError('Cannot group by undefined identification or result variable.'); } - return $identVariable; + return (isset($this->_queryComponents[$lookaheadValue]['metadata'])) + ? $this->IdentificationVariable() + : $this->ResultVariable(); } /** @@ -2633,7 +2635,7 @@ class Parser $isDistinct = true; } - $pathExp = ($lookaheadType === Lexer::T_COUNT) + $pathExp = ($lookaheadType === Lexer::T_COUNT) ? $this->SingleValuedPathExpression() : $this->SimpleArithmeticExpression(); diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 3b58dfba2..c12ea0690 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -1014,6 +1014,8 @@ class SqlWalker implements TreeWalker $sql .= $col . ' AS ' . $columnAlias; + $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; + if ( ! $hidden) { $this->_rsm->addScalarResult($columnAlias, $resultAlias); $this->_scalarFields[$dqlAlias][$fieldName] = $columnAlias; @@ -1103,6 +1105,8 @@ class SqlWalker implements TreeWalker $sqlParts[] = $col . ' AS '. $columnAlias; + $this->_scalarResultAliasMap[$resultAlias][] = $columnAlias; + $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); } @@ -1132,6 +1136,8 @@ class SqlWalker implements TreeWalker $sqlParts[] = $col . ' AS ' . $columnAlias; + $this->_scalarResultAliasMap[$resultAlias][] = $columnAlias; + $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName); } } @@ -1319,25 +1325,7 @@ class SqlWalker implements TreeWalker $sqlParts = array(); foreach ($groupByClause->groupByItems AS $groupByItem) { - if ( ! is_string($groupByItem)) { - $sqlParts[] = $this->walkGroupByItem($groupByItem); - - continue; - } - - foreach ($this->_queryComponents[$groupByItem]['metadata']->fieldNames AS $field) { - $item = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field); - $item->type = AST\PathExpression::TYPE_STATE_FIELD; - $sqlParts[] = $this->walkGroupByItem($item); - } - - foreach ($this->_queryComponents[$groupByItem]['metadata']->associationMappings AS $mapping) { - if ($mapping['isOwningSide'] && $mapping['type'] & ClassMetadataInfo::TO_ONE) { - $item = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping['fieldName']); - $item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION; - $sqlParts[] = $this->walkGroupByItem($item); - } - } + $sqlParts[] = $this->walkGroupByItem($groupByItem); } return ' GROUP BY ' . implode(', ', $sqlParts); @@ -1349,9 +1337,38 @@ class SqlWalker implements TreeWalker * @param GroupByItem * @return string The SQL. */ - public function walkGroupByItem(AST\PathExpression $pathExpr) + public function walkGroupByItem($groupByItem) { - return $this->walkPathExpression($pathExpr); + // StateFieldPathExpression + if ( ! is_string($groupByItem)) { + return $this->walkPathExpression($groupByItem); + } + + // ResultVariable + if (isset($this->_queryComponents[$groupByItem]['resultVariable'])) { + return $this->walkResultVariable($groupByItem); + } + + // IdentificationVariable + $sqlParts = array(); + + foreach ($this->_queryComponents[$groupByItem]['metadata']->fieldNames AS $field) { + $item = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field); + $item->type = AST\PathExpression::TYPE_STATE_FIELD; + + $sqlParts[] = $this->walkPathExpression($item); + } + + foreach ($this->_queryComponents[$groupByItem]['metadata']->associationMappings AS $mapping) { + if ($mapping['isOwningSide'] && $mapping['type'] & ClassMetadataInfo::TO_ONE) { + $item = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping['fieldName']); + $item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION; + + $sqlParts[] = $this->walkPathExpression($item); + } + } + + return implode(', ', $sqlParts); } /** @@ -1997,4 +2014,21 @@ class SqlWalker implements TreeWalker ? $this->_conn->quote($stringPrimary) : $stringPrimary->dispatch($this); } + + /** + * Walks down a ResultVriable that represents an AST node, thereby generating the appropriate SQL. + * + * @param string $resultVariable + * @return string The SQL. + */ + public function walkResultVariable($resultVariable) + { + $resultAlias = $this->_scalarResultAliasMap[$resultVariable]; + + if (is_array($resultAlias)) { + return implode(', ', $resultAlias); + } + + return $resultAlias; + } } diff --git a/lib/Doctrine/ORM/Query/TreeWalker.php b/lib/Doctrine/ORM/Query/TreeWalker.php index 4bbe963a6..84628981c 100644 --- a/lib/Doctrine/ORM/Query/TreeWalker.php +++ b/lib/Doctrine/ORM/Query/TreeWalker.php @@ -168,7 +168,7 @@ interface TreeWalker * @param GroupByItem * @return string The SQL. */ - function walkGroupByItem(AST\PathExpression $pathExpr); + function walkGroupByItem($groupByItem); /** * Walks down an UpdateStatement AST node, thereby generating the appropriate SQL. @@ -394,6 +394,14 @@ interface TreeWalker */ function walkPathExpression($pathExpr); + /** + * Walks down an ResultVariable AST node, thereby generating the appropriate SQL. + * + * @param string $resultVariable + * @return string The SQL. + */ + function walkResultVariable($resultVariable); + /** * Gets an executor that can be used to execute the result of this walker. * diff --git a/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php b/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php index ca2a49520..530b98c50 100644 --- a/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php +++ b/lib/Doctrine/ORM/Query/TreeWalkerAdapter.php @@ -24,7 +24,7 @@ namespace Doctrine\ORM\Query; /** * An adapter implementation of the TreeWalker interface. The methods in this class * are empty. This class exists as convenience for creating tree walkers. - * + * * @author Roman Borschel * @since 2.0 */ @@ -33,7 +33,7 @@ abstract class TreeWalkerAdapter implements TreeWalker private $_query; private $_parserResult; private $_queryComponents; - + /** * {@inheritdoc} */ @@ -71,7 +71,7 @@ abstract class TreeWalkerAdapter implements TreeWalker { return $this->_parserResult; } - + /** * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. * @@ -202,7 +202,7 @@ abstract class TreeWalkerAdapter implements TreeWalker * @param GroupByItem * @return string The SQL. */ - public function walkGroupByItem(AST\PathExpression $pathExpr) {} + public function walkGroupByItem($groupByItem) {} /** * Walks down an UpdateStatement AST node, thereby generating the appropriate SQL. @@ -291,7 +291,7 @@ abstract class TreeWalkerAdapter implements TreeWalker * @return string The SQL. */ public function walkExistsExpression($existsExpr) {} - + /** * Walks down a CollectionMemberExpression AST node, thereby generating the appropriate SQL. * @@ -427,10 +427,18 @@ abstract class TreeWalkerAdapter implements TreeWalker * @return string The SQL. */ public function walkPathExpression($pathExpr) {} - + + /** + * Walks down an ResultVariable AST node, thereby generating the appropriate SQL. + * + * @param string $resultVariable + * @return string The SQL. + */ + public function walkResultVariable($resultVariable) {} + /** * Gets an executor that can be used to execute the result of this walker. - * + * * @return AbstractExecutor */ public function getExecutor($AST) {} diff --git a/lib/Doctrine/ORM/Query/TreeWalkerChain.php b/lib/Doctrine/ORM/Query/TreeWalkerChain.php index 1fc197783..d3891a114 100644 --- a/lib/Doctrine/ORM/Query/TreeWalkerChain.php +++ b/lib/Doctrine/ORM/Query/TreeWalkerChain.php @@ -25,7 +25,7 @@ namespace Doctrine\ORM\Query; * Represents a chain of tree walkers that modify an AST and finally emit output. * Only the last walker in the chain can emit output. Any previous walkers can modify * the AST to influence the final output produced by the last walker. - * + * * @author Roman Borschel * @since 2.0 */ @@ -39,7 +39,7 @@ class TreeWalkerChain implements TreeWalker private $_parserResult; /** The query components of the original query (the "symbol table") that was produced by the Parser. */ private $_queryComponents; - + /** * @inheritdoc */ @@ -49,17 +49,17 @@ class TreeWalkerChain implements TreeWalker $this->_parserResult = $parserResult; $this->_queryComponents = $queryComponents; } - + /** * Adds a tree walker to the chain. - * + * * @param string $walkerClass The class of the walker to instantiate. */ public function addTreeWalker($walkerClass) { $this->_walkers[] = new $walkerClass($this->_query, $this->_parserResult, $this->_queryComponents); } - + /** * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. * @@ -270,10 +270,10 @@ class TreeWalkerChain implements TreeWalker * @param GroupByItem * @return string The SQL. */ - public function walkGroupByItem(AST\PathExpression $pathExpr) + public function walkGroupByItem($groupByItem) { foreach ($this->_walkers as $walker) { - $walker->walkGroupByItem($pathExpr); + $walker->walkGroupByItem($groupByItem); } } @@ -419,7 +419,7 @@ class TreeWalkerChain implements TreeWalker $walker->walkExistsExpression($existsExpr); } } - + /** * Walks down a CollectionMemberExpression AST node, thereby generating the appropriate SQL. * @@ -640,10 +640,23 @@ class TreeWalkerChain implements TreeWalker $walker->walkPathExpression($pathExpr); } } - + + /** + * Walks down an ResultVariable AST node, thereby generating the appropriate SQL. + * + * @param string $resultVariable + * @return string The SQL. + */ + public function walkResultVariable($resultVariable) + { + foreach ($this->_walkers as $walker) { + $walker->walkResultVariable($resultVariable); + } + } + /** * Gets an executor that can be used to execute the result of this walker. - * + * * @return AbstractExecutor */ public function getExecutor($AST) diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index ceb1a3729..50de767db 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -40,7 +40,14 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase $query->setHint($name, $value); } - parent::assertEquals($sqlToBeConfirmed, $query->getSQL()); + $sqlGenerated = $query->getSQL(); + + parent::assertEquals( + $sqlToBeConfirmed, + $sqlGenerated, + sprintf('"%s" is not equal of "%s"', $sqlGenerated, $sqlToBeConfirmed) + ); + $query->free(); } catch (\Exception $e) { $this->fail($e->getMessage() ."\n".$e->getTraceAsString()); @@ -1302,7 +1309,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase "SELECT d0_.article_id AS article_id0, d0_.title AS title1 FROM DDC117Article d0_ WHERE EXISTS (SELECT d1_.source_id, d1_.target_id FROM DDC117Reference d1_ WHERE d1_.source_id = d0_.article_id)" ); } - + /** * @group DDC-1474 */ @@ -1312,13 +1319,13 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase 'SELECT - e.value AS value, e.id FROM ' . __NAMESPACE__ . '\DDC1474Entity e', 'SELECT -d0_.value AS sclr0, d0_.id AS id1 FROM DDC1474Entity d0_' ); - + $this->assertSqlGeneration( 'SELECT e.id, + e.value AS value FROM ' . __NAMESPACE__ . '\DDC1474Entity e', 'SELECT d0_.id AS id0, +d0_.value AS sclr1 FROM DDC1474Entity d0_' ); } - + /** * @group DDC-1430 */ @@ -1328,13 +1335,35 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u GROUP BY u', 'SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ GROUP BY c0_.id, c0_.status, c0_.username, c0_.name, c0_.email_id' ); - + $this->assertSqlGeneration( 'SELECT e FROM Doctrine\Tests\Models\CMS\CmsEmployee e GROUP BY e', 'SELECT c0_.id AS id0, c0_.name AS name1 FROM cms_employees c0_ GROUP BY c0_.id, c0_.name, c0_.spouse_id' ); } + /** + * @group DDC-1236 + */ + public function testGroupBySupportsResultVariable() + { + $this->assertSqlGeneration( + 'SELECT u, u.status AS st FROM Doctrine\Tests\Models\CMS\CmsUser u GROUP BY st', + 'SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3, c0_.status AS status4 FROM cms_users c0_ GROUP BY status4' + ); + } + + /** + * @group DDC-1236 + */ + public function testGroupBySupportsIdentificationVariable() + { + $this->assertSqlGeneration( + 'SELECT u AS user FROM Doctrine\Tests\Models\CMS\CmsUser u GROUP BY user', + 'SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ GROUP BY id0, status1, username2, name3' + ); + } + public function testCustomTypeValueSql() { if (DBALType::hasType('negative_to_positive')) { @@ -1441,19 +1470,19 @@ class DDC1474Entity { /** - * @Id + * @Id * @Column(type="integer") * @GeneratedValue() */ protected $id; /** - * @column(type="float") + * @column(type="float") */ private $value; /** - * @param string $float + * @param string $float */ public function __construct($float) { @@ -1469,7 +1498,7 @@ class DDC1474Entity } /** - * @return float + * @return float */ public function getValue() { @@ -1477,7 +1506,7 @@ class DDC1474Entity } /** - * @param float $value + * @param float $value */ public function setValue($value) { From 2f6b930a8d418fc34572de1867806a63c34abc6d Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Sat, 3 Dec 2011 15:19:21 -0500 Subject: [PATCH 12/15] Implemented missing support in CollectionMemberComparison. Removed old todo in ArrayHydrator. Finished implementation of IdentificationVariable in ArithmeticPrimary. --- .../ORM/Internal/Hydration/ArrayHydrator.php | 5 - lib/Doctrine/ORM/Query/Parser.php | 2 +- lib/Doctrine/ORM/Query/SqlWalker.php | 125 +++++++++++------- lib/Doctrine/ORM/Tools/EntityGenerator.php | 10 +- .../ORM/Query/SelectSqlGenerationTest.php | 52 ++++++-- 5 files changed, 120 insertions(+), 74 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php index 817e30baf..f2869baa2 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php @@ -28,11 +28,6 @@ use PDO, Doctrine\DBAL\Connection, Doctrine\ORM\Mapping\ClassMetadata; * @since 2.0 * @author Roman Borschel * @author Guilherme Blanco - * - * @todo General behavior is "wrong" if you define an alias to selected IdentificationVariable. - * Example: SELECT u AS user FROM User u - * The result should contains an array where each array index is an array: array('user' => [User object]) - * Problem must be solved somehow by removing the isMixed in ResultSetMapping */ class ArrayHydrator extends AbstractHydrator { diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index ff3656b62..b63af9ac8 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -2607,7 +2607,7 @@ class Parser return $this->InputParameter(); } - return $this->IdentificationVariable(); + return $this->StateFieldPathExpression(); } /** diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index c12ea0690..5826b82d4 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -411,7 +411,8 @@ class SqlWalker implements TreeWalker { $this->_useSqlTableAliases = false; - return $this->walkUpdateClause($AST->updateClause) . $this->walkWhereClause($AST->whereClause); + return $this->walkUpdateClause($AST->updateClause) + . $this->walkWhereClause($AST->whereClause); } /** @@ -424,9 +425,29 @@ class SqlWalker implements TreeWalker { $this->_useSqlTableAliases = false; - return $this->walkDeleteClause($AST->deleteClause) . $this->walkWhereClause($AST->whereClause); + return $this->walkDeleteClause($AST->deleteClause) + . $this->walkWhereClause($AST->whereClause); } + /** + * Walks down an IdentificationVariable AST node, thereby generating the appropriate SQL. + * This one differs of ->walkIdentificationVariable() because it generates the entity identifiers. + * + * @param string $identVariable + * @return string + */ + public function walkEntityIdentificationVariable($identVariable) + { + $class = $this->_queryComponents[$identVariable]['metadata']; + $tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable); + $sqlParts = array(); + + foreach ($class->getQuotedIdentifierColumnNames($this->_platform) as $columnName) { + $sqlParts[] = $tableAlias . '.' . $columnName; + } + + return implode(', ', $sqlParts); + } /** * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL. @@ -689,7 +710,7 @@ class SqlWalker implements TreeWalker $expr = $orderByItem->expression; $sql = ($expr instanceof AST\PathExpression) ? $this->walkPathExpression($expr) - : $this->_scalarResultAliasMap[$this->_queryComponents[$expr]['token']['value']]; + : $this->walkResultVariable($this->_queryComponents[$expr]['token']['value']); return $sql . ' ' . strtoupper($orderByItem->type); } @@ -1287,15 +1308,7 @@ class SqlWalker implements TreeWalker break; default: // IdentificationVariable - $class = $this->_queryComponents[$expr]['metadata']; - $tableAlias = $this->getSQLTableAlias($class->getTableName(), $expr); - $sqlParts = array(); - - foreach ($class->getQuotedIdentifierColumnNames($this->_platform) as $columnName) { - $sqlParts[] = $tableAlias . '.' . $columnName; - } - - $sql .= implode(', ', $sqlParts); + $sql .= $this->walkEntityIdentificationVariable($expr); break; } @@ -1558,20 +1571,30 @@ class SqlWalker implements TreeWalker { $sql = $collMemberExpr->not ? 'NOT ' : ''; $sql .= 'EXISTS (SELECT 1 FROM '; - $entityExpr = $collMemberExpr->entityExpression; + + $entityExpr = $collMemberExpr->entityExpression; $collPathExpr = $collMemberExpr->collectionValuedPathExpression; $fieldName = $collPathExpr->field; - $dqlAlias = $collPathExpr->identificationVariable; + $dqlAlias = $collPathExpr->identificationVariable; $class = $this->_queryComponents[$dqlAlias]['metadata']; - if ($entityExpr instanceof AST\InputParameter) { - $dqlParamKey = $entityExpr->name; - $entity = $this->_query->getParameter($dqlParamKey); - } else { - //TODO - throw new \BadMethodCallException("Not implemented"); + switch (true) { + // InputParameter + case ($entityExpr instanceof AST\InputParameter): + $dqlParamKey = $entityExpr->name; + $entity = $this->_query->getParameter($dqlParamKey); + $entitySql = '?'; + break; + + // SingleValuedAssociationPathExpression | IdentificationVariable + case ($entityExpr instanceof AST\PathExpression): + $entitySql = $this->walkPathExpression($entityExpr); + break; + + default: + throw new \BadMethodCallException("Not implemented"); } $assoc = $class->associationMappings[$fieldName]; @@ -1584,25 +1607,23 @@ class SqlWalker implements TreeWalker $sql .= $targetClass->getQuotedTableName($this->_platform) . ' ' . $targetTableAlias . ' WHERE '; $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']]; - $first = true; + $sqlParts = array(); foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) { - if ($first) $first = false; else $sql .= ' AND '; + $targetColumn = $class->getQuotedColumnName($class->fieldNames[$targetColumn], $this->_platform); - $sql .= $sourceTableAlias . '.' . $class->getQuotedColumnName($class->fieldNames[$targetColumn], $this->_platform) - . ' = ' - . $targetTableAlias . '.' . $sourceColumn; + $sqlParts[] = $sourceTableAlias . '.' . $targetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn; } - $sql .= ' AND '; - $first = true; - foreach ($targetClass->getQuotedIdentifierColumnNames($this->_platform) as $targetColumnName) { - if ($first) $first = false; else $sql .= ' AND '; + if (isset($dqlParamKey)) { + $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); + } - $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); - $sql .= $targetTableAlias . '.' . $targetColumnName . ' = ?'; + $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' = ' . $entitySql; } + + $sql .= implode(' AND ', $sqlParts); } else { // many-to-many $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); @@ -1619,39 +1640,42 @@ class SqlWalker implements TreeWalker . ' INNER JOIN ' . $targetClass->getQuotedTableName($this->_platform) . ' ' . $targetTableAlias . ' ON '; // join conditions - $joinColumns = $assoc['isOwningSide'] - ? $joinTable['inverseJoinColumns'] - : $joinTable['joinColumns']; + $joinColumns = $assoc['isOwningSide'] ? $joinTable['inverseJoinColumns'] : $joinTable['joinColumns']; + $joinSqlParts = array(); - $first = true; foreach ($joinColumns as $joinColumn) { - if ($first) $first = false; else $sql .= ' AND '; + $targetColumn = $targetClass->getQuotedColumnName( + $targetClass->fieldNames[$joinColumn['referencedColumnName']], + $this->_platform + ); - $sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = ' - . $targetTableAlias . '.' . $targetClass->getQuotedColumnName($targetClass->fieldNames[$joinColumn['referencedColumnName']], $this->_platform); + $joinSqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $targetTableAlias . '.' . $targetColumn; } + $sql .= implode(' AND ', $joinSqlParts); $sql .= ' WHERE '; $joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns']; - $first = true; + $sqlParts = array(); foreach ($joinColumns as $joinColumn) { - if ($first) $first = false; else $sql .= ' AND '; + $targetColumn = $class->getQuotedColumnName( + $class->fieldNames[$joinColumn['referencedColumnName']], + $this->_platform + ); - $sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = ' - . $sourceTableAlias . '.' . $class->getQuotedColumnName($class->fieldNames[$joinColumn['referencedColumnName']], $this->_platform); + $sqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $sourceTableAlias . '.' . $targetColumn; } - $sql .= ' AND '; - $first = true; - foreach ($targetClass->getQuotedIdentifierColumnNames($this->_platform) as $targetColumnName) { - if ($first) $first = false; else $sql .= ' AND '; + if (isset($dqlParamKey)) { + $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); + } - $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); - $sql .= $targetTableAlias . '.' . $targetColumnName . ' = ?'; + $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' = ' . $entitySql; } + + $sql .= implode(' AND ', $sqlParts); } return $sql . ')'; @@ -1946,7 +1970,7 @@ class SqlWalker implements TreeWalker { if (is_string($term)) { return (isset($this->_queryComponents[$term])) - ? $this->_scalarResultAliasMap[$this->_queryComponents[$term]['token']['value']] + ? $this->walkResultVariable($this->_queryComponents[$term]['token']['value']) : $term; } @@ -1998,8 +2022,7 @@ class SqlWalker implements TreeWalker return $primary->dispatch($this); } - // TODO: We need to deal with IdentificationVariable here - return ''; + return $this->walkEntityIdentificationVariable($primary); } /** diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index b1ba714d8..66d79a837 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -196,7 +196,7 @@ public function () if ($this->_backupExisting && file_exists($path)) { $backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . "~"; if (!copy($path, $backupPath)) { - throw new \RuntimeException("Attempt to backup overwritten entitiy file but copy operation failed."); + throw new \RuntimeException("Attempt to backup overwritten entity file but copy operation failed."); } } @@ -404,11 +404,11 @@ public function () $collections[] = '$this->'.$mapping['fieldName'].' = new \Doctrine\Common\Collections\ArrayCollection();'; } } - + if ($collections) { return $this->_prefixCodeWithSpaces(str_replace("", implode("\n".$this->_spaces, $collections), self::$_constructorMethodTemplate)); } - + return ''; } @@ -576,7 +576,7 @@ public function () if (isset($metadata->table['schema'])) { $table[] = 'schema="' . $metadata->table['schema'] . '"'; } - + if (isset($metadata->table['name'])) { $table[] = 'name="' . $metadata->table['name'] . '"'; } @@ -754,7 +754,7 @@ public function () '' => Inflector::camelize($fieldName), '' => $methodName, '' => $fieldName, - '' => ($defaultValue !== null ) ? ('='.$defaultValue) : '', + '' => ($defaultValue !== null ) ? (' = '.$defaultValue) : '', '' => $this->_getClassName($metadata) ); diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 50de767db..031ec2138 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -536,43 +536,71 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase ); } - public function testSupportsMemberOfExpression() + public function testSupportsMemberOfExpressionOneToMany() { // "Get all users who have $phone as a phonenumber." (*cough* doesnt really make sense...) - $q1 = $this->_em->createQuery('SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE :param MEMBER OF u.phonenumbers'); - $q1->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true); + $q = $this->_em->createQuery('SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE :param MEMBER OF u.phonenumbers'); + $q->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true); $phone = new \Doctrine\Tests\Models\CMS\CmsPhonenumber; $phone->phonenumber = 101; - $q1->setParameter('param', $phone); + $q->setParameter('param', $phone); $this->assertEquals( 'SELECT c0_.id AS id0 FROM cms_users c0_ WHERE EXISTS (SELECT 1 FROM cms_phonenumbers c1_ WHERE c0_.id = c1_.user_id AND c1_.phonenumber = ?)', - $q1->getSql() + $q->getSql() ); + } + public function testSupportsMemberOfExpressionManyToMany() + { // "Get all users who are members of $group." - $q2 = $this->_em->createQuery('SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE :param MEMBER OF u.groups'); - $q2->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true); + $q = $this->_em->createQuery('SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE :param MEMBER OF u.groups'); + $q->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true); $group = new \Doctrine\Tests\Models\CMS\CmsGroup; $group->id = 101; - $q2->setParameter('param', $group); + $q->setParameter('param', $group); $this->assertEquals( 'SELECT c0_.id AS id0 FROM cms_users c0_ WHERE EXISTS (SELECT 1 FROM cms_users_groups c1_ INNER JOIN cms_groups c2_ ON c1_.group_id = c2_.id WHERE c1_.user_id = c0_.id AND c2_.id = ?)', - $q2->getSql() + $q->getSql() ); + } + public function testSupportsMemberOfExpressionSelfReferencing() + { // "Get all persons who have $person as a friend." // Tough one: Many-many self-referencing ("friends") with class table inheritance - $q3 = $this->_em->createQuery('SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p WHERE :param MEMBER OF p.friends'); + $q = $this->_em->createQuery('SELECT p FROM Doctrine\Tests\Models\Company\CompanyPerson p WHERE :param MEMBER OF p.friends'); $person = new \Doctrine\Tests\Models\Company\CompanyPerson; $this->_em->getClassMetadata(get_class($person))->setIdentifierValues($person, array('id' => 101)); - $q3->setParameter('param', $person); + $q->setParameter('param', $person); $this->assertEquals( 'SELECT c0_.id AS id0, c0_.name AS name1, c1_.title AS title2, c2_.salary AS salary3, c2_.department AS department4, c2_.startDate AS startDate5, c0_.discr AS discr6, c0_.spouse_id AS spouse_id7, c1_.car_id AS car_id8 FROM company_persons c0_ LEFT JOIN company_managers c1_ ON c0_.id = c1_.id LEFT JOIN company_employees c2_ ON c0_.id = c2_.id WHERE EXISTS (SELECT 1 FROM company_persons_friends c3_ INNER JOIN company_persons c4_ ON c3_.friend_id = c4_.id WHERE c3_.person_id = c0_.id AND c4_.id = ?)', - $q3->getSql() + $q->getSql() + ); + } + + public function testSupportsMemberOfWithSingleValuedAssociation() + { + // Impossible example, but it illustrates the purpose + $q = $this->_em->createQuery('SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.email MEMBER OF u.groups'); + + $this->assertEquals( + 'SELECT c0_.id AS id0 FROM cms_users c0_ WHERE EXISTS (SELECT 1 FROM cms_users_groups c1_ INNER JOIN cms_groups c2_ ON c1_.group_id = c2_.id WHERE c1_.user_id = c0_.id AND c2_.id = c0_.email_id)', + $q->getSql() + ); + } + + public function testSupportsMemberOfWithIdentificationVariable() + { + // Impossible example, but it illustrates the purpose + $q = $this->_em->createQuery('SELECT u.id FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u MEMBER OF u.groups'); + + $this->assertEquals( + 'SELECT c0_.id AS id0 FROM cms_users c0_ WHERE EXISTS (SELECT 1 FROM cms_users_groups c1_ INNER JOIN cms_groups c2_ ON c1_.group_id = c2_.id WHERE c1_.user_id = c0_.id AND c2_.id = c0_.id)', + $q->getSql() ); } From a26990c3e8693134a2789c4c7c57d6f053f01fcf Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Sun, 4 Dec 2011 02:14:47 -0500 Subject: [PATCH 13/15] DDC-1457: Fixed wrong docblock. --- lib/Doctrine/ORM/UnitOfWork.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 25d45fcd0..803e6683c 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -2147,7 +2147,7 @@ class UnitOfWork implements PropertyChangedListener if ($this->commitOrderCalculator === null) { $this->commitOrderCalculator = new Internal\CommitOrderCalculator; } - + return $this->commitOrderCalculator; } @@ -2652,7 +2652,7 @@ class UnitOfWork implements PropertyChangedListener * * @param string $entityName The name of the Entity. * - * @return Doctrine\ORM\Persisters\AbstractEntityPersister + * @return Doctrine\ORM\Persisters\BasicEntityPersister */ public function getEntityPersister($entityName) { From 0380d5ae580dcbefd63051028fc1ab78599fda6c Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Sun, 4 Dec 2011 02:41:54 -0500 Subject: [PATCH 14/15] Implemented multiple enhancements in InExpression support for DQL. Fixed DDC-1472 and DDC-1416. --- lib/Doctrine/ORM/Query/AST/InExpression.php | 6 +++--- lib/Doctrine/ORM/Query/Parser.php | 2 +- lib/Doctrine/ORM/Query/SqlWalker.php | 3 +-- .../Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php | 8 ++++---- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/Doctrine/ORM/Query/AST/InExpression.php b/lib/Doctrine/ORM/Query/AST/InExpression.php index b1da40156..15c517dc0 100644 --- a/lib/Doctrine/ORM/Query/AST/InExpression.php +++ b/lib/Doctrine/ORM/Query/AST/InExpression.php @@ -35,13 +35,13 @@ namespace Doctrine\ORM\Query\AST; class InExpression extends Node { public $not; - public $pathExpression; + public $expression; public $literals = array(); public $subselect; - public function __construct($pathExpression) + public function __construct($expression) { - $this->pathExpression = $pathExpression; + $this->expression = $expression; } public function dispatch($sqlWalker) diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index b63af9ac8..6c8f515f6 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -2720,7 +2720,7 @@ class Parser */ public function InExpression() { - $inExpression = new AST\InExpression($this->SingleValuedPathExpression()); + $inExpression = new AST\InExpression($this->ArithmeticExpression()); if ($this->_lexer->isNextToken(Lexer::T_NOT)) { $this->match(Lexer::T_NOT); diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 5826b82d4..6d0f926ad 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -1727,8 +1727,7 @@ class SqlWalker implements TreeWalker */ public function walkInExpression($inExpr) { - $sql = $this->walkPathExpression($inExpr->pathExpression) - . ($inExpr->not ? ' NOT' : '') . ' IN ('; + $sql = $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN ('; $sql .= ($inExpr->subselect) ? $this->walkSubselect($inExpr->subselect) diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 031ec2138..7a7b7cd3f 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -451,8 +451,8 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase public function testSupportsSingleValuedInExpressionWithoutSpacesInWherePart() { $this->assertSqlGeneration( - "SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id IN(46)", - "SELECT c0_.name AS name0 FROM cms_users c0_ WHERE c0_.id IN (46)" + "SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE IDENTITY(u.email) IN(46)", + "SELECT c0_.name AS name0 FROM cms_users c0_ WHERE c0_.email_id IN (46)" ); } @@ -467,8 +467,8 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase public function testSupportsNotInExpressionInWherePart() { $this->assertSqlGeneration( - 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id NOT IN (1)', - 'SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE c0_.id NOT IN (1)' + 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE :id NOT IN (1)', + 'SELECT c0_.id AS id0, c0_.status AS status1, c0_.username AS username2, c0_.name AS name3 FROM cms_users c0_ WHERE ? NOT IN (1)' ); } From 33c68df3bab8c39b0f04e20a02d46891033593dc Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Mon, 5 Dec 2011 17:35:49 -0200 Subject: [PATCH 15/15] Fixed DDC-1170 --- lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 4 + .../ORM/Mapping/Driver/YamlDriver.php | 4 + .../ORM/Mapping/AbstractMappingDriverTest.php | 80 +++++++++++++++++++ ...ctrine.Tests.ORM.Mapping.DDC1170Entity.php | 16 ++++ ...ne.Tests.ORM.Mapping.DDC1170Entity.dcm.xml | 15 ++++ ...ne.Tests.ORM.Mapping.DDC1170Entity.dcm.yml | 10 +++ 6 files changed, 129 insertions(+) create mode 100644 tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.DDC1170Entity.php create mode 100644 tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.DDC1170Entity.dcm.xml create mode 100644 tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.DDC1170Entity.dcm.yml diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index 6f655d0db..8228ba1ca 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -232,6 +232,10 @@ class XmlDriver extends AbstractFileDriver if (isset($idElement['column'])) { $mapping['columnName'] = (string)$idElement['column']; } + + if (isset($idElement['column-definition'])) { + $mapping['columnDefinition'] = (string)$idElement['column-definition']; + } $metadata->mapField($mapping); diff --git a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php index a33c19b6b..4ef66f9f8 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php @@ -181,6 +181,10 @@ class YamlDriver extends AbstractFileDriver if (isset($idElement['length'])) { $mapping['length'] = $idElement['length']; } + + if (isset($idElement['columnDefinition'])) { + $mapping['columnDefinition'] = $idElement['columnDefinition']; + } $metadata->mapField($mapping); diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 7c2887301..2499d4eb6 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -373,6 +373,25 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals(ClassMetadataInfo::GENERATOR_TYPE_NONE, $class->generatorType); } + + /** + * @group DDC-1170 + */ + public function testIdentifierColumnDefinition() + { + + $class = $this->createClassMetadata(__NAMESPACE__ . '\DDC1170Entity'); + + + $this->assertArrayHasKey('id', $class->fieldMappings); + $this->assertArrayHasKey('value', $class->fieldMappings); + + $this->assertArrayHasKey('columnDefinition', $class->fieldMappings['id']); + $this->assertArrayHasKey('columnDefinition', $class->fieldMappings['value']); + + $this->assertEquals("INT unsigned NOT NULL", $class->fieldMappings['id']['columnDefinition']); + $this->assertEquals("VARCHAR(255) NOT NULL", $class->fieldMappings['value']['columnDefinition']); + } } /** @@ -597,4 +616,65 @@ class Dog extends Animal { } +} + + +/** + * @Entity + */ +class DDC1170Entity +{ + + /** + * @param string $value + */ + function __construct($value = null) + { + $this->value = $value; + } + + /** + * @Id + * @GeneratedValue(strategy="NONE") + * @Column(type="integer", columnDefinition = "INT unsigned NOT NULL") + **/ + private $id; + + /** + * @Column(columnDefinition = "VARCHAR(255) NOT NULL") + */ + private $value; + + /** + * @return integer + */ + public function getId() + { + return $this->id; + } + + /** + * @return string + */ + public function getValue() + { + return $this->value; + } + + public static function loadMetadata(ClassMetadataInfo $metadata) + { + $metadata->mapField(array( + 'id' => true, + 'fieldName' => 'id', + 'columnDefinition' => 'INT unsigned NOT NULL', + )); + + $metadata->mapField(array( + 'fieldName' => 'value', + 'columnDefinition' => 'VARCHAR(255) NOT NULL' + )); + + $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_NONE); + } + } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.DDC1170Entity.php b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.DDC1170Entity.php new file mode 100644 index 000000000..97f4624ba --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.DDC1170Entity.php @@ -0,0 +1,16 @@ +mapField(array( + 'id' => true, + 'fieldName' => 'id', + 'columnDefinition' => 'INT unsigned NOT NULL', +)); + +$metadata->mapField(array( + 'fieldName' => 'value', + 'columnDefinition' => 'VARCHAR(255) NOT NULL' +)); + +$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_NONE); \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.DDC1170Entity.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.DDC1170Entity.dcm.xml new file mode 100644 index 000000000..9f5ad7fac --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.DDC1170Entity.dcm.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.DDC1170Entity.dcm.yml b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.DDC1170Entity.dcm.yml new file mode 100644 index 000000000..8b2ac518b --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.DDC1170Entity.dcm.yml @@ -0,0 +1,10 @@ +Doctrine\Tests\ORM\Mapping\DDC1170Entity: + type: entity + id: + id: + columnDefinition: INT unsigned NOT NULL + generator: + strategy: NONE + fields: + value: + columnDefinition: VARCHAR(255) NOT NULL \ No newline at end of file