Browse Source

Add native type hints for Utility

Corey Taylor 4 years ago
parent
commit
69da52e86b

+ 4 - 4
src/Utility/CookieCryptTrait.php

@@ -50,7 +50,7 @@ trait CookieCryptTrait
      * @param string|null $key Used as the security salt if specified.
      * @return string Encoded values
      */
-    protected function _encrypt($value, $encrypt, ?string $key = null): string
+    protected function _encrypt(array|string $value, string|false $encrypt, ?string $key = null): string
     {
         if (is_array($value)) {
             $value = $this->_implode($value);
@@ -97,7 +97,7 @@ trait CookieCryptTrait
      * @param string|null $key Used as the security salt if specified.
      * @return array|string Decrypted values
      */
-    protected function _decrypt($values, $mode, ?string $key = null)
+    protected function _decrypt(array|string $values, string|false $mode, ?string $key = null): array|string
     {
         if (is_string($values)) {
             return $this->_decode($values, $mode, $key);
@@ -119,7 +119,7 @@ trait CookieCryptTrait
      * @param string|null $key Used as the security salt if specified.
      * @return array|string Decoded values.
      */
-    protected function _decode(string $value, $encrypt, ?string $key)
+    protected function _decode(string $value, string|false $encrypt, ?string $key): array|string
     {
         if (!$encrypt) {
             return $this->_explode($value);
@@ -170,7 +170,7 @@ trait CookieCryptTrait
      * @param string $string A string containing JSON encoded data, or a bare string.
      * @return array|string Map of key and values
      */
-    protected function _explode(string $string)
+    protected function _explode(string $string): array|string
     {
         $first = substr($string, 0, 1);
         if ($first === '{' || $first === '[') {

+ 45 - 47
src/Utility/Hash.php

@@ -18,6 +18,13 @@ namespace Cake\Utility;
 use ArrayAccess;
 use InvalidArgumentException;
 use RuntimeException;
+use const SORT_ASC;
+use const SORT_DESC;
+use const SORT_LOCALE_STRING;
+use const SORT_NATURAL;
+use const SORT_NUMERIC;
+use const SORT_REGULAR;
+use const SORT_STRING;
 
 /**
  * Library of array functions for manipulating and extracting data
@@ -46,28 +53,15 @@ class Hash
      * @return mixed The value fetched from the array, or null.
      * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::get
      */
-    public static function get($data, $path, $default = null)
+    public static function get(ArrayAccess|array $data, array|string|int|null $path, mixed $default = null): mixed
     {
-        if (!(is_array($data) || $data instanceof ArrayAccess)) {
-            throw new InvalidArgumentException(
-                'Invalid data type, must be an array or \ArrayAccess instance.'
-            );
-        }
-
         if (empty($data) || $path === null) {
             return $default;
         }
 
-        if (is_string($path) || is_numeric($path)) {
+        if (is_string($path) || is_int($path)) {
             $parts = explode('.', (string)$path);
         } else {
-            if (!is_array($path)) {
-                throw new InvalidArgumentException(sprintf(
-                    'Invalid Parameter %s, should be dot separated path or array.',
-                    $path
-                ));
-            }
-
             $parts = $path;
         }
 
@@ -122,14 +116,8 @@ class Hash
      *   if there are no matches.
      * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::extract
      */
-    public static function extract($data, string $path)
+    public static function extract(ArrayAccess|array $data, string $path): ArrayAccess|array
     {
-        if (!(is_array($data) || $data instanceof ArrayAccess)) {
-            throw new InvalidArgumentException(
-                'Invalid data type, must be an array or \ArrayAccess instance.'
-            );
-        }
-
         if (empty($path)) {
             return $data;
         }
@@ -218,7 +206,7 @@ class Hash
      * @param string $token The token being matched.
      * @return bool
      */
-    protected static function _matchToken($key, string $token): bool
+    protected static function _matchToken(mixed $key, string $token): bool
     {
         switch ($token) {
             case '{n}':
@@ -239,7 +227,7 @@ class Hash
      * @param string $selector The patterns to match.
      * @return bool Fitness of expression.
      */
-    protected static function _matches($data, string $selector): bool
+    protected static function _matches(ArrayAccess|array $data, string $selector): bool
     {
         preg_match_all(
             '/(\[ (?P<attr>[^=><!]+?) (\s* (?P<op>[><!]?[=]|[><]) \s* (?P<val>(?:\/.*?\/ | [^\]]+)) )? \])/x',
@@ -310,7 +298,7 @@ class Hash
      * @return array The data with $values inserted.
      * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::insert
      */
-    public static function insert(array $data, string $path, $values = null): array
+    public static function insert(array $data, string $path, mixed $values = null): array
     {
         $noTokens = strpos($path, '[') === false;
         if ($noTokens && strpos($path, '.') === false) {
@@ -356,7 +344,7 @@ class Hash
      * @param mixed $values The values to insert when doing inserts.
      * @return array data.
      */
-    protected static function _simpleOp(string $op, array $data, array $path, $values = null): array
+    protected static function _simpleOp(string $op, array $data, array $path, mixed $values = null): array
     {
         $_list = &$data;
 
@@ -463,8 +451,12 @@ class Hash
      * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::combine
      * @throws \RuntimeException When keys and values count is unequal.
      */
-    public static function combine(array $data, $keyPath, $valuePath = null, ?string $groupPath = null): array
-    {
+    public static function combine(
+        array $data,
+        array|string|null $keyPath,
+        array|string|null $valuePath = null,
+        ?string $groupPath = null
+    ): array {
         if (empty($data)) {
             return [];
         }
@@ -644,12 +636,12 @@ class Hash
      * Recursively filters a data set.
      *
      * @param array $data Either an array to filter, or value when in callback
-     * @param callable|array $callback A function to filter the data with. Defaults to
-     *   `static::_filter()` Which strips out all non-zero empty values.
+     * @param callable|null $callback A function to filter the data with. Defaults to
+     *   all non-empty or zero values.
      * @return array Filtered array
      * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::filter
      */
-    public static function filter(array $data, $callback = ['self', '_filter']): array
+    public static function filter(array $data, ?callable $callback = null): array
     {
         foreach ($data as $k => $v) {
             if (is_array($v)) {
@@ -657,7 +649,7 @@ class Hash
             }
         }
 
-        return array_filter($data, $callback);
+        return array_filter($data, $callback ?? 'static::_filter');
     }
 
     /**
@@ -666,7 +658,7 @@ class Hash
      * @param mixed $var Array to filter.
      * @return bool
      */
-    protected static function _filter($var): bool
+    protected static function _filter(mixed $var): bool
     {
         return $var === 0 || $var === 0.0 || $var === '0' || !empty($var);
     }
@@ -762,7 +754,7 @@ class Hash
      * @return array Merged array
      * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::merge
      */
-    public static function merge(array $data, $merge): array
+    public static function merge(array $data, mixed $merge): array
     {
         $args = array_slice(func_get_args(), 1);
         $return = $data;
@@ -908,7 +900,7 @@ class Hash
      * @return mixed The reduced value.
      * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::reduce
      */
-    public static function reduce(array $data, string $path, callable $function)
+    public static function reduce(array $data, string $path, callable $function): mixed
     {
         $values = (array)static::extract($data, $path);
 
@@ -940,7 +932,7 @@ class Hash
      * @return mixed The results of the applied method.
      * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::apply
      */
-    public static function apply(array $data, string $path, callable $function)
+    public static function apply(array $data, string $path, callable $function): mixed
     {
         $values = (array)static::extract($data, $path);
 
@@ -980,8 +972,12 @@ class Hash
      * @return array Sorted array of data
      * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::sort
      */
-    public static function sort(array $data, string $path, $dir = 'asc', $type = 'regular'): array
-    {
+    public static function sort(
+        array $data,
+        string $path,
+        string|int $dir = 'asc',
+        array|string $type = 'regular'
+    ): array {
         if (empty($data)) {
             return [];
         }
@@ -1015,8 +1011,8 @@ class Hash
         if (is_string($dir)) {
             $dir = strtolower($dir);
         }
-        if (!in_array($dir, [\SORT_ASC, \SORT_DESC], true)) {
-            $dir = $dir === 'asc' ? \SORT_ASC : \SORT_DESC;
+        if (!in_array($dir, [SORT_ASC, SORT_DESC], true)) {
+            $dir = $dir === 'asc' ? SORT_ASC : SORT_DESC;
         }
 
         $ignoreCase = false;
@@ -1030,15 +1026,15 @@ class Hash
         $type = strtolower($type);
 
         if ($type === 'numeric') {
-            $type = \SORT_NUMERIC;
+            $type = SORT_NUMERIC;
         } elseif ($type === 'string') {
-            $type = \SORT_STRING;
+            $type = SORT_STRING;
         } elseif ($type === 'natural') {
-            $type = \SORT_NATURAL;
+            $type = SORT_NATURAL;
         } elseif ($type === 'locale') {
-            $type = \SORT_LOCALE_STRING;
+            $type = SORT_LOCALE_STRING;
         } else {
-            $type = \SORT_REGULAR;
+            $type = SORT_REGULAR;
         }
         if ($ignoreCase) {
             $values = array_map('mb_strtolower', $values);
@@ -1067,10 +1063,10 @@ class Hash
      * Squashes an array to a single hash so it can be sorted.
      *
      * @param array $data The data to squash.
-     * @param mixed $key The key for the data.
+     * @param string|int|null $key The key for the data.
      * @return array
      */
-    protected static function _squash(array $data, $key = null): array
+    protected static function _squash(array $data, string|int|null $key = null): array
     {
         $stack = [];
         foreach ($data as $k => $r) {
@@ -1232,8 +1228,10 @@ class Hash
             $parentId = static::get($result, $parentKeys);
 
             if (isset($idMap[$id][$options['children']])) {
+                /** @psalm-suppress PossiblyInvalidArgument psalm thinks $result could be ArrayAccess */
                 $idMap[$id] = array_merge($result, $idMap[$id]);
             } else {
+                /** @psalm-suppress PossiblyInvalidArgument psalm thinks $result could be ArrayAccess */
                 $idMap[$id] = array_merge($result, [$options['children'] => []]);
             }
             if (!$parentId || !in_array($parentId, $ids)) {

+ 1 - 1
src/Utility/Inflector.php

@@ -184,7 +184,7 @@ class Inflector
      * @param string|false $value Inflected value
      * @return string|false Inflected value on cache hit or false on cache miss.
      */
-    protected static function _cache(string $type, string $key, $value = false)
+    protected static function _cache(string $type, string $key, string|false $value = false): string|false
     {
         $key = '_' . $key;
         $type = '_' . $type;

+ 1 - 1
src/Utility/MergeVariablesTrait.php

@@ -101,7 +101,7 @@ trait MergeVariablesTrait
      * @param bool $isAssoc Whether or not the merging should be done in associative mode.
      * @return mixed The updated value.
      */
-    protected function _mergePropertyData(array $current, array $parent, bool $isAssoc)
+    protected function _mergePropertyData(array $current, array $parent, bool $isAssoc): mixed
     {
         if (!$isAssoc) {
             return array_merge($parent, $current);

+ 4 - 4
src/Utility/Security.php

@@ -54,13 +54,13 @@ class Security
      * @param string|null $algorithm Hashing algo to use (i.e. sha1, sha256 etc.).
      *   Can be any valid algo included in list returned by hash_algos().
      *   If no value is passed the type specified by `Security::$hashType` is used.
-     * @param mixed $salt If true, automatically prepends the value returned by
+     * @param string|bool $salt If true, automatically prepends the value returned by
      *   Security::getSalt() to $string.
      * @return string Hash
      * @throws \RuntimeException
      * @link https://book.cakephp.org/4/en/core-libraries/security.html#hashing-data
      */
-    public static function hash(string $string, ?string $algorithm = null, $salt = false): string
+    public static function hash(string $string, ?string $algorithm = null, string|bool $salt = false): string
     {
         if (empty($algorithm)) {
             $algorithm = static::$hashType;
@@ -160,7 +160,7 @@ class Security
      * @throws \InvalidArgumentException When no compatible crypto extension is available.
      * @psalm-suppress MoreSpecificReturnType
      */
-    public static function engine($instance = null)
+    public static function engine(?OpenSsl $instance = null): OpenSsl
     {
         if ($instance === null && static::$_instance === null) {
             if (extension_loaded('openssl')) {
@@ -274,7 +274,7 @@ class Security
      * @return bool
      * @since 3.6.2
      */
-    public static function constantEquals($original, $compare): bool
+    public static function constantEquals(mixed $original, mixed $compare): bool
     {
         return is_string($original) && is_string($compare) && hash_equals($original, $compare);
     }

+ 6 - 6
src/Utility/Text.php

@@ -317,7 +317,7 @@ class Text
      * @param array|int $options Array of options to use, or an integer to wrap the text to.
      * @return string Formatted text.
      */
-    public static function wrap(string $text, $options = []): string
+    public static function wrap(string $text, array|int $options = []): string
     {
         if (is_numeric($options)) {
             $options = ['width' => $options];
@@ -354,7 +354,7 @@ class Text
      * @param array|int $options Array of options to use, or an integer to wrap the text to.
      * @return string Formatted text.
      */
-    public static function wrapBlock(string $text, $options = []): string
+    public static function wrapBlock(string $text, array|int $options = []): string
     {
         if (is_numeric($options)) {
             $options = ['width' => $options];
@@ -481,7 +481,7 @@ class Text
      * @return string The highlighted text
      * @link https://book.cakephp.org/4/en/core-libraries/text.html#highlighting-substrings
      */
-    public static function highlight(string $text, $phrase, array $options = []): string
+    public static function highlight(string $text, array|string $phrase, array $options = []): string
     {
         if (empty($phrase)) {
             return $text;
@@ -1011,7 +1011,7 @@ class Text
      * @throws \InvalidArgumentException On invalid Unit type.
      * @link https://book.cakephp.org/4/en/core-libraries/text.html#Cake\Utility\Text::parseFileSize
      */
-    public static function parseFileSize(string $size, $default = false)
+    public static function parseFileSize(string $size, mixed $default = false): mixed
     {
         if (ctype_digit($size)) {
             return (int)$size;
@@ -1102,7 +1102,7 @@ class Text
      * @return string
      * @see https://secure.php.net/manual/en/transliterator.transliterate.php
      */
-    public static function transliterate(string $string, $transliterator = null): string
+    public static function transliterate(string $string, Transliterator|string|null $transliterator = null): string
     {
         if (empty($transliterator)) {
             $transliterator = static::$_defaultTransliterator ?: static::$_defaultTransliteratorId;
@@ -1137,7 +1137,7 @@ class Text
      * @see setTransliterator()
      * @see setTransliteratorId()
      */
-    public static function slug(string $string, $options = []): string
+    public static function slug(string $string, array|string $options = []): string
     {
         if (is_string($options)) {
             $options = ['replacement' => $options];

+ 10 - 16
src/Utility/Xml.php

@@ -19,6 +19,7 @@ namespace Cake\Utility;
 use Cake\Utility\Exception\XmlException;
 use Closure;
 use DOMDocument;
+use DOMElement;
 use DOMNode;
 use DOMText;
 use Exception;
@@ -102,7 +103,7 @@ class Xml
      * @return \SimpleXMLElement|\DOMDocument SimpleXMLElement or DOMDocument
      * @throws \Cake\Utility\Exception\XmlException
      */
-    public static function build($input, array $options = [])
+    public static function build(object|array|string $input, array $options = []): SimpleXMLElement|DOMDocument
     {
         $defaults = [
             'return' => 'simplexml',
@@ -120,11 +121,6 @@ class Xml
             return static::_loadXml(file_get_contents($input), $options);
         }
 
-        if (!is_string($input)) {
-            $type = gettype($input);
-            throw new XmlException("Invalid input. {$type} cannot be parsed as XML.");
-        }
-
         if (strpos($input, '<') !== false) {
             return static::_loadXml($input, $options);
         }
@@ -140,7 +136,7 @@ class Xml
      * @return \SimpleXMLElement|\DOMDocument
      * @throws \Cake\Utility\Exception\XmlException
      */
-    protected static function _loadXml(string $input, array $options)
+    protected static function _loadXml(string $input, array $options): SimpleXMLElement|DOMDocument
     {
         return static::load(
             $input,
@@ -167,7 +163,7 @@ class Xml
      * @return \SimpleXMLElement|\DOMDocument
      * @throws \Cake\Utility\Exception\XmlException
      */
-    public static function loadHtml(string $input, array $options = [])
+    public static function loadHtml(string $input, array $options = []): SimpleXMLElement|DOMDocument
     {
         $defaults = [
             'return' => 'simplexml',
@@ -200,7 +196,7 @@ class Xml
      * @return \SimpleXMLElement|\DOMDocument
      * @throws \Cake\Utility\Exception\XmlException
      */
-    protected static function load(string $input, array $options, Closure $callable)
+    protected static function load(string $input, array $options, Closure $callable): SimpleXMLElement|DOMDocument
     {
         $flags = 0;
         if (!empty($options['parseHuge'])) {
@@ -261,7 +257,7 @@ class Xml
      * @return \SimpleXMLElement|\DOMDocument SimpleXMLElement or DOMDocument
      * @throws \Cake\Utility\Exception\XmlException
      */
-    public static function fromArray($input, array $options = [])
+    public static function fromArray(object|array $input, array $options = []): SimpleXMLElement|DOMDocument
     {
         if (is_object($input) && method_exists($input, 'toArray') && is_callable([$input, 'toArray'])) {
             $input = $input->toArray();
@@ -307,7 +303,7 @@ class Xml
      * @return void
      * @throws \Cake\Utility\Exception\XmlException
      */
-    protected static function _fromArray(DOMDocument $dom, $node, &$data, $format): void
+    protected static function _fromArray(DOMDocument $dom, DOMDocument|DOMElement $node, $data, string $format): void
     {
         if (empty($data) || !is_array($data)) {
             return;
@@ -425,19 +421,17 @@ class Xml
     /**
      * Returns this XML structure as an array.
      *
-     * @param \SimpleXMLElement|\DOMDocument|\DOMNode $obj SimpleXMLElement, DOMDocument or DOMNode instance
+     * @param \SimpleXMLElement|\DOMNode $obj SimpleXMLElement, DOMNode instance
      * @return array Array representation of the XML structure.
      * @throws \Cake\Utility\Exception\XmlException
      */
-    public static function toArray($obj): array
+    public static function toArray(SimpleXMLElement|DOMNode $obj): array
     {
         if ($obj instanceof DOMNode) {
             $obj = simplexml_import_dom($obj);
         }
-        if (!($obj instanceof SimpleXMLElement)) {
-            throw new XmlException('The input is not instance of SimpleXMLElement, DOMDocument or DOMNode.');
-        }
         $result = [];
+        /** @var \SimpleXMLElement $obj */
         $namespaces = array_merge(['' => ''], $obj->getNamespaces(true));
         static::_toArray($obj, $result, '', array_keys($namespaces));
 

+ 0 - 29
tests/TestCase/Utility/HashTest.php

@@ -404,25 +404,6 @@ class HashTest extends TestCase
     }
 
     /**
-     * Test get() for invalid $data type
-     */
-    public function testGetInvalidData(): void
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionMessage('Invalid data type, must be an array or \ArrayAccess instance.');
-        Hash::get('string', 'path');
-    }
-
-    /**
-     * Test get() with an invalid path
-     */
-    public function testGetInvalidPath(): void
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        Hash::get(['one' => 'two'], true);
-    }
-
-    /**
      * Test dimensions.
      */
     public function testDimensions(): void
@@ -907,16 +888,6 @@ class HashTest extends TestCase
     }
 
     /**
-     * Test passing invalid argument type
-     */
-    public function testExtractInvalidArgument(): void
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionMessage('Invalid data type, must be an array or \ArrayAccess instance.');
-        Hash::extract('foo', '');
-    }
-
-    /**
      * Test the extraction of a single value filtered by another field.
      *
      * @dataProvider articleDataSets

+ 1 - 1
tests/TestCase/Utility/TextTest.php

@@ -721,7 +721,7 @@ HTML;
         $this->assertSame($expected, $result);
 
         $text = 'This is a test text';
-        $phrases = null;
+        $phrases = '';
         $result = $this->Text->highlight($text, $phrases, ['format' => '<b>\1</b>']);
         $this->assertSame($text, $result);
 

+ 0 - 30
tests/TestCase/Utility/XmlTest.php

@@ -193,8 +193,6 @@ class XmlTest extends TestCase
     public static function invalidDataProvider(): array
     {
         return [
-            [null],
-            [false],
             [''],
             ['http://localhost/notthere.xml'],
         ];
@@ -589,9 +587,6 @@ XML;
     public static function invalidArrayDataProvider(): array
     {
         return [
-            [''],
-            [null],
-            [false],
             [[]],
             [['numeric key as root']],
             [['item1' => '', 'item2' => '']],
@@ -1176,31 +1171,6 @@ XML;
     }
 
     /**
-     * data provider for toArray() failures
-     *
-     * @return array
-     */
-    public static function invalidToArrayDataProvider(): array
-    {
-        return [
-            [new \DateTime()],
-            [[]],
-        ];
-    }
-
-    /**
-     * testToArrayFail method
-     *
-     * @dataProvider invalidToArrayDataProvider
-     * @param mixed $value
-     */
-    public function testToArrayFail($value): void
-    {
-        $this->expectException(\Cake\Utility\Exception\XmlException::class);
-        Xml::toArray($value);
-    }
-
-    /**
      * Test ampersand in text elements.
      */
     public function testAmpInText(): void

+ 1 - 1
tests/TestCase/View/XmlViewTest.php

@@ -130,7 +130,7 @@ class XmlViewTest extends TestCase
                     ],
             ],
         ];
-        $xmlOptions = ['format' => ['format' => 'attributes', 'return' => 'domdocument']];
+        $xmlOptions = ['format' => 'attributes', 'return' => 'domdocument'];
 
         $Controller->set($data);
         $Controller->viewBuilder()