Browse Source

Merge branch 'master' into 3.next

Mark Story 7 years ago
parent
commit
6e185097f4

+ 3 - 3
src/I18n/Number.php

@@ -128,7 +128,7 @@ class Number
      *
      * - `places` - Minimum number or decimals to use, e.g 0
      * - `precision` - Maximum Number of decimal places to use, e.g. 2
-     * - `pattern` - An ICU number pattern to use for formatting the number. e.g #,###.00
+     * - `pattern` - An ICU number pattern to use for formatting the number. e.g #,##0.00
      * - `locale` - The locale name to use for formatting the number, e.g. fr_FR
      * - `before` - The string to place before whole numbers, e.g. '['
      * - `after` - The string to place after decimal numbers, e.g. ']'
@@ -204,7 +204,7 @@ class Number
      * - `zero` - The text to use for zero values, can be a string or a number. e.g. 0, 'Free!'
      * - `places` - Number of decimal places to use. e.g. 2
      * - `precision` - Maximum Number of decimal places to use, e.g. 2
-     * - `pattern` - An ICU number pattern to use for formatting the number. e.g #,###.00
+     * - `pattern` - An ICU number pattern to use for formatting the number. e.g #,##0.00
      * - `useIntlCode` - Whether or not to replace the currency symbol with the international
      *   currency code.
      *
@@ -277,7 +277,7 @@ class Number
      *    numbers representing money or a NumberFormatter constant.
      * - `places` - Number of decimal places to use. e.g. 2
      * - `precision` - Maximum Number of decimal places to use, e.g. 2
-     * - `pattern` - An ICU number pattern to use for formatting the number. e.g #,###.00
+     * - `pattern` - An ICU number pattern to use for formatting the number. e.g #,##0.00
      * - `useIntlCode` - Whether or not to replace the currency symbol with the international
      *   currency code.
      *

+ 15 - 16
src/ORM/Table.php

@@ -1674,7 +1674,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
      *   transaction (default: true)
      * - defaults: Whether to use the search criteria as default values for the new entity (default: true)
      *
-     * @param array|\Cake\ORM\Query $search The criteria to find existing
+     * @param array|callable|\Cake\ORM\Query $search The criteria to find existing
      *   records by. Note that when you pass a query object you'll have to use
      *   the 2nd arg of the method to modify the entity data before saving.
      * @param callable|null $callback A callback that will be invoked for newly
@@ -1704,7 +1704,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
     /**
      * Performs the actual find and/or create of an entity based on the passed options.
      *
-     * @param array|callable $search The criteria to find an existing record by, or a callable tha will
+     * @param array|callable|\Cake\ORM\Query $search The criteria to find an existing record by, or a callable tha will
      *   customize the find query.
      * @param callable|null $callback A callback that will be invoked for newly
      *   created entities. This callback will be called *before* the entity
@@ -1714,20 +1714,12 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
      */
     protected function _processFindOrCreate($search, callable $callback = null, $options = [])
     {
-        if (is_callable($search)) {
-            $query = $this->find();
-            $search($query);
-        } elseif (is_array($search)) {
-            $query = $this->find()->where($search);
-        } elseif ($search instanceof Query) {
-            $query = $search;
-        } else {
-            throw new InvalidArgumentException('Search criteria must be an array, callable or Query');
-        }
+        $query = $this->_getFindOrCreateQuery($search);
         $row = $query->first();
         if ($row !== null) {
             return $row;
         }
+
         $entity = $this->newEntity();
         if ($options['defaults'] && is_array($search)) {
             $accessibleFields = array_combine(array_keys($search), array_fill(0, count($search), true));
@@ -1750,16 +1742,23 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
     /**
      * Gets the query object for findOrCreate().
      *
-     * @param array|\Cake\ORM\Query|string $search The criteria to find existing records by.
+     * @param array|callable|\Cake\ORM\Query $search The criteria to find existing records by.
      * @return \Cake\ORM\Query
      */
     protected function _getFindOrCreateQuery($search)
     {
-        if ($search instanceof Query) {
-            return $search;
+        if (is_callable($search)) {
+            $query = $this->find();
+            $search($query);
+        } elseif (is_array($search)) {
+            $query = $this->find()->where($search);
+        } elseif ($search instanceof Query) {
+            $query = $search;
+        } else {
+            throw new InvalidArgumentException('Search criteria must be an array, callable or Query');
         }
 
-        return $this->find()->where($search);
+        return $query;
     }
 
     /**

+ 27 - 1
src/Routing/Router.php

@@ -18,7 +18,12 @@ use Cake\Core\Configure;
 use Cake\Http\ServerRequest;
 use Cake\Routing\Exception\MissingRouteException;
 use Cake\Utility\Inflector;
+use Exception;
 use Psr\Http\Message\ServerRequestInterface;
+use ReflectionFunction;
+use ReflectionMethod;
+use RuntimeException;
+use Throwable;
 
 /**
  * Parses the request URL into controller, action, and parameters. Uses the connected routes
@@ -585,8 +590,29 @@ class Router
     protected static function _applyUrlFilters($url)
     {
         $request = static::getRequest(true);
+        $e = null;
         foreach (static::$_urlFilters as $filter) {
-            $url = $filter($url, $request);
+            try {
+                $url = $filter($url, $request);
+            } catch (Exception $e) {
+                // fall through
+            } catch (Throwable $e) {
+                // fall through
+            }
+            if ($e !== null) {
+                if (is_array($filter)) {
+                    $ref = new ReflectionMethod($filter[0], $filter[1]);
+                } else {
+                    $ref = new ReflectionFunction($filter);
+                }
+                $message = sprintf(
+                    'URL filter defined in %s on line %s could not be applied. The filter failed with: %s',
+                    $ref->getFileName(),
+                    $ref->getStartLine(),
+                    $e->getMessage()
+                );
+                throw new RuntimeException($message, $e->getCode(), $e);
+            }
         }
 
         return $url;

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

@@ -169,7 +169,7 @@ class NumberHelper extends Helper
      * - `zero` - The text to use for zero values, can be a string or a number. e.g. 0, 'Free!'
      * - `places` - Number of decimal places to use. e.g. 2
      * - `precision` - Maximum Number of decimal places to use, e.g. 2
-     * - `pattern` - An ICU number pattern to use for formatting the number. e.g #,###.00
+     * - `pattern` - An ICU number pattern to use for formatting the number. e.g #,##0.00
      * - `useIntlCode` - Whether or not to replace the currency symbol with the international
      *   currency code.
      * - `escape` - Whether or not to escape html in resulting string

+ 8 - 0
tests/TestCase/I18n/NumberTest.php

@@ -261,6 +261,14 @@ class NumberTest extends TestCase
         $expected = '£0.00';
         $this->assertEquals($expected, $result);
 
+        $result = $this->Number->currency(0, 'GBP', ['pattern' => '¤#,###.00;¤-#,###.00']);
+        $expected = '£.00';
+        $this->assertEquals($expected, $result);
+
+        $result = $this->Number->currency(0, 'GBP', ['pattern' => '¤#,##0.00;¤-#,##0.00']);
+        $expected = '£0.00';
+        $this->assertEquals($expected, $result);
+
         $result = $this->Number->currency(0.00000, 'GBP');
         $expected = '£0.00';
         $this->assertEquals($expected, $result);

+ 67 - 0
tests/TestCase/Routing/RouterTest.php

@@ -23,6 +23,7 @@ use Cake\Routing\RouteCollection;
 use Cake\Routing\Router;
 use Cake\Routing\Route\Route;
 use Cake\TestSuite\TestCase;
+use RuntimeException;
 
 /**
  * RouterTest class
@@ -1339,6 +1340,72 @@ class RouterTest extends TestCase
     }
 
     /**
+     * Test that url filter failure gives better errors
+     *
+     * @return void
+     */
+    public function testUrlGenerationWithUrlFilterFailureClosure()
+    {
+        $this->expectException(RuntimeException::class);
+        $this->expectExceptionMessageRegExp(
+            '/URL filter defined in .*RouterTest\.php on line \d+ could not be applied\.' .
+            ' The filter failed with: nope/'
+        );
+        Router::connect('/:lang/:controller/:action/*');
+        $request = new ServerRequest([
+            'params' => [
+                'plugin' => null,
+                'lang' => 'en',
+                'controller' => 'posts',
+                'action' => 'index'
+            ]
+        ]);
+        Router::pushRequest($request);
+
+        Router::addUrlFilter(function ($url, $request) {
+            throw new RuntimeException('nope');
+        });
+        Router::url(['controller' => 'posts', 'action' => 'index', 'lang' => 'en']);
+    }
+
+    /**
+     * Test that url filter failure gives better errors
+     *
+     * @return void
+     */
+    public function testUrlGenerationWithUrlFilterFailureMethod()
+    {
+        $this->expectException(RuntimeException::class);
+        $this->expectExceptionMessageRegExp(
+            '/URL filter defined in .*RouterTest\.php on line \d+ could not be applied\.' .
+            ' The filter failed with: /'
+        );
+        Router::connect('/:lang/:controller/:action/*');
+        $request = new ServerRequest([
+            'params' => [
+                'plugin' => null,
+                'lang' => 'en',
+                'controller' => 'posts',
+                'action' => 'index'
+            ]
+        ]);
+        Router::pushRequest($request);
+
+        Router::addUrlFilter([$this, 'badFilter']);
+        Router::url(['controller' => 'posts', 'action' => 'index', 'lang' => 'en']);
+    }
+
+    /**
+     * Testing stub for broken URL filters.
+     *
+     * @throws \RuntimeException
+     */
+    public function badFilter()
+    {
+        throw new RuntimeException('nope');
+    }
+
+    /**
      * Test url param persistence.
      *
      * @return void