Initial Drupal 11 with DDEV setup

This commit is contained in:
gluebox
2025-10-08 11:39:17 -04:00
commit 89ef74b305
25344 changed files with 2599172 additions and 0 deletions

19
vendor/doctrine/annotations/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2006-2013 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

31
vendor/doctrine/annotations/README.md vendored Normal file
View File

@ -0,0 +1,31 @@
⚠️ PHP 8 introduced
[attributes](https://www.php.net/manual/en/language.attributes.overview.php),
which are a native replacement for annotations. As such, this library is
considered feature complete, and should receive exclusively bugfixes and
security fixes.
We do not recommend using this library in new projects and encourage authors
of downstream libraries to offer support for attributes as an alternative to
Doctrine Annotations.
Have a look at [our blog](https://www.doctrine-project.org/2022/11/04/annotations-to-attributes.html)
to learn more.
# Doctrine Annotations
[![Build Status](https://github.com/doctrine/annotations/workflows/Continuous%20Integration/badge.svg?label=build)](https://github.com/doctrine/persistence/actions)
[![Dependency Status](https://www.versioneye.com/package/php--doctrine--annotations/badge.png)](https://www.versioneye.com/package/php--doctrine--annotations)
[![Reference Status](https://www.versioneye.com/php/doctrine:annotations/reference_badge.svg)](https://www.versioneye.com/php/doctrine:annotations/references)
[![Total Downloads](https://poser.pugx.org/doctrine/annotations/downloads.png)](https://packagist.org/packages/doctrine/annotations)
[![Latest Stable Version](https://img.shields.io/packagist/v/doctrine/annotations.svg?label=stable)](https://packagist.org/packages/doctrine/annotations)
Docblock Annotations Parser library (extracted from [Doctrine Common](https://github.com/doctrine/common)).
## Documentation
See the [doctrine-project website](https://www.doctrine-project.org/projects/doctrine-annotations/en/stable/index.html).
## Contributing
When making a pull request, make sure your changes follow the
[Coding Standard Guidelines](https://www.doctrine-project.org/projects/doctrine-coding-standard/en/current/reference/index.html#introduction).

18
vendor/doctrine/annotations/UPGRADE.md vendored Normal file
View File

@ -0,0 +1,18 @@
# Upgrade from 1.0.x to 2.0.x
- The `NamedArgumentConstructorAnnotation` has been removed. Use the `@NamedArgumentConstructor`
annotation instead.
- `SimpleAnnotationReader` has been removed.
- `DocLexer::peek()` and `DocLexer::glimpse` now return
`Doctrine\Common\Lexer\Token` objects. When using `doctrine/lexer` 2, these
implement `ArrayAccess` as a way for you to still be able to treat them as
arrays in some ways.
- `CachedReader` and `FileCacheReader` have been removed use `PsrCachedReader` instead.
- `AnnotationRegistry` methods related to registering annotations instead of
using autoloading have been removed.
- Parameter type declarations have been added to all methods of all classes. If
you have classes inheriting from classes inside this package, you should add
parameter and return type declarations.
- Support for PHP < 7.2 has been removed
- `PhpParser::parseClass()` has been removed. Use
`PhpParser::parseUseStatements()` instead.

View File

@ -0,0 +1,72 @@
{
"name": "doctrine/annotations",
"description": "Docblock Annotations Parser",
"license": "MIT",
"type": "library",
"keywords": [
"annotations",
"docblock",
"parser"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
}
],
"homepage": "https://www.doctrine-project.org/projects/annotations.html",
"require": {
"php": "^7.2 || ^8.0",
"ext-tokenizer": "*",
"doctrine/lexer": "^2 || ^3",
"psr/cache": "^1 || ^2 || ^3"
},
"require-dev": {
"doctrine/cache": "^2.0",
"doctrine/coding-standard": "^10",
"phpstan/phpstan": "^1.10.28",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"symfony/cache": "^5.4 || ^6.4 || ^7",
"vimeo/psalm": "^4.30 || ^5.14"
},
"suggest": {
"php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations"
},
"autoload": {
"psr-4": {
"Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations"
}
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Performance\\Common\\Annotations\\": "tests/Doctrine/Performance/Common/Annotations",
"Doctrine\\Tests\\Common\\Annotations\\": "tests/Doctrine/Tests/Common/Annotations"
},
"files": [
"tests/Doctrine/Tests/Common/Annotations/Fixtures/functions.php",
"tests/Doctrine/Tests/Common/Annotations/Fixtures/SingleClassLOC1000.php"
]
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
},
"sort-packages": true
}
}

View File

@ -0,0 +1,189 @@
Handling Annotations
====================
There are several different approaches to handling annotations in PHP.
Doctrine Annotations maps docblock annotations to PHP classes. Because
not all docblock annotations are used for metadata purposes a filter is
applied to ignore or skip classes that are not Doctrine annotations.
Take a look at the following code snippet:
.. code-block:: php
namespace MyProject\Entities;
use Doctrine\ORM\Mapping AS ORM;
use Symfony\Component\Validator\Constraints AS Assert;
/**
* @author Benjamin Eberlei
* @ORM\Entity
* @MyProject\Annotations\Foobarable
*/
class User
{
/**
* @ORM\Id @ORM\Column @ORM\GeneratedValue
* @dummy
* @var int
*/
private $id;
/**
* @ORM\Column(type="string")
* @Assert\NotEmpty
* @Assert\Email
* @var string
*/
private $email;
}
In this snippet you can see a variety of different docblock annotations:
- Documentation annotations such as ``@var`` and ``@author``. These
annotations are ignored and never considered for throwing an
exception due to wrongly used annotations.
- Annotations imported through use statements. The statement ``use
Doctrine\ORM\Mapping AS ORM`` makes all classes under that namespace
available as ``@ORM\ClassName``. Same goes for the import of
``@Assert``.
- The ``@dummy`` annotation. It is not a documentation annotation and
not ignored. For Doctrine Annotations it is not entirely clear how
to handle this annotation. Depending on the configuration an exception
(unknown annotation) will be thrown when parsing this annotation.
- The fully qualified annotation ``@MyProject\Annotations\Foobarable``.
This is transformed directly into the given class name.
How are these annotations loaded? From looking at the code you could
guess that the ORM Mapping, Assert Validation and the fully qualified
annotation can just be loaded using
the defined PHP autoloaders. This is not the case however: For error
handling reasons every check for class existence inside the
``AnnotationReader`` sets the second parameter $autoload
of ``class_exists($name, $autoload)`` to false. To work flawlessly the
``AnnotationReader`` requires silent autoloaders which many autoloaders are
not. Silent autoloading is NOT part of the `PSR-0 specification
<https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md>`_
for autoloading.
This is why Doctrine Annotations uses its own autoloading mechanism
through a global registry. If you are wondering about the annotation
registry being global, there is no other way to solve the architectural
problems of autoloading annotation classes in a straightforward fashion.
Additionally if you think about PHP autoloading then you recognize it is
a global as well.
To anticipate the configuration section, making the above PHP class work
with Doctrine Annotations requires this setup:
.. code-block:: php
use Doctrine\Common\Annotations\AnnotationReader;
$reader = new AnnotationReader();
AnnotationReader::addGlobalIgnoredName('dummy');
We create the actual ``AnnotationReader`` instance.
Note that we also add ``dummy`` to the global list of ignored
annotations for which we do not throw exceptions. Setting this is
necessary in our example case, otherwise ``@dummy`` would trigger an
exception to be thrown during the parsing of the docblock of
``MyProject\Entities\User#id``.
Setup and Configuration
-----------------------
To use the annotations library is simple, you just need to create a new
``AnnotationReader`` instance:
.. code-block:: php
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
This creates a simple annotation reader with no caching other than in
memory (in php arrays). Since parsing docblocks can be expensive you
should cache this process by using a caching reader.
To cache annotations, you can create a ``Doctrine\Common\Annotations\PsrCachedReader``.
This reader decorates the original reader and stores all annotations in a PSR-6
cache:
.. code-block:: php
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\PsrCachedReader;
$cache = ... // instantiate a PSR-6 Cache pool
$reader = new PsrCachedReader(
new AnnotationReader(),
$cache,
$debug = true
);
The ``debug`` flag is used here as well to invalidate the cache files
when the PHP class with annotations changed and should be used during
development.
.. warning ::
The ``AnnotationReader`` works and caches under the
assumption that all annotations of a doc-block are processed at
once. That means that annotation classes that do not exist and
aren't loaded and cannot be autoloaded (using the
AnnotationRegistry) would never be visible and not accessible if a
cache is used unless the cache is cleared and the annotations
requested again, this time with all annotations defined.
By default the annotation reader returns a list of annotations with
numeric indexes. If you want your annotations to be indexed by their
class name you can wrap the reader in an ``IndexedReader``:
.. code-block:: php
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\IndexedReader;
$reader = new IndexedReader(new AnnotationReader());
.. warning::
You should never wrap the indexed reader inside a cached reader,
only the other way around. This way you can re-use the cache with
indexed or numeric keys, otherwise your code may experience failures
due to caching in a numerical or indexed format.
Ignoring missing exceptions
~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default an exception is thrown from the ``AnnotationReader`` if an
annotation was found that:
- is not part of the list of ignored "documentation annotations";
- was not imported through a use statement;
- is not a fully qualified class that exists.
You can disable this behavior for specific names if your docblocks do
not follow strict requirements:
.. code-block:: php
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
AnnotationReader::addGlobalIgnoredName('foo');
PHP Imports
~~~~~~~~~~~
By default the annotation reader parses the use-statement of a php file
to gain access to the import rules and register them for the annotation
processing. Only if you are using PHP Imports can you validate the
correct usage of annotations and throw exceptions if you misspelled an
annotation. This mechanism is enabled by default.
To ease the upgrade path, we still allow you to disable this mechanism.
Note however that we will remove this in future versions:
.. code-block:: php
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
$reader->setEnabledPhpImports(false);

View File

@ -0,0 +1,443 @@
Custom Annotation Classes
=========================
If you want to define your own annotations, you just have to group them
in a namespace.
Annotation classes have to contain a class-level docblock with the text
``@Annotation``:
.. code-block:: php
namespace MyCompany\Annotations;
/** @Annotation */
class Bar
{
// some code
}
Inject annotation values
------------------------
The annotation parser checks if the annotation constructor has arguments,
if so then it will pass the value array, otherwise it will try to inject
values into public properties directly:
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
*
* Some Annotation using a constructor
*/
class Bar
{
private $foo;
public function __construct(array $values)
{
$this->foo = $values['foo'];
}
}
/**
* @Annotation
*
* Some Annotation without a constructor
*/
class Foo
{
public $bar;
}
Optional: Constructors with Named Parameters
--------------------------------------------
Starting with Annotations v1.11 a new annotation instantiation strategy
is available that aims at compatibility of Annotation classes with the PHP 8
attribute feature. You need to declare a constructor with regular parameter
names that match the named arguments in the annotation syntax.
To enable this feature, you can tag your annotation class with
``@NamedArgumentConstructor`` (available from v1.12) or implement the
``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation`` interface
(available from v1.11 and deprecated as of v1.12).
When using the ``@NamedArgumentConstructor`` tag, the first argument of the
constructor is considered as the default one.
Usage with the ``@NamedArgumentConstructor`` tag
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
* @NamedArgumentConstructor
*/
class Bar implements NamedArgumentConstructorAnnotation
{
private $foo;
public function __construct(string $foo)
{
$this->foo = $foo;
}
}
/** Usable with @Bar(foo="baz") */
/** Usable with @Bar("baz") */
In combination with PHP 8's constructor property promotion feature
you can simplify this to:
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
* @NamedArgumentConstructor
*/
class Bar implements NamedArgumentConstructorAnnotation
{
public function __construct(private string $foo) {}
}
Usage with the
``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation``
interface (v1.11, deprecated as of v1.12):
.. code-block:: php
namespace MyCompany\Annotations;
use Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation;
/** @Annotation */
class Bar implements NamedArgumentConstructorAnnotation
{
private $foo;
public function __construct(private string $foo) {}
}
/** Usable with @Bar(foo="baz") */
Annotation Target
-----------------
``@Target`` indicates the kinds of class elements to which an annotation
type is applicable. Then you could define one or more targets:
- ``CLASS`` Allowed in class docblocks
- ``PROPERTY`` Allowed in property docblocks
- ``METHOD`` Allowed in the method docblocks
- ``FUNCTION`` Allowed in function dockblocks
- ``ALL`` Allowed in class, property, method and function docblocks
- ``ANNOTATION`` Allowed inside other annotations
If the annotations is not allowed in the current context, an
``AnnotationException`` is thrown.
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
* @Target({"METHOD","PROPERTY"})
*/
class Bar
{
// some code
}
/**
* @Annotation
* @Target("CLASS")
*/
class Foo
{
// some code
}
Attribute types
---------------
The annotation parser checks the given parameters using the phpdoc
annotation ``@var``, The data type could be validated using the ``@var``
annotation on the annotation properties or using the ``@Attributes`` and
``@Attribute`` annotations.
If the data type does not match you get an ``AnnotationException``
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
* @Target({"METHOD","PROPERTY"})
*/
class Bar
{
/** @var mixed */
public $mixed;
/** @var boolean */
public $boolean;
/** @var bool */
public $bool;
/** @var float */
public $float;
/** @var string */
public $string;
/** @var integer */
public $integer;
/** @var array */
public $array;
/** @var SomeAnnotationClass */
public $annotation;
/** @var array<integer> */
public $arrayOfIntegers;
/** @var array<SomeAnnotationClass> */
public $arrayOfAnnotations;
}
/**
* @Annotation
* @Target({"METHOD","PROPERTY"})
* @Attributes({
* @Attribute("stringProperty", type = "string"),
* @Attribute("annotProperty", type = "SomeAnnotationClass"),
* })
*/
class Foo
{
public function __construct(array $values)
{
$this->stringProperty = $values['stringProperty'];
$this->annotProperty = $values['annotProperty'];
}
// some code
}
Annotation Required
-------------------
``@Required`` indicates that the field must be specified when the
annotation is used. If it is not used you get an ``AnnotationException``
stating that this value can not be null.
Declaring a required field:
.. code-block:: php
/**
* @Annotation
* @Target("ALL")
*/
class Foo
{
/** @Required */
public $requiredField;
}
Usage:
.. code-block:: php
/** @Foo(requiredField="value") */
public $direction; // Valid
/** @Foo */
public $direction; // Required field missing, throws an AnnotationException
Enumerated values
-----------------
- An annotation property marked with ``@Enum`` is a field that accepts a
fixed set of scalar values.
- You should use ``@Enum`` fields any time you need to represent fixed
values.
- The annotation parser checks the given value and throws an
``AnnotationException`` if the value does not match.
Declaring an enumerated property:
.. code-block:: php
/**
* @Annotation
* @Target("ALL")
*/
class Direction
{
/**
* @Enum({"NORTH", "SOUTH", "EAST", "WEST"})
*/
public $value;
}
Annotation usage:
.. code-block:: php
/** @Direction("NORTH") */
public $direction; // Valid value
/** @Direction("NORTHEAST") */
public $direction; // Invalid value, throws an AnnotationException
Constants
---------
The use of constants and class constants is available on the annotations
parser.
The following usages are allowed:
.. code-block:: php
namespace MyCompany\Entity;
use MyCompany\Annotations\Foo;
use MyCompany\Annotations\Bar;
use MyCompany\Entity\SomeClass;
/**
* @Foo(PHP_EOL)
* @Bar(Bar::FOO)
* @Foo({SomeClass::FOO, SomeClass::BAR})
* @Bar({SomeClass::FOO_KEY = SomeClass::BAR_VALUE})
*/
class User
{
}
Be careful with constants and the cache !
.. note::
The cached reader will not re-evaluate each time an annotation is
loaded from cache. When a constant is changed the cache must be
cleaned.
Usage
-----
Using the library API is simple. Using the annotations described in the
previous section, you can now annotate other classes with your
annotations:
.. code-block:: php
namespace MyCompany\Entity;
use MyCompany\Annotations\Foo;
use MyCompany\Annotations\Bar;
/**
* @Foo(bar="foo")
* @Bar(foo="bar")
*/
class User
{
}
Now we can write a script to get the annotations above:
.. code-block:: php
$reflClass = new ReflectionClass('MyCompany\Entity\User');
$classAnnotations = $reader->getClassAnnotations($reflClass);
foreach ($classAnnotations AS $annot) {
if ($annot instanceof \MyCompany\Annotations\Foo) {
echo $annot->bar; // prints "foo";
} else if ($annot instanceof \MyCompany\Annotations\Bar) {
echo $annot->foo; // prints "bar";
}
}
You have a complete API for retrieving annotation class instances from a
class, property or method docblock:
Reader API
~~~~~~~~~~
Access all annotations of a class
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getClassAnnotations(\ReflectionClass $class);
Access one annotation of a class
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getClassAnnotation(\ReflectionClass $class, $annotationName);
Access all annotations of a method
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getMethodAnnotations(\ReflectionMethod $method);
Access one annotation of a method
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getMethodAnnotation(\ReflectionMethod $method, $annotationName);
Access all annotations of a property
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getPropertyAnnotations(\ReflectionProperty $property);
Access one annotation of a property
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName);
Access all annotations of a function
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getFunctionAnnotations(\ReflectionFunction $property);
Access one annotation of a function
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getFunctionAnnotation(\ReflectionFunction $property, $annotationName);

View File

@ -0,0 +1,109 @@
Deprecation notice
==================
PHP 8 introduced `attributes
<https://www.php.net/manual/en/language.attributes.overview.php>`_,
which are a native replacement for annotations. As such, this library is
considered feature complete, and should receive exclusively bugfixes and
security fixes.
We do not recommend using this library in new projects and encourage authors
of downstream libraries to offer support for attributes as an alternative to
Doctrine Annotations.
Have a look at [our blog](https://www.doctrine-project.org/2022/11/04/annotations-to-attributes.html)
to learn more.
Introduction
============
Doctrine Annotations allows to implement custom annotation
functionality for PHP classes and functions.
.. code-block:: php
class Foo
{
/**
* @MyAnnotation(myProperty="value")
*/
private $bar;
}
Annotations aren't implemented in PHP itself which is why this component
offers a way to use the PHP doc-blocks as a place for the well known
annotation syntax using the ``@`` char.
Annotations in Doctrine are used for the ORM configuration to build the
class mapping, but it can be used in other projects for other purposes
too.
Installation
============
You can install the Annotation component with composer:
.. code-block::
$ composer require doctrine/annotations
Create an annotation class
==========================
An annotation class is a representation of the later used annotation
configuration in classes. The annotation class of the previous example
looks like this:
.. code-block:: php
/**
* @Annotation
*/
final class MyAnnotation
{
public $myProperty;
}
The annotation class is declared as an annotation by ``@Annotation``.
:ref:`Read more about custom annotations. <custom>`
Reading annotations
===================
The access to the annotations happens by reflection of the class or function
containing them. There are multiple reader-classes implementing the
``Doctrine\Common\Annotations\Reader`` interface, that can access the
annotations of a class. A common one is
``Doctrine\Common\Annotations\AnnotationReader``:
.. code-block:: php
use Doctrine\Common\Annotations\AnnotationReader;
$reflectionClass = new ReflectionClass(Foo::class);
$property = $reflectionClass->getProperty('bar');
$reader = new AnnotationReader();
$myAnnotation = $reader->getPropertyAnnotation(
$property,
MyAnnotation::class
);
echo $myAnnotation->myProperty; // result: "value"
A reader has multiple methods to access the annotations of a class or
function.
:ref:`Read more about handling annotations. <annotations>`
IDE Support
-----------
Some IDEs already provide support for annotations:
- Eclipse via the `Symfony2 Plugin <https://github.com/pulse00/Symfony-2-Eclipse-Plugin>`_
- PhpStorm via the `PHP Annotations Plugin <https://plugins.jetbrains.com/plugin/7320-php-annotations>`_ or the `Symfony Plugin <https://plugins.jetbrains.com/plugin/7219-symfony-support>`_
.. _Read more about handling annotations.: annotations
.. _Read more about custom annotations.: custom

View File

@ -0,0 +1,6 @@
.. toctree::
:depth: 3
index
annotations
custom

View File

@ -0,0 +1,54 @@
<?php
namespace Doctrine\Common\Annotations;
use BadMethodCallException;
use function sprintf;
/**
* Annotations class.
*/
class Annotation
{
/**
* Value property. Common among all derived classes.
*
* @var mixed
*/
public $value;
/** @param array<string, mixed> $data Key-value for properties to be defined in this class. */
final public function __construct(array $data)
{
foreach ($data as $key => $value) {
$this->$key = $value;
}
}
/**
* Error handler for unknown property accessor in Annotation class.
*
* @throws BadMethodCallException
*/
public function __get(string $name)
{
throw new BadMethodCallException(
sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class)
);
}
/**
* Error handler for unknown property mutator in Annotation class.
*
* @param mixed $value Property value.
*
* @throws BadMethodCallException
*/
public function __set(string $name, $value)
{
throw new BadMethodCallException(
sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class)
);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
/**
* Annotation that can be used to signal to the parser
* to check the attribute type during the parsing process.
*
* @Annotation
*/
final class Attribute
{
/** @var string */
public $name;
/** @var string */
public $type;
/** @var bool */
public $required = false;
}

View File

@ -0,0 +1,15 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
/**
* Annotation that can be used to signal to the parser
* to check the types of all declared attributes during the parsing process.
*
* @Annotation
*/
final class Attributes
{
/** @var array<Attribute> */
public $value;
}

View File

@ -0,0 +1,69 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
use InvalidArgumentException;
use function get_class;
use function gettype;
use function in_array;
use function is_object;
use function is_scalar;
use function sprintf;
/**
* Annotation that can be used to signal to the parser
* to check the available values during the parsing process.
*
* @Annotation
* @Attributes({
* @Attribute("value", required = true, type = "array"),
* @Attribute("literal", required = false, type = "array")
* })
*/
final class Enum
{
/** @phpstan-var list<scalar> */
public $value;
/**
* Literal target declaration.
*
* @var mixed[]
*/
public $literal;
/**
* @phpstan-param array{literal?: mixed[], value: list<scalar>} $values
*
* @throws InvalidArgumentException
*/
public function __construct(array $values)
{
if (! isset($values['literal'])) {
$values['literal'] = [];
}
foreach ($values['value'] as $var) {
if (! is_scalar($var)) {
throw new InvalidArgumentException(sprintf(
'@Enum supports only scalar values "%s" given.',
is_object($var) ? get_class($var) : gettype($var)
));
}
}
foreach ($values['literal'] as $key => $var) {
if (! in_array($key, $values['value'])) {
throw new InvalidArgumentException(sprintf(
'Undefined enumerator value "%s" for literal "%s".',
$key,
$var
));
}
}
$this->value = $values['value'];
$this->literal = $values['literal'];
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
use RuntimeException;
use function is_array;
use function is_string;
use function json_encode;
use function sprintf;
/**
* Annotation that can be used to signal to the parser to ignore specific
* annotations during the parsing process.
*
* @Annotation
*/
final class IgnoreAnnotation
{
/** @phpstan-var list<string> */
public $names;
/**
* @phpstan-param array{value: string|list<string>} $values
*
* @throws RuntimeException
*/
public function __construct(array $values)
{
if (is_string($values['value'])) {
$values['value'] = [$values['value']];
}
if (! is_array($values['value'])) {
throw new RuntimeException(sprintf(
'@IgnoreAnnotation expects either a string name, or an array of strings, but got %s.',
json_encode($values['value'])
));
}
$this->names = $values['value'];
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
/**
* Annotation that indicates that the annotated class should be constructed with a named argument call.
*
* @Annotation
* @Target("CLASS")
*/
final class NamedArgumentConstructor
{
}

View File

@ -0,0 +1,13 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
/**
* Annotation that can be used to signal to the parser
* to check if that attribute is required during the parsing process.
*
* @Annotation
*/
final class Required
{
}

View File

@ -0,0 +1,101 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
use InvalidArgumentException;
use function array_keys;
use function get_class;
use function gettype;
use function implode;
use function is_array;
use function is_object;
use function is_string;
use function sprintf;
/**
* Annotation that can be used to signal to the parser
* to check the annotation target during the parsing process.
*
* @Annotation
*/
final class Target
{
public const TARGET_CLASS = 1;
public const TARGET_METHOD = 2;
public const TARGET_PROPERTY = 4;
public const TARGET_ANNOTATION = 8;
public const TARGET_FUNCTION = 16;
public const TARGET_ALL = 31;
/** @var array<string, int> */
private static $map = [
'ALL' => self::TARGET_ALL,
'CLASS' => self::TARGET_CLASS,
'METHOD' => self::TARGET_METHOD,
'PROPERTY' => self::TARGET_PROPERTY,
'FUNCTION' => self::TARGET_FUNCTION,
'ANNOTATION' => self::TARGET_ANNOTATION,
];
/** @phpstan-var list<string> */
public $value;
/**
* Targets as bitmask.
*
* @var int
*/
public $targets;
/**
* Literal target declaration.
*
* @var string
*/
public $literal;
/**
* @phpstan-param array{value?: string|list<string>} $values
*
* @throws InvalidArgumentException
*/
public function __construct(array $values)
{
if (! isset($values['value'])) {
$values['value'] = null;
}
if (is_string($values['value'])) {
$values['value'] = [$values['value']];
}
if (! is_array($values['value'])) {
throw new InvalidArgumentException(
sprintf(
'@Target expects either a string value, or an array of strings, "%s" given.',
is_object($values['value']) ? get_class($values['value']) : gettype($values['value'])
)
);
}
$bitmask = 0;
foreach ($values['value'] as $literal) {
if (! isset(self::$map[$literal])) {
throw new InvalidArgumentException(
sprintf(
'Invalid Target "%s". Available targets: [%s]',
$literal,
implode(', ', array_keys(self::$map))
)
);
}
$bitmask |= self::$map[$literal];
}
$this->targets = $bitmask;
$this->value = $values['value'];
$this->literal = implode(', ', $this->value);
}
}

View File

@ -0,0 +1,158 @@
<?php
namespace Doctrine\Common\Annotations;
use Exception;
use Throwable;
use function get_class;
use function gettype;
use function implode;
use function is_object;
use function sprintf;
/**
* Description of AnnotationException
*/
class AnnotationException extends Exception
{
/**
* Creates a new AnnotationException describing a Syntax error.
*
* @return AnnotationException
*/
public static function syntaxError(string $message)
{
return new self('[Syntax Error] ' . $message);
}
/**
* Creates a new AnnotationException describing a Semantical error.
*
* @return AnnotationException
*/
public static function semanticalError(string $message)
{
return new self('[Semantical Error] ' . $message);
}
/**
* Creates a new AnnotationException describing an error which occurred during
* the creation of the annotation.
*
* @return AnnotationException
*/
public static function creationError(string $message, ?Throwable $previous = null)
{
return new self('[Creation Error] ' . $message, 0, $previous);
}
/**
* Creates a new AnnotationException describing a type error.
*
* @return AnnotationException
*/
public static function typeError(string $message)
{
return new self('[Type Error] ' . $message);
}
/**
* Creates a new AnnotationException describing a constant semantical error.
*
* @return AnnotationException
*/
public static function semanticalErrorConstants(string $identifier, ?string $context = null)
{
return self::semanticalError(sprintf(
"Couldn't find constant %s%s.",
$identifier,
$context ? ', ' . $context : ''
));
}
/**
* Creates a new AnnotationException describing an type error of an attribute.
*
* @param mixed $actual
*
* @return AnnotationException
*/
public static function attributeTypeError(
string $attributeName,
string $annotationName,
string $context,
string $expected,
$actual
) {
return self::typeError(sprintf(
'Attribute "%s" of @%s declared on %s expects %s, but got %s.',
$attributeName,
$annotationName,
$context,
$expected,
is_object($actual) ? 'an instance of ' . get_class($actual) : gettype($actual)
));
}
/**
* Creates a new AnnotationException describing an required error of an attribute.
*
* @return AnnotationException
*/
public static function requiredError(
string $attributeName,
string $annotationName,
string $context,
string $expected
) {
return self::typeError(sprintf(
'Attribute "%s" of @%s declared on %s expects %s. This value should not be null.',
$attributeName,
$annotationName,
$context,
$expected
));
}
/**
* Creates a new AnnotationException describing a invalid enummerator.
*
* @param mixed $given
* @phpstan-param list<string> $available
*
* @return AnnotationException
*/
public static function enumeratorError(
string $attributeName,
string $annotationName,
string $context,
array $available,
$given
) {
return new self(sprintf(
'[Enum Error] Attribute "%s" of @%s declared on %s accepts only [%s], but got %s.',
$attributeName,
$annotationName,
$context,
implode(', ', $available),
is_object($given) ? get_class($given) : $given
));
}
/** @return AnnotationException */
public static function optimizerPlusSaveComments()
{
return new self(
'You have to enable opcache.save_comments=1 or zend_optimizerplus.save_comments=1.'
);
}
/** @return AnnotationException */
public static function optimizerPlusLoadComments()
{
return new self(
'You have to enable opcache.load_comments=1 or zend_optimizerplus.load_comments=1.'
);
}
}

View File

@ -0,0 +1,392 @@
<?php
namespace Doctrine\Common\Annotations;
use Doctrine\Common\Annotations\Annotation\IgnoreAnnotation;
use Doctrine\Common\Annotations\Annotation\Target;
use ReflectionClass;
use ReflectionFunction;
use ReflectionMethod;
use ReflectionProperty;
use function array_merge;
use function class_exists;
use function extension_loaded;
use function filter_var;
use function ini_get;
use const FILTER_VALIDATE_BOOLEAN;
/**
* A reader for docblock annotations.
*/
class AnnotationReader implements Reader
{
/**
* Global map for imports.
*
* @var array<string, class-string>
*/
private static $globalImports = [
'ignoreannotation' => Annotation\IgnoreAnnotation::class,
];
/**
* A list with annotations that are not causing exceptions when not resolved to an annotation class.
*
* The names are case sensitive.
*
* @var array<string, true>
*/
private static $globalIgnoredNames = ImplicitlyIgnoredAnnotationNames::LIST;
/**
* A list with annotations that are not causing exceptions when not resolved to an annotation class.
*
* The names are case sensitive.
*
* @var array<string, true>
*/
private static $globalIgnoredNamespaces = [];
/**
* Add a new annotation to the globally ignored annotation names with regard to exception handling.
*/
public static function addGlobalIgnoredName(string $name)
{
self::$globalIgnoredNames[$name] = true;
}
/**
* Add a new annotation to the globally ignored annotation namespaces with regard to exception handling.
*/
public static function addGlobalIgnoredNamespace(string $namespace)
{
self::$globalIgnoredNamespaces[$namespace] = true;
}
/**
* Annotations parser.
*
* @var DocParser
*/
private $parser;
/**
* Annotations parser used to collect parsing metadata.
*
* @var DocParser
*/
private $preParser;
/**
* PHP parser used to collect imports.
*
* @var PhpParser
*/
private $phpParser;
/**
* In-memory cache mechanism to store imported annotations per class.
*
* @psalm-var array<'class'|'function', array<string, array<string, class-string>>>
*/
private $imports = [];
/**
* In-memory cache mechanism to store ignored annotations per class.
*
* @psalm-var array<'class'|'function', array<string, array<string, true>>>
*/
private $ignoredAnnotationNames = [];
/**
* Initializes a new AnnotationReader.
*
* @throws AnnotationException
*/
public function __construct(?DocParser $parser = null)
{
if (
extension_loaded('Zend Optimizer+') &&
(filter_var(ini_get('zend_optimizerplus.save_comments'), FILTER_VALIDATE_BOOLEAN) === false ||
filter_var(ini_get('opcache.save_comments'), FILTER_VALIDATE_BOOLEAN) === false)
) {
throw AnnotationException::optimizerPlusSaveComments();
}
if (
extension_loaded('Zend OPcache') &&
filter_var(ini_get('opcache.save_comments'), FILTER_VALIDATE_BOOLEAN) === false
) {
throw AnnotationException::optimizerPlusSaveComments();
}
// Make sure that the IgnoreAnnotation annotation is loaded
class_exists(IgnoreAnnotation::class);
$this->parser = $parser ?: new DocParser();
$this->preParser = new DocParser();
$this->preParser->setImports(self::$globalImports);
$this->preParser->setIgnoreNotImportedAnnotations(true);
$this->preParser->setIgnoredAnnotationNames(self::$globalIgnoredNames);
$this->phpParser = new PhpParser();
}
/**
* {@inheritDoc}
*/
public function getClassAnnotations(ReflectionClass $class)
{
$this->parser->setTarget(Target::TARGET_CLASS);
$this->parser->setImports($this->getImports($class));
$this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
$this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName());
}
/**
* {@inheritDoc}
*/
public function getClassAnnotation(ReflectionClass $class, $annotationName)
{
$annotations = $this->getClassAnnotations($class);
foreach ($annotations as $annotation) {
if ($annotation instanceof $annotationName) {
return $annotation;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(ReflectionProperty $property)
{
$class = $property->getDeclaringClass();
$context = 'property ' . $class->getName() . '::$' . $property->getName();
$this->parser->setTarget(Target::TARGET_PROPERTY);
$this->parser->setImports($this->getPropertyImports($property));
$this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
$this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
return $this->parser->parse($property->getDocComment(), $context);
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
{
$annotations = $this->getPropertyAnnotations($property);
foreach ($annotations as $annotation) {
if ($annotation instanceof $annotationName) {
return $annotation;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(ReflectionMethod $method)
{
$class = $method->getDeclaringClass();
$context = 'method ' . $class->getName() . '::' . $method->getName() . '()';
$this->parser->setTarget(Target::TARGET_METHOD);
$this->parser->setImports($this->getMethodImports($method));
$this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
$this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
return $this->parser->parse($method->getDocComment(), $context);
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
$annotations = $this->getMethodAnnotations($method);
foreach ($annotations as $annotation) {
if ($annotation instanceof $annotationName) {
return $annotation;
}
}
return null;
}
/**
* Gets the annotations applied to a function.
*
* @phpstan-return list<object> An array of Annotations.
*/
public function getFunctionAnnotations(ReflectionFunction $function): array
{
$context = 'function ' . $function->getName();
$this->parser->setTarget(Target::TARGET_FUNCTION);
$this->parser->setImports($this->getImports($function));
$this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($function));
$this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
return $this->parser->parse($function->getDocComment(), $context);
}
/**
* Gets a function annotation.
*
* @return object|null The Annotation or NULL, if the requested annotation does not exist.
*/
public function getFunctionAnnotation(ReflectionFunction $function, string $annotationName)
{
$annotations = $this->getFunctionAnnotations($function);
foreach ($annotations as $annotation) {
if ($annotation instanceof $annotationName) {
return $annotation;
}
}
return null;
}
/**
* Returns the ignored annotations for the given class or function.
*
* @param ReflectionClass|ReflectionFunction $reflection
*
* @return array<string, true>
*/
private function getIgnoredAnnotationNames($reflection): array
{
$type = $reflection instanceof ReflectionClass ? 'class' : 'function';
$name = $reflection->getName();
if (isset($this->ignoredAnnotationNames[$type][$name])) {
return $this->ignoredAnnotationNames[$type][$name];
}
$this->collectParsingMetadata($reflection);
return $this->ignoredAnnotationNames[$type][$name];
}
/**
* Retrieves imports for a class or a function.
*
* @param ReflectionClass|ReflectionFunction $reflection
*
* @return array<string, class-string>
*/
private function getImports($reflection): array
{
$type = $reflection instanceof ReflectionClass ? 'class' : 'function';
$name = $reflection->getName();
if (isset($this->imports[$type][$name])) {
return $this->imports[$type][$name];
}
$this->collectParsingMetadata($reflection);
return $this->imports[$type][$name];
}
/**
* Retrieves imports for methods.
*
* @return array<string, class-string>
*/
private function getMethodImports(ReflectionMethod $method)
{
$class = $method->getDeclaringClass();
$classImports = $this->getImports($class);
$traitImports = [];
foreach ($class->getTraits() as $trait) {
if (
! $trait->hasMethod($method->getName())
|| $trait->getFileName() !== $method->getFileName()
) {
continue;
}
$traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait));
}
return array_merge($classImports, $traitImports);
}
/**
* Retrieves imports for properties.
*
* @return array<string, class-string>
*/
private function getPropertyImports(ReflectionProperty $property)
{
$class = $property->getDeclaringClass();
$classImports = $this->getImports($class);
$traitImports = [];
foreach ($class->getTraits() as $trait) {
if (! $trait->hasProperty($property->getName())) {
continue;
}
$traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait));
}
return array_merge($classImports, $traitImports);
}
/**
* Collects parsing metadata for a given class or function.
*
* @param ReflectionClass|ReflectionFunction $reflection
*/
private function collectParsingMetadata($reflection): void
{
$type = $reflection instanceof ReflectionClass ? 'class' : 'function';
$name = $reflection->getName();
$ignoredAnnotationNames = self::$globalIgnoredNames;
$annotations = $this->preParser->parse($reflection->getDocComment(), $type . ' ' . $name);
foreach ($annotations as $annotation) {
if (! ($annotation instanceof IgnoreAnnotation)) {
continue;
}
foreach ($annotation->names as $annot) {
$ignoredAnnotationNames[$annot] = true;
}
}
$this->imports[$type][$name] = array_merge(
self::$globalImports,
$this->phpParser->parseUseStatements($reflection),
[
'__NAMESPACE__' => $reflection->getNamespaceName(),
'self' => $name,
]
);
$this->ignoredAnnotationNames[$type][$name] = $ignoredAnnotationNames;
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace Doctrine\Common\Annotations;
use function array_key_exists;
use function class_exists;
final class AnnotationRegistry
{
/**
* An array of classes which cannot be found
*
* @var null[] indexed by class name
*/
private static $failedToAutoload = [];
public static function reset(): void
{
self::$failedToAutoload = [];
}
/**
* Autoloads an annotation class silently.
*/
public static function loadAnnotationClass(string $class): bool
{
if (class_exists($class, false)) {
return true;
}
if (array_key_exists($class, self::$failedToAutoload)) {
return false;
}
if (class_exists($class)) {
return true;
}
self::$failedToAutoload[$class] = null;
return false;
}
}

View File

@ -0,0 +1,131 @@
<?php
namespace Doctrine\Common\Annotations;
use Doctrine\Common\Lexer\AbstractLexer;
use function ctype_alpha;
use function is_numeric;
use function str_replace;
use function stripos;
use function strlen;
use function strpos;
use function strtolower;
use function substr;
/**
* Simple lexer for docblock annotations.
*
* @template-extends AbstractLexer<DocLexer::T_*, string>
*/
final class DocLexer extends AbstractLexer
{
public const T_NONE = 1;
public const T_INTEGER = 2;
public const T_STRING = 3;
public const T_FLOAT = 4;
// All tokens that are also identifiers should be >= 100
public const T_IDENTIFIER = 100;
public const T_AT = 101;
public const T_CLOSE_CURLY_BRACES = 102;
public const T_CLOSE_PARENTHESIS = 103;
public const T_COMMA = 104;
public const T_EQUALS = 105;
public const T_FALSE = 106;
public const T_NAMESPACE_SEPARATOR = 107;
public const T_OPEN_CURLY_BRACES = 108;
public const T_OPEN_PARENTHESIS = 109;
public const T_TRUE = 110;
public const T_NULL = 111;
public const T_COLON = 112;
public const T_MINUS = 113;
/** @var array<string, self::T*> */
protected $noCase = [
'@' => self::T_AT,
',' => self::T_COMMA,
'(' => self::T_OPEN_PARENTHESIS,
')' => self::T_CLOSE_PARENTHESIS,
'{' => self::T_OPEN_CURLY_BRACES,
'}' => self::T_CLOSE_CURLY_BRACES,
'=' => self::T_EQUALS,
':' => self::T_COLON,
'-' => self::T_MINUS,
'\\' => self::T_NAMESPACE_SEPARATOR,
];
/** @var array<string, self::T*> */
protected $withCase = [
'true' => self::T_TRUE,
'false' => self::T_FALSE,
'null' => self::T_NULL,
];
/**
* Whether the next token starts immediately, or if there were
* non-captured symbols before that
*/
public function nextTokenIsAdjacent(): bool
{
return $this->token === null
|| ($this->lookahead !== null
&& ($this->lookahead->position - $this->token->position) === strlen($this->token->value));
}
/**
* {@inheritDoc}
*/
protected function getCatchablePatterns()
{
return [
'[a-z_\\\][a-z0-9_\:\\\]*[a-z_][a-z0-9_]*',
'(?:[+-]?[0-9]+(?:[\.][0-9]+)*)(?:[eE][+-]?[0-9]+)?',
'"(?:""|[^"])*+"',
];
}
/**
* {@inheritDoc}
*/
protected function getNonCatchablePatterns()
{
return ['\s+', '\*+', '(.)'];
}
/**
* {@inheritDoc}
*/
protected function getType(&$value)
{
$type = self::T_NONE;
if ($value[0] === '"') {
$value = str_replace('""', '"', substr($value, 1, strlen($value) - 2));
return self::T_STRING;
}
if (isset($this->noCase[$value])) {
return $this->noCase[$value];
}
if ($value[0] === '_' || $value[0] === '\\' || ctype_alpha($value[0])) {
return self::T_IDENTIFIER;
}
$lowerValue = strtolower($value);
if (isset($this->withCase[$lowerValue])) {
return $this->withCase[$lowerValue];
}
// Checking numeric value
if (is_numeric($value)) {
return strpos($value, '.') !== false || stripos($value, 'e') !== false
? self::T_FLOAT : self::T_INTEGER;
}
return $type;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,178 @@
<?php
declare(strict_types=1);
namespace Doctrine\Common\Annotations;
/**
* A list of annotations that are implicitly ignored during the parsing process.
*
* All names are case sensitive.
*/
final class ImplicitlyIgnoredAnnotationNames
{
private const Reserved = [
'Annotation' => true,
'Attribute' => true,
'Attributes' => true,
/* Can we enable this? 'Enum' => true, */
'Required' => true,
'Target' => true,
'NamedArgumentConstructor' => true,
];
private const WidelyUsedNonStandard = [
'fix' => true,
'fixme' => true,
'override' => true,
];
private const PhpDocumentor1 = [
'abstract' => true,
'access' => true,
'code' => true,
'deprec' => true,
'endcode' => true,
'exception' => true,
'final' => true,
'ingroup' => true,
'inheritdoc' => true,
'inheritDoc' => true,
'magic' => true,
'name' => true,
'private' => true,
'static' => true,
'staticvar' => true,
'staticVar' => true,
'toc' => true,
'tutorial' => true,
'throw' => true,
];
private const PhpDocumentor2 = [
'api' => true,
'author' => true,
'category' => true,
'copyright' => true,
'deprecated' => true,
'example' => true,
'filesource' => true,
'global' => true,
'ignore' => true,
/* Can we enable this? 'index' => true, */
'internal' => true,
'license' => true,
'link' => true,
'method' => true,
'package' => true,
'param' => true,
'property' => true,
'property-read' => true,
'property-write' => true,
'return' => true,
'see' => true,
'since' => true,
'source' => true,
'subpackage' => true,
'throws' => true,
'todo' => true,
'TODO' => true,
'usedby' => true,
'uses' => true,
'var' => true,
'version' => true,
];
private const PHPUnit = [
'author' => true,
'after' => true,
'afterClass' => true,
'backupGlobals' => true,
'backupStaticAttributes' => true,
'before' => true,
'beforeClass' => true,
'codeCoverageIgnore' => true,
'codeCoverageIgnoreStart' => true,
'codeCoverageIgnoreEnd' => true,
'covers' => true,
'coversDefaultClass' => true,
'coversNothing' => true,
'dataProvider' => true,
'depends' => true,
'doesNotPerformAssertions' => true,
'expectedException' => true,
'expectedExceptionCode' => true,
'expectedExceptionMessage' => true,
'expectedExceptionMessageRegExp' => true,
'group' => true,
'large' => true,
'medium' => true,
'preserveGlobalState' => true,
'requires' => true,
'runTestsInSeparateProcesses' => true,
'runInSeparateProcess' => true,
'small' => true,
'test' => true,
'testdox' => true,
'testWith' => true,
'ticket' => true,
'uses' => true,
];
private const PhpCheckStyle = ['SuppressWarnings' => true];
private const PhpStorm = ['noinspection' => true];
private const PEAR = ['package_version' => true];
private const PlainUML = [
'startuml' => true,
'enduml' => true,
];
private const Symfony = ['experimental' => true];
private const PhpCodeSniffer = [
'codingStandardsIgnoreStart' => true,
'codingStandardsIgnoreEnd' => true,
];
private const SlevomatCodingStandard = ['phpcsSuppress' => true];
private const Phan = ['suppress' => true];
private const Rector = ['noRector' => true];
private const StaticAnalysis = [
// PHPStan, Psalm
'extends' => true,
'implements' => true,
'readonly' => true,
'template' => true,
'use' => true,
// Psalm
'pure' => true,
'immutable' => true,
];
public const LIST = self::Reserved
+ self::WidelyUsedNonStandard
+ self::PhpDocumentor1
+ self::PhpDocumentor2
+ self::PHPUnit
+ self::PhpCheckStyle
+ self::PhpStorm
+ self::PEAR
+ self::PlainUML
+ self::Symfony
+ self::SlevomatCodingStandard
+ self::PhpCodeSniffer
+ self::Phan
+ self::Rector
+ self::StaticAnalysis;
private function __construct()
{
}
}

View File

@ -0,0 +1,99 @@
<?php
namespace Doctrine\Common\Annotations;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use function call_user_func_array;
use function get_class;
/**
* Allows the reader to be used in-place of Doctrine's reader.
*/
class IndexedReader implements Reader
{
/** @var Reader */
private $delegate;
public function __construct(Reader $reader)
{
$this->delegate = $reader;
}
/**
* {@inheritDoc}
*/
public function getClassAnnotations(ReflectionClass $class)
{
$annotations = [];
foreach ($this->delegate->getClassAnnotations($class) as $annot) {
$annotations[get_class($annot)] = $annot;
}
return $annotations;
}
/**
* {@inheritDoc}
*/
public function getClassAnnotation(ReflectionClass $class, $annotationName)
{
return $this->delegate->getClassAnnotation($class, $annotationName);
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(ReflectionMethod $method)
{
$annotations = [];
foreach ($this->delegate->getMethodAnnotations($method) as $annot) {
$annotations[get_class($annot)] = $annot;
}
return $annotations;
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
return $this->delegate->getMethodAnnotation($method, $annotationName);
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(ReflectionProperty $property)
{
$annotations = [];
foreach ($this->delegate->getPropertyAnnotations($property) as $annot) {
$annotations[get_class($annot)] = $annot;
}
return $annotations;
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
{
return $this->delegate->getPropertyAnnotation($property, $annotationName);
}
/**
* Proxies all methods to the delegate.
*
* @param mixed[] $args
*
* @return mixed
*/
public function __call(string $method, array $args)
{
return call_user_func_array([$this->delegate, $method], $args);
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace Doctrine\Common\Annotations;
use ReflectionClass;
use ReflectionFunction;
use SplFileObject;
use function is_file;
use function method_exists;
use function preg_quote;
use function preg_replace;
/**
* Parses a file for namespaces/use/class declarations.
*/
final class PhpParser
{
/**
* Parse a class or function for use statements.
*
* @param ReflectionClass|ReflectionFunction $reflection
*
* @psalm-return array<string, string> a list with use statements in the form (Alias => FQN).
*/
public function parseUseStatements($reflection): array
{
if (method_exists($reflection, 'getUseStatements')) {
return $reflection->getUseStatements();
}
$filename = $reflection->getFileName();
if ($filename === false) {
return [];
}
$content = $this->getFileContent($filename, $reflection->getStartLine());
if ($content === null) {
return [];
}
$namespace = preg_quote($reflection->getNamespaceName());
$content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content);
$tokenizer = new TokenParser('<?php ' . $content);
return $tokenizer->parseUseStatements($reflection->getNamespaceName());
}
/**
* Gets the content of the file right up to the given line number.
*
* @param string $filename The name of the file to load.
* @param int $lineNumber The number of lines to read from file.
*
* @return string|null The content of the file or null if the file does not exist.
*/
private function getFileContent(string $filename, $lineNumber)
{
if (! is_file($filename)) {
return null;
}
$content = '';
$lineCnt = 0;
$file = new SplFileObject($filename);
while (! $file->eof()) {
if ($lineCnt++ === $lineNumber) {
break;
}
$content .= $file->fgets();
}
return $content;
}
}

View File

@ -0,0 +1,233 @@
<?php
namespace Doctrine\Common\Annotations;
use Psr\Cache\CacheItemPoolInterface;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use Reflector;
use function array_map;
use function array_merge;
use function assert;
use function filemtime;
use function is_file;
use function max;
use function rawurlencode;
use function time;
/**
* A cache aware annotation reader.
*/
final class PsrCachedReader implements Reader
{
/** @var Reader */
private $delegate;
/** @var CacheItemPoolInterface */
private $cache;
/** @var bool */
private $debug;
/** @var array<string, array<object>> */
private $loadedAnnotations = [];
/** @var int[] */
private $loadedFilemtimes = [];
public function __construct(Reader $reader, CacheItemPoolInterface $cache, bool $debug = false)
{
$this->delegate = $reader;
$this->cache = $cache;
$this->debug = (bool) $debug;
}
/**
* {@inheritDoc}
*/
public function getClassAnnotations(ReflectionClass $class)
{
$cacheKey = $class->getName();
if (isset($this->loadedAnnotations[$cacheKey])) {
return $this->loadedAnnotations[$cacheKey];
}
$annots = $this->fetchFromCache($cacheKey, $class, 'getClassAnnotations', $class);
return $this->loadedAnnotations[$cacheKey] = $annots;
}
/**
* {@inheritDoc}
*/
public function getClassAnnotation(ReflectionClass $class, $annotationName)
{
foreach ($this->getClassAnnotations($class) as $annot) {
if ($annot instanceof $annotationName) {
return $annot;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(ReflectionProperty $property)
{
$class = $property->getDeclaringClass();
$cacheKey = $class->getName() . '$' . $property->getName();
if (isset($this->loadedAnnotations[$cacheKey])) {
return $this->loadedAnnotations[$cacheKey];
}
$annots = $this->fetchFromCache($cacheKey, $class, 'getPropertyAnnotations', $property);
return $this->loadedAnnotations[$cacheKey] = $annots;
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
{
foreach ($this->getPropertyAnnotations($property) as $annot) {
if ($annot instanceof $annotationName) {
return $annot;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(ReflectionMethod $method)
{
$class = $method->getDeclaringClass();
$cacheKey = $class->getName() . '#' . $method->getName();
if (isset($this->loadedAnnotations[$cacheKey])) {
return $this->loadedAnnotations[$cacheKey];
}
$annots = $this->fetchFromCache($cacheKey, $class, 'getMethodAnnotations', $method);
return $this->loadedAnnotations[$cacheKey] = $annots;
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
foreach ($this->getMethodAnnotations($method) as $annot) {
if ($annot instanceof $annotationName) {
return $annot;
}
}
return null;
}
public function clearLoadedAnnotations(): void
{
$this->loadedAnnotations = [];
$this->loadedFilemtimes = [];
}
/** @return mixed[] */
private function fetchFromCache(
string $cacheKey,
ReflectionClass $class,
string $method,
Reflector $reflector
): array {
$cacheKey = rawurlencode($cacheKey);
$item = $this->cache->getItem($cacheKey);
if (($this->debug && ! $this->refresh($cacheKey, $class)) || ! $item->isHit()) {
$this->cache->save($item->set($this->delegate->{$method}($reflector)));
}
return $item->get();
}
/**
* Used in debug mode to check if the cache is fresh.
*
* @return bool Returns true if the cache was fresh, or false if the class
* being read was modified since writing to the cache.
*/
private function refresh(string $cacheKey, ReflectionClass $class): bool
{
$lastModification = $this->getLastModification($class);
if ($lastModification === 0) {
return true;
}
$item = $this->cache->getItem('[C]' . $cacheKey);
if ($item->isHit() && $item->get() >= $lastModification) {
return true;
}
$this->cache->save($item->set(time()));
return false;
}
/**
* Returns the time the class was last modified, testing traits and parents
*/
private function getLastModification(ReflectionClass $class): int
{
$filename = $class->getFileName();
if (isset($this->loadedFilemtimes[$filename])) {
return $this->loadedFilemtimes[$filename];
}
$parent = $class->getParentClass();
$lastModification = max(array_merge(
[$filename !== false && is_file($filename) ? filemtime($filename) : 0],
array_map(function (ReflectionClass $reflectionTrait): int {
return $this->getTraitLastModificationTime($reflectionTrait);
}, $class->getTraits()),
array_map(function (ReflectionClass $class): int {
return $this->getLastModification($class);
}, $class->getInterfaces()),
$parent ? [$this->getLastModification($parent)] : []
));
assert($lastModification !== false);
return $this->loadedFilemtimes[$filename] = $lastModification;
}
private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int
{
$fileName = $reflectionTrait->getFileName();
if (isset($this->loadedFilemtimes[$fileName])) {
return $this->loadedFilemtimes[$fileName];
}
$lastModificationTime = max(array_merge(
[$fileName !== false && is_file($fileName) ? filemtime($fileName) : 0],
array_map(function (ReflectionClass $reflectionTrait): int {
return $this->getTraitLastModificationTime($reflectionTrait);
}, $reflectionTrait->getTraits())
));
assert($lastModificationTime !== false);
return $this->loadedFilemtimes[$fileName] = $lastModificationTime;
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace Doctrine\Common\Annotations;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
/**
* Interface for annotation readers.
*/
interface Reader
{
/**
* Gets the annotations applied to a class.
*
* @param ReflectionClass $class The ReflectionClass of the class from which
* the class annotations should be read.
*
* @return array<object> An array of Annotations.
*/
public function getClassAnnotations(ReflectionClass $class);
/**
* Gets a class annotation.
*
* @param ReflectionClass $class The ReflectionClass of the class from which
* the class annotations should be read.
* @param class-string<T> $annotationName The name of the annotation.
*
* @return T|null The Annotation or NULL, if the requested annotation does not exist.
*
* @template T
*/
public function getClassAnnotation(ReflectionClass $class, $annotationName);
/**
* Gets the annotations applied to a method.
*
* @param ReflectionMethod $method The ReflectionMethod of the method from which
* the annotations should be read.
*
* @return array<object> An array of Annotations.
*/
public function getMethodAnnotations(ReflectionMethod $method);
/**
* Gets a method annotation.
*
* @param ReflectionMethod $method The ReflectionMethod to read the annotations from.
* @param class-string<T> $annotationName The name of the annotation.
*
* @return T|null The Annotation or NULL, if the requested annotation does not exist.
*
* @template T
*/
public function getMethodAnnotation(ReflectionMethod $method, $annotationName);
/**
* Gets the annotations applied to a property.
*
* @param ReflectionProperty $property The ReflectionProperty of the property
* from which the annotations should be read.
*
* @return array<object> An array of Annotations.
*/
public function getPropertyAnnotations(ReflectionProperty $property);
/**
* Gets a property annotation.
*
* @param ReflectionProperty $property The ReflectionProperty to read the annotations from.
* @param class-string<T> $annotationName The name of the annotation.
*
* @return T|null The Annotation or NULL, if the requested annotation does not exist.
*
* @template T
*/
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName);
}

View File

@ -0,0 +1,205 @@
<?php
namespace Doctrine\Common\Annotations;
use function array_merge;
use function count;
use function explode;
use function strtolower;
use function token_get_all;
use const PHP_VERSION_ID;
use const T_AS;
use const T_COMMENT;
use const T_DOC_COMMENT;
use const T_NAME_FULLY_QUALIFIED;
use const T_NAME_QUALIFIED;
use const T_NAMESPACE;
use const T_NS_SEPARATOR;
use const T_STRING;
use const T_USE;
use const T_WHITESPACE;
/**
* Parses a file for namespaces/use/class declarations.
*/
class TokenParser
{
/**
* The token list.
*
* @phpstan-var list<mixed[]>
*/
private $tokens;
/**
* The number of tokens.
*
* @var int
*/
private $numTokens;
/**
* The current array pointer.
*
* @var int
*/
private $pointer = 0;
public function __construct(string $contents)
{
$this->tokens = token_get_all($contents);
// The PHP parser sets internal compiler globals for certain things. Annoyingly, the last docblock comment it
// saw gets stored in doc_comment. When it comes to compile the next thing to be include()d this stored
// doc_comment becomes owned by the first thing the compiler sees in the file that it considers might have a
// docblock. If the first thing in the file is a class without a doc block this would cause calls to
// getDocBlock() on said class to return our long lost doc_comment. Argh.
// To workaround, cause the parser to parse an empty docblock. Sure getDocBlock() will return this, but at least
// it's harmless to us.
token_get_all("<?php\n/**\n *\n */");
$this->numTokens = count($this->tokens);
}
/**
* Gets the next non whitespace and non comment token.
*
* @param bool $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped.
* If FALSE then only whitespace and normal comments are skipped.
*
* @return mixed[]|string|null The token if exists, null otherwise.
*/
public function next(bool $docCommentIsComment = true)
{
for ($i = $this->pointer; $i < $this->numTokens; $i++) {
$this->pointer++;
if (
$this->tokens[$i][0] === T_WHITESPACE ||
$this->tokens[$i][0] === T_COMMENT ||
($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT)
) {
continue;
}
return $this->tokens[$i];
}
return null;
}
/**
* Parses a single use statement.
*
* @return array<string, string> A list with all found class names for a use statement.
*/
public function parseUseStatement()
{
$groupRoot = '';
$class = '';
$alias = '';
$statements = [];
$explicitAlias = false;
while (($token = $this->next())) {
if (! $explicitAlias && $token[0] === T_STRING) {
$class .= $token[1];
$alias = $token[1];
} elseif ($explicitAlias && $token[0] === T_STRING) {
$alias = $token[1];
} elseif (
PHP_VERSION_ID >= 80000 &&
($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED)
) {
$class .= $token[1];
$classSplit = explode('\\', $token[1]);
$alias = $classSplit[count($classSplit) - 1];
} elseif ($token[0] === T_NS_SEPARATOR) {
$class .= '\\';
$alias = '';
} elseif ($token[0] === T_AS) {
$explicitAlias = true;
$alias = '';
} elseif ($token === ',') {
$statements[strtolower($alias)] = $groupRoot . $class;
$class = '';
$alias = '';
$explicitAlias = false;
} elseif ($token === ';') {
$statements[strtolower($alias)] = $groupRoot . $class;
break;
} elseif ($token === '{') {
$groupRoot = $class;
$class = '';
} elseif ($token === '}') {
continue;
} else {
break;
}
}
return $statements;
}
/**
* Gets all use statements.
*
* @param string $namespaceName The namespace name of the reflected class.
*
* @return array<string, string> A list with all found use statements.
*/
public function parseUseStatements(string $namespaceName)
{
$statements = [];
while (($token = $this->next())) {
if ($token[0] === T_USE) {
$statements = array_merge($statements, $this->parseUseStatement());
continue;
}
if ($token[0] !== T_NAMESPACE || $this->parseNamespace() !== $namespaceName) {
continue;
}
// Get fresh array for new namespace. This is to prevent the parser to collect the use statements
// for a previous namespace with the same name. This is the case if a namespace is defined twice
// or if a namespace with the same name is commented out.
$statements = [];
}
return $statements;
}
/**
* Gets the namespace.
*
* @return string The found namespace.
*/
public function parseNamespace()
{
$name = '';
while (
($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR || (
PHP_VERSION_ID >= 80000 &&
($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED)
))
) {
$name .= $token[1];
}
return $name;
}
/**
* Gets the class name.
*
* @return string The found class name.
*/
public function parseClass()
{
// Namespaces and class names are tokenized the same: T_STRINGs
// separated by T_NS_SEPARATOR so we can use one function to provide
// both.
return $this->parseNamespace();
}
}

19
vendor/doctrine/common/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2006-2015 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

12
vendor/doctrine/common/README.md vendored Normal file
View File

@ -0,0 +1,12 @@
# Doctrine Common
[![Build Status](https://github.com/doctrine/common/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/common/actions)
[![codecov](https://codecov.io/gh/doctrine/common/branch/3.1.x/graph/badge.svg)](https://codecov.io/gh/doctrine/common)
The Doctrine Common project is a library that provides extensions to core PHP functionality.
## More resources:
* [Website](https://www.doctrine-project.org/)
* [Documentation](https://www.doctrine-project.org/projects/doctrine-common/en/current/)
* [Downloads](https://github.com/doctrine/common/releases)

39
vendor/doctrine/common/UPGRADE_TO_2_1 vendored Normal file
View File

@ -0,0 +1,39 @@
This document details all the possible changes that you should investigate when updating
your project from Doctrine Common 2.0.x to 2.1
## AnnotationReader changes
The annotation reader was heavily refactored between 2.0 and 2.1-RC1. In theory the operation of the new reader should be backwards compatible, but it has to be setup differently to work that way:
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
// new code necessary starting here
$reader->setIgnoreNotImportedAnnotations(true);
$reader->setEnableParsePhpImports(false);
$reader = new \Doctrine\Common\Annotations\CachedReader(
new \Doctrine\Common\Annotations\IndexedReader($reader), new ArrayCache()
);
## Annotation Base class or @Annotation
Beginning after 2.1-RC2 you have to either extend ``Doctrine\Common\Annotations\Annotation`` or add @Annotation to your annotations class-level docblock, otherwise the class will simply be ignored.
## Removed methods on AnnotationReader
* AnnotationReader::setAutoloadAnnotations()
* AnnotationReader::getAutoloadAnnotations()
* AnnotationReader::isAutoloadAnnotations()
## AnnotationRegistry
Autoloading through the PHP autoloader is removed from the 2.1 AnnotationReader. Instead you have to use the global AnnotationRegistry for loading purposes:
\Doctrine\Common\Annotations\AnnotationRegistry::registerFile($fileWithAnnotations);
\Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespace($namespace, $dirs = null);
\Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespaces($namespaces);
\Doctrine\Common\Annotations\AnnotationRegistry::registerLoader($callable);
The $callable for registering a loader accepts a class as first and only parameter and must try to silently autoload it. On success true has to be returned.
The registerAutoloadNamespace function registers a PSR-0 compatible silent autoloader for all classes with the given namespace in the given directories.
If null is passed as directory the include path will be used.

61
vendor/doctrine/common/UPGRADE_TO_2_2 vendored Normal file
View File

@ -0,0 +1,61 @@
This document details all the possible changes that you should investigate when
updating your project from Doctrine Common 2.1 to 2.2:
## Annotation Changes
- AnnotationReader::setIgnoreNotImportedAnnotations has been removed, you need to
add ignore annotation names which are supposed to be ignored via
AnnotationReader::addGlobalIgnoredName
- AnnotationReader::setAutoloadAnnotations was deprecated by the AnnotationRegistry
in 2.1 and has been removed in 2.2
- AnnotationReader::setEnableParsePhpImports was added to ease transition to the new
annotation mechanism in 2.1 and is removed in 2.2
- AnnotationReader::isParsePhpImportsEnabled is removed (see above)
- AnnotationReader::setDefaultAnnotationNamespace was deprecated in favor of explicit
configuration in 2.1 and will be removed in 2.2 (for isolated projects where you
have full-control over _all_ available annotations, we offer a dedicated reader
class ``SimpleAnnotationReader``)
- AnnotationReader::setAnnotationCreationFunction was deprecated in 2.1 and will be
removed in 2.2. We only offer two creation mechanisms which cannot be changed
anymore to allow the same reader instance to work with all annotations regardless
of which library they are coming from.
- AnnotationReader::setAnnotationNamespaceAlias was deprecated in 2.1 and will be
removed in 2.2 (see setDefaultAnnotationNamespace)
- If you use a class as annotation which has not the @Annotation marker in it's
class block, we will now throw an exception instead of silently ignoring it. You
can however still achieve the previous behavior using the @IgnoreAnnotation, or
AnnotationReader::addGlobalIgnoredName (the exception message will contain detailed
instructions when you run into this problem).
## Cache Changes
- Renamed old AbstractCache to CacheProvider
- Dropped the support to the following functions of all cache providers:
- CacheProvider::deleteByWildcard
- CacheProvider::deleteByRegEx
- CacheProvider::deleteByPrefix
- CacheProvider::deleteBySuffix
- CacheProvider::deleteAll will not remove ALL entries, it will only mark them as invalid
- CacheProvider::flushAll will remove ALL entries, namespaced or not
- Added support to MemcachedCache
- Added support to WincacheCache
## ClassLoader Changes
- ClassLoader::fileExistsInIncludePath() no longer exists. Use the native stream_resolve_include_path() PHP function

50
vendor/doctrine/common/composer.json vendored Normal file
View File

@ -0,0 +1,50 @@
{
"name": "doctrine/common",
"type": "library",
"description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.",
"keywords": [
"php",
"common",
"doctrine"
],
"homepage": "https://www.doctrine-project.org/projects/common.html",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"},
{"name": "Marco Pivetta", "email": "ocramius@gmail.com"}
],
"require": {
"php": "^7.1 || ^8.0",
"doctrine/persistence": "^2.0 || ^3.0 || ^4.0"
},
"require-dev": {
"doctrine/collections": "^1",
"phpstan/phpstan": "^1.4.1",
"phpstan/phpstan-phpunit": "^1",
"phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0",
"doctrine/coding-standard": "^9.0 || ^10.0",
"squizlabs/php_codesniffer": "^3.0",
"symfony/phpunit-bridge": "^6.1",
"vimeo/psalm": "^4.4"
},
"autoload": {
"psr-4": {
"Doctrine\\Common\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Tests\\": "tests"
}
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true,
"composer/package-versions-deprecated": true
}
}
}

View File

@ -0,0 +1,10 @@
Common Documentation
====================
Welcome to the Doctrine Common Library documentation.
.. toctree::
:depth: 2
:glob:
*

View File

@ -0,0 +1,241 @@
Class Loading
=============
Class loading is an essential part of any PHP application that
makes heavy use of classes and interfaces. Unfortunately, a lot of
people and projects spend a lot of time and effort on custom and
specialized class loading strategies. It can quickly become a pain
to understand what is going on when using multiple libraries and/or
frameworks, each with its own way to do class loading. Class
loading should be simple and it is an ideal candidate for
convention over configuration.
Overview
--------
The Doctrine Common ClassLoader implements a simple and efficient
approach to class loading that is easy to understand and use. The
implementation is based on the widely used and accepted convention
of mapping namespace and class names to a directory structure. This
approach is used for example by Symfony2, the Zend Framework and of
course, Doctrine.
For example, the following class:
.. code-block:: php
<?php
namespace MyProject\Shipping;
class ShippingStrategy { ... }
resides in the following directory structure:
::
src/
/MyProject
/Shipping
ShippingStrategy.php
Note that the name of "src" or the structure above or beside this
directory is completely arbitrary. "src" could be named "classes"
or "lib" or whatever. The only convention to adhere to is to map
namespaces to directories and classes to files named after the
class name.
Usage
-----
To use a Doctrine Common ClassLoader, you first need to load the
class file containing the ClassLoader. This is the only class file
that actually needs to be loaded explicitly via ``require``. All
other classes will be loaded on demand by the configured class
loaders.
.. code-block:: php
<?php
use Doctrine\Common\ClassLoader;
require '/path/to/Doctrine/Common/ClassLoader.php';
$classLoader = new ClassLoader('MyProject', '/path/to/src');
A ``ClassLoader`` takes two constructor parameters, both optional.
In the normal case both arguments are supplied. The first argument
specifies the namespace prefix this class loader should be
responsible for and the second parameter is the path to the root
directory where the classes can be found according to the
convention mentioned previously.
The class loader in the example above would thus be responsible for
all classes under the 'MyProject' namespace and it would look for
the class files starting at the directory '/path/to/src'.
Also note that the prefix supplied in the first argument need not
be a root namespace but can be an arbitrarily nested namespace as
well. This allows you to even have the sources of subnamespaces
split across different directories. For example, all projects under
the Doctrine umbrella reside in the Doctrine namespace, yet the
sources for each project usually do not reside under a common root
directory. The following is an example of configuring three class
loaders, one for each used Doctrine project:
.. code-block:: php
<?php
use Doctrine\Common\ClassLoader;
require '/path/to/Doctrine/Common/ClassLoader.php';
$commonLoader = new ClassLoader('Doctrine\Common', '/path/to/common/lib');
$dbalLoader = new ClassLoader('Doctrine\DBAL', '/path/to/dbal/lib');
$ormLoader = new ClassLoader('Doctrine\ORM', '/path/to/orm/lib');
$commonLoader->register();
$dbalLoader->register();
$ormLoader->register();
Do not be afraid of using multiple class loaders. Due to the
efficient class loading design you will not incur much overhead
from using many class loaders. Take a look at the implementation of
``ClassLoader#loadClass`` to see how simple and efficient the class
loading is. The iteration over the installed class loaders happens
in C (with the exception of using ``ClassLoader::classExists``).
A ClassLoader can be used in the following other variations,
however, these are rarely used/needed:
- If only the second argument is not supplied, the class loader
will be responsible for the namespace prefix given in the first
argument and it will rely on the PHP include_path.
- If only the first argument is not supplied, the class loader
will be responsible for *all* classes and it will try to look up
*all* classes starting at the directory given as the second
argument.
- If both arguments are not supplied, the class loader will be
responsible for *all* classes and it will rely on the PHP
include_path.
File Extension
--------------
By default, a ClassLoader uses the ``.php`` file extension for all
class files. You can change this behavior, for example to use a
ClassLoader to load classes from a library that uses the
".class.php" convention (but it must nevertheless adhere to the
directory structure convention!):
.. code-block:: php
<?php
$customLoader = new ClassLoader('CustomLib', '/path/to/custom/lib');
$customLoader->setFileExtension('.class.php');
$customLoader->register();
Namespace Separator
-------------------
By default, a ClassLoader uses the ``\`` namespace separator. You
can change this behavior, for example to use a ClassLoader to load
legacy Zend Framework classes that still use the underscore "_"
separator:
.. code-block:: php
<?php
$zend1Loader = new ClassLoader('Zend', '/path/to/zend/lib');
$zend1Loader->setNamespaceSeparator('_');
$zend1Loader->register();
Failing Silently and class_exists
----------------------------------
A lot of class/autoloaders these days try to fail silently when a
class file is not found. For the most part this is necessary in
order to support using ``class_exists('ClassName', true)`` which is
supposed to return a boolean value but triggers autoloading. This
is a bad thing as it basically forces class loaders to fail
silently, which in turn requires costly file_exists or fopen calls
for each class being loaded, even though in at least 99% of the
cases this is not necessary (compare the number of
class_exists(..., true) invocations to the total number of classes
being loaded in a request).
The Doctrine Common ClassLoader does not fail silently, by design.
It therefore does not need any costly checks for file existence. A
ClassLoader is always responsible for all classes with a certain
namespace prefix and if a class is requested to be loaded and can
not be found this is considered to be a fatal error. This also
means that using class_exists(..., true) to check for class
existence when using a Doctrine Common ClassLoader is not possible
but this is not a bad thing. What class\_exists(..., true) actually
means is two things: 1) Check whether the class is already
defined/exists (i.e. class_exists(..., false)) and if not 2) check
whether a class file can be loaded for that class. In the Doctrine
Common ClassLoader the two responsibilities of loading a class and
checking for its existence are separated, which can be observed by
the existence of the two methods ``loadClass`` and
``canLoadClass``. Thereby ``loadClass`` does not invoke
``canLoadClass`` internally, by design. However, you are free to
use it yourself to check whether a class can be loaded and the
following code snippet is thus equivalent to class\_exists(...,
true):
.. code-block:: php
<?php
// Equivalent to if (('Foo', true)) if there is only 1 class loader to check
if (class_exists('Foo', false) || $classLoader->canLoadClass('Foo')) {
// ...
}
The only problem with this is that it is inconvenient as you need
to have a reference to the class loaders around (and there are
often multiple class loaders in use). Therefore, a simpler
alternative exists for the cases in which you really want to ask
all installed class loaders whether they can load the class:
``ClassLoader::classExists($className)``:
.. code-block:: php
<?php
// Equivalent to if (class_exists('Foo', true))
if (ClassLoader::classExists('Foo')) {
// ...
}
This static method can basically be used as a drop-in replacement
for class_exists(..., true). It iterates over all installed class
loaders and asks each of them via ``canLoadClass``, returning early
(with TRUE) as soon as one class loader returns TRUE from
``canLoadClass``. If this sounds like it can potentially be rather
costly then because that is true but it is exactly the same thing
that class_exists(..., true) does under the hood, it triggers a
complete interaction of all class/auto loaders. Checking for class
existence via invoking autoloading was never a cheap thing to do
but now it is more obvious and more importantly, this check is no
longer interleaved with regular class loading, which avoids having
to check each and every class for existence prior to loading it.
The vast majority of classes to be loaded are *not* optional and a
failure to load such a class is, and should be, a fatal error. The
ClassLoader design reflects this.
If you have code that requires the usage of class\_exists(...,
true) or ClassLoader::classExists during normal runtime of the
application (i.e. on each request) try to refactor your design to
avoid it.
Summary
-------
No matter which class loader you prefer to use (Doctrine classes do
not care about how they are loaded), we kindly encourage you to
adhere to the simple convention of mapping namespaces and class
names to a directory structure.
Class loading should be simple, automated and uniform. Time is
better invested in actual application development than in designing
special directory structures, autoloaders and clever caching
strategies for class loading.

View File

@ -0,0 +1,285 @@
<?php
namespace Doctrine\Common;
use function class_exists;
use function interface_exists;
use function is_array;
use function is_file;
use function reset;
use function spl_autoload_functions;
use function spl_autoload_register;
use function spl_autoload_unregister;
use function str_replace;
use function stream_resolve_include_path;
use function strpos;
use function trait_exists;
use function trigger_error;
use const DIRECTORY_SEPARATOR;
use const E_USER_DEPRECATED;
@trigger_error(ClassLoader::class . ' is deprecated.', E_USER_DEPRECATED);
/**
* A <tt>ClassLoader</tt> is an autoloader for class files that can be
* installed on the SPL autoload stack. It is a class loader that either loads only classes
* of a specific namespace or all namespaces and it is suitable for working together
* with other autoloaders in the SPL autoload stack.
*
* If no include path is configured through the constructor or {@link setIncludePath}, a ClassLoader
* relies on the PHP <code>include_path</code>.
*
* @deprecated The ClassLoader is deprecated and will be removed in version 4.0 of doctrine/common.
*/
class ClassLoader
{
/**
* PHP file extension.
*
* @var string
*/
protected $fileExtension = '.php';
/**
* Current namespace.
*
* @var string|null
*/
protected $namespace;
/**
* Current include path.
*
* @var string|null
*/
protected $includePath;
/**
* PHP namespace separator.
*
* @var string
*/
protected $namespaceSeparator = '\\';
/**
* Creates a new <tt>ClassLoader</tt> that loads classes of the
* specified namespace from the specified include path.
*
* If no include path is given, the ClassLoader relies on the PHP include_path.
* If neither a namespace nor an include path is given, the ClassLoader will
* be responsible for loading all classes, thereby relying on the PHP include_path.
*
* @param string|null $ns The namespace of the classes to load.
* @param string|null $includePath The base include path to use.
*/
public function __construct($ns = null, $includePath = null)
{
$this->namespace = $ns;
$this->includePath = $includePath;
}
/**
* Sets the namespace separator used by classes in the namespace of this ClassLoader.
*
* @param string $sep The separator to use.
*
* @return void
*/
public function setNamespaceSeparator($sep)
{
$this->namespaceSeparator = $sep;
}
/**
* Gets the namespace separator used by classes in the namespace of this ClassLoader.
*
* @return string
*/
public function getNamespaceSeparator()
{
return $this->namespaceSeparator;
}
/**
* Sets the base include path for all class files in the namespace of this ClassLoader.
*
* @param string|null $includePath
*
* @return void
*/
public function setIncludePath($includePath)
{
$this->includePath = $includePath;
}
/**
* Gets the base include path for all class files in the namespace of this ClassLoader.
*
* @return string|null
*/
public function getIncludePath()
{
return $this->includePath;
}
/**
* Sets the file extension of class files in the namespace of this ClassLoader.
*
* @param string $fileExtension
*
* @return void
*/
public function setFileExtension($fileExtension)
{
$this->fileExtension = $fileExtension;
}
/**
* Gets the file extension of class files in the namespace of this ClassLoader.
*
* @return string
*/
public function getFileExtension()
{
return $this->fileExtension;
}
/**
* Registers this ClassLoader on the SPL autoload stack.
*
* @return void
*/
public function register()
{
spl_autoload_register([$this, 'loadClass']);
}
/**
* Removes this ClassLoader from the SPL autoload stack.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister([$this, 'loadClass']);
}
/**
* Loads the given class or interface.
*
* @param string $className The name of the class to load.
* @psalm-param class-string $className
*
* @return bool TRUE if the class has been successfully loaded, FALSE otherwise.
*/
public function loadClass($className)
{
if (self::typeExists($className)) {
return true;
}
if (! $this->canLoadClass($className)) {
return false;
}
require($this->includePath !== null ? $this->includePath . DIRECTORY_SEPARATOR : '')
. str_replace($this->namespaceSeparator, DIRECTORY_SEPARATOR, $className)
. $this->fileExtension;
return self::typeExists($className);
}
/**
* Asks this ClassLoader whether it can potentially load the class (file) with
* the given name.
*
* @param string $className The fully-qualified name of the class.
* @psalm-param class-string $className
*
* @return bool TRUE if this ClassLoader can load the class, FALSE otherwise.
*/
public function canLoadClass($className)
{
if ($this->namespace !== null && strpos($className, $this->namespace . $this->namespaceSeparator) !== 0) {
return false;
}
$file = str_replace($this->namespaceSeparator, DIRECTORY_SEPARATOR, $className) . $this->fileExtension;
if ($this->includePath !== null) {
return is_file($this->includePath . DIRECTORY_SEPARATOR . $file);
}
return stream_resolve_include_path($file) !== false;
}
/**
* Checks whether a class with a given name exists. A class "exists" if it is either
* already defined in the current request or if there is an autoloader on the SPL
* autoload stack that is a) responsible for the class in question and b) is able to
* load a class file in which the class definition resides.
*
* If the class is not already defined, each autoloader in the SPL autoload stack
* is asked whether it is able to tell if the class exists. If the autoloader is
* a <tt>ClassLoader</tt>, {@link canLoadClass} is used, otherwise the autoload
* function of the autoloader is invoked and expected to return a value that
* evaluates to TRUE if the class (file) exists. As soon as one autoloader reports
* that the class exists, TRUE is returned.
*
* Note that, depending on what kinds of autoloaders are installed on the SPL
* autoload stack, the class (file) might already be loaded as a result of checking
* for its existence. This is not the case with a <tt>ClassLoader</tt>, who separates
* these responsibilities.
*
* @param string $className The fully-qualified name of the class.
* @psalm-param class-string $className
*
* @return bool TRUE if the class exists as per the definition given above, FALSE otherwise.
*/
public static function classExists($className)
{
return self::typeExists($className, true);
}
/**
* Gets the <tt>ClassLoader</tt> from the SPL autoload stack that is responsible
* for (and is able to load) the class with the given name.
*
* @param string $className The name of the class.
* @psalm-param class-string $className
*
* @return ClassLoader|null The <tt>ClassLoader</tt> for the class or NULL if no such <tt>ClassLoader</tt> exists.
*/
public static function getClassLoader($className)
{
foreach (spl_autoload_functions() as $loader) {
if (! is_array($loader)) {
continue;
}
$classLoader = reset($loader);
if ($classLoader instanceof ClassLoader && $classLoader->canLoadClass($className)) {
return $classLoader;
}
}
return null;
}
/**
* Checks whether a given type exists
*
* @param string $type
* @param bool $autoload
*
* @return bool
*/
private static function typeExists($type, $autoload = false)
{
return class_exists($type, $autoload)
|| interface_exists($type, $autoload)
|| trait_exists($type, $autoload);
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Doctrine\Common;
use Exception;
/**
* Base exception class for package Doctrine\Common.
*
* @deprecated The doctrine/common package is deprecated, please use specific packages and their exceptions instead.
*/
class CommonException extends Exception
{
}

View File

@ -0,0 +1,26 @@
<?php
namespace Doctrine\Common;
/**
* Comparable interface that allows to compare two value objects to each other for similarity.
*
* @link www.doctrine-project.org
*/
interface Comparable
{
/**
* Compares the current object to the passed $other.
*
* Returns 0 if they are semantically equal, 1 if the other object
* is less than the current one, or -1 if its more than the current one.
*
* This method should not check for identity using ===, only for semantical equality for example
* when two different DateTime instances point to the exact same Date + TZ.
*
* @param mixed $other
*
* @return int
*/
public function compareTo($other);
}

View File

@ -0,0 +1,246 @@
<?php
namespace Doctrine\Common\Proxy;
use Doctrine\Common\Proxy\Exception\InvalidArgumentException;
use Doctrine\Common\Proxy\Exception\OutOfBoundsException;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\ClassMetadataFactory;
use function class_exists;
use function file_exists;
use function filemtime;
use function in_array;
/**
* Abstract factory for proxy objects.
*
* @deprecated The AbstractProxyFactory class is deprecated since doctrine/common 3.5.
*/
abstract class AbstractProxyFactory
{
/**
* Never autogenerate a proxy and rely that it was generated by some
* process before deployment.
*/
public const AUTOGENERATE_NEVER = 0;
/**
* Always generates a new proxy in every request.
*
* This is only sane during development.
*/
public const AUTOGENERATE_ALWAYS = 1;
/**
* Autogenerate the proxy class when the proxy file does not exist.
*
* This strategy causes a file_exists() call whenever any proxy is used the
* first time in a request.
*/
public const AUTOGENERATE_FILE_NOT_EXISTS = 2;
/**
* Generate the proxy classes using eval().
*
* This strategy is only sane for development, and even then it gives me
* the creeps a little.
*/
public const AUTOGENERATE_EVAL = 3;
/**
* Autogenerate the proxy class when the proxy file does not exist or
* when the proxied file changed.
*
* This strategy causes a file_exists() call whenever any proxy is used the
* first time in a request. When the proxied file is changed, the proxy will
* be updated.
*/
public const AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED = 4;
private const AUTOGENERATE_MODES = [
self::AUTOGENERATE_NEVER,
self::AUTOGENERATE_ALWAYS,
self::AUTOGENERATE_FILE_NOT_EXISTS,
self::AUTOGENERATE_EVAL,
self::AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED,
];
/** @var ClassMetadataFactory */
private $metadataFactory;
/** @var ProxyGenerator the proxy generator responsible for creating the proxy classes/files. */
private $proxyGenerator;
/** @var int Whether to automatically (re)generate proxy classes. */
private $autoGenerate;
/** @var ProxyDefinition[] */
private $definitions = [];
/**
* @param bool|int $autoGenerate
*
* @throws InvalidArgumentException When auto generate mode is not valid.
*/
public function __construct(ProxyGenerator $proxyGenerator, ClassMetadataFactory $metadataFactory, $autoGenerate)
{
$this->proxyGenerator = $proxyGenerator;
$this->metadataFactory = $metadataFactory;
$this->autoGenerate = (int) $autoGenerate;
if (! in_array($this->autoGenerate, self::AUTOGENERATE_MODES, true)) {
throw InvalidArgumentException::invalidAutoGenerateMode($autoGenerate);
}
}
/**
* Gets a reference proxy instance for the entity of the given type and identified by
* the given identifier.
*
* @param string $className
* @param array<mixed> $identifier
*
* @return Proxy
*
* @throws OutOfBoundsException
*/
public function getProxy($className, array $identifier)
{
$definition = $this->definitions[$className] ?? $this->getProxyDefinition($className);
$fqcn = $definition->proxyClassName;
$proxy = new $fqcn($definition->initializer, $definition->cloner);
foreach ($definition->identifierFields as $idField) {
if (! isset($identifier[$idField])) {
throw OutOfBoundsException::missingPrimaryKeyValue($className, $idField);
}
$definition->reflectionFields[$idField]->setValue($proxy, $identifier[$idField]);
}
return $proxy;
}
/**
* Generates proxy classes for all given classes.
*
* @param ClassMetadata[] $classes The classes (ClassMetadata instances)
* for which to generate proxies.
* @param string $proxyDir The target directory of the proxy classes. If not specified, the
* directory configured on the Configuration of the EntityManager used
* by this factory is used.
*
* @return int Number of generated proxies.
*/
public function generateProxyClasses(array $classes, $proxyDir = null)
{
$generated = 0;
foreach ($classes as $class) {
if ($this->skipClass($class)) {
continue;
}
$proxyFileName = $this->proxyGenerator->getProxyFileName($class->getName(), $proxyDir);
$this->proxyGenerator->generateProxyClass($class, $proxyFileName);
$generated += 1;
}
return $generated;
}
/**
* Reset initialization/cloning logic for an un-initialized proxy
*
* @return Proxy
*
* @throws InvalidArgumentException
*/
public function resetUninitializedProxy(Proxy $proxy)
{
if ($proxy->__isInitialized()) {
throw InvalidArgumentException::unitializedProxyExpected($proxy);
}
$className = ClassUtils::getClass($proxy);
$definition = $this->definitions[$className] ?? $this->getProxyDefinition($className);
$proxy->__setInitializer($definition->initializer);
$proxy->__setCloner($definition->cloner);
return $proxy;
}
/**
* Get a proxy definition for the given class name.
*
* @param string $className
* @psalm-param class-string $className
*
* @return ProxyDefinition
*/
private function getProxyDefinition($className)
{
$classMetadata = $this->metadataFactory->getMetadataFor($className);
$className = $classMetadata->getName(); // aliases and case sensitivity
$this->definitions[$className] = $this->createProxyDefinition($className);
$proxyClassName = $this->definitions[$className]->proxyClassName;
if (! class_exists($proxyClassName, false)) {
$fileName = $this->proxyGenerator->getProxyFileName($className);
switch ($this->autoGenerate) {
case self::AUTOGENERATE_NEVER:
require $fileName;
break;
case self::AUTOGENERATE_FILE_NOT_EXISTS:
if (! file_exists($fileName)) {
$this->proxyGenerator->generateProxyClass($classMetadata, $fileName);
}
require $fileName;
break;
case self::AUTOGENERATE_ALWAYS:
$this->proxyGenerator->generateProxyClass($classMetadata, $fileName);
require $fileName;
break;
case self::AUTOGENERATE_EVAL:
$this->proxyGenerator->generateProxyClass($classMetadata, false);
break;
case self::AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED:
if (! file_exists($fileName) || filemtime($fileName) < filemtime($classMetadata->getReflectionClass()->getFileName())) {
$this->proxyGenerator->generateProxyClass($classMetadata, $fileName);
}
require $fileName;
break;
}
}
return $this->definitions[$className];
}
/**
* Determine if this class should be skipped during proxy generation.
*
* @return bool
*/
abstract protected function skipClass(ClassMetadata $metadata);
/**
* @param string $className
* @psalm-param class-string $className
*
* @return ProxyDefinition
*/
abstract protected function createProxyDefinition($className);
}

View File

@ -0,0 +1,100 @@
<?php
namespace Doctrine\Common\Proxy;
use Closure;
use Doctrine\Common\Proxy\Exception\InvalidArgumentException;
use function call_user_func;
use function file_exists;
use function is_callable;
use function ltrim;
use function spl_autoload_register;
use function str_replace;
use function strlen;
use function strpos;
use function substr;
use const DIRECTORY_SEPARATOR;
/**
* Special Autoloader for Proxy classes, which are not PSR-0 compliant.
*
* @internal
* @deprecated The Autoloader class is deprecated since doctrine/common 3.5.
*/
class Autoloader
{
/**
* Resolves proxy class name to a filename based on the following pattern.
*
* 1. Remove Proxy namespace from class name.
* 2. Remove namespace separators from remaining class name.
* 3. Return PHP filename from proxy-dir with the result from 2.
*
* @param string $proxyDir
* @param string $proxyNamespace
* @param string $className
* @psalm-param class-string $className
*
* @return string
*
* @throws InvalidArgumentException
*/
public static function resolveFile($proxyDir, $proxyNamespace, $className)
{
if (strpos($className, $proxyNamespace) !== 0) {
throw InvalidArgumentException::notProxyClass($className, $proxyNamespace);
}
// remove proxy namespace from class name
$classNameRelativeToProxyNamespace = substr($className, strlen($proxyNamespace));
// remove namespace separators from remaining class name
$fileName = str_replace('\\', '', $classNameRelativeToProxyNamespace);
return $proxyDir . DIRECTORY_SEPARATOR . $fileName . '.php';
}
/**
* Registers and returns autoloader callback for the given proxy dir and namespace.
*
* @param string $proxyDir
* @param string $proxyNamespace
* @param callable|null $notFoundCallback Invoked when the proxy file is not found.
*
* @return Closure
*
* @throws InvalidArgumentException
*/
public static function register($proxyDir, $proxyNamespace, $notFoundCallback = null)
{
$proxyNamespace = ltrim($proxyNamespace, '\\');
if ($notFoundCallback !== null && ! is_callable($notFoundCallback)) {
throw InvalidArgumentException::invalidClassNotFoundCallback($notFoundCallback);
}
$autoloader = static function ($className) use ($proxyDir, $proxyNamespace, $notFoundCallback) {
if ($proxyNamespace === '') {
return;
}
if (strpos($className, $proxyNamespace) !== 0) {
return;
}
$file = Autoloader::resolveFile($proxyDir, $proxyNamespace, $className);
if ($notFoundCallback && ! file_exists($file)) {
call_user_func($notFoundCallback, $proxyDir, $proxyNamespace, $className);
}
require $file;
};
spl_autoload_register($autoloader);
return $autoloader;
}
}

View File

@ -0,0 +1,110 @@
<?php
namespace Doctrine\Common\Proxy\Exception;
use Doctrine\Persistence\Proxy;
use InvalidArgumentException as BaseInvalidArgumentException;
use function get_class;
use function gettype;
use function is_object;
use function sprintf;
/**
* Proxy Invalid Argument Exception.
*
* @deprecated The InvalidArgumentException class is deprecated since doctrine/common 3.5.
*/
class InvalidArgumentException extends BaseInvalidArgumentException implements ProxyException
{
/** @return self */
public static function proxyDirectoryRequired()
{
return new self('You must configure a proxy directory. See docs for details');
}
/**
* @param string $className
* @param string $proxyNamespace
* @psalm-param class-string $className
*
* @return self
*/
public static function notProxyClass($className, $proxyNamespace)
{
return new self(sprintf('The class "%s" is not part of the proxy namespace "%s"', $className, $proxyNamespace));
}
/**
* @param string $name
*
* @return self
*/
public static function invalidPlaceholder($name)
{
return new self(sprintf('Provided placeholder for "%s" must be either a string or a valid callable', $name));
}
/** @return self */
public static function proxyNamespaceRequired()
{
return new self('You must configure a proxy namespace');
}
/** @return self */
public static function unitializedProxyExpected(Proxy $proxy)
{
return new self(sprintf('Provided proxy of type "%s" must not be initialized.', get_class($proxy)));
}
/**
* @param mixed $callback
*
* @return self
*/
public static function invalidClassNotFoundCallback($callback)
{
$type = is_object($callback) ? get_class($callback) : gettype($callback);
return new self(sprintf('Invalid \$notFoundCallback given: must be a callable, "%s" given', $type));
}
/**
* @param string $className
* @psalm-param class-string $className
*
* @return self
*/
public static function classMustNotBeAbstract($className)
{
return new self(sprintf('Unable to create a proxy for an abstract class "%s".', $className));
}
/**
* @param string $className
* @psalm-param class-string $className
*
* @return self
*/
public static function classMustNotBeFinal($className)
{
return new self(sprintf('Unable to create a proxy for a final class "%s".', $className));
}
/**
* @param string $className
* @psalm-param class-string $className
*
* @return self
*/
public static function classMustNotBeReadOnly($className)
{
return new self(sprintf('Unable to create a proxy for a readonly class "%s".', $className));
}
/** @param mixed $value */
public static function invalidAutoGenerateMode($value): self
{
return new self(sprintf('Invalid auto generate mode "%s" given.', $value));
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Doctrine\Common\Proxy\Exception;
use OutOfBoundsException as BaseOutOfBoundsException;
use function sprintf;
/**
* Proxy Invalid Argument Exception.
*
* @deprecated The OutOfBoundsException class is deprecated since doctrine/common 3.5.
*/
class OutOfBoundsException extends BaseOutOfBoundsException implements ProxyException
{
/**
* @param string $className
* @param string $idField
* @psalm-param class-string $className
*
* @return self
*/
public static function missingPrimaryKeyValue($className, $idField)
{
return new self(sprintf('Missing value for primary key %s on %s', $idField, $className));
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Doctrine\Common\Proxy\Exception;
/**
* Base exception interface for proxy exceptions.
*
* @deprecated The ProxyException interface is deprecated since doctrine/common 3.5.
*/
interface ProxyException
{
}

View File

@ -0,0 +1,72 @@
<?php
namespace Doctrine\Common\Proxy\Exception;
use Throwable;
use UnexpectedValueException as BaseUnexpectedValueException;
use function sprintf;
/**
* Proxy Unexpected Value Exception.
*
* @deprecated The UnexpectedValueException class is deprecated since doctrine/common 3.5.
*/
class UnexpectedValueException extends BaseUnexpectedValueException implements ProxyException
{
/**
* @param string $proxyDirectory
*
* @return self
*/
public static function proxyDirectoryNotWritable($proxyDirectory)
{
return new self(sprintf('Your proxy directory "%s" must be writable', $proxyDirectory));
}
/**
* @param string $className
* @param string $methodName
* @param string $parameterName
* @psalm-param class-string $className
*
* @return self
*/
public static function invalidParameterTypeHint(
$className,
$methodName,
$parameterName,
?Throwable $previous = null
) {
return new self(
sprintf(
'The type hint of parameter "%s" in method "%s" in class "%s" is invalid.',
$parameterName,
$methodName,
$className
),
0,
$previous
);
}
/**
* @param string $className
* @param string $methodName
* @psalm-param class-string $className
*
* @return self
*/
public static function invalidReturnTypeHint($className, $methodName, ?Throwable $previous = null)
{
return new self(
sprintf(
'The return type of method "%s" in class "%s" is invalid.',
$methodName,
$className
),
0,
$previous
);
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace Doctrine\Common\Proxy;
use Closure;
use Doctrine\Persistence\Proxy as BaseProxy;
/**
* Interface for proxy classes.
*
* @deprecated The Proxy interface is deprecated since doctrine/common 3.5.
*
* @template T of object
* @template-extends BaseProxy<T>
*/
interface Proxy extends BaseProxy
{
/**
* Marks the proxy as initialized or not.
*
* @param bool $initialized
*
* @return void
*/
public function __setInitialized($initialized);
/**
* Sets the initializer callback to be used when initializing the proxy. That
* initializer should accept 3 parameters: $proxy, $method and $params. Those
* are respectively the proxy object that is being initialized, the method name
* that triggered initialization and the parameters passed to that method.
*
* @return void
*/
public function __setInitializer(?Closure $initializer = null);
/**
* Retrieves the initializer callback used to initialize the proxy.
*
* @see __setInitializer
*
* @return Closure|null
*/
public function __getInitializer();
/**
* Sets the callback to be used when cloning the proxy. That initializer should accept
* a single parameter, which is the cloned proxy instance itself.
*
* @return void
*/
public function __setCloner(?Closure $cloner = null);
/**
* Retrieves the callback to be used when cloning the proxy.
*
* @see __setCloner
*
* @return Closure|null
*/
public function __getCloner();
/**
* Retrieves the list of lazy loaded properties for a given proxy
*
* @return array<string, mixed> Keys are the property names, and values are the default values
* for those properties.
*/
public function __getLazyProperties();
}

View File

@ -0,0 +1,44 @@
<?php
namespace Doctrine\Common\Proxy;
use ReflectionProperty;
/**
* Definition structure how to create a proxy.
*
* @deprecated The ProxyDefinition class is deprecated since doctrine/common 3.5.
*/
class ProxyDefinition
{
/** @var string */
public $proxyClassName;
/** @var array<string> */
public $identifierFields;
/** @var ReflectionProperty[] */
public $reflectionFields;
/** @var callable */
public $initializer;
/** @var callable */
public $cloner;
/**
* @param string $proxyClassName
* @param array<string> $identifierFields
* @param array<string, ReflectionProperty> $reflectionFields
* @param callable $initializer
* @param callable $cloner
*/
public function __construct($proxyClassName, array $identifierFields, array $reflectionFields, $initializer, $cloner)
{
$this->proxyClassName = $proxyClassName;
$this->identifierFields = $identifierFields;
$this->reflectionFields = $reflectionFields;
$this->initializer = $initializer;
$this->cloner = $cloner;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,113 @@
<?php
namespace Doctrine\Common\Util;
use Doctrine\Persistence\Proxy;
use ReflectionClass;
use function get_class;
use function get_parent_class;
use function ltrim;
use function rtrim;
use function strrpos;
use function substr;
/**
* Class and reflection related functionality for objects that
* might or not be proxy objects at the moment.
*/
class ClassUtils
{
/**
* Gets the real class name of a class name that could be a proxy.
*
* @param string $className
* @psalm-param class-string<Proxy<T>>|class-string<T> $className
*
* @return string
* @psalm-return class-string<T>
*
* @template T of object
*/
public static function getRealClass($className)
{
$pos = strrpos($className, '\\' . Proxy::MARKER . '\\');
if ($pos === false) {
/** @psalm-var class-string<T> */
return $className;
}
return substr($className, $pos + Proxy::MARKER_LENGTH + 2);
}
/**
* Gets the real class name of an object (even if its a proxy).
*
* @param object $object
* @psalm-param Proxy<T>|T $object
*
* @return string
* @psalm-return class-string<T>
*
* @template T of object
*/
public static function getClass($object)
{
return self::getRealClass(get_class($object));
}
/**
* Gets the real parent class name of a class or object.
*
* @param string $className
* @psalm-param class-string $className
*
* @return string
* @psalm-return class-string
*/
public static function getParentClass($className)
{
return get_parent_class(self::getRealClass($className));
}
/**
* Creates a new reflection class.
*
* @param string $className
* @psalm-param class-string $className
*
* @return ReflectionClass
*/
public static function newReflectionClass($className)
{
return new ReflectionClass(self::getRealClass($className));
}
/**
* Creates a new reflection object.
*
* @param object $object
*
* @return ReflectionClass
*/
public static function newReflectionObject($object)
{
return self::newReflectionClass(self::getClass($object));
}
/**
* Given a class name and a proxy namespace returns the proxy name.
*
* @param string $className
* @param string $proxyNamespace
* @psalm-param class-string $className
*
* @return string
* @psalm-return class-string
*/
public static function generateProxyClassName($className, $proxyNamespace)
{
return rtrim($proxyNamespace, '\\') . '\\' . Proxy::MARKER . '\\' . ltrim($className, '\\');
}
}

View File

@ -0,0 +1,185 @@
<?php
namespace Doctrine\Common\Util;
use ArrayIterator;
use ArrayObject;
use DateTimeInterface;
use Doctrine\Common\Collections\Collection;
use Doctrine\Persistence\Proxy;
use stdClass;
use function array_keys;
use function count;
use function end;
use function explode;
use function extension_loaded;
use function get_class;
use function html_entity_decode;
use function ini_get;
use function ini_set;
use function is_array;
use function is_object;
use function method_exists;
use function ob_end_clean;
use function ob_get_contents;
use function ob_start;
use function spl_object_hash;
use function strip_tags;
use function var_dump;
/**
* Static class containing most used debug methods.
*
* @deprecated The Debug class is deprecated, please use symfony/var-dumper instead.
*
* @link www.doctrine-project.org
*/
final class Debug
{
/**
* Private constructor (prevents instantiation).
*/
private function __construct()
{
}
/**
* Prints a dump of the public, protected and private properties of $var.
*
* @link https://xdebug.org/
*
* @param mixed $var The variable to dump.
* @param int $maxDepth The maximum nesting level for object properties.
* @param bool $stripTags Whether output should strip HTML tags.
* @param bool $echo Send the dumped value to the output buffer
*
* @return string
*/
public static function dump($var, $maxDepth = 2, $stripTags = true, $echo = true)
{
$html = ini_get('html_errors');
if ($html !== true) {
ini_set('html_errors', 'on');
}
if (extension_loaded('xdebug')) {
ini_set('xdebug.var_display_max_depth', $maxDepth);
}
$var = self::export($var, $maxDepth);
ob_start();
var_dump($var);
$dump = ob_get_contents();
ob_end_clean();
$dumpText = ($stripTags ? strip_tags(html_entity_decode($dump)) : $dump);
ini_set('html_errors', $html);
if ($echo) {
echo $dumpText;
}
return $dumpText;
}
/**
* @param mixed $var
* @param int $maxDepth
*
* @return mixed
*/
public static function export($var, $maxDepth)
{
$return = null;
$isObj = is_object($var);
if ($var instanceof Collection) {
$var = $var->toArray();
}
if (! $maxDepth) {
return is_object($var) ? get_class($var)
: (is_array($var) ? 'Array(' . count($var) . ')' : $var);
}
if (is_array($var)) {
$return = [];
foreach ($var as $k => $v) {
$return[$k] = self::export($v, $maxDepth - 1);
}
return $return;
}
if (! $isObj) {
return $var;
}
$return = new stdClass();
if ($var instanceof DateTimeInterface) {
$return->__CLASS__ = get_class($var);
$return->date = $var->format('c');
$return->timezone = $var->getTimezone()->getName();
return $return;
}
$return->__CLASS__ = ClassUtils::getClass($var);
if ($var instanceof Proxy) {
$return->__IS_PROXY__ = true;
$return->__PROXY_INITIALIZED__ = $var->__isInitialized();
}
if ($var instanceof ArrayObject || $var instanceof ArrayIterator) {
$return->__STORAGE__ = self::export($var->getArrayCopy(), $maxDepth - 1);
}
return self::fillReturnWithClassAttributes($var, $return, $maxDepth);
}
/**
* Fill the $return variable with class attributes
* Based on obj2array function from {@see https://secure.php.net/manual/en/function.get-object-vars.php#47075}
*
* @param object $var
* @param int $maxDepth
*
* @return mixed
*/
private static function fillReturnWithClassAttributes($var, stdClass $return, $maxDepth)
{
$clone = (array) $var;
foreach (array_keys($clone) as $key) {
$aux = explode("\0", $key);
$name = end($aux);
if ($aux[0] === '') {
$name .= ':' . ($aux[1] === '*' ? 'protected' : $aux[1] . ':private');
}
$return->$name = self::export($clone[$key], $maxDepth - 1);
}
return $return;
}
/**
* Returns a string representation of an object.
*
* @param object $obj
*
* @return string
*/
public static function toString($obj)
{
return method_exists($obj, '__toString') ? (string) $obj : get_class($obj) . '@' . spl_object_hash($obj);
}
}

19
vendor/doctrine/deprecations/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2020-2021 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

218
vendor/doctrine/deprecations/README.md vendored Normal file
View File

@ -0,0 +1,218 @@
# Doctrine Deprecations
A small (side-effect free by default) layer on top of
`trigger_error(E_USER_DEPRECATED)` or PSR-3 logging.
- no side-effects by default, making it a perfect fit for libraries that don't know how the error handler works they operate under
- options to avoid having to rely on error handlers global state by using PSR-3 logging
- deduplicate deprecation messages to avoid excessive triggering and reduce overhead
We recommend to collect Deprecations using a PSR logger instead of relying on
the global error handler.
## Usage from consumer perspective:
Enable Doctrine deprecations to be sent to a PSR3 logger:
```php
\Doctrine\Deprecations\Deprecation::enableWithPsrLogger($logger);
```
Enable Doctrine deprecations to be sent as `@trigger_error($message, E_USER_DEPRECATED)`
messages by setting the `DOCTRINE_DEPRECATIONS` environment variable to `trigger`.
Alternatively, call:
```php
\Doctrine\Deprecations\Deprecation::enableWithTriggerError();
```
If you only want to enable deprecation tracking, without logging or calling `trigger_error`
then set the `DOCTRINE_DEPRECATIONS` environment variable to `track`.
Alternatively, call:
```php
\Doctrine\Deprecations\Deprecation::enableTrackingDeprecations();
```
Tracking is enabled with all three modes and provides access to all triggered
deprecations and their individual count:
```php
$deprecations = \Doctrine\Deprecations\Deprecation::getTriggeredDeprecations();
foreach ($deprecations as $identifier => $count) {
echo $identifier . " was triggered " . $count . " times\n";
}
```
### Suppressing Specific Deprecations
Disable triggering about specific deprecations:
```php
\Doctrine\Deprecations\Deprecation::ignoreDeprecations("https://link/to/deprecations-description-identifier");
```
Disable all deprecations from a package
```php
\Doctrine\Deprecations\Deprecation::ignorePackage("doctrine/orm");
```
### Other Operations
When used within PHPUnit or other tools that could collect multiple instances of the same deprecations
the deduplication can be disabled:
```php
\Doctrine\Deprecations\Deprecation::withoutDeduplication();
```
Disable deprecation tracking again:
```php
\Doctrine\Deprecations\Deprecation::disable();
```
## Usage from a library/producer perspective:
When you want to unconditionally trigger a deprecation even when called
from the library itself then the `trigger` method is the way to go:
```php
\Doctrine\Deprecations\Deprecation::trigger(
"doctrine/orm",
"https://link/to/deprecations-description",
"message"
);
```
If variable arguments are provided at the end, they are used with `sprintf` on
the message.
```php
\Doctrine\Deprecations\Deprecation::trigger(
"doctrine/orm",
"https://github.com/doctrine/orm/issue/1234",
"message %s %d",
"foo",
1234
);
```
When you want to trigger a deprecation only when it is called by a function
outside of the current package, but not trigger when the package itself is the cause,
then use:
```php
\Doctrine\Deprecations\Deprecation::triggerIfCalledFromOutside(
"doctrine/orm",
"https://link/to/deprecations-description",
"message"
);
```
Based on the issue link each deprecation message is only triggered once per
request.
A limited stacktrace is included in the deprecation message to find the
offending location.
Note: A producer/library should never call `Deprecation::enableWith` methods
and leave the decision how to handle deprecations to application and
frameworks.
## Usage in PHPUnit tests
There is a `VerifyDeprecations` trait that you can use to make assertions on
the occurrence of deprecations within a test.
```php
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
class MyTest extends TestCase
{
use VerifyDeprecations;
public function testSomethingDeprecation()
{
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234');
triggerTheCodeWithDeprecation();
}
public function testSomethingDeprecationFixed()
{
$this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234');
triggerTheCodeWithoutDeprecation();
}
}
```
## Displaying deprecations after running a PHPUnit test suite
It is possible to integrate this library with PHPUnit to display all
deprecations triggered during the test suite execution.
```xml
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
bootstrap="vendor/autoload.php"
displayDetailsOnTestsThatTriggerDeprecations="true"
failOnDeprecation="true"
>
<!-- one attribute to display the deprecations, the other to fail the test suite -->
<php>
<!-- ensures native PHP deprecations are used -->
<server name="DOCTRINE_DEPRECATIONS" value="trigger"/>
</php>
<!-- ensures the @ operator in @trigger_error is ignored -->
<source ignoreSuppressionOfDeprecations="true">
<include>
<directory>src</directory>
</include>
</source>
</phpunit>
```
Note that you can still trigger Deprecations in your code, provided you use the
`#[WithoutErrorHandler]` attribute to disable PHPUnit's error handler for tests
that call it. Be wary that this will disable all error handling, meaning it
will mask any warnings or errors that would otherwise be caught by PHPUnit.
At the moment, it is not possible to disable deduplication with an environment
variable, but you can use a bootstrap file to achieve that:
```php
// tests/bootstrap.php
<?php
declare(strict_types=1);
require dirname(__DIR__) . '/vendor/autoload.php';
use Doctrine\Deprecations\Deprecation;
Deprecation::withoutDeduplication();
```
Then, reference that file in your PHPUnit configuration:
```xml
<phpunit
bootstrap="tests/bootstrap.php"
>
</phpunit>
```
## What is a deprecation identifier?
An identifier for deprecations is just a link to any resource, most often a
Github Issue or Pull Request explaining the deprecation and potentially its
alternative.

View File

@ -0,0 +1,39 @@
{
"name": "doctrine/deprecations",
"description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
"license": "MIT",
"type": "library",
"homepage": "https://www.doctrine-project.org/",
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^9 || ^12 || ^13",
"phpstan/phpstan": "1.4.10 || 2.1.11",
"phpstan/phpstan-phpunit": "^1.0 || ^2",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12",
"psr/log": "^1 || ^2 || ^3"
},
"conflict": {
"phpunit/phpunit": "<=7.5 || >=13"
},
"suggest": {
"psr/log": "Allows logging deprecations via PSR-3 logger implementation"
},
"autoload": {
"psr-4": {
"Doctrine\\Deprecations\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"DeprecationTests\\": "test_fixtures/src",
"Doctrine\\Foo\\": "test_fixtures/vendor/doctrine/foo"
}
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}

View File

@ -0,0 +1,309 @@
<?php
declare(strict_types=1);
namespace Doctrine\Deprecations;
use Psr\Log\LoggerInterface;
use function array_key_exists;
use function array_reduce;
use function assert;
use function debug_backtrace;
use function sprintf;
use function str_replace;
use function strpos;
use function strrpos;
use function substr;
use function trigger_error;
use const DEBUG_BACKTRACE_IGNORE_ARGS;
use const DIRECTORY_SEPARATOR;
use const E_USER_DEPRECATED;
/**
* Manages Deprecation logging in different ways.
*
* By default triggered exceptions are not logged.
*
* To enable different deprecation logging mechanisms you can call the
* following methods:
*
* - Minimal collection of deprecations via getTriggeredDeprecations()
* \Doctrine\Deprecations\Deprecation::enableTrackingDeprecations();
*
* - Uses @trigger_error with E_USER_DEPRECATED
* \Doctrine\Deprecations\Deprecation::enableWithTriggerError();
*
* - Sends deprecation messages via a PSR-3 logger
* \Doctrine\Deprecations\Deprecation::enableWithPsrLogger($logger);
*
* Packages that trigger deprecations should use the `trigger()` or
* `triggerIfCalledFromOutside()` methods.
*/
class Deprecation
{
private const TYPE_NONE = 0;
private const TYPE_TRACK_DEPRECATIONS = 1;
private const TYPE_TRIGGER_ERROR = 2;
private const TYPE_PSR_LOGGER = 4;
/** @var int-mask-of<self::TYPE_*>|null */
private static $type;
/** @var LoggerInterface|null */
private static $logger;
/** @var array<string,bool> */
private static $ignoredPackages = [];
/** @var array<string,int> */
private static $triggeredDeprecations = [];
/** @var array<string,bool> */
private static $ignoredLinks = [];
/** @var bool */
private static $deduplication = true;
/**
* Trigger a deprecation for the given package and identfier.
*
* The link should point to a Github issue or Wiki entry detailing the
* deprecation. It is additionally used to de-duplicate the trigger of the
* same deprecation during a request.
*
* @param float|int|string $args
*/
public static function trigger(string $package, string $link, string $message, ...$args): void
{
$type = self::$type ?? self::getTypeFromEnv();
if ($type === self::TYPE_NONE) {
return;
}
if (isset(self::$ignoredLinks[$link])) {
return;
}
if (array_key_exists($link, self::$triggeredDeprecations)) {
self::$triggeredDeprecations[$link]++;
} else {
self::$triggeredDeprecations[$link] = 1;
}
if (self::$deduplication === true && self::$triggeredDeprecations[$link] > 1) {
return;
}
if (isset(self::$ignoredPackages[$package])) {
return;
}
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$message = sprintf($message, ...$args);
self::delegateTriggerToBackend($message, $backtrace, $link, $package);
}
/**
* Trigger a deprecation for the given package and identifier when called from outside.
*
* "Outside" means we assume that $package is currently installed as a
* dependency and the caller is not a file in that package. When $package
* is installed as a root package then deprecations triggered from the
* tests folder are also considered "outside".
*
* This deprecation method assumes that you are using Composer to install
* the dependency and are using the default /vendor/ folder and not a
* Composer plugin to change the install location. The assumption is also
* that $package is the exact composer packge name.
*
* Compared to {@link trigger()} this method causes some overhead when
* deprecation tracking is enabled even during deduplication, because it
* needs to call {@link debug_backtrace()}
*
* @param float|int|string $args
*/
public static function triggerIfCalledFromOutside(string $package, string $link, string $message, ...$args): void
{
$type = self::$type ?? self::getTypeFromEnv();
if ($type === self::TYPE_NONE) {
return;
}
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
// first check that the caller is not from a tests folder, in which case we always let deprecations pass
if (isset($backtrace[1]['file'], $backtrace[0]['file']) && strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === false) {
$path = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $package) . DIRECTORY_SEPARATOR;
if (strpos($backtrace[0]['file'], $path) === false) {
return;
}
if (strpos($backtrace[1]['file'], $path) !== false) {
return;
}
}
if (isset(self::$ignoredLinks[$link])) {
return;
}
if (array_key_exists($link, self::$triggeredDeprecations)) {
self::$triggeredDeprecations[$link]++;
} else {
self::$triggeredDeprecations[$link] = 1;
}
if (self::$deduplication === true && self::$triggeredDeprecations[$link] > 1) {
return;
}
if (isset(self::$ignoredPackages[$package])) {
return;
}
$message = sprintf($message, ...$args);
self::delegateTriggerToBackend($message, $backtrace, $link, $package);
}
/** @param list<array{function: string, line?: int, file?: string, class?: class-string, type?: string, args?: mixed[], object?: object}> $backtrace */
private static function delegateTriggerToBackend(string $message, array $backtrace, string $link, string $package): void
{
$type = self::$type ?? self::getTypeFromEnv();
if (($type & self::TYPE_PSR_LOGGER) > 0) {
$context = [
'file' => $backtrace[0]['file'] ?? null,
'line' => $backtrace[0]['line'] ?? null,
'package' => $package,
'link' => $link,
];
assert(self::$logger !== null);
self::$logger->notice($message, $context);
}
if (! (($type & self::TYPE_TRIGGER_ERROR) > 0)) {
return;
}
$message .= sprintf(
' (%s:%d called by %s:%d, %s, package %s)',
self::basename($backtrace[0]['file'] ?? 'native code'),
$backtrace[0]['line'] ?? 0,
self::basename($backtrace[1]['file'] ?? 'native code'),
$backtrace[1]['line'] ?? 0,
$link,
$package
);
@trigger_error($message, E_USER_DEPRECATED);
}
/**
* A non-local-aware version of PHPs basename function.
*/
private static function basename(string $filename): string
{
$pos = strrpos($filename, DIRECTORY_SEPARATOR);
if ($pos === false) {
return $filename;
}
return substr($filename, $pos + 1);
}
public static function enableTrackingDeprecations(): void
{
self::$type = self::$type ?? self::getTypeFromEnv();
self::$type |= self::TYPE_TRACK_DEPRECATIONS;
}
public static function enableWithTriggerError(): void
{
self::$type = self::$type ?? self::getTypeFromEnv();
self::$type |= self::TYPE_TRIGGER_ERROR;
}
public static function enableWithPsrLogger(LoggerInterface $logger): void
{
self::$type = self::$type ?? self::getTypeFromEnv();
self::$type |= self::TYPE_PSR_LOGGER;
self::$logger = $logger;
}
public static function withoutDeduplication(): void
{
self::$deduplication = false;
}
public static function disable(): void
{
self::$type = self::TYPE_NONE;
self::$logger = null;
self::$deduplication = true;
self::$ignoredLinks = [];
foreach (self::$triggeredDeprecations as $link => $count) {
self::$triggeredDeprecations[$link] = 0;
}
}
public static function ignorePackage(string $packageName): void
{
self::$ignoredPackages[$packageName] = true;
}
public static function ignoreDeprecations(string ...$links): void
{
foreach ($links as $link) {
self::$ignoredLinks[$link] = true;
}
}
public static function getUniqueTriggeredDeprecationsCount(): int
{
return array_reduce(self::$triggeredDeprecations, static function (int $carry, int $count) {
return $carry + $count;
}, 0);
}
/**
* Returns each triggered deprecation link identifier and the amount of occurrences.
*
* @return array<string,int>
*/
public static function getTriggeredDeprecations(): array
{
return self::$triggeredDeprecations;
}
/** @return int-mask-of<self::TYPE_*> */
private static function getTypeFromEnv(): int
{
switch ($_SERVER['DOCTRINE_DEPRECATIONS'] ?? $_ENV['DOCTRINE_DEPRECATIONS'] ?? null) {
case 'trigger':
self::$type = self::TYPE_TRIGGER_ERROR;
break;
case 'track':
self::$type = self::TYPE_TRACK_DEPRECATIONS;
break;
default:
self::$type = self::TYPE_NONE;
break;
}
return self::$type;
}
}

View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Doctrine\Deprecations\PHPUnit;
use Doctrine\Deprecations\Deprecation;
use PHPUnit\Framework\Attributes\After;
use PHPUnit\Framework\Attributes\Before;
use function sprintf;
trait VerifyDeprecations
{
/** @var array<string,int> */
private $doctrineDeprecationsExpectations = [];
/** @var array<string,int> */
private $doctrineNoDeprecationsExpectations = [];
public function expectDeprecationWithIdentifier(string $identifier): void
{
$this->doctrineDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0;
}
public function expectNoDeprecationWithIdentifier(string $identifier): void
{
$this->doctrineNoDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0;
}
/** @before */
#[Before]
public function enableDeprecationTracking(): void
{
Deprecation::enableTrackingDeprecations();
}
/** @after */
#[After]
public function verifyDeprecationsAreTriggered(): void
{
foreach ($this->doctrineDeprecationsExpectations as $identifier => $expectation) {
$actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0;
$this->assertTrue(
$actualCount > $expectation,
sprintf(
"Expected deprecation with identifier '%s' was not triggered by code executed in test.",
$identifier
)
);
}
foreach ($this->doctrineNoDeprecationsExpectations as $identifier => $expectation) {
$actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0;
$this->assertTrue(
$actualCount === $expectation,
sprintf(
"Expected deprecation with identifier '%s' was triggered by code executed in test, but expected not to.",
$identifier
)
);
}
}
}

19
vendor/doctrine/event-manager/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2006-2015 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

13
vendor/doctrine/event-manager/README.md vendored Normal file
View File

@ -0,0 +1,13 @@
# Doctrine Event Manager
[![Build Status](https://github.com/doctrine/event-manager/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/event-manager/actions)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/doctrine/event-manager/badges/quality-score.png?b=1.2.x)](https://scrutinizer-ci.com/g/doctrine/event-manager/?branch=1.2.x)
[![Code Coverage](https://scrutinizer-ci.com/g/doctrine/event-manager/badges/coverage.png?b=1.2.x)](https://scrutinizer-ci.com/g/doctrine/event-manager/?branch=1.2.x)
The Doctrine Event Manager is a library that provides a simple event system.
## More resources:
* [Website](https://www.doctrine-project.org/)
* [Documentation](https://www.doctrine-project.org/projects/doctrine-event-manager/en/latest/)
* [Downloads](https://github.com/doctrine/event-manager/releases)

View File

@ -0,0 +1,15 @@
# Upgrade to 2.0
## Made the `$event` parameter of `EventManager::getListeners()` mandatory
When calling `EventManager::getListeners()` you need to specify the event that
you want to fetch the listeners for. Call `getAllListeners()` instead if you
want to access the listeners of all events.
# Upgrade to 1.2
## Deprecated calling `EventManager::getListeners()` without an event name
When calling `EventManager::getListeners()` without an event name, all
listeners were returned, keyed by event name. A new method `getAllListeners()`
has been added to provide this functionality. It should be used instead.

View File

@ -0,0 +1,68 @@
{
"name": "doctrine/event-manager",
"description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.",
"license": "MIT",
"type": "library",
"keywords": [
"events",
"event",
"event dispatcher",
"event manager",
"event system"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
},
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com"
}
],
"homepage": "https://www.doctrine-project.org/projects/event-manager.html",
"require": {
"php": "^8.1"
},
"require-dev": {
"doctrine/coding-standard": "^12",
"phpstan/phpstan": "^1.8.8",
"phpunit/phpunit": "^10.5",
"vimeo/psalm": "^5.24"
},
"conflict": {
"doctrine/common": "<2.9"
},
"autoload": {
"psr-4": {
"Doctrine\\Common\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Tests\\Common\\": "tests"
}
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
},
"sort-packages": true
}
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="5.24.0@462c80e31c34e58cc4f750c656be3927e80e550e">
<file src="src/EventManager.php">
<RiskyTruthyFalsyComparison>
<code><![CDATA[empty($this->listeners[$event])]]></code>
</RiskyTruthyFalsyComparison>
</file>
</files>

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Doctrine\Common;
/**
* EventArgs is the base class for classes containing event data.
*
* This class contains no event data. It is used by events that do not pass state
* information to an event handler when an event is raised. The single empty EventArgs
* instance can be obtained through {@link getEmptyInstance}.
*/
class EventArgs
{
/**
* Single instance of EventArgs.
*/
private static EventArgs|null $emptyEventArgsInstance = null;
/**
* Gets the single, empty and immutable EventArgs instance.
*
* This instance will be used when events are dispatched without any parameter,
* like this: EventManager::dispatchEvent('eventname');
*
* The benefit from this is that only one empty instance is instantiated and shared
* (otherwise there would be instances for every dispatched in the abovementioned form).
*
* @link https://msdn.microsoft.com/en-us/library/system.eventargs.aspx
* @see EventManager::dispatchEvent
*/
public static function getEmptyInstance(): EventArgs
{
return self::$emptyEventArgsInstance ??= new EventArgs();
}
}

View File

@ -0,0 +1,129 @@
<?php
declare(strict_types=1);
namespace Doctrine\Common;
use function spl_object_hash;
/**
* The EventManager is the central point of Doctrine's event listener system.
* Listeners are registered on the manager and events are dispatched through the
* manager.
*/
class EventManager
{
/**
* Map of registered listeners.
* <event> => <listeners>
*
* @var array<string, object[]>
*/
private array $listeners = [];
/**
* Dispatches an event to all registered listeners.
*
* @param string $eventName The name of the event to dispatch. The name of the event is
* the name of the method that is invoked on listeners.
* @param EventArgs|null $eventArgs The event arguments to pass to the event handlers/listeners.
* If not supplied, the single empty EventArgs instance is used.
*/
public function dispatchEvent(string $eventName, EventArgs|null $eventArgs = null): void
{
if (! isset($this->listeners[$eventName])) {
return;
}
$eventArgs ??= EventArgs::getEmptyInstance();
foreach ($this->listeners[$eventName] as $listener) {
$listener->$eventName($eventArgs);
}
}
/**
* Gets the listeners of a specific event.
*
* @param string $event The name of the event.
*
* @return object[]
*/
public function getListeners(string $event): array
{
return $this->listeners[$event] ?? [];
}
/**
* Gets all listeners keyed by event name.
*
* @return array<string, object[]> The event listeners for the specified event, or all event listeners.
*/
public function getAllListeners(): array
{
return $this->listeners;
}
/**
* Checks whether an event has any registered listeners.
*/
public function hasListeners(string $event): bool
{
return ! empty($this->listeners[$event]);
}
/**
* Adds an event listener that listens on the specified events.
*
* @param string|string[] $events The event(s) to listen on.
* @param object $listener The listener object.
*/
public function addEventListener(string|array $events, object $listener): void
{
// Picks the hash code related to that listener
$hash = spl_object_hash($listener);
foreach ((array) $events as $event) {
// Overrides listener if a previous one was associated already
// Prevents duplicate listeners on same event (same instance only)
$this->listeners[$event][$hash] = $listener;
}
}
/**
* Removes an event listener from the specified events.
*
* @param string|string[] $events
*/
public function removeEventListener(string|array $events, object $listener): void
{
// Picks the hash code related to that listener
$hash = spl_object_hash($listener);
foreach ((array) $events as $event) {
unset($this->listeners[$event][$hash]);
}
}
/**
* Adds an EventSubscriber.
*
* The subscriber is asked for all the events it is interested in and added
* as a listener for these events.
*/
public function addEventSubscriber(EventSubscriber $subscriber): void
{
$this->addEventListener($subscriber->getSubscribedEvents(), $subscriber);
}
/**
* Removes an EventSubscriber.
*
* The subscriber is asked for all the events it is interested in and removed
* as a listener for these events.
*/
public function removeEventSubscriber(EventSubscriber $subscriber): void
{
$this->removeEventListener($subscriber->getSubscribedEvents(), $subscriber);
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Common;
/**
* An EventSubscriber knows what events it is interested in.
* If an EventSubscriber is added to an EventManager, the manager invokes
* {@link getSubscribedEvents} and registers the subscriber as a listener for all
* returned events.
*/
interface EventSubscriber
{
/**
* Returns an array of events this subscriber wants to listen to.
*
* @return string[]
*/
public function getSubscribedEvents();
}

19
vendor/doctrine/lexer/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2006-2018 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

9
vendor/doctrine/lexer/README.md vendored Normal file
View File

@ -0,0 +1,9 @@
# Doctrine Lexer
[![Build Status](https://github.com/doctrine/lexer/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/lexer/actions)
Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.
This lexer is used in Doctrine Annotations and in Doctrine ORM (DQL).
https://www.doctrine-project.org/projects/lexer.html

14
vendor/doctrine/lexer/UPGRADE.md vendored Normal file
View File

@ -0,0 +1,14 @@
Note about upgrading: Doctrine uses static and runtime mechanisms to raise
awareness about deprecated code.
- Use of `@deprecated` docblock that is detected by IDEs (like PHPStorm) or
Static Analysis tools (like Psalm, phpstan)
- Use of our low-overhead runtime deprecation API, details:
https://github.com/doctrine/deprecations/
# Upgrade to 2.0.0
`AbstractLexer::glimpse()` and `AbstractLexer::peek()` now return
instances of `Doctrine\Common\Lexer\Token`, which is an array-like class
Using it as an array is deprecated in favor of using properties of that class.
Using `count()` on it is deprecated with no replacement.

56
vendor/doctrine/lexer/composer.json vendored Normal file
View File

@ -0,0 +1,56 @@
{
"name": "doctrine/lexer",
"description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
"license": "MIT",
"type": "library",
"keywords": [
"php",
"parser",
"lexer",
"annotations",
"docblock"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
}
],
"homepage": "https://www.doctrine-project.org/projects/lexer.html",
"require": {
"php": "^7.1 || ^8.0",
"doctrine/deprecations": "^1.0"
},
"require-dev": {
"doctrine/coding-standard": "^9 || ^12",
"phpstan/phpstan": "^1.3",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6",
"psalm/plugin-phpunit": "^0.18.3",
"vimeo/psalm": "^4.11 || ^5.21"
},
"autoload": {
"psr-4": {
"Doctrine\\Common\\Lexer\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Tests\\Common\\Lexer\\": "tests"
}
},
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
"dealerdirect/phpcodesniffer-composer-installer": true
},
"sort-packages": true
}
}

View File

@ -0,0 +1,342 @@
<?php
declare(strict_types=1);
namespace Doctrine\Common\Lexer;
use ReflectionClass;
use UnitEnum;
use function get_class;
use function implode;
use function preg_split;
use function sprintf;
use function substr;
use const PREG_SPLIT_DELIM_CAPTURE;
use const PREG_SPLIT_NO_EMPTY;
use const PREG_SPLIT_OFFSET_CAPTURE;
/**
* Base class for writing simple lexers, i.e. for creating small DSLs.
*
* @template T of UnitEnum|string|int
* @template V of string|int
*/
abstract class AbstractLexer
{
/**
* Lexer original input string.
*
* @var string
*/
private $input;
/**
* Array of scanned tokens.
*
* @var list<Token<T, V>>
*/
private $tokens = [];
/**
* Current lexer position in input string.
*
* @var int
*/
private $position = 0;
/**
* Current peek of current lexer position.
*
* @var int
*/
private $peek = 0;
/**
* The next token in the input.
*
* @var Token<T, V>|null
*/
public $lookahead;
/**
* The last matched/seen token.
*
* @var Token<T, V>|null
*/
public $token;
/**
* Composed regex for input parsing.
*
* @var non-empty-string|null
*/
private $regex;
/**
* Sets the input data to be tokenized.
*
* The Lexer is immediately reset and the new input tokenized.
* Any unprocessed tokens from any previous input are lost.
*
* @param string $input The input to be tokenized.
*
* @return void
*/
public function setInput($input)
{
$this->input = $input;
$this->tokens = [];
$this->reset();
$this->scan($input);
}
/**
* Resets the lexer.
*
* @return void
*/
public function reset()
{
$this->lookahead = null;
$this->token = null;
$this->peek = 0;
$this->position = 0;
}
/**
* Resets the peek pointer to 0.
*
* @return void
*/
public function resetPeek()
{
$this->peek = 0;
}
/**
* Resets the lexer position on the input to the given position.
*
* @param int $position Position to place the lexical scanner.
*
* @return void
*/
public function resetPosition($position = 0)
{
$this->position = $position;
}
/**
* Retrieve the original lexer's input until a given position.
*
* @param int $position
*
* @return string
*/
public function getInputUntilPosition($position)
{
return substr($this->input, 0, $position);
}
/**
* Checks whether a given token matches the current lookahead.
*
* @param T $type
*
* @return bool
*
* @psalm-assert-if-true !=null $this->lookahead
*/
public function isNextToken($type)
{
return $this->lookahead !== null && $this->lookahead->isA($type);
}
/**
* Checks whether any of the given tokens matches the current lookahead.
*
* @param list<T> $types
*
* @return bool
*
* @psalm-assert-if-true !=null $this->lookahead
*/
public function isNextTokenAny(array $types)
{
return $this->lookahead !== null && $this->lookahead->isA(...$types);
}
/**
* Moves to the next token in the input string.
*
* @return bool
*
* @psalm-assert-if-true !null $this->lookahead
*/
public function moveNext()
{
$this->peek = 0;
$this->token = $this->lookahead;
$this->lookahead = isset($this->tokens[$this->position])
? $this->tokens[$this->position++] : null;
return $this->lookahead !== null;
}
/**
* Tells the lexer to skip input tokens until it sees a token with the given value.
*
* @param T $type The token type to skip until.
*
* @return void
*/
public function skipUntil($type)
{
while ($this->lookahead !== null && ! $this->lookahead->isA($type)) {
$this->moveNext();
}
}
/**
* Checks if given value is identical to the given token.
*
* @param string $value
* @param int|string $token
*
* @return bool
*/
public function isA($value, $token)
{
return $this->getType($value) === $token;
}
/**
* Moves the lookahead token forward.
*
* @return Token<T, V>|null The next token or NULL if there are no more tokens ahead.
*/
public function peek()
{
if (isset($this->tokens[$this->position + $this->peek])) {
return $this->tokens[$this->position + $this->peek++];
}
return null;
}
/**
* Peeks at the next token, returns it and immediately resets the peek.
*
* @return Token<T, V>|null The next token or NULL if there are no more tokens ahead.
*/
public function glimpse()
{
$peek = $this->peek();
$this->peek = 0;
return $peek;
}
/**
* Scans the input string for tokens.
*
* @param string $input A query string.
*
* @return void
*/
protected function scan($input)
{
if (! isset($this->regex)) {
$this->regex = sprintf(
'/(%s)|%s/%s',
implode(')|(', $this->getCatchablePatterns()),
implode('|', $this->getNonCatchablePatterns()),
$this->getModifiers()
);
}
$flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE;
$matches = preg_split($this->regex, $input, -1, $flags);
if ($matches === false) {
// Work around https://bugs.php.net/78122
$matches = [[$input, 0]];
}
foreach ($matches as $match) {
// Must remain before 'value' assignment since it can change content
$firstMatch = $match[0];
$type = $this->getType($firstMatch);
$this->tokens[] = new Token(
$firstMatch,
$type,
$match[1]
);
}
}
/**
* Gets the literal for a given token.
*
* @param T $token
*
* @return int|string
*/
public function getLiteral($token)
{
if ($token instanceof UnitEnum) {
return get_class($token) . '::' . $token->name;
}
$className = static::class;
$reflClass = new ReflectionClass($className);
$constants = $reflClass->getConstants();
foreach ($constants as $name => $value) {
if ($value === $token) {
return $className . '::' . $name;
}
}
return $token;
}
/**
* Regex modifiers
*
* @return string
*/
protected function getModifiers()
{
return 'iu';
}
/**
* Lexical catchable patterns.
*
* @return string[]
*/
abstract protected function getCatchablePatterns();
/**
* Lexical non-catchable patterns.
*
* @return string[]
*/
abstract protected function getNonCatchablePatterns();
/**
* Retrieve token type. Also processes the token value if necessary.
*
* @param string $value
*
* @return T|null
*
* @param-out V $value
*/
abstract protected function getType(&$value);
}

145
vendor/doctrine/lexer/src/Token.php vendored Normal file
View File

@ -0,0 +1,145 @@
<?php
declare(strict_types=1);
namespace Doctrine\Common\Lexer;
use ArrayAccess;
use Doctrine\Deprecations\Deprecation;
use ReturnTypeWillChange;
use UnitEnum;
use function in_array;
/**
* @template T of UnitEnum|string|int
* @template V of string|int
* @implements ArrayAccess<string,mixed>
*/
final class Token implements ArrayAccess
{
/**
* The string value of the token in the input string
*
* @readonly
* @var V
*/
public $value;
/**
* The type of the token (identifier, numeric, string, input parameter, none)
*
* @readonly
* @var T|null
*/
public $type;
/**
* The position of the token in the input string
*
* @readonly
* @var int
*/
public $position;
/**
* @param V $value
* @param T|null $type
*/
public function __construct($value, $type, int $position)
{
$this->value = $value;
$this->type = $type;
$this->position = $position;
}
/** @param T ...$types */
public function isA(...$types): bool
{
return in_array($this->type, $types, true);
}
/**
* @deprecated Use the value, type or position property instead
* {@inheritDoc}
*/
public function offsetExists($offset): bool
{
Deprecation::trigger(
'doctrine/lexer',
'https://github.com/doctrine/lexer/pull/79',
'Accessing %s properties via ArrayAccess is deprecated, use the value, type or position property instead',
self::class
);
return in_array($offset, ['value', 'type', 'position'], true);
}
/**
* @deprecated Use the value, type or position property instead
* {@inheritDoc}
*
* @param O $offset
*
* @return mixed
* @psalm-return (
* O is 'value'
* ? V
* : (
* O is 'type'
* ? T|null
* : (
* O is 'position'
* ? int
* : mixed
* )
* )
* )
*
* @template O of array-key
*/
#[ReturnTypeWillChange]
public function offsetGet($offset)
{
Deprecation::trigger(
'doctrine/lexer',
'https://github.com/doctrine/lexer/pull/79',
'Accessing %s properties via ArrayAccess is deprecated, use the value, type or position property instead',
self::class
);
return $this->$offset;
}
/**
* @deprecated no replacement planned
* {@inheritDoc}
*/
public function offsetSet($offset, $value): void
{
Deprecation::trigger(
'doctrine/lexer',
'https://github.com/doctrine/lexer/pull/79',
'Setting %s properties via ArrayAccess is deprecated',
self::class
);
$this->$offset = $value;
}
/**
* @deprecated no replacement planned
* {@inheritDoc}
*/
public function offsetUnset($offset): void
{
Deprecation::trigger(
'doctrine/lexer',
'https://github.com/doctrine/lexer/pull/79',
'Setting %s properties via ArrayAccess is deprecated',
self::class
);
$this->$offset = null;
}
}

19
vendor/doctrine/persistence/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2006-2015 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

17
vendor/doctrine/persistence/README.md vendored Normal file
View File

@ -0,0 +1,17 @@
# Doctrine Persistence
[![GitHub Actions][GA 4.0 image]][GA 4.0]
[![Code Coverage][Coverage 4.0 image]][CodeCov 4.0]
The Doctrine Persistence project is a library that provides common abstractions for object mapper persistence.
## More resources:
* [Website](https://www.doctrine-project.org/)
* [Documentation](https://www.doctrine-project.org/projects/doctrine-persistence/en/latest/index.html)
* [Downloads](https://github.com/doctrine/persistence/releases)
[Coverage 4.0 image]: https://codecov.io/gh/doctrine/persistence/branch/4.0.x/graph/badge.svg
[CodeCov 4.0]: https://codecov.io/gh/doctrine/persistence/branch/4.0.x
[GA 4.0 image]: https://github.com/doctrine/persistence/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0.x
[GA 4.0]: https://github.com/doctrine/persistence/actions/workflows/continuous-integration.yml?branch=4.0.x

195
vendor/doctrine/persistence/UPGRADE.md vendored Normal file
View File

@ -0,0 +1,195 @@
Note about upgrading: Doctrine uses static and runtime mechanisms to raise
awareness about deprecated code.
- Use of `@deprecated` docblock that is detected by IDEs (like PHPStorm) or
Static Analysis tools (like Psalm, phpstan)
- Use of our low-overhead runtime deprecation API, details:
https://github.com/doctrine/deprecations/
# Upgrade to 4.0
## BC Break: Removed `StaticReflectionService`
The class `Doctrine\Persistence\Mapping\StaticReflectionService` is removed
without replacement.
## BC Break: Narrowed `ReflectionService::getClass()` return type
The return type of `ReflectionService::getClass()` has been narrowed so that
`null` is no longer a valid return value.
## BC Break: Added `ObjectManager::isUninitializedObject()`
Classes implementing `Doctrine\Persistence\ObjectManager` must implement this
new method.
## BC Break: Added type declarations
The code base is now fully typed, meaning properties, parameters and return
type declarations have been added to all types.
## BC Break: Dropped support for Common proxies
Proxy objects implementing the `Doctrine\Common\Proxy\Proxy` interface are not
supported anymore. Implement `Doctrine\Persistence\Proxy` instead.
## BC Break: Removed deprecated ReflectionProperty overrides
Deprecated classes have been removed:
- `Doctrine\Persistence\Reflection\RuntimePublicReflectionProperty`
- `Doctrine\Persistence\Reflection\TypedNoDefaultRuntimePublicReflectionProperty`
# Upgrade to 3.4
## Deprecated `StaticReflectionService`
The class `Doctrine\Persistence\Mapping\StaticReflectionService` is deprecated
without replacement.
# Upgrade to 3.3
## Added method `ObjectManager::isUninitializedObject()`
Classes implementing `Doctrine\Persistence\ObjectManager` should implement the new
method. This method will be added to the interface in 4.0.
# Upgrade to 3.1
## Deprecated `RuntimePublicReflectionProperty`
Use `RuntimeReflectionProperty` instead.
# Upgrade to 3.0
## Removed `OnClearEventArgs::clearsAllEntities()` and `OnClearEventArgs::getEntityClass()`
These methods only make sense when partially clearing the object manager, which
is no longer possible.
The second argument of the constructor of `OnClearEventArgs` is removed as well.
## BC Break: removed `ObjectManagerAware`
Implement active record style functionality directly in your application, by
using a `postLoad` event.
## BC Break: removed `AnnotationDriver`
Use `ColocatedMappingDriver` instead.
## BC Break: Removed `MappingException::pathRequired()`
Use `MappingException::pathRequiredForDriver()` instead.
## BC Break: removed `LifecycleEventArgs::getEntity()`
Use `LifecycleEventArgs::getObject()` instead.
## BC Break: removed support for short namespace aliases
- `AbstractClassMetadataFactory::getFqcnFromAlias()` is removed.
- `ClassMetadataFactory` methods now require their `$className` argument to be an
actual FQCN.
## BC Break: removed `ObjectManager::merge()`
`ObjectManagerDecorator::merge()` is removed without replacement.
## BC Break: removed support for `doctrine/cache`
Removed support for using doctrine/cache for metadata caching. The
`setCacheDriver` and `getCacheDriver` methods have been removed from
`Doctrine\Persistence\Mapping\AbstractMetadata`. Please use `getCache` and
`setCache` with a PSR-6 implementation instead.
## BC Break: changed signatures
`$objectName` has been dropped from the signature of `ObjectManager::clear()`.
```diff
- public function clear($objectName = null)
+ public function clear(): void
```
Also, native parameter type declarations have been added on all public APIs.
Native return type declarations have not been added so that it is possible to
implement types compatible with both 2.x and 3.x.
## BC Break: Removed `PersistentObject`
Please implement this functionality directly in your application if you want
ActiveRecord style functionality.
# Upgrade to 2.5
## Deprecated `OnClearEventArgs::clearsAllEntities()` and `OnClearEventArgs::getEntityClass()`
These methods only make sense when partially clearing the object manager, which
is deprecated.
Passing a second argument to the constructor of `OnClearEventArgs` is
deprecated as well.
## Deprecated `ObjectManagerAware`
Along with deprecating `PersistentObject`, deprecating `ObjectManagerAware`
means deprecating support for active record, which already came with a word of
warning. Please implement this directly in your application with a `postLoad`
event if you need active record style functionality.
## Deprecated `MappingException::pathRequired()`
`MappingException::pathRequiredForDriver()` should be used instead.
# Upgrade to 2.4
## Deprecated `AnnotationDriver`
Since attributes were introduced in PHP 8.0, annotations are deprecated.
`AnnotationDriver` is an abstract class that is used when implementing concrete
annotation drivers in dependent packages. It is deprecated in favor of using
`ColocatedMappingDriver` to implement both annotation and attribute based
drivers. This will involve implementing `isTransient()` as well as
`__construct()` and `getReader()` to retain backward compatibility.
# Upgrade to 2.3
## Deprecated using short namespace alias syntax in favor of `::class` syntax.
Before:
```php
$objectManager->find('MyPackage:MyClass', $id);
$objectManager->createQuery('SELECT u FROM MyPackage:MyClass');
```
After:
```php
$objectManager->find(MyClass::class, $id);
$objectManager->createQuery('SELECT u FROM '. MyClass::class);
```
# Upgrade to 2.2
## Deprecated `doctrine/cache` usage for metadata caching
The `setCacheDriver` and `getCacheDriver` methods in
`Doctrine\Persistence\Mapping\AbstractMetadata` have been deprecated. Please
use `getCache` and `setCache` with a PSR-6 implementation instead. Note that
even after switching to PSR-6, `getCacheDriver` will return a cache instance
that wraps the PSR-6 cache. Note that if you use a custom implementation of
doctrine/cache, the library may not be able to provide a forward compatibility
layer. The cache implementation MUST extend the
`Doctrine\Common\Cache\CacheProvider` class.
# Upgrade to 1.2
## Deprecated `ObjectManager::merge()` and `ObjectManager::detach()`
Please handle merge operations in your application, and use
`ObjectManager::clear()` instead.
## Deprecated `PersistentObject`
Please implement this functionality directly in your application if you want
ActiveRecord style functionality.

View File

@ -0,0 +1,52 @@
{
"name": "doctrine/persistence",
"type": "library",
"description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.",
"keywords": [
"persistence",
"object",
"mapper",
"orm",
"odm"
],
"homepage": "https://www.doctrine-project.org/projects/persistence.html",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"},
{"name": "Marco Pivetta", "email": "ocramius@gmail.com"}
],
"require": {
"php": "^8.1",
"doctrine/event-manager": "^1 || ^2",
"psr/cache": "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
"phpstan/phpstan": "1.12.7",
"phpstan/phpstan-phpunit": "^1",
"phpstan/phpstan-strict-rules": "^1.6",
"doctrine/coding-standard": "^12",
"phpunit/phpunit": "^9.6",
"symfony/cache": "^4.4 || ^5.4 || ^6.0 || ^7.0",
"symfony/finder": "^4.4 || ^5.4 || ^6.0 || ^7.0"
},
"autoload": {
"psr-4": {
"Doctrine\\Persistence\\": "src/Persistence"
}
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Tests\\": "tests"
}
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true,
"composer/package-versions-deprecated": true
}
}
}

View File

@ -0,0 +1,218 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence;
use InvalidArgumentException;
use ReflectionClass;
use function assert;
use function sprintf;
/**
* Abstract implementation of the ManagerRegistry contract.
*/
abstract class AbstractManagerRegistry implements ManagerRegistry
{
/**
* @param array<string, string> $connections
* @param array<string, string> $managers
* @phpstan-param class-string $proxyInterfaceName
*/
public function __construct(
private readonly string $name,
private array $connections,
private array $managers,
private readonly string $defaultConnection,
private readonly string $defaultManager,
private readonly string $proxyInterfaceName,
) {
}
/**
* Fetches/creates the given services.
*
* A service in this context is connection or a manager instance.
*
* @param string $name The name of the service.
*
* @return object The instance of the given service.
*/
abstract protected function getService(string $name): object;
/**
* Resets the given services.
*
* A service in this context is connection or a manager instance.
*
* @param string $name The name of the service.
*/
abstract protected function resetService(string $name): void;
/** Gets the name of the registry. */
public function getName(): string
{
return $this->name;
}
public function getConnection(string|null $name = null): object
{
if ($name === null) {
$name = $this->defaultConnection;
}
if (! isset($this->connections[$name])) {
throw new InvalidArgumentException(
sprintf('Doctrine %s Connection named "%s" does not exist.', $this->name, $name),
);
}
return $this->getService($this->connections[$name]);
}
/**
* {@inheritDoc}
*/
public function getConnectionNames(): array
{
return $this->connections;
}
/**
* {@inheritDoc}
*/
public function getConnections(): array
{
$connections = [];
foreach ($this->connections as $name => $id) {
$connections[$name] = $this->getService($id);
}
return $connections;
}
public function getDefaultConnectionName(): string
{
return $this->defaultConnection;
}
public function getDefaultManagerName(): string
{
return $this->defaultManager;
}
/**
* {@inheritDoc}
*
* @throws InvalidArgumentException
*/
public function getManager(string|null $name = null): ObjectManager
{
if ($name === null) {
$name = $this->defaultManager;
}
if (! isset($this->managers[$name])) {
throw new InvalidArgumentException(
sprintf('Doctrine %s Manager named "%s" does not exist.', $this->name, $name),
);
}
$service = $this->getService($this->managers[$name]);
assert($service instanceof ObjectManager);
return $service;
}
public function getManagerForClass(string $class): ObjectManager|null
{
$proxyClass = new ReflectionClass($class);
if ($proxyClass->isAnonymous()) {
return null;
}
if ($proxyClass->implementsInterface($this->proxyInterfaceName)) {
$parentClass = $proxyClass->getParentClass();
if ($parentClass === false) {
return null;
}
$class = $parentClass->getName();
}
foreach ($this->managers as $id) {
$manager = $this->getService($id);
assert($manager instanceof ObjectManager);
if (! $manager->getMetadataFactory()->isTransient($class)) {
return $manager;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public function getManagerNames(): array
{
return $this->managers;
}
/**
* {@inheritDoc}
*/
public function getManagers(): array
{
$managers = [];
foreach ($this->managers as $name => $id) {
$manager = $this->getService($id);
assert($manager instanceof ObjectManager);
$managers[$name] = $manager;
}
return $managers;
}
public function getRepository(
string $persistentObject,
string|null $persistentManagerName = null,
): ObjectRepository {
return $this
->selectManager($persistentObject, $persistentManagerName)
->getRepository($persistentObject);
}
public function resetManager(string|null $name = null): ObjectManager
{
if ($name === null) {
$name = $this->defaultManager;
}
if (! isset($this->managers[$name])) {
throw new InvalidArgumentException(sprintf('Doctrine %s Manager named "%s" does not exist.', $this->name, $name));
}
// force the creation of a new document manager
// if the current one is closed
$this->resetService($this->managers[$name]);
return $this->getManager($name);
}
/** @phpstan-param class-string $persistentObject */
private function selectManager(
string $persistentObject,
string|null $persistentManagerName = null,
): ObjectManager {
if ($persistentManagerName !== null) {
return $this->getManager($persistentManagerName);
}
return $this->getManagerForClass($persistentObject) ?? $this->getManager();
}
}

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence;
/**
* Contract covering connection for a Doctrine persistence layer ManagerRegistry class to implement.
*/
interface ConnectionRegistry
{
/**
* Gets the default connection name.
*
* @return string The default connection name.
*/
public function getDefaultConnectionName(): string;
/**
* Gets the named connection.
*
* @param string|null $name The connection name (null for the default one).
*/
public function getConnection(string|null $name = null): object;
/**
* Gets an array of all registered connections.
*
* @return array<string, object> An array of Connection instances.
*/
public function getConnections(): array;
/**
* Gets all connection names.
*
* @return array<string, string> An array of connection names.
*/
public function getConnectionNames(): array;
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Event;
use Doctrine\Common\EventArgs;
use Doctrine\Persistence\ObjectManager;
/**
* Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions
* of entities.
*
* @template-covariant TObjectManager of ObjectManager
*/
class LifecycleEventArgs extends EventArgs
{
/** @phpstan-param TObjectManager $objectManager */
public function __construct(
private readonly object $object,
private readonly ObjectManager $objectManager,
) {
}
/** Retrieves the associated object. */
public function getObject(): object
{
return $this->object;
}
/**
* Retrieves the associated ObjectManager.
*
* @phpstan-return TObjectManager
*/
public function getObjectManager(): ObjectManager
{
return $this->objectManager;
}
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Event;
use Doctrine\Common\EventArgs;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\ObjectManager;
/**
* Class that holds event arguments for a loadMetadata event.
*
* @template-covariant TClassMetadata of ClassMetadata<object>
* @template-covariant TObjectManager of ObjectManager
*/
class LoadClassMetadataEventArgs extends EventArgs
{
/**
* @phpstan-param TClassMetadata $classMetadata
* @phpstan-param TObjectManager $objectManager
*/
public function __construct(
private readonly ClassMetadata $classMetadata,
private readonly ObjectManager $objectManager,
) {
}
/**
* Retrieves the associated ClassMetadata.
*
* @phpstan-return TClassMetadata
*/
public function getClassMetadata(): ClassMetadata
{
return $this->classMetadata;
}
/**
* Retrieves the associated ObjectManager.
*
* @phpstan-return TObjectManager
*/
public function getObjectManager(): ObjectManager
{
return $this->objectManager;
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Event;
use Doctrine\Common\EventArgs;
use Doctrine\Persistence\ObjectManager;
/**
* Provides event arguments for the preFlush event.
*
* @template-covariant TObjectManager of ObjectManager
*/
class ManagerEventArgs extends EventArgs
{
/** @phpstan-param TObjectManager $objectManager */
public function __construct(
private readonly ObjectManager $objectManager,
) {
}
/**
* Retrieves the associated ObjectManager.
*
* @phpstan-return TObjectManager
*/
public function getObjectManager(): ObjectManager
{
return $this->objectManager;
}
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Event;
use Doctrine\Common\EventArgs;
use Doctrine\Persistence\ObjectManager;
/**
* Provides event arguments for the onClear event.
*
* @template-covariant TObjectManager of ObjectManager
*/
class OnClearEventArgs extends EventArgs
{
/**
* @param ObjectManager $objectManager The object manager.
* @phpstan-param TObjectManager $objectManager
*/
public function __construct(private readonly ObjectManager $objectManager)
{
}
/**
* Retrieves the associated ObjectManager.
*
* @phpstan-return TObjectManager
*/
public function getObjectManager(): ObjectManager
{
return $this->objectManager;
}
}

View File

@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Event;
use Doctrine\Persistence\ObjectManager;
use InvalidArgumentException;
use function sprintf;
/**
* Class that holds event arguments for a preUpdate event.
*
* @template-covariant TObjectManager of ObjectManager
* @extends LifecycleEventArgs<TObjectManager>
*/
class PreUpdateEventArgs extends LifecycleEventArgs
{
/** @var array<string, array<int, mixed>> */
private array $entityChangeSet;
/**
* @param array<string, array<int, mixed>> $changeSet
* @phpstan-param TObjectManager $objectManager
*/
public function __construct(object $entity, ObjectManager $objectManager, array &$changeSet)
{
parent::__construct($entity, $objectManager);
$this->entityChangeSet = &$changeSet;
}
/**
* Retrieves the entity changeset.
*
* @return array<string, array<int, mixed>>
*/
public function getEntityChangeSet(): array
{
return $this->entityChangeSet;
}
/** Checks if field has a changeset. */
public function hasChangedField(string $field): bool
{
return isset($this->entityChangeSet[$field]);
}
/** Gets the old value of the changeset of the changed field. */
public function getOldValue(string $field): mixed
{
$this->assertValidField($field);
return $this->entityChangeSet[$field][0];
}
/** Gets the new value of the changeset of the changed field. */
public function getNewValue(string $field): mixed
{
$this->assertValidField($field);
return $this->entityChangeSet[$field][1];
}
/** Sets the new value of this field. */
public function setNewValue(string $field, mixed $value): void
{
$this->assertValidField($field);
$this->entityChangeSet[$field][1] = $value;
}
/**
* Asserts the field exists in changeset.
*
* @throws InvalidArgumentException
*/
private function assertValidField(string $field): void
{
if (! isset($this->entityChangeSet[$field])) {
throw new InvalidArgumentException(sprintf(
'Field "%s" is not a valid field of the entity "%s" in PreUpdateEventArgs.',
$field,
$this->getObject()::class,
));
}
}
}

View File

@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence;
/**
* Contract covering object managers for a Doctrine persistence layer ManagerRegistry class to implement.
*/
interface ManagerRegistry extends ConnectionRegistry
{
/**
* Gets the default object manager name.
*
* @return string The default object manager name.
*/
public function getDefaultManagerName(): string;
/**
* Gets a named object manager.
*
* @param string|null $name The object manager name (null for the default one).
*/
public function getManager(string|null $name = null): ObjectManager;
/**
* Gets an array of all registered object managers.
*
* @return array<string, ObjectManager> An array of ObjectManager instances
*/
public function getManagers(): array;
/**
* Resets a named object manager.
*
* This method is useful when an object manager has been closed
* because of a rollbacked transaction AND when you think that
* it makes sense to get a new one to replace the closed one.
*
* Be warned that you will get a brand new object manager as
* the existing one is not useable anymore. This means that any
* other object with a dependency on this object manager will
* hold an obsolete reference. You can inject the registry instead
* to avoid this problem.
*
* @param string|null $name The object manager name (null for the default one).
*/
public function resetManager(string|null $name = null): ObjectManager;
/**
* Gets all object manager names and associated service IDs. A service ID
* is a string that allows to obtain an object manager, typically from a
* PSR-11 container.
*
* @return array<string,string> An array with object manager names as keys,
* and service IDs as values.
*/
public function getManagerNames(): array;
/**
* Gets the ObjectRepository for a persistent object.
*
* @param string $persistentObject The name of the persistent object.
* @param string|null $persistentManagerName The object manager name (null for the default one).
* @phpstan-param class-string<T> $persistentObject
*
* @phpstan-return ObjectRepository<T>
*
* @template T of object
*/
public function getRepository(
string $persistentObject,
string|null $persistentManagerName = null,
): ObjectRepository;
/**
* Gets the object manager associated with a given class.
*
* @param class-string $class A persistent object class name.
*/
public function getManagerForClass(string $class): ObjectManager|null;
}

View File

@ -0,0 +1,460 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\Persistence\Proxy;
use Psr\Cache\CacheItemPoolInterface;
use ReflectionClass;
use ReflectionException;
use function array_combine;
use function array_keys;
use function array_map;
use function array_reverse;
use function array_unshift;
use function assert;
use function class_exists;
use function ltrim;
use function str_contains;
use function str_replace;
use function strrpos;
use function substr;
/**
* The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
* metadata mapping informations of a class which describes how a class should be mapped
* to a relational database.
*
* This class was abstracted from the ORM ClassMetadataFactory.
*
* @template CMTemplate of ClassMetadata
* @template-implements ClassMetadataFactory<CMTemplate>
*/
abstract class AbstractClassMetadataFactory implements ClassMetadataFactory
{
/** Salt used by specific Object Manager implementation. */
protected string $cacheSalt = '__CLASSMETADATA__';
private CacheItemPoolInterface|null $cache = null;
/**
* @var array<string, ClassMetadata>
* @phpstan-var CMTemplate[]
*/
private array $loadedMetadata = [];
protected bool $initialized = false;
private ReflectionService|null $reflectionService = null;
private ProxyClassNameResolver|null $proxyClassNameResolver = null;
public function setCache(CacheItemPoolInterface $cache): void
{
$this->cache = $cache;
}
final protected function getCache(): CacheItemPoolInterface|null
{
return $this->cache;
}
/**
* Returns an array of all the loaded metadata currently in memory.
*
* @return ClassMetadata[]
* @phpstan-return CMTemplate[]
*/
public function getLoadedMetadata(): array
{
return $this->loadedMetadata;
}
/**
* {@inheritDoc}
*/
public function getAllMetadata(): array
{
if (! $this->initialized) {
$this->initialize();
}
$driver = $this->getDriver();
$metadata = [];
foreach ($driver->getAllClassNames() as $className) {
$metadata[] = $this->getMetadataFor($className);
}
return $metadata;
}
public function setProxyClassNameResolver(ProxyClassNameResolver $resolver): void
{
$this->proxyClassNameResolver = $resolver;
}
/**
* Lazy initialization of this stuff, especially the metadata driver,
* since these are not needed at all when a metadata cache is active.
*/
abstract protected function initialize(): void;
/** Returns the mapping driver implementation. */
abstract protected function getDriver(): MappingDriver;
/**
* Wakes up reflection after ClassMetadata gets unserialized from cache.
*
* @phpstan-param CMTemplate $class
*/
abstract protected function wakeupReflection(
ClassMetadata $class,
ReflectionService $reflService,
): void;
/**
* Initializes Reflection after ClassMetadata was constructed.
*
* @phpstan-param CMTemplate $class
*/
abstract protected function initializeReflection(
ClassMetadata $class,
ReflectionService $reflService,
): void;
/**
* Checks whether the class metadata is an entity.
*
* This method should return false for mapped superclasses or embedded classes.
*
* @phpstan-param CMTemplate $class
*/
abstract protected function isEntity(ClassMetadata $class): bool;
/**
* Removes the prepended backslash of a class string to conform with how php outputs class names
*
* @phpstan-param class-string $className
*
* @phpstan-return class-string
*/
private function normalizeClassName(string $className): string
{
return ltrim($className, '\\');
}
/**
* {@inheritDoc}
*
* @throws ReflectionException
* @throws MappingException
*/
public function getMetadataFor(string $className): ClassMetadata
{
$className = $this->normalizeClassName($className);
if (isset($this->loadedMetadata[$className])) {
return $this->loadedMetadata[$className];
}
if (class_exists($className, false) && (new ReflectionClass($className))->isAnonymous()) {
throw MappingException::classIsAnonymous($className);
}
if (! class_exists($className, false) && str_contains($className, ':')) {
throw MappingException::nonExistingClass($className);
}
$realClassName = $this->getRealClass($className);
if (isset($this->loadedMetadata[$realClassName])) {
// We do not have the alias name in the map, include it
return $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
}
try {
if ($this->cache !== null) {
$cached = $this->cache->getItem($this->getCacheKey($realClassName))->get();
if ($cached instanceof ClassMetadata) {
/** @phpstan-var CMTemplate $cached */
$this->loadedMetadata[$realClassName] = $cached;
$this->wakeupReflection($cached, $this->getReflectionService());
} else {
$loadedMetadata = $this->loadMetadata($realClassName);
$classNames = array_combine(
array_map($this->getCacheKey(...), $loadedMetadata),
$loadedMetadata,
);
foreach ($this->cache->getItems(array_keys($classNames)) as $item) {
if (! isset($classNames[$item->getKey()])) {
continue;
}
$item->set($this->loadedMetadata[$classNames[$item->getKey()]]);
$this->cache->saveDeferred($item);
}
$this->cache->commit();
}
} else {
$this->loadMetadata($realClassName);
}
} catch (MappingException $loadingException) {
$fallbackMetadataResponse = $this->onNotFoundMetadata($realClassName);
if ($fallbackMetadataResponse === null) {
throw $loadingException;
}
$this->loadedMetadata[$realClassName] = $fallbackMetadataResponse;
}
if ($className !== $realClassName) {
// We do not have the alias name in the map, include it
$this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
}
return $this->loadedMetadata[$className];
}
public function hasMetadataFor(string $className): bool
{
$className = $this->normalizeClassName($className);
return isset($this->loadedMetadata[$className]);
}
/**
* Sets the metadata descriptor for a specific class.
*
* NOTE: This is only useful in very special cases, like when generating proxy classes.
*
* @phpstan-param class-string $className
* @phpstan-param CMTemplate $class
*/
public function setMetadataFor(string $className, ClassMetadata $class): void
{
$this->loadedMetadata[$this->normalizeClassName($className)] = $class;
}
/**
* Gets an array of parent classes for the given entity class.
*
* @phpstan-param class-string $name
*
* @return string[]
* @phpstan-return list<class-string>
*/
protected function getParentClasses(string $name): array
{
// Collect parent classes, ignoring transient (not-mapped) classes.
$parentClasses = [];
foreach (array_reverse($this->getReflectionService()->getParentClasses($name)) as $parentClass) {
if ($this->getDriver()->isTransient($parentClass)) {
continue;
}
$parentClasses[] = $parentClass;
}
return $parentClasses;
}
/**
* Loads the metadata of the class in question and all it's ancestors whose metadata
* is still not loaded.
*
* Important: The class $name does not necessarily exist at this point here.
* Scenarios in a code-generation setup might have access to XML/YAML
* Mapping files without the actual PHP code existing here. That is why the
* {@see \Doctrine\Persistence\Mapping\ReflectionService} interface
* should be used for reflection.
*
* @param string $name The name of the class for which the metadata should get loaded.
* @phpstan-param class-string $name
*
* @return array<int, string>
* @phpstan-return list<string>
*/
protected function loadMetadata(string $name): array
{
if (! $this->initialized) {
$this->initialize();
}
$loaded = [];
$parentClasses = $this->getParentClasses($name);
$parentClasses[] = $name;
// Move down the hierarchy of parent classes, starting from the topmost class
$parent = null;
$rootEntityFound = false;
$visited = [];
$reflService = $this->getReflectionService();
foreach ($parentClasses as $className) {
if (isset($this->loadedMetadata[$className])) {
$parent = $this->loadedMetadata[$className];
if ($this->isEntity($parent)) {
$rootEntityFound = true;
array_unshift($visited, $className);
}
continue;
}
$class = $this->newClassMetadataInstance($className);
$this->initializeReflection($class, $reflService);
$this->doLoadMetadata($class, $parent, $rootEntityFound, $visited);
$this->loadedMetadata[$className] = $class;
$parent = $class;
if ($this->isEntity($class)) {
$rootEntityFound = true;
array_unshift($visited, $className);
}
$this->wakeupReflection($class, $reflService);
$loaded[] = $className;
}
return $loaded;
}
/**
* Provides a fallback hook for loading metadata when loading failed due to reflection/mapping exceptions
*
* Override this method to implement a fallback strategy for failed metadata loading
*
* @phpstan-return CMTemplate|null
*/
protected function onNotFoundMetadata(string $className): ClassMetadata|null
{
return null;
}
/**
* Actually loads the metadata from the underlying metadata.
*
* @param bool $rootEntityFound True when there is another entity (non-mapped superclass) class above the current class in the PHP class hierarchy.
* @param list<class-string> $nonSuperclassParents All parent class names that are not marked as mapped superclasses, with the direct parent class being the first and the root entity class the last element.
* @phpstan-param CMTemplate $class
* @phpstan-param CMTemplate|null $parent
*/
abstract protected function doLoadMetadata(
ClassMetadata $class,
ClassMetadata|null $parent,
bool $rootEntityFound,
array $nonSuperclassParents,
): void;
/**
* Creates a new ClassMetadata instance for the given class name.
*
* @phpstan-param class-string<T> $className
*
* @return ClassMetadata<T>
* @phpstan-return CMTemplate
*
* @template T of object
*/
abstract protected function newClassMetadataInstance(string $className): ClassMetadata;
public function isTransient(string $className): bool
{
if (! $this->initialized) {
$this->initialize();
}
if (class_exists($className, false) && (new ReflectionClass($className))->isAnonymous()) {
return false;
}
if (! class_exists($className, false) && str_contains($className, ':')) {
throw MappingException::nonExistingClass($className);
}
/** @phpstan-var class-string $className */
return $this->getDriver()->isTransient($className);
}
/** Sets the reflectionService. */
public function setReflectionService(ReflectionService $reflectionService): void
{
$this->reflectionService = $reflectionService;
}
/** Gets the reflection service associated with this metadata factory. */
public function getReflectionService(): ReflectionService
{
if ($this->reflectionService === null) {
$this->reflectionService = new RuntimeReflectionService();
}
return $this->reflectionService;
}
protected function getCacheKey(string $realClassName): string
{
return str_replace('\\', '__', $realClassName) . $this->cacheSalt;
}
/**
* Gets the real class name of a class name that could be a proxy.
*
* @phpstan-param class-string<Proxy<T>>|class-string<T> $class
*
* @phpstan-return class-string<T>
*
* @template T of object
*/
private function getRealClass(string $class): string
{
if ($this->proxyClassNameResolver === null) {
$this->createDefaultProxyClassNameResolver();
}
assert($this->proxyClassNameResolver !== null);
return $this->proxyClassNameResolver->resolveClassName($class);
}
private function createDefaultProxyClassNameResolver(): void
{
$this->proxyClassNameResolver = new class implements ProxyClassNameResolver {
/**
* @phpstan-param class-string<Proxy<T>>|class-string<T> $className
*
* @phpstan-return class-string<T>
*
* @template T of object
*/
public function resolveClassName(string $className): string
{
$pos = strrpos($className, '\\' . Proxy::MARKER . '\\');
if ($pos === false) {
/** @phpstan-var class-string<T> */
return $className;
}
/** @phpstan-var class-string<T> */
return substr($className, $pos + Proxy::MARKER_LENGTH + 2);
}
};
}
}

View File

@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping;
use ReflectionClass;
/**
* Contract for a Doctrine persistence layer ClassMetadata class to implement.
*
* @template-covariant T of object
*/
interface ClassMetadata
{
/**
* Gets the fully-qualified class name of this persistent class.
*
* @phpstan-return class-string<T>
*/
public function getName(): string;
/**
* Gets the mapped identifier field name.
*
* The returned structure is an array of the identifier field names.
*
* @return array<int, string>
* @phpstan-return list<string>
*/
public function getIdentifier(): array;
/**
* Gets the ReflectionClass instance for this mapped class.
*
* @return ReflectionClass<T>
*/
public function getReflectionClass(): ReflectionClass;
/** Checks if the given field name is a mapped identifier for this class. */
public function isIdentifier(string $fieldName): bool;
/** Checks if the given field is a mapped property for this class. */
public function hasField(string $fieldName): bool;
/** Checks if the given field is a mapped association for this class. */
public function hasAssociation(string $fieldName): bool;
/** Checks if the given field is a mapped single valued association for this class. */
public function isSingleValuedAssociation(string $fieldName): bool;
/** Checks if the given field is a mapped collection valued association for this class. */
public function isCollectionValuedAssociation(string $fieldName): bool;
/**
* A numerically indexed list of field names of this persistent class.
*
* This array includes identifier fields if present on this class.
*
* @return array<int, string>
*/
public function getFieldNames(): array;
/**
* Returns an array of identifier field names numerically indexed.
*
* @return array<int, string>
*/
public function getIdentifierFieldNames(): array;
/**
* Returns a numerically indexed list of association names of this persistent class.
*
* This array includes identifier associations if present on this class.
*
* @return array<int, string>
*/
public function getAssociationNames(): array;
/**
* Returns a type name of this field.
*
* This type names can be implementation specific but should at least include the php types:
* integer, string, boolean, float/double, datetime.
*/
public function getTypeOfField(string $fieldName): string|null;
/**
* Returns the target class name of the given association.
*
* @phpstan-return class-string|null
*/
public function getAssociationTargetClass(string $assocName): string|null;
/** Checks if the association is the inverse side of a bidirectional association. */
public function isAssociationInverseSide(string $assocName): bool;
/** Returns the target field of the owning side of the association. */
public function getAssociationMappedByTargetField(string $assocName): string;
/**
* Returns the identifier of this object as an array with field name as key.
*
* Has to return an empty array if no identifier isset.
*
* @return array<string, mixed>
*/
public function getIdentifierValues(object $object): array;
}

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping;
/**
* Contract for a Doctrine persistence layer ClassMetadata class to implement.
*
* @template T of ClassMetadata
*/
interface ClassMetadataFactory
{
/**
* Forces the factory to load the metadata of all classes known to the underlying
* mapping driver.
*
* @return ClassMetadata[] The ClassMetadata instances of all mapped classes.
* @phpstan-return list<T>
*/
public function getAllMetadata(): array;
/**
* Gets the class metadata descriptor for a class.
*
* @param class-string $className The name of the class.
*
* @phpstan-return T
*/
public function getMetadataFor(string $className): ClassMetadata;
/**
* Checks whether the factory has the metadata for a class loaded already.
*
* @param class-string $className
*
* @return bool TRUE if the metadata of the class in question is already loaded, FALSE otherwise.
*/
public function hasMetadataFor(string $className): bool;
/**
* Sets the metadata descriptor for a specific class.
*
* @param class-string $className
* @phpstan-param T $class
*/
public function setMetadataFor(string $className, ClassMetadata $class): void;
/**
* Returns whether the class with the specified name should have its metadata loaded.
* This is only the case if it is either mapped directly or as a MappedSuperclass.
*
* @phpstan-param class-string $className
*/
public function isTransient(string $className): bool;
}

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
/**
* ClassLocator is an interface for classes that can provide a list of class names.
*/
interface ClassLocator
{
/** @return list<class-string> */
public function getClassNames(): array;
}

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
/**
* Basic implementation of ClassLocator that passes a list of class names.
*/
final class ClassNames implements ClassLocator
{
/** @param list<class-string> $classNames */
public function __construct(
private array $classNames,
) {
}
/** @return list<class-string> */
public function getClassNames(): array
{
return $this->classNames;
}
}

View File

@ -0,0 +1,138 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
use Doctrine\Persistence\Mapping\MappingException;
use function array_filter;
use function array_merge;
use function array_unique;
use function array_values;
/**
* The ColocatedMappingDriver reads the mapping metadata located near the code.
*/
trait ColocatedMappingDriver
{
private ClassLocator|null $classLocator = null;
/**
* The directory paths where to look for mapping files.
*
* @var array<int, string>
*/
protected array $paths = [];
/**
* The paths excluded from path where to look for mapping files.
*
* @var array<int, string>
*/
protected array $excludePaths = [];
/** The file extension of mapping documents. */
protected string $fileExtension = '.php';
/**
* Cache for {@see getAllClassNames()}.
*
* @var array<int, string>|null
* @phpstan-var list<class-string>|null
*/
protected array|null $classNames = null;
/**
* Appends lookup paths to metadata driver.
*
* @param array<int, string> $paths
*/
public function addPaths(array $paths): void
{
$this->paths = array_unique(array_merge($this->paths, $paths));
}
/**
* Retrieves the defined metadata lookup paths.
*
* @return array<int, string>
*/
public function getPaths(): array
{
return $this->paths;
}
/**
* Append exclude lookup paths to a metadata driver.
*
* @param string[] $paths
*/
public function addExcludePaths(array $paths): void
{
$this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
}
/**
* Retrieve the defined metadata lookup exclude paths.
*
* @return array<int, string>
*/
public function getExcludePaths(): array
{
return $this->excludePaths;
}
/** Gets the file extension used to look for mapping files under. */
public function getFileExtension(): string
{
return $this->fileExtension;
}
/** Sets the file extension used to look for mapping files under. */
public function setFileExtension(string $fileExtension): void
{
$this->fileExtension = $fileExtension;
}
/**
* {@inheritDoc}
*
* Returns whether the class with the specified name is transient. Only non-transient
* classes, that is entities and mapped superclasses, should have their metadata loaded.
*
* @phpstan-param class-string $className
*/
abstract public function isTransient(string $className): bool;
/**
* Gets the names of all mapped classes known to this driver.
*
* @return string[] The names of all mapped classes known to this driver.
* @phpstan-return list<class-string>
*/
public function getAllClassNames(): array
{
if ($this->classNames !== null) {
return $this->classNames;
}
if ($this->paths === [] && $this->classLocator === null) {
throw MappingException::pathRequiredForDriver(static::class);
}
$classNames = $this->classLocator?->getClassNames() ?? [];
if ($this->paths !== []) {
$classNames = array_unique([
...FileClassLocator::createFromDirectories($this->paths, $this->excludePaths, $this->fileExtension)->getClassNames(),
...$classNames,
]);
}
return $this->classNames = array_values(array_filter(
$classNames,
fn (string $className): bool => ! $this->isTransient($className),
));
}
}

View File

@ -0,0 +1,156 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
use Doctrine\Persistence\Mapping\MappingException;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use function array_unique;
use function assert;
use function is_dir;
use function is_file;
use function is_string;
use function str_replace;
use const DIRECTORY_SEPARATOR;
/**
* Locates the file that contains the metadata information for a given class name.
*
* This behavior is independent of the actual content of the file. It just detects
* the file which is responsible for the given class name.
*/
class DefaultFileLocator implements FileLocator
{
/**
* The paths where to look for mapping files.
*
* @var array<int, string>
*/
protected array $paths = [];
/** The file extension of mapping documents. */
protected string|null $fileExtension;
/**
* Initializes a new FileDriver that looks in the given path(s) for mapping
* documents and operates in the specified operating mode.
*
* @param string|array<int, string> $paths One or multiple paths where mapping documents
* can be found.
* @param string|null $fileExtension The file extension of mapping documents,
* usually prefixed with a dot.
*/
public function __construct(string|array $paths, string|null $fileExtension = null)
{
$this->addPaths((array) $paths);
$this->fileExtension = $fileExtension;
}
/**
* Appends lookup paths to metadata driver.
*
* @param array<int, string> $paths
*/
public function addPaths(array $paths): void
{
$this->paths = array_unique([...$this->paths, ...$paths]);
}
/**
* Retrieves the defined metadata lookup paths.
*
* @return array<int, string>
*/
public function getPaths(): array
{
return $this->paths;
}
/** Gets the file extension used to look for mapping files under. */
public function getFileExtension(): string|null
{
return $this->fileExtension;
}
/**
* Sets the file extension used to look for mapping files under.
*
* @param string|null $fileExtension The file extension to set.
*/
public function setFileExtension(string|null $fileExtension): void
{
$this->fileExtension = $fileExtension;
}
public function findMappingFile(string $className): string
{
$fileName = str_replace('\\', '.', $className) . $this->fileExtension;
// Check whether file exists
foreach ($this->paths as $path) {
if (is_file($path . DIRECTORY_SEPARATOR . $fileName)) {
return $path . DIRECTORY_SEPARATOR . $fileName;
}
}
throw MappingException::mappingFileNotFound($className, $fileName);
}
/**
* {@inheritDoc}
*/
public function getAllClassNames(string $globalBasename): array
{
if ($this->paths === []) {
return [];
}
$classes = [];
foreach ($this->paths as $path) {
if (! is_dir($path)) {
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path),
RecursiveIteratorIterator::LEAVES_ONLY,
);
foreach ($iterator as $file) {
$fileName = $file->getBasename($this->fileExtension);
if ($fileName === $file->getBasename() || $fileName === $globalBasename) {
continue;
}
// NOTE: All files found here means classes are not transient!
assert(is_string($fileName));
/** @phpstan-var class-string */
$class = str_replace('.', '\\', $fileName);
$classes[] = $class;
}
}
return $classes;
}
public function fileExists(string $className): bool
{
$fileName = str_replace('\\', '.', $className) . $this->fileExtension;
// Check whether file exists
foreach ($this->paths as $path) {
if (is_file($path . DIRECTORY_SEPARATOR . $fileName)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,143 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
use AppendIterator;
use CallbackFilterIterator;
use Doctrine\Persistence\Mapping\MappingException;
use FilesystemIterator;
use InvalidArgumentException;
use Iterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use ReflectionClass;
use RegexIterator;
use SplFileInfo;
use function array_key_exists;
use function array_map;
use function assert;
use function get_debug_type;
use function get_declared_classes;
use function is_dir;
use function preg_quote;
use function realpath;
use function sprintf;
use function str_replace;
use function str_starts_with;
/**
* ClassLocator implementation that uses a list of file names to locate PHP files
* and extract class names from them.
*
* It is compatible with the Symfony Finder component, but does not require it.
*/
final class FileClassLocator implements ClassLocator
{
/** @param iterable<SplFileInfo> $files An iterable of files to include. */
public function __construct(
private iterable $files,
) {
}
/** @return list<class-string> */
public function getClassNames(): array
{
$includedFiles = [];
foreach ($this->files as $file) {
// @phpstan-ignore function.alreadyNarrowedType, instanceof.alwaysTrue
assert($file instanceof SplFileInfo, new InvalidArgumentException(sprintf('Expected an iterable of SplFileInfo, got %s', get_debug_type($file))));
// Skip non-files
if (! $file->isFile()) {
continue;
}
// getRealPath() returns false if the file is in a phar archive
// @phpstan-ignore ternary.shortNotAllowed (false is the only falsy value getRealPath() may return)
$fileName = $file->getRealPath() ?: $file->getPathname();
$includedFiles[$fileName] = true;
require_once $fileName;
}
$classes = [];
foreach (get_declared_classes() as $className) {
$fileName = (new ReflectionClass($className))->getFileName();
if ($fileName === false || ! array_key_exists($fileName, $includedFiles)) {
continue;
}
$classes[] = $className;
}
return $classes;
}
/**
* Creates a FileClassLocator from an array of directories.
*
* @param list<string> $directories
* @param list<string> $excludedDirectories Directories to exclude from the search.
* @param string $fileExtension The file extension to look for (default is '.php').
*
* @throws MappingException if any of the directories are not valid.
*/
public static function createFromDirectories(
array $directories,
array $excludedDirectories = [],
string $fileExtension = '.php',
): self {
$filesIterator = new AppendIterator();
foreach ($directories as $directory) {
if (! is_dir($directory)) {
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($directory);
}
/** @var Iterator<array-key,SplFileInfo> $iterator */
$iterator = new RegexIterator(
new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::LEAVES_ONLY,
),
sprintf('/%s$/', preg_quote($fileExtension, '/')),
RegexIterator::MATCH,
);
$filesIterator->append($iterator);
}
if ($excludedDirectories !== []) {
$excludedDirectories = array_map(
// realpath() returns false if the file is in a phar archive
// @phpstan-ignore ternary.shortNotAllowed (false is the only falsy value realpath() may return)
static fn (string $dir): string => str_replace('\\', '/', realpath($dir) ?: $dir),
$excludedDirectories,
);
$filesIterator = new CallbackFilterIterator(
$filesIterator,
static function (SplFileInfo $file) use ($excludedDirectories): bool {
// getRealPath() returns false if the file is in a phar archive
// @phpstan-ignore ternary.shortNotAllowed (false is the only falsy value getRealPath() may return)
$sourceFile = str_replace('\\', '/', $file->getRealPath() ?: $file->getPathname());
foreach ($excludedDirectories as $excludedDirectory) {
if (str_starts_with($sourceFile, $excludedDirectory)) {
return false;
}
}
return true;
},
);
}
return new self($filesIterator);
}
}

View File

@ -0,0 +1,182 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\MappingException;
use function array_keys;
use function array_unique;
use function array_values;
use function is_file;
use function str_replace;
/**
* Base driver for file-based metadata drivers.
*
* A file driver operates in a mode where it loads the mapping files of individual
* classes on demand. This requires the user to adhere to the convention of 1 mapping
* file per class and the file names of the mapping files must correspond to the full
* class name, including namespace, with the namespace delimiters '\', replaced by dots '.'.
*
* @template T
*/
abstract class FileDriver implements MappingDriver
{
protected FileLocator $locator;
/**
* @var mixed[]|null
* @phpstan-var array<class-string, T>|null
*/
protected array|null $classCache = null;
protected string $globalBasename = '';
/**
* Initializes a new FileDriver that looks in the given path(s) for mapping
* documents and operates in the specified operating mode.
*
* @param string|array<int, string>|FileLocator $locator A FileLocator or one/multiple paths
* where mapping documents can be found.
*/
public function __construct(string|array|FileLocator $locator, string|null $fileExtension = null)
{
if ($locator instanceof FileLocator) {
$this->locator = $locator;
} else {
$this->locator = new DefaultFileLocator((array) $locator, $fileExtension);
}
}
/** Sets the global basename. */
public function setGlobalBasename(string $file): void
{
$this->globalBasename = $file;
}
/** Retrieves the global basename. */
public function getGlobalBasename(): string
{
return $this->globalBasename;
}
/**
* Gets the element of schema meta data for the class from the mapping file.
* This will lazily load the mapping file if it is not loaded yet.
*
* @phpstan-param class-string $className
*
* @return T The element of schema meta data.
*
* @throws MappingException
*/
public function getElement(string $className): mixed
{
if ($this->classCache === null) {
$this->initialize();
}
if (isset($this->classCache[$className])) {
return $this->classCache[$className];
}
$result = $this->loadMappingFile($this->locator->findMappingFile($className));
if (! isset($result[$className])) {
throw MappingException::invalidMappingFile(
$className,
str_replace('\\', '.', $className) . $this->locator->getFileExtension(),
);
}
$this->classCache[$className] = $result[$className];
return $result[$className];
}
public function isTransient(string $className): bool
{
if ($this->classCache === null) {
$this->initialize();
}
if (isset($this->classCache[$className])) {
return false;
}
return ! $this->locator->fileExists($className);
}
/**
* {@inheritDoc}
*/
public function getAllClassNames(): array
{
if ($this->classCache === null) {
$this->initialize();
}
if ($this->classCache === []) {
return $this->locator->getAllClassNames($this->globalBasename);
}
/** @phpstan-var array<class-string, ClassMetadata<object>> $classCache */
$classCache = $this->classCache;
/** @var list<class-string> $keys */
$keys = array_keys($classCache);
return array_values(array_unique([...$keys, ...$this->locator->getAllClassNames($this->globalBasename)]));
}
/**
* Loads a mapping file with the given name and returns a map
* from class/entity names to their corresponding file driver elements.
*
* @param string $file The mapping file to load.
*
* @return mixed[]
* @phpstan-return array<class-string, T>
*/
abstract protected function loadMappingFile(string $file): array;
/**
* Initializes the class cache from all the global files.
*
* Using this feature adds a substantial performance hit to file drivers as
* more metadata has to be loaded into memory than might actually be
* necessary. This may not be relevant to scenarios where caching of
* metadata is in place, however hits very hard in scenarios where no
* caching is used.
*/
protected function initialize(): void
{
$this->classCache = [];
if ($this->globalBasename === '') {
return;
}
foreach ($this->locator->getPaths() as $path) {
$file = $path . '/' . $this->globalBasename . $this->locator->getFileExtension();
if (! is_file($file)) {
continue;
}
$this->classCache = [...$this->classCache, ...$this->loadMappingFile($file)];
}
}
/** Retrieves the locator used to discover mapping files by className. */
public function getLocator(): FileLocator
{
return $this->locator;
}
/** Sets the locator used to discover mapping files by className. */
public function setLocator(FileLocator $locator): void
{
$this->locator = $locator;
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
/**
* Locates the file that contains the metadata information for a given class name.
*
* This behavior is independent of the actual content of the file. It just detects
* the file which is responsible for the given class name.
*/
interface FileLocator
{
/** Locates mapping file for the given class name. */
public function findMappingFile(string $className): string;
/**
* Gets all class names that are found with this file locator.
*
* @param string $globalBasename Passed to allow excluding the basename.
*
* @return array<int, string>
* @phpstan-return list<class-string>
*/
public function getAllClassNames(string $globalBasename): array;
/** Checks if a file can be found for this class name. */
public function fileExists(string $className): bool;
/**
* Gets all the paths that this file locator looks for mapping files.
*
* @return array<int, string>
*/
public function getPaths(): array;
/** Gets the file extension that mapping files are suffixed with. */
public function getFileExtension(): string|null;
}

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
use Doctrine\Persistence\Mapping\ClassMetadata;
/**
* Contract for metadata drivers.
*/
interface MappingDriver
{
/**
* Loads the metadata for the specified class into the provided container.
*
* @phpstan-param class-string<T> $className
* @phpstan-param ClassMetadata<T> $metadata
*
* @template T of object
*/
public function loadMetadataForClass(string $className, ClassMetadata $metadata): void;
/**
* Gets the names of all mapped classes known to this driver.
*
* @return array<int, string> The names of all mapped classes known to this driver.
* @phpstan-return list<class-string>
*/
public function getAllClassNames(): array;
/**
* Returns whether the class with the specified name should have its metadata loaded.
* This is only the case if it is either mapped as an Entity or a MappedSuperclass.
*
* @phpstan-param class-string $className
*/
public function isTransient(string $className): bool;
}

View File

@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\MappingException;
use function array_keys;
use function spl_object_id;
use function str_starts_with;
/**
* The DriverChain allows you to add multiple other mapping drivers for
* certain namespaces.
*/
class MappingDriverChain implements MappingDriver
{
/**
* The default driver.
*/
private MappingDriver|null $defaultDriver = null;
/** @var array<string, MappingDriver> */
private array $drivers = [];
/** Gets the default driver. */
public function getDefaultDriver(): MappingDriver|null
{
return $this->defaultDriver;
}
/** Set the default driver. */
public function setDefaultDriver(MappingDriver $driver): void
{
$this->defaultDriver = $driver;
}
/** Adds a nested driver. */
public function addDriver(MappingDriver $nestedDriver, string $namespace): void
{
$this->drivers[$namespace] = $nestedDriver;
}
/**
* Gets the array of nested drivers.
*
* @return array<string, MappingDriver> $drivers
*/
public function getDrivers(): array
{
return $this->drivers;
}
public function loadMetadataForClass(string $className, ClassMetadata $metadata): void
{
foreach ($this->drivers as $namespace => $driver) {
if (str_starts_with($className, $namespace)) {
$driver->loadMetadataForClass($className, $metadata);
return;
}
}
if ($this->defaultDriver !== null) {
$this->defaultDriver->loadMetadataForClass($className, $metadata);
return;
}
throw MappingException::classNotFoundInNamespaces($className, array_keys($this->drivers));
}
/**
* {@inheritDoc}
*/
public function getAllClassNames(): array
{
$classNames = [];
$driverClasses = [];
foreach ($this->drivers as $namespace => $driver) {
$oid = spl_object_id($driver);
if (! isset($driverClasses[$oid])) {
$driverClasses[$oid] = $driver->getAllClassNames();
}
foreach ($driverClasses[$oid] as $className) {
if (! str_starts_with($className, $namespace)) {
continue;
}
$classNames[$className] = true;
}
}
if ($this->defaultDriver !== null) {
foreach ($this->defaultDriver->getAllClassNames() as $className) {
$classNames[$className] = true;
}
}
return array_keys($classNames);
}
public function isTransient(string $className): bool
{
foreach ($this->drivers as $namespace => $driver) {
if (str_starts_with($className, $namespace)) {
return $driver->isTransient($className);
}
}
if ($this->defaultDriver !== null) {
return $this->defaultDriver->isTransient($className);
}
return true;
}
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
use Doctrine\Persistence\Mapping\ClassMetadata;
/**
* The PHPDriver includes php files which just populate ClassMetadataInfo
* instances with plain PHP code.
*
* @template-extends FileDriver<ClassMetadata<object>>
*/
class PHPDriver extends FileDriver
{
/** @phpstan-var ClassMetadata<object> */
protected ClassMetadata $metadata;
/** @param string|array<int, string>|FileLocator $locator */
public function __construct(string|array|FileLocator $locator)
{
parent::__construct($locator, '.php');
}
public function loadMetadataForClass(string $className, ClassMetadata $metadata): void
{
$this->metadata = $metadata;
$this->loadMappingFile($this->locator->findMappingFile($className));
}
/**
* {@inheritDoc}
*/
protected function loadMappingFile(string $file): array
{
$metadata = $this->metadata;
include $file;
return [$metadata->getName() => $metadata];
}
}

View File

@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\MappingException;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use ReflectionClass;
use function array_unique;
use function get_declared_classes;
use function in_array;
use function is_dir;
use function method_exists;
use function realpath;
/**
* The StaticPHPDriver calls a static loadMetadata() method on your entity
* classes where you can manually populate the ClassMetadata instance.
*/
class StaticPHPDriver implements MappingDriver
{
/**
* Paths of entity directories.
*
* @var array<int, string>
*/
private array $paths = [];
/**
* Map of all class names.
*
* @var array<int, string>
* @phpstan-var list<class-string>
*/
private array|null $classNames = null;
/** @param array<int, string>|string $paths */
public function __construct(array|string $paths)
{
$this->addPaths((array) $paths);
}
/** @param array<int, string> $paths */
public function addPaths(array $paths): void
{
$this->paths = array_unique([...$this->paths, ...$paths]);
}
public function loadMetadataForClass(string $className, ClassMetadata $metadata): void
{
$className::loadMetadata($metadata);
}
/**
* {@inheritDoc}
*
* @todo Same code exists in ColocatedMappingDriver, should we re-use it
* somehow or not worry about it?
*/
public function getAllClassNames(): array
{
if ($this->classNames !== null) {
return $this->classNames;
}
if ($this->paths === []) {
throw MappingException::pathRequiredForDriver(static::class);
}
$classes = [];
$includedFiles = [];
foreach ($this->paths as $path) {
if (! is_dir($path)) {
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path),
RecursiveIteratorIterator::LEAVES_ONLY,
);
foreach ($iterator as $file) {
if ($file->getBasename('.php') === $file->getBasename()) {
continue;
}
$sourceFile = realpath($file->getPathName());
require_once $sourceFile;
$includedFiles[] = $sourceFile;
}
}
$declared = get_declared_classes();
foreach ($declared as $className) {
$rc = new ReflectionClass($className);
$sourceFile = $rc->getFileName();
if (! in_array($sourceFile, $includedFiles, true) || $this->isTransient($className)) {
continue;
}
$classes[] = $className;
}
$this->classNames = $classes;
return $classes;
}
public function isTransient(string $className): bool
{
return ! method_exists($className, 'loadMetadata');
}
}

View File

@ -0,0 +1,245 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
use Doctrine\Persistence\Mapping\MappingException;
use InvalidArgumentException;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RuntimeException;
use function array_keys;
use function assert;
use function is_dir;
use function is_file;
use function is_int;
use function realpath;
use function sprintf;
use function str_replace;
use function str_starts_with;
use function strlen;
use function strrpos;
use function strtr;
use function substr;
use const DIRECTORY_SEPARATOR;
/**
* The Symfony File Locator makes a simplifying assumptions compared
* to the DefaultFileLocator. By assuming paths only contain entities of a certain
* namespace the mapping files consists of the short classname only.
*/
class SymfonyFileLocator implements FileLocator
{
/**
* The paths where to look for mapping files.
*
* @var array<int, string>
*/
protected array $paths = [];
/**
* A map of mapping directory path to namespace prefix used to expand class shortnames.
*
* @var array<string, string>
*/
protected array $prefixes = [];
/** File extension that is searched for. */
protected string|null $fileExtension;
/**
* Represents PHP namespace delimiters when looking for files
*/
private readonly string $nsSeparator;
/**
* @param array<string, string> $prefixes
* @param string $nsSeparator String which would be used when converting FQCN
* to filename and vice versa. Should not be empty
*/
public function __construct(
array $prefixes,
string $fileExtension = '',
string $nsSeparator = '.',
) {
$this->addNamespacePrefixes($prefixes);
$this->fileExtension = $fileExtension;
if ($nsSeparator === '') {
throw new InvalidArgumentException('Namespace separator should not be empty');
}
$this->nsSeparator = $nsSeparator;
}
/**
* Adds Namespace Prefixes.
*
* @param array<string, string> $prefixes
*/
public function addNamespacePrefixes(array $prefixes): void
{
$this->prefixes = [...$this->prefixes, ...$prefixes];
$this->paths = [...$this->paths, ...array_keys($prefixes)];
}
/**
* Gets Namespace Prefixes.
*
* @return string[]
*/
public function getNamespacePrefixes(): array
{
return $this->prefixes;
}
/**
* {@inheritDoc}
*/
public function getPaths(): array
{
return $this->paths;
}
public function getFileExtension(): string|null
{
return $this->fileExtension;
}
/**
* Sets the file extension used to look for mapping files under.
*
* @param string $fileExtension The file extension to set.
*/
public function setFileExtension(string $fileExtension): void
{
$this->fileExtension = $fileExtension;
}
public function fileExists(string $className): bool
{
$defaultFileName = str_replace('\\', $this->nsSeparator, $className) . $this->fileExtension;
foreach ($this->paths as $path) {
if (! isset($this->prefixes[$path])) {
// global namespace class
if (is_file($path . DIRECTORY_SEPARATOR . $defaultFileName)) {
return true;
}
continue;
}
$prefix = $this->prefixes[$path];
if (! str_starts_with($className, $prefix . '\\')) {
continue;
}
$filename = $path . '/' . strtr(substr($className, strlen($prefix) + 1), '\\', $this->nsSeparator) . $this->fileExtension;
if (is_file($filename)) {
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*/
public function getAllClassNames(string|null $globalBasename = null): array
{
if ($this->paths === []) {
return [];
}
$classes = [];
foreach ($this->paths as $path) {
if (! is_dir($path)) {
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path),
RecursiveIteratorIterator::LEAVES_ONLY,
);
foreach ($iterator as $file) {
$fileName = $file->getBasename($this->fileExtension);
if ($fileName === $file->getBasename() || $fileName === $globalBasename) {
continue;
}
// NOTE: All files found here means classes are not transient!
if (isset($this->prefixes[$path])) {
// Calculate namespace suffix for given prefix as a relative path from basepath to file path
$nsSuffix = strtr(
substr($this->realpath($file->getPath()), strlen($this->realpath($path))),
$this->nsSeparator,
'\\',
);
/** @phpstan-var class-string */
$class = $this->prefixes[$path] . str_replace(DIRECTORY_SEPARATOR, '\\', $nsSuffix) . '\\' . str_replace($this->nsSeparator, '\\', $fileName);
} else {
/** @phpstan-var class-string */
$class = str_replace($this->nsSeparator, '\\', $fileName);
}
$classes[] = $class;
}
}
return $classes;
}
public function findMappingFile(string $className): string
{
$defaultFileName = str_replace('\\', $this->nsSeparator, $className) . $this->fileExtension;
foreach ($this->paths as $path) {
if (! isset($this->prefixes[$path])) {
if (is_file($path . DIRECTORY_SEPARATOR . $defaultFileName)) {
return $path . DIRECTORY_SEPARATOR . $defaultFileName;
}
continue;
}
$prefix = $this->prefixes[$path];
if (! str_starts_with($className, $prefix . '\\')) {
continue;
}
$filename = $path . '/' . strtr(substr($className, strlen($prefix) + 1), '\\', $this->nsSeparator) . $this->fileExtension;
if (is_file($filename)) {
return $filename;
}
}
$pos = strrpos($className, '\\');
assert(is_int($pos));
throw MappingException::mappingFileNotFound(
$className,
substr($className, $pos + 1) . $this->fileExtension,
);
}
private function realpath(string $path): string
{
$realpath = realpath($path);
if ($realpath === false) {
throw new RuntimeException(sprintf('Could not get realpath for %s', $path));
}
return $realpath;
}
}

View File

@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping;
use Exception;
use function implode;
use function sprintf;
/**
* A MappingException indicates that something is wrong with the mapping setup.
*/
class MappingException extends Exception
{
/** @param array<int, string> $namespaces */
public static function classNotFoundInNamespaces(
string $className,
array $namespaces,
): self {
return new self(sprintf(
"The class '%s' was not found in the chain configured namespaces %s",
$className,
implode(', ', $namespaces),
));
}
/** @param class-string $driverClassName */
public static function pathRequiredForDriver(string $driverClassName): self
{
return new self(sprintf(
'Specifying source file paths to your entities is required when using %s to retrieve all class names.',
$driverClassName,
));
}
public static function fileMappingDriversRequireConfiguredDirectoryPath(
string|null $path = null,
): self {
if ($path !== null) {
$path = '[' . $path . ']';
}
return new self(sprintf(
'File mapping drivers must have a valid directory path, ' .
'however the given path %s seems to be incorrect!',
(string) $path,
));
}
public static function mappingFileNotFound(string $entityName, string $fileName): self
{
return new self(sprintf(
"No mapping file found named '%s' for class '%s'.",
$fileName,
$entityName,
));
}
public static function invalidMappingFile(string $entityName, string $fileName): self
{
return new self(sprintf(
"Invalid mapping file '%s' for class '%s'.",
$fileName,
$entityName,
));
}
public static function nonExistingClass(string $className): self
{
return new self(sprintf("Class '%s' does not exist", $className));
}
/** @param class-string $className */
public static function classIsAnonymous(string $className): self
{
return new self(sprintf('Class "%s" is anonymous', $className));
}
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping;
use Doctrine\Persistence\Proxy;
interface ProxyClassNameResolver
{
/**
* @phpstan-param class-string<Proxy<T>>|class-string<T> $className
*
* @phpstan-return class-string<T>
*
* @template T of object
*/
public function resolveClassName(string $className): string;
}

View File

@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping;
use ReflectionClass;
use ReflectionProperty;
/**
* Very simple reflection service abstraction.
*
* This is required inside metadata layers that may require either
* static or runtime reflection.
*/
interface ReflectionService
{
/**
* Returns an array of the parent classes (not interfaces) for the given class.
*
* @phpstan-param class-string $class
*
* @return string[]
* @phpstan-return class-string[]
*
* @throws MappingException
*/
public function getParentClasses(string $class): array;
/**
* Returns the shortname of a class.
*
* @phpstan-param class-string $class
*/
public function getClassShortName(string $class): string;
/** @phpstan-param class-string $class */
public function getClassNamespace(string $class): string;
/**
* Returns a reflection class instance or null.
*
* @phpstan-param class-string<T> $class
*
* @phpstan-return ReflectionClass<T>
*
* @template T of object
*/
public function getClass(string $class): ReflectionClass;
/**
* Returns an accessible property or null.
*
* @phpstan-param class-string $class
*/
public function getAccessibleProperty(string $class, string $property): ReflectionProperty|null;
/**
* Checks if the class have a public method with the given name.
*
* @phpstan-param class-string $class
*/
public function hasPublicMethod(string $class, string $method): bool;
}

View File

@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping;
use Doctrine\Persistence\Reflection\RuntimeReflectionProperty;
use Doctrine\Persistence\Reflection\TypedNoDefaultReflectionProperty;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
use function array_key_exists;
use function assert;
use function class_exists;
use function class_parents;
use function phpversion;
use function version_compare;
/**
* PHP Runtime Reflection Service.
*/
class RuntimeReflectionService implements ReflectionService
{
private readonly bool $supportsTypedPropertiesWorkaround;
public function __construct()
{
$this->supportsTypedPropertiesWorkaround = version_compare(phpversion(), '7.4.0') >= 0;
}
/**
* {@inheritDoc}
*/
public function getParentClasses(string $class): array
{
if (! class_exists($class)) {
throw MappingException::nonExistingClass($class);
}
$parents = class_parents($class);
assert($parents !== false);
return $parents;
}
public function getClassShortName(string $class): string
{
$reflectionClass = new ReflectionClass($class);
return $reflectionClass->getShortName();
}
public function getClassNamespace(string $class): string
{
$reflectionClass = new ReflectionClass($class);
return $reflectionClass->getNamespaceName();
}
/**
* @phpstan-param class-string<T> $class
*
* @phpstan-return ReflectionClass<T>
*
* @template T of object
*/
public function getClass(string $class): ReflectionClass
{
return new ReflectionClass($class);
}
public function getAccessibleProperty(string $class, string $property): RuntimeReflectionProperty
{
$reflectionProperty = new RuntimeReflectionProperty($class, $property);
if ($this->supportsTypedPropertiesWorkaround && ! array_key_exists($property, $this->getClass($class)->getDefaultProperties())) {
$reflectionProperty = new TypedNoDefaultReflectionProperty($class, $property);
}
return $reflectionProperty;
}
public function hasPublicMethod(string $class, string $method): bool
{
try {
$reflectionMethod = new ReflectionMethod($class, $method);
} catch (ReflectionException) {
return false;
}
return $reflectionMethod->isPublic();
}
}

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence;
/**
* Interface for classes that notify event listeners of changes to their managed properties.
*
* This interface is implemented by objects that manually want to notify their object manager or
* other listeners when properties change, instead of relying on the object manager to compute
* property changes itself when changes are to be persisted.
*/
interface NotifyPropertyChanged
{
/** Adds a listener that wants to be notified about property changes. */
public function addPropertyChangedListener(PropertyChangedListener $listener): void;
}

View File

@ -0,0 +1,128 @@
<?php
declare(strict_types=1);
namespace Doctrine\Persistence;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\ClassMetadataFactory;
/** Contract for a Doctrine persistence layer ObjectManager class to implement. */
interface ObjectManager
{
/**
* Finds an object by its identifier.
*
* This is just a convenient shortcut for getRepository($className)->find($id).
*
* @param string $className The class name of the object to find.
* @param mixed $id The identity of the object to find.
* @phpstan-param class-string<T> $className
*
* @return object|null The found object.
* @phpstan-return T|null
*
* @template T of object
*/
public function find(string $className, mixed $id): object|null;
/**
* Tells the ObjectManager to make an instance managed and persistent.
*
* The object will be entered into the database as a result of the flush operation.
*
* NOTE: The persist operation always considers objects that are not yet known to
* this ObjectManager as NEW. Do not pass detached objects to the persist operation.
*
* @param object $object The instance to make managed and persistent.
*/
public function persist(object $object): void;
/**
* Removes an object instance.
*
* A removed object will be removed from the database as a result of the flush operation.
*
* @param object $object The object instance to remove.
*/
public function remove(object $object): void;
/**
* Clears the ObjectManager. All objects that are currently managed
* by this ObjectManager become detached.
*/
public function clear(): void;
/**
* Detaches an object from the ObjectManager, causing a managed object to
* become detached. Unflushed changes made to the object if any
* (including removal of the object), will not be synchronized to the database.
* Objects which previously referenced the detached object will continue to
* reference it.
*
* @param object $object The object to detach.
*/
public function detach(object $object): void;
/**
* Refreshes the persistent state of an object from the database,
* overriding any local changes that have not yet been persisted.
*
* @param object $object The object to refresh.
*/
public function refresh(object $object): void;
/**
* Flushes all changes to objects that have been queued up to now to the database.
* This effectively synchronizes the in-memory state of managed objects with the
* database.
*/
public function flush(): void;
/**
* Gets the repository for a class.
*
* @phpstan-param class-string<T> $className
*
* @phpstan-return ObjectRepository<T>
*
* @template T of object
*/
public function getRepository(string $className): ObjectRepository;
/**
* Returns the ClassMetadata descriptor for a class.
*
* The class name must be the fully-qualified class name without a leading backslash
* (as it is returned by get_class($obj)).
*
* @phpstan-param class-string<T> $className
*
* @phpstan-return ClassMetadata<T>
*
* @template T of object
*/
public function getClassMetadata(string $className): ClassMetadata;
/**
* Gets the metadata factory used to gather the metadata of classes.
*
* @phpstan-return ClassMetadataFactory<ClassMetadata<object>>
*/
public function getMetadataFactory(): ClassMetadataFactory;
/**
* Helper method to initialize a lazy loading proxy or persistent collection.
*
* This method is a no-op for other objects.
*/
public function initializeObject(object $obj): void;
/** Helper method to check whether a lazy loading proxy or persistent collection has been initialized. */
public function isUninitializedObject(mixed $value): bool;
/**
* Checks if the object is part of the current UnitOfWork and therefore managed.
*/
public function contains(object $object): bool;
}

Some files were not shown because too many files have changed in this diff Show More