init
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.1.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
|
||||
/**
|
||||
* This interface defines the methods you can depend on in a connection.
|
||||
*/
|
||||
interface ConnectionInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const ROLE_WRITE = 'write';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const ROLE_READ = 'read';
|
||||
|
||||
/**
|
||||
* Gets the driver instance.
|
||||
*
|
||||
* @param string $role
|
||||
* @return object
|
||||
*/
|
||||
public function getDriver(string $role = self::ROLE_WRITE): object;
|
||||
|
||||
/**
|
||||
* Set a cacher.
|
||||
*
|
||||
* @param \Psr\SimpleCache\CacheInterface $cacher Cacher object
|
||||
* @return $this
|
||||
*/
|
||||
public function setCacher(CacheInterface $cacher);
|
||||
|
||||
/**
|
||||
* Get a cacher.
|
||||
*
|
||||
* @return \Psr\SimpleCache\CacheInterface $cacher Cacher object
|
||||
*/
|
||||
public function getCacher(): CacheInterface;
|
||||
|
||||
/**
|
||||
* Get the configuration name for this connection.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function configName(): string;
|
||||
|
||||
/**
|
||||
* Get the configuration data used to create the connection.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function config(): array;
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 0.10.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource;
|
||||
|
||||
use Cake\Core\StaticConfigTrait;
|
||||
use Cake\Database\Connection;
|
||||
use Cake\Database\Driver\Mysql;
|
||||
use Cake\Database\Driver\Postgres;
|
||||
use Cake\Database\Driver\Sqlite;
|
||||
use Cake\Database\Driver\Sqlserver;
|
||||
use Cake\Datasource\Exception\MissingDatasourceConfigException;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* Manages and loads instances of Connection
|
||||
*
|
||||
* Provides an interface to loading and creating connection objects. Acts as
|
||||
* a registry for the connections defined in an application.
|
||||
*
|
||||
* Provides an interface for loading and enumerating connections defined in
|
||||
* config/app.php
|
||||
*/
|
||||
class ConnectionManager
|
||||
{
|
||||
use StaticConfigTrait {
|
||||
setConfig as protected _setConfig;
|
||||
parseDsn as protected _parseDsn;
|
||||
}
|
||||
|
||||
/**
|
||||
* A map of connection aliases.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected static array $_aliasMap = [];
|
||||
|
||||
/**
|
||||
* An array mapping url schemes to fully qualified driver class names
|
||||
*
|
||||
* @var array<string, string>
|
||||
* @phpstan-var array<string, class-string>
|
||||
*/
|
||||
protected static array $_dsnClassMap = [
|
||||
'mysql' => Mysql::class,
|
||||
'postgres' => Postgres::class,
|
||||
'sqlite' => Sqlite::class,
|
||||
'sqlserver' => Sqlserver::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The ConnectionRegistry used by the manager.
|
||||
*
|
||||
* @var \Cake\Datasource\ConnectionRegistry
|
||||
*/
|
||||
protected static ConnectionRegistry $_registry;
|
||||
|
||||
/**
|
||||
* Configure a new connection object.
|
||||
*
|
||||
* The connection will not be constructed until it is first used.
|
||||
*
|
||||
* @param array<string, mixed>|string $key The name of the connection config, or an array of multiple configs.
|
||||
* @param \Cake\Datasource\ConnectionInterface|\Closure|array<string, mixed>|null $config An array of name => config data for adapter.
|
||||
* @return void
|
||||
* @throws \Cake\Core\Exception\CakeException When trying to modify an existing config.
|
||||
* @see \Cake\Core\StaticConfigTrait::config()
|
||||
*/
|
||||
public static function setConfig(array|string $key, ConnectionInterface|Closure|array|null $config = null): void
|
||||
{
|
||||
if (is_array($config)) {
|
||||
$config['name'] = $key;
|
||||
}
|
||||
|
||||
static::_setConfig($key, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a DSN into a valid connection configuration
|
||||
*
|
||||
* This method allows setting a DSN using formatting similar to that used by PEAR::DB.
|
||||
* The following is an example of its usage:
|
||||
*
|
||||
* ```
|
||||
* $dsn = 'mysql://user:pass@localhost/database';
|
||||
* $config = ConnectionManager::parseDsn($dsn);
|
||||
*
|
||||
* $dsn = 'Cake\Database\Driver\Mysql://localhost:3306/database?className=Cake\Database\Connection';
|
||||
* $config = ConnectionManager::parseDsn($dsn);
|
||||
*
|
||||
* $dsn = 'Cake\Database\Connection://localhost:3306/database?driver=Cake\Database\Driver\Mysql';
|
||||
* $config = ConnectionManager::parseDsn($dsn);
|
||||
* ```
|
||||
*
|
||||
* For all classes, the value of `scheme` is set as the value of both the `className` and `driver`
|
||||
* unless they have been otherwise specified.
|
||||
*
|
||||
* Note that query-string arguments are also parsed and set as values in the returned configuration.
|
||||
*
|
||||
* @param string $dsn The DSN string to convert to a configuration array
|
||||
* @return array<int|string, array|bool|string|null> The configuration array to be stored after parsing the DSN
|
||||
*/
|
||||
public static function parseDsn(string $dsn): array
|
||||
{
|
||||
$config = static::_parseDsn($dsn);
|
||||
|
||||
if (isset($config['path']) && empty($config['database']) && is_string($config['path'])) {
|
||||
$config['database'] = substr($config['path'], 1);
|
||||
}
|
||||
|
||||
if (empty($config['driver'])) {
|
||||
$config['driver'] = $config['className'] ?? null;
|
||||
$config['className'] = Connection::class;
|
||||
}
|
||||
|
||||
unset($config['path']);
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set one or more connection aliases.
|
||||
*
|
||||
* Connection aliases allow you to rename active connections without overwriting
|
||||
* the aliased connection. This is most useful in the test-suite for replacing
|
||||
* connections with their test variant.
|
||||
*
|
||||
* Defined aliases will take precedence over normal connection names. For example,
|
||||
* if you alias 'default' to 'test', fetching 'default' will always return the 'test'
|
||||
* connection as long as the alias is defined.
|
||||
*
|
||||
* You can remove aliases with ConnectionManager::dropAlias().
|
||||
*
|
||||
* ### Usage
|
||||
*
|
||||
* ```
|
||||
* // Make 'things' resolve to 'test_things' connection
|
||||
* ConnectionManager::alias('test_things', 'things');
|
||||
* ```
|
||||
*
|
||||
* @param string $source The existing connection to alias.
|
||||
* @param string $alias The alias name that resolves to `$source`.
|
||||
* @return void
|
||||
*/
|
||||
public static function alias(string $source, string $alias): void
|
||||
{
|
||||
static::$_aliasMap[$alias] = $source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop an alias.
|
||||
*
|
||||
* Removes an alias from ConnectionManager. Fetching the aliased
|
||||
* connection may fail if there is no other connection with that name.
|
||||
*
|
||||
* @param string $alias The connection alias to drop
|
||||
* @return void
|
||||
*/
|
||||
public static function dropAlias(string $alias): void
|
||||
{
|
||||
unset(static::$_aliasMap[$alias]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current connection aliases and what they alias.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function aliases(): array
|
||||
{
|
||||
return static::$_aliasMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a connection.
|
||||
*
|
||||
* If the connection has not been constructed an instance will be added
|
||||
* to the registry. This method will use any aliases that have been
|
||||
* defined. If you want the original unaliased connections pass `false`
|
||||
* as second parameter.
|
||||
*
|
||||
* @param string $name The connection name.
|
||||
* @param bool $useAliases Whether connection aliases are used
|
||||
* @return \Cake\Datasource\ConnectionInterface
|
||||
* @throws \Cake\Datasource\Exception\MissingDatasourceConfigException When config
|
||||
* data is missing.
|
||||
*/
|
||||
public static function get(string $name, bool $useAliases = true): ConnectionInterface
|
||||
{
|
||||
if ($useAliases && isset(static::$_aliasMap[$name])) {
|
||||
$name = static::$_aliasMap[$name];
|
||||
}
|
||||
|
||||
if (!isset(static::$_config[$name])) {
|
||||
throw new MissingDatasourceConfigException(['name' => $name]);
|
||||
}
|
||||
static::$_registry ??= new ConnectionRegistry();
|
||||
|
||||
return static::$_registry->{$name} ?? static::$_registry->load($name, static::$_config[$name]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource;
|
||||
|
||||
use Cake\Core\App;
|
||||
use Cake\Core\ObjectRegistry;
|
||||
use Cake\Datasource\Exception\MissingDatasourceException;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* A registry object for connection instances.
|
||||
*
|
||||
* @see \Cake\Datasource\ConnectionManager
|
||||
* @extends \Cake\Core\ObjectRegistry<\Cake\Datasource\ConnectionInterface>
|
||||
*/
|
||||
class ConnectionRegistry extends ObjectRegistry
|
||||
{
|
||||
/**
|
||||
* Resolve a datasource classname.
|
||||
*
|
||||
* Part of the template method for Cake\Core\ObjectRegistry::load()
|
||||
*
|
||||
* @param string $class Partial classname to resolve.
|
||||
* @return class-string<\Cake\Datasource\ConnectionInterface>|null Either the correct class name or null.
|
||||
*/
|
||||
protected function _resolveClassName(string $class): ?string
|
||||
{
|
||||
/** @var class-string<\Cake\Datasource\ConnectionInterface>|null */
|
||||
return App::className($class, 'Datasource');
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception when a datasource is missing
|
||||
*
|
||||
* Part of the template method for Cake\Core\ObjectRegistry::load()
|
||||
*
|
||||
* @param string $class The classname that is missing.
|
||||
* @param string|null $plugin The plugin the datasource is missing in.
|
||||
* @return void
|
||||
* @throws \Cake\Datasource\Exception\MissingDatasourceException
|
||||
*/
|
||||
protected function _throwMissingClassError(string $class, ?string $plugin): void
|
||||
{
|
||||
throw new MissingDatasourceException([
|
||||
'class' => $class,
|
||||
'plugin' => $plugin,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the connection object with the correct settings.
|
||||
*
|
||||
* Part of the template method for Cake\Core\ObjectRegistry::load()
|
||||
*
|
||||
* If a closure is passed as first argument, The returned value of this
|
||||
* function will be the result from calling the closure.
|
||||
*
|
||||
* @param \Cake\Datasource\ConnectionInterface|\Closure|class-string<\Cake\Datasource\ConnectionInterface> $class The classname or object to make.
|
||||
* @param string $alias The alias of the object.
|
||||
* @param array<string, mixed> $config An array of settings to use for the datasource.
|
||||
* @return \Cake\Datasource\ConnectionInterface A connection with the correct settings.
|
||||
*/
|
||||
protected function _create(object|string $class, string $alias, array $config): ConnectionInterface
|
||||
{
|
||||
if (is_string($class)) {
|
||||
unset($config['className']);
|
||||
|
||||
return new $class($config);
|
||||
}
|
||||
|
||||
if ($class instanceof Closure) {
|
||||
return $class($alias);
|
||||
}
|
||||
|
||||
return $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a single adapter from the registry.
|
||||
*
|
||||
* @param string $name The adapter name.
|
||||
* @return $this
|
||||
*/
|
||||
public function unload(string $name)
|
||||
{
|
||||
unset($this->_loaded[$name]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource;
|
||||
|
||||
use ArrayAccess;
|
||||
use JsonSerializable;
|
||||
use Stringable;
|
||||
|
||||
/**
|
||||
* Describes the methods that any class representing a data storage should
|
||||
* comply with.
|
||||
*
|
||||
* @property mixed $id Alias for commonly used primary key.
|
||||
* @template-extends \ArrayAccess<string, mixed>
|
||||
* @method bool hasValue(string $field)
|
||||
* @method static patch(array $values, array $options = [])
|
||||
*/
|
||||
interface EntityInterface extends ArrayAccess, JsonSerializable, Stringable
|
||||
{
|
||||
/**
|
||||
* Sets hidden fields.
|
||||
*
|
||||
* @param array<string> $fields An array of fields to hide from array exports.
|
||||
* @param bool $merge Merge the new fields with the existing. By default false.
|
||||
* @return $this
|
||||
*/
|
||||
public function setHidden(array $fields, bool $merge = false);
|
||||
|
||||
/**
|
||||
* Gets the hidden fields.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getHidden(): array;
|
||||
|
||||
/**
|
||||
* Sets the virtual fields on this entity.
|
||||
*
|
||||
* @param array<string> $fields An array of fields to treat as virtual.
|
||||
* @param bool $merge Merge the new fields with the existing. By default false.
|
||||
* @return $this
|
||||
*/
|
||||
public function setVirtual(array $fields, bool $merge = false);
|
||||
|
||||
/**
|
||||
* Gets the virtual fields on this entity.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getVirtual(): array;
|
||||
|
||||
/**
|
||||
* Returns whether a field is an original one.
|
||||
* Original fields are those that an entity was instantiated with.
|
||||
*
|
||||
* @param string $name Name
|
||||
* @return bool
|
||||
*/
|
||||
public function isOriginalField(string $name): bool;
|
||||
|
||||
/**
|
||||
* Returns an array of original fields.
|
||||
* Original fields are those that an entity was initialized with.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getOriginalFields(): array;
|
||||
|
||||
/**
|
||||
* Sets the dirty status of a single field.
|
||||
*
|
||||
* @param string $field the field to set or check status for
|
||||
* @param bool $isDirty true means the field was changed, false means
|
||||
* it was not changed. Default true.
|
||||
* @return $this
|
||||
*/
|
||||
public function setDirty(string $field, bool $isDirty = true);
|
||||
|
||||
/**
|
||||
* Checks if the entity is dirty or if a single field of it is dirty.
|
||||
*
|
||||
* @param string|null $field The field to check the status for. Null for the whole entity.
|
||||
* @return bool Whether the field was changed or not
|
||||
*/
|
||||
public function isDirty(?string $field = null): bool;
|
||||
|
||||
/**
|
||||
* Gets the dirty fields.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getDirty(): array;
|
||||
|
||||
/**
|
||||
* Returns whether this entity has errors.
|
||||
*
|
||||
* @param bool $includeNested true will check nested entities for hasErrors()
|
||||
* @return bool
|
||||
*/
|
||||
public function hasErrors(bool $includeNested = true): bool;
|
||||
|
||||
/**
|
||||
* Returns all validation errors.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getErrors(): array;
|
||||
|
||||
/**
|
||||
* Returns validation errors of a field
|
||||
*
|
||||
* @param string $field Field name to get the errors from
|
||||
* @return array
|
||||
*/
|
||||
public function getError(string $field): array;
|
||||
|
||||
/**
|
||||
* Sets error messages to the entity
|
||||
*
|
||||
* @param array $errors The array of errors to set.
|
||||
* @param bool $overwrite Whether to overwrite pre-existing errors for $fields
|
||||
* @return $this
|
||||
*/
|
||||
public function setErrors(array $errors, bool $overwrite = false);
|
||||
|
||||
/**
|
||||
* Sets errors for a single field
|
||||
*
|
||||
* @param string $field The field to get errors for, or the array of errors to set.
|
||||
* @param array|string $errors The errors to be set for $field
|
||||
* @param bool $overwrite Whether to overwrite pre-existing errors for $field
|
||||
* @return $this
|
||||
*/
|
||||
public function setError(string $field, array|string $errors, bool $overwrite = false);
|
||||
|
||||
/**
|
||||
* Stores whether a field value can be changed or set in this entity.
|
||||
*
|
||||
* @param array<string>|string $field single or list of fields to change its accessibility
|
||||
* @param bool $set true marks the field as accessible, false will
|
||||
* mark it as protected.
|
||||
* @return $this
|
||||
*/
|
||||
public function setAccess(array|string $field, bool $set);
|
||||
|
||||
/**
|
||||
* Accessible configuration for this entity.
|
||||
*
|
||||
* @return array<bool>
|
||||
*/
|
||||
public function getAccessible(): array;
|
||||
|
||||
/**
|
||||
* Checks if a field is accessible
|
||||
*
|
||||
* @param string $field Field name to check
|
||||
* @return bool
|
||||
*/
|
||||
public function isAccessible(string $field): bool;
|
||||
|
||||
/**
|
||||
* Sets the source alias
|
||||
*
|
||||
* @param string $alias the alias of the repository
|
||||
* @return $this
|
||||
*/
|
||||
public function setSource(string $alias);
|
||||
|
||||
/**
|
||||
* Returns the alias of the repository from which this entity came from.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSource(): string;
|
||||
|
||||
/**
|
||||
* Returns an array with the requested original fields
|
||||
* stored in this entity, indexed by field name.
|
||||
*
|
||||
* @param array<string> $fields List of fields to be returned
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function extractOriginal(array $fields): array;
|
||||
|
||||
/**
|
||||
* Returns an array with only the original fields
|
||||
* stored in this entity, indexed by field name.
|
||||
*
|
||||
* @param array<string> $fields List of fields to be returned
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function extractOriginalChanged(array $fields): array;
|
||||
|
||||
/**
|
||||
* Sets one or multiple fields to the specified value
|
||||
*
|
||||
* @param array<string, mixed>|string $field the name of field to set or a list of
|
||||
* fields with their respective values
|
||||
* @param mixed $value The value to set to the field or an array if the
|
||||
* first argument is also an array, in which case will be treated as $options
|
||||
* @param array<string, mixed> $options Options to be used for setting the field. Allowed option
|
||||
* keys are `setter` and `guard`
|
||||
* @return $this
|
||||
*/
|
||||
public function set(array|string $field, mixed $value = null, array $options = []);
|
||||
|
||||
/**
|
||||
* Returns the value of a field by name
|
||||
*
|
||||
* @param string $field the name of the field to retrieve
|
||||
* @return mixed
|
||||
*/
|
||||
public function &get(string $field): mixed;
|
||||
|
||||
/**
|
||||
* Enable/disable field presence check when accessing a property.
|
||||
*
|
||||
* If enabled an exception will be thrown when trying to access a non-existent property.
|
||||
*
|
||||
* @param bool $value `true` to enable, `false` to disable.
|
||||
*/
|
||||
public function requireFieldPresence(bool $value = true): void;
|
||||
|
||||
/**
|
||||
* Returns whether a field has an original value
|
||||
*
|
||||
* @param string $field
|
||||
* @return bool
|
||||
*/
|
||||
public function hasOriginal(string $field): bool;
|
||||
|
||||
/**
|
||||
* Returns the original value of a field.
|
||||
*
|
||||
* @param string $field The name of the field.
|
||||
* @param bool $allowFallback whether to allow falling back to the current field value if no original exists
|
||||
* @return mixed
|
||||
*/
|
||||
public function getOriginal(string $field, bool $allowFallback = true): mixed;
|
||||
|
||||
/**
|
||||
* Gets all original values of the entity.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOriginalValues(): array;
|
||||
|
||||
/**
|
||||
* Returns whether this entity contains a field named $field.
|
||||
*
|
||||
* The method will return `true` even when the field is set to `null`.
|
||||
*
|
||||
* @param array<string>|string $field The field to check.
|
||||
* @return bool
|
||||
*/
|
||||
public function has(array|string $field): bool;
|
||||
|
||||
/**
|
||||
* Removes a field or list of fields from this entity
|
||||
*
|
||||
* @param array<string>|string $field The field to unset.
|
||||
* @return $this
|
||||
*/
|
||||
public function unset(array|string $field);
|
||||
|
||||
/**
|
||||
* Get the list of visible fields.
|
||||
*
|
||||
* @return array<string> A list of fields that are 'visible' in all representations.
|
||||
*/
|
||||
public function getVisible(): array;
|
||||
|
||||
/**
|
||||
* Returns an array with all the visible fields set in this entity.
|
||||
*
|
||||
* *Note* hidden fields are not visible, and will not be output
|
||||
* by toArray().
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(): array;
|
||||
|
||||
/**
|
||||
* Returns an array with the requested fields
|
||||
* stored in this entity, indexed by field name
|
||||
*
|
||||
* @param array<string> $fields list of fields to be returned
|
||||
* @param bool $onlyDirty Return the requested field only if it is dirty
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function extract(array $fields, bool $onlyDirty = false): array;
|
||||
|
||||
/**
|
||||
* Sets the entire entity as clean, which means that it will appear as
|
||||
* no fields being modified or added at all. This is an useful call
|
||||
* for an initial object hydration
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clean(): void;
|
||||
|
||||
/**
|
||||
* Set the status of this entity.
|
||||
*
|
||||
* Using `true` means that the entity has not been persisted in the database,
|
||||
* `false` indicates that the entity has been persisted.
|
||||
*
|
||||
* @param bool $new Indicate whether this entity has been persisted.
|
||||
* @return $this
|
||||
*/
|
||||
public function setNew(bool $new);
|
||||
|
||||
/**
|
||||
* Returns whether this entity has already been persisted.
|
||||
*
|
||||
* @return bool Whether the entity has been persisted.
|
||||
*/
|
||||
public function isNew(): bool;
|
||||
|
||||
/**
|
||||
* Returns a string representation of this object.
|
||||
*
|
||||
* @return string
|
||||
* @deprecated 5.2.0 Casting an entity to string is deprecated. Use `json_encode()` instead to get a string representation of the entity.
|
||||
*/
|
||||
public function __toString(): string;
|
||||
}
|
||||
+1491
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource\Exception;
|
||||
|
||||
use Cake\Core\Exception\CakeException;
|
||||
|
||||
/**
|
||||
* Exception raised when the provided primary key does not match the table primary key
|
||||
*/
|
||||
class InvalidPrimaryKeyException extends CakeException
|
||||
{
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
* @since 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource\Exception;
|
||||
|
||||
use Cake\Core\Exception\CakeException;
|
||||
|
||||
/**
|
||||
* Exception class to be thrown when a datasource configuration is not found
|
||||
*/
|
||||
class MissingDatasourceConfigException extends CakeException
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $_messageTemplate = 'The datasource configuration `%s` was not found.';
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
* @since 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource\Exception;
|
||||
|
||||
use Cake\Core\Exception\CakeException;
|
||||
|
||||
/**
|
||||
* Used when a datasource cannot be found.
|
||||
*/
|
||||
class MissingDatasourceException extends CakeException
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $_messageTemplate = 'Datasource class `%s` could not be found. %s';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource\Exception;
|
||||
|
||||
use Cake\Core\Exception\CakeException;
|
||||
|
||||
/**
|
||||
* Used when a model cannot be found.
|
||||
*/
|
||||
class MissingModelException extends CakeException
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $_messageTemplate = 'Model class `%s` of type `%s` could not be found.';
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 5.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource\Exception;
|
||||
|
||||
use Cake\Core\Exception\CakeException;
|
||||
|
||||
/**
|
||||
* A required property does not exist for an entity.
|
||||
*/
|
||||
class MissingPropertyException extends CakeException
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $_messageTemplate = 'Property `%s` does not exist for the entity `%s`.';
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource\Exception;
|
||||
|
||||
use Cake\Core\Exception\CakeException;
|
||||
use Cake\Core\Exception\HttpErrorCodeInterface;
|
||||
|
||||
/**
|
||||
* Exception raised when a particular record was not found
|
||||
*/
|
||||
class RecordNotFoundException extends CakeException implements HttpErrorCodeInterface
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected int $_defaultCode = 404;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.3.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource;
|
||||
|
||||
use Cake\Datasource\Locator\LocatorInterface;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class FactoryLocator
|
||||
*/
|
||||
class FactoryLocator
|
||||
{
|
||||
/**
|
||||
* A list of model factory functions.
|
||||
*
|
||||
* @var array<string, \Cake\Datasource\Locator\LocatorInterface>
|
||||
*/
|
||||
protected static array $_modelFactories = [];
|
||||
|
||||
/**
|
||||
* Register a locator to return repositories of a given type.
|
||||
*
|
||||
* @param string $type The name of the repository type the factory function is for.
|
||||
* @param \Cake\Datasource\Locator\LocatorInterface $factory The factory function used to create instances.
|
||||
* @return void
|
||||
*/
|
||||
public static function add(string $type, LocatorInterface $factory): void
|
||||
{
|
||||
static::$_modelFactories[$type] = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop a model factory.
|
||||
*
|
||||
* @param string $type The name of the repository type to drop the factory for.
|
||||
* @return void
|
||||
*/
|
||||
public static function drop(string $type): void
|
||||
{
|
||||
unset(static::$_modelFactories[$type]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the factory for the specified repository type.
|
||||
*
|
||||
* @param string $type The repository type to get the factory for.
|
||||
* @throws \InvalidArgumentException If the specified repository type has no factory.
|
||||
* @return \Cake\Datasource\Locator\LocatorInterface The factory for the repository type.
|
||||
*/
|
||||
public static function get(string $type): LocatorInterface
|
||||
{
|
||||
if (isset(static::$_modelFactories[$type])) {
|
||||
return static::$_modelFactories[$type];
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Unknown repository type `%s`. Make sure you register a type before trying to use it.',
|
||||
$type,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.1.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource;
|
||||
|
||||
/**
|
||||
* Defines the interface that testing fixtures use.
|
||||
*/
|
||||
interface FixtureInterface
|
||||
{
|
||||
/**
|
||||
* Run before each test is executed.
|
||||
*
|
||||
* Should insert all the records into the test database.
|
||||
*
|
||||
* @param \Cake\Datasource\ConnectionInterface $connection An instance of the connection
|
||||
* into which the records will be inserted.
|
||||
* @return bool
|
||||
*/
|
||||
public function insert(ConnectionInterface $connection): bool;
|
||||
|
||||
/**
|
||||
* Truncates the current fixture.
|
||||
*
|
||||
* @param \Cake\Datasource\ConnectionInterface $connection A reference to a db instance
|
||||
* @return bool
|
||||
*/
|
||||
public function truncate(ConnectionInterface $connection): bool;
|
||||
|
||||
/**
|
||||
* Get the connection name this fixture should be inserted into.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function connection(): string;
|
||||
|
||||
/**
|
||||
* Get the table/collection name for this fixture.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function sourceName(): string;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.2.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource;
|
||||
|
||||
/**
|
||||
* Describes the methods that any class representing a data storage should
|
||||
* comply with.
|
||||
*/
|
||||
interface InvalidPropertyInterface
|
||||
{
|
||||
/**
|
||||
* Get a list of invalid fields and their data for errors upon validation/patching
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getInvalid(): array;
|
||||
|
||||
/**
|
||||
* Set fields as invalid and not patchable into the entity.
|
||||
*
|
||||
* This is useful for batch operations when one needs to get the original value for an error message after patching.
|
||||
* This value could not be patched into the entity and is simply copied into the _invalid property for debugging
|
||||
* purposes or to be able to log it away.
|
||||
*
|
||||
* @param array<string, mixed> $fields The values to set.
|
||||
* @param bool $overwrite Whether to overwrite pre-existing values for $field.
|
||||
* @return $this
|
||||
*/
|
||||
public function setInvalid(array $fields, bool $overwrite = false);
|
||||
|
||||
/**
|
||||
* Get a single value of an invalid field. Returns null if not set.
|
||||
*
|
||||
* @param string $field The name of the field.
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getInvalidField(string $field): mixed;
|
||||
|
||||
/**
|
||||
* Sets a field as invalid and not patchable into the entity.
|
||||
*
|
||||
* @param string $field The value to set.
|
||||
* @param mixed $value The invalid value to be set for $field.
|
||||
* @return $this
|
||||
*/
|
||||
public function setInvalidField(string $field, mixed $value);
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
CakePHP(tm) : The Rapid Development PHP Framework (https://cakephp.org)
|
||||
Copyright (c) 2005-2020, Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 4.1.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource\Locator;
|
||||
|
||||
use Cake\Core\Exception\CakeException;
|
||||
use Cake\Datasource\RepositoryInterface;
|
||||
|
||||
/**
|
||||
* Provides an abstract registry/factory for repository objects.
|
||||
*
|
||||
* @template TRepo of \Cake\Datasource\RepositoryInterface
|
||||
* @implements \Cake\Datasource\Locator\LocatorInterface<TRepo>
|
||||
*/
|
||||
abstract class AbstractLocator implements LocatorInterface
|
||||
{
|
||||
/**
|
||||
* Instances that belong to the registry.
|
||||
*
|
||||
* @var array<string, TRepo>
|
||||
*/
|
||||
protected array $instances = [];
|
||||
|
||||
/**
|
||||
* Contains a list of options that were passed to get() method.
|
||||
*
|
||||
* @var array<string, array>
|
||||
*/
|
||||
protected array $options = [];
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @param string $alias The alias name you want to get.
|
||||
* @param array<string, mixed> $options The options you want to build the table with.
|
||||
* @return TRepo
|
||||
* @throws \Cake\Core\Exception\CakeException When trying to get alias for which instance
|
||||
* has already been created with different options.
|
||||
*/
|
||||
public function get(string $alias, array $options = []): RepositoryInterface
|
||||
{
|
||||
$storeOptions = $options;
|
||||
unset($storeOptions['allowFallbackClass']);
|
||||
|
||||
if (isset($this->instances[$alias])) {
|
||||
if ($storeOptions && isset($this->options[$alias]) && $this->options[$alias] !== $storeOptions) {
|
||||
throw new CakeException(sprintf(
|
||||
'You cannot configure `%s`, it already exists in the registry.',
|
||||
$alias,
|
||||
));
|
||||
}
|
||||
|
||||
return $this->instances[$alias];
|
||||
}
|
||||
|
||||
$this->options[$alias] = $storeOptions;
|
||||
|
||||
return $this->instances[$alias] = $this->createInstance($alias, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of a given classname.
|
||||
*
|
||||
* @param string $alias Repository alias.
|
||||
* @param array<string, mixed> $options The options you want to build the instance with.
|
||||
* @return TRepo
|
||||
*/
|
||||
abstract protected function createInstance(string $alias, array $options): RepositoryInterface;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function set(string $alias, RepositoryInterface $repository): RepositoryInterface
|
||||
{
|
||||
return $this->instances[$alias] = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function exists(string $alias): bool
|
||||
{
|
||||
return isset($this->instances[$alias]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function remove(string $alias): void
|
||||
{
|
||||
unset(
|
||||
$this->instances[$alias],
|
||||
$this->options[$alias],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
$this->instances = [];
|
||||
$this->options = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 4.1.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource\Locator;
|
||||
|
||||
use Cake\Datasource\RepositoryInterface;
|
||||
|
||||
/**
|
||||
* Registries for repository objects should implement this interface.
|
||||
*
|
||||
* @template TRepo of \Cake\Datasource\RepositoryInterface
|
||||
*/
|
||||
interface LocatorInterface
|
||||
{
|
||||
/**
|
||||
* Get a repository instance from the registry.
|
||||
*
|
||||
* @param string $alias The alias name you want to get.
|
||||
* @param array<string, mixed> $options The options you want to build the table with.
|
||||
* @return TRepo
|
||||
* @throws \RuntimeException When trying to get alias for which instance
|
||||
* has already been created with different options.
|
||||
*/
|
||||
public function get(string $alias, array $options = []): RepositoryInterface;
|
||||
|
||||
/**
|
||||
* Set a repository instance.
|
||||
*
|
||||
* @param string $alias The alias to set.
|
||||
* @param TRepo $repository The repository to set.
|
||||
* @return TRepo
|
||||
*/
|
||||
public function set(string $alias, RepositoryInterface $repository): RepositoryInterface;
|
||||
|
||||
/**
|
||||
* Check to see if an instance exists in the registry.
|
||||
*
|
||||
* @param string $alias The alias to check for.
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(string $alias): bool;
|
||||
|
||||
/**
|
||||
* Removes an repository instance from the registry.
|
||||
*
|
||||
* @param string $alias The alias to remove.
|
||||
* @return void
|
||||
*/
|
||||
public function remove(string $alias): void;
|
||||
|
||||
/**
|
||||
* Clears the registry of configuration and instances.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear(): void;
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource;
|
||||
|
||||
use Cake\Datasource\Exception\MissingModelException;
|
||||
use Cake\Datasource\Locator\LocatorInterface;
|
||||
use UnexpectedValueException;
|
||||
use function Cake\Core\pluginSplit;
|
||||
|
||||
/**
|
||||
* Provides functionality for loading table classes
|
||||
* and other repositories onto properties of the host object.
|
||||
*
|
||||
* Example users of this trait are {@link \Cake\Controller\Controller} and
|
||||
* {@link \Cake\Command\Command}.
|
||||
*/
|
||||
trait ModelAwareTrait
|
||||
{
|
||||
/**
|
||||
* This object's primary model class name. Should be a plural form.
|
||||
* CakePHP will not inflect the name.
|
||||
*
|
||||
* Example: For an object named 'Comments', the modelClass would be 'Comments'.
|
||||
* Plugin classes should use `Plugin.Comments` style names to correctly load
|
||||
* models from the correct plugin.
|
||||
*
|
||||
* Use empty string to not use auto-loading on this object. Null auto-detects based on
|
||||
* controller name.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected ?string $modelClass = null;
|
||||
|
||||
/**
|
||||
* A list of overridden model factory functions.
|
||||
*
|
||||
* @var array<callable|\Cake\Datasource\Locator\LocatorInterface>
|
||||
*/
|
||||
protected array $_modelFactories = [];
|
||||
|
||||
/**
|
||||
* The model type to use.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected string $_modelType = 'Table';
|
||||
|
||||
/**
|
||||
* Set the modelClass property based on conventions.
|
||||
*
|
||||
* If the property is already set it will not be overwritten
|
||||
*
|
||||
* @param string $name Class name.
|
||||
* @return void
|
||||
*/
|
||||
protected function _setModelClass(string $name): void
|
||||
{
|
||||
$this->modelClass ??= $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch or construct a model instance from a locator.
|
||||
*
|
||||
* Uses a modelFactory based on `$modelType` to fetch and construct a `RepositoryInterface`
|
||||
* and return it. The default `modelType` can be defined with `setModelType()`.
|
||||
*
|
||||
* Unlike `loadModel()` this method will *not* set an object property.
|
||||
*
|
||||
* If a repository provider does not return an object a MissingModelException will
|
||||
* be thrown.
|
||||
*
|
||||
* @param string|null $modelClass Name of model class to load. Defaults to $this->modelClass.
|
||||
* The name can be an alias like `'Post'` or FQCN like `App\Model\Table\PostsTable::class`.
|
||||
* @param string|null $modelType The type of repository to load. Defaults to the getModelType() value.
|
||||
* @return \Cake\Datasource\RepositoryInterface The model instance created.
|
||||
* @throws \Cake\Datasource\Exception\MissingModelException If the model class cannot be found.
|
||||
* @throws \UnexpectedValueException If $modelClass argument is not provided
|
||||
* and ModelAwareTrait::$modelClass property value is empty.
|
||||
*/
|
||||
public function fetchModel(?string $modelClass = null, ?string $modelType = null): RepositoryInterface
|
||||
{
|
||||
$modelClass ??= $this->modelClass;
|
||||
if (!$modelClass) {
|
||||
throw new UnexpectedValueException('Default modelClass is empty');
|
||||
}
|
||||
$modelType ??= $this->getModelType();
|
||||
|
||||
$options = [];
|
||||
if (!str_contains($modelClass, '\\')) {
|
||||
[, $alias] = pluginSplit($modelClass, true);
|
||||
} else {
|
||||
$options['className'] = $modelClass;
|
||||
$alias = substr(
|
||||
$modelClass,
|
||||
strrpos($modelClass, '\\') + 1,
|
||||
-strlen($modelType),
|
||||
);
|
||||
$modelClass = $alias;
|
||||
}
|
||||
|
||||
$factory = $this->_modelFactories[$modelType] ?? FactoryLocator::get($modelType);
|
||||
if ($factory instanceof LocatorInterface) {
|
||||
$instance = $factory->get($modelClass, $options);
|
||||
} else {
|
||||
$instance = $factory($modelClass, $options);
|
||||
}
|
||||
if ($instance) {
|
||||
return $instance;
|
||||
}
|
||||
|
||||
throw new MissingModelException([$modelClass, $modelType]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override a existing callable to generate repositories of a given type.
|
||||
*
|
||||
* @param string $type The name of the repository type the factory function is for.
|
||||
* @param \Cake\Datasource\Locator\LocatorInterface|callable $factory The factory function used to create instances.
|
||||
* @return void
|
||||
*/
|
||||
public function modelFactory(string $type, LocatorInterface|callable $factory): void
|
||||
{
|
||||
$this->_modelFactories[$type] = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the model type to be used by this class
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getModelType(): string
|
||||
{
|
||||
return $this->_modelType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the model type to be used by this class
|
||||
*
|
||||
* @param string $modelType The model type
|
||||
* @return $this
|
||||
*/
|
||||
public function setModelType(string $modelType)
|
||||
{
|
||||
$this->_modelType = $modelType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
* @since 3.5.0
|
||||
* @license https://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource\Paging\Exception;
|
||||
|
||||
use Cake\Core\Exception\CakeException;
|
||||
use Cake\Core\Exception\HttpErrorCodeInterface;
|
||||
|
||||
/**
|
||||
* Exception raised when requested page number does not exist.
|
||||
*/
|
||||
class PageOutOfBoundsException extends CakeException implements HttpErrorCodeInterface
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected int $_defaultCode = 404;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected string $_messageTemplate = 'Page number `%s` could not be found.';
|
||||
}
|
||||
@@ -0,0 +1,792 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.5.0
|
||||
* @license https://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource\Paging;
|
||||
|
||||
use Cake\Core\Exception\CakeException;
|
||||
use Cake\Core\InstanceConfigTrait;
|
||||
use Cake\Datasource\Paging\Exception\PageOutOfBoundsException;
|
||||
use Cake\Datasource\QueryInterface;
|
||||
use Cake\Datasource\RepositoryInterface;
|
||||
use Cake\Datasource\ResultSetInterface;
|
||||
use function Cake\Core\triggerWarning;
|
||||
|
||||
/**
|
||||
* This class is used to handle automatic model data pagination.
|
||||
*/
|
||||
class NumericPaginator implements PaginatorInterface
|
||||
{
|
||||
use InstanceConfigTrait;
|
||||
|
||||
/**
|
||||
* Default pagination settings.
|
||||
*
|
||||
* When calling paginate() these settings will be merged with the configuration
|
||||
* you provide.
|
||||
*
|
||||
* - `maxLimit` - The maximum limit users can choose to view. Defaults to 100
|
||||
* - `limit` - The initial number of items per page. Defaults to 20.
|
||||
* - `page` - The starting page, defaults to 1.
|
||||
* - `allowedParameters` - A list of parameters users are allowed to set using request
|
||||
* parameters. Modifying this list will allow users to have more influence
|
||||
* over pagination, be careful with what you permit.
|
||||
* - `sortableFields` - Controls which fields can be used for sorting. Accepts multiple formats:
|
||||
* - Simple array: A list of field names that can be sorted. By default all table
|
||||
* columns can be used. Use this to restrict sorting to specific fields. An empty
|
||||
* array will disable sorting altogether.
|
||||
* - Map with SortField objects: A map of sort keys to their corresponding database fields.
|
||||
* Allows creating friendly sort keys that map to one or more actual fields. Supports
|
||||
* simple mapping, multi-column sorting, locked directions, and default directions.
|
||||
* Can accept a callable that receives a SortableFieldsBuilder instance.
|
||||
*
|
||||
* Examples:
|
||||
* ```
|
||||
* // Simple array (traditional)
|
||||
* 'sortableFields' => ['title', 'created', 'author_id']
|
||||
*
|
||||
* // Map with SortField objects
|
||||
* 'sortableFields' => [
|
||||
* 'name' => 'Users.name',
|
||||
* 'newest' => [
|
||||
* SortField::desc('created'),
|
||||
* SortField::asc('title'),
|
||||
* ],
|
||||
* ]
|
||||
*
|
||||
* // Callable with builder
|
||||
* 'sortableFields' => function(SortableFieldsBuilder $builder) {
|
||||
* return $builder
|
||||
* ->add('name', SortField::asc('Users.name'))
|
||||
* ->add('popularity', SortField::desc('score', locked: true), 'created');
|
||||
* }
|
||||
* ```
|
||||
* - `finder` - The table finder to use. Defaults to `all`.
|
||||
* - `scope` - If specified this scope will be used to get the paging options
|
||||
* from the query params passed to paginate(). Scopes allow namespacing the
|
||||
* paging options and allows paginating multiple models in the same action.
|
||||
* Default `null`.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected array $_defaultConfig = [
|
||||
'page' => 1,
|
||||
'limit' => 20,
|
||||
'maxLimit' => 100,
|
||||
'allowedParameters' => ['limit', 'sort', 'page', 'direction'],
|
||||
'sortableFields' => null,
|
||||
'finder' => 'all',
|
||||
'scope' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* Calculated paging params.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $pagingParams = [
|
||||
'limit' => null,
|
||||
'maxLimit' => null,
|
||||
'count' => null,
|
||||
'totalCount' => null,
|
||||
'perPage' => null,
|
||||
'pageCount' => null,
|
||||
'currentPage' => null,
|
||||
'requestedPage' => null,
|
||||
'start' => null,
|
||||
'end' => null,
|
||||
'hasPrevPage' => null,
|
||||
'hasNextPage' => null,
|
||||
'sort' => null,
|
||||
'sortDefault' => null,
|
||||
'direction' => null,
|
||||
'directionDefault' => null,
|
||||
'completeSort' => null,
|
||||
'alias' => null,
|
||||
'scope' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* Handles automatic pagination of model records.
|
||||
*
|
||||
* ### Configuring pagination
|
||||
*
|
||||
* When calling `paginate()` you can use the $settings parameter to pass in
|
||||
* pagination settings. These settings are used to build the queries made
|
||||
* and control other pagination settings.
|
||||
*
|
||||
* If your settings contain a key with the current table's alias. The data
|
||||
* inside that key will be used. Otherwise, the top level configuration will
|
||||
* be used.
|
||||
*
|
||||
* ```
|
||||
* $settings = [
|
||||
* 'limit' => 20,
|
||||
* 'maxLimit' => 100
|
||||
* ];
|
||||
* $results = $paginator->paginate($table, $settings);
|
||||
* ```
|
||||
*
|
||||
* The above settings will be used to paginate any repository. You can configure
|
||||
* repository specific settings by keying the settings with the repository alias.
|
||||
*
|
||||
* ```
|
||||
* $settings = [
|
||||
* 'Articles' => [
|
||||
* 'limit' => 20,
|
||||
* 'maxLimit' => 100
|
||||
* ],
|
||||
* 'Comments' => [ ... ]
|
||||
* ];
|
||||
* $results = $paginator->paginate($table, $settings);
|
||||
* ```
|
||||
*
|
||||
* This would allow you to have different pagination settings for
|
||||
* `Articles` and `Comments` repositories.
|
||||
*
|
||||
* ### Controlling sort fields
|
||||
*
|
||||
* By default CakePHP will automatically allow sorting on any column on the
|
||||
* repository object being paginated. Often times you will want to allow
|
||||
* sorting on either associated columns or calculated fields. In these cases
|
||||
* you will need to define an allowed list of all the columns you wish to allow
|
||||
* sorting on. You can define the allowed sort fields in the `$settings` parameter:
|
||||
*
|
||||
* ```
|
||||
* $settings = [
|
||||
* 'Articles' => [
|
||||
* 'finder' => 'custom',
|
||||
* 'sortableFields' => ['title', 'author_id', 'comment_count'],
|
||||
* ]
|
||||
* ];
|
||||
* ```
|
||||
*
|
||||
* Passing an empty array as sortableFields disallows sorting altogether.
|
||||
*
|
||||
* ### Paginating with custom finders
|
||||
*
|
||||
* You can paginate with any find type defined on your table using the
|
||||
* `finder` option.
|
||||
*
|
||||
* ```
|
||||
* $settings = [
|
||||
* 'Articles' => [
|
||||
* 'finder' => 'popular'
|
||||
* ]
|
||||
* ];
|
||||
* $results = $paginator->paginate($table, $settings);
|
||||
* ```
|
||||
*
|
||||
* Would paginate using the `find('popular')` method.
|
||||
*
|
||||
* You can also pass an already created instance of a query to this method:
|
||||
*
|
||||
* ```
|
||||
* $query = $this->Articles->find('popular')->matching('Tags', function ($q) {
|
||||
* return $q->where(['name' => 'CakePHP'])
|
||||
* });
|
||||
* $results = $paginator->paginate($query);
|
||||
* ```
|
||||
*
|
||||
* ### Scoping Request parameters
|
||||
*
|
||||
* By using request parameter scopes you can paginate multiple queries in
|
||||
* the same controller action:
|
||||
*
|
||||
* ```
|
||||
* $articles = $paginator->paginate($articlesQuery, ['scope' => 'articles']);
|
||||
* $tags = $paginator->paginate($tagsQuery, ['scope' => 'tags']);
|
||||
* ```
|
||||
*
|
||||
* Each of the above queries will use different query string parameter sets
|
||||
* for pagination data. An example URL paginating both results would be:
|
||||
*
|
||||
* ```
|
||||
* /dashboard?articles[page]=1&tags[page]=2
|
||||
* ```
|
||||
*
|
||||
* @param mixed $target The repository or query
|
||||
* to paginate.
|
||||
* @param array $params Request params
|
||||
* @param array $settings The settings/configuration used for pagination.
|
||||
* @return \Cake\Datasource\Paging\PaginatedInterface
|
||||
* @throws \Cake\Datasource\Paging\Exception\PageOutOfBoundsException
|
||||
*/
|
||||
public function paginate(
|
||||
mixed $target,
|
||||
array $params = [],
|
||||
array $settings = [],
|
||||
): PaginatedInterface {
|
||||
$query = null;
|
||||
if ($target instanceof QueryInterface) {
|
||||
$query = $target;
|
||||
$target = $query->getRepository();
|
||||
if ($target === null) {
|
||||
throw new CakeException('No repository set for query.');
|
||||
}
|
||||
}
|
||||
|
||||
assert(
|
||||
$target instanceof RepositoryInterface,
|
||||
'Pagination target must be an instance of `' . QueryInterface::class
|
||||
. '` or `' . RepositoryInterface::class . '`.',
|
||||
);
|
||||
|
||||
$data = $this->extractData($target, $params, $settings);
|
||||
$query = $this->getQuery($target, $query, $data);
|
||||
|
||||
$countQuery = clone $query;
|
||||
$items = $this->getItems($query, $data);
|
||||
$this->pagingParams['count'] = count($items);
|
||||
$this->pagingParams['totalCount'] = $this->getCount($countQuery, $data);
|
||||
|
||||
$pagingParams = $this->buildParams($data);
|
||||
if ($pagingParams['requestedPage'] > $pagingParams['currentPage']) {
|
||||
throw new PageOutOfBoundsException([
|
||||
'requestedPage' => $pagingParams['requestedPage'],
|
||||
'pagingParams' => $pagingParams,
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->buildPaginated($items, $pagingParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build paginated result set.
|
||||
*
|
||||
* @param \Cake\Datasource\ResultSetInterface $items
|
||||
* @param array $pagingParams
|
||||
* @return \Cake\Datasource\Paging\PaginatedInterface
|
||||
*/
|
||||
protected function buildPaginated(ResultSetInterface $items, array $pagingParams): PaginatedInterface
|
||||
{
|
||||
return new PaginatedResultSet($items, $pagingParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get query for fetching paginated results.
|
||||
*
|
||||
* @param \Cake\Datasource\RepositoryInterface $object Repository instance.
|
||||
* @param \Cake\Datasource\QueryInterface|null $query Query Instance.
|
||||
* @param array<string, mixed> $data Pagination data.
|
||||
* @return \Cake\Datasource\QueryInterface
|
||||
*/
|
||||
protected function getQuery(RepositoryInterface $object, ?QueryInterface $query, array $data): QueryInterface
|
||||
{
|
||||
$options = $data['options'];
|
||||
$queryOptions = array_intersect_key(
|
||||
$options,
|
||||
['order' => null, 'page' => null, 'limit' => null],
|
||||
);
|
||||
|
||||
$args = [];
|
||||
$type = $options['finder'] ?? null;
|
||||
if (is_array($type)) {
|
||||
$args = (array)current($type);
|
||||
$type = key($type);
|
||||
}
|
||||
|
||||
if ($query === null) {
|
||||
$query = $object->find($type ?? 'all', ...$args);
|
||||
} elseif ($type !== null) {
|
||||
$query->find($type, ...$args);
|
||||
}
|
||||
|
||||
$query->applyOptions($queryOptions);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get paginated items.
|
||||
*
|
||||
* @param \Cake\Datasource\QueryInterface $query Query to fetch items.
|
||||
* @param array $data Paging data.
|
||||
* @return \Cake\Datasource\ResultSetInterface
|
||||
*/
|
||||
protected function getItems(QueryInterface $query, array $data): ResultSetInterface
|
||||
{
|
||||
return $query->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total count of records.
|
||||
*
|
||||
* @param \Cake\Datasource\QueryInterface $query Query instance.
|
||||
* @param array $data Pagination data.
|
||||
* @return int|null
|
||||
*/
|
||||
protected function getCount(QueryInterface $query, array $data): ?int
|
||||
{
|
||||
return $query->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract pagination data needed
|
||||
*
|
||||
* @param \Cake\Datasource\RepositoryInterface $object The repository object.
|
||||
* @param array<string, mixed> $params Request params
|
||||
* @param array<string, mixed> $settings The settings/configuration used for pagination.
|
||||
* @return array
|
||||
*/
|
||||
protected function extractData(RepositoryInterface $object, array $params, array $settings): array
|
||||
{
|
||||
$alias = $object->getAlias();
|
||||
$defaults = $this->getDefaults($alias, $settings);
|
||||
|
||||
$validSettings = array_keys($this->_defaultConfig);
|
||||
$validSettings[] = 'order';
|
||||
$extraSettings = array_diff_key($defaults, array_flip($validSettings));
|
||||
if ($extraSettings) {
|
||||
triggerWarning(
|
||||
'Passing query options as paginator settings is no longer supported.'
|
||||
. ' Use a custom finder through the `finder` config or pass a SelectQuery instance to paginate().'
|
||||
. ' Extra keys found are: `' . implode('`, `', array_keys($extraSettings)) . '`.',
|
||||
);
|
||||
}
|
||||
|
||||
$options = $this->mergeOptions($params, $defaults);
|
||||
$options = $this->validateSort($object, $options);
|
||||
$options = $this->checkLimit($options);
|
||||
|
||||
$options['page'] = max((int)$options['page'], 1);
|
||||
|
||||
return compact('defaults', 'options', 'alias');
|
||||
}
|
||||
|
||||
/**
|
||||
* Build pagination params.
|
||||
*
|
||||
* @param array<string, mixed> $data Paginator data containing keys 'options',
|
||||
* 'defaults', 'alias'.
|
||||
* @return array<string, mixed> Paging params.
|
||||
*/
|
||||
protected function buildParams(array $data): array
|
||||
{
|
||||
$this->pagingParams = [
|
||||
'perPage' => $data['options']['limit'],
|
||||
'requestedPage' => $data['options']['page'],
|
||||
'alias' => $data['alias'],
|
||||
'scope' => $data['options']['scope'],
|
||||
'maxLimit' => $data['options']['maxLimit'],
|
||||
] + $this->pagingParams;
|
||||
|
||||
$this->addPageCountParams($data);
|
||||
$this->addStartEndParams($data);
|
||||
$this->addPrevNextParams($data);
|
||||
$this->addSortingParams($data);
|
||||
|
||||
$this->pagingParams['limit'] = $data['defaults']['limit'] != $data['options']['limit']
|
||||
? $data['options']['limit']
|
||||
: null;
|
||||
|
||||
// Add sortableFields configuration for view helpers
|
||||
if (isset($data['options']['sortableFields'])) {
|
||||
$sortableFields = $data['options']['sortableFields'];
|
||||
if ($sortableFields instanceof SortableFieldsBuilder) {
|
||||
$this->pagingParams['sortableFields'] = $sortableFields->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->pagingParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add "currentPage" and "pageCount" params.
|
||||
*
|
||||
* @param array $data Paginator data.
|
||||
* @return void
|
||||
*/
|
||||
protected function addPageCountParams(array $data): void
|
||||
{
|
||||
$page = $data['options']['page'];
|
||||
$pageCount = null;
|
||||
|
||||
if ($this->pagingParams['totalCount'] !== null) {
|
||||
$pageCount = max((int)ceil($this->pagingParams['totalCount'] / $this->pagingParams['perPage']), 1);
|
||||
$page = min($page, $pageCount);
|
||||
} elseif ($this->pagingParams['count'] === 0 && $this->pagingParams['requestedPage'] > 1) {
|
||||
$page = 1;
|
||||
}
|
||||
|
||||
$this->pagingParams['currentPage'] = $page;
|
||||
$this->pagingParams['pageCount'] = $pageCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add "start" and "end" params.
|
||||
*
|
||||
* @param array $data Paginator data.
|
||||
* @return void
|
||||
*/
|
||||
protected function addStartEndParams(array $data): void
|
||||
{
|
||||
$start = 0;
|
||||
$end = 0;
|
||||
if ($this->pagingParams['count'] > 0) {
|
||||
$start = (($this->pagingParams['currentPage'] - 1) * $this->pagingParams['perPage']) + 1;
|
||||
$end = $start + $this->pagingParams['count'] - 1;
|
||||
}
|
||||
|
||||
$this->pagingParams['start'] = $start;
|
||||
$this->pagingParams['end'] = $end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add "prevPage" and "nextPage" params.
|
||||
*
|
||||
* @param array $data Paging data.
|
||||
* @return void
|
||||
*/
|
||||
protected function addPrevNextParams(array $data): void
|
||||
{
|
||||
$this->pagingParams['hasPrevPage'] = $this->pagingParams['currentPage'] > 1;
|
||||
if ($this->pagingParams['totalCount'] === null) {
|
||||
$this->pagingParams['hasNextPage'] = true;
|
||||
} else {
|
||||
$this->pagingParams['hasNextPage'] = $this->pagingParams['totalCount']
|
||||
> $this->pagingParams['currentPage'] * $this->pagingParams['perPage'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add sorting / ordering params.
|
||||
*
|
||||
* @param array $data Paging data.
|
||||
* @return void
|
||||
*/
|
||||
protected function addSortingParams(array $data): void
|
||||
{
|
||||
$defaults = $data['defaults'];
|
||||
$order = (array)$data['options']['order'];
|
||||
$sortDefault = false;
|
||||
$directionDefault = false;
|
||||
|
||||
if (!empty($defaults['order']) && count($defaults['order']) >= 1) {
|
||||
$sortDefault = key($defaults['order']);
|
||||
$directionDefault = current($defaults['order']);
|
||||
}
|
||||
if (isset($data['options']['sortDirection'])) {
|
||||
$direction = $data['options']['sortDirection'];
|
||||
} else {
|
||||
$direction = isset($data['options']['sort']) && count($order) ? current($order) : null;
|
||||
}
|
||||
|
||||
$this->pagingParams = [
|
||||
'sort' => $data['options']['sort'],
|
||||
'direction' => $direction,
|
||||
'sortDefault' => $sortDefault,
|
||||
'directionDefault' => $directionDefault,
|
||||
'completeSort' => $order,
|
||||
] + $this->pagingParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the various options that Paginator uses.
|
||||
* Pulls settings together from the following places:
|
||||
*
|
||||
* - General pagination settings
|
||||
* - Model specific settings.
|
||||
* - Request parameters
|
||||
*
|
||||
* The result of this method is the aggregate of all the option sets
|
||||
* combined together. You can change config value `allowedParameters` to modify
|
||||
* which options/values can be set using request parameters.
|
||||
*
|
||||
* @param array<string, mixed> $params Request params.
|
||||
* @param array $settings The settings to merge with the request data.
|
||||
* @return array<string, mixed> Array of merged options.
|
||||
*/
|
||||
protected function mergeOptions(array $params, array $settings): array
|
||||
{
|
||||
if (!empty($settings['scope'])) {
|
||||
$scope = $settings['scope'];
|
||||
$params = !empty($params[$scope]) ? (array)$params[$scope] : [];
|
||||
}
|
||||
$params = array_intersect_key($params, array_flip($this->getConfig('allowedParameters')));
|
||||
|
||||
return array_merge($settings, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the settings for a $model. If there are no settings for a specific
|
||||
* repository, the general settings will be used.
|
||||
*
|
||||
* @param string $alias Model name to get settings for.
|
||||
* @param array<string, mixed> $settings The settings which is used for combining.
|
||||
* @return array<string, mixed> An array of pagination settings for a model,
|
||||
* or the general settings.
|
||||
*/
|
||||
protected function getDefaults(string $alias, array $settings): array
|
||||
{
|
||||
if (isset($settings[$alias])) {
|
||||
$settings = $settings[$alias];
|
||||
}
|
||||
|
||||
$defaults = $this->getConfig();
|
||||
|
||||
$maxLimit = $settings['maxLimit'] ?? $defaults['maxLimit'];
|
||||
$limit = $settings['limit'] ?? $defaults['limit'];
|
||||
|
||||
if ($limit > $maxLimit) {
|
||||
$limit = $maxLimit;
|
||||
}
|
||||
|
||||
$settings['maxLimit'] = $maxLimit;
|
||||
$settings['limit'] = $limit;
|
||||
|
||||
return $settings + $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the desired sorting can be performed on the $object.
|
||||
*
|
||||
* Only fields or virtualFields can be sorted on. The direction param will
|
||||
* also be sanitized. Lastly sort + direction keys will be converted into
|
||||
* the model friendly order key.
|
||||
*
|
||||
/**
|
||||
* You can use the allowedParameters option to control which columns/fields are
|
||||
* available for sorting via URL parameters. This helps prevent users from ordering large
|
||||
* result sets on un-indexed values.
|
||||
*
|
||||
* If you need to sort on associated columns or synthetic properties you
|
||||
* will need to use the `sortableFields` option.
|
||||
*
|
||||
* Any columns listed in the allowed sort fields will be implicitly trusted.
|
||||
* You can use this to sort on synthetic columns, or columns added in custom
|
||||
* find operations that may not exist in the schema.
|
||||
*
|
||||
* The default order options provided to paginate() will be merged with the user's
|
||||
* requested sorting field/direction.
|
||||
*
|
||||
* @param \Cake\Datasource\RepositoryInterface $object Repository object.
|
||||
* @param array<string, mixed> $options The pagination options being used for this request.
|
||||
* @return array<string, mixed> An array of options with sort + direction removed and
|
||||
* replaced with order if possible.
|
||||
*/
|
||||
protected function validateSort(RepositoryInterface $object, array $options): array
|
||||
{
|
||||
// Check if we have sortableFields configured
|
||||
$sortableFields = $options['sortableFields'] ?? null;
|
||||
$builder = $sortableFields instanceof SortableFieldsBuilder
|
||||
? $sortableFields
|
||||
: SortableFieldsBuilder::create($sortableFields);
|
||||
|
||||
// Store the converted builder for later use in paging params
|
||||
if ($builder !== null) {
|
||||
$options['sortableFields'] = $builder;
|
||||
}
|
||||
|
||||
$sortAllowed = $builder !== null;
|
||||
|
||||
if (isset($options['sort'])) {
|
||||
// Parse sort and direction parameters
|
||||
$sortParams = $this->parseSortParams($options);
|
||||
|
||||
// Update options with parsed sort key (handles combined format)
|
||||
$options['sort'] = $sortParams['sortKey'];
|
||||
|
||||
if ($builder !== null) {
|
||||
// Use builder to resolve sort key
|
||||
$order = $builder->resolve(
|
||||
$sortParams['sortKey'],
|
||||
$sortParams['direction'],
|
||||
$sortParams['directionSpecified'],
|
||||
);
|
||||
|
||||
if ($order === null) {
|
||||
// Invalid sort key, clear sort
|
||||
$options['order'] = [];
|
||||
$options['sort'] = null;
|
||||
unset($options['direction']);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
// Merge with existing order - existing order comes AFTER our resolved order
|
||||
$existingOrder = isset($options['order']) && is_array($options['order']) ? $options['order'] : [];
|
||||
// Only keep fields from existing order that aren't already in our resolved order
|
||||
foreach ($existingOrder as $field => $dir) {
|
||||
if (!isset($order[$field])) {
|
||||
$order[$field] = $dir;
|
||||
}
|
||||
}
|
||||
$options['order'] = $order;
|
||||
$options['sortDirection'] = $sortParams['direction'];
|
||||
} else {
|
||||
// No sortableFields configured - allow any field (default behavior)
|
||||
$order = isset($options['order']) && is_array($options['order']) ? $options['order'] : [];
|
||||
if ($order && $sortParams['sortKey'] && !str_contains($sortParams['sortKey'], '.')) {
|
||||
$order = $this->_removeAliases($order, $object->getAlias());
|
||||
}
|
||||
|
||||
$options['order'] = [$sortParams['sortKey'] => $sortParams['direction']] + $order;
|
||||
}
|
||||
} else {
|
||||
$options['sort'] = null;
|
||||
}
|
||||
|
||||
unset($options['direction']);
|
||||
|
||||
if (empty($options['order'])) {
|
||||
$options['order'] = [];
|
||||
}
|
||||
if (!is_array($options['order'])) {
|
||||
return $options;
|
||||
}
|
||||
|
||||
if (
|
||||
$options['sort'] === null
|
||||
&& count($options['order']) >= 1
|
||||
&& !is_numeric(key($options['order']))
|
||||
) {
|
||||
$options['sort'] = key($options['order']);
|
||||
}
|
||||
|
||||
$options['order'] = $this->_prefix($object, $options['order'], $sortAllowed);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove alias if needed.
|
||||
*
|
||||
* @param array<string, mixed> $fields Current fields
|
||||
* @param string $model Current model alias
|
||||
* @return array<string, mixed> $fields Unaliased fields where applicable
|
||||
*/
|
||||
protected function _removeAliases(array $fields, string $model): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($fields as $field => $sort) {
|
||||
if (is_int($field)) {
|
||||
throw new CakeException(sprintf(
|
||||
'The `order` config must be an associative array. Found invalid value with numeric key: `%s`',
|
||||
$sort,
|
||||
));
|
||||
}
|
||||
|
||||
if (!str_contains($field, '.')) {
|
||||
$result[$field] = $sort;
|
||||
continue;
|
||||
}
|
||||
|
||||
[$alias, $currentField] = explode('.', $field);
|
||||
|
||||
if ($alias === $model) {
|
||||
$result[$currentField] = $sort;
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[$field] = $sort;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefixes the field with the table alias if possible.
|
||||
*
|
||||
* @param \Cake\Datasource\RepositoryInterface $object Repository object.
|
||||
* @param array $order Order array.
|
||||
* @param bool $allowed Whether the field was allowed.
|
||||
* @return array Final order array.
|
||||
*/
|
||||
protected function _prefix(RepositoryInterface $object, array $order, bool $allowed = false): array
|
||||
{
|
||||
$tableAlias = $object->getAlias();
|
||||
$tableOrder = [];
|
||||
foreach ($order as $key => $value) {
|
||||
if (is_numeric($key)) {
|
||||
$tableOrder[] = $value;
|
||||
continue;
|
||||
}
|
||||
$field = $key;
|
||||
$alias = $tableAlias;
|
||||
|
||||
if (str_contains($key, '.')) {
|
||||
[$alias, $field] = explode('.', $key);
|
||||
}
|
||||
$correctAlias = ($tableAlias === $alias);
|
||||
|
||||
if ($correctAlias && $allowed) {
|
||||
// Disambiguate fields in schema. As id is quite common.
|
||||
if ($object->hasField($field)) {
|
||||
$field = $alias . '.' . $field;
|
||||
}
|
||||
$tableOrder[$field] = $value;
|
||||
} elseif ($correctAlias && $object->hasField($field)) {
|
||||
$tableOrder[$tableAlias . '.' . $field] = $value;
|
||||
} elseif (!$correctAlias && $allowed) {
|
||||
$tableOrder[$alias . '.' . $field] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $tableOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse sort parameters from options.
|
||||
*
|
||||
* Extracts and normalizes sort key and direction from pagination options.
|
||||
* Supports both traditional format (?sort=field&direction=asc) and
|
||||
* combined format (?sort=field-asc).
|
||||
*
|
||||
* @param array<string, mixed> $options The options array
|
||||
* @return array{sortKey: string, direction: string, directionSpecified: bool}
|
||||
*/
|
||||
protected function parseSortParams(array $options): array
|
||||
{
|
||||
$sortKey = $options['sort'];
|
||||
$direction = isset($options['direction']) ? strtolower($options['direction']) : SortField::ASC;
|
||||
$directionSpecified = isset($options['direction']);
|
||||
|
||||
// Check for combined sort-direction format (e.g., 'title-asc' or 'title-desc')
|
||||
if (preg_match('/^(.+)-(asc|desc)$/i', $sortKey, $matches)) {
|
||||
$sortKey = $matches[1];
|
||||
$direction = strtolower($matches[2]);
|
||||
$directionSpecified = true;
|
||||
}
|
||||
|
||||
// Validate direction
|
||||
if (!in_array($direction, [SortField::ASC, SortField::DESC], true)) {
|
||||
$direction = SortField::ASC;
|
||||
}
|
||||
|
||||
return [
|
||||
'sortKey' => $sortKey,
|
||||
'direction' => $direction,
|
||||
'directionSpecified' => $directionSpecified,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the limit parameter and ensure it's within the maxLimit bounds.
|
||||
*
|
||||
* @param array<string, mixed> $options An array of options with a limit key to be checked.
|
||||
* @return array<string, mixed> An array of options for pagination.
|
||||
*/
|
||||
protected function checkLimit(array $options): array
|
||||
{
|
||||
$options['limit'] = (int)$options['limit'];
|
||||
if ($options['limit'] < 1) {
|
||||
$options['limit'] = 1;
|
||||
}
|
||||
$options['limit'] = max(min($options['limit'], $options['maxLimit']), 1);
|
||||
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://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. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @since 5.0.0
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource\Paging;
|
||||
|
||||
use Countable;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* This interface describes the methods for pagination instance.
|
||||
*
|
||||
* @template TKey
|
||||
* @template-covariant TValue
|
||||
* @template-extends \Traversable<TKey, TValue>
|
||||
* @method array<mixed> toArray() Get the paginated items as an array
|
||||
*/
|
||||
interface PaginatedInterface extends Countable, Traversable
|
||||
{
|
||||
/**
|
||||
* Get current page number.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function currentPage(): int;
|
||||
|
||||
/**
|
||||
* Get items per page.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function perPage(): int;
|
||||
|
||||
/**
|
||||
* Get Total items counts.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function totalCount(): ?int;
|
||||
|
||||
/**
|
||||
* Get total page count.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function pageCount(): ?int;
|
||||
|
||||
/**
|
||||
* Get whether there's a previous page.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPrevPage(): bool;
|
||||
|
||||
/**
|
||||
* Get whether there's a next page.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasNextPage(): bool;
|
||||
|
||||
/**
|
||||
* Get paginated items.
|
||||
*
|
||||
* @return iterable<TKey, TValue>
|
||||
*/
|
||||
public function items(): iterable;
|
||||
|
||||
/**
|
||||
* Get paging param.
|
||||
*
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function pagingParam(string $name): mixed;
|
||||
|
||||
/**
|
||||
* Get all paging params.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function pagingParams(): array;
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 5.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource\Paging;
|
||||
|
||||
use IteratorAggregate;
|
||||
use JsonSerializable;
|
||||
use Traversable;
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
/**
|
||||
* Paginated result set.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @implements \IteratorAggregate<TKey, TValue>
|
||||
* @implements \Cake\Datasource\Paging\PaginatedInterface<TKey, TValue>
|
||||
*/
|
||||
class PaginatedResultSet implements IteratorAggregate, JsonSerializable, PaginatedInterface
|
||||
{
|
||||
/**
|
||||
* Resultset instance.
|
||||
*
|
||||
* @var \Traversable<TKey, TValue>
|
||||
*/
|
||||
protected Traversable $results;
|
||||
|
||||
/**
|
||||
* Paging params.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $params = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \Traversable<TKey, TValue> $results Resultset instance.
|
||||
* @param array $params Paging params.
|
||||
*/
|
||||
public function __construct(Traversable $results, array $params)
|
||||
{
|
||||
$this->results = $results;
|
||||
$this->params = $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return $this->params['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the paginated items as an array.
|
||||
*
|
||||
* This will exhaust the iterator `items`.
|
||||
*
|
||||
* @return array<array-key, TValue>
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->jsonSerialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get paginated items.
|
||||
*
|
||||
* @return \Traversable<TKey, TValue> The paginated items result set.
|
||||
*/
|
||||
public function items(): Traversable
|
||||
{
|
||||
return $this->results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide data which should be serialized to JSON.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return iterator_to_array($this->items());
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function totalCount(): ?int
|
||||
{
|
||||
return $this->params['totalCount'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function perPage(): int
|
||||
{
|
||||
return $this->params['perPage'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function pageCount(): ?int
|
||||
{
|
||||
return $this->params['pageCount'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function currentPage(): int
|
||||
{
|
||||
return $this->params['currentPage'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function hasPrevPage(): bool
|
||||
{
|
||||
return $this->params['hasPrevPage'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function hasNextPage(): bool
|
||||
{
|
||||
return $this->params['hasNextPage'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function pagingParam(string $name): mixed
|
||||
{
|
||||
return $this->params[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function pagingParams(): array
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
return $this->results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxies method calls to internal result set instance.
|
||||
*
|
||||
* @param string $name Method name
|
||||
* @param array $arguments Arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call(string $name, array $arguments): mixed
|
||||
{
|
||||
deprecationWarning(
|
||||
'5.1.0',
|
||||
sprintf(
|
||||
'Calling `%s` methods, such as `%s()`, on PaginatedResultSet is deprecated. ' .
|
||||
'You must call `items()` first (for example, `items()->%s()`).',
|
||||
$this->results::class,
|
||||
$name,
|
||||
$name,
|
||||
),
|
||||
);
|
||||
|
||||
return $this->results->$name(...$arguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @since 5.0.0
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource\Paging;
|
||||
|
||||
/**
|
||||
* This interface describes the methods for paginator instance.
|
||||
*/
|
||||
interface PaginatorInterface
|
||||
{
|
||||
/**
|
||||
* Handles pagination of data.
|
||||
*
|
||||
* @param mixed $target Anything that needs to be paginated.
|
||||
* @param array $params Request params.
|
||||
* @param array $settings The settings/configuration used for pagination.
|
||||
* @return \Cake\Datasource\Paging\PaginatedInterface
|
||||
*/
|
||||
public function paginate(
|
||||
mixed $target,
|
||||
array $params = [],
|
||||
array $settings = [],
|
||||
): PaginatedInterface;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.9.0
|
||||
* @license https://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource\Paging;
|
||||
|
||||
use Cake\Datasource\QueryInterface;
|
||||
use Cake\Datasource\ResultSetInterface;
|
||||
|
||||
/**
|
||||
* Simplified paginator which avoids potentially expensive queries
|
||||
* to get the total count of records.
|
||||
*
|
||||
* When using a simple paginator you will not be able to generate page numbers.
|
||||
* Instead use only the prev/next pagination controls.
|
||||
*/
|
||||
class SimplePaginator extends NumericPaginator
|
||||
{
|
||||
/**
|
||||
* Get paginated items.
|
||||
*
|
||||
* Get one additional record than the limit. This helps deduce if next page exits.
|
||||
*
|
||||
* @param \Cake\Datasource\QueryInterface $query Query to fetch items.
|
||||
* @param array $data Paging data.
|
||||
* @return \Cake\Datasource\ResultSetInterface
|
||||
*/
|
||||
protected function getItems(QueryInterface $query, array $data): ResultSetInterface
|
||||
{
|
||||
return $query->limit($data['options']['limit'] + 1)->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function buildParams(array $data): array
|
||||
{
|
||||
$hasNextPage = false;
|
||||
if ($this->pagingParams['count'] > $data['options']['limit']) {
|
||||
$hasNextPage = true;
|
||||
$this->pagingParams['count'] -= 1;
|
||||
}
|
||||
|
||||
parent::buildParams($data);
|
||||
|
||||
$this->pagingParams['hasNextPage'] = $hasNextPage;
|
||||
|
||||
return $this->pagingParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build paginated result set.
|
||||
*
|
||||
* Since the query fetches an extra record, drop the last record if records
|
||||
* fetched exceeds the limit/per page.
|
||||
*
|
||||
* @param \Cake\Datasource\ResultSetInterface $items
|
||||
* @param array $pagingParams
|
||||
* @return \Cake\Datasource\Paging\PaginatedInterface
|
||||
*/
|
||||
protected function buildPaginated(ResultSetInterface $items, array $pagingParams): PaginatedInterface
|
||||
{
|
||||
if (count($items) > $this->pagingParams['perPage']) {
|
||||
$items = $items->take($this->pagingParams['perPage']);
|
||||
}
|
||||
|
||||
return new PaginatedResultSet($items, $pagingParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple pagination does not perform any count query, so this method returns `null`.
|
||||
*
|
||||
* @param \Cake\Datasource\QueryInterface $query Query instance.
|
||||
* @param array $data Pagination data.
|
||||
* @return int|null
|
||||
*/
|
||||
protected function getCount(QueryInterface $query, array $data): ?int
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 5.3.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource\Paging;
|
||||
|
||||
/**
|
||||
* Represents a sort field configuration for pagination.
|
||||
*/
|
||||
class SortField
|
||||
{
|
||||
/**
|
||||
* Ascending sort direction
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ASC = 'asc';
|
||||
|
||||
/**
|
||||
* Descending sort direction
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const DESC = 'desc';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $field The field name to sort by
|
||||
* @param string|null $defaultDirection The default sort direction
|
||||
* @param bool $locked Whether the sort direction is locked
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $field,
|
||||
protected ?string $defaultDirection = null,
|
||||
protected bool $locked = false,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sort field with ascending default direction.
|
||||
*
|
||||
* @param string $field The field name to sort by
|
||||
* @param bool $locked Whether the sort direction is locked
|
||||
* @return self
|
||||
*/
|
||||
public static function asc(string $field, bool $locked = false): self
|
||||
{
|
||||
return new self($field, self::ASC, $locked);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sort field with descending default direction.
|
||||
*
|
||||
* @param string $field The field name to sort by
|
||||
* @param bool $locked Whether the sort direction is locked
|
||||
* @return self
|
||||
*/
|
||||
public static function desc(string $field, bool $locked = false): self
|
||||
{
|
||||
return new self($field, self::DESC, $locked);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the field name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getField(): string
|
||||
{
|
||||
return $this->field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sort direction to use.
|
||||
*
|
||||
* @param string $requestedDirection The direction requested by the user
|
||||
* @param bool $directionSpecified Whether a direction was explicitly specified
|
||||
* @return string
|
||||
*/
|
||||
public function getDirection(string $requestedDirection, bool $directionSpecified): string
|
||||
{
|
||||
if ($this->locked) {
|
||||
return $this->defaultDirection ?? self::ASC;
|
||||
}
|
||||
|
||||
if (!$directionSpecified && $this->defaultDirection) {
|
||||
return $this->defaultDirection;
|
||||
}
|
||||
|
||||
if ($this->defaultDirection === static::DESC) {
|
||||
return $requestedDirection === static::DESC ? static::ASC : static::DESC;
|
||||
}
|
||||
|
||||
return $requestedDirection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the sort direction is locked.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isLocked(): bool
|
||||
{
|
||||
return $this->locked;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 5.3.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource\Paging;
|
||||
|
||||
use Closure;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Builder for creating complete sortable fields configurations.
|
||||
*
|
||||
* Provides interface for building sortable fields with multiple sort keys and fields.
|
||||
* Also handles resolution of sort keys to database ORDER BY clauses.
|
||||
*/
|
||||
class SortableFieldsBuilder
|
||||
{
|
||||
/**
|
||||
* @var array<string, array<\Cake\Datasource\Paging\SortField|string>|string> The sortable fields map being built
|
||||
*/
|
||||
protected array $map = [];
|
||||
|
||||
/**
|
||||
* @var bool Whether this builder represents a simple array format
|
||||
*/
|
||||
protected bool $isSimpleArray = false;
|
||||
|
||||
/**
|
||||
* Create builder from various sortableFields configurations.
|
||||
*
|
||||
* @param \Closure|array<mixed>|null $config The sortableFields configuration
|
||||
* @return static|null Builder instance or null if no config
|
||||
*/
|
||||
public static function create(array|Closure|null $config): ?static
|
||||
{
|
||||
if ($config === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($config instanceof Closure) {
|
||||
return static::fromCallable($config);
|
||||
}
|
||||
|
||||
return static::fromArray($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create builder from array configuration.
|
||||
*
|
||||
* Handles both simple array format (['field1', 'field2']) and
|
||||
* associative map format (['key' => 'field', ...]).
|
||||
*
|
||||
* @param array<mixed> $config Array configuration
|
||||
* @return static
|
||||
*/
|
||||
public static function fromArray(array $config): static
|
||||
{
|
||||
$builder = new static();
|
||||
$hasNumericKeys = false;
|
||||
|
||||
// Check if it's a simple array format
|
||||
foreach ($config as $key => $value) {
|
||||
if (is_int($key)) {
|
||||
$hasNumericKeys = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasNumericKeys) {
|
||||
// Simple or mixed format - convert numeric keys
|
||||
$builder->isSimpleArray = true;
|
||||
foreach ($config as $key => $value) {
|
||||
if (is_int($key) && is_string($value)) {
|
||||
// Numeric key with string value: 'field' becomes 'field' => ['field']
|
||||
$builder->add($value, $value);
|
||||
} else {
|
||||
// String key: use as-is
|
||||
$builder->set($key, $value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Associative map format
|
||||
foreach ($config as $key => $value) {
|
||||
$builder->set($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return $builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create builder from callable factory.
|
||||
*
|
||||
* @param \Closure $factory Closure that receives builder and returns it
|
||||
* @return static
|
||||
*/
|
||||
public static function fromCallable(Closure $factory): static
|
||||
{
|
||||
$builder = new static();
|
||||
$builder = $factory($builder);
|
||||
|
||||
return $builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a sort key with its associated SortField objects.
|
||||
*
|
||||
* @param string $sortKey The sort key name
|
||||
* @param \Cake\Datasource\Paging\SortField|string ...$fields The sort fields to add
|
||||
* @return $this
|
||||
*/
|
||||
public function add(string $sortKey, SortField|string ...$fields)
|
||||
{
|
||||
if ($fields === []) {
|
||||
// If no fields provided, use the key as the field name
|
||||
$this->map[$sortKey] = [$sortKey];
|
||||
} else {
|
||||
$this->map[$sortKey] = $fields;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a sort key with type-safe validation.
|
||||
*
|
||||
* Internal method used by fromArray() to ensure type safety while preserving
|
||||
* backward compatibility with string and array representations.
|
||||
*
|
||||
* @param string $sortKey The sort key name
|
||||
* @param mixed $value The sort field(s) - can be string, SortField, or array
|
||||
* @return $this
|
||||
*/
|
||||
protected function set(string $sortKey, mixed $value)
|
||||
{
|
||||
if (is_string($value)) {
|
||||
$this->map[$sortKey] = $value;
|
||||
} elseif ($value instanceof SortField) {
|
||||
$this->map[$sortKey] = [$value];
|
||||
} elseif (is_array($value)) {
|
||||
$this->add($sortKey, ...$value);
|
||||
} else {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Invalid sortable field value type for key `%s`. Expected string, array, or SortField, got `%s`.',
|
||||
$sortKey,
|
||||
get_debug_type($value),
|
||||
));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the complete sortable fields map.
|
||||
*
|
||||
* @return array<string, array<\Cake\Datasource\Paging\SortField|string>|string>
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a sort key to its corresponding ORDER BY clause.
|
||||
*
|
||||
* @param string $sortKey The sort key from URL
|
||||
* @param string $direction The requested direction (asc/desc)
|
||||
* @param bool $directionSpecified Whether direction was explicitly specified
|
||||
* @return array<string, string>|null Array of field => direction pairs, or null if invalid
|
||||
*/
|
||||
public function resolve(
|
||||
string $sortKey,
|
||||
string $direction,
|
||||
bool $directionSpecified = true,
|
||||
): ?array {
|
||||
// Check if sort key exists in map
|
||||
if (!isset($this->map[$sortKey])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mapping = $this->map[$sortKey];
|
||||
|
||||
// Empty array means use key as field
|
||||
if ($mapping === []) {
|
||||
return [$sortKey => $direction];
|
||||
}
|
||||
|
||||
return $this->resolveMapping($mapping, $direction, $directionSpecified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a mapping configuration to ORDER BY clause.
|
||||
*
|
||||
* @param mixed $mapping The mapping to resolve
|
||||
* @param string $direction The requested direction
|
||||
* @param bool $directionSpecified Whether direction was explicitly specified
|
||||
* @return array<string, string> Array of field => direction pairs
|
||||
*/
|
||||
protected function resolveMapping(mixed $mapping, string $direction, bool $directionSpecified): array
|
||||
{
|
||||
// Single string: 'name' => 'Users.name'
|
||||
if (is_string($mapping)) {
|
||||
return [$mapping => $direction];
|
||||
}
|
||||
|
||||
// Array of fields/SortField objects
|
||||
if (is_array($mapping)) {
|
||||
return $this->resolveArrayMapping($mapping, $direction, $directionSpecified);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an array mapping to ORDER BY clause.
|
||||
*
|
||||
* @param array<mixed> $fields Array of fields or SortField objects
|
||||
* @param string $direction The requested direction
|
||||
* @param bool $directionSpecified Whether direction was explicitly specified
|
||||
* @return array<string, string> Array of field => direction pairs
|
||||
*/
|
||||
protected function resolveArrayMapping(array $fields, string $direction, bool $directionSpecified): array
|
||||
{
|
||||
$order = [];
|
||||
$shouldInvert = $directionSpecified && $direction === SortField::DESC;
|
||||
|
||||
foreach ($fields as $key => $value) {
|
||||
if ($value instanceof SortField) {
|
||||
// SortField object with locked/default directions
|
||||
$field = $value->getField();
|
||||
$fieldDirection = $value->getDirection($direction, $directionSpecified);
|
||||
$order[$field] = $fieldDirection;
|
||||
} elseif (is_int($key)) {
|
||||
// Numeric array: ['field1', 'field2'] - use requested direction
|
||||
$order[$value] = $direction;
|
||||
} elseif (is_string($value)) {
|
||||
// Associative array with default directions per field
|
||||
// Format: ['field1' => 'ASC', 'field2' => 'DESC']
|
||||
$defaultDirection = strtolower($value);
|
||||
|
||||
if ($shouldInvert) {
|
||||
// Invert the direction when toggling to desc
|
||||
$fieldDirection = $defaultDirection === SortField::ASC ? SortField::DESC : SortField::ASC;
|
||||
} else {
|
||||
// Use default direction (for asc or no direction specified)
|
||||
$fieldDirection = $defaultDirection;
|
||||
}
|
||||
|
||||
$order[$key] = $fieldDirection;
|
||||
} else {
|
||||
// Fallback for other cases
|
||||
$order[$key] = $direction;
|
||||
}
|
||||
}
|
||||
|
||||
return $order;
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource;
|
||||
|
||||
use Cake\Cache\Cache;
|
||||
use Cake\Core\Exception\CakeException;
|
||||
use Closure;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Handles caching queries and loading results from the cache.
|
||||
*
|
||||
* Used by {@link \Cake\Datasource\QueryTrait} internally.
|
||||
*
|
||||
* @internal
|
||||
* @see \Cake\Datasource\QueryTrait::cache() for the public interface.
|
||||
*/
|
||||
class QueryCacher
|
||||
{
|
||||
/**
|
||||
* The key or function to generate a key.
|
||||
*
|
||||
* @var \Closure|string
|
||||
*/
|
||||
protected Closure|string $_key;
|
||||
|
||||
/**
|
||||
* Config for cache engine.
|
||||
*
|
||||
* @var \Psr\SimpleCache\CacheInterface|string
|
||||
*/
|
||||
protected CacheInterface|string $_config;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Closure|string $key The key or function to generate a key.
|
||||
* @param \Psr\SimpleCache\CacheInterface|string $config The cache config name or cache engine instance.
|
||||
*/
|
||||
public function __construct(Closure|string $key, CacheInterface|string $config)
|
||||
{
|
||||
$this->_key = $key;
|
||||
$this->_config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the cached results from the cache or run the query.
|
||||
*
|
||||
* @param object $query The query the cache read is for.
|
||||
* @return mixed|null Either the cached results or null.
|
||||
*/
|
||||
public function fetch(object $query): mixed
|
||||
{
|
||||
$key = $this->_resolveKey($query);
|
||||
$storage = $this->_resolveCacher();
|
||||
$result = $storage->get($key);
|
||||
if (!$result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the result set into the cache.
|
||||
*
|
||||
* @param object $query The query the cache read is for.
|
||||
* @param \Traversable $results The result set to store.
|
||||
* @return bool True if the data was successfully cached, false on failure
|
||||
*/
|
||||
public function store(object $query, Traversable $results): bool
|
||||
{
|
||||
$key = $this->_resolveKey($query);
|
||||
$storage = $this->_resolveCacher();
|
||||
|
||||
return $storage->set($key, $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/generate the cache key.
|
||||
*
|
||||
* @param object $query The query to generate a key for.
|
||||
* @return string
|
||||
* @throws \Cake\Core\Exception\CakeException
|
||||
*/
|
||||
protected function _resolveKey(object $query): string
|
||||
{
|
||||
if (is_string($this->_key)) {
|
||||
return $this->_key;
|
||||
}
|
||||
$func = $this->_key;
|
||||
$key = $func($query);
|
||||
if (!is_string($key)) {
|
||||
$msg = sprintf('Cache key functions must return a string. Got %s.', var_export($key, true));
|
||||
throw new CakeException($msg);
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache engine.
|
||||
*
|
||||
* @return \Psr\SimpleCache\CacheInterface
|
||||
*/
|
||||
protected function _resolveCacher(): CacheInterface
|
||||
{
|
||||
if (is_string($this->_config)) {
|
||||
return Cache::pool($this->_config);
|
||||
}
|
||||
|
||||
return $this->_config;
|
||||
}
|
||||
}
|
||||
+461
@@ -0,0 +1,461 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.1
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Cake\Datasource;
|
||||
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* The basis for every query object
|
||||
*
|
||||
* @method $this andWhere($conditions, array $types = []) Connects any previously defined set of conditions to the
|
||||
* provided list using the AND operator. {@see \Cake\Database\Query::andWhere()}
|
||||
* @method \Cake\Datasource\EntityInterface|array firstOrFail() Get the first result from the executing query or raise an exception.
|
||||
* {@see \Cake\Database\Query::firstOrFail()}
|
||||
*/
|
||||
interface QueryInterface
|
||||
{
|
||||
/**
|
||||
* Adds fields to be selected from datasource.
|
||||
*
|
||||
* Calling this function multiple times will append more fields to the list
|
||||
* of fields to be selected.
|
||||
*
|
||||
* If `true` is passed in the second argument, any previous selections will
|
||||
* be overwritten with the list passed in the first argument.
|
||||
*
|
||||
* @param \Closure|array|string|float|int $fields Fields.
|
||||
* @param bool $overwrite whether to reset fields with passed list or not
|
||||
* @return $this
|
||||
*/
|
||||
public function select(Closure|array|string|float|int $fields, bool $overwrite = false);
|
||||
|
||||
/**
|
||||
* Returns a key => value array representing a single aliased field
|
||||
* that can be passed directly to the select() method.
|
||||
* The key will contain the alias and the value the actual field name.
|
||||
*
|
||||
* If the field is already aliased, then it will not be changed.
|
||||
* If no $alias is passed, the default table for this query will be used.
|
||||
*
|
||||
* @param string $field The field to alias
|
||||
* @param string|null $alias the alias used to prefix the field
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function aliasField(string $field, ?string $alias = null): array;
|
||||
|
||||
/**
|
||||
* Runs `aliasField()` for each field in the provided list and returns
|
||||
* the result under a single array.
|
||||
*
|
||||
* @param array<int|string, string|\Cake\Database\Expression\IdentifierExpression> $fields The fields to alias
|
||||
* @param string|null $defaultAlias The default alias
|
||||
* @return array<int|string, string|\Cake\Database\Expression\IdentifierExpression>
|
||||
*/
|
||||
public function aliasFields(array $fields, ?string $defaultAlias = null): array;
|
||||
|
||||
/**
|
||||
* Fetch the results for this query.
|
||||
*
|
||||
* Will return either the results set through setResult(), or execute this query
|
||||
* and return the ResultSetDecorator object ready for streaming of results.
|
||||
*
|
||||
* ResultSetDecorator is a traversable object that implements the methods found
|
||||
* on Cake\Collection\Collection.
|
||||
*
|
||||
* @template TKey of array-key
|
||||
* @template TValue of mixed
|
||||
* @return \Cake\Datasource\ResultSetInterface<TKey, TValue>
|
||||
*/
|
||||
public function all(): ResultSetInterface;
|
||||
|
||||
/**
|
||||
* Populates or adds parts to current query clauses using an array.
|
||||
* This is handy for passing all query clauses at once. The option array accepts:
|
||||
*
|
||||
* - fields: Maps to the select method
|
||||
* - conditions: Maps to the where method
|
||||
* - limit: Maps to the limit method
|
||||
* - order: Maps to the order method
|
||||
* - offset: Maps to the offset method
|
||||
* - group: Maps to the group method
|
||||
* - having: Maps to the having method
|
||||
* - contain: Maps to the contain options for eager loading
|
||||
* - join: Maps to the join method
|
||||
* - page: Maps to the page method
|
||||
*
|
||||
* ### Example:
|
||||
*
|
||||
* ```
|
||||
* $query->applyOptions([
|
||||
* 'fields' => ['id', 'name'],
|
||||
* 'conditions' => [
|
||||
* 'created >=' => '2013-01-01'
|
||||
* ],
|
||||
* 'limit' => 10
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* Is equivalent to:
|
||||
*
|
||||
* ```
|
||||
* $query
|
||||
* ->select(['id', 'name'])
|
||||
* ->where(['created >=' => '2013-01-01'])
|
||||
* ->limit(10)
|
||||
* ```
|
||||
*
|
||||
* @param array<string, mixed> $options list of query clauses to apply new parts to.
|
||||
* @return $this
|
||||
*/
|
||||
public function applyOptions(array $options);
|
||||
|
||||
/**
|
||||
* Apply custom finds to against an existing query object.
|
||||
*
|
||||
* Allows custom find methods to be combined and applied to each other.
|
||||
*
|
||||
* ```
|
||||
* $repository->find('all')->find('recent');
|
||||
* ```
|
||||
*
|
||||
* The above is an example of stacking multiple finder methods onto
|
||||
* a single query.
|
||||
*
|
||||
* @param string $finder The finder method to use.
|
||||
* @param mixed ...$args Arguments that match up to finder-specific parameters
|
||||
* @return static Returns a modified query.
|
||||
*/
|
||||
public function find(string $finder, mixed ...$args): static;
|
||||
|
||||
/**
|
||||
* Returns the first result out of executing this query, if the query has not been
|
||||
* executed before, it will set the limit clause to 1 for performance reasons.
|
||||
*
|
||||
* ### Example:
|
||||
*
|
||||
* ```
|
||||
* $singleUser = $query->select(['id', 'username'])->first();
|
||||
* ```
|
||||
*
|
||||
* @return mixed the first result from the ResultSet
|
||||
*/
|
||||
public function first(): mixed;
|
||||
|
||||
/**
|
||||
* Returns the total amount of results for the query.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int;
|
||||
|
||||
/**
|
||||
* Sets the number of records that should be retrieved from database,
|
||||
* accepts an integer or an expression object that evaluates to an integer.
|
||||
* In some databases, this operation might not be supported or will require
|
||||
* the query to be transformed in order to limit the result set size.
|
||||
*
|
||||
* ### Examples
|
||||
*
|
||||
* ```
|
||||
* $query->limit(10) // generates LIMIT 10
|
||||
* $query->limit($query->expr()->add(['1 + 1'])); // LIMIT (1 + 1)
|
||||
* ```
|
||||
*
|
||||
* @param int|null $limit number of records to be returned
|
||||
* @return $this
|
||||
*/
|
||||
public function limit(?int $limit);
|
||||
|
||||
/**
|
||||
* Sets the number of records that should be skipped from the original result set
|
||||
* This is commonly used for paginating large results. Accepts an integer or an
|
||||
* expression object that evaluates to an integer.
|
||||
*
|
||||
* In some databases, this operation might not be supported or will require
|
||||
* the query to be transformed in order to limit the result set size.
|
||||
*
|
||||
* ### Examples
|
||||
*
|
||||
* ```
|
||||
* $query->offset(10) // generates OFFSET 10
|
||||
* $query->offset($query->expr()->add(['1 + 1'])); // OFFSET (1 + 1)
|
||||
* ```
|
||||
*
|
||||
* @param int|null $offset number of records to be skipped
|
||||
* @return $this
|
||||
*/
|
||||
public function offset(?int $offset);
|
||||
|
||||
/**
|
||||
* Adds a single or multiple fields to be used in the ORDER clause for this query.
|
||||
* Fields can be passed as an array of strings, array of expression
|
||||
* objects, a single expression or a single string.
|
||||
*
|
||||
* If an array is passed, keys will be used as the field itself and the value will
|
||||
* represent the order in which such field should be ordered. When called multiple
|
||||
* times with the same fields as key, the last order definition will prevail over
|
||||
* the others.
|
||||
*
|
||||
* By default this function will append any passed argument to the list of fields
|
||||
* to be selected, unless the second argument is set to true.
|
||||
*
|
||||
* ### Examples:
|
||||
*
|
||||
* ```
|
||||
* $query->orderBy(['title' => 'DESC', 'author_id' => 'ASC']);
|
||||
* ```
|
||||
*
|
||||
* Produces:
|
||||
*
|
||||
* `ORDER BY title DESC, author_id ASC`
|
||||
*
|
||||
* ```
|
||||
* $query
|
||||
* ->orderBy(['title' => $query->expr('DESC NULLS FIRST')])
|
||||
* ->orderBy('author_id');
|
||||
* ```
|
||||
*
|
||||
* Will generate:
|
||||
*
|
||||
* `ORDER BY title DESC NULLS FIRST, author_id`
|
||||
*
|
||||
* ```
|
||||
* $expression = $query->expr()->add(['id % 2 = 0']);
|
||||
* $query->orderBy($expression)->orderBy(['title' => 'ASC']);
|
||||
* ```
|
||||
*
|
||||
* Will become:
|
||||
*
|
||||
* `ORDER BY (id %2 = 0), title ASC`
|
||||
*
|
||||
* If you need to set complex expressions as order conditions, you
|
||||
* should use `orderByAsc()` or `orderByDesc()`.
|
||||
*
|
||||
* @param \Closure|array|string $fields fields to be added to the list
|
||||
* @param bool $overwrite whether to reset order with field list or not
|
||||
* @return $this
|
||||
* @deprecated 5.0.0 Use orderBy() instead now that CollectionInterface methods are no longer proxied.
|
||||
*/
|
||||
public function order(Closure|array|string $fields, bool $overwrite = false);
|
||||
|
||||
/**
|
||||
* Adds a single or multiple fields to be used in the ORDER clause for this query.
|
||||
* Fields can be passed as an array of strings, array of expression
|
||||
* objects, a single expression or a single string.
|
||||
*
|
||||
* If an array is passed, keys will be used as the field itself and the value will
|
||||
* represent the order in which such field should be ordered. When called multiple
|
||||
* times with the same fields as key, the last order definition will prevail over
|
||||
* the others.
|
||||
*
|
||||
* By default this function will append any passed argument to the list of fields
|
||||
* to be selected, unless the second argument is set to true.
|
||||
*
|
||||
* ### Examples:
|
||||
*
|
||||
* ```
|
||||
* $query->orderBy(['title' => 'DESC', 'author_id' => 'ASC']);
|
||||
* ```
|
||||
*
|
||||
* Produces:
|
||||
*
|
||||
* `ORDER BY title DESC, author_id ASC`
|
||||
*
|
||||
* ```
|
||||
* $query
|
||||
* ->orderBy(['title' => $query->expr('DESC NULLS FIRST')])
|
||||
* ->orderBy('author_id');
|
||||
* ```
|
||||
*
|
||||
* Will generate:
|
||||
*
|
||||
* `ORDER BY title DESC NULLS FIRST, author_id`
|
||||
*
|
||||
* ```
|
||||
* $expression = $query->expr()->add(['id % 2 = 0']);
|
||||
* $query->orderBy($expression)->orderBy(['title' => 'ASC']);
|
||||
* ```
|
||||
*
|
||||
* Will become:
|
||||
*
|
||||
* `ORDER BY (id %2 = 0), title ASC`
|
||||
*
|
||||
* If you need to set complex expressions as order conditions, you
|
||||
* should use `orderByAsc()` or `orderByDesc()`.
|
||||
*
|
||||
* @param \Closure|array|string $fields fields to be added to the list
|
||||
* @param bool $overwrite whether to reset order with field list or not
|
||||
* @return $this
|
||||
*/
|
||||
public function orderBy(Closure|array|string $fields, bool $overwrite = false);
|
||||
|
||||
/**
|
||||
* Set the page of results you want.
|
||||
*
|
||||
* This method provides an easier to use interface to set the limit + offset
|
||||
* in the record set you want as results. If empty the limit will default to
|
||||
* the existing limit clause, and if that too is empty, then `25` will be used.
|
||||
*
|
||||
* Pages must start at 1.
|
||||
*
|
||||
* @param int $num The page number you want.
|
||||
* @param int|null $limit The number of rows you want in the page. If null
|
||||
* the current limit clause will be used.
|
||||
* @return $this
|
||||
* @throws \InvalidArgumentException If page number < 1.
|
||||
*/
|
||||
public function page(int $num, ?int $limit = null);
|
||||
|
||||
/**
|
||||
* Returns an array representation of the results after executing the query.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array;
|
||||
|
||||
/**
|
||||
* Set the default Table object that will be used by this query
|
||||
* and form the `FROM` clause.
|
||||
*
|
||||
* @param \Cake\Datasource\RepositoryInterface $repository The default repository object to use
|
||||
* @return $this
|
||||
*/
|
||||
public function setRepository(RepositoryInterface $repository);
|
||||
|
||||
/**
|
||||
* Returns the default repository object that will be used by this query,
|
||||
* that is, the repository that will appear in the "from" clause.
|
||||
*
|
||||
* @return \Cake\Datasource\RepositoryInterface|null $repository The default repository object to use
|
||||
*/
|
||||
public function getRepository(): ?RepositoryInterface;
|
||||
|
||||
/**
|
||||
* Adds a condition or set of conditions to be used in the WHERE clause for this
|
||||
* query. Conditions can be expressed as an array of fields as keys with
|
||||
* comparison operators in it, the values for the array will be used for comparing
|
||||
* the field to such literal. Finally, conditions can be expressed as a single
|
||||
* string or an array of strings.
|
||||
*
|
||||
* When using arrays, each entry will be joined to the rest of the conditions using
|
||||
* an AND operator. Consecutive calls to this function will also join the new
|
||||
* conditions specified using the AND operator. Additionally, values can be
|
||||
* expressed using expression objects which can include other query objects.
|
||||
*
|
||||
* Any conditions created with this method can be used with any SELECT, UPDATE
|
||||
* and DELETE type of queries.
|
||||
*
|
||||
* ### Conditions using operators:
|
||||
*
|
||||
* ```
|
||||
* $query->where([
|
||||
* 'posted >=' => new DateTime('3 days ago'),
|
||||
* 'title LIKE' => 'Hello W%',
|
||||
* 'author_id' => 1,
|
||||
* ], ['posted' => 'datetime']);
|
||||
* ```
|
||||
*
|
||||
* The previous example produces:
|
||||
*
|
||||
* `WHERE posted >= 2012-01-27 AND title LIKE 'Hello W%' AND author_id = 1`
|
||||
*
|
||||
* Second parameter is used to specify what type is expected for each passed
|
||||
* key. Valid types can be used from the mapped with Database\Type class.
|
||||
*
|
||||
* ### Nesting conditions with conjunctions:
|
||||
*
|
||||
* ```
|
||||
* $query->where([
|
||||
* 'author_id !=' => 1,
|
||||
* 'OR' => ['published' => true, 'posted <' => new DateTime('now')],
|
||||
* 'NOT' => ['title' => 'Hello']
|
||||
* ], ['published' => boolean, 'posted' => 'datetime']
|
||||
* ```
|
||||
*
|
||||
* The previous example produces:
|
||||
*
|
||||
* `WHERE author_id = 1 AND (published = 1 OR posted < '2012-02-01') AND NOT (title = 'Hello')`
|
||||
*
|
||||
* You can nest conditions using conjunctions as much as you like. Sometimes, you
|
||||
* may want to define 2 different options for the same key, in that case, you can
|
||||
* wrap each condition inside a new array:
|
||||
*
|
||||
* `$query->where(['OR' => [['published' => false], ['published' => true]])`
|
||||
*
|
||||
* Keep in mind that every time you call where() with the third param set to false
|
||||
* (default), it will join the passed conditions to the previous stored list using
|
||||
* the AND operator. Also, using the same array key twice in consecutive calls to
|
||||
* this method will not override the previous value.
|
||||
*
|
||||
* ### Using expressions objects:
|
||||
*
|
||||
* ```
|
||||
* $exp = $query->expr()->add(['id !=' => 100, 'author_id' != 1])->tieWith('OR');
|
||||
* $query->where(['published' => true], ['published' => 'boolean'])->where($exp);
|
||||
* ```
|
||||
*
|
||||
* The previous example produces:
|
||||
*
|
||||
* `WHERE (id != 100 OR author_id != 1) AND published = 1`
|
||||
*
|
||||
* Other Query objects that be used as conditions for any field.
|
||||
*
|
||||
* ### Adding conditions in multiple steps:
|
||||
*
|
||||
* You can use callback to construct complex expressions, functions
|
||||
* receive as first argument a new QueryExpression object and this query instance
|
||||
* as second argument. Functions must return an expression object that will be
|
||||
* added to the list of conditions for the query using the AND operator.
|
||||
*
|
||||
* ```
|
||||
* $query
|
||||
* ->where(['title !=' => 'Hello World'])
|
||||
* ->where(function ($exp, $query) {
|
||||
* $or = $exp->or(['id' => 1]);
|
||||
* $and = $exp->and(['id >' => 2, 'id <' => 10]);
|
||||
* return $or->add($and);
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* * The previous example produces:
|
||||
*
|
||||
* `WHERE title != 'Hello World' AND (id = 1 OR (id > 2 AND id < 10))`
|
||||
*
|
||||
* ### Conditions as strings:
|
||||
*
|
||||
* ```
|
||||
* $query->where(['articles.author_id = authors.id', 'modified IS NULL']);
|
||||
* ```
|
||||
*
|
||||
* The previous example produces:
|
||||
*
|
||||
* `WHERE articles.author_id = authors.id AND modified IS NULL`
|
||||
*
|
||||
* Please note that when using the array notation or the expression objects, all
|
||||
* values will be correctly quoted and transformed to the correspondent database
|
||||
* data type automatically for you, thus securing your application from SQL injections.
|
||||
* If you use string conditions, make sure that your values are correctly quoted.
|
||||
* The safest thing you can do is to never use string conditions.
|
||||
*
|
||||
* @param \Closure|array|string|null $conditions The conditions to filter on.
|
||||
* @param array<string, string> $types Associative array of type names used to bind values to query
|
||||
* @param bool $overwrite whether to reset conditions with passed list or not
|
||||
* @return $this
|
||||
*/
|
||||
public function where(Closure|array|string|null $conditions = null, array $types = [], bool $overwrite = false);
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
[](https://packagist.org/packages/cakephp/datasource)
|
||||
[](LICENSE.txt)
|
||||
|
||||
# CakePHP Datasource Library
|
||||
|
||||
This library contains interfaces for implementing Repositories and Entities using any data source,
|
||||
a class for managing connections to datasources and traits to help you quickly implement the
|
||||
interfaces provided by this package.
|
||||
|
||||
## Repositories
|
||||
|
||||
A repository is a class capable of interfacing with a data source using operations such as
|
||||
`find`, `save` and `delete` by using intermediate query objects for expressing commands to
|
||||
the data store and returning Entities as the single result unit of such a system.
|
||||
|
||||
In the case of a Relational database, a Repository would be a `Table`, which can be return single
|
||||
or multiple `Entity` objects by using a `Query`.
|
||||
|
||||
This library exposes the following interfaces for creating a system that implements the
|
||||
repository pattern and is compatible with the CakePHP framework:
|
||||
|
||||
* `RepositoryInterface` - Describes the methods for a base repository class.
|
||||
* `EntityInterface` - Describes the methods for a single result object.
|
||||
* `ResultSetInterface` - Represents the idea of a collection of Entities as a result of a query.
|
||||
|
||||
Additionally, this package provides a few traits and classes you can use in your own implementations:
|
||||
|
||||
* `EntityTrait` - Contains the default implementation for the `EntityInterface`.
|
||||
* `QueryTrait` - Exposes the methods for creating a query object capable of returning decoratable collections.
|
||||
* `ResultSetDecorator` - Decorates any traversable object, so it complies with `ResultSetInterface`.
|
||||
|
||||
|
||||
## Connections
|
||||
|
||||
This library contains a couple of utility classes meant to create and manage
|
||||
connection objects. Connections are typically used in repositories for
|
||||
interfacing with the actual data source system.
|
||||
|
||||
The `ConnectionManager` class acts as a registry to access database connections
|
||||
your application has. It provides a place that other objects can get references
|
||||
to existing connections. Creating connections with the `ConnectionManager` is
|
||||
easy:
|
||||
|
||||
```php
|
||||
use Cake\Datasource\ConnectionManager;
|
||||
|
||||
ConnectionManager::config('connection-one', [
|
||||
'className' => 'MyApp\Connections\CustomConnection',
|
||||
'param1' => 'value',
|
||||
'param2' => 'another value'
|
||||
]);
|
||||
|
||||
ConnectionManager::config('connection-two', [
|
||||
'className' => 'MyApp\Connections\CustomConnection',
|
||||
'param1' => 'different value',
|
||||
'param2' => 'another value'
|
||||
]);
|
||||
```
|
||||
|
||||
When requested, the `ConnectionManager` will instantiate
|
||||
`MyApp\Connections\CustomConnection` by passing `param1` and `param2` inside an
|
||||
array as the first argument of the constructor.
|
||||
|
||||
Once configured connections can be fetched using `ConnectionManager::get()`.
|
||||
This method will construct and load a connection if it has not been built
|
||||
before, or return the existing known connection:
|
||||
|
||||
```php
|
||||
use Cake\Datasource\ConnectionManager;
|
||||
$conn = ConnectionManager::get('master');
|
||||
```
|
||||
|
||||
It is also possible to store connection objects by passing the instance directly to the manager:
|
||||
|
||||
```php
|
||||
use Cake\Datasource\ConnectionManager;
|
||||
$conn = ConnectionManager::config('other', $connectionInstance);
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Please make sure you check the [official API documentation](https://api.cakephp.org/4.x/namespace-Cake.Datasource.html)
|
||||
@@ -0,0 +1,275 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource;
|
||||
|
||||
use Closure;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
|
||||
/**
|
||||
* Describes the methods that any class representing a data storage should
|
||||
* comply with.
|
||||
*/
|
||||
interface RepositoryInterface
|
||||
{
|
||||
/**
|
||||
* Sets the repository alias.
|
||||
*
|
||||
* @param string $alias Table alias
|
||||
* @return $this
|
||||
*/
|
||||
public function setAlias(string $alias);
|
||||
|
||||
/**
|
||||
* Returns the repository alias.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAlias(): string;
|
||||
|
||||
/**
|
||||
* Alias a field with the repository's current alias.
|
||||
*
|
||||
* If field is already aliased, it will result in no-op.
|
||||
*
|
||||
* @param string $field The field to alias.
|
||||
* @return string The field prefixed with the repository alias.
|
||||
*/
|
||||
public function aliasField(string $field): string;
|
||||
|
||||
/**
|
||||
* Sets the table registry key used to create this table instance.
|
||||
*
|
||||
* @param string $registryAlias The key used to access this object.
|
||||
* @return $this
|
||||
*/
|
||||
public function setRegistryAlias(string $registryAlias);
|
||||
|
||||
/**
|
||||
* Returns the table registry key used to create this table instance.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRegistryAlias(): string;
|
||||
|
||||
/**
|
||||
* Test to see if a Repository has a specific field/column.
|
||||
*
|
||||
* @param string $field The field to check for.
|
||||
* @return bool True if the field exists, false if it does not.
|
||||
*/
|
||||
public function hasField(string $field): bool;
|
||||
|
||||
/**
|
||||
* Creates a new Query for this repository and applies some defaults based on the
|
||||
* type of search that was selected.
|
||||
*
|
||||
* @param string $type the type of query to perform
|
||||
* @param mixed ...$args Arguments that match up to finder-specific parameters
|
||||
* @return \Cake\Datasource\QueryInterface
|
||||
*/
|
||||
public function find(string $type = 'all', mixed ...$args): QueryInterface;
|
||||
|
||||
/**
|
||||
* Returns a single record after finding it by its primary key, if no record is
|
||||
* found this method throws an exception.
|
||||
*
|
||||
* ### Example:
|
||||
*
|
||||
* ```
|
||||
* $id = 10;
|
||||
* $article = $articles->get($id);
|
||||
*
|
||||
* $article = $articles->get($id, ['contain' => ['Comments']]);
|
||||
* ```
|
||||
*
|
||||
* @param mixed $primaryKey primary key value to find
|
||||
* @param array|string $finder The finder to use. Passing an options array is deprecated.
|
||||
* @param \Psr\SimpleCache\CacheInterface|string|null $cache The cache config to use.
|
||||
* Defaults to `null`, i.e. no caching.
|
||||
* @param \Closure|string|null $cacheKey The cache key to use. If not provided
|
||||
* one will be autogenerated if `$cache` is not null.
|
||||
* @throws \Cake\Datasource\Exception\RecordNotFoundException if the record with such id
|
||||
* could not be found
|
||||
* @return \Cake\Datasource\EntityInterface
|
||||
* @see \Cake\Datasource\RepositoryInterface::find()
|
||||
*/
|
||||
public function get(
|
||||
mixed $primaryKey,
|
||||
array|string $finder = 'all',
|
||||
CacheInterface|string|null $cache = null,
|
||||
Closure|string|null $cacheKey = null,
|
||||
mixed ...$args,
|
||||
): EntityInterface;
|
||||
|
||||
/**
|
||||
* Creates a new Query instance for this repository
|
||||
*
|
||||
* @return \Cake\Datasource\QueryInterface
|
||||
*/
|
||||
public function query(): QueryInterface;
|
||||
|
||||
/**
|
||||
* Update all matching records.
|
||||
*
|
||||
* Sets the $fields to the provided values based on $conditions.
|
||||
* This method will *not* trigger beforeSave/afterSave events. If you need those,
|
||||
* first load a collection of records and update them.
|
||||
*
|
||||
* @param \Closure|array|string $fields A hash of field => new value.
|
||||
* @param \Closure|array|string|null $conditions Conditions to be used, accepts anything Query::where()
|
||||
* can take.
|
||||
* @return int Count Returns the affected rows.
|
||||
*/
|
||||
public function updateAll(Closure|array|string $fields, Closure|array|string|null $conditions): int;
|
||||
|
||||
/**
|
||||
* Deletes all records matching the provided conditions.
|
||||
*
|
||||
* This method will *not* trigger beforeDelete/afterDelete events. If you
|
||||
* need those, first load a collection of records and delete them.
|
||||
*
|
||||
* This method will *not* execute on associations' `cascade` attribute. You should
|
||||
* use database foreign keys + ON CASCADE rules if you need cascading deletes combined
|
||||
* with this method.
|
||||
*
|
||||
* @param \Closure|array|string|null $conditions Conditions to be used, accepts anything Query::where()
|
||||
* can take.
|
||||
* @return int Returns the number of affected rows.
|
||||
* @see \Cake\Datasource\RepositoryInterface::delete()
|
||||
*/
|
||||
public function deleteAll(Closure|array|string|null $conditions): int;
|
||||
|
||||
/**
|
||||
* Returns true if there is any record in this repository matching the specified
|
||||
* conditions.
|
||||
*
|
||||
* @param \Closure|array|string|null $conditions list of conditions to pass to the query
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(Closure|array|string|null $conditions): bool;
|
||||
|
||||
/**
|
||||
* Persists an entity based on the fields that are marked as dirty and
|
||||
* returns the same entity after a successful save or false in case
|
||||
* of any error.
|
||||
*
|
||||
* @param \Cake\Datasource\EntityInterface $entity the entity to be saved
|
||||
* @param array<string, mixed> $options The options to use when saving.
|
||||
* @return \Cake\Datasource\EntityInterface|false
|
||||
*/
|
||||
public function save(EntityInterface $entity, array $options = []): EntityInterface|false;
|
||||
|
||||
/**
|
||||
* Delete a single entity.
|
||||
*
|
||||
* Deletes an entity and possibly related associations from the database
|
||||
* based on the 'dependent' option used when defining the association.
|
||||
*
|
||||
* @param \Cake\Datasource\EntityInterface $entity The entity to remove.
|
||||
* @param array<string, mixed> $options The options for the delete.
|
||||
* @return bool success
|
||||
*/
|
||||
public function delete(EntityInterface $entity, array $options = []): bool;
|
||||
|
||||
/**
|
||||
* This creates a new entity object.
|
||||
*
|
||||
* Careful: This does not trigger any field validation.
|
||||
* This entity can be persisted without validation error as empty record.
|
||||
* Always patch in required fields before saving.
|
||||
*
|
||||
* @return \Cake\Datasource\EntityInterface
|
||||
*/
|
||||
public function newEmptyEntity(): EntityInterface;
|
||||
|
||||
/**
|
||||
* Create a new entity + associated entities from an array.
|
||||
*
|
||||
* This is most useful when hydrating request data back into entities.
|
||||
* For example, in your controller code:
|
||||
*
|
||||
* ```
|
||||
* $article = $this->Articles->newEntity($this->request->getData());
|
||||
* ```
|
||||
*
|
||||
* The hydrated entity will correctly do an insert/update based
|
||||
* on the primary key data existing in the database when the entity
|
||||
* is saved. Until the entity is saved, it will be a detached record.
|
||||
*
|
||||
* @param array $data The data to build an entity with.
|
||||
* @param array<string, mixed> $options A list of options for the object hydration.
|
||||
* @return \Cake\Datasource\EntityInterface
|
||||
*/
|
||||
public function newEntity(array $data, array $options = []): EntityInterface;
|
||||
|
||||
/**
|
||||
* Create a list of entities + associated entities from an array.
|
||||
*
|
||||
* This is most useful when hydrating request data back into entities.
|
||||
* For example, in your controller code:
|
||||
*
|
||||
* ```
|
||||
* $articles = $this->Articles->newEntities($this->request->getData());
|
||||
* ```
|
||||
*
|
||||
* The hydrated entities can then be iterated and saved.
|
||||
*
|
||||
* @param array $data The data to build an entity with.
|
||||
* @param array<string, mixed> $options A list of options for the objects hydration.
|
||||
* @return array<\Cake\Datasource\EntityInterface> An array of hydrated records.
|
||||
*/
|
||||
public function newEntities(array $data, array $options = []): array;
|
||||
|
||||
/**
|
||||
* Merges the passed `$data` into `$entity` respecting the accessible
|
||||
* fields configured on the entity. Returns the same entity after being
|
||||
* altered.
|
||||
*
|
||||
* This is most useful when editing an existing entity using request data:
|
||||
*
|
||||
* ```
|
||||
* $article = $this->Articles->patchEntity($article, $this->request->getData());
|
||||
* ```
|
||||
*
|
||||
* @param \Cake\Datasource\EntityInterface $entity the entity that will get the
|
||||
* data merged in
|
||||
* @param array $data key value list of fields to be merged into the entity
|
||||
* @param array<string, mixed> $options A list of options for the object hydration.
|
||||
* @return \Cake\Datasource\EntityInterface
|
||||
*/
|
||||
public function patchEntity(EntityInterface $entity, array $data, array $options = []): EntityInterface;
|
||||
|
||||
/**
|
||||
* Merges each of the elements passed in `$data` into the entities
|
||||
* found in `$entities` respecting the accessible fields configured on the entities.
|
||||
* Merging is done by matching the primary key in each of the elements in `$data`
|
||||
* and `$entities`.
|
||||
*
|
||||
* This is most useful when editing a list of existing entities using request data:
|
||||
*
|
||||
* ```
|
||||
* $article = $this->Articles->patchEntities($articles, $this->request->getData());
|
||||
* ```
|
||||
*
|
||||
* @param iterable<\Cake\Datasource\EntityInterface> $entities the entities that will get the
|
||||
* data merged in
|
||||
* @param array $data list of arrays to be merged into the entities
|
||||
* @param array<string, mixed> $options A list of options for the objects hydration.
|
||||
* @return array<\Cake\Datasource\EntityInterface>
|
||||
*/
|
||||
public function patchEntities(iterable $entities, array $data, array $options = []): array;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource;
|
||||
|
||||
use Cake\Collection\Collection;
|
||||
use Cake\Core\Configure;
|
||||
|
||||
/**
|
||||
* Generic ResultSet decorator. This will make any traversable object appear to
|
||||
* be a database result
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @extends \Cake\Collection\Collection<TKey, TValue>
|
||||
* @implements \Cake\Datasource\ResultSetInterface<TKey, TValue>
|
||||
*/
|
||||
class ResultSetDecorator extends Collection implements ResultSetInterface
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
$parentInfo = parent::__debugInfo();
|
||||
$limit = Configure::read('App.ResultSetDebugLimit', 10);
|
||||
|
||||
return array_merge($parentInfo, ['items' => $this->take($limit)->toArray()]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource;
|
||||
|
||||
use Cake\Collection\CollectionInterface;
|
||||
|
||||
/**
|
||||
* Describes how a collection of datasource results should look like
|
||||
*
|
||||
* @template TKey
|
||||
* @template-covariant TValue
|
||||
* @extends \Cake\Collection\CollectionInterface<TKey, TValue>
|
||||
*/
|
||||
interface ResultSetInterface extends CollectionInterface
|
||||
{
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.2.12
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource;
|
||||
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* Contains logic for invoking an application rule.
|
||||
*
|
||||
* Combined with {@link \Cake\Datasource\RulesChecker} as an implementation
|
||||
* detail to de-duplicate rule decoration and provide cleaner separation
|
||||
* of duties.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class RuleInvoker
|
||||
{
|
||||
/**
|
||||
* The rule name
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected ?string $name = null;
|
||||
|
||||
/**
|
||||
* Rule options
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected array $options = [];
|
||||
|
||||
/**
|
||||
* Rule callable
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
protected $rule;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* ### Options
|
||||
*
|
||||
* - `errorField` The field errors should be set onto.
|
||||
* - `message` The error message.
|
||||
*
|
||||
* Individual rules may have additional options that can be
|
||||
* set here. Any options will be passed into the rule as part of the
|
||||
* rule $scope.
|
||||
*
|
||||
* @param callable $rule The rule to be invoked.
|
||||
* @param string|null $name The name of the rule. Used in error messages.
|
||||
* @param array<string, mixed> $options The options for the rule. See above.
|
||||
*/
|
||||
public function __construct(callable $rule, ?string $name, array $options = [])
|
||||
{
|
||||
$this->rule = $rule;
|
||||
$this->name = $name;
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set options for the rule invocation.
|
||||
*
|
||||
* Old options will be merged with the new ones.
|
||||
*
|
||||
* @param array<string, mixed> $options The options to set.
|
||||
* @return $this
|
||||
*/
|
||||
public function setOptions(array $options)
|
||||
{
|
||||
$this->options = $options + $this->options;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the rule name.
|
||||
*
|
||||
* Only truthy names will be set.
|
||||
*
|
||||
* @param string|null $name The name to set.
|
||||
* @return $this
|
||||
*/
|
||||
public function setName(?string $name)
|
||||
{
|
||||
if ($name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the rule.
|
||||
*
|
||||
* @param \Cake\Datasource\EntityInterface $entity The entity the rule
|
||||
* should apply to.
|
||||
* @param array $scope The rule's scope/options.
|
||||
* @return bool Whether the rule passed.
|
||||
*/
|
||||
public function __invoke(EntityInterface $entity, array $scope): bool
|
||||
{
|
||||
$rule = $this->rule;
|
||||
$pass = $rule($entity, $this->options + $scope);
|
||||
if ($pass === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$message = $this->options['message'] ?? 'invalid';
|
||||
if (is_string($pass)) {
|
||||
$message = $pass;
|
||||
}
|
||||
if ($message instanceof Closure) {
|
||||
$message = $message($entity, $this->options + $scope);
|
||||
}
|
||||
if ($this->name) {
|
||||
$message = [$this->name => $message];
|
||||
} else {
|
||||
$message = [$message];
|
||||
}
|
||||
|
||||
$errorField = $this->options['errorField'] ?? ($this->name ?? '_rule');
|
||||
$entity->setError($errorField, $message);
|
||||
|
||||
if ($entity instanceof InvalidPropertyInterface && isset($entity->{$errorField})) {
|
||||
$invalidValue = $entity->{$errorField};
|
||||
$entity->setInvalidField($errorField, $invalidValue);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.0.7
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource;
|
||||
|
||||
use ArrayObject;
|
||||
use Cake\Event\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* A trait that allows a class to build and apply application.
|
||||
* rules.
|
||||
*
|
||||
* If the implementing class also implements EventAwareTrait, then
|
||||
* events will be emitted when rules are checked.
|
||||
*
|
||||
* The implementing class is expected to define the `RULES_CLASS` constant
|
||||
* if they need to customize which class is used for rules objects.
|
||||
*/
|
||||
trait RulesAwareTrait
|
||||
{
|
||||
/**
|
||||
* The domain rules to be applied to entities saved by this table
|
||||
*
|
||||
* @var \Cake\Datasource\RulesChecker|null
|
||||
*/
|
||||
protected ?RulesChecker $_rulesChecker = null;
|
||||
|
||||
/**
|
||||
* Returns whether the passed entity complies with all the rules stored in
|
||||
* the rules checker.
|
||||
*
|
||||
* @param \Cake\Datasource\EntityInterface $entity The entity to check for validity.
|
||||
* @param string $operation The operation being run. Either 'create', 'update' or 'delete'.
|
||||
* @param \ArrayObject<string, mixed>|array|null $options The options To be passed to the rules.
|
||||
* @return bool
|
||||
*/
|
||||
public function checkRules(
|
||||
EntityInterface $entity,
|
||||
string $operation = RulesChecker::CREATE,
|
||||
ArrayObject|array|null $options = null,
|
||||
): bool {
|
||||
$rules = $this->rulesChecker();
|
||||
$options = $options ?: new ArrayObject();
|
||||
$options = is_array($options) ? new ArrayObject($options) : $options;
|
||||
$hasEvents = ($this instanceof EventDispatcherInterface);
|
||||
|
||||
if ($hasEvents) {
|
||||
$event = $this->dispatchEvent(
|
||||
'Model.beforeRules',
|
||||
compact('entity', 'options', 'operation'),
|
||||
);
|
||||
if ($event->isStopped()) {
|
||||
return $event->getResult();
|
||||
}
|
||||
}
|
||||
|
||||
$result = $rules->check($entity, $operation, $options->getArrayCopy());
|
||||
|
||||
if ($hasEvents) {
|
||||
$event = $this->dispatchEvent(
|
||||
'Model.afterRules',
|
||||
compact('entity', 'options', 'result', 'operation'),
|
||||
);
|
||||
|
||||
if ($event->isStopped()) {
|
||||
return $event->getResult();
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RulesChecker for this instance.
|
||||
*
|
||||
* A RulesChecker object is used to test an entity for validity
|
||||
* on rules that may involve complex logic or data that
|
||||
* needs to be fetched from relevant datasources.
|
||||
*
|
||||
* @see \Cake\Datasource\RulesChecker
|
||||
* @return \Cake\Datasource\RulesChecker
|
||||
*/
|
||||
public function rulesChecker(): RulesChecker
|
||||
{
|
||||
if ($this->_rulesChecker !== null) {
|
||||
return $this->_rulesChecker;
|
||||
}
|
||||
/** @var class-string<\Cake\Datasource\RulesChecker> $class */
|
||||
$class = defined('static::RULES_CLASS') ? static::RULES_CLASS : RulesChecker::class;
|
||||
/**
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
$this->_rulesChecker = $this->buildRules(new $class(['repository' => $this]));
|
||||
$this->dispatchEvent('Model.buildRules', ['rules' => $this->_rulesChecker]);
|
||||
|
||||
return $this->_rulesChecker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a RulesChecker object after modifying the one that was supplied.
|
||||
*
|
||||
* Subclasses should override this method in order to initialize the rules to be applied to
|
||||
* entities saved by this instance.
|
||||
*
|
||||
* @param \Cake\Datasource\RulesChecker $rules The rules object to be modified.
|
||||
* @return \Cake\Datasource\RulesChecker
|
||||
*/
|
||||
public function buildRules(RulesChecker $rules): RulesChecker
|
||||
{
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
+427
@@ -0,0 +1,427 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.0.7
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource;
|
||||
|
||||
use Cake\Core\Exception\CakeException;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Contains logic for storing and checking rules on entities
|
||||
*
|
||||
* RulesCheckers are used by Table classes to ensure that the
|
||||
* current entity state satisfies the application logic and business rules.
|
||||
*
|
||||
* RulesCheckers afford different rules to be applied in the create and update
|
||||
* scenario.
|
||||
*
|
||||
* ### Adding rules
|
||||
*
|
||||
* Rules must be callable objects that return true/false depending on whether
|
||||
* the rule has been satisfied. You can use RulesChecker::add(), RulesChecker::addCreate(),
|
||||
* RulesChecker::addUpdate() and RulesChecker::addDelete to add rules to a checker.
|
||||
*
|
||||
* ### Running checks
|
||||
*
|
||||
* Generally a Table object will invoke the rules objects, but you can manually
|
||||
* invoke the checks by calling RulesChecker::checkCreate(), RulesChecker::checkUpdate() or
|
||||
* RulesChecker::checkDelete().
|
||||
*/
|
||||
class RulesChecker
|
||||
{
|
||||
/**
|
||||
* Indicates that the checking rules to apply are those used for creating entities
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const CREATE = 'create';
|
||||
|
||||
/**
|
||||
* Indicates that the checking rules to apply are those used for updating entities
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const UPDATE = 'update';
|
||||
|
||||
/**
|
||||
* Indicates that the checking rules to apply are those used for deleting entities
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const DELETE = 'delete';
|
||||
|
||||
/**
|
||||
* The list of rules to be checked on both create and update operations
|
||||
*
|
||||
* @var array<\Cake\Datasource\RuleInvoker>
|
||||
*/
|
||||
protected array $_rules = [];
|
||||
|
||||
/**
|
||||
* The list of rules to check during create operations
|
||||
*
|
||||
* @var array<\Cake\Datasource\RuleInvoker>
|
||||
*/
|
||||
protected array $_createRules = [];
|
||||
|
||||
/**
|
||||
* The list of rules to check during update operations
|
||||
*
|
||||
* @var array<\Cake\Datasource\RuleInvoker>
|
||||
*/
|
||||
protected array $_updateRules = [];
|
||||
|
||||
/**
|
||||
* The list of rules to check during delete operations
|
||||
*
|
||||
* @var array<\Cake\Datasource\RuleInvoker>
|
||||
*/
|
||||
protected array $_deleteRules = [];
|
||||
|
||||
/**
|
||||
* List of options to pass to every callable rule
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $_options = [];
|
||||
|
||||
/**
|
||||
* Whether to use I18n functions for translating default error messages
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $_useI18n = false;
|
||||
|
||||
/**
|
||||
* Constructor. Takes the options to be passed to all rules.
|
||||
*
|
||||
* @param array<string, mixed> $options The options to pass to every rule
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->_options = $options;
|
||||
$this->_useI18n = function_exists('\Cake\I18n\__d');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a rule that will be applied to the entity on create, update and delete
|
||||
* operations.
|
||||
*
|
||||
* ### Options
|
||||
*
|
||||
* The options array accept the following special keys:
|
||||
*
|
||||
* - `errorField`: The name of the entity field that will be marked as invalid
|
||||
* if the rule does not pass.
|
||||
* - `message`: The error message to set to `errorField` if the rule does not pass.
|
||||
*
|
||||
* @param callable $rule A callable function or object that will return whether
|
||||
* the entity is valid or not.
|
||||
* @param array|string|null $name The alias for a rule, or an array of options.
|
||||
* @param array<string, mixed> $options List of extra options to pass to the rule callable as
|
||||
* second argument.
|
||||
* @return $this
|
||||
* @throws \Cake\Core\Exception\CakeException If a rule with the same name already exists
|
||||
*/
|
||||
public function add(callable $rule, array|string|null $name = null, array $options = [])
|
||||
{
|
||||
if (is_string($name)) {
|
||||
$this->checkName($name, $this->_rules);
|
||||
$this->_rules[$name] = $this->_addError($rule, $name, $options);
|
||||
} else {
|
||||
$this->_rules[] = $this->_addError($rule, $name, $options);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a rule from the set.
|
||||
*
|
||||
* @param string $name The name of the rule to remove.
|
||||
* @return $this
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function remove(string $name)
|
||||
{
|
||||
unset($this->_rules[$name]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a rule that will be applied to the entity on create operations.
|
||||
*
|
||||
* ### Options
|
||||
*
|
||||
* The options array accept the following special keys:
|
||||
*
|
||||
* - `errorField`: The name of the entity field that will be marked as invalid
|
||||
* if the rule does not pass.
|
||||
* - `message`: The error message to set to `errorField` if the rule does not pass.
|
||||
*
|
||||
* @param callable $rule A callable function or object that will return whether
|
||||
* the entity is valid or not.
|
||||
* @param array|string|null $name The alias for a rule or an array of options.
|
||||
* @param array<string, mixed> $options List of extra options to pass to the rule callable as
|
||||
* second argument.
|
||||
* @return $this
|
||||
* @throws \Cake\Core\Exception\CakeException If a rule with the same name already exists
|
||||
*/
|
||||
public function addCreate(callable $rule, array|string|null $name = null, array $options = [])
|
||||
{
|
||||
if (is_string($name)) {
|
||||
$this->checkName($name, $this->_createRules);
|
||||
$this->_createRules[$name] = $this->_addError($rule, $name, $options);
|
||||
} else {
|
||||
$this->_createRules[] = $this->_addError($rule, $name, $options);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a rule from the create set.
|
||||
*
|
||||
* @param string $name The name of the rule to remove.
|
||||
* @return $this
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function removeCreate(string $name)
|
||||
{
|
||||
unset($this->_createRules[$name]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a rule that will be applied to the entity on update operations.
|
||||
*
|
||||
* ### Options
|
||||
*
|
||||
* The options array accept the following special keys:
|
||||
*
|
||||
* - `errorField`: The name of the entity field that will be marked as invalid
|
||||
* if the rule does not pass.
|
||||
* - `message`: The error message to set to `errorField` if the rule does not pass.
|
||||
*
|
||||
* @param callable $rule A callable function or object that will return whether
|
||||
* the entity is valid or not.
|
||||
* @param array|string|null $name The alias for a rule, or an array of options.
|
||||
* @param array<string, mixed> $options List of extra options to pass to the rule callable as
|
||||
* second argument.
|
||||
* @return $this
|
||||
* @throws \Cake\Core\Exception\CakeException If a rule with the same name already exists
|
||||
*/
|
||||
public function addUpdate(callable $rule, array|string|null $name = null, array $options = [])
|
||||
{
|
||||
if (is_string($name)) {
|
||||
$this->checkName($name, $this->_updateRules);
|
||||
$this->_updateRules[$name] = $this->_addError($rule, $name, $options);
|
||||
} else {
|
||||
$this->_updateRules[] = $this->_addError($rule, $name, $options);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a rule from the update set.
|
||||
*
|
||||
* @param string $name The name of the rule to remove.
|
||||
* @return $this
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function removeUpdate(string $name)
|
||||
{
|
||||
unset($this->_updateRules[$name]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a rule that will be applied to the entity on delete operations.
|
||||
*
|
||||
* ### Options
|
||||
*
|
||||
* The options array accept the following special keys:
|
||||
*
|
||||
* - `errorField`: The name of the entity field that will be marked as invalid
|
||||
* if the rule does not pass.
|
||||
* - `message`: The error message to set to `errorField` if the rule does not pass.
|
||||
*
|
||||
* @param callable $rule A callable function or object that will return whether
|
||||
* the entity is valid or not.
|
||||
* @param array|string|null $name The alias for a rule, or an array of options.
|
||||
* @param array<string, mixed> $options List of extra options to pass to the rule callable as
|
||||
* second argument.
|
||||
* @return $this
|
||||
* @throws \Cake\Core\Exception\CakeException If a rule with the same name already exists
|
||||
*/
|
||||
public function addDelete(callable $rule, array|string|null $name = null, array $options = [])
|
||||
{
|
||||
if (is_string($name)) {
|
||||
$this->checkName($name, $this->_deleteRules);
|
||||
$this->_deleteRules[$name] = $this->_addError($rule, $name, $options);
|
||||
} else {
|
||||
$this->_deleteRules[] = $this->_addError($rule, $name, $options);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a rule from the delete set.
|
||||
*
|
||||
* @param string $name The name of the rule to remove.
|
||||
* @return $this
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function removeDelete(string $name)
|
||||
{
|
||||
unset($this->_deleteRules[$name]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs each of the rules by passing the provided entity and returns true if all
|
||||
* of them pass. The rules to be applied are depended on the $mode parameter which
|
||||
* can only be RulesChecker::CREATE, RulesChecker::UPDATE or RulesChecker::DELETE
|
||||
*
|
||||
* @param \Cake\Datasource\EntityInterface $entity The entity to check for validity.
|
||||
* @param string $mode Either 'create, 'update' or 'delete'.
|
||||
* @param array<string, mixed> $options Extra options to pass to checker functions.
|
||||
* @return bool
|
||||
* @throws \InvalidArgumentException if an invalid mode is passed.
|
||||
*/
|
||||
public function check(EntityInterface $entity, string $mode, array $options = []): bool
|
||||
{
|
||||
return match ($mode) {
|
||||
self::CREATE => $this->checkCreate($entity, $options),
|
||||
self::UPDATE => $this->checkUpdate($entity, $options),
|
||||
self::DELETE => $this->checkDelete($entity, $options),
|
||||
default => throw new InvalidArgumentException('Wrong checking mode: ' . $mode),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs each of the rules by passing the provided entity and returns true if all
|
||||
* of them pass. The rules selected will be only those specified to be run on 'create'
|
||||
*
|
||||
* @param \Cake\Datasource\EntityInterface $entity The entity to check for validity.
|
||||
* @param array<string, mixed> $options Extra options to pass to checker functions.
|
||||
* @return bool
|
||||
*/
|
||||
public function checkCreate(EntityInterface $entity, array $options = []): bool
|
||||
{
|
||||
return $this->_checkRules(
|
||||
$entity,
|
||||
$options,
|
||||
array_merge(array_values($this->_rules), array_values($this->_createRules)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs each of the rules by passing the provided entity and returns true if all
|
||||
* of them pass. The rules selected will be only those specified to be run on 'update'
|
||||
*
|
||||
* @param \Cake\Datasource\EntityInterface $entity The entity to check for validity.
|
||||
* @param array<string, mixed> $options Extra options to pass to checker functions.
|
||||
* @return bool
|
||||
*/
|
||||
public function checkUpdate(EntityInterface $entity, array $options = []): bool
|
||||
{
|
||||
return $this->_checkRules(
|
||||
$entity,
|
||||
$options,
|
||||
array_merge(array_values($this->_rules), array_values($this->_updateRules)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs each of the rules by passing the provided entity and returns true if all
|
||||
* of them pass. The rules selected will be only those specified to be run on 'delete'
|
||||
*
|
||||
* @param \Cake\Datasource\EntityInterface $entity The entity to check for validity.
|
||||
* @param array<string, mixed> $options Extra options to pass to checker functions.
|
||||
* @return bool
|
||||
*/
|
||||
public function checkDelete(EntityInterface $entity, array $options = []): bool
|
||||
{
|
||||
return $this->_checkRules($entity, $options, $this->_deleteRules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by top level functions checkDelete, checkCreate and checkUpdate, this function
|
||||
* iterates an array containing the rules to be checked and checks them all.
|
||||
*
|
||||
* @param \Cake\Datasource\EntityInterface $entity The entity to check for validity.
|
||||
* @param array<string, mixed> $options Extra options to pass to checker functions.
|
||||
* @param array<\Cake\Datasource\RuleInvoker> $rules The list of rules that must be checked.
|
||||
* @return bool
|
||||
*/
|
||||
protected function _checkRules(EntityInterface $entity, array $options = [], array $rules = []): bool
|
||||
{
|
||||
$success = true;
|
||||
$options += $this->_options;
|
||||
foreach ($rules as $rule) {
|
||||
$success = $rule($entity, $options) && $success;
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method for decorating any callable so that if it returns false, the correct
|
||||
* property in the entity is marked as invalid.
|
||||
*
|
||||
* @param callable $rule The rule to decorate
|
||||
* @param array|string|null $name The alias for a rule or an array of options
|
||||
* @param array<string, mixed> $options The options containing the error message and field.
|
||||
* @return \Cake\Datasource\RuleInvoker
|
||||
*/
|
||||
protected function _addError(callable $rule, array|string|null $name = null, array $options = []): RuleInvoker
|
||||
{
|
||||
if (is_array($name)) {
|
||||
$options = $name;
|
||||
$name = null;
|
||||
}
|
||||
|
||||
if (!($rule instanceof RuleInvoker)) {
|
||||
$rule = new RuleInvoker($rule, $name, $options);
|
||||
} else {
|
||||
$rule->setOptions($options)->setName($name);
|
||||
}
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a rule with the same name doesn't already exist
|
||||
*
|
||||
* @param string $name The name to check
|
||||
* @param array<\Cake\Datasource\RuleInvoker> $rules The rules array to check
|
||||
* @return void
|
||||
* @throws \Cake\Core\Exception\CakeException
|
||||
*/
|
||||
protected function checkName(string $name, array $rules): void
|
||||
{
|
||||
if (array_key_exists($name, $rules)) {
|
||||
throw new CakeException('A rule with the same name already exists');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : 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(tm) Project
|
||||
* @since 3.5.0
|
||||
* @license https://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Datasource;
|
||||
|
||||
/**
|
||||
* An interface used by TableSchema objects.
|
||||
*/
|
||||
interface SchemaInterface
|
||||
{
|
||||
/**
|
||||
* Get the name of the table.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string;
|
||||
|
||||
/**
|
||||
* Add a column to the table.
|
||||
*
|
||||
* ### Attributes
|
||||
*
|
||||
* Columns can have several attributes:
|
||||
*
|
||||
* - `type` The type of the column. This should be
|
||||
* one of CakePHP's abstract types.
|
||||
* - `length` The length of the column.
|
||||
* - `precision` The number of decimal places to store
|
||||
* for float and decimal types.
|
||||
* - `default` The default value of the column.
|
||||
* - `null` Whether the column can hold nulls.
|
||||
* - `fixed` Whether the column is a fixed length column.
|
||||
* This is only present/valid with string columns.
|
||||
* - `unsigned` Whether the column is an unsigned column.
|
||||
* This is only present/valid for integer, decimal, float columns.
|
||||
*
|
||||
* In addition to the above keys, the following keys are
|
||||
* implemented in some database dialects, but not all:
|
||||
*
|
||||
* - `comment` The comment for the column.
|
||||
*
|
||||
* @param string $name The name of the column
|
||||
* @param array<string, mixed>|string $attrs The attributes for the column or the type name.
|
||||
* @return $this
|
||||
*/
|
||||
public function addColumn(string $name, array|string $attrs);
|
||||
|
||||
/**
|
||||
* Get column data in the table.
|
||||
*
|
||||
* @param string $name The column name.
|
||||
* @return array<string, mixed>|null Column data or null.
|
||||
*/
|
||||
public function getColumn(string $name): ?array;
|
||||
|
||||
/**
|
||||
* Returns true if a column exists in the schema.
|
||||
*
|
||||
* @param string $name Column name.
|
||||
* @return bool
|
||||
*/
|
||||
public function hasColumn(string $name): bool;
|
||||
|
||||
/**
|
||||
* Remove a column from the table schema.
|
||||
*
|
||||
* If the column is not defined in the table, no error will be raised.
|
||||
*
|
||||
* @param string $name The name of the column
|
||||
* @return $this
|
||||
*/
|
||||
public function removeColumn(string $name);
|
||||
|
||||
/**
|
||||
* Get the column names in the table.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function columns(): array;
|
||||
|
||||
/**
|
||||
* Returns column type or null if a column does not exist.
|
||||
*
|
||||
* @param string $name The column to get the type of.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getColumnType(string $name): ?string;
|
||||
|
||||
/**
|
||||
* Sets the type of column.
|
||||
*
|
||||
* @param string $name The column to set the type of.
|
||||
* @param string $type The type to set the column to.
|
||||
* @return $this
|
||||
*/
|
||||
public function setColumnType(string $name, string $type);
|
||||
|
||||
/**
|
||||
* Returns the base type name for the provided column.
|
||||
* This represents the database type a more complex class is
|
||||
* based upon.
|
||||
*
|
||||
* @param string $column The column name to get the base type from
|
||||
* @return string|null The base type name
|
||||
*/
|
||||
public function baseColumnType(string $column): ?string;
|
||||
|
||||
/**
|
||||
* Check whether a field is nullable
|
||||
*
|
||||
* Missing columns are nullable.
|
||||
*
|
||||
* @param string $name The column to get the type of.
|
||||
* @return bool Whether the field is nullable.
|
||||
*/
|
||||
public function isNullable(string $name): bool;
|
||||
|
||||
/**
|
||||
* Returns an array where the keys are the column names in the schema
|
||||
* and the values the database type they have.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function typeMap(): array;
|
||||
|
||||
/**
|
||||
* Get a hash of columns and their default values.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function defaultValues(): array;
|
||||
|
||||
/**
|
||||
* Sets the options for a table.
|
||||
*
|
||||
* Table options allow you to set platform specific table level options.
|
||||
* For example the engine type in MySQL.
|
||||
*
|
||||
* @param array<string, mixed> $options The options to set, or null to read options.
|
||||
* @return $this
|
||||
*/
|
||||
public function setOptions(array $options);
|
||||
|
||||
/**
|
||||
* Gets the options for a table.
|
||||
*
|
||||
* Table options allow you to set platform specific table level options.
|
||||
* For example the engine type in MySQL.
|
||||
*
|
||||
* @return array<string, mixed> An array of options.
|
||||
*/
|
||||
public function getOptions(): array;
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "cakephp/datasource",
|
||||
"description": "Provides connection managing and traits for Entities and Queries that can be reused for different datastores",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"cakephp",
|
||||
"datasource",
|
||||
"connection management",
|
||||
"entity",
|
||||
"query"
|
||||
],
|
||||
"homepage": "https://cakephp.org",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "CakePHP Community",
|
||||
"homepage": "https://github.com/cakephp/datasource/graphs/contributors"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/cakephp/cakephp/issues",
|
||||
"forum": "https://stackoverflow.com/tags/cakephp",
|
||||
"irc": "irc://irc.freenode.org/cakephp",
|
||||
"source": "https://github.com/cakephp/datasource"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"cakephp/core": "^5.3.0",
|
||||
"psr/simple-cache": "^2.0 || ^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"cakephp/cache": "^5.3.0",
|
||||
"cakephp/collection": "^5.3.0",
|
||||
"cakephp/utility": "^5.3.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Cake\\Datasource\\": "."
|
||||
}
|
||||
},
|
||||
"suggest": {
|
||||
"cakephp/utility": "If you decide to use EntityTrait.",
|
||||
"cakephp/collection": "If you decide to use ResultSetInterface.",
|
||||
"cakephp/cache": "If you decide to use Query caching."
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-5.next": "5.3.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user