Browse Source

Merge branch '3.next' into 4.x

ADmad 6 years ago
parent
commit
11a2fcd88e

+ 1 - 1
src/Database/Dialect/TupleComparisonTranslatorTrait.php

@@ -32,7 +32,7 @@ trait TupleComparisonTranslatorTrait
      * Receives a TupleExpression and changes it so that it conforms to this
      * SQL dialect.
      *
-     * It transforms expressions looking like '(a, b) IN ((c, d), (e, f)' into an
+     * It transforms expressions looking like '(a, b) IN ((c, d), (e, f))' into an
      * equivalent expression of the form '((a = c) AND (b = d)) OR ((a = e) AND (b = f))'.
      *
      * It can also transform transform expressions where the right hand side is a query

+ 1 - 0
src/Database/TypeFactory.php

@@ -102,6 +102,7 @@ class TypeFactory
     public static function set(string $name, TypeInterface $instance): void
     {
         static::$_builtTypes[$name] = $instance;
+        static::$_types[$name] = get_class($instance);
     }
 
     /**

+ 10 - 4
src/Datasource/EntityTrait.php

@@ -951,13 +951,19 @@ trait EntityTrait
      */
     protected function _nestedErrors(string $field): array
     {
-        $path = explode('.', $field);
-
         // Only one path element, check for nested entity with error.
-        if (count($path) === 1) {
-            return $this->_readError($this->get($path[0]));
+        if (strpos($field, '.') === false) {
+            return $this->_readError($this->get($field));
         }
+        // Try reading the errors data with field as a simple path
+        $error = Hash::get($this->_errors, $field);
+        if ($error !== null) {
+            return $error;
+        }
+        $path = explode('.', $field);
 
+        // Traverse down the related entities/arrays for
+        // the relevant entity.
         $entity = $this;
         $len = count($path);
         while ($len) {

+ 1 - 1
src/I18n/Date.php

@@ -31,7 +31,7 @@ class Date extends MutableDate implements I18nDateTimeInterface
 
     /**
      * The format to use when formatting a time using `Cake\I18n\Date::i18nFormat()`
-     * and `__toString`
+     * and `__toString`. This format is also used by `parseDateTime()`.
      *
      * The format should be either the formatting constants from IntlDateFormatter as
      * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern

+ 20 - 6
src/I18n/DateFormatTrait.php

@@ -112,9 +112,7 @@ trait DateFormatTrait
      * $time->i18nFormat(Time::UNIX_TIMESTAMP_FORMAT); // outputs '1398031800'
      * ```
      *
-     * If you wish to control the default format to be used for this method, you can alter
-     * the value of the static `Time::$defaultLocale` variable and set it to one of the
-     * possible formats accepted by this function.
+     * You can control the default format used through `Time::setToStringFormat()`.
      *
      * You can read about the available IntlDateFormatter constants at
      * https://secure.php.net/manual/en/class.intldateformatter.php
@@ -134,9 +132,8 @@ trait DateFormatTrait
      * $time->i18nFormat(\IntlDateFormatter::FULL, 'Europe/Berlin', 'de-DE');
      * ```
      *
-     * You can control the default locale to be used by setting the static variable
-     * `Time::$defaultLocale` to a valid locale string. If empty, the default will be
-     * taken from the `intl.default_locale` ini config.
+     * You can control the default locale used through `Time::setDefaultLocale()`.
+     * If empty, the default will be taken from the `intl.default_locale` ini config.
      *
      * @param string|int|null $format Format string.
      * @param string|\DateTimeZone|null $timezone Timezone string or DateTimeZone object
@@ -249,6 +246,14 @@ trait DateFormatTrait
     /**
      * Sets the default format used when type converting instances of this type to string
      *
+     * The format should be either the formatting constants from IntlDateFormatter as
+     * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern
+     * as specified in (http://www.icu-project.org/apiref/icu4c/classSimpleDateFormat.html#details)
+     *
+     * It is possible to provide an array of 2 constants. In this case, the first position
+     * will be used for formatting the date part of the object and the second position
+     * will be used to format the time part.
+     *
      * @param string|array|int $format Format.
      * @return void
      */
@@ -260,6 +265,15 @@ trait DateFormatTrait
     /**
      * Sets the default format used when converting this object to json
      *
+     * The format should be either the formatting constants from IntlDateFormatter as
+     * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern
+     * as specified in (http://www.icu-project.org/apiref/icu4c/classSimpleDateFormat.html#details)
+     *
+     * It is possible to provide an array of 2 constants. In this case, the first position
+     * will be used for formatting the date part of the object and the second position
+     * will be used to format the time part.
+     *
+     * @see \Cake\I18n\Time::i18nFormat()
      * @param string|array|int $format Format.
      * @return void
      */

+ 1 - 1
src/I18n/FrozenDate.php

@@ -33,7 +33,7 @@ class FrozenDate extends ChronosDate implements I18nDateTimeInterface
 
     /**
      * The format to use when formatting a time using `Cake\I18n\Date::i18nFormat()`
-     * and `__toString`
+     * and `__toString`. This format is also used by `parseDateTime()`.
      *
      * The format should be either the formatting constants from IntlDateFormatter as
      * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern

+ 1 - 1
src/I18n/FrozenTime.php

@@ -33,7 +33,7 @@ class FrozenTime extends Chronos implements I18nDateTimeInterface
 
     /**
      * The format to use when formatting a time using `Cake\I18n\FrozenTime::i18nFormat()`
-     * and `__toString`
+     * and `__toString`. This format is also used by `parseDateTime()`.
      *
      * The format should be either the formatting constants from IntlDateFormatter as
      * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern

+ 1 - 1
src/I18n/Time.php

@@ -31,7 +31,7 @@ class Time extends MutableDateTime implements I18nDateTimeInterface
 
     /**
      * The format to use when formatting a time using `Cake\I18n\Time::i18nFormat()`
-     * and `__toString`
+     * and `__toString`. This format is also used by `parseDateTime()`.
      *
      * The format should be either the formatting constants from IntlDateFormatter as
      * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern

+ 0 - 9
src/ORM/ResultSet.php

@@ -143,15 +143,6 @@ class ResultSet implements ResultSetInterface
     protected $_count;
 
     /**
-     * Type cache for type converters.
-     *
-     * Converters are indexed by alias and column name.
-     *
-     * @var array
-     */
-    protected $_types = [];
-
-    /**
      * The Database driver object.
      *
      * Cached in a property to avoid multiple calls to the same function.

+ 2 - 13
src/TestSuite/ConsoleIntegrationTestTrait.php

@@ -17,7 +17,6 @@ namespace Cake\TestSuite;
 
 use Cake\Console\Command;
 use Cake\Console\CommandRunner;
-use Cake\Console\ConsoleInput;
 use Cake\Console\ConsoleIo;
 use Cake\Console\Exception\StopException;
 use Cake\Core\Configure;
@@ -27,6 +26,7 @@ use Cake\TestSuite\Constraint\Console\ContentsEmpty;
 use Cake\TestSuite\Constraint\Console\ContentsNotContain;
 use Cake\TestSuite\Constraint\Console\ContentsRegExp;
 use Cake\TestSuite\Constraint\Console\ExitCode;
+use Cake\TestSuite\Stub\ConsoleInput;
 use Cake\TestSuite\Stub\ConsoleOutput;
 
 /**
@@ -83,18 +83,7 @@ trait ConsoleIntegrationTestTrait
 
         $this->_out = new ConsoleOutput();
         $this->_err = new ConsoleOutput();
-        $this->_in = $this->getMockBuilder(ConsoleInput::class)
-            ->disableOriginalConstructor()
-            ->setMethods(['read'])
-            ->getMock();
-
-        $i = 0;
-        foreach ($input as $in) {
-            $this->_in
-                ->expects($this->at($i++))
-                ->method('read')
-                ->will($this->returnValue($in));
-        }
+        $this->_in = new ConsoleInput($input);
 
         $args = $this->commandStringToArgs("cake $command");
         $io = new ConsoleIo($this->_out, $this->_err, $this->_in);

+ 83 - 0
src/TestSuite/Stub/ConsoleInput.php

@@ -0,0 +1,83 @@
+<?php
+/**
+ * CakePHP :  Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @link          https://cakephp.org CakePHP Project
+ * @license       https://opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\TestSuite\Stub;
+
+use Cake\Console\ConsoleInput as ConsoleInputBase;
+use Cake\Console\Exception\ConsoleException;
+
+/**
+ * Stub class used by the console integration harness.
+ *
+ * This class enables input to be stubbed and have exceptions
+ * raised when no answer is available.
+ */
+class ConsoleInput extends ConsoleInputBase
+{
+    /**
+     * Reply values for ask() and askChoice()
+     *
+     * @var array
+     */
+    protected $replies = [];
+
+    /**
+     * Current message index
+     *
+     * @var int
+     */
+    protected $currentIndex = -1;
+
+    /**
+     * Constructor
+     *
+     * @param string[] $replies A list of replies for read()
+     */
+    public function __construct(array $replies)
+    {
+        parent::__construct();
+
+        $this->replies = $replies;
+    }
+
+    /**
+     * Read a reply
+     *
+     * @return mixed The value of the reply
+     */
+    public function read()
+    {
+        $this->currentIndex += 1;
+
+        if (!isset($this->replies[$this->currentIndex])) {
+            $total = count($this->replies);
+            $replies = implode(', ', $this->replies);
+            $message = "There are no more input replies available. Only {$total} replies were set, " .
+                "this is the {$this->currentIndex} read operation. The provided replies are: {$replies}";
+            throw new ConsoleException($message);
+        }
+
+        return $this->replies[$this->currentIndex];
+    }
+
+    /**
+     * Check if data is available on stdin
+     *
+     * @param int $timeout An optional time to wait for data
+     * @return bool True for data available, false otherwise
+     */
+    public function dataAvailable($timeout = 0)
+    {
+        return true;
+    }
+}

+ 2 - 2
src/Validation/Validation.php

@@ -1118,7 +1118,7 @@ class Validation
             $mimeTypes[$key] = strtolower($val);
         }
 
-        return in_array($mime, $mimeTypes, true);
+        return in_array(strtolower($mime), $mimeTypes, true);
     }
 
     /**
@@ -1481,7 +1481,7 @@ class Validation
      */
     public static function isInteger($value): bool
     {
-        if (!is_scalar($value) || is_float($value)) {
+        if (!is_numeric($value) || is_float($value)) {
             return false;
         }
         if (is_int($value)) {

+ 86 - 6
src/View/Form/EntityContext.php

@@ -323,11 +323,12 @@ class EntityContext implements ContextInterface
     }
 
     /**
-     * Fetch the leaf entity for the given path.
+     * Fetch the entity or data value for a given path
      *
-     * This method will traverse the given path and find the leaf
-     * entity. If the path does not contain a leaf entity false
-     * will be returned.
+     * This method will traverse the given path and find the entity
+     * or array value for a given path.
+     *
+     * If you only want the terminal Entity for a path use `leafEntity` instead.
      *
      * @param array|null $path Each one of the parts in a path for a field name
      *  or null to get the entity passed in constructor context.
@@ -359,7 +360,6 @@ class EntityContext implements ContextInterface
             $prop = $path[$i];
             $next = $this->_getProp($entity, $prop);
             $isLast = ($i === $last);
-
             if (!$isLast && $next === null && $prop !== '_ids') {
                 $table = $this->_getTable($path);
                 if ($table) {
@@ -383,6 +383,74 @@ class EntityContext implements ContextInterface
     }
 
     /**
+     * Fetch the terminal or leaf entity for the given path.
+     *
+     * Traverse the path until an entity cannot be found. Lists containing
+     * entities will be traversed if the first element contains an entity.
+     * Otherwise the containing Entity will be assumed to be the terminal one.
+     *
+     * @param array|null $path Each one of the parts in a path for a field name
+     *  or null to get the entity passed in constructor context.
+     * @return array Containing the found entity, and remaining un-matched path.
+     * @throws \RuntimeException When properties cannot be read.
+     */
+    protected function leafEntity($path = null)
+    {
+        if ($path === null) {
+            return $this->_context['entity'];
+        }
+
+        $oneElement = count($path) === 1;
+        if ($oneElement && $this->_isCollection) {
+            throw new RuntimeException(sprintf(
+                'Unable to fetch property "%s"',
+                implode('.', $path)
+            ));
+        }
+        $entity = $this->_context['entity'];
+        if ($oneElement) {
+            return [$entity, $path];
+        }
+
+        if ($path[0] === $this->_rootName) {
+            $path = array_slice($path, 1);
+        }
+
+        $len = count($path);
+        $last = $len - 1;
+        $leafEntity = $entity;
+        for ($i = 0; $i < $len; $i++) {
+            $prop = $path[$i];
+            $next = $this->_getProp($entity, $prop);
+
+            // Did not dig into an entity, return the current one.
+            if (is_array($entity) && !($next instanceof EntityInterface || $next instanceof Traversable)) {
+                return [$leafEntity, array_slice($path, $i - 1)];
+            }
+
+            if ($next instanceof EntityInterface) {
+                $leafEntity = $next;
+            }
+
+            // If we are at the end of traversable elements
+            // return the last entity found.
+            $isTraversable = (
+                is_array($next) ||
+                $next instanceof Traversable ||
+                $next instanceof EntityInterface
+            );
+            if (!$isTraversable) {
+                return [$leafEntity, array_slice($path, $i)];
+            }
+            $entity = $next;
+        }
+        throw new RuntimeException(sprintf(
+            'Unable to fetch property "%s"',
+            implode('.', $path)
+        ));
+    }
+
+    /**
      * Read property values or traverse arrays/iterators.
      *
      * @param mixed $target The entity/array/collection to fetch $field from.
@@ -660,9 +728,21 @@ class EntityContext implements ContextInterface
     public function error(string $field): array
     {
         $parts = explode('.', $field);
-        $entity = $this->entity($parts);
+        try {
+            list($entity, $remainingParts) = $this->leafEntity($parts);
+        } catch (RuntimeException $e) {
+            return [];
+        }
+        if (count($remainingParts) === 0) {
+            return $entity->getErrors();
+        }
 
         if ($entity instanceof EntityInterface) {
+            $error = $entity->getError(implode('.', $remainingParts));
+            if ($error) {
+                return $error;
+            }
+
             return $entity->getError(array_pop($parts));
         }
 

+ 19 - 0
tests/Fixture/sample.a68

@@ -0,0 +1,19 @@
+MODE ELEMENT = STRING;
+MODE NODE = 
+  STRUCT ( ELEMENT value, REF NODE next );
+MODE LIST = REF NODE;
+LIST empty = NIL;
+PROC append = ( REF LIST list, ELEMENT val ) VOID:
+BEGIN
+  IF list IS empty
+  THEN
+    list := HEAP NODE := ( val, empty )
+  ELSE
+    REF LIST tail := list;
+    WHILE next OF tail ISNT empty
+    DO
+      tail := next OF tail
+    OD;
+    next OF tail := HEAP NODE := ( val, empty )
+  FI
+END;

+ 13 - 2
tests/TestCase/Database/QueryTest.php

@@ -22,11 +22,13 @@ use Cake\Database\ExpressionInterface;
 use Cake\Database\Query;
 use Cake\Database\Statement\StatementDecorator;
 use Cake\Database\StatementInterface;
+use Cake\Database\TypeFactory;
 use Cake\Database\TypeMap;
 use Cake\Datasource\ConnectionManager;
 use Cake\TestSuite\TestCase;
 use DateTimeImmutable;
 use InvalidArgumentException;
+use TestApp\Database\Type\BarType;
 
 /**
  * Tests Query class
@@ -4304,16 +4306,25 @@ class QueryTest extends TestCase
      */
     public function testSelectTypeConversion()
     {
+        TypeFactory::set('custom_datetime', new BarType('custom_datetime'));
         $this->loadFixtures('Comments');
+
         $query = new Query($this->connection);
         $query
-            ->select(['id', 'comment', 'the_date' => 'created'])
+            ->select(['id', 'comment', 'the_date' => 'created', 'updated'])
             ->from('comments')
             ->limit(1)
-            ->getSelectTypeMap()->setTypes(['id' => 'integer', 'the_date' => 'datetime']);
+            ->getSelectTypeMap()
+                ->setTypes([
+                    'id' => 'integer',
+                    'the_date' => 'datetime',
+                    'updated' => 'custom_datetime'
+                ]);
+
         $result = $query->execute()->fetchAll('assoc');
         $this->assertIsInt($result[0]['id']);
         $this->assertInstanceOf(DateTimeImmutable::class, $result[0]['the_date']);
+        $this->assertInstanceOf(DateTimeImmutable::class, $result[0]['updated']);
     }
 
     /**

+ 15 - 0
tests/TestCase/Database/TypeFactoryTest.php

@@ -139,6 +139,21 @@ class TypeFactoryTest extends TestCase
     }
 
     /**
+     * Tests new types set with set() are returned by buildAll()
+     *
+     * @return void
+     */
+    public function testSetAndBuild()
+    {
+        $types = TypeFactory::buildAll();
+        $this->assertFalse(isset($types['foo']));
+
+        TypeFactory::set('foo', new FooType());
+        $types = TypeFactory::buildAll();
+        $this->assertTrue(isset($types['foo']));
+    }
+
+    /**
      * Tests overwriting type map works for building
      *
      * @return void

+ 20 - 1
tests/TestCase/ORM/EntityTest.php

@@ -1206,7 +1206,7 @@ class EntityTest extends TestCase
      *
      * @return void
      */
-    public function testGetAndSetErrors()
+    public function testGetErrorAndSetError()
     {
         $entity = new Entity();
         $this->assertEmpty($entity->getErrors());
@@ -1232,6 +1232,25 @@ class EntityTest extends TestCase
     }
 
     /**
+     * Tests reading errors from nested validator
+     *
+     * @return void
+     */
+    public function testGetErrorNested()
+    {
+        $entity = new Entity();
+        $entity->setError('options', ['subpages' => ['_empty' => 'required']]);
+
+        $expected = [
+            'subpages' => ['_empty' => 'required']
+        ];
+        $this->assertEquals($expected, $entity->getError('options'));
+
+        $expected = ['_empty' => 'required'];
+        $this->assertEquals($expected, $entity->getError('options.subpages'));
+    }
+
+    /**
      * Tests that it is possible to get errors for nested entities
      *
      * @return void

+ 13 - 0
tests/TestCase/TestSuite/ConsoleIntegrationTestTraitTest.php

@@ -15,6 +15,7 @@ declare(strict_types=1);
  */
 namespace Cake\Test\TestCase\TestSuite;
 
+use Cake\Console\Exception\ConsoleException;
 use Cake\Console\Shell;
 use Cake\TestSuite\ConsoleIntegrationTestCase;
 use PHPUnit\Framework\AssertionFailedError;
@@ -144,6 +145,18 @@ class ConsoleIntegrationTestTraitTest extends ConsoleIntegrationTestCase
     }
 
     /**
+     * tests exec with fewer inputs than questions
+     *
+     * @return void
+     */
+    public function testExecWithMissingInput()
+    {
+        $this->expectException(ConsoleException::class);
+        $this->expectExceptionMessage('no more input');
+        $this->exec('integration bridge', ['cake']);
+    }
+
+    /**
      * tests exec with multiple inputs
      *
      * @return void

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

@@ -2064,6 +2064,7 @@ class ValidationTest extends TestCase
 
         $this->assertFalse(Validation::maxLength('abcd', 3));
         $this->assertFalse(Validation::maxLength('ÆΔΩЖÇ', 3));
+        $this->assertFalse(Validation::maxLength(['abc'], 10));
     }
 
     /**
@@ -2080,6 +2081,7 @@ class ValidationTest extends TestCase
 
         $this->assertFalse(Validation::maxLengthBytes('abcd', 3));
         $this->assertFalse(Validation::maxLengthBytes('ÆΔΩЖÇ', 9));
+        $this->assertFalse(Validation::maxLengthBytes(['abc'], 10));
     }
 
     /**
@@ -2095,6 +2097,8 @@ class ValidationTest extends TestCase
         $this->assertTrue(Validation::minLength('abc', 3));
         $this->assertTrue(Validation::minLength('abcd', 3));
         $this->assertTrue(Validation::minLength('ÆΔΩЖÇ', 2));
+
+        $this->assertFalse(Validation::minLength(['abc'], 1));
     }
 
     /**
@@ -2111,6 +2115,8 @@ class ValidationTest extends TestCase
         $this->assertTrue(Validation::minLengthBytes('abcd', 3));
         $this->assertTrue(Validation::minLengthBytes('ÆΔΩЖÇ', 10));
         $this->assertTrue(Validation::minLengthBytes('ÆΔΩЖÇ', 9));
+
+        $this->assertFalse(Validation::minLengthBytes(['abc'], 1));
     }
 
     /**
@@ -2522,6 +2528,25 @@ class ValidationTest extends TestCase
     }
 
     /**
+     * testMimeTypeCaseInsensitive method
+     *
+     * @return void
+     */
+    public function testMimeTypeCaseInsensitive()
+    {
+        $algol68 = CORE_TESTS . 'Fixture/sample.a68';
+        $File = new File($algol68, false);
+
+        $this->skipIf($File->mime() != 'text/x-Algol68', 'Cannot determine text/x-Algol68 mimeType');
+
+        $this->assertTrue(Validation::mimeType($algol68, ['text/x-Algol68']));
+        $this->assertTrue(Validation::mimeType($algol68, ['text/x-algol68']));
+        $this->assertTrue(Validation::mimeType($algol68, ['text/X-ALGOL68']));
+
+        $this->assertFalse(Validation::mimeType($algol68, ['image/png']));
+    }
+
+    /**
      * Test mimetype with a PSR7 object
      *
      * @return void
@@ -2947,9 +2972,12 @@ class ValidationTest extends TestCase
         $this->assertTrue(Validation::isInteger('-012'));
 
         $this->assertFalse(Validation::isInteger('2.5'));
+        $this->assertFalse(Validation::isInteger(2.5));
         $this->assertFalse(Validation::isInteger([]));
         $this->assertFalse(Validation::isInteger(new \StdClass()));
         $this->assertFalse(Validation::isInteger('2 bears'));
+        $this->assertFalse(Validation::isInteger(true));
+        $this->assertFalse(Validation::isInteger(false));
     }
 
     /**

+ 56 - 0
tests/TestCase/View/Form/EntityContextTest.php

@@ -1302,6 +1302,62 @@ class EntityContextTest extends TestCase
     }
 
     /**
+     * Test error on nested validation
+     *
+     * @return void
+     */
+    public function testErrorNestedValidator()
+    {
+        $this->_setupTables();
+
+        $row = new Article([
+            'title' => 'My title',
+            'options' => ['subpages' => '']
+        ]);
+        $row->setError('options', ['subpages' => ['_empty' => 'required value']]);
+
+        $context = new EntityContext($this->request, [
+            'entity' => $row,
+            'table' => 'Articles',
+        ]);
+        $expected = ['_empty' => 'required value'];
+        $this->assertEquals($expected, $context->error('options.subpages'));
+    }
+
+    /**
+     * Test error on nested validation
+     *
+     * @return void
+     */
+    public function testErrorAssociatedNestedValidator()
+    {
+        $this->_setupTables();
+
+        $tagOne = new Tag(['name' => 'first-post']);
+        $tagTwo = new Tag(['name' => 'second-post']);
+        $tagOne->setError(
+            'metadata',
+            ['description' => ['_empty' => 'required value']]
+        );
+        $row = new Article([
+            'title' => 'My title',
+            'tags' => [
+                $tagOne,
+                $tagTwo
+            ]
+        ]);
+
+        $context = new EntityContext($this->request, [
+            'entity' => $row,
+            'table' => 'Articles',
+        ]);
+        $expected = ['_empty' => 'required value'];
+        $this->assertSame([], $context->error('tags.0.notthere'));
+        $this->assertSame([], $context->error('tags.1.notthere'));
+        $this->assertEquals($expected, $context->error('tags.0.metadata.description'));
+    }
+
+    /**
      * Setup tables for tests.
      *
      * @return void

+ 3 - 3
tests/test_app/TestApp/Database/Type/BarType.php

@@ -16,12 +16,12 @@ declare(strict_types=1);
  */
 namespace TestApp\Database\Type;
 
-use Cake\Database\Type\StringType;
+use Cake\Database\Type\DateTimeType;
 
-class BarType extends StringType
+class BarType extends DateTimeType
 {
     public function getBaseType(): string
     {
-        return 'text';
+        return 'datetimetype';
     }
 }