Skip to content

Commit

Permalink
ReferenceMany: insert an empty array
Browse files Browse the repository at this point in the history
  • Loading branch information
khaperets committed Oct 24, 2023
1 parent 69e64e7 commit dce31ed
Show file tree
Hide file tree
Showing 64 changed files with 622 additions and 121 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:

steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2

Expand Down Expand Up @@ -80,7 +80,7 @@ jobs:
run: composer remove --no-update --dev phpbench/phpbench

- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
uses: "ramsey/composer-install@v2"
with:
dependency-versions: "${{ matrix.dependencies }}"
composer-options: "--prefer-dist"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/performance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:

steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"

- name: Setup cache environment
id: extcache
Expand Down Expand Up @@ -52,7 +52,7 @@ jobs:
run: "php --ri mongodb"

- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
uses: "ramsey/composer-install@v2"

- name: "Upload composer.lock as build artifact"
uses: actions/upload-artifact@v2
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:

steps:
- name: "Checkout code"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"

- name: Setup cache environment
id: extcache
Expand Down Expand Up @@ -47,7 +47,7 @@ jobs:
run: "php --ri mongodb"

- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
uses: "ramsey/composer-install@v2"

- name: "Upload composer.lock as build artifact"
uses: actions/upload-artifact@v2
Expand All @@ -69,7 +69,7 @@ jobs:

steps:
- name: "Checkout code"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"

- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
Expand All @@ -79,7 +79,7 @@ jobs:
php-version: "${{ matrix.php-version }}"

- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
uses: "ramsey/composer-install@v2"

- name: "Upload composer.lock as build artifact"
uses: actions/upload-artifact@v2
Expand Down
5 changes: 2 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@
"ext-bcmath": "*",
"doctrine/coding-standard": "^11.0",
"jmikola/geojson": "^1.0",
"phpbench/phpbench": "^1.0.0@dev",
"phpbench/phpbench": "^1.0.0",
"phpstan/phpstan": "^1.10.11",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^9.5.5 || ^10.0.15",
"phpunit/phpunit": "^9.6 || ^10.0.15",
"squizlabs/php_codesniffer": "^3.5",
"symfony/cache": "^4.4 || ^5.0 || ^6.0",
"vimeo/psalm": "^5.9.0"
Expand All @@ -60,7 +60,6 @@
"Doctrine\\ODM\\MongoDB\\Benchmark\\": "benchmark",
"Doctrine\\ODM\\MongoDB\\Tests\\": "tests/Doctrine/ODM/MongoDB/Tests",
"Documents\\": "tests/Documents",
"Documents74\\": "tests/Documents74",
"Documents81\\": "tests/Documents81",
"Stubs\\": "tests/Stubs",
"TestDocuments\\" :"tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures"
Expand Down
5 changes: 4 additions & 1 deletion docs/en/cookbook/soft-delete-extension.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ An implementation might look like this in a ``User`` document:
{
// ...
/** @ODM\Field(type="date") @ODM\Index */
/**
* @ODM\Field(type="date")
* @ODM\Index
*/
private $deletedAt;
public function getDeletedAt(): ?\DateTime
Expand Down
57 changes: 52 additions & 5 deletions docs/en/reference/embedded-mapping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Embed a single document:
{
/** @Field(type="string") */
private $street;
// ...
}
Expand All @@ -47,7 +47,7 @@ Embed a single document:
<document name="Documents\User">
<embed-one field="address" target-document="Address" />
</document>
<embedded-document name="Address">
<field name="street" type="string" />
</embedded-document>
Expand Down Expand Up @@ -88,7 +88,7 @@ Embed many documents:
{
/** @Field(type="string") */
private $number;
// ...
}
Expand All @@ -102,7 +102,7 @@ Embed many documents:
<document name="Documents\User">
<embed-many field="phoneNumbers" target-document="PhoneNumber" />
</document>
<embedded-document name="PhoneNumber">
<field name="number" type="string" />
</embedded-document>
Expand Down Expand Up @@ -167,7 +167,7 @@ the embedded document. The field name can be customized with the
private $tasks;
// ...
public function __construct()
public function __construct()
{
$this->tasks = new ArrayCollection();
}
Expand Down Expand Up @@ -269,3 +269,50 @@ document and cannot exist without those by nature.

.. |FQCN| raw:: html
<abbr title="Fully-Qualified Class Name">FQCN</abbr>

.. _embed_store_empty_array:

Storing Empty Arrays in Embedded Documents
-------------------------------------------

By default, when an embedded collection property is empty, Doctrine does not store any data for it in the database.
However, in some cases, you may want to explicitly store an empty array for such properties.
You can achieve this behavior by using the `storeEmptyArray` option for embedded collections.

.. configuration-block::

.. code-block:: php
<?php
/** @Document */
class User
{
// ...
/**
* @EmbedMany(targetDocument=PhoneNumber::class, storeEmptyArray=true)
*/
private $phoneNumbers = [];
// ...
}
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mongo-mapping xmlns="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping
http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping.xsd">
<document name="Documents\User">
<embed-many field="phoneNumbers" target-document="PhoneNumber" store-empty-array="true" />
</document>
<embedded-document name="PhoneNumber">
<field name="number" type="string" />
</embedded-document>
</doctrine-mongo-mapping>
Now, when the `$phoneNumbers` collection is empty, an empty array will be stored in the database for the `User`
document's embedded `phoneNumbers` collection, even if there are no actual embedded documents in the collection.
43 changes: 43 additions & 0 deletions docs/en/reference/reference-mapping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -434,3 +434,46 @@ from the database.
.. _`DBRef`: https://docs.mongodb.com/manual/reference/database-references/#dbrefs
.. |FQCN| raw:: html
<abbr title="Fully-Qualified Class Name">FQCN</abbr>

.. _store_empty_array:

Storing Empty Arrays
---------------------

By default, when a collection property is empty, Doctrine does not store any data for it in the database.
However, in some cases, you may want to explicitly store an empty array for such properties.
You can achieve this behavior by using the `storeEmptyArray` option.

.. configuration-block::

.. code-block:: php
<?php
/** @Document */
class User
{
// ...
/**
* @ReferenceMany(targetDocument=Account::class, storeEmptyArray=true)
*/
private $accounts = [];
// ...
}
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mongo-mapping xmlns="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping
http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping.xsd">
<document name="Documents\User">
<reference-many field="accounts" target-document="Documents\Account" store-empty-array="true" />
</document>
</doctrine-mongo-mapping>
Now, when the `$accounts` collection is empty, an empty array will be stored in the database for the `User` document,
even if there are no actual referenced documents.
2 changes: 2 additions & 0 deletions doctrine-mongo-mapping.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@
<xs:attribute name="strategy" type="odm:storage-strategy" default="pushAll" />
<xs:attribute name="not-saved" type="xs:boolean" />
<xs:attribute name="nullable" type="xs:boolean" />
<xs:attribute name="store-empty-array" type="xs:boolean" />
<xs:attribute name="also-load" type="xs:NMTOKEN" />
</xs:complexType>

Expand Down Expand Up @@ -293,6 +294,7 @@
<xs:attribute name="orphan-removal" type="xs:boolean" />
<xs:attribute name="not-saved" type="xs:boolean" />
<xs:attribute name="nullable" type="xs:boolean" />
<xs:attribute name="store-empty-array" type="xs:boolean" />
<xs:attribute name="also-load" type="xs:NMTOKEN" />
</xs:complexType>

Expand Down
19 changes: 15 additions & 4 deletions lib/Doctrine/ODM/MongoDB/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Doctrine\Common\Cache\Psr6\CacheAdapter;
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactoryInterface;
use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver;
use Doctrine\ODM\MongoDB\PersistentCollection\DefaultPersistentCollectionFactory;
use Doctrine\ODM\MongoDB\PersistentCollection\DefaultPersistentCollectionGenerator;
Expand Down Expand Up @@ -86,7 +87,7 @@ class Configuration
* @psalm-var array{
* autoGenerateHydratorClasses?: self::AUTOGENERATE_*,
* autoGeneratePersistentCollectionClasses?: self::AUTOGENERATE_*,
* classMetadataFactoryName?: class-string<ClassMetadataFactory>,
* classMetadataFactoryName?: class-string<ClassMetadataFactoryInterface>,
* defaultCommitOptions?: CommitOptions,
* defaultDocumentRepositoryClassName?: class-string<ObjectRepository<object>>,
* defaultGridFSRepositoryClassName?: class-string<GridFSRepository<object>>,
Expand Down Expand Up @@ -409,13 +410,23 @@ public function getDefaultDB(): ?string
return $this->attributes['defaultDB'] ?? null;
}

/** @psalm-param class-string<ClassMetadataFactory> $cmfName */
/**
* @psalm-param class-string<ClassMetadataFactoryInterface> $cmfName
*
* @throws MongoDBException If is not a ClassMetadataFactoryInterface.
*/
public function setClassMetadataFactoryName(string $cmfName): void
{
$reflectionClass = new ReflectionClass($cmfName);

if (! $reflectionClass->implementsInterface(ClassMetadataFactoryInterface::class)) {
throw MongoDBException::invalidClassMetadataFactory($cmfName);
}

$this->attributes['classMetadataFactoryName'] = $cmfName;
}

/** @psalm-return class-string<ClassMetadataFactory> */
/** @psalm-return class-string<ClassMetadataFactoryInterface> */
public function getClassMetadataFactoryName(): string
{
if (! isset($this->attributes['classMetadataFactoryName'])) {
Expand Down Expand Up @@ -474,7 +485,7 @@ public function getFilterParameters(string $name): array
/**
* @psalm-param class-string<ObjectRepository<object>> $className
*
* @throws MongoDBException If not is a ObjectRepository.
* @throws MongoDBException If is not an ObjectRepository.
*/
public function setDefaultDocumentRepositoryClassName(string $className): void
{
Expand Down
16 changes: 9 additions & 7 deletions lib/Doctrine/ODM/MongoDB/DocumentManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use Doctrine\Common\EventManager;
use Doctrine\ODM\MongoDB\Hydrator\HydratorFactory;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactoryInterface;
use Doctrine\ODM\MongoDB\Mapping\MappingException;
use Doctrine\ODM\MongoDB\Proxy\Factory\ProxyFactory;
use Doctrine\ODM\MongoDB\Proxy\Factory\StaticProxyFactory;
Expand All @@ -19,6 +19,7 @@
use Doctrine\ODM\MongoDB\Repository\GridFSRepository;
use Doctrine\ODM\MongoDB\Repository\RepositoryFactory;
use Doctrine\ODM\MongoDB\Repository\ViewRepository;
use Doctrine\Persistence\Mapping\ProxyClassNameResolver;
use Doctrine\Persistence\ObjectManager;
use InvalidArgumentException;
use Jean85\PrettyVersions;
Expand Down Expand Up @@ -68,7 +69,7 @@ class DocumentManager implements ObjectManager
/**
* The metadata factory, used to retrieve the ODM metadata of document classes.
*/
private ClassMetadataFactory $metadataFactory;
private ClassMetadataFactoryInterface $metadataFactory;

/**
* The UnitOfWork used to coordinate object-level transactions.
Expand Down Expand Up @@ -131,7 +132,8 @@ class DocumentManager implements ObjectManager
*/
private ?FilterCollection $filterCollection = null;

private ClassNameResolver $classNameResolver;
/** @var ProxyClassNameResolver&ClassNameResolver */
private ProxyClassNameResolver $classNameResolver;

private static ?string $version = null;

Expand All @@ -154,10 +156,13 @@ protected function __construct(?Client $client = null, ?Configuration $config =
],
);

$this->classNameResolver = new CachingClassNameResolver(new ProxyManagerClassNameResolver($this->config));

$metadataFactoryClassName = $this->config->getClassMetadataFactoryName();
$this->metadataFactory = new $metadataFactoryClassName();
$this->metadataFactory->setDocumentManager($this);
$this->metadataFactory->setConfiguration($this->config);
$this->metadataFactory->setProxyClassNameResolver($this->classNameResolver);

$cacheDriver = $this->config->getMetadataCache();
if ($cacheDriver) {
Expand All @@ -179,9 +184,6 @@ protected function __construct(?Client $client = null, ?Configuration $config =
$this->schemaManager = new SchemaManager($this, $this->metadataFactory);
$this->proxyFactory = new StaticProxyFactory($this);
$this->repositoryFactory = $this->config->getRepositoryFactory();
$this->classNameResolver = new CachingClassNameResolver(new ProxyManagerClassNameResolver($this->config));

$this->metadataFactory->setProxyClassNameResolver($this->classNameResolver);
}

/**
Expand Down Expand Up @@ -220,7 +222,7 @@ public function getClient(): Client
/**
* Gets the metadata factory used to gather the metadata of classes.
*
* @return ClassMetadataFactory
* @return ClassMetadataFactoryInterface
*/
public function getMetadataFactory()
{
Expand Down
Loading

0 comments on commit dce31ed

Please sign in to comment.