Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc: Review custom collections and repository docs #2653

Merged
merged 2 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 48 additions & 32 deletions docs/en/reference/custom-collections.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
Custom Collections
==================

.. note::
This feature was introduced in version 1.1
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are v2.9, no need to mention such an old version.


By default, Doctrine uses ``ArrayCollection`` implementation of its ``Collection``
interface to hold both embedded and referenced documents. That collection may then
be wrapped by a ``PersistentCollection`` to allow for change tracking and other
Expand All @@ -16,14 +13,16 @@ persistence-related features.
<?php

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;

#[Document]
class Application
{
// ...

/** @var Collection<Section> */
#[EmbedMany(targetDocument: Section::class)]
private $sections;
public Collection $sections;

public function __construct()
{
Expand All @@ -41,11 +40,6 @@ owning document's class.
Custom Collection Classes
-------------------------

.. note::
You may want to check `malarzm/collections <https://github.com/malarzm/collections>`_
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This package is not compatible with doctrine/collection v2. Consider restoring a link once @malarzm's package is updated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to remove just yet, the package will be compatible soon ™️ :D

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a more serious note, feel free ;)

which provides alternative implementations of Doctrine's ``Collection`` interface and
aims to kickstart development of your own collections.

Using your own ``Collection`` implementation is as simple as specifying the
``collectionClass`` parameter in the ``#[EmbedMany]`` or ``#[ReferenceMany]`` mapping
and ensuring that your custom class is initialized in the owning class' constructor:
Expand All @@ -65,7 +59,7 @@ and ensuring that your custom class is initialized in the owning class' construc
collectionClass: SectionCollection::class,
targetDocument: Section::class,
)]
private $sections;
private Collection $sections;

public function __construct()
{
Expand Down Expand Up @@ -104,12 +98,9 @@ Alternatively, you may want to implement the whole class from scratch:

class SectionCollection implements Collection
{
private $elements = [];

public function __construct(array $elements = [])
{
$this->elements = $elements;
}
public function __construct(
private array $elements = []
) {}

// your implementation of all methods interface requires
}
Expand All @@ -120,36 +111,61 @@ Taking Control of the Collection's Constructor
By default, Doctrine assumes that it can instantiate your collections in same
manner as an ``ArrayCollection`` (i.e. the only parameter is an optional PHP
array); however, you may want to inject additional dependencies into your
custom collection class(es). This will require you to create a
`PersistentCollectionFactory implementation <https://github.com/doctrine/mongodb-odm/blob/2.2.x/lib/Doctrine/ODM/MongoDB/PersistentCollection/PersistentCollectionFactory.php>`_,
which Doctrine will then use to construct its persistent collections.
custom collection class(es).

For this example, we assume that you want to pass Symfony's event dispatcher
to your custom collection class. To do this, you need to modify the
constructor to accept the event dispatcher. You also need to override the
``createFrom`` method to allow Doctrine to pass the dependencies to the
collection constructor.

.. code-block:: php

<?php

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;

class SectionCollection extend ArrayCollection
{
public function __construct(
private EventDispatcherInterface $eventDispatcher,
Copy link
Member Author

@GromNaN GromNaN Jun 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc doesn't say how to inject the dependency when creating a new SectionCollection.

I think, it must be done when creating the Application instance:

#[Document]
class Application
{
    public function __construct(
        #[EmbedMany(
            collectionClass: SectionCollection::class,
            targetDocument: Section::class,
        )]
        public Collection $sections;
    ) {}
}
$application = new Application(
    sections: new SectionCollection($eventDispatcher),
);

Or keeping the SectionCollection class internal:

#[Document]
class Application
{
    #[EmbedMany(
        collectionClass: SectionCollection::class,
        targetDocument: Section::class,
    )]
    public Collection $sections;
    public function __construct(
        EventDispatcherInterface $eventDispatcher,
    ) {
        $this->sections = new SectionCollection($eventDispatcher);
    }
}
$application = new Application($eventDispatcher);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, instantiating the class for new documents is totally on user and was left as an exercise ;) Feel free to propose these 2 ways! Although in first one you should pass a SectionCollection to Application's ctor

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've chosen to keep only the first example. The 2nd is just a syntax variation. Since it's an advanced example, it's intended for developers who will be able to find the alternative if needed.

private array $elements = [],
) {}

public function createFrom(array $elements): static
{
return new static($this->eventDispatcher, $elements);
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementing this method was missing from the doc.


// your custom methods
}

This requires you to create a ``PersistentCollectionFactory`` implementation,
which Doctrine ODM will then use to construct its persistent collections.
You may decide to implement this class from scratch or extend our
``AbstractPersistentCollectionFactory``:

.. code-block:: php

<?php

use Doctrine\Common\Collections\Collection;
use Doctrine\ODM\MongoDB\PersistentCollection\AbstractPersistentCollectionFactory;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

final class YourPersistentCollectionFactory extends AbstractPersistentCollectionFactory
{
private $eventDispatcher;

public function __construct(EventDispatcherInterface $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
}
public function __construct(
private EventDispatcherInterface $eventDispatcher,
) {}

protected function createCollectionClass(string $collectionClass)
protected function createCollectionClass(string $collectionClass): Collection
{
switch ($collectionClass) {
case SectionCollection::class:
return new $collectionClass([], $this->eventDispatcher);
default:
return new $collectionClass();
}
return match ($collectionClass) {
SectionCollection::class => new SectionCollection([], $this->eventDispatcher),
default => new $collectionClass(),
};
Comment on lines +199 to +202
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The match operator is perfect for this use-case.

}
}

Expand Down
44 changes: 23 additions & 21 deletions docs/en/reference/document-repositories.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,6 @@ implementation for all documents (unless overridden by the mapping):
Repositories with Additional Dependencies
-----------------------------------------

.. note::

Implementing your own RepositoryFactory is possible since version 1.0, but the
``AbstractRepositoryFactory`` class used in this example is only available since 1.2.

By default, Doctrine assumes that it can instantiate your repositories in same manner
as its default one:

Expand All @@ -117,42 +112,49 @@ as its default one:

namespace Repositories;

use Doctrine\ODM\MongoDB\DocumentRepository;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\ODM\MongoDB\UnitOfWork;

class UserRepository extends DocumentRepository
{
public function __construct(DocumentManager $dm, UnitOfWork $uow, ClassMetadata $classMetadata)
{
/* constructor is inherited from DocumentRepository */
/* ... */
// The constructor arguments are inherited from DocumentRepository
parent::__construct($dm, $uow, $classMetadata);
}
}

In order to change the way Doctrine instantiates repositories, you will need to implement your own
`RepositoryFactory <https://github.com/doctrine/mongodb-odm/blob/2.2.x/lib/Doctrine/ODM/MongoDB/Repository/RepositoryFactory.php>`_
In order to change the way Doctrine instantiates repositories, you will need to
implement your own `RepositoryFactory <https://github.com/doctrine/mongodb-odm/blob/2.9.x/lib/Doctrine/ODM/MongoDB/Repository/RepositoryFactory.php>`_

In the following example, we create a custom repository factory to pass Symfony's
event dispatcher to the repository constructor.

.. code-block:: php

<?php

use Doctrine\ODM\MongoDB\DocumentRepository;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\ODM\MongoDB\Repository\AbstractRepositoryFactory;
use Doctrine\ODM\MongoDB\UnitOfWork;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

final class YourRepositoryFactory extends AbstractRepositoryFactory
{
private $eventDispatcher;

public function __construct(EventDispatcherInterface $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
}
public function __construct(
private EventDispatcherInterface $eventDispatcher
) {}

protected function instantiateRepository(string $repositoryClassName, DocumentManager $documentManager, ClassMetadata $metadata)
{
switch ($repositoryClassName) {
case UserRepository::class:
return new UserRepository($this->eventDispatcher, $documentManager, $metadata);
default:
return new $repositoryClassName($documentManager, $documentManager->getUnitOfWork(), $metadata);
}
return match ($repositoryClassName) {
UserRepository::class => new UserRepository($this->eventDispatcher, $documentManager, $metadata),
default => new $repositoryClassName($documentManager, $documentManager->getUnitOfWork(), $metadata),
};
}
}

Expand Down
Loading