Drupal core and module updates

This commit is contained in:
Robert 2022-10-07 15:20:07 +02:00
parent a870febec9
commit 75e6cf3a57
1850 changed files with 19069 additions and 27335 deletions

File diff suppressed because it is too large Load Diff

View File

@ -57,12 +57,13 @@ return array(
'Drupal\\Component\\' => array($baseDir . '/web/core/lib/Drupal/Component'), 'Drupal\\Component\\' => array($baseDir . '/web/core/lib/Drupal/Component'),
'Doctrine\\Persistence\\' => array($vendorDir . '/doctrine/persistence/lib/Doctrine/Persistence'), 'Doctrine\\Persistence\\' => array($vendorDir . '/doctrine/persistence/lib/Doctrine/Persistence'),
'Doctrine\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib/Doctrine/Inflector'), 'Doctrine\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib/Doctrine/Inflector'),
'Doctrine\\Deprecations\\' => array($vendorDir . '/doctrine/deprecations/lib/Doctrine/Deprecations'),
'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer'), 'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer'),
'Doctrine\\Common\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib/Doctrine/Common/Inflector'), 'Doctrine\\Common\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib/Doctrine/Common/Inflector'),
'Doctrine\\Common\\Collections\\' => array($vendorDir . '/doctrine/collections/lib/Doctrine/Common/Collections'), 'Doctrine\\Common\\Collections\\' => array($vendorDir . '/doctrine/collections/lib/Doctrine/Common/Collections'),
'Doctrine\\Common\\Cache\\' => array($vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache'), 'Doctrine\\Common\\Cache\\' => array($vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache'),
'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations'), 'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations'),
'Doctrine\\Common\\' => array($vendorDir . '/doctrine/common/lib/Doctrine/Common', $vendorDir . '/doctrine/event-manager/lib/Doctrine/Common', $vendorDir . '/doctrine/persistence/lib/Doctrine/Common', $vendorDir . '/doctrine/reflection/lib/Doctrine/Common'), 'Doctrine\\Common\\' => array($vendorDir . '/doctrine/common/lib/Doctrine/Common', $vendorDir . '/doctrine/persistence/lib/Doctrine/Common', $vendorDir . '/doctrine/reflection/lib/Doctrine/Common', $vendorDir . '/doctrine/event-manager/lib/Doctrine/Common'),
'Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'), 'Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'),
'Composer\\Installers\\' => array($vendorDir . '/composer/installers/src/Composer/Installers'), 'Composer\\Installers\\' => array($vendorDir . '/composer/installers/src/Composer/Installers'),
'Asm89\\Stack\\' => array($vendorDir . '/asm89/stack-cors/src/Asm89/Stack'), 'Asm89\\Stack\\' => array($vendorDir . '/asm89/stack-cors/src/Asm89/Stack'),

View File

@ -116,6 +116,7 @@ class ComposerStaticInitd428c8960f3d72900807e718545d9f8d
'Drupal\\Component\\' => 17, 'Drupal\\Component\\' => 17,
'Doctrine\\Persistence\\' => 21, 'Doctrine\\Persistence\\' => 21,
'Doctrine\\Inflector\\' => 19, 'Doctrine\\Inflector\\' => 19,
'Doctrine\\Deprecations\\' => 22,
'Doctrine\\Common\\Lexer\\' => 22, 'Doctrine\\Common\\Lexer\\' => 22,
'Doctrine\\Common\\Inflector\\' => 26, 'Doctrine\\Common\\Inflector\\' => 26,
'Doctrine\\Common\\Collections\\' => 28, 'Doctrine\\Common\\Collections\\' => 28,
@ -340,6 +341,10 @@ class ComposerStaticInitd428c8960f3d72900807e718545d9f8d
array ( array (
0 => __DIR__ . '/..' . '/doctrine/inflector/lib/Doctrine/Inflector', 0 => __DIR__ . '/..' . '/doctrine/inflector/lib/Doctrine/Inflector',
), ),
'Doctrine\\Deprecations\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/deprecations/lib/Doctrine/Deprecations',
),
'Doctrine\\Common\\Lexer\\' => 'Doctrine\\Common\\Lexer\\' =>
array ( array (
0 => __DIR__ . '/..' . '/doctrine/lexer/lib/Doctrine/Common/Lexer', 0 => __DIR__ . '/..' . '/doctrine/lexer/lib/Doctrine/Common/Lexer',
@ -363,9 +368,9 @@ class ComposerStaticInitd428c8960f3d72900807e718545d9f8d
'Doctrine\\Common\\' => 'Doctrine\\Common\\' =>
array ( array (
0 => __DIR__ . '/..' . '/doctrine/common/lib/Doctrine/Common', 0 => __DIR__ . '/..' . '/doctrine/common/lib/Doctrine/Common',
1 => __DIR__ . '/..' . '/doctrine/event-manager/lib/Doctrine/Common', 1 => __DIR__ . '/..' . '/doctrine/persistence/lib/Doctrine/Common',
2 => __DIR__ . '/..' . '/doctrine/persistence/lib/Doctrine/Common', 2 => __DIR__ . '/..' . '/doctrine/reflection/lib/Doctrine/Common',
3 => __DIR__ . '/..' . '/doctrine/reflection/lib/Doctrine/Common', 3 => __DIR__ . '/..' . '/doctrine/event-manager/lib/Doctrine/Common',
), ),
'Composer\\Semver\\' => 'Composer\\Semver\\' =>
array ( array (

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -10,17 +10,23 @@
"slug": "latest", "slug": "latest",
"upcoming": true "upcoming": true
}, },
{
"name": "1.8",
"branchName": "1.8.x",
"slug": "1.8",
"upcoming": true
},
{ {
"name": "1.7", "name": "1.7",
"branchName": "1.7.x", "branchName": "1.7.x",
"slug": "1.7", "slug": "1.7",
"upcoming": true "current": true
}, },
{ {
"name": "1.6", "name": "1.6",
"branchName": "1.6.x", "branchName": "1.6.x",
"slug": "1.6", "slug": "1.6",
"current": true "maintained": false
} }
] ]
} }

View File

@ -6,16 +6,6 @@ Before we can merge your Pull-Request here are some guidelines that you need to
These guidelines exist not to annoy you, but to keep the code base clean, These guidelines exist not to annoy you, but to keep the code base clean,
unified and future proof. unified and future proof.
## We only accept PRs to "master"
Our branching strategy is "everything to master first", even
bugfixes and we then merge them into the stable branches. You should only
open pull requests against the master branch. Otherwise we cannot accept the PR.
There is one exception to the rule, when we merged a bug into some stable branches
we do occasionally accept pull requests that merge the same bug fix into earlier
branches.
## Coding Standard ## Coding Standard
We use the [Doctrine Coding Standard](https://github.com/doctrine/coding-standard). We use the [Doctrine Coding Standard](https://github.com/doctrine/coding-standard).

View File

@ -1,92 +1,6 @@
# Doctrine Collections # Doctrine Collections
[![Build Status](https://github.com/doctrine/collections/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/collections/actions) [![Build Status](https://github.com/doctrine/collections/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/collections/actions)
[![Code Coverage](https://codecov.io/gh/doctrine/collections/branch/master/graph/badge.svg)](https://codecov.io/gh/doctrine/collections/branch/master) [![Code Coverage](https://codecov.io/gh/doctrine/collections/branch/2.0.x/graph/badge.svg)](https://codecov.io/gh/doctrine/collections/branch/2.0.x)
Collections Abstraction library Collections Abstraction library
## Changelog
### v1.6.1
This release, combined with the release of [`doctrine/collections` `v1.6.1`](https://github.com/doctrine/collections/releases/tag/v1.6.1),
fixes an issue where parsing annotations was not possible
for classes within `doctrine/collections`.
Specifically, `v1.6.0` introduced Psalm-specific annotations
such as (for example) `@template` and `@template-implements`,
which were both incorrectly recognized as `@template`.
`@template` has therefore been removed, and instead we use
the prefixed `@psalm-template`, which is no longer parsed
by `doctrine/collections` `v1.6.1`
Total issues resolved: **1**
- [186: Use `@psalm-template` annotation to avoid clashes](https://github.com/doctrine/collections/pull/186) thanks to @muglug
### v1.6.0
This release bumps the minimum required PHP version to 7.1.3.
Following improvements were introduced:
* `ArrayCollection#filter()` now allows filtering by key, value or both.
* When using the `ClosureExpressionVisitor` over objects with a defined
accessor and property, the accessor is prioritised.
* Updated testing tools and coding standards, autoloading, which also
led to marginal performance improvements
* Introduced generic type docblock declarations from [psalm](https://github.com/vimeo/psalm),
which should allow users to declare `/** @var Collection<KeyType, ValueType> */`
in their code, and leverage the type propagation deriving from that.
Total issues resolved: **16**
- [127: Use PSR-4](https://github.com/doctrine/collections/pull/127) thanks to @Nyholm
- [129: Remove space in method declaration](https://github.com/doctrine/collections/pull/129) thanks to @bounoable
- [130: Update build to add PHPCS and PHPStan](https://github.com/doctrine/collections/pull/130) thanks to @lcobucci
- [131: ClosureExpressionVisitor &gt; Don't duplicate the accessor when the field already starts with it](https://github.com/doctrine/collections/pull/131) thanks to @ruudk
- [139: Apply Doctrine CS 2.1](https://github.com/doctrine/collections/pull/139) thanks to @Majkl578
- [142: CS 4.0, version composer.lock, merge stages](https://github.com/doctrine/collections/pull/142) thanks to @Majkl578
- [144: Update to PHPUnit 7](https://github.com/doctrine/collections/pull/144) thanks to @carusogabriel
- [146: Update changelog for v1.4.0 and v1.5.0](https://github.com/doctrine/collections/pull/146) thanks to @GromNaN
- [154: Update index.rst](https://github.com/doctrine/collections/pull/154) thanks to @chraiet
- [158: Extract Selectable method into own documentation section](https://github.com/doctrine/collections/pull/158) thanks to @SenseException
- [160: Update homepage](https://github.com/doctrine/collections/pull/160) thanks to @Majkl578
- [165: Allow `ArrayCollection#filter()` to filter by key, value or both](https://github.com/doctrine/collections/issues/165) thanks to @0x13a
- [167: Allow `ArrayCollection#filter()` to filter by key and also value](https://github.com/doctrine/collections/pull/167) thanks to @0x13a
- [175: CI: Test against PHP 7.4snapshot instead of nightly (8.0)](https://github.com/doctrine/collections/pull/175) thanks to @Majkl578
- [177: Generify collections using Psalm](https://github.com/doctrine/collections/pull/177) thanks to @nschoellhorn
- [178: Updated doctrine/coding-standard to 6.0](https://github.com/doctrine/collections/pull/178) thanks to @patrickjahns
### v1.5.0
* [Require PHP 7.1+](https://github.com/doctrine/collections/pull/105)
* [Drop HHVM support](https://github.com/doctrine/collections/pull/118)
### v1.4.0
* [Require PHP 5.6+](https://github.com/doctrine/collections/pull/105)
* [Add `ArrayCollection::createFrom()`](https://github.com/doctrine/collections/pull/91)
* [Support non-camel-case naming](https://github.com/doctrine/collections/pull/57)
* [Comparison `START_WITH`, `END_WITH`](https://github.com/doctrine/collections/pull/78)
* [Comparison `MEMBER_OF`](https://github.com/doctrine/collections/pull/66)
* [Add Contributing guide](https://github.com/doctrine/collections/pull/103)
### v1.3.0
* [Explicit casting of first and max results in criteria API](https://github.com/doctrine/collections/pull/26)
* [Keep keys when using `ArrayCollection#matching()` with sorting](https://github.com/doctrine/collections/pull/49)
* [Made `AbstractLazyCollection#$initialized` protected for extensibility](https://github.com/doctrine/collections/pull/52)
### v1.2.0
* Add a new ``AbstractLazyCollection``
### v1.1.0
* Deprecated ``Comparison::IS``, because it's only there for SQL semantics.
These are fixed in the ORM instead.
* Add ``Comparison::CONTAINS`` to perform partial string matches:
$criteria->andWhere($criteria->expr()->contains('property', 'Foo'));

View File

@ -1,37 +1,61 @@
{ {
"name": "doctrine/collections", "name": "doctrine/collections",
"type": "library",
"description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.",
"license": "MIT",
"type": "library",
"keywords": [ "keywords": [
"php", "php",
"collections", "collections",
"array", "array",
"iterators" "iterators"
], ],
"homepage": "https://www.doctrine-project.org/projects/collections.html",
"license": "MIT",
"authors": [ "authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, {
{"name": "Roman Borschel", "email": "roman@code-factory.org"}, "name": "Guilherme Blanco",
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, "email": "guilhermeblanco@gmail.com"
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, },
{"name": "Johannes Schmitt", "email": "schmittjoh@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/collections.html",
"require": { "require": {
"php": "^7.1.3 || ^8.0" "php": "^7.1.3 || ^8.0",
"doctrine/deprecations": "^0.5.3 || ^1"
}, },
"require-dev": { "require-dev": {
"doctrine/coding-standard": "^9.0 || ^10.0",
"phpstan/phpstan": "^1.4.8",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5",
"doctrine/coding-standard": "^9.0", "vimeo/psalm": "^4.22"
"phpstan/phpstan": "^0.12",
"vimeo/psalm": "^4.2.1"
}, },
"autoload": { "autoload": {
"psr-4": { "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" } "psr-4": {
"Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections"
}
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {
"Doctrine\\Tests\\": "tests/Doctrine/Tests" "Doctrine\\Tests\\": "tests/Doctrine/Tests"
} }
},
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
"dealerdirect/phpcodesniffer-composer-installer": true
}
} }
} }

View File

@ -32,6 +32,11 @@ explicitly retrieve an iterator though ``getIterator()`` which can then be
used to iterate over the collection. You can not rely on the internal iterator used to iterate over the collection. You can not rely on the internal iterator
of the collection being at a certain position unless you explicitly positioned it before. of the collection being at a certain position unless you explicitly positioned it before.
Methods that do not alter the collection or have template types
appearing in invariant or contravariant positions are not directly
defined in ``Doctrine\Common\Collections\Collection``, but are inherited
from the ``Doctrine\Common\Collections\ReadableCollection`` interface.
The methods available on the interface are: The methods available on the interface are:
add add

View File

@ -3,6 +3,7 @@
namespace Doctrine\Common\Collections; namespace Doctrine\Common\Collections;
use Closure; use Closure;
use LogicException;
use ReturnTypeWillChange; use ReturnTypeWillChange;
use Traversable; use Traversable;
@ -18,8 +19,8 @@ abstract class AbstractLazyCollection implements Collection
/** /**
* The backed collection to use * The backed collection to use
* *
* @psalm-var Collection<TKey,T> * @psalm-var Collection<TKey,T>|null
* @var Collection<mixed> * @var Collection<mixed>|null
*/ */
protected $collection; protected $collection;
@ -60,6 +61,8 @@ abstract class AbstractLazyCollection implements Collection
/** /**
* {@inheritDoc} * {@inheritDoc}
*
* @template TMaybeContained
*/ */
public function contains($element) public function contains($element)
{ {
@ -259,6 +262,8 @@ abstract class AbstractLazyCollection implements Collection
/** /**
* {@inheritDoc} * {@inheritDoc}
*
* @template TMaybeContained
*/ */
public function indexOf($element) public function indexOf($element)
{ {
@ -292,9 +297,7 @@ abstract class AbstractLazyCollection implements Collection
} }
/** /**
* {@inheritDoc} * @param TKey $offset
*
* @psalm-param TKey $offset
* *
* @return bool * @return bool
*/ */
@ -307,10 +310,7 @@ abstract class AbstractLazyCollection implements Collection
} }
/** /**
* {@inheritDoc} * @param TKey $offset
*
* @param int|string $offset
* @psalm-param TKey $offset
* *
* @return mixed * @return mixed
*/ */
@ -323,10 +323,8 @@ abstract class AbstractLazyCollection implements Collection
} }
/** /**
* {@inheritDoc} * @param TKey|null $offset
* * @param T $value
* @param mixed $value
* @psalm-param TKey $offset
* *
* @return void * @return void
*/ */
@ -338,9 +336,7 @@ abstract class AbstractLazyCollection implements Collection
} }
/** /**
* {@inheritDoc} * @param TKey $offset
*
* @psalm-param TKey $offset
* *
* @return void * @return void
*/ */
@ -355,6 +351,8 @@ abstract class AbstractLazyCollection implements Collection
* Is the lazy collection already initialized? * Is the lazy collection already initialized?
* *
* @return bool * @return bool
*
* @psalm-assert-if-true Collection<TKey,T> $this->collection
*/ */
public function isInitialized() public function isInitialized()
{ {
@ -365,6 +363,8 @@ abstract class AbstractLazyCollection implements Collection
* Initialize the collection * Initialize the collection
* *
* @return void * @return void
*
* @psalm-assert Collection<TKey,T> $this->collection
*/ */
protected function initialize() protected function initialize()
{ {
@ -374,6 +374,10 @@ abstract class AbstractLazyCollection implements Collection
$this->doInitialize(); $this->doInitialize();
$this->initialized = true; $this->initialized = true;
if ($this->collection === null) {
throw new LogicException('You must initialize the collection property in the doInitialize() method.');
}
} }
/** /**

View File

@ -165,9 +165,7 @@ class ArrayCollection implements Collection, Selectable
/** /**
* Required by interface ArrayAccess. * Required by interface ArrayAccess.
* *
* {@inheritDoc} * @param TKey $offset
*
* @psalm-param TKey $offset
* *
* @return bool * @return bool
*/ */
@ -180,9 +178,7 @@ class ArrayCollection implements Collection, Selectable
/** /**
* Required by interface ArrayAccess. * Required by interface ArrayAccess.
* *
* {@inheritDoc} * @param TKey $offset
*
* @psalm-param TKey $offset
* *
* @return mixed * @return mixed
*/ */
@ -195,7 +191,8 @@ class ArrayCollection implements Collection, Selectable
/** /**
* Required by interface ArrayAccess. * Required by interface ArrayAccess.
* *
* {@inheritDoc} * @param TKey|null $offset
* @param T $value
* *
* @return void * @return void
*/ */
@ -214,9 +211,7 @@ class ArrayCollection implements Collection, Selectable
/** /**
* Required by interface ArrayAccess. * Required by interface ArrayAccess.
* *
* {@inheritDoc} * @param TKey $offset
*
* @psalm-param TKey $offset
* *
* @return void * @return void
*/ */
@ -236,6 +231,8 @@ class ArrayCollection implements Collection, Selectable
/** /**
* {@inheritDoc} * {@inheritDoc}
*
* @template TMaybeContained
*/ */
public function contains($element) public function contains($element)
{ {
@ -258,6 +255,12 @@ class ArrayCollection implements Collection, Selectable
/** /**
* {@inheritDoc} * {@inheritDoc}
*
* @psalm-param TMaybeContained $element
*
* @psalm-return (TMaybeContained is T ? TKey|false : false)
*
* @template TMaybeContained
*/ */
public function indexOf($element) public function indexOf($element)
{ {
@ -345,7 +348,7 @@ class ArrayCollection implements Collection, Selectable
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* @psalm-param Closure(T=):U $func * @psalm-param Closure(T):U $func
* *
* @return static * @return static
* @psalm-return static<TKey, U> * @psalm-return static<TKey, U>

View File

@ -4,8 +4,6 @@ namespace Doctrine\Common\Collections;
use ArrayAccess; use ArrayAccess;
use Closure; use Closure;
use Countable;
use IteratorAggregate;
/** /**
* The missing (SPL) Collection/Array/OrderedMap interface. * The missing (SPL) Collection/Array/OrderedMap interface.
@ -26,10 +24,10 @@ use IteratorAggregate;
* *
* @psalm-template TKey of array-key * @psalm-template TKey of array-key
* @psalm-template T * @psalm-template T
* @template-extends IteratorAggregate<TKey, T> * @template-extends ReadableCollection<TKey, T>
* @template-extends ArrayAccess<TKey|null, T> * @template-extends ArrayAccess<TKey, T>
*/ */
interface Collection extends Countable, IteratorAggregate, ArrayAccess interface Collection extends ReadableCollection, ArrayAccess
{ {
/** /**
* Adds an element at the end of the collection. * Adds an element at the end of the collection.
@ -48,24 +46,6 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
*/ */
public function clear(); public function clear();
/**
* Checks whether an element is contained in the collection.
* This is an O(n) operation, where n is the size of the collection.
*
* @param mixed $element The element to search for.
* @psalm-param T $element
*
* @return bool TRUE if the collection contains the element, FALSE otherwise.
*/
public function contains($element);
/**
* Checks whether the collection is empty (contains no elements).
*
* @return bool TRUE if the collection is empty, FALSE otherwise.
*/
public function isEmpty();
/** /**
* Removes the element at the specified index from the collection. * Removes the element at the specified index from the collection.
* *
@ -87,46 +67,6 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
*/ */
public function removeElement($element); public function removeElement($element);
/**
* Checks whether the collection contains an element with the specified key/index.
*
* @param string|int $key The key/index to check for.
* @psalm-param TKey $key
*
* @return bool TRUE if the collection contains an element with the specified key/index,
* FALSE otherwise.
*/
public function containsKey($key);
/**
* Gets the element at the specified key/index.
*
* @param string|int $key The key/index of the element to retrieve.
* @psalm-param TKey $key
*
* @return mixed
* @psalm-return T|null
*/
public function get($key);
/**
* Gets all keys/indices of the collection.
*
* @return int[]|string[] The keys/indices of the collection, in the order of the corresponding
* elements in the collection.
* @psalm-return TKey[]
*/
public function getKeys();
/**
* Gets all values of the collection.
*
* @return mixed[] The values of all elements in the collection, in the
* order they appear in the collection.
* @psalm-return T[]
*/
public function getValues();
/** /**
* Sets an element in the collection at the specified key/index. * Sets an element in the collection at the specified key/index.
* *
@ -140,69 +80,7 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
public function set($key, $value); public function set($key, $value);
/** /**
* Gets a native PHP array representation of the collection. * {@inheritdoc}
*
* @return mixed[]
* @psalm-return array<TKey,T>
*/
public function toArray();
/**
* Sets the internal iterator to the first element in the collection and returns this element.
*
* @return mixed
* @psalm-return T|false
*/
public function first();
/**
* Sets the internal iterator to the last element in the collection and returns this element.
*
* @return mixed
* @psalm-return T|false
*/
public function last();
/**
* Gets the key/index of the element at the current iterator position.
*
* @return int|string|null
* @psalm-return TKey|null
*/
public function key();
/**
* Gets the element of the collection at the current iterator position.
*
* @return mixed
* @psalm-return T|false
*/
public function current();
/**
* Moves the internal iterator position to the next element and returns this element.
*
* @return mixed
* @psalm-return T|false
*/
public function next();
/**
* Tests for the existence of an element that satisfies the given predicate.
*
* @param Closure $p The predicate.
* @psalm-param Closure(TKey=, T=):bool $p
*
* @return bool TRUE if the predicate is TRUE for at least one element, FALSE otherwise.
*/
public function exists(Closure $p);
/**
* Returns all the elements of this collection that satisfy the predicate p.
* The order of the elements is preserved.
*
* @param Closure $p The predicate used for filtering.
* @psalm-param Closure(T=):bool $p
* *
* @return Collection<mixed> A collection with the results of the filter operation. * @return Collection<mixed> A collection with the results of the filter operation.
* @psalm-return Collection<TKey, T> * @psalm-return Collection<TKey, T>
@ -210,67 +88,12 @@ interface Collection extends Countable, IteratorAggregate, ArrayAccess
public function filter(Closure $p); public function filter(Closure $p);
/** /**
* Tests whether the given predicate p holds for all elements of this collection. * {@inheritdoc}
* *
* @param Closure $p The predicate. * @return Collection<mixed>[] An array with two elements. The first element contains the collection
* @psalm-param Closure(TKey=, T=):bool $p
*
* @return bool TRUE, if the predicate yields TRUE for all elements, FALSE otherwise.
*/
public function forAll(Closure $p);
/**
* Applies the given function to each element in the collection and returns
* a new collection with the elements returned by the function.
*
* @psalm-param Closure(T=):U $func
*
* @return Collection<mixed>
* @psalm-return Collection<TKey, U>
*
* @psalm-template U
*/
public function map(Closure $func);
/**
* Partitions this collection in two collections according to a predicate.
* Keys are preserved in the resulting collections.
*
* @param Closure $p The predicate on which to partition.
* @psalm-param Closure(TKey=, T=):bool $p
*
* @return Collection<mixed> An array with two elements. The first element contains the collection
* of elements where the predicate returned TRUE, the second element * of elements where the predicate returned TRUE, the second element
* contains the collection of elements where the predicate returned FALSE. * contains the collection of elements where the predicate returned FALSE.
* @psalm-return array{0: Collection<TKey, T>, 1: Collection<TKey, T>} * @psalm-return array{0: Collection<TKey, T>, 1: Collection<TKey, T>}
*/ */
public function partition(Closure $p); public function partition(Closure $p);
/**
* Gets the index/key of a given element. The comparison of two elements is strict,
* that means not only the value but also the type must match.
* For objects this means reference equality.
*
* @param mixed $element The element to search for.
* @psalm-param T $element
*
* @return int|string|bool The key/index of the element or FALSE if the element was not found.
* @psalm-return TKey|false
*/
public function indexOf($element);
/**
* Extracts a slice of $length elements starting at position $offset from the Collection.
*
* If $length is null it returns all elements from $offset to the end of the Collection.
* Keys have to be preserved by this method. Calling this method will only return the
* selected slice and NOT change the elements contained in the collection slice is called on.
*
* @param int $offset The offset to start from.
* @param int|null $length The maximum number of elements to return, or null for no limit.
*
* @return mixed[]
* @psalm-return array<TKey,T>
*/
public function slice($offset, $length = null);
} }

View File

@ -4,8 +4,10 @@ namespace Doctrine\Common\Collections;
use Doctrine\Common\Collections\Expr\CompositeExpression; use Doctrine\Common\Collections\Expr\CompositeExpression;
use Doctrine\Common\Collections\Expr\Expression; use Doctrine\Common\Collections\Expr\Expression;
use Doctrine\Deprecations\Deprecation;
use function array_map; use function array_map;
use function func_num_args;
use function strtoupper; use function strtoupper;
/** /**
@ -69,6 +71,15 @@ class Criteria
{ {
$this->expression = $expression; $this->expression = $expression;
if ($firstResult === null && func_num_args() > 2) {
Deprecation::trigger(
'doctrine/collections',
'https://github.com/doctrine/collections/pull/311',
'Passing null as $firstResult to the constructor of %s is deprecated. Pass 0 instead or omit the argument.',
self::class
);
}
$this->setFirstResult($firstResult); $this->setFirstResult($firstResult);
$this->setMaxResults($maxResults); $this->setMaxResults($maxResults);
@ -82,7 +93,7 @@ class Criteria
/** /**
* Sets the where expression to evaluate when this Criteria is searched for. * Sets the where expression to evaluate when this Criteria is searched for.
* *
* @return Criteria * @return $this
*/ */
public function where(Expression $expression) public function where(Expression $expression)
{ {
@ -95,7 +106,7 @@ class Criteria
* Appends the where expression to evaluate when this Criteria is searched for * Appends the where expression to evaluate when this Criteria is searched for
* using an AND with previous expression. * using an AND with previous expression.
* *
* @return Criteria * @return $this
*/ */
public function andWhere(Expression $expression) public function andWhere(Expression $expression)
{ {
@ -115,7 +126,7 @@ class Criteria
* Appends the where expression to evaluate when this Criteria is searched for * Appends the where expression to evaluate when this Criteria is searched for
* using an OR with previous expression. * using an OR with previous expression.
* *
* @return Criteria * @return $this
*/ */
public function orWhere(Expression $expression) public function orWhere(Expression $expression)
{ {
@ -161,7 +172,7 @@ class Criteria
* *
* @param string[] $orderings * @param string[] $orderings
* *
* @return Criteria * @return $this
*/ */
public function orderBy(array $orderings) public function orderBy(array $orderings)
{ {
@ -190,10 +201,19 @@ class Criteria
* *
* @param int|null $firstResult The value to set. * @param int|null $firstResult The value to set.
* *
* @return Criteria * @return $this
*/ */
public function setFirstResult($firstResult) public function setFirstResult($firstResult)
{ {
if ($firstResult === null) {
Deprecation::triggerIfCalledFromOutside(
'doctrine/collections',
'https://github.com/doctrine/collections/pull/311',
'Passing null to %s() is deprecated, pass 0 instead.',
__METHOD__
);
}
$this->firstResult = $firstResult; $this->firstResult = $firstResult;
return $this; return $this;
@ -214,7 +234,7 @@ class Criteria
* *
* @param int|null $maxResults The value to set. * @param int|null $maxResults The value to set.
* *
* @return Criteria * @return $this
*/ */
public function setMaxResults($maxResults) public function setMaxResults($maxResults)
{ {

View File

@ -6,6 +6,7 @@ use ArrayAccess;
use Closure; use Closure;
use RuntimeException; use RuntimeException;
use function explode;
use function in_array; use function in_array;
use function is_array; use function is_array;
use function is_scalar; use function is_scalar;
@ -38,11 +39,18 @@ class ClosureExpressionVisitor extends ExpressionVisitor
*/ */
public static function getObjectFieldValue($object, $field) public static function getObjectFieldValue($object, $field)
{ {
if (strpos($field, '.') !== false) {
[$field, $subField] = explode('.', $field, 2);
$object = self::getObjectFieldValue($object, $field);
return self::getObjectFieldValue($object, $subField);
}
if (is_array($object)) { if (is_array($object)) {
return $object[$field]; return $object[$field];
} }
$accessors = ['get', 'is']; $accessors = ['get', 'is', ''];
foreach ($accessors as $accessor) { foreach ($accessors as $accessor) {
$accessor .= $field; $accessor .= $field;
@ -231,9 +239,7 @@ class ClosureExpressionVisitor extends ExpressionVisitor
} }
} }
/** /** @param callable[] $expressions */
* @param callable[] $expressions
*/
private function andExpressions(array $expressions): callable private function andExpressions(array $expressions): callable
{ {
return static function ($object) use ($expressions): bool { return static function ($object) use ($expressions): bool {
@ -247,9 +253,7 @@ class ClosureExpressionVisitor extends ExpressionVisitor
}; };
} }
/** /** @param callable[] $expressions */
* @param callable[] $expressions
*/
private function orExpressions(array $expressions): callable private function orExpressions(array $expressions): callable
{ {
return static function ($object) use ($expressions): bool { return static function ($object) use ($expressions): bool {

View File

@ -46,25 +46,19 @@ class Comparison implements Expression
$this->value = $value; $this->value = $value;
} }
/** /** @return string */
* @return string
*/
public function getField() public function getField()
{ {
return $this->field; return $this->field;
} }
/** /** @return Value */
* @return Value
*/
public function getValue() public function getValue()
{ {
return $this->value; return $this->value;
} }
/** /** @return string */
* @return string
*/
public function getOperator() public function getOperator()
{ {
return $this->op; return $this->op;

View File

@ -51,9 +51,7 @@ class CompositeExpression implements Expression
return $this->expressions; return $this->expressions;
} }
/** /** @return string */
* @return string
*/
public function getType() public function getType()
{ {
return $this->type; return $this->type;

View File

@ -7,8 +7,6 @@ namespace Doctrine\Common\Collections\Expr;
*/ */
interface Expression interface Expression
{ {
/** /** @return mixed */
* @return mixed
*/
public function visit(ExpressionVisitor $visitor); public function visit(ExpressionVisitor $visitor);
} }

View File

@ -7,17 +7,13 @@ class Value implements Expression
/** @var mixed */ /** @var mixed */
private $value; private $value;
/** /** @param mixed $value */
* @param mixed $value
*/
public function __construct($value) public function __construct($value)
{ {
$this->value = $value; $this->value = $value;
} }
/** /** @return mixed */
* @return mixed
*/
public function getValue() public function getValue()
{ {
return $this->value; return $this->value;

View File

@ -0,0 +1,213 @@
<?php
namespace Doctrine\Common\Collections;
use Closure;
use Countable;
use IteratorAggregate;
/**
* @psalm-template TKey of array-key
* @template-covariant T
* @template-extends IteratorAggregate<TKey, T>
*/
interface ReadableCollection extends Countable, IteratorAggregate
{
/**
* Checks whether an element is contained in the collection.
* This is an O(n) operation, where n is the size of the collection.
*
* @param mixed $element The element to search for.
* @psalm-param TMaybeContained $element
*
* @return bool TRUE if the collection contains the element, FALSE otherwise.
* @psalm-return (TMaybeContained is T ? bool : false)
*
* @template TMaybeContained
*/
public function contains($element);
/**
* Checks whether the collection is empty (contains no elements).
*
* @return bool TRUE if the collection is empty, FALSE otherwise.
*/
public function isEmpty();
/**
* Checks whether the collection contains an element with the specified key/index.
*
* @param string|int $key The key/index to check for.
* @psalm-param TKey $key
*
* @return bool TRUE if the collection contains an element with the specified key/index,
* FALSE otherwise.
*/
public function containsKey($key);
/**
* Gets the element at the specified key/index.
*
* @param string|int $key The key/index of the element to retrieve.
* @psalm-param TKey $key
*
* @return mixed
* @psalm-return T|null
*/
public function get($key);
/**
* Gets all keys/indices of the collection.
*
* @return int[]|string[] The keys/indices of the collection, in the order of the corresponding
* elements in the collection.
* @psalm-return list<TKey>
*/
public function getKeys();
/**
* Gets all values of the collection.
*
* @return mixed[] The values of all elements in the collection, in the
* order they appear in the collection.
* @psalm-return list<T>
*/
public function getValues();
/**
* Gets a native PHP array representation of the collection.
*
* @return mixed[]
* @psalm-return array<TKey,T>
*/
public function toArray();
/**
* Sets the internal iterator to the first element in the collection and returns this element.
*
* @return mixed
* @psalm-return T|false
*/
public function first();
/**
* Sets the internal iterator to the last element in the collection and returns this element.
*
* @return mixed
* @psalm-return T|false
*/
public function last();
/**
* Gets the key/index of the element at the current iterator position.
*
* @return int|string|null
* @psalm-return TKey|null
*/
public function key();
/**
* Gets the element of the collection at the current iterator position.
*
* @return mixed
* @psalm-return T|false
*/
public function current();
/**
* Moves the internal iterator position to the next element and returns this element.
*
* @return mixed
* @psalm-return T|false
*/
public function next();
/**
* Extracts a slice of $length elements starting at position $offset from the Collection.
*
* If $length is null it returns all elements from $offset to the end of the Collection.
* Keys have to be preserved by this method. Calling this method will only return the
* selected slice and NOT change the elements contained in the collection slice is called on.
*
* @param int $offset The offset to start from.
* @param int|null $length The maximum number of elements to return, or null for no limit.
*
* @return mixed[]
* @psalm-return array<TKey,T>
*/
public function slice($offset, $length = null);
/**
* Tests for the existence of an element that satisfies the given predicate.
*
* @param Closure $p The predicate.
* @psalm-param Closure(TKey, T):bool $p
*
* @return bool TRUE if the predicate is TRUE for at least one element, FALSE otherwise.
*/
public function exists(Closure $p);
/**
* Returns all the elements of this collection that satisfy the predicate p.
* The order of the elements is preserved.
*
* @param Closure $p The predicate used for filtering.
* @psalm-param Closure(T):bool $p
*
* @return ReadableCollection<mixed> A collection with the results of the filter operation.
* @psalm-return ReadableCollection<TKey, T>
*/
public function filter(Closure $p);
/**
* Applies the given function to each element in the collection and returns
* a new collection with the elements returned by the function.
*
* @psalm-param Closure(T):U $func
*
* @return Collection<mixed>
* @psalm-return Collection<TKey, U>
*
* @psalm-template U
*/
public function map(Closure $func);
/**
* Partitions this collection in two collections according to a predicate.
* Keys are preserved in the resulting collections.
*
* @param Closure $p The predicate on which to partition.
* @psalm-param Closure(TKey, T):bool $p
*
* @return ReadableCollection<mixed>[] An array with two elements. The first element contains the collection
* of elements where the predicate returned TRUE, the second element
* contains the collection of elements where the predicate returned FALSE.
* @psalm-return array{0: ReadableCollection<TKey, T>, 1: ReadableCollection<TKey, T>}
*/
public function partition(Closure $p);
/**
* Tests whether the given predicate p holds for all elements of this collection.
*
* @param Closure $p The predicate.
* @psalm-param Closure(TKey, T):bool $p
*
* @return bool TRUE, if the predicate yields TRUE for all elements, FALSE otherwise.
*/
public function forAll(Closure $p);
/**
* Gets the index/key of a given element. The comparison of two elements is strict,
* that means not only the value but also the type must match.
* For objects this means reference equality.
*
* @param mixed $element The element to search for.
* @psalm-param TMaybeContained $element
*
* @return int|string|bool The key/index of the element or FALSE if the element was not found.
* @psalm-return (TMaybeContained is T ? TKey|false : false)
*
* @template TMaybeContained
*/
public function indexOf($element);
}

View File

@ -23,8 +23,8 @@ interface Selectable
* Selects all elements from a selectable that match the expression and * Selects all elements from a selectable that match the expression and
* returns a new collection containing these elements. * returns a new collection containing these elements.
* *
* @return Collection<mixed> * @return Collection<mixed>&Selectable<mixed>
* @psalm-return Collection<TKey,T> * @psalm-return Collection<TKey,T>&Selectable<TKey,T>
*/ */
public function matching(Criteria $criteria); public function matching(Criteria $criteria);
} }

View File

@ -1,17 +0,0 @@
parameters:
level: 3
paths:
- lib
ignoreErrors:
# Making classes final as suggested would be a BC-break
-
message: '~Unsafe usage of new static\(\)\.~'
paths:
- 'lib/Doctrine/Common/Collections/ArrayCollection.php'
- 'lib/Doctrine/Common/Collections/Criteria.php'
-
message: '~Array \(array\<TKey of \(int\|string\), T\>\) does not accept key int\.~'
path: 'lib/Doctrine/Common/Collections/ArrayCollection.php'
# This class is new in PHP 8.1 and PHPStan does not know it yet.
- '/Attribute class ReturnTypeWillChange does not exist./'

View File

@ -1,65 +0,0 @@
<?xml version="1.0"?>
<psalm
totallyTyped="false"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="lib" />
<ignoreFiles>
<directory name="vendor" />
<directory name="lib/Doctrine/Common/Collections/Expr"/>
</ignoreFiles>
</projectFiles>
<issueHandlers>
<LessSpecificReturnType errorLevel="info" />
<!-- level 3 issues - slightly lazy code writing, but provably low false-negatives -->
<DeprecatedMethod errorLevel="info" />
<DeprecatedProperty errorLevel="info" />
<DeprecatedClass errorLevel="info" />
<DeprecatedConstant errorLevel="info" />
<DeprecatedInterface errorLevel="info" />
<DeprecatedTrait errorLevel="info" />
<InternalMethod errorLevel="info" />
<InternalProperty errorLevel="info" />
<InternalClass errorLevel="info" />
<MissingClosureReturnType errorLevel="info" />
<MissingReturnType errorLevel="info" />
<MissingPropertyType errorLevel="info" />
<InvalidDocblock errorLevel="info" />
<PropertyNotSetInConstructor errorLevel="info" />
<MissingConstructor errorLevel="info" />
<MissingClosureParamType errorLevel="info" />
<MissingParamType errorLevel="info" />
<RedundantCondition errorLevel="info" />
<DocblockTypeContradiction errorLevel="info" />
<RedundantConditionGivenDocblockType errorLevel="info" />
<UnresolvableInclude errorLevel="info" />
<RawObjectIteration errorLevel="info" />
<InvalidStringClass errorLevel="info" />
<UnsafeGenericInstantiation>
<errorLevel type="suppress">
<file name="lib/Doctrine/Common/Collections/ArrayCollection.php"/>
</errorLevel>
</UnsafeGenericInstantiation>
<UndefinedAttributeClass>
<errorLevel type="suppress">
<!-- This class is new in PHP 8.1 and Psalm does not know it yet. -->
<referencedClass name="ReturnTypeWillChange"/>
</errorLevel>
</UndefinedAttributeClass>
</issueHandlers>
</psalm>

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.

View File

@ -0,0 +1,154 @@
# 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.
```php
\Doctrine\Deprecations\Deprecation::enableWithTriggerError();
```
If you only want to enable deprecation tracking, without logging or calling `trigger_error` then 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();
}
}
```
## 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,32 @@
{
"name": "doctrine/deprecations",
"type": "library",
"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.",
"homepage": "https://www.doctrine-project.org/",
"license": "MIT",
"require": {
"php": "^7.1|^8.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5|^8.5|^9.5",
"psr/log": "^1|^2|^3",
"doctrine/coding-standard": "^9"
},
"suggest": {
"psr/log": "Allows logging deprecations via PSR-3 logger implementation"
},
"autoload": {
"psr-4": {"Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"}
},
"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,266 @@
<?php
declare(strict_types=1);
namespace Doctrine\Deprecations;
use Psr\Log\LoggerInterface;
use function array_key_exists;
use function array_reduce;
use function debug_backtrace;
use function sprintf;
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 */
private static $type = self::TYPE_NONE;
/** @var LoggerInterface|null */
private static $logger;
/** @var array<string,bool> */
private static $ignoredPackages = [];
/** @var array<string,int> */
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 mixed $args
*/
public static function trigger(string $package, string $link, string $message, ...$args): void
{
if (self::$type === self::TYPE_NONE) {
return;
}
if (array_key_exists($link, self::$ignoredLinks)) {
self::$ignoredLinks[$link]++;
} else {
self::$ignoredLinks[$link] = 1;
}
if (self::$deduplication === true && self::$ignoredLinks[$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 mixed $args
*/
public static function triggerIfCalledFromOutside(string $package, string $link, string $message, ...$args): void
{
if (self::$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 (strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === false) {
$path = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $package . DIRECTORY_SEPARATOR;
if (strpos($backtrace[0]['file'], $path) === false) {
return;
}
if (strpos($backtrace[1]['file'], $path) !== false) {
return;
}
}
if (array_key_exists($link, self::$ignoredLinks)) {
self::$ignoredLinks[$link]++;
} else {
self::$ignoredLinks[$link] = 1;
}
if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) {
return;
}
if (isset(self::$ignoredPackages[$package])) {
return;
}
$message = sprintf($message, ...$args);
self::delegateTriggerToBackend($message, $backtrace, $link, $package);
}
/**
* @param array<mixed> $backtrace
*/
private static function delegateTriggerToBackend(string $message, array $backtrace, string $link, string $package): void
{
if ((self::$type & self::TYPE_PSR_LOGGER) > 0) {
$context = [
'file' => $backtrace[0]['file'],
'line' => $backtrace[0]['line'],
'package' => $package,
'link' => $link,
];
self::$logger->notice($message, $context);
}
if (! ((self::$type & self::TYPE_TRIGGER_ERROR) > 0)) {
return;
}
$message .= sprintf(
' (%s:%d called by %s:%d, %s, package %s)',
self::basename($backtrace[0]['file']),
$backtrace[0]['line'],
self::basename($backtrace[1]['file']),
$backtrace[1]['line'],
$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_TRACK_DEPRECATIONS;
}
public static function enableWithTriggerError(): void
{
self::$type |= self::TYPE_TRIGGER_ERROR;
}
public static function enableWithPsrLogger(LoggerInterface $logger): void
{
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;
foreach (self::$ignoredLinks as $link => $count) {
self::$ignoredLinks[$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] = 0;
}
}
public static function getUniqueTriggeredDeprecationsCount(): int
{
return array_reduce(self::$ignoredLinks, 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::$ignoredLinks;
}
}

View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Doctrine\Deprecations\PHPUnit;
use Doctrine\Deprecations\Deprecation;
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
*/
public function enableDeprecationTracking(): void
{
Deprecation::enableTrackingDeprecations();
}
/**
* @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
)
);
}
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<ruleset>
<arg name="basepath" value="."/>
<arg name="extensions" value="php"/>
<arg name="parallel" value="80"/>
<arg name="cache" value=".phpcs-cache"/>
<arg name="colors"/>
<!-- Ignore warnings, show progress of the run and show sniff names -->
<arg value="nps"/>
<config name="php_version" value="70100"/>
<!-- Directories to be checked -->
<file>lib</file>
<file>tests</file>
<!-- Include full Doctrine Coding Standard -->
<rule ref="Doctrine">
<exclude name="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint" />
</rule>
</ruleset>

View File

@ -5,14 +5,21 @@
"docsSlug": "doctrine-event-manager", "docsSlug": "doctrine-event-manager",
"versions": [ "versions": [
{ {
"name": "1.0", "name": "1.1",
"branchName": "master", "branchName": "1.1.x",
"slug": "latest", "slug": "latest",
"current": true, "current": true,
"aliases": [ "aliases": [
"current", "current",
"stable" "stable"
] ]
},
{
"name": "1.0",
"branchName": "1.0",
"slug": "1.0",
"current": false,
"maintained": false
} }
] ]
} }

View File

@ -1,6 +1,6 @@
# Doctrine Event Manager # Doctrine Event Manager
[![Build Status](https://travis-ci.org/doctrine/event-manager.svg)](https://travis-ci.org/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=master)](https://scrutinizer-ci.com/g/doctrine/event-manager/?branch=master) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/doctrine/event-manager/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/doctrine/event-manager/?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/doctrine/event-manager/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/doctrine/event-manager/?branch=master) [![Code Coverage](https://scrutinizer-ci.com/g/doctrine/event-manager/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/doctrine/event-manager/?branch=master)

View File

@ -23,13 +23,18 @@
"php": "^7.1 || ^8.0" "php": "^7.1 || ^8.0"
}, },
"require-dev": { "require-dev": {
"doctrine/coding-standard": "^6.0", "doctrine/coding-standard": "^9",
"phpunit/phpunit": "^7.0" "phpstan/phpstan": "~1.4.10 || ^1.5.4",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"vimeo/psalm": "^4.22"
}, },
"conflict": { "conflict": {
"doctrine/common": "<2.9@dev" "doctrine/common": "<2.9"
}, },
"config": { "config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
},
"sort-packages": true "sort-packages": true
}, },
"autoload": { "autoload": {
@ -41,10 +46,5 @@
"psr-4": { "psr-4": {
"Doctrine\\Tests\\": "tests/Doctrine/Tests" "Doctrine\\Tests\\": "tests/Doctrine/Tests"
} }
},
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
} }
} }

View File

@ -16,7 +16,7 @@ class EventArgs
/** /**
* Single instance of EventArgs. * Single instance of EventArgs.
* *
* @var EventArgs * @var EventArgs|null
*/ */
private static $_emptyEventArgsInstance; private static $_emptyEventArgsInstance;

View File

@ -48,6 +48,7 @@ class EventManager
* @param string|null $event The name of the event. * @param string|null $event The name of the event.
* *
* @return object[]|object[][] The event listeners for the specified event, or all event listeners. * @return object[]|object[][] The event listeners for the specified event, or all event listeners.
* @psalm-return ($event is null ? object[][] : object[])
*/ */
public function getListeners($event = null) public function getListeners($event = null)
{ {

View File

@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Doctrine\Common; namespace Doctrine\Common;
/** /**
* An EventSubscriber knows himself what events he is interested in. * An EventSubscriber knows what events it is interested in.
* If an EventSubscriber is added to an EventManager, the manager invokes * If an EventSubscriber is added to an EventManager, the manager invokes
* {@link getSubscribedEvents} and registers the subscriber as a listener for all * {@link getSubscribedEvents} and registers the subscriber as a listener for all
* returned events. * returned events.

View File

@ -0,0 +1,5 @@
parameters:
level: 9
paths:
- lib/
- tests/

View File

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<psalm
errorLevel="2"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="lib/Doctrine/Common" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>

View File

@ -1,5 +1,11 @@
# CHANGELOG # CHANGELOG
## 1.5.2 - 2022-08-07
### Changed
- Officially support PHP 8.2
## 1.5.1 - 2021-10-22 ## 1.5.1 - 2021-10-22
### Fixed ### Fixed
@ -12,10 +18,11 @@
### Changed ### Changed
- Call handler when waiting on fulfilled/rejected Promise - Call handler when waiting on fulfilled/rejected Promise
- Officially support PHP 8.1
### Fixed ### Fixed
- Fix manually settle promises generated with Utils::task - Fix manually settle promises generated with `Utils::task`
## 1.4.1 - 2021-02-18 ## 1.4.1 - 2021-02-18

View File

@ -1,13 +0,0 @@
all: clean test
test:
vendor/bin/phpunit
coverage:
vendor/bin/phpunit --coverage-html=artifacts/coverage
view-coverage:
open artifacts/coverage/index.html
clean:
rm -rf artifacts/*

View File

@ -17,7 +17,7 @@ for a general introduction to promises.
- [Implementation notes](#implementation-notes) - [Implementation notes](#implementation-notes)
# Features ## Features
- [Promises/A+](https://promisesaplus.com/) implementation. - [Promises/A+](https://promisesaplus.com/) implementation.
- Promise resolution and chaining is handled iteratively, allowing for - Promise resolution and chaining is handled iteratively, allowing for
@ -29,15 +29,14 @@ for a general introduction to promises.
`GuzzleHttp\Promise\Coroutine::of()`. `GuzzleHttp\Promise\Coroutine::of()`.
# Quick start ## Quick Start
A *promise* represents the eventual result of an asynchronous operation. The A *promise* represents the eventual result of an asynchronous operation. The
primary way of interacting with a promise is through its `then` method, which primary way of interacting with a promise is through its `then` method, which
registers callbacks to receive either a promise's eventual value or the reason registers callbacks to receive either a promise's eventual value or the reason
why the promise cannot be fulfilled. why the promise cannot be fulfilled.
### Callbacks
## Callbacks
Callbacks are registered with the `then` method by providing an optional Callbacks are registered with the `then` method by providing an optional
`$onFulfilled` followed by an optional `$onRejected` function. `$onFulfilled` followed by an optional `$onRejected` function.
@ -60,12 +59,11 @@ $promise->then(
``` ```
*Resolving* a promise means that you either fulfill a promise with a *value* or *Resolving* a promise means that you either fulfill a promise with a *value* or
reject a promise with a *reason*. Resolving a promises triggers callbacks reject a promise with a *reason*. Resolving a promise triggers callbacks
registered with the promises's `then` method. These callbacks are triggered registered with the promise's `then` method. These callbacks are triggered
only once and in the order in which they were added. only once and in the order in which they were added.
### Resolving a Promise
## Resolving a promise
Promises are fulfilled using the `resolve($value)` method. Resolving a promise Promises are fulfilled using the `resolve($value)` method. Resolving a promise
with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger
@ -92,8 +90,7 @@ $promise
$promise->resolve('reader.'); $promise->resolve('reader.');
``` ```
### Promise Forwarding
## Promise forwarding
Promises can be chained one after the other. Each then in the chain is a new Promises can be chained one after the other. Each then in the chain is a new
promise. The return value of a promise is what's forwarded to the next promise. The return value of a promise is what's forwarded to the next
@ -123,7 +120,7 @@ $promise->resolve('A');
$nextPromise->resolve('B'); $nextPromise->resolve('B');
``` ```
## Promise rejection ### Promise Rejection
When a promise is rejected, the `$onRejected` callbacks are invoked with the When a promise is rejected, the `$onRejected` callbacks are invoked with the
rejection reason. rejection reason.
@ -140,7 +137,7 @@ $promise->reject('Error!');
// Outputs "Error!" // Outputs "Error!"
``` ```
## Rejection forwarding ### Rejection Forwarding
If an exception is thrown in an `$onRejected` callback, subsequent If an exception is thrown in an `$onRejected` callback, subsequent
`$onRejected` callbacks are invoked with the thrown exception as the reason. `$onRejected` callbacks are invoked with the thrown exception as the reason.
@ -195,7 +192,8 @@ $promise
$promise->reject('Error!'); $promise->reject('Error!');
``` ```
# Synchronous wait
## Synchronous Wait
You can synchronously force promises to complete using a promise's `wait` You can synchronously force promises to complete using a promise's `wait`
method. When creating a promise, you can provide a wait function that is used method. When creating a promise, you can provide a wait function that is used
@ -247,8 +245,7 @@ $promise->wait();
> PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo' > PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo'
### Unwrapping a Promise
## Unwrapping a promise
When synchronously waiting on a promise, you are joining the state of the When synchronously waiting on a promise, you are joining the state of the
promise into the current state of execution (i.e., return the value of the promise into the current state of execution (i.e., return the value of the
@ -275,7 +272,7 @@ wait function will be the value delivered to promise B.
**Note**: when you do not unwrap the promise, no value is returned. **Note**: when you do not unwrap the promise, no value is returned.
# Cancellation ## Cancellation
You can cancel a promise that has not yet been fulfilled using the `cancel()` You can cancel a promise that has not yet been fulfilled using the `cancel()`
method of a promise. When creating a promise you can provide an optional method of a promise. When creating a promise you can provide an optional
@ -283,10 +280,9 @@ cancel function that when invoked cancels the action of computing a resolution
of the promise. of the promise.
# API ## API
### Promise
## Promise
When creating a promise object, you can provide an optional `$waitFn` and When creating a promise object, you can provide an optional `$waitFn` and
`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is `$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is
@ -349,7 +345,7 @@ A promise has the following methods:
Rejects the promise with the given `$reason`. Rejects the promise with the given `$reason`.
## FulfilledPromise ### FulfilledPromise
A fulfilled promise can be created to represent a promise that has been A fulfilled promise can be created to represent a promise that has been
fulfilled. fulfilled.
@ -366,7 +362,7 @@ $promise->then(function ($value) {
``` ```
## RejectedPromise ### RejectedPromise
A rejected promise can be created to represent a promise that has been A rejected promise can be created to represent a promise that has been
rejected. rejected.
@ -383,7 +379,7 @@ $promise->then(null, function ($reason) {
``` ```
# Promise interop ## Promise Interoperability
This library works with foreign promises that have a `then` method. This means This library works with foreign promises that have a `then` method. This means
you can use Guzzle promises with [React promises](https://github.com/reactphp/promise) you can use Guzzle promises with [React promises](https://github.com/reactphp/promise)
@ -409,7 +405,7 @@ a foreign promise. You will need to wrap a third-party promise with a Guzzle
promise in order to utilize wait and cancel functions with foreign promises. promise in order to utilize wait and cancel functions with foreign promises.
## Event Loop Integration ### Event Loop Integration
In order to keep the stack size constant, Guzzle promises are resolved In order to keep the stack size constant, Guzzle promises are resolved
asynchronously using a task queue. When waiting on promises synchronously, the asynchronously using a task queue. When waiting on promises synchronously, the
@ -437,10 +433,9 @@ $loop->addPeriodicTimer(0, [$queue, 'run']);
*TODO*: Perhaps adding a `futureTick()` on each tick would be faster? *TODO*: Perhaps adding a `futureTick()` on each tick would be faster?
# Implementation notes ## Implementation Notes
### Promise Resolution and Chaining is Handled Iteratively
## Promise resolution and chaining is handled iteratively
By shuffling pending handlers from one owner to another, promises are By shuffling pending handlers from one owner to another, promises are
resolved iteratively, allowing for "infinite" then chaining. resolved iteratively, allowing for "infinite" then chaining.
@ -476,8 +471,7 @@ all of its pending handlers to the new promise. When the new promise is
eventually resolved, all of the pending handlers are delivered the forwarded eventually resolved, all of the pending handlers are delivered the forwarded
value. value.
### A Promise is the Deferred
## A promise is the deferred.
Some promise libraries implement promises using a deferred object to represent Some promise libraries implement promises using a deferred object to represent
a computation and a promise object to represent the delivery of the result of a computation and a promise object to represent the delivery of the result of
@ -505,7 +499,10 @@ $promise->resolve('foo');
## Upgrading from Function API ## Upgrading from Function API
A static API was first introduced in 1.4.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API will be removed in 2.0.0. A migration table has been provided here for your convenience: A static API was first introduced in 1.4.0, in order to mitigate problems with
functions conflicting between global and local copies of the package. The
function API will be removed in 2.0.0. A migration table has been provided here
for your convenience:
| Original Function | Replacement Method | | Original Function | Replacement Method |
|----------------|----------------| |----------------|----------------|
@ -536,10 +533,12 @@ A static API was first introduced in 1.4.0, in order to mitigate problems with f
If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/promises/security/policy) for more information. If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/promises/security/policy) for more information.
## License ## License
Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information.
## For Enterprise ## For Enterprise
Available as part of the Tidelift Subscription Available as part of the Tidelift Subscription

View File

@ -81,16 +81,8 @@ class EachPromise implements PromisorInterface
$this->iterable->rewind(); $this->iterable->rewind();
$this->refillPending(); $this->refillPending();
} catch (\Throwable $e) { } catch (\Throwable $e) {
/**
* @psalm-suppress NullReference
* @phpstan-ignore-next-line
*/
$this->aggregate->reject($e); $this->aggregate->reject($e);
} catch (\Exception $e) { } catch (\Exception $e) {
/**
* @psalm-suppress NullReference
* @phpstan-ignore-next-line
*/
$this->aggregate->reject($e); $this->aggregate->reject($e);
} }

View File

@ -1,3 +1,19 @@
> # UKRAINE NEEDS YOUR HELP NOW!
>
> On 24 February 2022, Russian [President Vladimir Putin ordered an invasion of Ukraine by Russian Armed Forces](https://www.bbc.com/news/world-europe-60504334).
>
> Your support is urgently needed.
>
> - Donate to the volunteers. Here is the volunteer fund helping the Ukrainian army to provide all the necessary equipment:
> https://bank.gov.ua/en/news/all/natsionalniy-bank-vidkriv-spetsrahunok-dlya-zboru-koshtiv-na-potrebi-armiyi or https://savelife.in.ua/en/donate/
> - Triple-check social media sources. Russian disinformation is attempting to coverup and distort the reality in Ukraine.
> - Help Ukrainian refugees who are fleeing Russian attacks and shellings: https://www.globalcitizen.org/en/content/ways-to-help-ukraine-conflict/
> - Put pressure on your political representatives to provide help to Ukraine.
> - Believe in the Ukrainian people, they will not surrender, they don't have another Ukraine.
>
> THANK YOU!
----
# HTML5-PHP # HTML5-PHP
HTML5 is a standards-compliant HTML5 parser and writer written entirely in PHP. HTML5 is a standards-compliant HTML5 parser and writer written entirely in PHP.

View File

@ -1,5 +1,9 @@
# Release Notes # Release Notes
2.7.6 (2021-08-18)
- #218: Address comment handling issues
2.7.5 (2021-07-01) 2.7.5 (2021-07-01)
- #204: Travis: Enable tests on PHP 8.0 - #204: Travis: Enable tests on PHP 8.0

View File

@ -104,7 +104,7 @@ class Scanner
*/ */
public function peek() public function peek()
{ {
if (($this->char + 1) <= $this->EOF) { if (($this->char + 1) < $this->EOF) {
return $this->data[$this->char + 1]; return $this->data[$this->char + 1];
} }

View File

@ -712,18 +712,24 @@ class Tokenizer
return true; return true;
} }
// If it doesn't start with -, not the end. // If next two tokens are not '--', not the end.
if ('-' != $tok) { if ('-' != $tok || '-' != $this->scanner->peek()) {
return false; return false;
} }
// Advance one, and test for '->' $this->scanner->consume(2); // Consume '-' and one of '!' or '>'
if ('-' == $this->scanner->next() && '>' == $this->scanner->peek()) {
// Test for '>'
if ('>' == $this->scanner->current()) {
return true;
}
// Test for '!>'
if ('!' == $this->scanner->current() && '>' == $this->scanner->peek()) {
$this->scanner->consume(); // Consume the last '>' $this->scanner->consume(); // Consume the last '>'
return true; return true;
} }
// Unread '-'; // Unread '-' and one of '!' or '>';
$this->scanner->unconsume(1); $this->scanner->unconsume(2);
return false; return false;
} }

View File

@ -195,7 +195,7 @@ class Command
* *
* @return int The command exit code * @return int The command exit code
* *
* @throws \Exception When binding input fails. Bypass this by calling {@link ignoreValidationErrors()}. * @throws ExceptionInterface When input binding fails. Bypass this by calling {@link ignoreValidationErrors()}.
* *
* @see setCode() * @see setCode()
* @see execute() * @see execute()

View File

@ -55,7 +55,7 @@ class AddConsoleCommandPass implements CompilerPassInterface
if (!$r->isSubclassOf(Command::class)) { if (!$r->isSubclassOf(Command::class)) {
throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class));
} }
$commandName = $class::getDefaultName() !== null ? str_replace('%', '%%', $class::getDefaultName()) : null; $commandName = null !== $class::getDefaultName() ? str_replace('%', '%%', $class::getDefaultName()) : null;
} }
if (null === $commandName) { if (null === $commandName) {

View File

@ -131,7 +131,7 @@ class ApplicationDescription
} }
if ($namespacedCommands) { if ($namespacedCommands) {
ksort($namespacedCommands); ksort($namespacedCommands, \SORT_STRING);
foreach ($namespacedCommands as $key => $commandsSet) { foreach ($namespacedCommands as $key => $commandsSet) {
ksort($commandsSet); ksort($commandsSet);
$sortedCommands[$key] = $commandsSet; $sortedCommands[$key] = $commandsSet;

View File

@ -641,7 +641,7 @@ class Table
{ {
$unmergedRows = []; $unmergedRows = [];
foreach ($rows[$line] as $column => $cell) { foreach ($rows[$line] as $column => $cell) {
if (null !== $cell && !$cell instanceof TableCell && !is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) { if (null !== $cell && !$cell instanceof TableCell && !\is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) {
throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', \gettype($cell))); throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', \gettype($cell)));
} }
if ($cell instanceof TableCell && $cell->getRowspan() > 1) { if ($cell instanceof TableCell && $cell->getRowspan() > 1) {

View File

@ -110,7 +110,7 @@ class ConsoleLogger extends AbstractLogger
$replacements = []; $replacements = [];
foreach ($context as $key => $val) { foreach ($context as $key => $val) {
if (null === $val || is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) { if (null === $val || \is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) {
$replacements["{{$key}}"] = $val; $replacements["{{$key}}"] = $val;
} elseif ($val instanceof \DateTimeInterface) { } elseif ($val instanceof \DateTimeInterface) {
$replacements["{{$key}}"] = $val->format(\DateTime::RFC3339); $replacements["{{$key}}"] = $val->format(\DateTime::RFC3339);

View File

@ -49,7 +49,7 @@ class Debug
if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
ini_set('display_errors', 0); ini_set('display_errors', 0);
ExceptionHandler::register(); ExceptionHandler::register();
} elseif ($displayErrors && (!filter_var(ini_get('log_errors'), \FILTER_VALIDATE_BOOLEAN) || ini_get('error_log'))) { } elseif ($displayErrors && (!filter_var(\ini_get('log_errors'), \FILTER_VALIDATE_BOOLEAN) || \ini_get('error_log'))) {
// CLI - display errors only if they're not already logged to STDERR // CLI - display errors only if they're not already logged to STDERR
ini_set('display_errors', 1); ini_set('display_errors', 1);
} }

View File

@ -354,8 +354,8 @@ class ErrorHandler
*/ */
private function reRegister(int $prev) private function reRegister(int $prev)
{ {
if ($prev !== $this->thrownErrors | $this->loggedErrors) { if ($prev !== ($this->thrownErrors | $this->loggedErrors)) {
$handler = set_error_handler('var_dump'); $handler = set_error_handler('is_int');
$handler = \is_array($handler) ? $handler[0] : null; $handler = \is_array($handler) ? $handler[0] : null;
restore_error_handler(); restore_error_handler();
if ($handler === $this) { if ($handler === $this) {
@ -490,7 +490,7 @@ class ErrorHandler
$log = 0; $log = 0;
} else { } else {
if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404)) { if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404)) {
$currentErrorHandler = set_error_handler('var_dump'); $currentErrorHandler = set_error_handler('is_int');
restore_error_handler(); restore_error_handler();
} }
@ -601,7 +601,7 @@ class ErrorHandler
$sameHandlerLimit = 10; $sameHandlerLimit = 10;
while (!\is_array($handler) || !$handler[0] instanceof self) { while (!\is_array($handler) || !$handler[0] instanceof self) {
$handler = set_exception_handler('var_dump'); $handler = set_exception_handler('is_int');
restore_exception_handler(); restore_exception_handler();
if (!$handler) { if (!$handler) {

View File

@ -55,7 +55,7 @@ class ExceptionHandler
public function __construct(bool $debug = true, string $charset = null, $fileLinkFormat = null) public function __construct(bool $debug = true, string $charset = null, $fileLinkFormat = null)
{ {
$this->debug = $debug; $this->debug = $debug;
$this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8'; $this->charset = $charset ?: \ini_get('default_charset') ?: 'UTF-8';
$this->fileLinkFormat = $fileLinkFormat; $this->fileLinkFormat = $fileLinkFormat;
} }
@ -390,7 +390,7 @@ EOF;
private function formatPath(string $path, int $line): string private function formatPath(string $path, int $line): string
{ {
$file = $this->escapeHtml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path); $file = $this->escapeHtml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path);
$fmt = $this->fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); $fmt = $this->fileLinkFormat ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
if (!$fmt) { if (!$fmt) {
return sprintf('<span class="block trace-file-path">in <span title="%s%3$s"><strong>%s</strong>%s</span></span>', $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : ''); return sprintf('<span class="block trace-file-path">in <span title="%s%3$s"><strong>%s</strong>%s</span></span>', $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : '');

View File

@ -63,7 +63,7 @@ class CheckDefinitionValidityPass implements CompilerPassInterface
foreach ($definition->getTags() as $name => $tags) { foreach ($definition->getTags() as $name => $tags) {
foreach ($tags as $attributes) { foreach ($tags as $attributes) {
foreach ($attributes as $attribute => $value) { foreach ($attributes as $attribute => $value) {
if (!is_scalar($value) && null !== $value) { if (!\is_scalar($value) && null !== $value) {
throw new RuntimeException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s".', $id, $name, $attribute)); throw new RuntimeException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s".', $id, $name, $attribute));
} }
} }

View File

@ -688,13 +688,13 @@ EOF;
if ($v instanceof Reference && $this->container->has($id = (string) $v) && $this->container->findDefinition($id)->isSynthetic()) { if ($v instanceof Reference && $this->container->has($id = (string) $v) && $this->container->findDefinition($id)->isSynthetic()) {
continue; continue;
} }
if (!is_scalar($v) || $this->dumpValue($v) !== $this->dumpValue($v, false)) { if (!\is_scalar($v) || $this->dumpValue($v) !== $this->dumpValue($v, false)) {
return false; return false;
} }
} }
} elseif ($arg instanceof Reference && $this->container->has($id = (string) $arg) && $this->container->findDefinition($id)->isSynthetic()) { } elseif ($arg instanceof Reference && $this->container->has($id = (string) $arg) && $this->container->findDefinition($id)->isSynthetic()) {
continue; continue;
} elseif (!is_scalar($arg) || $this->dumpValue($arg) !== $this->dumpValue($arg, false)) { } elseif (!\is_scalar($arg) || $this->dumpValue($arg) !== $this->dumpValue($arg, false)) {
return false; return false;
} }
} }

View File

@ -111,7 +111,7 @@ class EnvVarProcessor implements EnvVarProcessorInterface
} }
if ('file' === $prefix || 'require' === $prefix) { if ('file' === $prefix || 'require' === $prefix) {
if (!is_scalar($file = $getEnv($name))) { if (!\is_scalar($file = $getEnv($name))) {
throw new RuntimeException(sprintf('Invalid file name: env var "%s" is non-scalar.', $name)); throw new RuntimeException(sprintf('Invalid file name: env var "%s" is non-scalar.', $name));
} }
if (!file_exists($file)) { if (!file_exists($file)) {
@ -183,7 +183,7 @@ class EnvVarProcessor implements EnvVarProcessorInterface
return null; return null;
} }
if (!is_scalar($env)) { if (!\is_scalar($env)) {
throw new RuntimeException(sprintf('Non-scalar env var "%s" cannot be cast to "%s".', $name, $prefix)); throw new RuntimeException(sprintf('Non-scalar env var "%s" cannot be cast to "%s".', $name, $prefix));
} }
@ -280,7 +280,7 @@ class EnvVarProcessor implements EnvVarProcessorInterface
$value = $this->container->getParameter($match[1]); $value = $this->container->getParameter($match[1]);
} }
if (!is_scalar($value)) { if (!\is_scalar($value)) {
throw new RuntimeException(sprintf('Parameter "%s" found when resolving env var "%s" must be scalar, "%s" given.', $match[1], $name, \gettype($value))); throw new RuntimeException(sprintf('Parameter "%s" found when resolving env var "%s" must be scalar, "%s" given.', $match[1], $name, \gettype($value)));
} }

View File

@ -32,6 +32,11 @@ class ProxyHelper
return null; return null;
} }
return self::getTypeHintForType($type, $r, $noBuiltin);
}
private static function getTypeHintForType(\ReflectionType $type, \ReflectionFunctionAbstract $r, bool $noBuiltin): ?string
{
$types = []; $types = [];
$glue = '|'; $glue = '|';
if ($type instanceof \ReflectionUnionType) { if ($type instanceof \ReflectionUnionType) {
@ -46,6 +51,17 @@ class ProxyHelper
} }
foreach ($reflectionTypes as $type) { foreach ($reflectionTypes as $type) {
if ($type instanceof \ReflectionIntersectionType) {
$typeHint = self::getTypeHintForType($type, $r, $noBuiltin);
if (null === $typeHint) {
return null;
}
$types[] = sprintf('(%s)', $typeHint);
continue;
}
if ($type->isBuiltin()) { if ($type->isBuiltin()) {
if (!$noBuiltin) { if (!$noBuiltin) {
$types[] = $type->getName(); $types[] = $type->getName();

View File

@ -82,7 +82,7 @@ abstract class AbstractConfigurator
switch (true) { switch (true) {
case null === $value: case null === $value:
case is_scalar($value): case \is_scalar($value):
return $value; return $value;
case $value instanceof ArgumentInterface: case $value instanceof ArgumentInterface:

View File

@ -49,7 +49,7 @@ class DefaultsConfigurator extends AbstractServiceConfigurator
} }
foreach ($attributes as $attribute => $value) { foreach ($attributes as $attribute => $value) {
if (null !== $value && !is_scalar($value)) { if (null !== $value && !\is_scalar($value)) {
throw new InvalidArgumentException(sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type.', $name, $attribute)); throw new InvalidArgumentException(sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type.', $name, $attribute));
} }
} }

View File

@ -27,7 +27,7 @@ trait TagTrait
} }
foreach ($attributes as $attribute => $value) { foreach ($attributes as $attribute => $value) {
if (!is_scalar($value) && null !== $value) { if (!\is_scalar($value) && null !== $value) {
throw new InvalidArgumentException(sprintf('A tag attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s".', $this->id, $name, $attribute)); throw new InvalidArgumentException(sprintf('A tag attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s".', $this->id, $name, $attribute));
} }
} }

View File

@ -676,7 +676,7 @@ EOF
}); });
$schema = '<?xml version="1.0" encoding="utf-8"?> $schema = '<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:include schemaLocation="file:///'.str_replace('\\', '/', $tmpfile).'" /> <xsd:include schemaLocation="file:///'.rawurlencode(str_replace('\\', '/', $tmpfile)).'" />
</xsd:schema>'; </xsd:schema>';
file_put_contents($tmpfile, '<?xml version="1.0" encoding="utf-8"?> file_put_contents($tmpfile, '<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">

View File

@ -277,7 +277,7 @@ class YamlFileLoader extends FileLoader
} }
foreach ($tag as $attribute => $value) { foreach ($tag as $attribute => $value) {
if (!is_scalar($value) && null !== $value) { if (!\is_scalar($value) && null !== $value) {
throw new InvalidArgumentException(sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type in "%s". Check your YAML syntax.', $name, $attribute, $file)); throw new InvalidArgumentException(sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type in "%s". Check your YAML syntax.', $name, $attribute, $file));
} }
} }
@ -534,7 +534,7 @@ class YamlFileLoader extends FileLoader
} }
foreach ($tag as $attribute => $value) { foreach ($tag as $attribute => $value) {
if (!is_scalar($value) && null !== $value) { if (!\is_scalar($value) && null !== $value) {
throw new InvalidArgumentException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in "%s". Check your YAML syntax.', $id, $name, $attribute, $file)); throw new InvalidArgumentException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in "%s". Check your YAML syntax.', $id, $name, $attribute, $file));
} }
} }

View File

@ -51,10 +51,10 @@ class EnvPlaceholderParameterBag extends ParameterBag
if ($this->has($name)) { if ($this->has($name)) {
$defaultValue = parent::get($name); $defaultValue = parent::get($name);
if (null !== $defaultValue && !is_scalar($defaultValue)) { // !is_string in 5.0 if (null !== $defaultValue && !\is_scalar($defaultValue)) { // !is_string in 5.0
// throw new RuntimeException(sprintf('The default value of an env() parameter must be a string or null, but "%s" given to "%s".', \gettype($defaultValue), $name)); // throw new RuntimeException(sprintf('The default value of an env() parameter must be a string or null, but "%s" given to "%s".', \gettype($defaultValue), $name));
throw new RuntimeException(sprintf('The default value of an env() parameter must be scalar or null, but "%s" given to "%s".', \gettype($defaultValue), $name)); throw new RuntimeException(sprintf('The default value of an env() parameter must be scalar or null, but "%s" given to "%s".', \gettype($defaultValue), $name));
} elseif (is_scalar($defaultValue) && !\is_string($defaultValue)) { } elseif (\is_scalar($defaultValue) && !\is_string($defaultValue)) {
@trigger_error(sprintf('A non-string default value of an env() parameter is deprecated since 4.3, cast "%s" to string instead.', $name), \E_USER_DEPRECATED); @trigger_error(sprintf('A non-string default value of an env() parameter is deprecated since 4.3, cast "%s" to string instead.', $name), \E_USER_DEPRECATED);
} }
} }
@ -162,10 +162,10 @@ class EnvPlaceholderParameterBag extends ParameterBag
@trigger_error(sprintf('A non-string default value of env parameter "%s" is deprecated since 4.3, cast it to string instead.', $env), \E_USER_DEPRECATED); @trigger_error(sprintf('A non-string default value of env parameter "%s" is deprecated since 4.3, cast it to string instead.', $env), \E_USER_DEPRECATED);
} }
$this->parameters[$name] = (string) $default; $this->parameters[$name] = (string) $default;
} elseif (null !== $default && !is_scalar($default)) { // !is_string in 5.0 } elseif (null !== $default && !\is_scalar($default)) { // !is_string in 5.0
// throw new RuntimeException(sprintf('The default value of env parameter "%s" must be a string or null, "%s" given.', $env, \gettype($default))); // throw new RuntimeException(sprintf('The default value of env parameter "%s" must be a string or null, "%s" given.', $env, \gettype($default)));
throw new RuntimeException(sprintf('The default value of env parameter "%s" must be scalar or null, "%s" given.', $env, \gettype($default))); throw new RuntimeException(sprintf('The default value of env parameter "%s" must be scalar or null, "%s" given.', $env, \gettype($default)));
} elseif (is_scalar($default) && !\is_string($default)) { } elseif (\is_scalar($default) && !\is_string($default)) {
@trigger_error(sprintf('A non-string default value of env parameter "%s" is deprecated since 4.3, cast it to string instead.', $env), \E_USER_DEPRECATED); @trigger_error(sprintf('A non-string default value of env parameter "%s" is deprecated since 4.3, cast it to string instead.', $env), \E_USER_DEPRECATED);
} }
} }

View File

@ -53,7 +53,7 @@ class BufferingLogger extends AbstractLogger
foreach ($this->logs as [$level, $message, $context]) { foreach ($this->logs as [$level, $message, $context]) {
if (false !== strpos($message, '{')) { if (false !== strpos($message, '{')) {
foreach ($context as $key => $val) { foreach ($context as $key => $val) {
if (null === $val || is_scalar($val) || (\is_object($val) && \is_callable([$val, '__toString']))) { if (null === $val || \is_scalar($val) || (\is_object($val) && \is_callable([$val, '__toString']))) {
$message = str_replace("{{$key}}", $val, $message); $message = str_replace("{{$key}}", $val, $message);
} elseif ($val instanceof \DateTimeInterface) { } elseif ($val instanceof \DateTimeInterface) {
$message = str_replace("{{$key}}", $val->format(\DateTime::RFC3339), $message); $message = str_replace("{{$key}}", $val->format(\DateTime::RFC3339), $message);

View File

@ -24,7 +24,7 @@ class Debug
if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
ini_set('display_errors', 0); ini_set('display_errors', 0);
} elseif (!filter_var(ini_get('log_errors'), \FILTER_VALIDATE_BOOLEAN) || ini_get('error_log')) { } elseif (!filter_var(\ini_get('log_errors'), \FILTER_VALIDATE_BOOLEAN) || \ini_get('error_log')) {
// CLI - display errors only if they're not already logged to STDERR // CLI - display errors only if they're not already logged to STDERR
ini_set('display_errors', 1); ini_set('display_errors', 1);
} }

View File

@ -70,6 +70,8 @@ class DebugClassLoader
'self' => 'self', 'self' => 'self',
'parent' => 'parent', 'parent' => 'parent',
'mixed' => 'mixed', 'mixed' => 'mixed',
'list' => 'array',
'class-string' => 'string',
] + (\PHP_VERSION_ID >= 80000 ? [ ] + (\PHP_VERSION_ID >= 80000 ? [
'static' => 'static', 'static' => 'static',
'$this' => 'static', '$this' => 'static',

View File

@ -374,8 +374,8 @@ class ErrorHandler
*/ */
private function reRegister(int $prev): void private function reRegister(int $prev): void
{ {
if ($prev !== $this->thrownErrors | $this->loggedErrors) { if ($prev !== ($this->thrownErrors | $this->loggedErrors)) {
$handler = set_error_handler('var_dump'); $handler = set_error_handler('is_int');
$handler = \is_array($handler) ? $handler[0] : null; $handler = \is_array($handler) ? $handler[0] : null;
restore_error_handler(); restore_error_handler();
if ($handler === $this) { if ($handler === $this) {
@ -522,7 +522,7 @@ class ErrorHandler
$log = 0; $log = 0;
} else { } else {
if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404)) { if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404)) {
$currentErrorHandler = set_error_handler('var_dump'); $currentErrorHandler = set_error_handler('is_int');
restore_error_handler(); restore_error_handler();
} }
@ -639,7 +639,7 @@ class ErrorHandler
$sameHandlerLimit = 10; $sameHandlerLimit = 10;
while (!\is_array($handler) || !$handler[0] instanceof self) { while (!\is_array($handler) || !$handler[0] instanceof self) {
$handler = set_exception_handler('var_dump'); $handler = set_exception_handler('is_int');
restore_exception_handler(); restore_exception_handler();
if (!$handler) { if (!$handler) {

View File

@ -56,8 +56,8 @@ class HtmlErrorRenderer implements ErrorRendererInterface
} }
$this->debug = $debug; $this->debug = $debug;
$this->charset = $charset ?: (ini_get('default_charset') ?: 'UTF-8'); $this->charset = $charset ?: (\ini_get('default_charset') ?: 'UTF-8');
$this->fileLinkFormat = $fileLinkFormat ?: (ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format')); $this->fileLinkFormat = $fileLinkFormat ?: (\ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'));
$this->projectDir = $projectDir; $this->projectDir = $projectDir;
$this->outputBuffer = $outputBuffer; $this->outputBuffer = $outputBuffer;
$this->logger = $logger; $this->logger = $logger;
@ -319,7 +319,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
if ($context && false !== strpos($message, '{')) { if ($context && false !== strpos($message, '{')) {
$replacements = []; $replacements = [];
foreach ($context as $key => $val) { foreach ($context as $key => $val) {
if (is_scalar($val)) { if (\is_scalar($val)) {
$replacements['{'.$key.'}'] = $val; $replacements['{'.$key.'}'] = $val;
} }
} }

View File

@ -34,7 +34,7 @@ class ImmutableEventDispatcher implements EventDispatcherInterface
{ {
$eventName = 1 < \func_num_args() ? func_get_arg(1) : null; $eventName = 1 < \func_num_args() ? func_get_arg(1) : null;
if (is_scalar($event)) { if (\is_scalar($event)) {
// deprecated // deprecated
$swap = $event; $swap = $event;
$event = $eventName ?? new Event(); $event = $eventName ?? new Event();

View File

@ -34,6 +34,7 @@ class BinaryFileResponse extends Response
protected $offset = 0; protected $offset = 0;
protected $maxlen = -1; protected $maxlen = -1;
protected $deleteFileAfterSend = false; protected $deleteFileAfterSend = false;
protected $chunkSize = 8 * 1024;
/** /**
* @param \SplFileInfo|string $file The file to stream * @param \SplFileInfo|string $file The file to stream
@ -124,6 +125,22 @@ class BinaryFileResponse extends Response
return $this->file; return $this->file;
} }
/**
* Sets the response stream chunk size.
*
* @return $this
*/
public function setChunkSize(int $chunkSize): self
{
if ($chunkSize < 1 || $chunkSize > \PHP_INT_MAX) {
throw new \LogicException('The chunk size of a BinaryFileResponse cannot be less than 1 or greater than PHP_INT_MAX.');
}
$this->chunkSize = $chunkSize;
return $this;
}
/** /**
* Automatically sets the Last-Modified header according the file modification date. * Automatically sets the Last-Modified header according the file modification date.
*/ */
@ -184,22 +201,25 @@ class BinaryFileResponse extends Response
*/ */
public function prepare(Request $request) public function prepare(Request $request)
{ {
parent::prepare($request);
if ($this->isInformational() || $this->isEmpty()) {
$this->maxlen = 0;
return $this;
}
if (!$this->headers->has('Content-Type')) { if (!$this->headers->has('Content-Type')) {
$this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream'); $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream');
} }
if ('HTTP/1.0' !== $request->server->get('SERVER_PROTOCOL')) {
$this->setProtocolVersion('1.1');
}
$this->ensureIEOverSSLCompatibility($request);
$this->offset = 0; $this->offset = 0;
$this->maxlen = -1; $this->maxlen = -1;
if (false === $fileSize = $this->file->getSize()) { if (false === $fileSize = $this->file->getSize()) {
return $this; return $this;
} }
$this->headers->remove('Transfer-Encoding');
$this->headers->set('Content-Length', $fileSize); $this->headers->set('Content-Length', $fileSize);
if (!$this->headers->has('Accept-Ranges')) { if (!$this->headers->has('Accept-Ranges')) {
@ -269,6 +289,10 @@ class BinaryFileResponse extends Response
} }
} }
if ($request->isMethod('HEAD')) {
$this->maxlen = 0;
}
return $this; return $this;
} }
@ -292,6 +316,7 @@ class BinaryFileResponse extends Response
*/ */
public function sendContent() public function sendContent()
{ {
try {
if (!$this->isSuccessful()) { if (!$this->isSuccessful()) {
return parent::sendContent(); return parent::sendContent();
} }
@ -303,14 +328,31 @@ class BinaryFileResponse extends Response
$out = fopen('php://output', 'w'); $out = fopen('php://output', 'w');
$file = fopen($this->file->getPathname(), 'r'); $file = fopen($this->file->getPathname(), 'r');
stream_copy_to_stream($file, $out, $this->maxlen, $this->offset); ignore_user_abort(true);
if (0 !== $this->offset) {
fseek($file, $this->offset);
}
$length = $this->maxlen;
while ($length && !feof($file)) {
$read = ($length > $this->chunkSize) ? $this->chunkSize : $length;
$length -= $read;
stream_copy_to_stream($file, $out, $read);
if (connection_aborted()) {
break;
}
}
fclose($out); fclose($out);
fclose($file); fclose($file);
} finally {
if ($this->deleteFileAfterSend && file_exists($this->file->getPathname())) { if ($this->deleteFileAfterSend && file_exists($this->file->getPathname())) {
unlink($this->file->getPathname()); unlink($this->file->getPathname());
} }
}
return $this; return $this;
} }

View File

@ -243,8 +243,8 @@ class UploadedFile extends File
*/ */
public static function getMaxFilesize() public static function getMaxFilesize()
{ {
$sizePostMax = self::parseFilesize(ini_get('post_max_size')); $sizePostMax = self::parseFilesize(\ini_get('post_max_size'));
$sizeUploadMax = self::parseFilesize(ini_get('upload_max_filesize')); $sizeUploadMax = self::parseFilesize(\ini_get('upload_max_filesize'));
return min($sizePostMax ?: \PHP_INT_MAX, $sizeUploadMax ?: \PHP_INT_MAX); return min($sizePostMax ?: \PHP_INT_MAX, $sizeUploadMax ?: \PHP_INT_MAX);
} }
@ -273,8 +273,11 @@ class UploadedFile extends File
switch (substr($size, -1)) { switch (substr($size, -1)) {
case 't': $max *= 1024; case 't': $max *= 1024;
// no break
case 'g': $max *= 1024; case 'g': $max *= 1024;
// no break
case 'm': $max *= 1024; case 'm': $max *= 1024;
// no break
case 'k': $max *= 1024; case 'k': $max *= 1024;
} }

View File

@ -551,7 +551,7 @@ class Request
$request = ['g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE]; $request = ['g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE];
$requestOrder = ini_get('request_order') ?: ini_get('variables_order'); $requestOrder = \ini_get('request_order') ?: \ini_get('variables_order');
$requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp'; $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp';
$_REQUEST = [[]]; $_REQUEST = [[]];
@ -1646,7 +1646,8 @@ class Request
$languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all(); $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all();
$this->languages = []; $this->languages = [];
foreach ($languages as $lang => $acceptHeaderItem) { foreach ($languages as $acceptHeaderItem) {
$lang = $acceptHeaderItem->getValue();
if (str_contains($lang, '-')) { if (str_contains($lang, '-')) {
$codes = explode('-', $lang); $codes = explode('-', $lang);
if ('i' === $codes[0]) { if ('i' === $codes[0]) {
@ -1684,7 +1685,7 @@ class Request
return $this->charsets; return $this->charsets;
} }
return $this->charsets = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all()); return $this->charsets = array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all()));
} }
/** /**
@ -1698,7 +1699,7 @@ class Request
return $this->encodings; return $this->encodings;
} }
return $this->encodings = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all()); return $this->encodings = array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all()));
} }
/** /**
@ -1712,7 +1713,7 @@ class Request
return $this->acceptableContentTypes; return $this->acceptableContentTypes;
} }
return $this->acceptableContentTypes = array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all()); return $this->acceptableContentTypes = array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all()));
} }
/** /**

View File

@ -77,7 +77,7 @@ class Response
public const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585 public const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585
public const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585 public const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585
public const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585 public const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585
public const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; public const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; // RFC7725
public const HTTP_INTERNAL_SERVER_ERROR = 500; public const HTTP_INTERNAL_SERVER_ERROR = 500;
public const HTTP_NOT_IMPLEMENTED = 501; public const HTTP_NOT_IMPLEMENTED = 501;
public const HTTP_BAD_GATEWAY = 502; public const HTTP_BAD_GATEWAY = 502;
@ -384,6 +384,7 @@ class Response
fastcgi_finish_request(); fastcgi_finish_request();
} elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
static::closeOutputBuffers(0, true); static::closeOutputBuffers(0, true);
flush();
} }
return $this; return $this;

View File

@ -35,8 +35,8 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess
public function open($savePath, $sessionName) public function open($savePath, $sessionName)
{ {
$this->sessionName = $sessionName; $this->sessionName = $sessionName;
if (!headers_sent() && !ini_get('session.cache_limiter') && '0' !== ini_get('session.cache_limiter')) { if (!headers_sent() && !\ini_get('session.cache_limiter') && '0' !== \ini_get('session.cache_limiter')) {
header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) ini_get('session.cache_expire'))); header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) \ini_get('session.cache_expire')));
} }
return true; return true;
@ -133,7 +133,7 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess
#[\ReturnTypeWillChange] #[\ReturnTypeWillChange]
public function destroy($sessionId) public function destroy($sessionId)
{ {
if (!headers_sent() && filter_var(ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN)) { if (!headers_sent() && filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN)) {
if (!$this->sessionName) { if (!$this->sessionName) {
throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', static::class)); throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', static::class));
} }
@ -148,7 +148,7 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess
*/ */
if (null === $cookie || isset($_COOKIE[$this->sessionName])) { if (null === $cookie || isset($_COOKIE[$this->sessionName])) {
if (\PHP_VERSION_ID < 70300) { if (\PHP_VERSION_ID < 70300) {
setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), filter_var(ini_get('session.cookie_secure'), \FILTER_VALIDATE_BOOLEAN), filter_var(ini_get('session.cookie_httponly'), \FILTER_VALIDATE_BOOLEAN)); setcookie($this->sessionName, '', 0, \ini_get('session.cookie_path'), \ini_get('session.cookie_domain'), filter_var(\ini_get('session.cookie_secure'), \FILTER_VALIDATE_BOOLEAN), filter_var(\ini_get('session.cookie_httponly'), \FILTER_VALIDATE_BOOLEAN));
} else { } else {
$params = session_get_cookie_params(); $params = session_get_cookie_params();
unset($params['lifetime']); unset($params['lifetime']);

View File

@ -116,7 +116,7 @@ class MongoDbSessionHandler extends AbstractSessionHandler
*/ */
protected function doWrite($sessionId, $data) protected function doWrite($sessionId, $data)
{ {
$expiry = new \MongoDB\BSON\UTCDateTime((time() + (int) ini_get('session.gc_maxlifetime')) * 1000); $expiry = new \MongoDB\BSON\UTCDateTime((time() + (int) \ini_get('session.gc_maxlifetime')) * 1000);
$fields = [ $fields = [
$this->options['time_field'] => new \MongoDB\BSON\UTCDateTime(), $this->options['time_field'] => new \MongoDB\BSON\UTCDateTime(),
@ -139,7 +139,7 @@ class MongoDbSessionHandler extends AbstractSessionHandler
#[\ReturnTypeWillChange] #[\ReturnTypeWillChange]
public function updateTimestamp($sessionId, $data) public function updateTimestamp($sessionId, $data)
{ {
$expiry = new \MongoDB\BSON\UTCDateTime((time() + (int) ini_get('session.gc_maxlifetime')) * 1000); $expiry = new \MongoDB\BSON\UTCDateTime((time() + (int) \ini_get('session.gc_maxlifetime')) * 1000);
$this->getCollection()->updateOne( $this->getCollection()->updateOne(
[$this->options['id_field'] => $sessionId], [$this->options['id_field'] => $sessionId],

View File

@ -31,7 +31,7 @@ class NativeFileSessionHandler extends \SessionHandler
public function __construct(string $savePath = null) public function __construct(string $savePath = null)
{ {
if (null === $savePath) { if (null === $savePath) {
$savePath = ini_get('session.save_path'); $savePath = \ini_get('session.save_path');
} }
$baseDir = $savePath; $baseDir = $savePath;

View File

@ -328,7 +328,7 @@ class PdoSessionHandler extends AbstractSessionHandler
*/ */
protected function doWrite($sessionId, $data) protected function doWrite($sessionId, $data)
{ {
$maxlifetime = (int) ini_get('session.gc_maxlifetime'); $maxlifetime = (int) \ini_get('session.gc_maxlifetime');
try { try {
// We use a single MERGE SQL query when supported by the database. // We use a single MERGE SQL query when supported by the database.
@ -375,7 +375,7 @@ class PdoSessionHandler extends AbstractSessionHandler
#[\ReturnTypeWillChange] #[\ReturnTypeWillChange]
public function updateTimestamp($sessionId, $data) public function updateTimestamp($sessionId, $data)
{ {
$expiry = time() + (int) ini_get('session.gc_maxlifetime'); $expiry = time() + (int) \ini_get('session.gc_maxlifetime');
try { try {
$updateStmt = $this->pdo->prepare( $updateStmt = $this->pdo->prepare(
@ -650,7 +650,7 @@ class PdoSessionHandler extends AbstractSessionHandler
throw new \RuntimeException('Failed to read session: INSERT reported a duplicate id but next SELECT did not return any data.'); throw new \RuntimeException('Failed to read session: INSERT reported a duplicate id but next SELECT did not return any data.');
} }
if (!filter_var(ini_get('session.use_strict_mode'), \FILTER_VALIDATE_BOOLEAN) && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { if (!filter_var(\ini_get('session.use_strict_mode'), \FILTER_VALIDATE_BOOLEAN) && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
// In strict mode, session fixation is not possible: new sessions always start with a unique // In strict mode, session fixation is not possible: new sessions always start with a unique
// random id, so that concurrency is not possible and this code path can be skipped. // random id, so that concurrency is not possible and this code path can be skipped.
// Exclusive-reading of non-existent rows does not block, so we need to do an insert to block // Exclusive-reading of non-existent rows does not block, so we need to do an insert to block
@ -898,7 +898,7 @@ class PdoSessionHandler extends AbstractSessionHandler
protected function getConnection() protected function getConnection()
{ {
if (null === $this->pdo) { if (null === $this->pdo) {
$this->connect($this->dsn ?: ini_get('session.save_path')); $this->connect($this->dsn ?: \ini_get('session.save_path'));
} }
return $this->pdo; return $this->pdo;

View File

@ -79,7 +79,7 @@ class RedisSessionHandler extends AbstractSessionHandler
*/ */
protected function doWrite($sessionId, $data): bool protected function doWrite($sessionId, $data): bool
{ {
$result = $this->redis->setEx($this->prefix.$sessionId, (int) ($this->ttl ?? ini_get('session.gc_maxlifetime')), $data); $result = $this->redis->setEx($this->prefix.$sessionId, (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime')), $data);
return $result && !$result instanceof ErrorInterface; return $result && !$result instanceof ErrorInterface;
} }
@ -120,6 +120,6 @@ class RedisSessionHandler extends AbstractSessionHandler
#[\ReturnTypeWillChange] #[\ReturnTypeWillChange]
public function updateTimestamp($sessionId, $data) public function updateTimestamp($sessionId, $data)
{ {
return (bool) $this->redis->expire($this->prefix.$sessionId, (int) ($this->ttl ?? ini_get('session.gc_maxlifetime'))); return (bool) $this->redis->expire($this->prefix.$sessionId, (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime')));
} }
} }

View File

@ -30,6 +30,16 @@ class StrictSessionHandler extends AbstractSessionHandler
$this->handler = $handler; $this->handler = $handler;
} }
/**
* Returns true if this handler wraps an internal PHP session save handler using \SessionHandler.
*
* @internal
*/
public function isWrapper(): bool
{
return $this->handler instanceof \SessionHandler;
}
/** /**
* @return bool * @return bool
*/ */

View File

@ -163,6 +163,6 @@ class MetadataBag implements SessionBagInterface
{ {
$timeStamp = time(); $timeStamp = time();
$this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp; $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp;
$this->meta[self::LIFETIME] = $lifetime ?? (int) ini_get('session.cookie_lifetime'); $this->meta[self::LIFETIME] = $lifetime ?? (int) \ini_get('session.cookie_lifetime');
} }
} }

View File

@ -148,12 +148,42 @@ class NativeSessionStorage implements SessionStorageInterface
throw new \RuntimeException('Failed to start the session: already started by PHP.'); throw new \RuntimeException('Failed to start the session: already started by PHP.');
} }
if (filter_var(ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN) && headers_sent($file, $line)) { if (filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN) && headers_sent($file, $line)) {
throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line)); throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
} }
$sessionId = $_COOKIE[session_name()] ?? null; $sessionId = $_COOKIE[session_name()] ?? null;
if ($sessionId && $this->saveHandler instanceof AbstractProxy && 'files' === $this->saveHandler->getSaveHandlerName() && !preg_match('/^[a-zA-Z0-9,-]{22,}$/', $sessionId)) { /*
* Explanation of the session ID regular expression: `/^[a-zA-Z0-9,-]{22,250}$/`.
*
* ---------- Part 1
*
* The part `[a-zA-Z0-9,-]` is related to the PHP ini directive `session.sid_bits_per_character` defined as 6.
* See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-bits-per-character.
* Allowed values are integers such as:
* - 4 for range `a-f0-9`
* - 5 for range `a-v0-9`
* - 6 for range `a-zA-Z0-9,-`
*
* ---------- Part 2
*
* The part `{22,250}` is related to the PHP ini directive `session.sid_length`.
* See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-length.
* Allowed values are integers between 22 and 256, but we use 250 for the max.
*
* Where does the 250 come from?
* - The length of Windows and Linux filenames is limited to 255 bytes. Then the max must not exceed 255.
* - The session filename prefix is `sess_`, a 5 bytes string. Then the max must not exceed 255 - 5 = 250.
*
* ---------- Conclusion
*
* The parts 1 and 2 prevent the warning below:
* `PHP Warning: SessionHandler::read(): Session ID is too long or contains illegal characters. Only the A-Z, a-z, 0-9, "-", and "," characters are allowed.`
*
* The part 2 prevents the warning below:
* `PHP Warning: SessionHandler::read(): open(filepath, O_RDWR) failed: No such file or directory (2).`
*/
if ($sessionId && $this->saveHandler instanceof AbstractProxy && 'files' === $this->saveHandler->getSaveHandlerName() && !preg_match('/^[a-zA-Z0-9,-]{22,250}$/', $sessionId)) {
// the session ID in the header is invalid, create a new one // the session ID in the header is invalid, create a new one
session_id(session_create_id()); session_id(session_create_id());
} }
@ -221,7 +251,7 @@ class NativeSessionStorage implements SessionStorageInterface
return false; return false;
} }
if (null !== $lifetime && $lifetime != ini_get('session.cookie_lifetime')) { if (null !== $lifetime && $lifetime != \ini_get('session.cookie_lifetime')) {
$this->save(); $this->save();
ini_set('session.cookie_lifetime', $lifetime); ini_set('session.cookie_lifetime', $lifetime);
$this->start(); $this->start();
@ -256,7 +286,7 @@ class NativeSessionStorage implements SessionStorageInterface
unset($_SESSION[$key]); unset($_SESSION[$key]);
} }
} }
if ([$key = $this->metadataBag->getStorageKey()] === array_keys($_SESSION)) { if ($_SESSION && [$key = $this->metadataBag->getStorageKey()] === array_keys($_SESSION)) {
unset($_SESSION[$key]); unset($_SESSION[$key]);
} }

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
/** /**
* @author Drak <drak@zikula.org> * @author Drak <drak@zikula.org>
*/ */
@ -22,7 +24,7 @@ class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterf
{ {
$this->handler = $handler; $this->handler = $handler;
$this->wrapper = $handler instanceof \SessionHandler; $this->wrapper = $handler instanceof \SessionHandler;
$this->saveHandlerName = $this->wrapper ? ini_get('session.save_handler') : 'user'; $this->saveHandlerName = $this->wrapper || ($handler instanceof StrictSessionHandler && $handler->isWrapper()) ? \ini_get('session.save_handler') : 'user';
} }
/** /**

View File

@ -79,8 +79,8 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte
'php_intl_locale' => class_exists(\Locale::class, false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a', 'php_intl_locale' => class_exists(\Locale::class, false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a',
'php_timezone' => date_default_timezone_get(), 'php_timezone' => date_default_timezone_get(),
'xdebug_enabled' => \extension_loaded('xdebug'), 'xdebug_enabled' => \extension_loaded('xdebug'),
'apcu_enabled' => \extension_loaded('apcu') && filter_var(ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN), 'apcu_enabled' => \extension_loaded('apcu') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN),
'zend_opcache_enabled' => \extension_loaded('Zend OPcache') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN), 'zend_opcache_enabled' => \extension_loaded('Zend OPcache') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN),
'bundles' => [], 'bundles' => [],
'sapi_name' => \PHP_SAPI, 'sapi_name' => \PHP_SAPI,
]; ];

View File

@ -49,9 +49,10 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
*/ */
public function __construct(Stopwatch $stopwatch = null, $fileLinkFormat = null, string $charset = null, RequestStack $requestStack = null, $dumper = null) public function __construct(Stopwatch $stopwatch = null, $fileLinkFormat = null, string $charset = null, RequestStack $requestStack = null, $dumper = null)
{ {
$fileLinkFormat = $fileLinkFormat ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
$this->stopwatch = $stopwatch; $this->stopwatch = $stopwatch;
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); $this->fileLinkFormat = $fileLinkFormat instanceof FileLinkFormatter && false === $fileLinkFormat->format('', 0) ? false : $fileLinkFormat;
$this->charset = $charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8'; $this->charset = $charset ?: \ini_get('php.output_encoding') ?: \ini_get('default_charset') ?: 'UTF-8';
$this->requestStack = $requestStack; $this->requestStack = $requestStack;
$this->dumper = $dumper; $this->dumper = $dumper;
@ -237,7 +238,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
$h = headers_list(); $h = headers_list();
$i = \count($h); $i = \count($h);
array_unshift($h, 'Content-Type: '.ini_get('default_mimetype')); array_unshift($h, 'Content-Type: '.\ini_get('default_mimetype'));
while (0 !== stripos($h[$i], 'Content-Type:')) { while (0 !== stripos($h[$i], 'Content-Type:')) {
--$i; --$i;
} }

View File

@ -45,7 +45,7 @@ class MemoryDataCollector extends DataCollector implements LateDataCollectorInte
{ {
$this->data = [ $this->data = [
'memory' => 0, 'memory' => 0,
'memory_limit' => $this->convertToBytes(ini_get('memory_limit')), 'memory_limit' => $this->convertToBytes(\ini_get('memory_limit')),
]; ];
} }
@ -114,8 +114,11 @@ class MemoryDataCollector extends DataCollector implements LateDataCollectorInte
switch (substr($memoryLimit, -1)) { switch (substr($memoryLimit, -1)) {
case 't': $max *= 1024; case 't': $max *= 1024;
// no break
case 'g': $max *= 1024; case 'g': $max *= 1024;
// no break
case 'm': $max *= 1024; case 'm': $max *= 1024;
// no break
case 'k': $max *= 1024; case 'k': $max *= 1024;
} }

View File

@ -30,12 +30,12 @@ class FileLinkFormatter
private $urlFormat; private $urlFormat;
/** /**
* @param string|array|null $fileLinkFormat
* @param string|\Closure $urlFormat the URL format, or a closure that returns it on-demand * @param string|\Closure $urlFormat the URL format, or a closure that returns it on-demand
*/ */
public function __construct(string $fileLinkFormat = null, RequestStack $requestStack = null, string $baseDir = null, $urlFormat = null) public function __construct($fileLinkFormat = null, RequestStack $requestStack = null, string $baseDir = null, $urlFormat = null)
{ {
$fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); if (!\is_array($fileLinkFormat) && $fileLinkFormat = $fileLinkFormat ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format')) {
if ($fileLinkFormat && !\is_array($fileLinkFormat)) {
$i = strpos($f = $fileLinkFormat, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: \strlen($f); $i = strpos($f = $fileLinkFormat, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: \strlen($f);
$fileLinkFormat = [substr($f, 0, $i)] + preg_split('/&([^>]++)>/', substr($f, $i), -1, \PREG_SPLIT_DELIM_CAPTURE); $fileLinkFormat = [substr($f, 0, $i)] + preg_split('/&([^>]++)>/', substr($f, $i), -1, \PREG_SPLIT_DELIM_CAPTURE);
} }

View File

@ -54,7 +54,7 @@ class DebugHandlersListener implements EventSubscriberInterface
*/ */
public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = \E_ALL, ?int $throwAt = \E_ALL, bool $scream = true, $fileLinkFormat = null, bool $scope = true) public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = \E_ALL, ?int $throwAt = \E_ALL, bool $scream = true, $fileLinkFormat = null, bool $scope = true)
{ {
$handler = set_exception_handler('var_dump'); $handler = set_exception_handler('is_int');
$this->earlyHandler = \is_array($handler) ? $handler[0] : null; $this->earlyHandler = \is_array($handler) ? $handler[0] : null;
restore_exception_handler(); restore_exception_handler();
@ -80,7 +80,7 @@ class DebugHandlersListener implements EventSubscriberInterface
} }
$this->firstCall = $this->hasTerminatedWithException = false; $this->firstCall = $this->hasTerminatedWithException = false;
$handler = set_exception_handler('var_dump'); $handler = set_exception_handler('is_int');
$handler = \is_array($handler) ? $handler[0] : null; $handler = \is_array($handler) ? $handler[0] : null;
restore_exception_handler(); restore_exception_handler();

View File

@ -96,9 +96,11 @@ abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRendere
private function containsNonScalars(array $values): bool private function containsNonScalars(array $values): bool
{ {
foreach ($values as $value) { foreach ($values as $value) {
if (\is_array($value)) { if (\is_scalar($value) || null === $value) {
return $this->containsNonScalars($value); continue;
} elseif (!is_scalar($value) && null !== $value) { }
if (!\is_array($value) || $this->containsNonScalars($value)) {
return true; return true;
} }
} }

View File

@ -80,7 +80,7 @@ abstract class RoutableFragmentRenderer implements FragmentRendererInterface
foreach ($values as $key => $value) { foreach ($values as $key => $value) {
if (\is_array($value)) { if (\is_array($value)) {
$this->checkNonScalar($value); $this->checkNonScalar($value);
} elseif (!is_scalar($value) && null !== $value) { } elseif (!\is_scalar($value) && null !== $value) {
throw new \LogicException(sprintf('Controller attributes cannot contain non-scalar/non-null values (value for key "%s" is not a scalar or null).', $key)); throw new \LogicException(sprintf('Controller attributes cannot contain non-scalar/non-null values (value for key "%s" is not a scalar or null).', $key));
} }
} }

View File

@ -95,7 +95,7 @@ abstract class AbstractSurrogate implements SurrogateInterface
try { try {
$response = $cache->handle($subRequest, HttpKernelInterface::SUB_REQUEST, true); $response = $cache->handle($subRequest, HttpKernelInterface::SUB_REQUEST, true);
if (!$response->isSuccessful()) { if (!$response->isSuccessful() && Response::HTTP_NOT_MODIFIED !== $response->getStatusCode()) {
throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %d).', $subRequest->getUri(), $response->getStatusCode())); throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %d).', $subRequest->getUri(), $response->getStatusCode()));
} }

View File

@ -76,6 +76,7 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface
{ {
$request->headers->set('X-Php-Ob-Level', (string) ob_get_level()); $request->headers->set('X-Php-Ob-Level', (string) ob_get_level());
$this->requestStack->push($request);
try { try {
return $this->handleRaw($request, $type); return $this->handleRaw($request, $type);
} catch (\Exception $e) { } catch (\Exception $e) {
@ -89,6 +90,8 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface
} }
return $this->handleThrowable($e, $request, $type); return $this->handleThrowable($e, $request, $type);
} finally {
$this->requestStack->pop();
} }
} }
@ -127,8 +130,6 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface
*/ */
private function handleRaw(Request $request, int $type = self::MASTER_REQUEST): Response private function handleRaw(Request $request, int $type = self::MASTER_REQUEST): Response
{ {
$this->requestStack->push($request);
// request // request
$event = new RequestEvent($this, $request, $type); $event = new RequestEvent($this, $request, $type);
$this->dispatcher->dispatch($event, KernelEvents::REQUEST); $this->dispatcher->dispatch($event, KernelEvents::REQUEST);
@ -205,7 +206,6 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface
private function finishRequest(Request $request, int $type) private function finishRequest(Request $request, int $type)
{ {
$this->dispatcher->dispatch(new FinishRequestEvent($this, $request, $type), KernelEvents::FINISH_REQUEST); $this->dispatcher->dispatch(new FinishRequestEvent($this, $request, $type), KernelEvents::FINISH_REQUEST);
$this->requestStack->pop();
} }
/** /**

View File

@ -76,11 +76,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
private static $freshCache = []; private static $freshCache = [];
public const VERSION = '4.4.43'; public const VERSION = '4.4.46';
public const VERSION_ID = 40443; public const VERSION_ID = 40446;
public const MAJOR_VERSION = 4; public const MAJOR_VERSION = 4;
public const MINOR_VERSION = 4; public const MINOR_VERSION = 4;
public const RELEASE_VERSION = 43; public const RELEASE_VERSION = 46;
public const EXTRA_VERSION = ''; public const EXTRA_VERSION = '';
public const END_OF_MAINTENANCE = '11/2022'; public const END_OF_MAINTENANCE = '11/2022';

View File

@ -91,7 +91,7 @@ class Logger extends AbstractLogger
if (str_contains($message, '{')) { if (str_contains($message, '{')) {
$replacements = []; $replacements = [];
foreach ($context as $key => $val) { foreach ($context as $key => $val) {
if (null === $val || is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) { if (null === $val || \is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) {
$replacements["{{$key}}"] = $val; $replacements["{{$key}}"] = $val;
} elseif ($val instanceof \DateTimeInterface) { } elseif ($val instanceof \DateTimeInterface) {
$replacements["{{$key}}"] = $val->format(\DateTime::RFC3339); $replacements["{{$key}}"] = $val->format(\DateTime::RFC3339);

View File

@ -115,19 +115,7 @@ class FileProfilerStorage implements ProfilerStorageInterface
*/ */
public function read($token): ?Profile public function read($token): ?Profile
{ {
if (!$token || !file_exists($file = $this->getFilename($token))) { return $this->doRead($token);
return null;
}
if (\function_exists('gzcompress')) {
$file = 'compress.zlib://'.$file;
}
if (!$data = unserialize(file_get_contents($file))) {
return null;
}
return $this->createProfileFromData($token, $data);
} }
/** /**
@ -169,14 +157,13 @@ class FileProfilerStorage implements ProfilerStorageInterface
'status_code' => $profile->getStatusCode(), 'status_code' => $profile->getStatusCode(),
]; ];
$context = stream_context_create(); $data = serialize($data);
if (\function_exists('gzcompress')) { if (\function_exists('gzencode')) {
$file = 'compress.zlib://'.$file; $data = gzencode($data, 3);
stream_context_set_option($context, 'zlib', 'level', 3);
} }
if (false === file_put_contents($file, serialize($data), 0, $context)) { if (false === file_put_contents($file, $data, \LOCK_EX)) {
return false; return false;
} }
@ -293,21 +280,34 @@ class FileProfilerStorage implements ProfilerStorageInterface
} }
foreach ($data['children'] as $token) { foreach ($data['children'] as $token) {
if (!$token || !file_exists($file = $this->getFilename($token))) { if (null !== $childProfile = $this->doRead($token, $profile)) {
continue; $profile->addChild($childProfile);
} }
if (\function_exists('gzcompress')) {
$file = 'compress.zlib://'.$file;
}
if (!$childData = unserialize(file_get_contents($file))) {
continue;
}
$profile->addChild($this->createProfileFromData($token, $childData, $profile));
} }
return $profile; return $profile;
} }
private function doRead($token, Profile $profile = null): ?Profile
{
if (!$token || !file_exists($file = $this->getFilename($token))) {
return null;
}
$h = fopen($file, 'r');
flock($h, \LOCK_SH);
$data = stream_get_contents($h);
flock($h, \LOCK_UN);
fclose($h);
if (\function_exists('gzdecode')) {
$data = @gzdecode($data) ?: $data;
}
if (!$data = unserialize($data)) {
return null;
}
return $this->createProfileFromData($token, $data, $profile);
}
} }

View File

@ -43,6 +43,10 @@ class Email extends Message
private $html; private $html;
private $htmlCharset; private $htmlCharset;
private $attachments = []; private $attachments = [];
/**
* @var AbstractPart|null
*/
private $cachedBody; // Used to avoid wrong body hash in DKIM signatures with multiple parts (e.g. HTML + TEXT) due to multiple boundaries.
/** /**
* @return $this * @return $this
@ -282,6 +286,7 @@ class Email extends Message
throw new \TypeError(sprintf('The body must be a string, a resource or null (got "%s").', get_debug_type($body))); throw new \TypeError(sprintf('The body must be a string, a resource or null (got "%s").', get_debug_type($body)));
} }
$this->cachedBody = null;
$this->text = $body; $this->text = $body;
$this->textCharset = $charset; $this->textCharset = $charset;
@ -312,6 +317,7 @@ class Email extends Message
throw new \TypeError(sprintf('The body must be a string, a resource or null (got "%s").', get_debug_type($body))); throw new \TypeError(sprintf('The body must be a string, a resource or null (got "%s").', get_debug_type($body)));
} }
$this->cachedBody = null;
$this->html = $body; $this->html = $body;
$this->htmlCharset = $charset; $this->htmlCharset = $charset;
@ -342,6 +348,7 @@ class Email extends Message
throw new \TypeError(sprintf('The body must be a string or a resource (got "%s").', get_debug_type($body))); throw new \TypeError(sprintf('The body must be a string or a resource (got "%s").', get_debug_type($body)));
} }
$this->cachedBody = null;
$this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => false]; $this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => false];
return $this; return $this;
@ -352,6 +359,7 @@ class Email extends Message
*/ */
public function attachFromPath(string $path, string $name = null, string $contentType = null) public function attachFromPath(string $path, string $name = null, string $contentType = null)
{ {
$this->cachedBody = null;
$this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => false]; $this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => false];
return $this; return $this;
@ -368,6 +376,7 @@ class Email extends Message
throw new \TypeError(sprintf('The body must be a string or a resource (got "%s").', get_debug_type($body))); throw new \TypeError(sprintf('The body must be a string or a resource (got "%s").', get_debug_type($body)));
} }
$this->cachedBody = null;
$this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => true]; $this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => true];
return $this; return $this;
@ -378,6 +387,7 @@ class Email extends Message
*/ */
public function embedFromPath(string $path, string $name = null, string $contentType = null) public function embedFromPath(string $path, string $name = null, string $contentType = null)
{ {
$this->cachedBody = null;
$this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => true]; $this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => true];
return $this; return $this;
@ -388,6 +398,7 @@ class Email extends Message
*/ */
public function attachPart(DataPart $part) public function attachPart(DataPart $part)
{ {
$this->cachedBody = null;
$this->attachments[] = ['part' => $part]; $this->attachments[] = ['part' => $part];
return $this; return $this;
@ -446,9 +457,13 @@ class Email extends Message
*/ */
private function generateBody(): AbstractPart private function generateBody(): AbstractPart
{ {
if (null !== $this->cachedBody) {
return $this->cachedBody;
}
$this->ensureValidity(); $this->ensureValidity();
[$htmlPart, $attachmentParts, $inlineParts] = $this->prepareParts(); [$htmlPart, $otherParts, $relatedParts] = $this->prepareParts();
$part = null === $this->text ? null : new TextPart($this->text, $this->textCharset); $part = null === $this->text ? null : new TextPart($this->text, $this->textCharset);
if (null !== $htmlPart) { if (null !== $htmlPart) {
@ -459,19 +474,19 @@ class Email extends Message
} }
} }
if ($inlineParts) { if ($relatedParts) {
$part = new RelatedPart($part, ...$inlineParts); $part = new RelatedPart($part, ...$relatedParts);
} }
if ($attachmentParts) { if ($otherParts) {
if ($part) { if ($part) {
$part = new MixedPart($part, ...$attachmentParts); $part = new MixedPart($part, ...$otherParts);
} else { } else {
$part = new MixedPart(...$attachmentParts); $part = new MixedPart(...$otherParts);
} }
} }
return $part; return $this->cachedBody = $part;
} }
private function prepareParts(): ?array private function prepareParts(): ?array
@ -482,35 +497,49 @@ class Email extends Message
if (null !== $html) { if (null !== $html) {
$htmlPart = new TextPart($html, $this->htmlCharset, 'html'); $htmlPart = new TextPart($html, $this->htmlCharset, 'html');
$html = $htmlPart->getBody(); $html = $htmlPart->getBody();
preg_match_all('(<img\s+[^>]*src\s*=\s*(?:([\'"])cid:([^"]+)\\1|cid:([^>\s]+)))i', $html, $names); preg_match_all('(<img\s+[^>]*src\s*=\s*(?:([\'"])cid:(.+?)\\1|cid:([^>\s]+)))i', $html, $names);
$names = array_filter(array_unique(array_merge($names[2], $names[3]))); $names = array_filter(array_unique(array_merge($names[2], $names[3])));
} }
$attachmentParts = $inlineParts = []; // usage of reflection is a temporary workaround for missing getters that will be added in 6.2
$nameRef = new \ReflectionProperty(TextPart::class, 'name');
$nameRef->setAccessible(true);
$otherParts = $relatedParts = [];
foreach ($this->attachments as $attachment) { foreach ($this->attachments as $attachment) {
foreach ($names as $name) { $part = $this->createDataPart($attachment);
if (isset($attachment['part'])) { if (isset($attachment['part'])) {
continue; $attachment['name'] = $nameRef->getValue($part);
} }
$related = false;
foreach ($names as $name) {
if ($name !== $attachment['name']) { if ($name !== $attachment['name']) {
continue; continue;
} }
if (isset($inlineParts[$name])) { if (isset($relatedParts[$name])) {
continue 2; continue 2;
} }
$attachment['inline'] = true; $part->setDisposition('inline');
$inlineParts[$name] = $part = $this->createDataPart($attachment); $html = str_replace('cid:'.$name, 'cid:'.$part->getContentId(), $html, $count);
$html = str_replace('cid:'.$name, 'cid:'.$part->getContentId(), $html); if ($count) {
$related = true;
}
$part->setName($part->getContentId()); $part->setName($part->getContentId());
continue 2;
break;
}
if ($related) {
$relatedParts[$attachment['name']] = $part;
} else {
$otherParts[] = $part;
} }
$attachmentParts[] = $this->createDataPart($attachment);
} }
if (null !== $htmlPart) { if (null !== $htmlPart) {
$htmlPart = new TextPart($html, $this->htmlCharset, 'html'); $htmlPart = new TextPart($html, $this->htmlCharset, 'html');
} }
return [$htmlPart, $attachmentParts, array_values($inlineParts)]; return [$htmlPart, $otherParts, array_values($relatedParts)];
} }
private function createDataPart(array $attachment): DataPart private function createDataPart(array $attachment): DataPart

View File

@ -109,6 +109,11 @@ abstract class AbstractHeader implements HeaderInterface
} }
$phraseStr = $this->encodeWords($header, $string, $usedLength); $phraseStr = $this->encodeWords($header, $string, $usedLength);
} }
} elseif (str_contains($phraseStr, '(')) {
foreach (['\\', '"'] as $char) {
$phraseStr = str_replace($char, '\\'.$char, $phraseStr);
}
$phraseStr = '"'.$phraseStr.'"';
} }
return $phraseStr; return $phraseStr;

View File

@ -197,6 +197,7 @@ class TextPart extends AbstractPart
// convert resources to strings for serialization // convert resources to strings for serialization
if (null !== $this->seekable) { if (null !== $this->seekable) {
$this->body = $this->getBody(); $this->body = $this->getBody();
$this->seekable = null;
} }
$this->_headers = $this->getHeaders(); $this->_headers = $this->getHeaders();

View File

@ -50,8 +50,8 @@ class ExecutableFinder
*/ */
public function find($name, $default = null, array $extraDirs = []) public function find($name, $default = null, array $extraDirs = [])
{ {
if (ini_get('open_basedir')) { if (\ini_get('open_basedir')) {
$searchPath = array_merge(explode(\PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs); $searchPath = array_merge(explode(\PATH_SEPARATOR, \ini_get('open_basedir')), $extraDirs);
$dirs = []; $dirs = [];
foreach ($searchPath as $path) { foreach ($searchPath as $path) {
// Silencing against https://bugs.php.net/69240 // Silencing against https://bugs.php.net/69240

View File

@ -104,7 +104,7 @@ abstract class AbstractPipes implements PipesInterface
stream_set_blocking($input, 0); stream_set_blocking($input, 0);
} elseif (!isset($this->inputBuffer[0])) { } elseif (!isset($this->inputBuffer[0])) {
if (!\is_string($input)) { if (!\is_string($input)) {
if (!is_scalar($input)) { if (!\is_scalar($input)) {
throw new InvalidArgumentException(sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', \get_class($this->input), \gettype($input))); throw new InvalidArgumentException(sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', \get_class($this->input), \gettype($input)));
} }
$input = (string) $input; $input = (string) $input;

View File

@ -48,7 +48,7 @@ class ProcessUtils
if (\is_string($input)) { if (\is_string($input)) {
return $input; return $input;
} }
if (is_scalar($input)) { if (\is_scalar($input)) {
return (string) $input; return (string) $input;
} }
if ($input instanceof Process) { if ($input instanceof Process) {

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