Browse Source

Merge branch 'master' into 3.next

Mark Story 7 years ago
parent
commit
9258019c65

+ 1 - 2
.travis.yml

@@ -72,9 +72,8 @@ script:
   - if [[ $DEFAULT = 1 && $TRAVIS_PHP_VERSION != 7.0 ]]; then vendor/bin/phpunit; fi
 
   - if [[ $PHPCS = 1 ]]; then composer cs-check; fi
-  - if [[ $PHPSTAN = 1 ]]; then composer require --dev "phpstan/phpstan:0.9.*" ; fi
+  - if [[ $PHPSTAN = 1 ]]; then composer require --dev "phpstan/phpstan:^0.10" && vendor/bin/phpstan analyse -c phpstan.neon -l 2 src; fi
   - if [[ $PHPSTAN = 1 ]]; then composer require --dev "phpstan/phpstan-phpunit:0.9.*" ; fi
-  - if [[ $PHPSTAN = 1 ]]; then vendor/bin/phpstan analyse -c phpstan.neon -l 2 src; fi
 
 after_success:
   - if [[ $DEFAULT = 1 && $TRAVIS_PHP_VERSION = 7.0 ]]; then bash <(curl -s https://codecov.io/bash); fi

+ 6 - 4
phpstan.neon

@@ -14,7 +14,6 @@ parameters:
         - '#Access to undefined constant PDO::SQLSRV_ATTR_ENCODING#'
         - '#Access to undefined constant PDO::SQLSRV_ENCODING_BINARY#'
         - '#Constant XC_TYPE_VAR not found#'
-        - '#Call to an undefined method Psr\\Http\\Message\\ResponseInterface::getCookies\(\)#'
         - '#Access to an undefined property Psr\\Http\\Message\\UriInterface::\$webroot#'
         - '#Access to an undefined property Psr\\Http\\Message\\UriInterface::\$base#'
         - '#Result of method Cake\\Http\\Response::send\(\) \(void\) is used#'
@@ -26,10 +25,13 @@ parameters:
         - '#Variable \$config in isset\(\) is never defined#'
         - '#Call to static method id\(\) on an unknown class PHPUnit_Runner_Version#'
         - '#Call to an undefined method DateTimeInterface::i18nFormat\(\)#'
-        - '#Call to an undefined method object::__toString\(\)#'
-        - '#Call to an undefined method object::toArray\(\)#'
-        - '#Call to an undefined method object::__debugInfo\(\)#'
         - '#Cannot call method lastInsertId\(\) on null#'
+        - '#Call to an undefined method Cake\\Auth\\Storage\\StorageInterface::getConfig\(\)#'
+        - '#Call to an undefined method Cake\\Auth\\Storage\\StorageInterface::setConfig\(\)#'
+        - '#Variable \$_SESSION in isset\(\) always exists and is not nullable#'
+        - '#Call to an undefined method Traversable::getInnerIterator\(\)#'
+        - '#PHPDoc tag @throws with type PHPUnit\\Exception is not subtype of Throwable#'
+        - '#Binary operation "\+=" between \(array\&hasOffset\(.*\)\) and array results in an error#'
     earlyTerminatingMethodCalls:
         Cake\Shell\Shell:
             - abort

+ 1 - 0
src/Collection/Iterator/FilterIterator.php

@@ -67,6 +67,7 @@ class FilterIterator extends Collection
      */
     public function unwrap()
     {
+        /** @var \IteratorIterator $filter */
         $filter = $this->getInnerIterator();
         $iterator = $filter->getInnerIterator();
 

+ 1 - 1
src/Console/ConsoleInput.php

@@ -77,7 +77,7 @@ class ConsoleInput
     public function dataAvailable($timeout = 0)
     {
         $readFds = [$this->_input];
-        $readyFds = stream_select($readFds, $writeFds, $errorFds, $timeout);
+        $readyFds = stream_select($readFds, null, null, $timeout);
 
         return ($readyFds > 0);
     }

+ 1 - 1
src/Controller/Component/AuthComponent.php

@@ -881,7 +881,7 @@ class AuthComponent extends Component
      *
      * @param \Cake\Auth\Storage\StorageInterface|null $storage Sets provided
      *   object as storage or if null returns configured storage object.
-     * @return \Cake\Auth\Storage\StorageInterface|\Cake\Core\InstanceConfigTrait|null
+     * @return \Cake\Auth\Storage\StorageInterface|null
      */
     public function storage(StorageInterface $storage = null)
     {

+ 1 - 1
src/Core/PluginCollection.php

@@ -104,7 +104,7 @@ class PluginCollection implements Iterator, Countable
      *
      * @param string $name The plugin name to locate a path for. Will return '' when a plugin cannot be found.
      * @return string
-     * @throws Cake\Core\Exception\MissingPluginException when a plugin path cannot be resolved.
+     * @throws \Cake\Core\Exception\MissingPluginException when a plugin path cannot be resolved.
      * @internal
      */
     public function findPath($name)

+ 1 - 1
src/Datasource/EntityTrait.php

@@ -1141,7 +1141,7 @@ trait EntityTrait
     /**
      * Read the error(s) from one or many objects.
      *
-     * @param array|\Cake\Datasource\EntityTrait $object The object to read errors from.
+     * @param array|\Cake\Datasource\EntityInterface $object The object to read errors from.
      * @param string|null $path The field name for errors.
      * @return array
      */

+ 3 - 1
src/Datasource/QueryTrait.php

@@ -481,9 +481,11 @@ trait QueryTrait
     {
         $entity = $this->first();
         if (!$entity) {
+            /** @var \Cake\ORM\Table $table */
+            $table = $this->getRepository();
             throw new RecordNotFoundException(sprintf(
                 'Record not found in table "%s"',
-                $this->getRepository()->getTable()
+                $table->getTable()
             ));
         }
 

+ 4 - 1
src/Http/BaseApplication.php

@@ -99,7 +99,10 @@ abstract class BaseApplication implements
             $plugin = $name;
         }
         if (!$plugin instanceof PluginInterface) {
-            throw new InvalidArgumentException("The `{$name}` plugin does not implement Cake\Core\PluginInterface.");
+            throw new InvalidArgumentException(sprintf(
+                "The `%s` plugin does not implement Cake\Core\PluginInterface.",
+                get_class($plugin)
+            ));
         }
         $this->plugins->add($plugin);
 

+ 4 - 1
src/Http/CallbackStream.php

@@ -39,7 +39,10 @@ class CallbackStream extends BaseCallbackStream
     public function getContents()
     {
         $callback = $this->detach();
-        $result = $callback ? $callback() : '';
+        $result = '';
+        if (is_callable($callback)) {
+            $result = $callback();
+        }
         if (!is_string($result)) {
             return '';
         }

+ 1 - 1
src/Http/Session.php

@@ -608,7 +608,7 @@ class Session
      */
     protected function _timedOut()
     {
-        $time = $this->read('Config.time');
+        $time = (int)$this->read('Config.time');
         $result = false;
 
         $checkTime = $time !== null && $this->_lifetime > 0;

+ 3 - 3
src/I18n/RelativeTimeFormatter.php

@@ -180,15 +180,15 @@ class RelativeTimeFormatter
     /**
      * Calculate the data needed to format a relative difference string.
      *
-     * @param \DateTime $futureTime The time from the future.
-     * @param \DateTime $pastTime The time from the past.
+     * @param int|string $futureTime The timestamp from the future.
+     * @param int|string $pastTime The timestamp from the past.
      * @param bool $backwards Whether or not the difference was backwards.
      * @param array $options An array of options.
      * @return array An array of values.
      */
     protected function _diffData($futureTime, $pastTime, $backwards, $options)
     {
-        $diff = $futureTime - $pastTime;
+        $diff = (int)$futureTime - (int)$pastTime;
 
         // If more than a week, then take into account the length of months
         if ($diff >= 604800) {

+ 2 - 0
src/ORM/Association/BelongsToMany.php

@@ -1129,6 +1129,7 @@ class BelongsToMany extends Association
     protected function _appendJunctionJoin($query, $conditions)
     {
         $name = $this->_junctionAssociationName();
+        /** @var array $joins */
         $joins = $query->clause('join');
         $matching = [
             $name => [
@@ -1386,6 +1387,7 @@ class BelongsToMany extends Association
         $assocForeignKey = (array)$belongsTo->getForeignKey();
         $sourceKey = $sourceEntity->extract((array)$source->getPrimaryKey());
 
+        $unions = [];
         foreach ($missing as $key) {
             $unions[] = $hasMany->find('all')
                 ->where(array_combine($foreignKey, $sourceKey))

+ 2 - 2
src/ORM/Behavior/TimestampBehavior.php

@@ -61,7 +61,7 @@ class TimestampBehavior extends Behavior
     /**
      * Current timestamp
      *
-     * @var \DateTime
+     * @var \Cake\I18n\Time
      */
     protected $_ts;
 
@@ -136,7 +136,7 @@ class TimestampBehavior extends Behavior
      *
      * @param \DateTime|null $ts Timestamp
      * @param bool $refreshTimestamp If true timestamp is refreshed.
-     * @return \DateTime
+     * @return \Cake\I18n\Time
      */
     public function timestamp(DateTime $ts = null, $refreshTimestamp = false)
     {

+ 1 - 0
src/ORM/Marshaller.php

@@ -248,6 +248,7 @@ class Marshaller
         } elseif (is_string($options['validate'])) {
             $validator = $this->_table->getValidator($options['validate']);
         } elseif (is_object($options['validate'])) {
+            /** @var \Cake\Validation\Validator $validator */
             $validator = $options['validate'];
         }
 

+ 29 - 12
src/ORM/Query.php

@@ -1084,10 +1084,11 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface
     public function triggerBeforeFind()
     {
         if (!$this->_beforeFindFired && $this->_type === 'select') {
-            $table = $this->getRepository();
             $this->_beforeFindFired = true;
-            /* @var \Cake\Event\EventDispatcherInterface $table */
-            $table->dispatchEvent('Model.beforeFind', [
+
+            /** @var \Cake\Event\EventDispatcherInterface $repository */
+            $repository = $this->getRepository();
+            $repository->dispatchEvent('Model.beforeFind', [
                 $this,
                 new ArrayObject($this->_options),
                 !$this->isEagerLoaded()
@@ -1146,11 +1147,14 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface
             return;
         }
 
+        /** @var \Cake\ORM\Table $repository */
+        $repository = $this->getRepository();
+
         if (empty($this->_parts['from'])) {
-            $this->from([$this->_repository->getAlias() => $this->_repository->getTable()]);
+            $this->from([$repository->getAlias() => $repository->getTable()]);
         }
         $this->_addDefaultFields();
-        $this->getEagerLoader()->attachAssociations($this, $this->_repository, !$this->_hasFields);
+        $this->getEagerLoader()->attachAssociations($this, $repository, !$this->_hasFields);
         $this->_addDefaultSelectTypes();
     }
 
@@ -1165,13 +1169,16 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface
         $select = $this->clause('select');
         $this->_hasFields = true;
 
+        /** @var \Cake\ORM\Table $repository */
+        $repository = $this->getRepository();
+
         if (!count($select) || $this->_autoFields === true) {
             $this->_hasFields = false;
-            $this->select($this->getRepository()->getSchema()->columns());
+            $this->select($repository->getSchema()->columns());
             $select = $this->clause('select');
         }
 
-        $aliased = $this->aliasFields($select, $this->getRepository()->getAlias());
+        $aliased = $this->aliasFields($select, $repository->getAlias());
         $this->select($aliased, true);
     }
 
@@ -1208,7 +1215,10 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface
      */
     public function find($finder, array $options = [])
     {
-        return $this->getRepository()->callFinder($finder, $this, $options);
+        /** @var \Cake\ORM\Table $table */
+        $table = $this->getRepository();
+
+        return $table->callFinder($finder, $this, $options);
     }
 
     /**
@@ -1235,7 +1245,11 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface
      */
     public function update($table = null)
     {
-        $table = $table ?: $this->getRepository()->getTable();
+        if (!$table) {
+            /** @var \Cake\ORM\Table $repository */
+            $repository = $this->getRepository();
+            $table = $repository->getTable();
+        }
 
         return parent::update($table);
     }
@@ -1251,8 +1265,9 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface
      */
     public function delete($table = null)
     {
-        $repo = $this->getRepository();
-        $this->from([$repo->getAlias() => $repo->getTable()]);
+        /** @var \Cake\ORM\Table $repository */
+        $repository = $this->getRepository();
+        $this->from([$repository->getAlias() => $repository->getTable()]);
 
         return parent::delete();
     }
@@ -1272,7 +1287,9 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface
      */
     public function insert(array $columns, array $types = [])
     {
-        $table = $this->getRepository()->getTable();
+        /** @var \Cake\ORM\Table $repository */
+        $repository = $this->getRepository();
+        $table = $repository->getTable();
         $this->into($table);
 
         return parent::insert($columns, $types);

+ 2 - 1
src/ORM/ResultSet.php

@@ -175,6 +175,7 @@ class ResultSet implements ResultSetInterface
      */
     public function __construct($query, $statement)
     {
+        /** @var \Cake\ORM\Table $repository */
         $repository = $query->getRepository();
         $this->_statement = $statement;
         $this->_driver = $query->getConnection()->getDriver();
@@ -447,7 +448,7 @@ class ResultSet implements ResultSetInterface
     {
         $types = [];
         $schema = $table->getSchema();
-        $map = array_keys(Type::map() + ['string' => 1, 'text' => 1, 'boolean' => 1]);
+        $map = array_keys((array)Type::getMap() + ['string' => 1, 'text' => 1, 'boolean' => 1]);
         $typeMap = array_combine(
             $map,
             array_map(['Cake\Database\Type', 'build'], $map)

+ 2 - 2
src/ORM/Table.php

@@ -1894,7 +1894,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
             $options = $options->toArray();
         }
 
-        $options = new ArrayObject($options + [
+        $options = new ArrayObject((array)$options + [
             'atomic' => true,
             'associated' => true,
             'checkRules' => true,
@@ -2272,7 +2272,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
      */
     public function delete(EntityInterface $entity, $options = [])
     {
-        $options = new ArrayObject($options + [
+        $options = new ArrayObject((array)$options + [
             'atomic' => true,
             'checkRules' => true,
             '_primary' => true,

+ 0 - 1
src/Routing/Middleware/RoutingMiddleware.php

@@ -124,7 +124,6 @@ class RoutingMiddleware
      * @param \Psr\Http\Message\ResponseInterface $response The response.
      * @param callable $next The next middleware to call.
      * @return \Psr\Http\Message\ResponseInterface A response.
-     * @throws \Cake\Routing\InvalidArgumentException
      */
     public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
     {

+ 2 - 2
src/Routing/RouteBuilder.php

@@ -388,7 +388,7 @@ class RouteBuilder
      */
     public function resources($name, $options = [], $callback = null)
     {
-        if (is_callable($options) && $callback === null) {
+        if (is_callable($options)) {
             $callback = $options;
             $options = [];
         }
@@ -982,7 +982,7 @@ class RouteBuilder
      */
     public function scope($path, $params, $callback = null)
     {
-        if ($callback === null) {
+        if (is_callable($params)) {
             $callback = $params;
             $params = [];
         }

+ 2 - 2
src/TestSuite/ConsoleIntegrationTestTrait.php

@@ -49,14 +49,14 @@ trait ConsoleIntegrationTestTrait
     /**
      * Console output stub
      *
-     * @var \Cake\Console\ConsoleOutput|\PHPUnit_Framework_MockObject_MockObject|null
+     * @var \Cake\TestSuite\Stub\ConsoleOutput|\PHPUnit_Framework_MockObject_MockObject|null
      */
     private $out;
 
     /**
      * Console error output stub
      *
-     * @var \Cake\Console\ConsoleOutput|\PHPUnit_Framework_MockObject_MockObject|null
+     * @var \Cake\TestSuite\Stub\ConsoleOutput|\PHPUnit_Framework_MockObject_MockObject|null
      */
     private $err;
 

+ 1 - 1
src/Utility/Text.php

@@ -1041,7 +1041,7 @@ class Text
             $i = array_search(substr($size, -1), ['K', 'M', 'G', 'T', 'P']);
         }
         if ($i !== false) {
-            $size = substr($size, 0, $l);
+            $size = (float)substr($size, 0, $l);
 
             return $size * pow(1024, $i + 1);
         }

+ 8 - 9
src/Validation/Validation.php

@@ -775,12 +775,15 @@ class Validation
     /**
      * Checks that value has a valid file extension.
      *
-     * @param string|array $check Value to check
+     * @param string|array|\Psr\Http\Message\UploadedFileInterface $check Value to check
      * @param array $extensions file extensions to allow. By default extensions are 'gif', 'jpeg', 'png', 'jpg'
      * @return bool Success
      */
     public static function extension($check, $extensions = ['gif', 'jpeg', 'png', 'jpg'])
     {
+        if ($check instanceof UploadedFileInterface) {
+            return static::extension($check->getClientFilename(), $extensions);
+        }
         if (is_array($check)) {
             $check = isset($check['name']) ? $check['name'] : array_shift($check);
 
@@ -1108,7 +1111,7 @@ class Validation
         }
 
         for ($position = ($length % 2); $position < $length; $position += 2) {
-            $number = $check[$position] * 2;
+            $number = (int)$check[$position] * 2;
             $sum += ($number < 10) ? $number : $number - 9;
         }
 
@@ -1258,7 +1261,7 @@ class Validation
      * - `optional` - Whether or not this file is optional. Defaults to false.
      *   If true a missing file will pass the validator regardless of other constraints.
      *
-     * @param array $file The uploaded file data from PHP.
+     * @param array|\Psr\Http\Message\UploadedFileInterface $file The uploaded file data from PHP.
      * @param array $options An array of options for the validation.
      * @return bool
      */
@@ -1310,7 +1313,7 @@ class Validation
     /**
      * Validates the size of an uploaded image.
      *
-     * @param array $file The uploaded file data from PHP.
+     * @param array|\Psr\Http\Message\UploadedFileInterface  $file The uploaded file data from PHP.
      * @param array $options Options to validate width and height.
      * @return bool
      */
@@ -1320,11 +1323,7 @@ class Validation
             throw new InvalidArgumentException('Invalid image size validation parameters! Missing `width` and / or `height`.');
         }
 
-        if ($file instanceof UploadedFileInterface) {
-            $file = $file->getStream()->getContents();
-        } elseif (is_array($file) && isset($file['tmp_name'])) {
-            $file = $file['tmp_name'];
-        }
+        $file = static::getFilename($file);
 
         list($width, $height) = getimagesize($file);
 

+ 1 - 1
src/View/Helper/TimeHelper.php

@@ -371,7 +371,7 @@ class TimeHelper extends Helper
      */
     public function i18nFormat($date, $format = null, $invalid = false, $timezone = null)
     {
-        if (!isset($date)) {
+        if ($date === null) {
             return $invalid;
         }
         $timezone = $this->_getTimezone($timezone);

+ 1 - 1
tests/PHPStan/AssociationTableMixinClassReflectionExtension.php

@@ -23,7 +23,7 @@ class AssociationTableMixinClassReflectionExtension implements PropertiesClassRe
      * @param Broker $broker Class reflection broker
      * @return void
      */
-    public function setBroker(Broker $broker)
+    public function setBroker(Broker $broker): void
     {
         $this->broker = $broker;
     }

+ 30 - 0
tests/TestCase/Validation/ValidationTest.php

@@ -2337,6 +2337,20 @@ class ValidationTest extends TestCase
     }
 
     /**
+     * Test extension with a PSR7 object
+     */
+    public function testExtensionPsr7()
+    {
+        $file = WWW_ROOT . 'test_theme' . DS . 'img' . DS . 'test.jpg';
+
+        $upload = new UploadedFile($file, 5308, UPLOAD_ERR_OK, 'extension.jpeg', 'image/jpeg');
+        $this->assertTrue(Validation::extension($upload));
+
+        $upload = new UploadedFile($file, 163, UPLOAD_ERR_OK, 'no_php_extension', 'text/plain');
+        $this->assertFalse(Validation::extension($upload));
+    }
+
+    /**
      * testMoney method
      *
      * @return void
@@ -3118,6 +3132,22 @@ class ValidationTest extends TestCase
     }
 
     /**
+     * Test imageSize with a PSR7 object
+     *
+     * @return void
+     */
+    public function testImageSizePsr7()
+    {
+        $image = WWW_ROOT . 'test_theme' . DS . 'img' . DS . 'test.jpg';
+        $upload = new UploadedFile($image, 5308, UPLOAD_ERR_OK, 'test.jpg', 'image/jpeg');
+
+        $this->assertTrue(Validation::imageSize($upload, [
+            'width' => [Validation::COMPARE_GREATER, 100],
+            'height' => [Validation::COMPARE_GREATER, 100],
+        ]));
+    }
+
+    /**
      * Test imageHeight
      *
      * @return void