From 794777b21f41dadace84655f7a523d6ec164df87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Wed, 12 Oct 2022 21:18:59 +0200 Subject: [PATCH] Modernize documentation code - migrate to attributes; - add helpful phpdoc annotations; - use typed properties; - add type declarations. Co-authored-by: Alexander M. Turek --- docs/en/cookbook/decorator-pattern.rst | 88 ++---- docs/en/cookbook/working-with-datetime.rst | 29 +- docs/en/reference/advanced-configuration.rst | 8 +- docs/en/reference/association-mapping.rst | 244 ++++++++-------- docs/en/reference/basic-mapping.rst | 27 +- docs/en/reference/best-practices.rst | 10 +- .../en/reference/change-tracking-policies.rst | 43 ++- .../reference/dql-doctrine-query-language.rst | 59 ++-- docs/en/reference/faq.rst | 9 +- docs/en/reference/filters.rst | 2 +- docs/en/reference/inheritance-mapping.rst | 218 +++++++------- docs/en/reference/second-level-cache.rst | 96 +++---- docs/en/reference/security.rst | 25 +- .../transactions-and-concurrency.rst | 30 +- docs/en/reference/unitofwork.rst | 7 +- .../reference/working-with-associations.rst | 118 ++++---- docs/en/reference/working-with-objects.rst | 41 ++- docs/en/tutorials/composite-primary-keys.rst | 161 +++++------ docs/en/tutorials/embeddables.rst | 37 ++- docs/en/tutorials/extra-lazy-associations.rst | 12 +- docs/en/tutorials/getting-started.rst | 265 +++++++----------- docs/en/tutorials/ordered-associations.rst | 16 +- ...eld-association-mappings-in-subclasses.rst | 68 ++--- .../working-with-indexed-associations.rst | 86 +++--- 24 files changed, 754 insertions(+), 945 deletions(-) diff --git a/docs/en/cookbook/decorator-pattern.rst b/docs/en/cookbook/decorator-pattern.rst index cffc45867ab..aaff372e856 100644 --- a/docs/en/cookbook/decorator-pattern.rst +++ b/docs/en/cookbook/decorator-pattern.rst @@ -23,48 +23,32 @@ concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``. namespace Test; - /** - * @Entity - * @InheritanceType("SINGLE_TABLE") - * @DiscriminatorColumn(name="discr", type="string") - * @DiscriminatorMap({"cc" = "Test\Component\ConcreteComponent", - "cd" = "Test\Decorator\ConcreteDecorator"}) - */ + #[Entity] + #[InheritanceType('SINGLE_TABLE')] + #[DiscriminatorColumn(name: 'discr', type: 'string')] + #[DiscriminatorMap(['cc' => Component\ConcreteComponent::class, + 'cd' => Decorator\ConcreteDecorator::class])] abstract class Component { - /** - * @Id @Column(type="integer") - * @GeneratedValue(strategy="AUTO") - */ - protected $id; + #[Id, Column] + #[GeneratedValue(strategy: 'AUTO')] + protected int|null $id = null; - /** @Column(type="string", nullable=true) */ + #[Column(type: 'string', nullable: true)] protected $name; - /** - * Get id - * @return integer $id - */ - public function getId() + public function getId(): int|null { return $this->id; } - /** - * Set name - * @param string $name - */ - public function setName($name) + public function setName(string $name): void { $this->name = $name; } - /** - * Get name - * @return string $name - */ - public function getName() + public function getName(): string { return $this->name; } @@ -86,7 +70,7 @@ purpose of keeping this example simple). use Test\Component; - /** @Entity */ + #[Entity] class ConcreteComponent extends Component {} @@ -103,14 +87,11 @@ use a ``MappedSuperclass`` for this. namespace Test; - /** @MappedSuperclass */ + #[MappedSuperclass] abstract class Decorator extends Component { - - /** - * @OneToOne(targetEntity="Test\Component", cascade={"all"}) - * @JoinColumn(name="decorates", referencedColumnName="id") - */ + #[OneToOne(targetEntity: Component::class, cascade: ['all'])] + #[JoinColumn(name: 'decorates', referencedColumnName: 'id')] protected $decorates; /** @@ -126,25 +107,19 @@ use a ``MappedSuperclass`` for this. * (non-PHPdoc) * @see Test.Component::getName() */ - public function getName() + public function getName(): string { return 'Decorated ' . $this->getDecorates()->getName(); } - /** - * the component being decorated - * @return Component - */ - protected function getDecorates() + /** the component being decorated */ + protected function getDecorates(): Component { return $this->decorates; } - /** - * sets the component being decorated - * @param Component $c - */ - protected function setDecorates(Component $c) + /** sets the component being decorated */ + protected function setDecorates(Component $c): void { $this->decorates = $c; } @@ -187,27 +162,19 @@ of the getSpecial() method to its return value. use Test\Decorator; - /** @Entity */ + #[Entity] class ConcreteDecorator extends Decorator { - /** @Column(type="string", nullable=true) */ - protected $special; + #[Column(type: 'string', nullable: true)] + protected string|null $special = null; - /** - * Set special - * @param string $special - */ - public function setSpecial($special) + public function setSpecial(string|null $special): void { $this->special = $special; } - /** - * Get special - * @return string $special - */ - public function getSpecial() + public function getSpecial(): string|null { return $this->special; } @@ -216,7 +183,7 @@ of the getSpecial() method to its return value. * (non-PHPdoc) * @see Test.Component::getName() */ - public function getName() + public function getName(): string { return '[' . $this->getSpecial() . '] ' . parent::getName(); @@ -270,4 +237,3 @@ objects echo $d->getName(); // prints: [Really] Decorated Test Component 2 - diff --git a/docs/en/cookbook/working-with-datetime.rst b/docs/en/cookbook/working-with-datetime.rst index 95e0687fa41..b877b5c82b0 100644 --- a/docs/en/cookbook/working-with-datetime.rst +++ b/docs/en/cookbook/working-with-datetime.rst @@ -15,13 +15,16 @@ these comparisons are always made **BY REFERENCE**. That means the following cha .. code-block:: php updated->modify("now"); @@ -33,12 +36,14 @@ The way to go would be: .. code-block:: php updated = new \DateTime("now"); + $this->updated = new DateTime("now"); } } @@ -84,16 +89,14 @@ the UTC time at the time of the booking and the timezone the event happened in. namespace DoctrineExtensions\DBAL\Types; + use DateTimeZone; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\DateTimeType; class UTCDateTimeType extends DateTimeType { - /** - * @var \DateTimeZone - */ - private static $utc; + private static DateTimeZone $utc; public function convertToDatabaseValue($value, AbstractPlatform $platform) { @@ -126,10 +129,10 @@ the UTC time at the time of the booking and the timezone the event happened in. return $converted; } - - private static function getUtc(): \DateTimeZone + + private static function getUtc(): DateTimeZone { - return self::$utc ?: self::$utc = new \DateTimeZone('UTC'); + return self::$utc ??= new DateTimeZone('UTC'); } } diff --git a/docs/en/reference/advanced-configuration.rst b/docs/en/reference/advanced-configuration.rst index 70a4fb358e5..e3a76f7e303 100644 --- a/docs/en/reference/advanced-configuration.rst +++ b/docs/en/reference/advanced-configuration.rst @@ -119,10 +119,10 @@ There are currently 5 available implementations: - ``Doctrine\ORM\Mapping\Driver\YamlDriver`` - ``Doctrine\ORM\Mapping\Driver\DriverChain`` -Throughout the most part of this manual the AnnotationDriver is -used in the examples. For information on the usage of the XmlDriver -or YamlDriver please refer to the dedicated chapters -``XML Mapping`` and ``YAML Mapping``. +Throughout the most part of this manual the AttributeDriver is +used in the examples. For information on the usage of the +AnnotationDriver, XmlDriver or YamlDriver please refer to the dedicated +chapters ``Annotation Reference``, ``XML Mapping`` and ``YAML Mapping``. The annotation driver can be configured with a factory method on the ``Doctrine\ORM\Configuration``: diff --git a/docs/en/reference/association-mapping.rst b/docs/en/reference/association-mapping.rst index 8dbcd1f19a6..8d4b06762e5 100644 --- a/docs/en/reference/association-mapping.rst +++ b/docs/en/reference/association-mapping.rst @@ -40,19 +40,17 @@ A many-to-one association is the most common association between objects. Exampl .. code-block:: php */ - private $features; + #[OneToMany(targetEntity: Feature::class, mappedBy: 'product')] + private Collection $features; // ... public function __construct() { @@ -347,16 +340,14 @@ bidirectional many-to-one. } } - /** @Entity */ + #[Entity] class Feature { // ... - /** - * Many features have one product. This is the owning side. - * @ManyToOne(targetEntity="Product", inversedBy="features") - * @JoinColumn(name="product_id", referencedColumnName="id") - */ - private $product; + /** Many features have one product. This is the owning side. */ + #[ManyToOne(targetEntity: Product::class, inversedBy: 'features')] + #[JoinColumn(name: 'product_id', referencedColumnName: 'id')] + private Product|null $product = null; // ... } @@ -424,30 +415,30 @@ The following example sets up such a unidirectional one-to-many association: .. code-block:: php */ - private $phonenumbers; + #[JoinTable(name: 'users_phonenumbers')] + #[JoinColumn(name: 'user_id', referencedColumnName: 'id')] + #[InverseJoinColumn(name: 'phonenumber_id', referencedColumnName: 'id', unique: true)] + #[ManyToMany(targetEntity: 'Phonenumber')] + private Collection $phonenumbers; public function __construct() { - $this->phonenumbers = new \Doctrine\Common\Collections\ArrayCollection(); + $this->phonenumbers = new ArrayCollection(); } // ... } - /** @Entity */ + #[Entity] class Phonenumber { // ... @@ -526,26 +517,25 @@ database perspective is known as an adjacency list approach. .. code-block:: php */ - private $children; + #[OneToMany(targetEntity: Category::class, mappedBy: 'parent')] + private Collection $children; - /** - * Many Categories have One Category. - * @ManyToOne(targetEntity="Category", inversedBy="children") - * @JoinColumn(name="parent_id", referencedColumnName="id") - */ - private $parent; + /** Many Categories have One Category. */ + #[ManyToOne(targetEntity: Category::class, inversedBy: 'children')] + #[JoinColumn(name: 'parent_id', referencedColumnName: 'id')] + private Category|null $parent = null; // ... public function __construct() { - $this->children = new \Doctrine\Common\Collections\ArrayCollection(); + $this->children = new ArrayCollection(); } } @@ -597,29 +587,29 @@ entities: .. code-block:: php */ - private $groups; + #[JoinTable(name: 'users_groups')] + #[JoinColumn(name: 'user_id', referencedColumnName: 'id')] + #[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')] + #[ManyToMany(targetEntity: Group::class)] + private Collection $groups; // ... public function __construct() { - $this->groups = new \Doctrine\Common\Collections\ArrayCollection(); + $this->groups = new ArrayCollection(); } } - /** @Entity */ + #[Entity] class Group { // ... @@ -698,37 +688,39 @@ one is bidirectional. .. code-block:: php */ - private $groups; + #[ManyToMany(targetEntity: Group::class, inversedBy: 'users')] + #[JoinTable(name: 'users_groups')] + private Collection $groups; public function __construct() { - $this->groups = new \Doctrine\Common\Collections\ArrayCollection(); + $this->groups = new ArrayCollection(); } // ... } - /** @Entity */ + #[Entity] class Group { // ... /** * Many Groups have Many Users. - * @ManyToMany(targetEntity="User", mappedBy="groups") + * @var Collection */ - private $users; + #[ManyToMany(targetEntity: User::class, mappedBy: 'groups')] + private Collection $users; public function __construct() { - $this->users = new \Doctrine\Common\Collections\ArrayCollection(); + $this->users = new ArrayCollection(); } // ... @@ -806,9 +798,9 @@ understandable: addArticle($this); // synchronously updating inverse side $this->tags[] = $tag; @@ -817,9 +809,9 @@ understandable: class Tag { - private $articles; + private Collection $articles; - public function addArticle(Article $article) + public function addArticle(Article $article): void { $this->articles[] = $article; } @@ -847,30 +839,31 @@ field named ``$friendsWithMe`` and ``$myFriends``. .. code-block:: php */ - private $friendsWithMe; + #[ManyToMany(targetEntity: User::class, mappedBy: 'myFriends')] + private Collection $friendsWithMe; /** * Many Users have many Users. - * @ManyToMany(targetEntity="User", inversedBy="friendsWithMe") - * @JoinTable(name="friends", - * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, - * inverseJoinColumns={@JoinColumn(name="friend_user_id", referencedColumnName="id")} - * ) + * @var Collection */ - private $myFriends; + #[JoinTable(name: 'friends')] + #[JoinColumn(name: 'user_id', referencedColumnName: 'id')] + #[InverseJoinColumn(name: 'friend_user_id', referencedColumnName: 'id')] + #[ManyToMany(targetEntity: 'User', inversedBy: 'friendsWithMe')] + private Collection $myFriends; public function __construct() { - $this->friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection(); - $this->myFriends = new \Doctrine\Common\Collections\ArrayCollection(); + $this->friendsWithMe = new ArrayCollection(); + $this->myFriends = new ArrayCollection(); } // ... @@ -913,8 +906,8 @@ As an example, consider this mapping: .. code-block:: php */ - private $groups; + #[JoinTable(name: 'User_Group')] + #[JoinColumn(name: 'User_id', referencedColumnName: 'id')] + #[InverseJoinColumn(name: 'Group_id', referencedColumnName: 'id')] + #[ManyToMany(targetEntity: Group::class)] + private Collection $groups; // ... } @@ -1072,7 +1063,7 @@ attribute on ``JoinColumn`` will be inherited from PHP type. So that: .. code-block:: php groups = new ArrayCollection(); } - public function getGroups() + public function getGroups(): Collection { return $this->groups; } diff --git a/docs/en/reference/basic-mapping.rst b/docs/en/reference/basic-mapping.rst index 2de9be98e7a..4f543cacb74 100644 --- a/docs/en/reference/basic-mapping.rst +++ b/docs/en/reference/basic-mapping.rst @@ -365,17 +365,15 @@ annotation. .. configuration-block:: - .. code-block:: php + .. code-block:: attribute */ + private Collection $addresses; + /** @var Collection */ + private Collection $articles; + public function __construct() { $this->addresses = new ArrayCollection; $this->articles = new ArrayCollection; diff --git a/docs/en/reference/change-tracking-policies.rst b/docs/en/reference/change-tracking-policies.rst index f8bc8b7cffd..2f72ec80705 100644 --- a/docs/en/reference/change-tracking-policies.rst +++ b/docs/en/reference/change-tracking-policies.rst @@ -49,10 +49,9 @@ This policy can be configured as follows: .. code-block:: php _listeners[] = $listener; } @@ -104,12 +101,12 @@ behaviour: _listeners) { foreach ($this->_listeners as $listener) { @@ -117,8 +114,8 @@ behaviour: } } } - - public function setData($data) + + public function setData($data): void { if ($data != $this->data) { $this->_onPropertyChanged('data', $this->data, $data); @@ -134,18 +131,18 @@ The check whether the new value is different from the old one is not mandatory but recommended. That way you also have full control over when you consider a property changed. -If your entity contains an embeddable, you will need to notify -separately for each property in the embeddable when it changes +If your entity contains an embeddable, you will need to notify +separately for each property in the embeddable when it changes for example: .. code-block:: php equals($this->embeddable)) { // notice the entityField.embeddableField notation for referencing the property @@ -178,5 +175,3 @@ The positive point and main advantage of this policy is its effectiveness. It has the best performance characteristics of the 3 policies with larger units of work and a flush() operation is very cheap when nothing has changed. - - diff --git a/docs/en/reference/dql-doctrine-query-language.rst b/docs/en/reference/dql-doctrine-query-language.rst index 2c045ba7706..57b301bd672 100644 --- a/docs/en/reference/dql-doctrine-query-language.rst +++ b/docs/en/reference/dql-doctrine-query-language.rst @@ -672,18 +672,18 @@ The same restrictions apply for the reference of related entities. DQL DELETE statements are ported directly into an SQL DELETE statement. Therefore, some limitations apply: - + - Lifecycle events for the affected entities are not executed. - - A cascading ``remove`` operation (as indicated e. g. by ``cascade={"remove"}`` - or ``cascade={"all"}`` in the mapping configuration) is not being performed + - A cascading ``remove`` operation (as indicated e. g. by ``cascade: ['remove']`` + or ``cascade: ['all']`` in the mapping configuration) is not being performed for associated entities. You can rely on database level cascade operations by configuring each join column with the ``onDelete`` option. - Checks for the version column are bypassed if they are not explicitly added to the WHERE clause of the query. - - When you rely on one of these features, one option is to use the - ``EntityManager#remove($entity)`` method. This, however, is costly performance-wise: - It means collections and related entities are fetched into memory + + When you rely on one of these features, one option is to use the + ``EntityManager#remove($entity)`` method. This, however, is costly performance-wise: + It means collections and related entities are fetched into memory (even if they are marked as lazy). Pulling object graphs into memory on cascade can cause considerable performance overhead, especially when the cascaded collections are large. Make sure to weigh the benefits and downsides. @@ -864,36 +864,26 @@ scenario it is a generic Person and Employee example: 'Person', 'employee' => 'Employee'])] class Person { - /** - * @Id @Column(type="integer") - * @GeneratedValue - */ - protected $id; + #[Id, Column(type: 'integer')] + #[GeneratedValue] + protected int|null $id = null; - /** - * @Column(type="string", length=50) - */ - protected $name; + #[Column(type: 'string', length: 50)] + protected string $name; // ... } - /** - * @Entity - */ + #[Entity] class Employee extends Person { - /** - * @Column(type="string", length=50) - */ + #[Column(type: 'string', length: 50)] private $department; // ... @@ -959,12 +949,11 @@ table, you just need to change the inheritance type from .. code-block:: php 'Person', 'employee' => 'Employee'])] class Person { // ... @@ -1841,5 +1830,3 @@ Functions "LOWER" "(" StringPrimary ")" | "UPPER" "(" StringPrimary ")" | "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")" - - diff --git a/docs/en/reference/faq.rst b/docs/en/reference/faq.rst index d5e86863a76..06edf9458b9 100644 --- a/docs/en/reference/faq.rst +++ b/docs/en/reference/faq.rst @@ -32,11 +32,12 @@ upon insert: class User { - const STATUS_DISABLED = 0; - const STATUS_ENABLED = 1; + private const STATUS_DISABLED = 0; + private const STATUS_ENABLED = 1; - private $algorithm = "sha1"; - private $status = self:STATUS_DISABLED; + private string $algorithm = "sha1"; + /** @var self::STATUS_* */ + private int $status = self:STATUS_DISABLED; } . diff --git a/docs/en/reference/filters.rst b/docs/en/reference/filters.rst index deb0a22e391..ba80851bd3c 100644 --- a/docs/en/reference/filters.rst +++ b/docs/en/reference/filters.rst @@ -42,7 +42,7 @@ proper quoting of parameters. class MyLocaleFilter extends SQLFilter { - public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias) + public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string { // Check if the entity implements the LocalAware interface if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) { diff --git a/docs/en/reference/inheritance-mapping.rst b/docs/en/reference/inheritance-mapping.rst index 8695b50d296..183769a9b02 100644 --- a/docs/en/reference/inheritance-mapping.rst +++ b/docs/en/reference/inheritance-mapping.rst @@ -38,38 +38,36 @@ Example: use Doctrine\ORM\Mapping\MappedSuperclass; use Doctrine\ORM\Mapping\Entity; - /** @MappedSuperclass */ + #[MappedSuperclass] class Person { - /** @Column(type="integer") */ - protected $mapped1; - /** @Column(type="string") */ - protected $mapped2; - /** - * @OneToOne(targetEntity="Toothbrush") - * @JoinColumn(name="toothbrush_id", referencedColumnName="id") - */ - protected $toothbrush; + #[Column(type: 'integer')] + protected int $mapped1; + #[Column(type: 'string')] + protected string $mapped2; + #[OneToOne(targetEntity: Toothbrush::class)] + #[JoinColumn(name: 'toothbrush_id', referencedColumnName: 'id')] + protected Toothbrush|null $toothbrush = null; // ... more fields and methods } - - /** @Entity */ + + #[Entity] class Employee extends Person { - /** @Id @Column(type="integer") */ - private $id; - /** @Column(type="string") */ - private $name; - + #[Id, Column(type: 'integer')] + private int|null $id = null; + #[Column(type: 'string')] + private string $name; + // ... more fields and methods } - /** @Entity */ + #[Entity] class Toothbrush { - /** @Id @Column(type="integer") */ - private $id; + #[Id, Column(type: 'integer')] + private int|null $id = null; // ... more fields and methods } @@ -100,31 +98,27 @@ Example: .. configuration-block:: .. code-block:: php - + Person::class, 'employee' => Employee::class])] class Person { // ... } - - /** - * @Entity - */ + + #[Entity] class Employee extends Person { // ... } .. code-block:: yaml - + MyProject\Model\Person: type: entity inheritanceType: SINGLE_TABLE @@ -134,29 +128,30 @@ Example: discriminatorMap: person: Person employee: Employee - + MyProject\Model\Employee: type: entity - + Things to note: -- The @InheritanceType and @DiscriminatorColumn must be specified - on the topmost class that is part of the mapped entity hierarchy. -- The @DiscriminatorMap specifies which values of the +- The ``#[InheritanceType]`` and ``#[DiscriminatorColumn]`` must be + specified on the topmost class that is part of the mapped entity + hierarchy. +- The ``#[DiscriminatorMap]`` specifies which values of the discriminator column identify a row as being of a certain type. In the case above a value of "person" identifies a row as being of type ``Person`` and "employee" identifies a row as being of type ``Employee``. - All entity classes that is part of the mapped entity hierarchy (including the topmost class) should be specified in the - @DiscriminatorMap. In the case above Person class included. + ``#[DiscriminatorMap]``. In the case above Person class included. - The names of the classes in the discriminator map do not need to be fully qualified if the classes are contained in the same namespace as the entity class on which the discriminator map is applied. - If no discriminator map is provided, then the map is generated - automatically. The automatically generated discriminator map + automatically. The automatically generated discriminator map contains the lowercase short name of each class as key. Design-time considerations @@ -212,19 +207,17 @@ Example: Person::class, 'employee' => Employee::class])] class Person { // ... } - - /** @Entity */ + + #[Entity] class Employee extends Person { // ... @@ -233,10 +226,10 @@ Example: Things to note: -- The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap - must be specified on the topmost class that is part of the mapped - entity hierarchy. -- The @DiscriminatorMap specifies which values of the +- The ``#[InheritanceType]``, ``#[DiscriminatorColumn]`` and + ``#[DiscriminatorMap]`` must be specified on the topmost class that is + part of the mapped entity hierarchy. +- The ``#[DiscriminatorMap]`` specifies which values of the discriminator column identify a row as being of which type. In the case above a value of "person" identifies a row as being of type ``Person`` and "employee" identifies a row as being of type @@ -246,7 +239,7 @@ Things to note: namespace as the entity class on which the discriminator map is applied. - If no discriminator map is provided, then the map is generated - automatically. The automatically generated discriminator map + automatically. The automatically generated discriminator map contains the lowercase short name of each class as key. .. note:: @@ -290,7 +283,7 @@ be a leaf entity in the inheritance hierarchy, (ie. have no subclasses). Otherwise Doctrine *CANNOT* create proxy instances of this entity and will *ALWAYS* load the entity eagerly. -There is also another important performance consideration that it is *NOT POSSIBLE* +There is also another important performance consideration that it is *NOT POSSIBLE* to query for the base entity without any LEFT JOINs to the sub-types. SQL Schema considerations @@ -335,48 +328,42 @@ Example: */ + #[JoinTable(name: 'users_groups')] + #[JoinColumn(name: 'user_id', referencedColumnName: 'id')] + #[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')] + #[ManyToMany(targetEntity: 'Group', inversedBy: 'users')] + protected Collection $groups; + + #[ManyToOne(targetEntity: 'Address')] + #[JoinColumn(name: 'address_id', referencedColumnName: 'id')] + protected Address|null $address = null; } // admin mapping namespace MyProject\Model; - /** - * @Entity - * @AssociationOverrides({ - * @AssociationOverride(name="groups", - * joinTable=@JoinTable( - * name="users_admingroups", - * joinColumns=@JoinColumn(name="adminuser_id"), - * inverseJoinColumns=@JoinColumn(name="admingroup_id") - * ) - * ), - * @AssociationOverride(name="address", - * joinColumns=@JoinColumn( - * name="adminaddress_id", referencedColumnName="id" - * ) - * ) - * }) - */ + + #[Entity] + #[AssociationOverrides([ + new AssociationOverride( + name: 'groups', + joinTable: new JoinTable( + name: 'users_admingroups', + ), + joinColumns: [new JoinColumn(name: 'adminuser_id')], + inverseJoinColumns: [new JoinColumn(name: 'admingroup_id')] + ), + new AssociationOverride( + name: 'address', + joinColumns: [new JoinColumn(name: 'adminaddress_id', referencedColumnName: 'id')] + ) + ])] class Admin extends User { } @@ -495,42 +482,41 @@ Could be used by an entity that extends a mapped superclass to override a field */ + #[Cache(usage: 'NONSTRICT_READ_WRITE')] + #[OneToMany(targetEntity: City::class, mappedBy: 'state')] + protected Collection $cities; // other properties and methods } @@ -673,23 +658,18 @@ For performance reasons the cache API does not extract from composite primary ke .. code-block:: php $userInput */ + public function fromArray(array $userInput): void { foreach ($userInput as $key => $value) { $this->$key = $value; @@ -118,7 +119,7 @@ entity might look like this: } } -Now the possiblity of mass-asignment exists on this entity and can +Now the possiblity of mass-assignment exists on this entity and can be exploited by attackers to set the "isAdmin" flag to true on any object when you pass the whole request data to this method like: diff --git a/docs/en/reference/transactions-and-concurrency.rst b/docs/en/reference/transactions-and-concurrency.rst index c32ea898982..e57d3d84842 100644 --- a/docs/en/reference/transactions-and-concurrency.rst +++ b/docs/en/reference/transactions-and-concurrency.rst @@ -195,8 +195,8 @@ example we'll use an integer. class User { // ... - /** @Version @Column(type="integer") */ - private $version; + #[Version, Column(type: 'integer')] + private int $version; // ... } @@ -228,8 +228,8 @@ timestamp or datetime): class User { // ... - /** @Version @Column(type="datetime") */ - private $version; + #[Version, Column(type: 'datetime')] + private DateTime $version; // ... } @@ -279,15 +279,15 @@ either when calling ``EntityManager#find()``: find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion); - + // do the work - + $em->flush(); } catch(OptimisticLockException $e) { echo "Sorry, but someone else has already changed this entity. Please apply the changes again!"; @@ -300,16 +300,16 @@ Or you can use ``EntityManager#lock()`` to find out: find('User', $theEntityId); - + try { // assert version $em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion); - + } catch(OptimisticLockException $e) { echo "Sorry, but someone else has already changed this entity. Please apply the changes again!"; } @@ -348,7 +348,7 @@ See the example code, The form (GET Request): find('BlogPost', 123456); - + echo ''; echo ''; @@ -359,7 +359,7 @@ And the change headline action (POST Request): find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion); .. _transactions-and-concurrency_pessimistic-locking: @@ -405,5 +405,3 @@ You can use pessimistic locks in three different scenarios: ``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)`` or ``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ)`` - - diff --git a/docs/en/reference/unitofwork.rst b/docs/en/reference/unitofwork.rst index 6955f334286..bded6247ebb 100644 --- a/docs/en/reference/unitofwork.rst +++ b/docs/en/reference/unitofwork.rst @@ -17,7 +17,7 @@ ask for an entity with a specific ID twice, it will return the same instance: .. code-block:: php - public function testIdentityMap() + public function testIdentityMap(): void { $objectA = $this->entityManager->find('EntityName', 1); $objectB = $this->entityManager->find('EntityName', 1); @@ -34,7 +34,7 @@ will still end up with the same reference: .. code-block:: php - public function testIdentityMapReference() + public function testIdentityMapReference(): void { $objectA = $this->entityManager->getReference('EntityName', 1); // check for proxyinterface @@ -104,7 +104,7 @@ How Doctrine Detects Changes Doctrine is a data-mapper that tries to achieve persistence-ignorance (PI). This means you map php objects into a relational database that don't necessarily know about the database at all. A natural question would now be, -"how does Doctrine even detect objects have changed?". +"how does Doctrine even detect objects have changed?". For this Doctrine keeps a second map inside the UnitOfWork. Whenever you fetch an object from the database Doctrine will keep a copy of all the properties and @@ -202,4 +202,3 @@ ClassMetadataFactory ~~~~~~~~~~~~~~~~~~~~ tbr - diff --git a/docs/en/reference/working-with-associations.rst b/docs/en/reference/working-with-associations.rst index 510d85f05b3..fb1b465b2ac 100644 --- a/docs/en/reference/working-with-associations.rst +++ b/docs/en/reference/working-with-associations.rst @@ -32,62 +32,62 @@ information about its type and if it's the owning or inverse side. .. code-block:: php */ - private $favorites; + #[ManyToMany(targetEntity: Comment::class, inversedBy: 'userFavorites')] + #[JoinTable(name: 'user_favorite_comments')] + private Collection $favorites; /** * Unidirectional - Many users have marked many comments as read * - * @ManyToMany(targetEntity="Comment") - * @JoinTable(name="user_read_comments") + * @var Collection */ - private $commentsRead; + #[ManyToMany(targetEntity: Comment::class)] + #[JoinTable(name: 'user_read_comments')] + private Collection $commentsRead; /** * Bidirectional - One-To-Many (INVERSE SIDE) * - * @OneToMany(targetEntity="Comment", mappedBy="author") + * @var Collection */ - private $commentsAuthored; + #[OneToMany(targetEntity: Comment::class, mappedBy: 'author')] + private Collection $commentsAuthored; - /** - * Unidirectional - Many-To-One - * - * @ManyToOne(targetEntity="Comment") - */ - private $firstComment; + /** Unidirectional - Many-To-One */ + #[ManyToOne(targetEntity: Comment::class)] + private Comment|null $firstComment = null; } - /** @Entity */ + #[Entity] class Comment { - /** @Id @GeneratedValue @Column(type="string") */ - private $id; + #[Id, GeneratedValue, Column] + private string $id; /** * Bidirectional - Many comments are favorited by many users (INVERSE SIDE) * - * @ManyToMany(targetEntity="User", mappedBy="favorites") + * @var Collection */ - private $userFavorites; + #[ManyToMany(targetEntity: User::class, mappedBy: 'favorites')] + private Collection $userFavorites; /** * Bidirectional - Many Comments are authored by one user (OWNING SIDE) - * - * @ManyToOne(targetEntity="User", inversedBy="commentsAuthored") */ - private $author; + #[ManyToOne(targetEntity: User::class, inversedBy: 'commentsAuthored')] + private User|null $author = null; } This two entities generate the following MySQL Schema (Foreign Key @@ -132,11 +132,13 @@ relations of the ``User``: class User { // ... - public function getReadComments() { + /** @return Collection */ + public function getReadComments(): Collection { return $this->commentsRead; } - public function setFirstComment(Comment $c) { + /** @param Collection $c */ + public function setFirstComment(Comment $c): void { $this->firstComment = $c; } } @@ -172,11 +174,13 @@ fields on both sides: { // .. - public function getAuthoredComments() { + /** @return Collection */ + public function getAuthoredComments(): Collection { return $this->commentsAuthored; } - public function getFavoriteComments() { + /** @return Collection */ + public function getFavoriteComments(): Collection { return $this->favorites; } } @@ -185,11 +189,12 @@ fields on both sides: { // ... - public function getUserFavorites() { + /** @return Collection */ + public function getUserFavorites(): Collection { return $this->userFavorites; } - public function setAuthor(User $author = null) { + public function setAuthor(User|null $author = null): void { $this->author = $author; } } @@ -292,12 +297,12 @@ example that encapsulate much of the association management code: class User { // ... - public function markCommentRead(Comment $comment) { + public function markCommentRead(Comment $comment): void { // Collections implement ArrayAccess $this->commentsRead[] = $comment; } - public function addComment(Comment $comment) { + public function addComment(Comment $comment): void { if (count($this->commentsAuthored) == 0) { $this->setFirstComment($comment); } @@ -305,16 +310,16 @@ example that encapsulate much of the association management code: $comment->setAuthor($this); } - private function setFirstComment(Comment $c) { + private function setFirstComment(Comment $c): void { $this->firstComment = $c; } - public function addFavorite(Comment $comment) { + public function addFavorite(Comment $comment): void { $this->favorites->add($comment); $comment->addUserFavorite($this); } - public function removeFavorite(Comment $comment) { + public function removeFavorite(Comment $comment): void { $this->favorites->removeElement($comment); $comment->removeUserFavorite($this); } @@ -324,11 +329,11 @@ example that encapsulate much of the association management code: { // .. - public function addUserFavorite(User $user) { + public function addUserFavorite(User $user): void { $this->userFavorites[] = $user; } - public function removeUserFavorite(User $user) { + public function removeUserFavorite(User $user): void { $this->userFavorites->removeElement($user); } } @@ -356,7 +361,8 @@ the details inside the classes can be challenging. */ + public function getReadComments(): array { return $this->commentsRead->toArray(); } } @@ -437,8 +443,10 @@ only accessing it through the User entity: // User entity class User { - private $id; - private $comments; + private int $id; + + /** @var Collection */ + private Collection $comments; public function __construct() { @@ -464,11 +472,8 @@ If you then set up the cascading to the ``User#commentsAuthored`` property... class User { // ... - /** - * Bidirectional - One-To-Many (INVERSE SIDE) - * - * @OneToMany(targetEntity="Comment", mappedBy="author", cascade={"persist", "remove"}) - */ + /** Bidirectional - One-To-Many (INVERSE SIDE) */ + #[OneToMany(targetEntity: Comment::class, mappedBy: 'author', cascade: ['persist', 'remove'])] private $commentsAuthored; // ... } @@ -577,31 +582,30 @@ and StandingData: use Doctrine\Common\Collections\ArrayCollection; - /** - * @Entity - */ + #[Entity] class Contact { - /** @Id @Column(type="integer") @GeneratedValue */ - private $id; + #[Id, Column(type: 'integer'), GeneratedValue] + private int|null $id = null; - /** @OneToOne(targetEntity="StandingData", cascade={"persist"}, orphanRemoval=true) */ - private $standingData; + #[OneToOne(targetEntity: StandingData::class, cascade: ['persist'], orphanRemoval: true)] + private StandingData|null $standingData = null; - /** @OneToMany(targetEntity="Address", mappedBy="contact", cascade={"persist"}, orphanRemoval=true) */ - private $addresses; + /** @var Collection */ + #[OneToMany(targetEntity: Address::class, mappedBy: 'contact', cascade: ['persist'], orphanRemoval: true)] + private Collection $addresses; public function __construct() { $this->addresses = new ArrayCollection(); } - public function newStandingData(StandingData $sd) + public function newStandingData(StandingData $sd): void { $this->standingData = $sd; } - public function removeAddress($pos) + public function removeAddress(int $pos): void { unset($this->addresses[$pos]); } diff --git a/docs/en/reference/working-with-objects.rst b/docs/en/reference/working-with-objects.rst index 2364530ec78..ec3e52e7683 100644 --- a/docs/en/reference/working-with-objects.rst +++ b/docs/en/reference/working-with-objects.rst @@ -95,28 +95,29 @@ from newly opened EntityManager. .. code-block:: php */ + #[OneToMany(targetEntity: Comment::class, mappedBy: 'article')] + private Collection $comments; public function __construct() { $this->comments = new ArrayCollection(); } - public function getAuthor() { return $this->author; } - public function getComments() { return $this->comments; } + public function getAuthor(): User|null { return $this->author; } + public function getComments(): Collection { return $this->comments; } } $article = $em->find('Article', 1); @@ -170,12 +171,12 @@ methods along the lines of the ``getName()`` method shown below: _load(); return parent::getName(); @@ -339,8 +340,8 @@ in multiple ways with very different performance impacts. .. note:: Calling ``remove`` on an entity will remove the object from the identity - map and therefore detach it. Querying the same entity again, for example - via a lazy loaded relation, will return a new object. + map and therefore detach it. Querying the same entity again, for example + via a lazy loaded relation, will return a new object. Detaching entities @@ -843,12 +844,11 @@ in a central location. */ + public function getAllAdminUsers(): Collection { return $this->_em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"') ->getResult(); @@ -871,5 +872,3 @@ You can access your repository now by calling: // $em instanceof EntityManager $admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers(); - - diff --git a/docs/en/tutorials/composite-primary-keys.rst b/docs/en/tutorials/composite-primary-keys.rst index 12ad55e53bb..c2b48e290d8 100644 --- a/docs/en/tutorials/composite-primary-keys.rst +++ b/docs/en/tutorials/composite-primary-keys.rst @@ -23,33 +23,28 @@ and year of production as primary keys: .. configuration-block:: - .. code-block:: php + .. code-block:: attribute name = $name; - $this->year = $year; + public function __construct( + #[Id, Column(type: 'string')] + private string $name, + #[Id, Column(type: 'integer')] + private int $year, + ) { } - public function getModelName() + public function getModelName(): string { return $this->name; } - public function getYearOfProduction() + public function getYearOfProduction(): int { return $this->year; } @@ -149,42 +144,37 @@ We keep up the example of an Article with arbitrary attributes, the mapping look use Doctrine\Common\Collections\ArrayCollection; - /** - * @Entity - */ + #[Entity] class Article { - /** @Id @Column(type="integer") @GeneratedValue */ - private $id; - /** @Column(type="string") */ - private $title; + #[Id, Column(type: 'integer'), GeneratedValue] + private int|null $id = null; + #[Column(type: 'string')] + private string $title; - /** - * @OneToMany(targetEntity="ArticleAttribute", mappedBy="article", cascade={"ALL"}, indexBy="attribute") - */ - private $attributes; + /** @var ArrayCollection */ + #[OneToMany(targetEntity: ArticleAttribute::class, mappedBy: 'article', cascade: ['ALL'], indexBy: 'attribute')] + private Collection $attributes; - public function addAttribute($name, $value) + public function addAttribute(string $name, ArticleAttribute $value): void { $this->attributes[$name] = new ArticleAttribute($name, $value, $this); } } - /** - * @Entity - */ + #[Entity] class ArticleAttribute { - /** @Id @ManyToOne(targetEntity="Article", inversedBy="attributes") */ - private $article; + #[Id, ManyToOne(targetEntity: Article::class, inversedBy: 'attributes')] + private Article $article; - /** @Id @Column(type="string") */ - private $attribute; + #[Id, Column(type: 'string')] + private string $attribute; - /** @Column(type="string") */ - private $value; + #[Column(type: 'string')] + private string $value; - public function __construct($name, $value, $article) + public function __construct(string $name, string $value, Article $article) { $this->attribute = $name; $this->value = $value; @@ -202,7 +192,7 @@ We keep up the example of an Article with arbitrary attributes, the mapping look - + @@ -240,22 +230,19 @@ One good example for this is a user-address relationship: .. code-block:: php customer = $customer; + #[Id, Column(type: 'integer'), GeneratedValue] + private int|null $id = null; + + /** @var ArrayCollection */ + #[OneToMany(targetEntity: OrderItem::class, mappedBy: 'order')] + private Collection $items; + + #[Column(type: 'boolean')] + private bool $paid = false; + #[Column(type: 'boolean')] + private bool $shipped = false; + #[Column(type: 'datetime')] + private DateTime $created; + + public function __construct( + #[ManyToOne(targetEntity: Customer::class)] + private Customer $customer, + ) { $this->items = new ArrayCollection(); - $this->created = new \DateTime("now"); + $this->created = new DateTime("now"); } } - /** @Entity */ + #[Entity] class Product { - /** @Id @Column(type="integer") @GeneratedValue */ - private $id; + #[Id, Column(type: 'integer'), GeneratedValue] + private int|null $id = null; - /** @Column(type="string") */ - private $name; + #[Column(type: 'string')] + private string $name; - /** @Column(type="decimal") */ - private $currentPrice; + #[Column(type: 'decimal')] + private float $currentPrice; - public function getCurrentPrice() + public function getCurrentPrice(): float { return $this->currentPrice; } } - /** @Entity */ + #[Entity] class OrderItem { - /** @Id @ManyToOne(targetEntity="Order") */ - private $order; + #[Id, ManyToOne(targetEntity: Order::class)] + private Order|null $order = null; - /** @Id @ManyToOne(targetEntity="Product") */ - private $product; + #[Id, ManyToOne(targetEntity: Product::class)] + private Product|null $product = null; - /** @Column(type="integer") */ - private $amount = 1; + #[Column(type: 'integer')] + private int $amount = 1; - /** @Column(type="decimal") */ - private $offeredPrice; + #[Column(type: 'decimal')] + private float $offeredPrice; - public function __construct(Order $order, Product $product, $amount = 1) + public function __construct(Order $order, Product $product, int $amount = 1) { $this->order = $order; $this->product = $product; diff --git a/docs/en/tutorials/embeddables.rst b/docs/en/tutorials/embeddables.rst index e1da830d943..0ea63c04f48 100644 --- a/docs/en/tutorials/embeddables.rst +++ b/docs/en/tutorials/embeddables.rst @@ -4,7 +4,7 @@ Separating Concerns using Embeddables Embeddables are classes which are not entities themselves, but are embedded in entities and can also be queried in DQL. You'll mostly want to use them to reduce duplication or separating concerns. Value objects such as date range -or address are the primary use case for this feature. +or address are the primary use case for this feature. .. note:: @@ -21,27 +21,27 @@ instead of simply adding the respective columns to the ``User`` class. */ + #[ManyToMany(targetEntity: CmsUser::class, mappedBy: 'groups', fetch: 'EXTRA_LAZY')] + public Collection $users; } .. code-block:: xml @@ -92,4 +89,3 @@ switch to extra lazy as shown in these examples: targetEntity: CmsUser mappedBy: groups fetch: EXTRA_LAZY - diff --git a/docs/en/tutorials/getting-started.rst b/docs/en/tutorials/getting-started.rst index a9200997dd3..5a317337c5d 100644 --- a/docs/en/tutorials/getting-started.rst +++ b/docs/en/tutorials/getting-started.rst @@ -256,14 +256,8 @@ entity definition: // src/Product.php class Product { - /** - * @var int - */ - private $id; - /** - * @var string - */ - private $name; + private int|null $id = null; + private string $name; } When creating entity classes, all of the fields should be ``private``. @@ -514,22 +508,16 @@ but you only need to choose one. use Doctrine\ORM\Mapping as ORM; - /** - * @ORM\Entity - * @ORM\Table(name="products") - */ + #[ORM\Entity] + #[ORM\Table(name: 'products')] class Product { - /** - * @ORM\Id - * @ORM\Column(type="integer") - * @ORM\GeneratedValue - */ - private $id; - /** - * @ORM\Column(type="string") - */ - private $name; + #[ORM\Id] + #[ORM\Column(type: 'integer')] + #[ORM\GeneratedValue] + private int|null $id = null; + #[ORM\Column(type: 'string')] + private string $name; // .. (other code) } @@ -708,49 +696,35 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively. use Doctrine\ORM\Mapping as ORM; - /** - * @ORM\Entity - * @ORM\Table(name="bugs") - */ + #[ORM\Entity] + #[ORM\Table(name: 'bugs')] class Bug { - /** - * @ORM\Id - * @ORM\Column(type="integer") - * @ORM\GeneratedValue - * @var int - */ - private $id; + #[ORM\Id] + #[ORM\Column(type: 'integer')] + #[ORM\GeneratedValue] + private int $id; - /** - * @ORM\Column(type="string") - * @var string - */ - private $description; + #[ORM\Column(type: 'string')] + private string $description; - /** - * @ORM\Column(type="datetime") - * @var DateTime - */ - private $created; + #[ORM\Column(type: 'datetime')] + private DateTime $created; - /** - * @ORM\Column(type="string") - * @var string - */ - private $status; + #[ORM\Column(type: 'string')] + private string $status; - public function getId() + public function getId(): int|null { return $this->id; } - public function getDescription() + public function getDescription(): string { return $this->description; } - public function setDescription($description) + public function setDescription(string $description): void { $this->description = $description; } @@ -760,17 +734,17 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively. $this->created = $created; } - public function getCreated() + public function getCreated(): DateTime { return $this->created; } - public function setStatus($status) + public function setStatus($status): void { $this->status = $status; } - public function getStatus() + public function getStatus():string { return $this->status; } @@ -783,37 +757,31 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively. use Doctrine\ORM\Mapping as ORM; - /** - * @ORM\Entity - * @ORM\Table(name="users") - */ + #[ORM\Entity] + #[ORM\Table(name: 'users')] class User { - /** - * @ORM\Id - * @ORM\GeneratedValue - * @ORM\Column(type="integer") - * @var int - */ - private $id; + /** @var int */ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: 'integer')] + private int|null $id = null; - /** - * @ORM\Column(type="string") - * @var string - */ - private $name; + /** @var string */ + #[ORM\Column(type: 'string')] + private string $name; - public function getId() + public function getId(): int|null { return $this->id; } - public function getName() + public function getName(): string { return $this->name; } - public function setName($name) + public function setName(string $name): void { $this->name = $name; } @@ -842,13 +810,16 @@ domain model to match the requirements: */ + private Collection $products; public function __construct() { @@ -866,8 +837,10 @@ domain model to match the requirements: { // ... (previous code) - private $reportedBugs; - private $assignedBugs; + /** @var Collection */ + private Collection $reportedBugs; + /** @var Collection */ + private Collection $assignedBugs; public function __construct() { @@ -942,27 +915,27 @@ the bi-directional reference: { // ... (previous code) - private $engineer; - private $reporter; + private User $engineer; + private User $reporter; - public function setEngineer(User $engineer) + public function setEngineer(User $engineer): void { $engineer->assignedToBug($this); $this->engineer = $engineer; } - public function setReporter(User $reporter) + public function setReporter(User $reporter): void { $reporter->addReportedBug($this); $this->reporter = $reporter; } - public function getEngineer() + public function getEngineer(): User { return $this->engineer; } - public function getReporter() + public function getReporter(): User { return $this->reporter; } @@ -976,15 +949,17 @@ the bi-directional reference: { // ... (previous code) - private $reportedBugs; - private $assignedBugs; + /** @var Collection */ + private Collection $reportedBugs; + /** @var Collection */ + private Collection $assignedBugs; - public function addReportedBug(Bug $bug) + public function addReportedBug(Bug $bug): void { $this->reportedBugs[] = $bug; } - public function assignedToBug(Bug $bug) + public function assignedToBug(Bug $bug): void { $this->assignedBugs[] = $bug; } @@ -1028,14 +1003,16 @@ the database that points from Bugs to Products. { // ... (previous code) - private $products; + /** @var Collection */ + private Collection $products; - public function assignToProduct(Product $product) + public function assignToProduct(Product $product): void { $this->products[] = $product; } - public function getProducts() + /** @return Collection */ + public function getProducts(): Collection { return $this->products; } @@ -1051,50 +1028,36 @@ the ``Product`` before: */ + #[ORM\ManyToMany(targetEntity: Product::class)] + private Collection $products; // ... (other code) } @@ -1190,36 +1153,24 @@ Finally, we'll add metadata mappings for the ``User`` entity. use Doctrine\ORM\Mapping as ORM; - /** - * @ORM\Entity - * @ORM\Table(name="users") - */ + #[ORM\Entity] + #[ORM\Table(name: 'users')] class User { - /** - * @ORM\Id - * @ORM\GeneratedValue - * @ORM\Column(type="integer") - * @var int - */ - private $id; - - /** - * @ORM\Column(type="string") - * @var string - */ - private $name; - - /** - * @ORM\OneToMany(targetEntity="Bug", mappedBy="reporter") - * @var Bug[] An ArrayCollection of Bug objects. - */ - private $reportedBugs; - - /** - * @ORM\OneToMany(targetEntity="Bug", mappedBy="engineer") - * @var Bug[] An ArrayCollection of Bug objects. - */ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: 'integer')] + private int|null $id = null; + + #[ORM\Column(type: 'string')] + private string $name; + + /** @var Collection An ArrayCollection of Bug objects. */ + #[ORM\OneToMany(targetEntity: Bug::class, mappedBy: 'reporter')] + private Collection $reportedBugs; + + /** @var Collection An ArrayCollection of Bug objects. */ + #[ORM\OneToMany(targetEntity: Bug::class, mappedBy: 'engineer')] private $assignedBugs; // .. (other code) diff --git a/docs/en/tutorials/ordered-associations.rst b/docs/en/tutorials/ordered-associations.rst index cb0e64a498a..a103c74a8b4 100644 --- a/docs/en/tutorials/ordered-associations.rst +++ b/docs/en/tutorials/ordered-associations.rst @@ -14,19 +14,17 @@ can specify the ``@OrderBy`` in the following way: .. configuration-block:: - .. code-block:: php + .. code-block:: attribute "ASC"])] + private Collection $groups; } .. code-block:: xml @@ -106,5 +104,3 @@ You can reverse the order with an explicit DQL ORDER BY: .. code-block:: sql SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC - - diff --git a/docs/en/tutorials/override-field-association-mappings-in-subclasses.rst b/docs/en/tutorials/override-field-association-mappings-in-subclasses.rst index de9f0d993da..5a2edc866e1 100644 --- a/docs/en/tutorials/override-field-association-mappings-in-subclasses.rst +++ b/docs/en/tutorials/override-field-association-mappings-in-subclasses.rst @@ -14,40 +14,36 @@ Suppose we have a class ExampleEntityWithOverride. This class uses trait Example .. code-block:: php new Column([ + 'name' => 'foo_overridden', + 'type' => 'integer', + 'length' => 140, + 'nullable' => false, + 'unique' => false, + ]), + ]), + ])] + #[AssociationOverrides([ + new AssociationOverride('bar', [ + 'joinColumns' => new JoinColumn([ + 'name' => 'example_entity_overridden_bar_id', + 'referencedColumnName' => 'id', + ]), + ]), + ])] class ExampleEntityWithOverride { use ExampleTrait; } - /** - * @Entity - */ + #[Entity] class Bar { - /** @Id @Column(type="string") */ + #[Id, Column(type: 'string')] private $id; } @@ -64,19 +60,15 @@ which has mapping metadata that is overridden by the annotation above: */ trait ExampleTrait { - /** @Id @Column(type="string") */ - private $id; + #[Id, Column(type: 'integer')] + private int|null $id = null; - /** - * @Column(name="trait_foo", type="integer", length=100, nullable=true, unique=true) - */ - protected $foo; + #[Column(name: 'trait_foo', type: 'integer', length: 100, nullable: true, unique: true)] + protected int $foo; - /** - * @OneToOne(targetEntity="Bar", cascade={"persist", "merge"}) - * @JoinColumn(name="example_trait_bar_id", referencedColumnName="id") - */ - protected $bar; + #[OneToOne(targetEntity: Bar::class, cascade: ['persist', 'merge'])] + #[JoinColumn(name: 'example_trait_bar_id', referencedColumnName: 'id')] + protected Bar|null $bar = null; } The case for just extending a class would be just the same but: diff --git a/docs/en/tutorials/working-with-indexed-associations.rst b/docs/en/tutorials/working-with-indexed-associations.rst index c38b8c5cae6..e41738737b3 100644 --- a/docs/en/tutorials/working-with-indexed-associations.rst +++ b/docs/en/tutorials/working-with-indexed-associations.rst @@ -36,53 +36,44 @@ The code and mappings for the Market entity looks like this: namespace Doctrine\Tests\Models\StockExchange; use Doctrine\Common\Collections\ArrayCollection; + use Doctrine\Common\Collections\Collection; - /** - * @Entity - * @Table(name="exchange_markets") - */ + #[Entity] + #[Table(name: 'exchange_markets')] class Market { - /** - * @Id @Column(type="integer") @GeneratedValue - * @var int - */ - private $id; - - /** - * @Column(type="string") - * @var string - */ - private $name; - - /** - * @OneToMany(targetEntity="Stock", mappedBy="market", indexBy="symbol") - * @var Stock[] - */ - private $stocks; - - public function __construct($name) + #[Id, Column(type: 'integer'), GeneratedValue] + private int|null $id = null; + + #[Column(type: 'string')] + private string $name; + + /** @var Collection */ + #[OneToMany(targetEntity: Stock::class, mappedBy: 'market', indexBy: 'symbol')] + private Collection $stocks; + + public function __construct(string $name) { $this->name = $name; $this->stocks = new ArrayCollection(); } - public function getId() + public function getId(): int|null { return $this->id; } - public function getName() + public function getName(): string { return $this->name; } - public function addStock(Stock $stock) + public function addStock(Stock $stock): void { $this->stocks[$stock->getSymbol()] = $stock; } - public function getStock($symbol) + public function getStock(string $symbol): Stock { if (!isset($this->stocks[$symbol])) { throw new \InvalidArgumentException("Symbol is not traded on this market."); @@ -91,7 +82,8 @@ The code and mappings for the Market entity looks like this: return $this->stocks[$symbol]; } - public function getStocks() + /** @return array */ + public function getStocks(): array { return $this->stocks->toArray(); } @@ -147,37 +139,27 @@ here are the code and mappings for it: symbol = $symbol; $this->market = $market; $market->addStock($this); } - public function getSymbol() + public function getSymbol(): string { return $this->symbol; } @@ -249,7 +231,7 @@ now query for the market: // $em is the EntityManager $marketId = 1; $symbol = "AAPL"; - + $market = $em->find("Doctrine\Tests\Models\StockExchange\Market", $marketId); // Access the stocks by symbol now: