Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Allow Hash::remove() to remove from ArrayAccess objects #17569

Merged
merged 1 commit into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .phive/phars.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<phive xmlns="https://phar.io/phive">
<phar name="psalm" version="5.20.0" installed="5.20.0" location="./tools/psalm" copy="false"/>
<phar name="psalm" version="5.23.1" installed="5.23.1" location="./tools/psalm" copy="false"/>
</phive>
15 changes: 11 additions & 4 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@ parameters:
message: "#^Property Cake\\\\Console\\\\ConsoleInput\\:\\:\\$_input \\(resource\\) in isset\\(\\) is not nullable\\.$#"
count: 1
path: src/Console/ConsoleInput.php
-
message: "#^Property Cake\\\\Console\\\\ConsoleOutput\\:\\:\\$_output \\(resource\\) in isset\\(\\) is not nullable\\.$#"
count: 2
path: src/Console/ConsoleOutput.php

-
message: "#^Unsafe usage of new static\\(\\)\\.$#"
count: 2
path: src/Console/ConsoleOptionParser.php

-
message: "#^Property Cake\\\\Console\\\\ConsoleOutput\\:\\:\\$_output \\(resource\\) in isset\\(\\) is not nullable\\.$#"
count: 2
path: src/Console/ConsoleOutput.php

-
message: "#^Unsafe usage of new static\\(\\)\\.$#"
count: 8
Expand Down Expand Up @@ -118,6 +120,11 @@ parameters:
count: 1
path: src/TestSuite/TestCase.php

-
message: "#^Unable to resolve the template type T in call to method static method Cake\\\\Utility\\\\Hash\\:\\:insert\\(\\)$#"
count: 1
path: src/Utility/Hash.php

-
message: "#^Unsafe usage of new static\\(\\)\\.$#"
count: 1
Expand Down
115 changes: 60 additions & 55 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="5.20.0@3f284e96c9d9be6fe6b15c79416e1d1903dcfef4">
<files psalm-version="5.23.1@8471a896ccea3526b26d082f4461eeea467f10a4">
<file src="src/Cache/Engine/FileEngine.php">
<TooManyTemplateParams>
<code>$iterator</code>
<code><![CDATA[$iterator]]></code>
</TooManyTemplateParams>
</file>
<file src="src/Cache/Engine/RedisEngine.php">
Expand All @@ -15,93 +15,93 @@
<InvalidReturnStatement>
<code><![CDATA[$this->_Redis->set($key, $value)]]></code>
<code><![CDATA[$this->_Redis->setEx($key, $duration, $value)]]></code>
<code>$value</code>
<code>$value</code>
<code><![CDATA[$value]]></code>
<code><![CDATA[$value]]></code>
</InvalidReturnStatement>
<InvalidReturnType>
<code>bool</code>
<code>int|false</code>
<code>int|false</code>
<code><![CDATA[bool]]></code>
<code><![CDATA[int|false]]></code>
<code><![CDATA[int|false]]></code>
</InvalidReturnType>
</file>
<file src="src/Core/PluginConfig.php">
<RedundantCondition>
<code>is_array($pluginLoadConfig)</code>
<code><![CDATA[is_array($pluginLoadConfig)]]></code>
</RedundantCondition>
</file>
<file src="src/Event/EventDispatcherTrait.php">
<MoreSpecificImplementedParamType>
<code>$subject</code>
<code><![CDATA[$subject]]></code>
</MoreSpecificImplementedParamType>
</file>
<file src="src/Event/EventManager.php">
<InvalidArgument>
<code>_callListener</code>
<code>addEventToList</code>
<code>addEventToList</code>
<code><![CDATA[_callListener]]></code>
<code><![CDATA[addEventToList]]></code>
<code><![CDATA[addEventToList]]></code>
</InvalidArgument>
<InvalidReturnStatement>
<code>$event</code>
<code>$event</code>
<code><![CDATA[$event]]></code>
<code><![CDATA[$event]]></code>
</InvalidReturnStatement>
<InvalidReturnType>
<code>EventInterface</code>
<code><![CDATA[EventInterface]]></code>
</InvalidReturnType>
</file>
<file src="src/I18n/Date.php">
<ImpureFunctionCall>
<code>call_user_func(static::$_jsonEncodeFormat, $this)</code>
<code>static::$_jsonEncodeFormat</code>
<code><![CDATA[call_user_func(static::$_jsonEncodeFormat, $this)]]></code>
<code><![CDATA[static::$_jsonEncodeFormat]]></code>
</ImpureFunctionCall>
<ImpureMethodCall>
<code>_formatObject</code>
<code>dateAgoInWords</code>
<code>diffFormatter</code>
<code>getDefaultLocale</code>
<code><![CDATA[_formatObject]]></code>
<code><![CDATA[dateAgoInWords]]></code>
<code><![CDATA[diffFormatter]]></code>
<code><![CDATA[getDefaultLocale]]></code>
</ImpureMethodCall>
<ImpureStaticProperty>
<code>static::$_jsonEncodeFormat</code>
<code>static::$_jsonEncodeFormat</code>
<code>static::$_jsonEncodeFormat</code>
<code>static::$_toStringFormat</code>
<code>static::$niceFormat</code>
<code><![CDATA[static::$_jsonEncodeFormat]]></code>
<code><![CDATA[static::$_jsonEncodeFormat]]></code>
<code><![CDATA[static::$_jsonEncodeFormat]]></code>
<code><![CDATA[static::$_toStringFormat]]></code>
<code><![CDATA[static::$niceFormat]]></code>
</ImpureStaticProperty>
</file>
<file src="src/I18n/DateTime.php">
<ImpureFunctionCall>
<code>call_user_func(static::$_jsonEncodeFormat, $this)</code>
<code>static::$_jsonEncodeFormat</code>
<code><![CDATA[call_user_func(static::$_jsonEncodeFormat, $this)]]></code>
<code><![CDATA[static::$_jsonEncodeFormat]]></code>
</ImpureFunctionCall>
<ImpureMethodCall>
<code>_formatObject</code>
<code>diffFormatter</code>
<code>getDefaultLocale</code>
<code>timeAgoInWords</code>
<code><![CDATA[_formatObject]]></code>
<code><![CDATA[diffFormatter]]></code>
<code><![CDATA[getDefaultLocale]]></code>
<code><![CDATA[timeAgoInWords]]></code>
</ImpureMethodCall>
<ImpureStaticProperty>
<code>static::$_jsonEncodeFormat</code>
<code>static::$_jsonEncodeFormat</code>
<code>static::$_jsonEncodeFormat</code>
<code>static::$_toStringFormat</code>
<code>static::$niceFormat</code>
<code><![CDATA[static::$_jsonEncodeFormat]]></code>
<code><![CDATA[static::$_jsonEncodeFormat]]></code>
<code><![CDATA[static::$_jsonEncodeFormat]]></code>
<code><![CDATA[static::$_toStringFormat]]></code>
<code><![CDATA[static::$niceFormat]]></code>
</ImpureStaticProperty>
</file>
<file src="src/I18n/Time.php">
<ImpureFunctionCall>
<code>call_user_func(static::$_jsonEncodeFormat, $this)</code>
<code>static::$_jsonEncodeFormat</code>
<code><![CDATA[call_user_func(static::$_jsonEncodeFormat, $this)]]></code>
<code><![CDATA[static::$_jsonEncodeFormat]]></code>
</ImpureFunctionCall>
<ImpureMethodCall>
<code>_formatObject</code>
<code>getDefaultLocale</code>
<code>toNative</code>
<code><![CDATA[_formatObject]]></code>
<code><![CDATA[getDefaultLocale]]></code>
<code><![CDATA[toNative]]></code>
</ImpureMethodCall>
<ImpureStaticProperty>
<code>static::$_jsonEncodeFormat</code>
<code>static::$_jsonEncodeFormat</code>
<code>static::$_jsonEncodeFormat</code>
<code>static::$_toStringFormat</code>
<code>static::$niceFormat</code>
<code><![CDATA[static::$_jsonEncodeFormat]]></code>
<code><![CDATA[static::$_jsonEncodeFormat]]></code>
<code><![CDATA[static::$_jsonEncodeFormat]]></code>
<code><![CDATA[static::$_toStringFormat]]></code>
<code><![CDATA[static::$niceFormat]]></code>
</ImpureStaticProperty>
</file>
<file src="src/TestSuite/Constraint/EventFired.php">
Expand Down Expand Up @@ -148,27 +148,32 @@
</file>
<file src="src/TestSuite/Constraint/Session/FlashParamEquals.php">
<InternalClass>
<code>new AssertionFailedError($message)</code>
<code><![CDATA[new AssertionFailedError($message)]]></code>
</InternalClass>
<InternalMethod>
<code>new AssertionFailedError($message)</code>
<code><![CDATA[new AssertionFailedError($message)]]></code>
</InternalMethod>
</file>
<file src="src/TestSuite/TestCase.php">
<UndefinedVariable>
<code>$previousHandler</code>
<code><![CDATA[$previousHandler]]></code>
</UndefinedVariable>
</file>
<file src="src/Utility/Filesystem.php">
<TooManyTemplateParams>
<code>$iterator</code>
<code>$iterator</code>
<code><![CDATA[$iterator]]></code>
<code><![CDATA[$iterator]]></code>
</TooManyTemplateParams>
</file>
<file src="src/Utility/Hash.php">
<RedundantCondition>
<code>is_array($_list)</code>
</RedundantCondition>
<InvalidReturnStatement>
<code><![CDATA[$data]]></code>
<code><![CDATA[$data]]></code>
</InvalidReturnStatement>
<InvalidReturnType>
<code><![CDATA[(T is array ? array : \ArrayAccess)]]></code>
<code><![CDATA[(T is array ? array : \ArrayAccess)]]></code>
</InvalidReturnType>
</file>
<file src="src/Validation/Validation.php">
<RedundantCondition>
Expand Down
1 change: 0 additions & 1 deletion src/Http/ServerRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1562,7 +1562,6 @@ public function withoutAttribute(string $name): static
* @param string $name The attribute name.
* @param mixed $default The default value if the attribute has not been set.
* @return mixed
* @psalm-suppress MethodSignatureMismatch
*/
public function getAttribute(string $name, mixed $default = null): mixed
{
Expand Down
43 changes: 30 additions & 13 deletions src/Utility/Hash.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
namespace Cake\Utility;

use ArrayAccess;
use Cake\Core\Exception\CakeException;
use InvalidArgumentException;
use const SORT_ASC;
use const SORT_DESC;
Expand Down Expand Up @@ -288,13 +289,15 @@ protected static function _matches(ArrayAccess|array $data, string $selector): b
* Insert $values into an array with the given $path. You can use
* `{n}` and `{s}` elements to insert $data multiple times.
*
* @param array $data The data to insert into.
* @template T of \ArrayAccess|array
* @param T $data The data to insert into.
* @param string $path The path to insert at.
* @param mixed $values The values to insert.
* @return array The data with $values inserted.
* @return \ArrayAccess|array The data with $values inserted.
* @psalm-return (T is array ? array : \ArrayAccess)
* @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::insert
*/
public static function insert(array $data, string $path, mixed $values = null): array
public static function insert(ArrayAccess|array $data, string $path, mixed $values = null): ArrayAccess|array
{
$noTokens = !str_contains($path, '[');
if ($noTokens && !str_contains($path, '.')) {
Expand All @@ -313,6 +316,10 @@ public static function insert(array $data, string $path, mixed $values = null):
return static::_simpleOp('insert', $data, $tokens, $values);
}

if (!is_iterable($data)) {
throw new CakeException('Cannot use path tokens of type `{}` or `[]` for non-iterable objects.');
}

/** @var string $token */
$token = array_shift($tokens);
$nextPath = implode('.', $tokens);
Expand All @@ -337,13 +344,17 @@ public static function insert(array $data, string $path, mixed $values = null):
* Perform a simple insert/remove operation.
*
* @param string $op The operation to do.
* @param array $data The data to operate on.
* @param \ArrayAccess|array $data The data to operate on.
* @param list<string> $path The path to work on.
* @param mixed $values The values to insert when doing inserts.
* @return array data.
* @return \ArrayAccess|array
*/
protected static function _simpleOp(string $op, array $data, array $path, mixed $values = null): array
{
protected static function _simpleOp(
string $op,
ArrayAccess|array $data,
array $path,
mixed $values = null
): ArrayAccess|array {
$_list = &$data;

$count = count($path);
Expand All @@ -357,12 +368,12 @@ protected static function _simpleOp(string $op, array $data, array $path, mixed
}
$_list[$key] ??= [];
$_list = &$_list[$key];
if (!is_array($_list)) {
if (!is_array($_list) && !$_list instanceof ArrayAccess) {
$_list = [];
}
} elseif ($op === 'remove') {
if ($i === $last) {
if (is_array($_list)) {
if (is_array($_list) || $_list instanceof ArrayAccess) {
unset($_list[$key]);
}

Expand All @@ -383,12 +394,14 @@ protected static function _simpleOp(string $op, array $data, array $path, mixed
* You can use `{n}` and `{s}` to remove multiple elements
* from $data.
*
* @param array $data The data to operate on
* @template T of \ArrayAccess|array
* @param T $data The data to operate on
* @param string $path A path expression to use to remove.
* @return array The modified array.
* @return \ArrayAccess|array The modified array.
* @psalm-return (T is array ? array : \ArrayAccess)
* @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::remove
*/
public static function remove(array $data, string $path): array
public static function remove(ArrayAccess|array $data, string $path): ArrayAccess|array
{
$noTokens = !str_contains($path, '[');
$noExpansion = !str_contains($path, '{');
Expand All @@ -405,6 +418,10 @@ public static function remove(array $data, string $path): array
return static::_simpleOp('remove', $data, $tokens);
}

if (!is_iterable($data)) {
throw new CakeException('Cannot use path tokens of type `{}` or `[]` for non-iterable objects.');
}

/** @var string $token */
$token = array_shift($tokens);
$nextPath = implode('.', $tokens);
Expand All @@ -413,7 +430,7 @@ public static function remove(array $data, string $path): array

foreach ($data as $k => $v) {
$match = static::_matchToken($k, $token);
if ($match && is_array($v)) {
if ($match && (is_array($v) || $v instanceof ArrayAccess)) {
if ($conditions) {
if (static::_matches($v, $conditions)) {
if ($nextPath !== '') {
Expand Down