Browse Source

Merge branch '3.next' of github.com:cakephp/cakephp into 3.next

Mark Story 6 years ago
parent
commit
7280b292b1

+ 6 - 2
src/Http/Response.php

@@ -46,6 +46,10 @@ class Response implements ResponseInterface
 {
     use MessageTrait;
 
+    const STATUS_CODE_MIN = 100;
+
+    const STATUS_CODE_MAX = 599;
+
     /**
      * Allowed HTTP status codes and their default description.
      *
@@ -992,7 +996,7 @@ class Response implements ResponseInterface
      */
     protected function _setStatus($code, $reasonPhrase = '')
     {
-        if (!isset($this->_statusCodes[$code])) {
+        if ($code < static::STATUS_CODE_MIN || $code > static::STATUS_CODE_MAX) {
             throw new InvalidArgumentException(sprintf(
                 'Invalid status code: %s. Use a valid HTTP status code in range 1xx - 5xx.',
                 $code
@@ -1000,7 +1004,7 @@ class Response implements ResponseInterface
         }
 
         $this->_status = $code;
-        if (empty($reasonPhrase)) {
+        if ($reasonPhrase === '' && isset($this->_statusCodes[$code])) {
             $reasonPhrase = $this->_statusCodes[$code];
         }
         $this->_reasonPhrase = $reasonPhrase;

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

@@ -353,10 +353,17 @@ class BelongsToMany extends Association
     {
         $junctionAlias = $junction->getAlias();
         $sAlias = $source->getAlias();
+        $tAlias = $target->getAlias();
+
+        $targetBindingKey = null;
+        if ($junction->hasAssociation($tAlias)) {
+            $targetBindingKey = $junction->getAssociation($tAlias)->getBindingKey();
+        }
 
         if (!$target->hasAssociation($junctionAlias)) {
             $target->hasMany($junctionAlias, [
                 'targetTable' => $junction,
+                'bindingKey' => $targetBindingKey,
                 'foreignKey' => $this->getTargetForeignKey(),
                 'strategy' => $this->_strategy,
             ]);
@@ -391,9 +398,17 @@ class BelongsToMany extends Association
     protected function _generateSourceAssociations($junction, $source)
     {
         $junctionAlias = $junction->getAlias();
+        $sAlias = $source->getAlias();
+
+        $sourceBindingKey = null;
+        if ($junction->hasAssociation($sAlias)) {
+            $sourceBindingKey = $junction->getAssociation($sAlias)->getBindingKey();
+        }
+
         if (!$source->hasAssociation($junctionAlias)) {
             $source->hasMany($junctionAlias, [
                 'targetTable' => $junction,
+                'bindingKey' => $sourceBindingKey,
                 'foreignKey' => $this->getForeignKey(),
                 'strategy' => $this->_strategy,
             ]);
@@ -822,7 +837,7 @@ class BelongsToMany extends Association
         $belongsTo = $junction->getAssociation($target->getAlias());
         $foreignKey = (array)$this->getForeignKey();
         $assocForeignKey = (array)$belongsTo->getForeignKey();
-        $targetPrimaryKey = (array)$target->getPrimaryKey();
+        $targetBindingKey = (array)$belongsTo->getBindingKey();
         $bindingKey = (array)$this->getBindingKey();
         $jointProperty = $this->_junctionProperty;
         $junctionRegistryAlias = $junction->getRegistryAlias();
@@ -833,7 +848,7 @@ class BelongsToMany extends Association
                 $joint = new $entityClass([], ['markNew' => true, 'source' => $junctionRegistryAlias]);
             }
             $sourceKeys = array_combine($foreignKey, $sourceEntity->extract($bindingKey));
-            $targetKeys = array_combine($assocForeignKey, $e->extract($targetPrimaryKey));
+            $targetKeys = array_combine($assocForeignKey, $e->extract($targetBindingKey));
 
             $changedKeys = (
                 $sourceKeys !== $joint->extract($foreignKey) ||

+ 7 - 3
src/ORM/Table.php

@@ -2282,13 +2282,17 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
             }
         };
 
+        /** @var \Cake\Datasource\EntityInterface|null $failed */
+        $failed = null;
         try {
-            $failed = $this->getConnection()
-                ->transactional(function () use ($entities, $options, &$isNew) {
+            $this->getConnection()
+                ->transactional(function () use ($entities, $options, &$isNew, &$failed) {
                     foreach ($entities as $key => $entity) {
                         $isNew[$key] = $entity->isNew();
                         if ($this->save($entity, $options) === false) {
-                            return $entity;
+                            $failed = $entity;
+
+                            return false;
                         }
                     }
                 });

+ 19 - 3
src/TestSuite/MockBuilder.php

@@ -30,12 +30,28 @@ class MockBuilder extends BaseMockBuilder
      */
     public function getMock()
     {
-        $errorLevel = error_reporting();
-        error_reporting(E_ALL ^ E_DEPRECATED);
+        static::setSupressedErrorHandler();
+
         try {
             return parent::getMock();
         } finally {
-            error_reporting($errorLevel);
+            restore_error_handler();
         }
     }
+
+    /**
+     * Set error handler to supress `ReflectionType::__toString()` deprecation warning
+     *
+     * @return void
+     */
+    public static function setSupressedErrorHandler()
+    {
+        $previousHandler = set_error_handler(function ($code, $description, $file = null, $line = null, $context = null) use (&$previousHandler) {
+            if (($code & E_DEPRECATED) && ($description === 'Function ReflectionType::__toString() is deprecated')) {
+                return true;
+            }
+
+            return $previousHandler($code, $description, $file, $line, $context);
+        });
+    }
 }

+ 6 - 6
src/TestSuite/TestCase.php

@@ -753,8 +753,8 @@ abstract class TestCase extends BaseTestCase
         $callAutoload = true,
         $cloneArguments = false
     ) {
-        $errorLevel = error_reporting();
-        error_reporting(E_ALL ^ E_DEPRECATED);
+        MockBuilder::setSupressedErrorHandler();
+
         try {
             return parent::getMockClass(
                 $originalClassName,
@@ -767,7 +767,7 @@ abstract class TestCase extends BaseTestCase
                 $cloneArguments
             );
         } finally {
-            error_reporting($errorLevel);
+            restore_error_handler();
         }
     }
 
@@ -784,8 +784,8 @@ abstract class TestCase extends BaseTestCase
         $mockedMethods = [],
         $cloneArguments = false
     ) {
-        $errorLevel = error_reporting();
-        error_reporting(E_ALL ^ E_DEPRECATED);
+        MockBuilder::setSupressedErrorHandler();
+
         try {
             return parent::getMockForAbstractClass(
                 $originalClassName,
@@ -798,7 +798,7 @@ abstract class TestCase extends BaseTestCase
                 $cloneArguments
             );
         } finally {
-            error_reporting($errorLevel);
+            restore_error_handler();
         }
     }
 

+ 14 - 8
src/Utility/Hash.php

@@ -452,12 +452,12 @@ class Hash
      * following the path specified in `$groupPath`.
      *
      * @param array $data Array from where to extract keys and values
-     * @param string $keyPath A dot-separated string.
+     * @param string|null $keyPath A dot-separated string. If null it will create a numbered array.
      * @param string|null $valuePath A dot-separated string.
      * @param string|null $groupPath A dot-separated string.
      * @return array Combined array
      * @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::combine
-     * @throws \RuntimeException When keys and values count is unequal.
+     * @throws \RuntimeException When keys is an array, and keys and values count is unequal.
      */
     public static function combine(array $data, $keyPath, $valuePath = null, $groupPath = null)
     {
@@ -468,10 +468,12 @@ class Hash
         if (is_array($keyPath)) {
             $format = array_shift($keyPath);
             $keys = static::format($data, $keyPath, $format);
+        } elseif ($keyPath === null) {
+            $keys = $keyPath;
         } else {
             $keys = static::extract($data, $keyPath);
         }
-        if (empty($keys)) {
+        if ($keyPath !== null && empty($keys)) {
             return [];
         }
 
@@ -483,10 +485,10 @@ class Hash
             $vals = static::extract($data, $valuePath);
         }
         if (empty($vals)) {
-            $vals = array_fill(0, count($keys), null);
+            $vals = array_fill(0, $keys === null ? count($data) : count($keys), null);
         }
 
-        if (count($keys) !== count($vals)) {
+        if (is_array($keys) && count($keys) !== count($vals)) {
             throw new RuntimeException(
                 'Hash::combine() needs an equal number of keys + values.'
             );
@@ -495,7 +497,7 @@ class Hash
         if ($groupPath !== null) {
             $group = static::extract($data, $groupPath);
             if (!empty($group)) {
-                $c = count($keys);
+                $c = is_array($keys) ? count($keys) : count($vals);
                 $out = [];
                 for ($i = 0; $i < $c; $i++) {
                     if (!isset($group[$i])) {
@@ -504,7 +506,11 @@ class Hash
                     if (!isset($out[$group[$i]])) {
                         $out[$group[$i]] = [];
                     }
-                    $out[$group[$i]][$keys[$i]] = $vals[$i];
+                    if ($keys === null) {
+                        $out[$group[$i]][] = $vals[$i];
+                    } else {
+                        $out[$group[$i]][$keys[$i]] = $vals[$i];
+                    }
                 }
 
                 return $out;
@@ -514,7 +520,7 @@ class Hash
             return [];
         }
 
-        return array_combine($keys, $vals);
+        return array_combine($keys === null ? range(0, count($vals) - 1) : $keys, $vals);
     }
 
     /**

+ 7 - 1
tests/TestCase/Http/ResponseTest.php

@@ -2967,7 +2967,7 @@ class ResponseTest extends TestCase
     }
 
     /**
-     * Test with protocol.
+     * Test with status code.
      *
      * @return void
      */
@@ -2976,12 +2976,18 @@ class ResponseTest extends TestCase
         $response = new Response();
         $statusCode = $response->getStatusCode();
         $this->assertEquals(200, $statusCode);
+
         $response2 = $response->withStatus(404);
         $statusCode = $response2->getStatusCode();
         $this->assertEquals(404, $statusCode);
+
         $statusCode = $response->getStatusCode();
         $this->assertEquals(200, $statusCode);
         $this->assertNotEquals($response, $response2);
+
+        $response3 = $response->withStatus(111);
+        $this->assertEquals(111, $response3->getStatusCode());
+        $this->assertSame('', $response3->getReasonPhrase());
     }
 
     /**

+ 76 - 0
tests/TestCase/ORM/Association/BelongsToManyTest.php

@@ -212,6 +212,9 @@ class BelongsToManyTest extends TestCase
         $this->assertSame($assoc->getStrategy(), $this->tag->getAssociation('Articles')->getStrategy());
         $this->assertSame($assoc->getStrategy(), $this->tag->getAssociation('ArticlesTags')->getStrategy());
         $this->assertSame($assoc->getStrategy(), $this->article->getAssociation('ArticlesTags')->getStrategy());
+
+        $this->assertSame($this->article->getPrimaryKey(), $junction->getAssociation('Articles')->getBindingKey());
+        $this->assertSame($this->tag->getPrimaryKey(), $junction->getAssociation('Tags')->getBindingKey());
     }
 
     /**
@@ -1424,4 +1427,77 @@ class BelongsToManyTest extends TestCase
         // The inner join on special_tags excludes the results.
         $this->assertEquals(0, $query->count());
     }
+
+    /**
+     * Test custom binding key for target table association
+     *
+     * @return void
+     */
+    public function testCustomTargetBindingKeyContain()
+    {
+        $this->getTableLocator()->get('ArticlesTags')
+            ->belongsTo('SpecialTags', [
+                'bindingKey' => 'tag_id',
+                'foreignKey' => 'tag_id',
+            ]);
+
+        $table = $this->getTableLocator()->get('Articles');
+        $table->belongsToMany('SpecialTags', [
+            'through' => 'ArticlesTags',
+            'targetForeignKey' => 'tag_id',
+        ]);
+
+        $results = $table->find()
+            ->contain('SpecialTags', function ($query) {
+                return $query->order(['SpecialTags.tag_id']);
+            })
+            ->where(['id' => 2])
+            ->toArray();
+
+        $this->assertCount(1, $results);
+        $this->assertCount(2, $results[0]->special_tags);
+
+        $this->assertSame(2, $results[0]->special_tags[0]->id);
+        $this->assertSame(1, $results[0]->special_tags[0]->tag_id);
+
+        $this->assertSame(1, $results[0]->special_tags[1]->id);
+        $this->assertSame(3, $results[0]->special_tags[1]->tag_id);
+    }
+
+    /**
+     * Test custom binding key for target table association
+     *
+     * @return void
+     */
+    public function testCustomTargetBindingKeyLink()
+    {
+        $this->getTableLocator()->get('ArticlesTags')
+            ->belongsTo('SpecialTags', [
+                'bindingKey' => 'tag_id',
+                'foreignKey' => 'tag_id',
+            ]);
+
+        $table = $this->getTableLocator()->get('Articles');
+        $table->belongsToMany('SpecialTags', [
+            'through' => 'ArticlesTags',
+            'targetForeignKey' => 'tag_id',
+        ]);
+
+        $specialTag = $table->SpecialTags->newEntity([
+            'article_id' => 2,
+            'tag_id' => 2,
+        ]);
+        $table->SpecialTags->save($specialTag);
+
+        $article = $table->get(2);
+        $this->assertTrue($table->SpecialTags->link($article, [$specialTag]));
+
+        $results = $table->find()
+            ->contain('SpecialTags')
+            ->where(['id' => 2])
+            ->toArray();
+
+        $this->assertCount(1, $results);
+        $this->assertCount(3, $results[0]->special_tags);
+    }
 }

+ 2 - 0
tests/TestCase/ORM/TableTest.php

@@ -2813,6 +2813,7 @@ class TableTest extends TestCase
     public function testSaveManyFailed()
     {
         $table = $this->getTableLocator()->get('authors');
+        $expectedCount = $table->find()->count();
         $entities = [
             new Entity(['name' => 'mark']),
             new Entity(['name' => 'jose']),
@@ -2821,6 +2822,7 @@ class TableTest extends TestCase
         $result = $table->saveMany($entities);
 
         $this->assertFalse($result);
+        $this->assertSame($expectedCount, $table->find()->count());
         foreach ($entities as $entity) {
             $this->assertTrue($entity->isNew());
         }

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

@@ -2309,6 +2309,41 @@ class HashTest extends TestCase
     }
 
     /**
+     * test combine() with null key path.
+     *
+     * @return void
+     */
+    public function testCombineWithNullKeyPath()
+    {
+        $result = Hash::combine([], null, '{n}.User.Data');
+        $this->assertEmpty($result);
+
+        $a = static::userData();
+
+        $result = Hash::combine($a, null);
+        $expected = [0 => null, 1 => null, 2 => null];
+        $this->assertEquals($expected, $result);
+
+        $result = Hash::combine($a, null, '{n}.User.non-existant');
+        $expected = [0 => null, 1 => null, 2 => null];
+        $this->assertEquals($expected, $result);
+
+        $result = Hash::combine($a, null, '{n}.User.Data');
+        $expected = [
+            0 => ['user' => 'mariano.iglesias', 'name' => 'Mariano Iglesias'],
+            1 => ['user' => 'phpnut', 'name' => 'Larry E. Masters'],
+            2 => ['user' => 'gwoo', 'name' => 'The Gwoo']];
+        $this->assertEquals($expected, $result);
+
+        $result = Hash::combine($a, null, '{n}.User.Data.name');
+        $expected = [
+            0 => 'Mariano Iglesias',
+            1 => 'Larry E. Masters',
+            2 => 'The Gwoo'];
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
      * test combine() giving errors on key/value length mismatches.
      *
      * @return void
@@ -2359,6 +2394,18 @@ class HashTest extends TestCase
         ];
         $this->assertEquals($expected, $result);
 
+        $result = Hash::combine($a, null, '{n}.User.Data', '{n}.User.group_id');
+        $expected = [
+            1 => [
+                0 => ['user' => 'mariano.iglesias', 'name' => 'Mariano Iglesias'],
+                1 => ['user' => 'gwoo', 'name' => 'The Gwoo']
+            ],
+            2 => [
+                0 => ['user' => 'phpnut', 'name' => 'Larry E. Masters']
+            ]
+        ];
+        $this->assertEquals($expected, $result);
+
         $result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.name', '{n}.User.group_id');
         $expected = [
             1 => [
@@ -2371,6 +2418,18 @@ class HashTest extends TestCase
         ];
         $this->assertEquals($expected, $result);
 
+        $result = Hash::combine($a, null, '{n}.User.Data.name', '{n}.User.group_id');
+        $expected = [
+            1 => [
+                0 => 'Mariano Iglesias',
+                1 => 'The Gwoo'
+            ],
+            2 => [
+                0 => 'Larry E. Masters'
+            ]
+        ];
+        $this->assertEquals($expected, $result);
+
         $result = Hash::combine($a, '{n}.User.id', '{n}.User.Data', '{n}.User.group_id');
         $expected = [
             1 => [
@@ -2383,6 +2442,18 @@ class HashTest extends TestCase
         ];
         $this->assertEquals($expected, $result);
 
+        $result = Hash::combine($a, null, '{n}.User.Data', '{n}.User.group_id');
+        $expected = [
+            1 => [
+                0 => ['user' => 'mariano.iglesias', 'name' => 'Mariano Iglesias'],
+                1 => ['user' => 'gwoo', 'name' => 'The Gwoo']
+            ],
+            2 => [
+                0 => ['user' => 'phpnut', 'name' => 'Larry E. Masters']
+            ]
+        ];
+        $this->assertEquals($expected, $result);
+
         $result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.name', '{n}.User.group_id');
         $expected = [
             1 => [
@@ -2394,6 +2465,18 @@ class HashTest extends TestCase
             ],
         ];
         $this->assertEquals($expected, $result);
+
+        $result = Hash::combine($a, null, '{n}.User.Data.name', '{n}.User.group_id');
+        $expected = [
+            1 => [
+                0 => 'Mariano Iglesias',
+                1 => 'The Gwoo'
+            ],
+            2 => [
+                0 => 'Larry E. Masters'
+            ]
+        ];
+        $this->assertEquals($expected, $result);
     }
 
     /**
@@ -2424,6 +2507,23 @@ class HashTest extends TestCase
 
         $result = Hash::combine(
             $a,
+            null,
+            ['%1$s: %2$s', '{n}.User.Data.user', '{n}.User.Data.name'],
+            '{n}.User.group_id'
+        );
+        $expected = [
+            1 => [
+                0 => 'mariano.iglesias: Mariano Iglesias',
+                1 => 'gwoo: The Gwoo'
+            ],
+            2 => [
+                0 => 'phpnut: Larry E. Masters'
+            ]
+        ];
+        $this->assertEquals($expected, $result);
+
+        $result = Hash::combine(
+            $a,
             [
                 '%s: %s',
                 '{n}.User.Data.user',

+ 0 - 2
tests/test_app/TestApp/Auth/CallCounterPasswordHasher.php

@@ -1,6 +1,4 @@
 <?php
-declare(strict_types=1);
-
 namespace TestApp\Auth;
 
 use Cake\Auth\AbstractPasswordHasher;