Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
20 changes: 20 additions & 0 deletions .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: "Documentation"

on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/documentation.yml
- docs/**
push:
branches:
- "*.x"
paths:
- .github/workflows/documentation.yml
- docs/**

jobs:
documentation:
name: "Documentation"
uses: "doctrine/.github/.github/workflows/documentation.yml@12.2.0"
35 changes: 35 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,41 @@ awareness about deprecated code.
- Use of our low-overhead runtime deprecation API, details:
https://github.com/doctrine/deprecations/

# Upgrade to 4.2

## Add `getFieldValue` and `setFieldValue` to `ClassMetadata` implementation

The interface `Doctrine\Persistence\Mapping\ClassMetadata` has two new methods:
- `getFieldValue(object $object, string $field)`
- `setFieldValue(object $object, string $field, mixed $value): void`

Not implementing these methods is deprecated. They will be required in 5.0.

## Deprecated modifying `$metadata` in PHP mapping files

Relying on the `$metadata` variable directly in PHP mapping files is deprecated.
Instead, wrap the code in a closure that is returned by the configuration file.

Before:

```php
<?php // mappings/App.Entity.User.php

$metadata->name = \App\Entity\User::class;
```

After:

```php
<?php // mappings/App.Entity.User.php

use Doctrine\Persistence\Mapping\ClassMetadata;

return function (ClassMetadata $metadata): void {
$metadata->name = \App\Entity\User::class;
};
```

# Upgrade to 4.0

## BC Break: Removed `StaticReflectionService`
Expand Down
4 changes: 4 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
],
"require": {
"php": "^8.1",
"doctrine/deprecations": "^1",
"doctrine/event-manager": "^1 || ^2",
"psr/cache": "^1.0 || ^2.0 || ^3.0"
},
Expand Down Expand Up @@ -48,5 +49,8 @@
"dealerdirect/phpcodesniffer-composer-installer": true,
"composer/package-versions-deprecated": true
}
},
"scripts": {
"docs": "composer --working-dir docs update && ./docs/vendor/bin/build-docs.sh @additional_args"
}
}
3 changes: 3 additions & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/composer.lock
/vendor/
/output/
5 changes: 5 additions & 0 deletions docs/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"require": {
"doctrine/docs-builder": "^1.0"
}
}
11 changes: 9 additions & 2 deletions docs/en/reference/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,15 @@ mapping metadata.
.. code-block:: php

use App\Model\User;
use Doctrine\Persistence\Mapping\ClassMetadata;

return function (ClassMetadata $metadata): void {
$metadata->name = User::class;

// ...

$metadata->name = User::class;
};

// ...

StaticPHPDriver
--------------
Expand All @@ -284,6 +289,8 @@ Your class in ``App\Model\User`` would look like the following.

namespace App\Model;

use Doctrine\Persistence\Mapping\ClassMetadata;

final class User
{
// ...
Expand Down
2 changes: 2 additions & 0 deletions docs/en/sidebar.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
:orphan:

.. toctree::
:depth: 3

Expand Down
2 changes: 1 addition & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ parameters:
- tests/Persistence/Mapping/_files/colocated/Foo.mphp

excludePaths:
- tests/Persistence/Mapping/_files/Doctrine.Tests.Persistence.Mapping.PHPTestEntity.php
- tests/Persistence/Mapping/_files/Doctrine.*.php

ignoreErrors:
- '#Variable property access on \$this\(Doctrine\\Persistence\\Reflection\\TypedNoDefaultReflectionProperty\)\.#'
Expand Down
2 changes: 2 additions & 0 deletions src/Persistence/Mapping/ClassMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
* Contract for a Doctrine persistence layer ClassMetadata class to implement.
*
* @template-covariant T of object
* @method mixed getFieldValue(object $entity, string $field)
* @method void setFieldValue(object $entity, string $field, mixed $value)
*/
interface ClassMetadata
{
Expand Down
31 changes: 31 additions & 0 deletions src/Persistence/Mapping/Driver/PHPDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

namespace Doctrine\Persistence\Mapping\Driver;

use Closure;
use CompileError;
use Doctrine\Deprecations\Deprecation;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Error;

/**
* The PHPDriver includes php files which just populate ClassMetadataInfo
Expand Down Expand Up @@ -35,6 +39,33 @@ public function loadMetadataForClass(string $className, ClassMetadata $metadata)
*/
protected function loadMappingFile(string $file): array
{
try {
$callback = Closure::bind(static function (string $file): mixed {
$metadata = null;

return include $file;
}, null, null)($file);
} catch (CompileError $e) {
throw $e;
} catch (Error) {
// Calling any method on $metadata=null will raise an Error
// Falling back to legacy behavior of expecting $metadata to be populated
$callback = null;
}

if ($callback instanceof Closure) {
$callback($this->metadata);

return [$this->metadata->getName() => $this->metadata];
}

Deprecation::trigger(
'doctrine/persistence',
'https://github.com/doctrine/persistence/pull/450',
'Not returning a Closure from a PHP mapping file is deprecated',
);

unset($callback);
$metadata = $this->metadata;
include $file;

Expand Down
62 changes: 60 additions & 2 deletions tests/Persistence/Mapping/PHPDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,80 @@

namespace Doctrine\Tests\Persistence\Mapping;

use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\Driver\PHPDriver;
use Doctrine\Tests\DoctrineTestCase;
use Error;
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
use PHPUnit\Framework\Attributes\TestWith;

class PHPDriverTest extends DoctrineTestCase
{
public function testLoadMetadata(): void
use VerifyDeprecations;

/** @phpstan-param class-string $className */
#[IgnoreDeprecations]
#[TestWith([PHPTestEntity::class])]
#[TestWith([PHPTestEntityAssert::class])]
public function testLoadMetadata(string $className): void
{
$metadata = $this->createMock(ClassMetadata::class);
$metadata->expects(self::once())->method('getFieldNames');
$driver = new PHPDriver([__DIR__ . '/_files']);

$this->expectDeprecationWithIdentifier('https://github.com/doctrine/persistence/pull/450');
$driver->loadMetadataForClass($className, $metadata);
}

public function testLoadMetadataWithClosure(): void
{
$metadata = $this->createMock(ClassMetadata::class);
$metadata->expects(self::once())->method('getFieldNames');

$driver = new PHPDriver([__DIR__ . '/_files']);
$driver->loadMetadataForClass(PHPTestEntity::class, $metadata);
$driver->loadMetadataForClass(PHPTestEntityClosure::class, $metadata);
}

public function testLoadMetadataClosureNotBoundToObject(): void
{
$metadata = $this->createMock(ClassMetadata::class);
$driver = new PHPDriver([__DIR__ . '/_files']);

$this->expectException(Error::class);
$this->expectExceptionMessage('Using $this when not in object context');

$driver->loadMetadataForClass(PHPTestIncorrectUseThis::class, $metadata);
}

public function testLoadMetadataClosureNotBoundToClass(): void
{
$metadata = $this->createMock(ClassMetadata::class);
$driver = new PHPDriver([__DIR__ . '/_files']);

$this->expectException(Error::class);
$this->expectExceptionMessage('Cannot use "static" in the global scope');

$driver->loadMetadataForClass(PHPTestIncorrectUseStatic::class, $metadata);
}
}

class PHPTestEntity
{
}

class PHPTestEntityAssert
{
}

class PHPTestEntityClosure
{
}

class PHPTestIncorrectUseThis
{
}

class PHPTestIncorrectUseStatic
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

declare(strict_types=1);

use Doctrine\Persistence\Mapping\ClassMetadata;

assert($metadata instanceof ClassMetadata);
$metadata->getFieldNames();
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

use Doctrine\Persistence\Mapping\ClassMetadata;

return static function (ClassMetadata $metadata): void {
$metadata->getFieldNames();
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

use Doctrine\Persistence\Mapping\ClassMetadata;

return static function (ClassMetadata $metadata): void {
$metadata->isIdentifier(static::class);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

declare(strict_types=1);

return function (): void {
$this->setGlobalBasename('global-mapping');
};