init
This commit is contained in:
Vendored
+269
@@ -0,0 +1,269 @@
|
||||
<?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 1.2.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core;
|
||||
|
||||
/**
|
||||
* App is responsible for resource location, and path management.
|
||||
*
|
||||
* ### Adding paths
|
||||
*
|
||||
* Additional paths for Templates and Plugins are configured with Configure now. See config/app.php for an
|
||||
* example. The `App.paths.plugins` and `App.paths.templates` variables are used to configure paths for plugins
|
||||
* and templates respectively. All class based resources should be mapped using your application's autoloader.
|
||||
*
|
||||
* ### Inspecting loaded paths
|
||||
*
|
||||
* You can inspect the currently loaded paths using `App::classPath('Controller')` for example to see loaded
|
||||
* controller paths.
|
||||
*
|
||||
* It is also possible to inspect paths for plugin classes, for instance, to get
|
||||
* the path to a plugin's helpers you would call `App::classPath('View/Helper', 'MyPlugin')`
|
||||
*
|
||||
* ### Locating plugins
|
||||
*
|
||||
* Plugins can be located with App as well. Using Plugin::path('DebugKit') for example, will
|
||||
* give you the full path to the DebugKit plugin.
|
||||
*
|
||||
* @link https://book.cakephp.org/4/en/core-libraries/app.html
|
||||
*/
|
||||
class App
|
||||
{
|
||||
/**
|
||||
* Return the class name namespaced. This method checks if the class is defined on the
|
||||
* application/plugin, otherwise try to load from the CakePHP core
|
||||
*
|
||||
* @param string $class Class name
|
||||
* @param string $type Type of class
|
||||
* @param string $suffix Class name suffix
|
||||
* @return string|null Namespaced class name, null if the class is not found.
|
||||
* @psalm-return class-string|null
|
||||
*/
|
||||
public static function className(string $class, string $type = '', string $suffix = ''): ?string
|
||||
{
|
||||
if (strpos($class, '\\') !== false) {
|
||||
return class_exists($class) ? $class : null;
|
||||
}
|
||||
|
||||
[$plugin, $name] = pluginSplit($class);
|
||||
$fullname = '\\' . str_replace('/', '\\', $type . '\\' . $name) . $suffix;
|
||||
|
||||
$base = $plugin ?: Configure::read('App.namespace');
|
||||
if ($base !== null) {
|
||||
$base = str_replace('/', '\\', rtrim($base, '\\'));
|
||||
|
||||
if (static::_classExistsInBase($fullname, $base)) {
|
||||
/** @var class-string */
|
||||
return $base . $fullname;
|
||||
}
|
||||
}
|
||||
|
||||
if ($plugin || !static::_classExistsInBase($fullname, 'Cake')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var class-string */
|
||||
return 'Cake' . $fullname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plugin split name of a class
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* ```
|
||||
* App::shortName(
|
||||
* 'SomeVendor\SomePlugin\Controller\Component\TestComponent',
|
||||
* 'Controller/Component',
|
||||
* 'Component'
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* Returns: SomeVendor/SomePlugin.Test
|
||||
*
|
||||
* ```
|
||||
* App::shortName(
|
||||
* 'SomeVendor\SomePlugin\Controller\Component\Subfolder\TestComponent',
|
||||
* 'Controller/Component',
|
||||
* 'Component'
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* Returns: SomeVendor/SomePlugin.Subfolder/Test
|
||||
*
|
||||
* ```
|
||||
* App::shortName(
|
||||
* 'Cake\Controller\Component\AuthComponent',
|
||||
* 'Controller/Component',
|
||||
* 'Component'
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* Returns: Auth
|
||||
*
|
||||
* @param string $class Class name
|
||||
* @param string $type Type of class
|
||||
* @param string $suffix Class name suffix
|
||||
* @return string Plugin split name of class
|
||||
*/
|
||||
public static function shortName(string $class, string $type, string $suffix = ''): string
|
||||
{
|
||||
$class = str_replace('\\', '/', $class);
|
||||
$type = '/' . $type . '/';
|
||||
|
||||
$pos = strrpos($class, $type);
|
||||
if ($pos === false) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
$pluginName = (string)substr($class, 0, $pos);
|
||||
$name = (string)substr($class, $pos + strlen($type));
|
||||
|
||||
if ($suffix) {
|
||||
$name = (string)substr($name, 0, -strlen($suffix));
|
||||
}
|
||||
|
||||
$nonPluginNamespaces = [
|
||||
'Cake',
|
||||
str_replace('\\', '/', (string)Configure::read('App.namespace')),
|
||||
];
|
||||
if (in_array($pluginName, $nonPluginNamespaces, true)) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
return $pluginName . '.' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* _classExistsInBase
|
||||
*
|
||||
* Test isolation wrapper
|
||||
*
|
||||
* @param string $name Class name.
|
||||
* @param string $namespace Namespace.
|
||||
* @return bool
|
||||
*/
|
||||
protected static function _classExistsInBase(string $name, string $namespace): bool
|
||||
{
|
||||
return class_exists($namespace . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to read information stored path.
|
||||
*
|
||||
* The 1st character of $type argument should be lower cased and will return the
|
||||
* value of `App.paths.$type` config.
|
||||
*
|
||||
* Default types:
|
||||
* - plugins
|
||||
* - templates
|
||||
* - locales
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* App::path('plugins');
|
||||
* ```
|
||||
*
|
||||
* Will return the value of `App.paths.plugins` config.
|
||||
*
|
||||
* Deprecated: 4.0 App::path() is deprecated for class path (inside src/ directory).
|
||||
* Use \Cake\Core\App::classPath() instead or directly the method on \Cake\Core\Plugin class.
|
||||
*
|
||||
* @param string $type Type of path
|
||||
* @param string|null $plugin Plugin name
|
||||
* @return array<string>
|
||||
* @link https://book.cakephp.org/4/en/core-libraries/app.html#finding-paths-to-namespaces
|
||||
*/
|
||||
public static function path(string $type, ?string $plugin = null): array
|
||||
{
|
||||
if ($plugin === null && $type[0] === strtolower($type[0])) {
|
||||
return (array)Configure::read('App.paths.' . $type);
|
||||
}
|
||||
|
||||
if ($type === 'templates') {
|
||||
/** @psalm-suppress PossiblyNullArgument */
|
||||
return [Plugin::templatePath($plugin)];
|
||||
}
|
||||
|
||||
if ($type === 'locales') {
|
||||
/** @psalm-suppress PossiblyNullArgument */
|
||||
return [Plugin::path($plugin) . 'resources' . DIRECTORY_SEPARATOR . 'locales' . DIRECTORY_SEPARATOR];
|
||||
}
|
||||
|
||||
deprecationWarning(
|
||||
'App::path() is deprecated for class path.'
|
||||
. ' Use \Cake\Core\App::classPath() or \Cake\Core\Plugin::classPath() instead.'
|
||||
);
|
||||
|
||||
return static::classPath($type, $plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path to a class type in the application or a plugin.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* App::classPath('Model/Table');
|
||||
* ```
|
||||
*
|
||||
* Will return the path for tables - e.g. `src/Model/Table/`.
|
||||
*
|
||||
* ```
|
||||
* App::classPath('Model/Table', 'My/Plugin');
|
||||
* ```
|
||||
*
|
||||
* Will return the plugin based path for those.
|
||||
*
|
||||
* @param string $type Package type.
|
||||
* @param string|null $plugin Plugin name.
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function classPath(string $type, ?string $plugin = null): array
|
||||
{
|
||||
if ($plugin !== null) {
|
||||
return [
|
||||
Plugin::classPath($plugin) . $type . DIRECTORY_SEPARATOR,
|
||||
];
|
||||
}
|
||||
|
||||
return [APP . $type . DIRECTORY_SEPARATOR];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path to a package inside the CakePHP core
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```
|
||||
* App::core('Cache/Engine');
|
||||
* ```
|
||||
*
|
||||
* Will return the full path to the cache engines package.
|
||||
*
|
||||
* @param string $type Package type.
|
||||
* @return array<string> Full path to package
|
||||
*/
|
||||
public static function core(string $type): array
|
||||
{
|
||||
if ($type === 'templates') {
|
||||
return [CORE_PATH . 'templates' . DIRECTORY_SEPARATOR];
|
||||
}
|
||||
|
||||
return [CAKE . str_replace('/', DIRECTORY_SEPARATOR, $type) . DIRECTORY_SEPARATOR];
|
||||
}
|
||||
}
|
||||
Vendored
+305
@@ -0,0 +1,305 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright 2005-2011, 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)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 3.6.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core;
|
||||
|
||||
use Cake\Console\CommandCollection;
|
||||
use Cake\Http\MiddlewareQueue;
|
||||
use Cake\Routing\RouteBuilder;
|
||||
use Closure;
|
||||
use InvalidArgumentException;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* Base Plugin Class
|
||||
*
|
||||
* Every plugin should extend from this class or implement the interfaces and
|
||||
* include a plugin class in its src root folder.
|
||||
*/
|
||||
class BasePlugin implements PluginInterface
|
||||
{
|
||||
/**
|
||||
* Do bootstrapping or not
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $bootstrapEnabled = true;
|
||||
|
||||
/**
|
||||
* Console middleware
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $consoleEnabled = true;
|
||||
|
||||
/**
|
||||
* Enable middleware
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $middlewareEnabled = true;
|
||||
|
||||
/**
|
||||
* Register container services
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $servicesEnabled = true;
|
||||
|
||||
/**
|
||||
* Load routes or not
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $routesEnabled = true;
|
||||
|
||||
/**
|
||||
* The path to this plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* The class path for this plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $classPath;
|
||||
|
||||
/**
|
||||
* The config path for this plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $configPath;
|
||||
|
||||
/**
|
||||
* The templates path for this plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $templatePath;
|
||||
|
||||
/**
|
||||
* The name of this plugin
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array<string, mixed> $options Options
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
foreach (static::VALID_HOOKS as $key) {
|
||||
if (isset($options[$key])) {
|
||||
$this->{"{$key}Enabled"} = (bool)$options[$key];
|
||||
}
|
||||
}
|
||||
foreach (['name', 'path', 'classPath', 'configPath', 'templatePath'] as $path) {
|
||||
if (isset($options[$path])) {
|
||||
$this->{$path} = $options[$path];
|
||||
}
|
||||
}
|
||||
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization hook called from constructor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
if ($this->name) {
|
||||
return $this->name;
|
||||
}
|
||||
$parts = explode('\\', static::class);
|
||||
array_pop($parts);
|
||||
$this->name = implode('/', $parts);
|
||||
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getPath(): string
|
||||
{
|
||||
if ($this->path) {
|
||||
return $this->path;
|
||||
}
|
||||
$reflection = new ReflectionClass($this);
|
||||
$path = dirname($reflection->getFileName());
|
||||
|
||||
// Trim off src
|
||||
if (substr($path, -3) === 'src') {
|
||||
$path = substr($path, 0, -3);
|
||||
}
|
||||
$this->path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
||||
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getConfigPath(): string
|
||||
{
|
||||
if ($this->configPath) {
|
||||
return $this->configPath;
|
||||
}
|
||||
$path = $this->getPath();
|
||||
|
||||
return $path . 'config' . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getClassPath(): string
|
||||
{
|
||||
if ($this->classPath) {
|
||||
return $this->classPath;
|
||||
}
|
||||
$path = $this->getPath();
|
||||
|
||||
return $path . 'src' . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getTemplatePath(): string
|
||||
{
|
||||
if ($this->templatePath) {
|
||||
return $this->templatePath;
|
||||
}
|
||||
$path = $this->getPath();
|
||||
|
||||
return $this->templatePath = $path . 'templates' . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function enable(string $hook)
|
||||
{
|
||||
$this->checkHook($hook);
|
||||
$this->{"{$hook}Enabled"} = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function disable(string $hook)
|
||||
{
|
||||
$this->checkHook($hook);
|
||||
$this->{"{$hook}Enabled"} = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function isEnabled(string $hook): bool
|
||||
{
|
||||
$this->checkHook($hook);
|
||||
|
||||
return $this->{"{$hook}Enabled"} === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a hook name is valid
|
||||
*
|
||||
* @param string $hook The hook name to check
|
||||
* @throws \InvalidArgumentException on invalid hooks
|
||||
* @return void
|
||||
*/
|
||||
protected function checkHook(string $hook): void
|
||||
{
|
||||
if (!in_array($hook, static::VALID_HOOKS, true)) {
|
||||
throw new InvalidArgumentException(
|
||||
"`$hook` is not a valid hook name. Must be one of " . implode(', ', static::VALID_HOOKS)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function routes(RouteBuilder $routes): void
|
||||
{
|
||||
$path = $this->getConfigPath() . 'routes.php';
|
||||
if (is_file($path)) {
|
||||
$return = require $path;
|
||||
if ($return instanceof Closure) {
|
||||
$return($routes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function bootstrap(PluginApplicationInterface $app): void
|
||||
{
|
||||
$bootstrap = $this->getConfigPath() . 'bootstrap.php';
|
||||
if (is_file($bootstrap)) {
|
||||
require $bootstrap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function console(CommandCollection $commands): CommandCollection
|
||||
{
|
||||
return $commands->addMany($commands->discoverPlugin($this->getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
|
||||
{
|
||||
return $middlewareQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register container services for this plugin.
|
||||
*
|
||||
* @param \Cake\Core\ContainerInterface $container The container to add services to.
|
||||
* @return void
|
||||
*/
|
||||
public function services(ContainerInterface $container): void
|
||||
{
|
||||
}
|
||||
}
|
||||
Vendored
+139
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright 2005-2011, 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)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core;
|
||||
|
||||
/**
|
||||
* ClassLoader
|
||||
*
|
||||
* @deprecated 4.0.3 Use composer to generate autoload files instead.
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
/**
|
||||
* An associative array where the key is a namespace prefix and the value
|
||||
* is an array of base directories for classes in that namespace.
|
||||
*
|
||||
* @var array<string, array>
|
||||
*/
|
||||
protected $_prefixes = [];
|
||||
|
||||
/**
|
||||
* Register loader with SPL autoloader stack.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
/** @var callable $callable */
|
||||
$callable = [$this, 'loadClass'];
|
||||
spl_autoload_register($callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a base directory for a namespace prefix.
|
||||
*
|
||||
* @param string $prefix The namespace prefix.
|
||||
* @param string $baseDir A base directory for class files in the
|
||||
* namespace.
|
||||
* @param bool $prepend If true, prepend the base directory to the stack
|
||||
* instead of appending it; this causes it to be searched first rather
|
||||
* than last.
|
||||
* @return void
|
||||
*/
|
||||
public function addNamespace(string $prefix, string $baseDir, bool $prepend = false): void
|
||||
{
|
||||
$prefix = trim($prefix, '\\') . '\\';
|
||||
|
||||
$baseDir = rtrim($baseDir, '/') . DIRECTORY_SEPARATOR;
|
||||
$baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR) . '/';
|
||||
|
||||
$this->_prefixes[$prefix] = $this->_prefixes[$prefix] ?? [];
|
||||
|
||||
if ($prepend) {
|
||||
array_unshift($this->_prefixes[$prefix], $baseDir);
|
||||
} else {
|
||||
$this->_prefixes[$prefix][] = $baseDir;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the class file for a given class name.
|
||||
*
|
||||
* @param string $class The fully-qualified class name.
|
||||
* @return string|false The mapped file name on success, or boolean false on
|
||||
* failure.
|
||||
*/
|
||||
public function loadClass(string $class)
|
||||
{
|
||||
$prefix = $class;
|
||||
|
||||
while (($pos = strrpos($prefix, '\\')) !== false) {
|
||||
$prefix = substr($class, 0, $pos + 1);
|
||||
$relativeClass = substr($class, $pos + 1);
|
||||
|
||||
$mappedFile = $this->_loadMappedFile($prefix, $relativeClass);
|
||||
if ($mappedFile) {
|
||||
return $mappedFile;
|
||||
}
|
||||
|
||||
$prefix = rtrim($prefix, '\\');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the mapped file for a namespace prefix and relative class.
|
||||
*
|
||||
* @param string $prefix The namespace prefix.
|
||||
* @param string $relativeClass The relative class name.
|
||||
* @return string|false Boolean false if no mapped file can be loaded, or the
|
||||
* name of the mapped file that was loaded.
|
||||
*/
|
||||
protected function _loadMappedFile(string $prefix, string $relativeClass)
|
||||
{
|
||||
if (!isset($this->_prefixes[$prefix])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->_prefixes[$prefix] as $baseDir) {
|
||||
$file = $baseDir . str_replace('\\', DIRECTORY_SEPARATOR, $relativeClass) . '.php';
|
||||
|
||||
if ($this->_requireFile($file)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a file exists, require it from the file system.
|
||||
*
|
||||
* @param string $file The file to require.
|
||||
* @return bool True if the file exists, false if not.
|
||||
*/
|
||||
protected function _requireFile(string $file): bool
|
||||
{
|
||||
if (file_exists($file)) {
|
||||
require $file;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Vendored
+498
@@ -0,0 +1,498 @@
|
||||
<?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 1.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core;
|
||||
|
||||
use Cake\Cache\Cache;
|
||||
use Cake\Core\Configure\ConfigEngineInterface;
|
||||
use Cake\Core\Configure\Engine\PhpConfig;
|
||||
use Cake\Core\Exception\CakeException;
|
||||
use Cake\Utility\Hash;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Configuration class. Used for managing runtime configuration information.
|
||||
*
|
||||
* Provides features for reading and writing to the runtime configuration, as well
|
||||
* as methods for loading additional configuration files or storing runtime configuration
|
||||
* for future use.
|
||||
*
|
||||
* @link https://book.cakephp.org/4/en/development/configuration.html
|
||||
*/
|
||||
class Configure
|
||||
{
|
||||
/**
|
||||
* Array of values currently stored in Configure.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected static $_values = [
|
||||
'debug' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* Configured engine classes, used to load config files from resources
|
||||
*
|
||||
* @see \Cake\Core\Configure::load()
|
||||
* @var array<\Cake\Core\Configure\ConfigEngineInterface>
|
||||
*/
|
||||
protected static $_engines = [];
|
||||
|
||||
/**
|
||||
* Flag to track whether ini_set exists.
|
||||
*
|
||||
* @var bool|null
|
||||
*/
|
||||
protected static $_hasIniSet;
|
||||
|
||||
/**
|
||||
* Used to store a dynamic variable in Configure.
|
||||
*
|
||||
* Usage:
|
||||
* ```
|
||||
* Configure::write('One.key1', 'value of the Configure::One[key1]');
|
||||
* Configure::write(['One.key1' => 'value of the Configure::One[key1]']);
|
||||
* Configure::write('One', [
|
||||
* 'key1' => 'value of the Configure::One[key1]',
|
||||
* 'key2' => 'value of the Configure::One[key2]'
|
||||
* ]);
|
||||
*
|
||||
* Configure::write([
|
||||
* 'One.key1' => 'value of the Configure::One[key1]',
|
||||
* 'One.key2' => 'value of the Configure::One[key2]'
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* @param array<string, mixed>|string $config The key to write, can be a dot notation value.
|
||||
* Alternatively can be an array containing key(s) and value(s).
|
||||
* @param mixed $value Value to set for the given key.
|
||||
* @return void
|
||||
* @link https://book.cakephp.org/4/en/development/configuration.html#writing-configuration-data
|
||||
*/
|
||||
public static function write($config, $value = null): void
|
||||
{
|
||||
if (!is_array($config)) {
|
||||
$config = [$config => $value];
|
||||
}
|
||||
|
||||
foreach ($config as $name => $valueToInsert) {
|
||||
static::$_values = Hash::insert(static::$_values, $name, $valueToInsert);
|
||||
}
|
||||
|
||||
if (isset($config['debug'])) {
|
||||
if (static::$_hasIniSet === null) {
|
||||
static::$_hasIniSet = function_exists('ini_set');
|
||||
}
|
||||
if (static::$_hasIniSet) {
|
||||
ini_set('display_errors', $config['debug'] ? '1' : '0');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to read information stored in Configure. It's not
|
||||
* possible to store `null` values in Configure.
|
||||
*
|
||||
* Usage:
|
||||
* ```
|
||||
* Configure::read('Name'); will return all values for Name
|
||||
* Configure::read('Name.key'); will return only the value of Configure::Name[key]
|
||||
* ```
|
||||
*
|
||||
* @param string|null $var Variable to obtain. Use '.' to access array elements.
|
||||
* @param mixed $default The return value when the configure does not exist
|
||||
* @return mixed Value stored in configure, or null.
|
||||
* @link https://book.cakephp.org/4/en/development/configuration.html#reading-configuration-data
|
||||
*/
|
||||
public static function read(?string $var = null, $default = null)
|
||||
{
|
||||
if ($var === null) {
|
||||
return static::$_values;
|
||||
}
|
||||
|
||||
return Hash::get(static::$_values, $var, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if given variable is set in Configure.
|
||||
*
|
||||
* @param string $var Variable name to check for
|
||||
* @return bool True if variable is there
|
||||
*/
|
||||
public static function check(string $var): bool
|
||||
{
|
||||
if (empty($var)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return static::read($var) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to get information stored in Configure. It's not
|
||||
* possible to store `null` values in Configure.
|
||||
*
|
||||
* Acts as a wrapper around Configure::read() and Configure::check().
|
||||
* The configure key/value pair fetched via this method is expected to exist.
|
||||
* In case it does not an exception will be thrown.
|
||||
*
|
||||
* Usage:
|
||||
* ```
|
||||
* Configure::readOrFail('Name'); will return all values for Name
|
||||
* Configure::readOrFail('Name.key'); will return only the value of Configure::Name[key]
|
||||
* ```
|
||||
*
|
||||
* @param string $var Variable to obtain. Use '.' to access array elements.
|
||||
* @return mixed Value stored in configure.
|
||||
* @throws \RuntimeException if the requested configuration is not set.
|
||||
* @link https://book.cakephp.org/4/en/development/configuration.html#reading-configuration-data
|
||||
*/
|
||||
public static function readOrFail(string $var)
|
||||
{
|
||||
if (!static::check($var)) {
|
||||
throw new RuntimeException(sprintf('Expected configuration key "%s" not found.', $var));
|
||||
}
|
||||
|
||||
return static::read($var);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to delete a variable from Configure.
|
||||
*
|
||||
* Usage:
|
||||
* ```
|
||||
* Configure::delete('Name'); will delete the entire Configure::Name
|
||||
* Configure::delete('Name.key'); will delete only the Configure::Name[key]
|
||||
* ```
|
||||
*
|
||||
* @param string $var the var to be deleted
|
||||
* @return void
|
||||
* @link https://book.cakephp.org/4/en/development/configuration.html#deleting-configuration-data
|
||||
*/
|
||||
public static function delete(string $var): void
|
||||
{
|
||||
static::$_values = Hash::remove(static::$_values, $var);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to consume information stored in Configure. It's not
|
||||
* possible to store `null` values in Configure.
|
||||
*
|
||||
* Acts as a wrapper around Configure::consume() and Configure::check().
|
||||
* The configure key/value pair consumed via this method is expected to exist.
|
||||
* In case it does not an exception will be thrown.
|
||||
*
|
||||
* @param string $var Variable to consume. Use '.' to access array elements.
|
||||
* @return mixed Value stored in configure.
|
||||
* @throws \RuntimeException if the requested configuration is not set.
|
||||
* @since 3.6.0
|
||||
*/
|
||||
public static function consumeOrFail(string $var)
|
||||
{
|
||||
if (!static::check($var)) {
|
||||
throw new RuntimeException(sprintf('Expected configuration key "%s" not found.', $var));
|
||||
}
|
||||
|
||||
return static::consume($var);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to read and delete a variable from Configure.
|
||||
*
|
||||
* This is primarily used during bootstrapping to move configuration data
|
||||
* out of configure into the various other classes in CakePHP.
|
||||
*
|
||||
* @param string $var The key to read and remove.
|
||||
* @return array|string|null
|
||||
*/
|
||||
public static function consume(string $var)
|
||||
{
|
||||
if (strpos($var, '.') === false) {
|
||||
if (!isset(static::$_values[$var])) {
|
||||
return null;
|
||||
}
|
||||
$value = static::$_values[$var];
|
||||
unset(static::$_values[$var]);
|
||||
|
||||
return $value;
|
||||
}
|
||||
$value = Hash::get(static::$_values, $var);
|
||||
static::delete($var);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new engine to Configure. Engines allow you to read configuration
|
||||
* files in various formats/storage locations. CakePHP comes with two built-in engines
|
||||
* PhpConfig and IniConfig. You can also implement your own engine classes in your application.
|
||||
*
|
||||
* To add a new engine to Configure:
|
||||
*
|
||||
* ```
|
||||
* Configure::config('ini', new IniConfig());
|
||||
* ```
|
||||
*
|
||||
* @param string $name The name of the engine being configured. This alias is used later to
|
||||
* read values from a specific engine.
|
||||
* @param \Cake\Core\Configure\ConfigEngineInterface $engine The engine to append.
|
||||
* @return void
|
||||
*/
|
||||
public static function config(string $name, ConfigEngineInterface $engine): void
|
||||
{
|
||||
static::$_engines[$name] = $engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the Engine objects is configured.
|
||||
*
|
||||
* @param string $name Engine name.
|
||||
* @return bool
|
||||
*/
|
||||
public static function isConfigured(string $name): bool
|
||||
{
|
||||
return isset(static::$_engines[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the names of the configured Engine objects.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function configured(): array
|
||||
{
|
||||
$engines = array_keys(static::$_engines);
|
||||
|
||||
return array_map(function ($key) {
|
||||
return (string)$key;
|
||||
}, $engines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a configured engine. This will unset the engine
|
||||
* and make any future attempts to use it cause an Exception.
|
||||
*
|
||||
* @param string $name Name of the engine to drop.
|
||||
* @return bool Success
|
||||
*/
|
||||
public static function drop(string $name): bool
|
||||
{
|
||||
if (!isset(static::$_engines[$name])) {
|
||||
return false;
|
||||
}
|
||||
unset(static::$_engines[$name]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads stored configuration information from a resource. You can add
|
||||
* config file resource engines with `Configure::config()`.
|
||||
*
|
||||
* Loaded configuration information will be merged with the current
|
||||
* runtime configuration. You can load configuration files from plugins
|
||||
* by preceding the filename with the plugin name.
|
||||
*
|
||||
* `Configure::load('Users.user', 'default')`
|
||||
*
|
||||
* Would load the 'user' config file using the default config engine. You can load
|
||||
* app config files by giving the name of the resource you want loaded.
|
||||
*
|
||||
* ```
|
||||
* Configure::load('setup', 'default');
|
||||
* ```
|
||||
*
|
||||
* If using `default` config and no engine has been configured for it yet,
|
||||
* one will be automatically created using PhpConfig
|
||||
*
|
||||
* @param string $key name of configuration resource to load.
|
||||
* @param string $config Name of the configured engine to use to read the resource identified by $key.
|
||||
* @param bool $merge if config files should be merged instead of simply overridden
|
||||
* @return bool True if load successful.
|
||||
* @throws \Cake\Core\Exception\CakeException if the $config engine is not found
|
||||
* @link https://book.cakephp.org/4/en/development/configuration.html#reading-and-writing-configuration-files
|
||||
*/
|
||||
public static function load(string $key, string $config = 'default', bool $merge = true): bool
|
||||
{
|
||||
$engine = static::_getEngine($config);
|
||||
if (!$engine) {
|
||||
throw new CakeException(
|
||||
sprintf(
|
||||
'Config %s engine not found when attempting to load %s.',
|
||||
$config,
|
||||
$key
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$values = $engine->read($key);
|
||||
|
||||
if ($merge) {
|
||||
$values = Hash::merge(static::$_values, $values);
|
||||
}
|
||||
|
||||
static::write($values);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump data currently in Configure into $key. The serialization format
|
||||
* is decided by the config engine attached as $config. For example, if the
|
||||
* 'default' adapter is a PhpConfig, the generated file will be a PHP
|
||||
* configuration file loadable by the PhpConfig.
|
||||
*
|
||||
* ### Usage
|
||||
*
|
||||
* Given that the 'default' engine is an instance of PhpConfig.
|
||||
* Save all data in Configure to the file `my_config.php`:
|
||||
*
|
||||
* ```
|
||||
* Configure::dump('my_config', 'default');
|
||||
* ```
|
||||
*
|
||||
* Save only the error handling configuration:
|
||||
*
|
||||
* ```
|
||||
* Configure::dump('error', 'default', ['Error', 'Exception'];
|
||||
* ```
|
||||
*
|
||||
* @param string $key The identifier to create in the config adapter.
|
||||
* This could be a filename or a cache key depending on the adapter being used.
|
||||
* @param string $config The name of the configured adapter to dump data with.
|
||||
* @param array<string> $keys The name of the top-level keys you want to dump.
|
||||
* This allows you save only some data stored in Configure.
|
||||
* @return bool Success
|
||||
* @throws \Cake\Core\Exception\CakeException if the adapter does not implement a `dump` method.
|
||||
*/
|
||||
public static function dump(string $key, string $config = 'default', array $keys = []): bool
|
||||
{
|
||||
$engine = static::_getEngine($config);
|
||||
if (!$engine) {
|
||||
throw new CakeException(sprintf('There is no "%s" config engine.', $config));
|
||||
}
|
||||
$values = static::$_values;
|
||||
if (!empty($keys)) {
|
||||
$values = array_intersect_key($values, array_flip($keys));
|
||||
}
|
||||
|
||||
return $engine->dump($key, $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configured engine. Internally used by `Configure::load()` and `Configure::dump()`
|
||||
* Will create new PhpConfig for default if not configured yet.
|
||||
*
|
||||
* @param string $config The name of the configured adapter
|
||||
* @return \Cake\Core\Configure\ConfigEngineInterface|null Engine instance or null
|
||||
*/
|
||||
protected static function _getEngine(string $config): ?ConfigEngineInterface
|
||||
{
|
||||
if (!isset(static::$_engines[$config])) {
|
||||
if ($config !== 'default') {
|
||||
return null;
|
||||
}
|
||||
static::config($config, new PhpConfig());
|
||||
}
|
||||
|
||||
return static::$_engines[$config];
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to determine the current version of CakePHP.
|
||||
*
|
||||
* Usage
|
||||
* ```
|
||||
* Configure::version();
|
||||
* ```
|
||||
*
|
||||
* @return string Current version of CakePHP
|
||||
*/
|
||||
public static function version(): string
|
||||
{
|
||||
$version = static::read('Cake.version');
|
||||
if ($version !== null) {
|
||||
return $version;
|
||||
}
|
||||
|
||||
$path = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'config/config.php';
|
||||
if (is_file($path)) {
|
||||
$config = require $path;
|
||||
static::write($config);
|
||||
|
||||
return static::read('Cake.version');
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to write runtime configuration into Cache. Stored runtime configuration can be
|
||||
* restored using `Configure::restore()`. These methods can be used to enable configuration managers
|
||||
* frontends, or other GUI type interfaces for configuration.
|
||||
*
|
||||
* @param string $name The storage name for the saved configuration.
|
||||
* @param string $cacheConfig The cache configuration to save into. Defaults to 'default'
|
||||
* @param array|null $data Either an array of data to store, or leave empty to store all values.
|
||||
* @return bool Success
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function store(string $name, string $cacheConfig = 'default', ?array $data = null): bool
|
||||
{
|
||||
if ($data === null) {
|
||||
$data = static::$_values;
|
||||
}
|
||||
if (!class_exists(Cache::class)) {
|
||||
throw new RuntimeException('You must install cakephp/cache to use Configure::store()');
|
||||
}
|
||||
|
||||
return Cache::write($name, $data, $cacheConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores configuration data stored in the Cache into configure. Restored
|
||||
* values will overwrite existing ones.
|
||||
*
|
||||
* @param string $name Name of the stored config file to load.
|
||||
* @param string $cacheConfig Name of the Cache configuration to read from.
|
||||
* @return bool Success.
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function restore(string $name, string $cacheConfig = 'default'): bool
|
||||
{
|
||||
if (!class_exists(Cache::class)) {
|
||||
throw new RuntimeException('You must install cakephp/cache to use Configure::restore()');
|
||||
}
|
||||
$values = Cache::read($name, $cacheConfig);
|
||||
if ($values) {
|
||||
static::write($values);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all values stored in Configure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clear(): void
|
||||
{
|
||||
static::$_values = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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 1.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core\Configure;
|
||||
|
||||
/**
|
||||
* An interface for creating objects compatible with Configure::load()
|
||||
*/
|
||||
interface ConfigEngineInterface
|
||||
{
|
||||
/**
|
||||
* Read a configuration file/storage key
|
||||
*
|
||||
* This method is used for reading configuration information from sources.
|
||||
* These sources can either be static resources like files, or dynamic ones like
|
||||
* a database, or other datasource.
|
||||
*
|
||||
* @param string $key Key to read.
|
||||
* @return array An array of data to merge into the runtime configuration
|
||||
*/
|
||||
public function read(string $key): array;
|
||||
|
||||
/**
|
||||
* Dumps the configure data into the storage key/file of the given `$key`.
|
||||
*
|
||||
* @param string $key The identifier to write to.
|
||||
* @param array $data The data to dump.
|
||||
* @return bool True on success or false on failure.
|
||||
*/
|
||||
public function dump(string $key, array $data): bool;
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
<?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 2.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core\Configure\Engine;
|
||||
|
||||
use Cake\Core\Configure\ConfigEngineInterface;
|
||||
use Cake\Core\Configure\FileConfigTrait;
|
||||
use Cake\Utility\Hash;
|
||||
|
||||
/**
|
||||
* Ini file configuration engine.
|
||||
*
|
||||
* Since IniConfig uses parse_ini_file underneath, you should be aware that this
|
||||
* class shares the same behavior, especially with regards to boolean and null values.
|
||||
*
|
||||
* In addition to the native `parse_ini_file` features, IniConfig also allows you
|
||||
* to create nested array structures through usage of `.` delimited names. This allows
|
||||
* you to create nested arrays structures in an ini config file. For example:
|
||||
*
|
||||
* `db.password = secret` would turn into `['db' => ['password' => 'secret']]`
|
||||
*
|
||||
* You can nest properties as deeply as needed using `.`'s. In addition to using `.` you
|
||||
* can use standard ini section notation to create nested structures:
|
||||
*
|
||||
* ```
|
||||
* [section]
|
||||
* key = value
|
||||
* ```
|
||||
*
|
||||
* Once loaded into Configure, the above would be accessed using:
|
||||
*
|
||||
* `Configure::read('section.key');
|
||||
*
|
||||
* You can also use `.` separated values in section names to create more deeply
|
||||
* nested structures.
|
||||
*
|
||||
* IniConfig also manipulates how the special ini values of
|
||||
* 'yes', 'no', 'on', 'off', 'null' are handled. These values will be
|
||||
* converted to their boolean equivalents.
|
||||
*
|
||||
* @see https://secure.php.net/parse_ini_file
|
||||
*/
|
||||
class IniConfig implements ConfigEngineInterface
|
||||
{
|
||||
use FileConfigTrait;
|
||||
|
||||
/**
|
||||
* File extension.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_extension = '.ini';
|
||||
|
||||
/**
|
||||
* The section to read, if null all sections will be read.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $_section;
|
||||
|
||||
/**
|
||||
* Build and construct a new ini file parser. The parser can be used to read
|
||||
* ini files that are on the filesystem.
|
||||
*
|
||||
* @param string|null $path Path to load ini config files from. Defaults to CONFIG.
|
||||
* @param string|null $section Only get one section, leave null to parse and fetch
|
||||
* all sections in the ini file.
|
||||
*/
|
||||
public function __construct(?string $path = null, ?string $section = null)
|
||||
{
|
||||
if ($path === null) {
|
||||
$path = CONFIG;
|
||||
}
|
||||
$this->_path = $path;
|
||||
$this->_section = $section;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an ini file and return the results as an array.
|
||||
*
|
||||
* @param string $key The identifier to read from. If the key has a . it will be treated
|
||||
* as a plugin prefix. The chosen file must be on the engine's path.
|
||||
* @return array Parsed configuration values.
|
||||
* @throws \Cake\Core\Exception\CakeException when files don't exist.
|
||||
* Or when files contain '..' as this could lead to abusive reads.
|
||||
*/
|
||||
public function read(string $key): array
|
||||
{
|
||||
$file = $this->_getFilePath($key, true);
|
||||
|
||||
$contents = parse_ini_file($file, true);
|
||||
if ($this->_section && isset($contents[$this->_section])) {
|
||||
$values = $this->_parseNestedValues($contents[$this->_section]);
|
||||
} else {
|
||||
$values = [];
|
||||
foreach ($contents as $section => $attribs) {
|
||||
if (is_array($attribs)) {
|
||||
$values[$section] = $this->_parseNestedValues($attribs);
|
||||
} else {
|
||||
$parse = $this->_parseNestedValues([$attribs]);
|
||||
$values[$section] = array_shift($parse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* parses nested values out of keys.
|
||||
*
|
||||
* @param array $values Values to be exploded.
|
||||
* @return array Array of values exploded
|
||||
*/
|
||||
protected function _parseNestedValues(array $values): array
|
||||
{
|
||||
foreach ($values as $key => $value) {
|
||||
if ($value === '1') {
|
||||
$value = true;
|
||||
}
|
||||
if ($value === '') {
|
||||
$value = false;
|
||||
}
|
||||
unset($values[$key]);
|
||||
if (strpos((string)$key, '.') !== false) {
|
||||
$values = Hash::insert($values, $key, $value);
|
||||
} else {
|
||||
$values[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the state of Configure data into an ini formatted string.
|
||||
*
|
||||
* @param string $key The identifier to write to. If the key has a . it will be treated
|
||||
* as a plugin prefix.
|
||||
* @param array $data The data to convert to ini file.
|
||||
* @return bool Success.
|
||||
*/
|
||||
public function dump(string $key, array $data): bool
|
||||
{
|
||||
$result = [];
|
||||
foreach ($data as $k => $value) {
|
||||
$isSection = false;
|
||||
/** @psalm-suppress InvalidArrayAccess */
|
||||
if ($k[0] !== '[') {
|
||||
$result[] = "[$k]";
|
||||
$isSection = true;
|
||||
}
|
||||
if (is_array($value)) {
|
||||
$kValues = Hash::flatten($value, '.');
|
||||
foreach ($kValues as $k2 => $v) {
|
||||
$result[] = "$k2 = " . $this->_value($v);
|
||||
}
|
||||
}
|
||||
if ($isSection) {
|
||||
$result[] = '';
|
||||
}
|
||||
}
|
||||
$contents = trim(implode("\n", $result));
|
||||
|
||||
$filename = $this->_getFilePath($key);
|
||||
|
||||
return file_put_contents($filename, $contents) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a value into the ini equivalent
|
||||
*
|
||||
* @param mixed $value Value to export.
|
||||
* @return string String value for ini file.
|
||||
*/
|
||||
protected function _value($value): string
|
||||
{
|
||||
if ($value === null) {
|
||||
return 'null';
|
||||
}
|
||||
if ($value === true) {
|
||||
return 'true';
|
||||
}
|
||||
if ($value === false) {
|
||||
return 'false';
|
||||
}
|
||||
|
||||
return (string)$value;
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
<?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\Core\Configure\Engine;
|
||||
|
||||
use Cake\Core\Configure\ConfigEngineInterface;
|
||||
use Cake\Core\Configure\FileConfigTrait;
|
||||
use Cake\Core\Exception\CakeException;
|
||||
|
||||
/**
|
||||
* JSON engine allows Configure to load configuration values from
|
||||
* files containing JSON strings.
|
||||
*
|
||||
* An example JSON file would look like::
|
||||
*
|
||||
* ```
|
||||
* {
|
||||
* "debug": false,
|
||||
* "App": {
|
||||
* "namespace": "MyApp"
|
||||
* },
|
||||
* "Security": {
|
||||
* "salt": "its-secret"
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class JsonConfig implements ConfigEngineInterface
|
||||
{
|
||||
use FileConfigTrait;
|
||||
|
||||
/**
|
||||
* File extension.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_extension = '.json';
|
||||
|
||||
/**
|
||||
* Constructor for JSON Config file reading.
|
||||
*
|
||||
* @param string|null $path The path to read config files from. Defaults to CONFIG.
|
||||
*/
|
||||
public function __construct(?string $path = null)
|
||||
{
|
||||
if ($path === null) {
|
||||
$path = CONFIG;
|
||||
}
|
||||
$this->_path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a config file and return its contents.
|
||||
*
|
||||
* Files with `.` in the name will be treated as values in plugins. Instead of
|
||||
* reading from the initialized path, plugin keys will be located using Plugin::path().
|
||||
*
|
||||
* @param string $key The identifier to read from. If the key has a . it will be treated
|
||||
* as a plugin prefix.
|
||||
* @return array Parsed configuration values.
|
||||
* @throws \Cake\Core\Exception\CakeException When files don't exist or when
|
||||
* files contain '..' (as this could lead to abusive reads) or when there
|
||||
* is an error parsing the JSON string.
|
||||
*/
|
||||
public function read(string $key): array
|
||||
{
|
||||
$file = $this->_getFilePath($key, true);
|
||||
|
||||
$values = json_decode(file_get_contents($file), true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new CakeException(sprintf(
|
||||
'Error parsing JSON string fetched from config file "%s.json": %s',
|
||||
$key,
|
||||
json_last_error_msg()
|
||||
));
|
||||
}
|
||||
if (!is_array($values)) {
|
||||
throw new CakeException(sprintf(
|
||||
'Decoding JSON config file "%s.json" did not return an array',
|
||||
$key
|
||||
));
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided $data into a JSON string that can be used saved
|
||||
* into a file and loaded later.
|
||||
*
|
||||
* @param string $key The identifier to write to. If the key has a . it will
|
||||
* be treated as a plugin prefix.
|
||||
* @param array $data Data to dump.
|
||||
* @return bool Success
|
||||
*/
|
||||
public function dump(string $key, array $data): bool
|
||||
{
|
||||
$filename = $this->_getFilePath($key);
|
||||
|
||||
return file_put_contents($filename, json_encode($data, JSON_PRETTY_PRINT)) > 0;
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
<?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 2.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core\Configure\Engine;
|
||||
|
||||
use Cake\Core\Configure\ConfigEngineInterface;
|
||||
use Cake\Core\Configure\FileConfigTrait;
|
||||
use Cake\Core\Exception\CakeException;
|
||||
|
||||
/**
|
||||
* PHP engine allows Configure to load configuration values from
|
||||
* files containing simple PHP arrays.
|
||||
*
|
||||
* Files compatible with PhpConfig should return an array that
|
||||
* contains all the configuration data contained in the file.
|
||||
*
|
||||
* An example configuration file would look like::
|
||||
*
|
||||
* ```
|
||||
* <?php
|
||||
* return [
|
||||
* 'debug' => false,
|
||||
* 'Security' => [
|
||||
* 'salt' => 'its-secret'
|
||||
* ],
|
||||
* 'App' => [
|
||||
* 'namespace' => 'App'
|
||||
* ]
|
||||
* ];
|
||||
* ```
|
||||
*
|
||||
* @see \Cake\Core\Configure::load() for how to load custom configuration files.
|
||||
*/
|
||||
class PhpConfig implements ConfigEngineInterface
|
||||
{
|
||||
use FileConfigTrait;
|
||||
|
||||
/**
|
||||
* File extension.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_extension = '.php';
|
||||
|
||||
/**
|
||||
* Constructor for PHP Config file reading.
|
||||
*
|
||||
* @param string|null $path The path to read config files from. Defaults to CONFIG.
|
||||
*/
|
||||
public function __construct(?string $path = null)
|
||||
{
|
||||
if ($path === null) {
|
||||
$path = CONFIG;
|
||||
}
|
||||
$this->_path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a config file and return its contents.
|
||||
*
|
||||
* Files with `.` in the name will be treated as values in plugins. Instead of
|
||||
* reading from the initialized path, plugin keys will be located using Plugin::path().
|
||||
*
|
||||
* @param string $key The identifier to read from. If the key has a . it will be treated
|
||||
* as a plugin prefix.
|
||||
* @return array Parsed configuration values.
|
||||
* @throws \Cake\Core\Exception\CakeException when files don't exist or they don't contain `$config`.
|
||||
* Or when files contain '..' as this could lead to abusive reads.
|
||||
*/
|
||||
public function read(string $key): array
|
||||
{
|
||||
$file = $this->_getFilePath($key, true);
|
||||
|
||||
$config = null;
|
||||
|
||||
$return = include $file;
|
||||
if (is_array($return)) {
|
||||
return $return;
|
||||
}
|
||||
|
||||
throw new CakeException(sprintf('Config file "%s" did not return an array', $key . '.php'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided $data into a string of PHP code that can
|
||||
* be used saved into a file and loaded later.
|
||||
*
|
||||
* @param string $key The identifier to write to. If the key has a . it will be treated
|
||||
* as a plugin prefix.
|
||||
* @param array $data Data to dump.
|
||||
* @return bool Success
|
||||
*/
|
||||
public function dump(string $key, array $data): bool
|
||||
{
|
||||
$contents = '<?php' . "\n" . 'return ' . var_export($data, true) . ';';
|
||||
|
||||
$filename = $this->_getFilePath($key);
|
||||
|
||||
return file_put_contents($filename, $contents) > 0;
|
||||
}
|
||||
}
|
||||
@@ -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.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core\Configure;
|
||||
|
||||
use Cake\Core\Exception\CakeException;
|
||||
use Cake\Core\Plugin;
|
||||
use function Cake\Core\pluginSplit;
|
||||
|
||||
/**
|
||||
* Trait providing utility methods for file based config engines.
|
||||
*/
|
||||
trait FileConfigTrait
|
||||
{
|
||||
/**
|
||||
* The path this engine finds files on.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_path = '';
|
||||
|
||||
/**
|
||||
* Get file path
|
||||
*
|
||||
* @param string $key The identifier to write to. If the key has a . it will be treated
|
||||
* as a plugin prefix.
|
||||
* @param bool $checkExists Whether to check if file exists. Defaults to false.
|
||||
* @return string Full file path
|
||||
* @throws \Cake\Core\Exception\CakeException When files don't exist or when
|
||||
* files contain '..' as this could lead to abusive reads.
|
||||
*/
|
||||
protected function _getFilePath(string $key, bool $checkExists = false): string
|
||||
{
|
||||
if (strpos($key, '..') !== false) {
|
||||
throw new CakeException('Cannot load/dump configuration files with ../ in them.');
|
||||
}
|
||||
|
||||
[$plugin, $key] = pluginSplit($key);
|
||||
|
||||
if ($plugin) {
|
||||
$file = Plugin::configPath($plugin) . $key;
|
||||
} else {
|
||||
$file = $this->_path . $key;
|
||||
}
|
||||
|
||||
$file .= $this->_extension;
|
||||
|
||||
if (!$checkExists || is_file($file)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
$realPath = realpath($file);
|
||||
if ($realPath !== false && is_file($realPath)) {
|
||||
return $realPath;
|
||||
}
|
||||
|
||||
throw new CakeException(sprintf('Could not load configuration file: %s', $file));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright 2005-2011, 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)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 3.5.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core;
|
||||
|
||||
use Cake\Console\CommandCollection;
|
||||
|
||||
/**
|
||||
* An interface defining the methods that the
|
||||
* console runner depend on.
|
||||
*/
|
||||
interface ConsoleApplicationInterface
|
||||
{
|
||||
/**
|
||||
* Load all the application configuration and bootstrap logic.
|
||||
*
|
||||
* Override this method to add additional bootstrap logic for your application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function bootstrap(): void;
|
||||
|
||||
/**
|
||||
* Define the console commands for an application.
|
||||
*
|
||||
* @param \Cake\Console\CommandCollection $commands The CommandCollection to add commands into.
|
||||
* @return \Cake\Console\CommandCollection The updated collection.
|
||||
*/
|
||||
public function console(CommandCollection $commands): CommandCollection;
|
||||
}
|
||||
Vendored
+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
|
||||
* 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.2.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core;
|
||||
|
||||
use League\Container\Container as LeagueContainer;
|
||||
|
||||
/**
|
||||
* Dependency Injection container
|
||||
*
|
||||
* Based on the container out of League\Container
|
||||
*/
|
||||
class Container extends LeagueContainer implements ContainerInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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.2.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core;
|
||||
|
||||
/**
|
||||
* Interface for applications that configure and use a dependency injection container.
|
||||
*/
|
||||
interface ContainerApplicationInterface
|
||||
{
|
||||
/**
|
||||
* Register services to the container
|
||||
*
|
||||
* Registered services can have instances fetched out of the container
|
||||
* using `get()`. Dependencies and parameters will be resolved based
|
||||
* on service definitions.
|
||||
*
|
||||
* @param \Cake\Core\ContainerInterface $container The container to add services to
|
||||
* @return void
|
||||
*/
|
||||
public function services(ContainerInterface $container): void;
|
||||
|
||||
/**
|
||||
* Create a new container and register services.
|
||||
*
|
||||
* This will `register()` services provided by both the application
|
||||
* and any plugins if the application has plugin support.
|
||||
*
|
||||
* @return \Cake\Core\ContainerInterface A populated container
|
||||
*/
|
||||
public function getContainer(): ContainerInterface;
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<?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.2.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core;
|
||||
|
||||
use League\Container\DefinitionContainerInterface;
|
||||
|
||||
/**
|
||||
* Interface for the Dependency Injection Container in CakePHP applications
|
||||
*
|
||||
* This interface extends the PSR-11 container interface and adds
|
||||
* methods to add services and service providers to the container.
|
||||
*
|
||||
* The methods defined in this interface use the conventions provided
|
||||
* by league/container as that is the library that CakePHP uses.
|
||||
*/
|
||||
interface ContainerInterface extends DefinitionContainerInterface
|
||||
{
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
<?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\Core;
|
||||
|
||||
use Cake\Utility\Inflector;
|
||||
|
||||
/**
|
||||
* Provides methods that allow other classes access to conventions based inflections.
|
||||
*/
|
||||
trait ConventionsTrait
|
||||
{
|
||||
/**
|
||||
* Creates a fixture name
|
||||
*
|
||||
* @param string $name Model class name
|
||||
* @return string Singular model key
|
||||
*/
|
||||
protected function _fixtureName(string $name): string
|
||||
{
|
||||
return Inflector::camelize($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the proper entity name (singular) for the specified name
|
||||
*
|
||||
* @param string $name Name
|
||||
* @return string Camelized and plural model name
|
||||
*/
|
||||
protected function _entityName(string $name): string
|
||||
{
|
||||
return Inflector::singularize(Inflector::camelize($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the proper underscored model key for associations
|
||||
*
|
||||
* If the input contains a dot, assume that the right side is the real table name.
|
||||
*
|
||||
* @param string $name Model class name
|
||||
* @return string Singular model key
|
||||
*/
|
||||
protected function _modelKey(string $name): string
|
||||
{
|
||||
[, $name] = pluginSplit($name);
|
||||
|
||||
return Inflector::underscore(Inflector::singularize($name)) . '_id';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the proper model name from a foreign key
|
||||
*
|
||||
* @param string $key Foreign key
|
||||
* @return string Model name
|
||||
*/
|
||||
protected function _modelNameFromKey(string $key): string
|
||||
{
|
||||
$key = str_replace('_id', '', $key);
|
||||
|
||||
return Inflector::camelize(Inflector::pluralize($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the singular name for use in views.
|
||||
*
|
||||
* @param string $name Name to use
|
||||
* @return string Variable name
|
||||
*/
|
||||
protected function _singularName(string $name): string
|
||||
{
|
||||
return Inflector::variable(Inflector::singularize($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the plural variable name for views
|
||||
*
|
||||
* @param string $name Name to use
|
||||
* @return string Plural name for views
|
||||
*/
|
||||
protected function _variableName(string $name): string
|
||||
{
|
||||
return Inflector::variable($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the singular human name used in views
|
||||
*
|
||||
* @param string $name Controller name
|
||||
* @return string Singular human name
|
||||
*/
|
||||
protected function _singularHumanName(string $name): string
|
||||
{
|
||||
return Inflector::humanize(Inflector::underscore(Inflector::singularize($name)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a camelized version of $name
|
||||
*
|
||||
* @param string $name name
|
||||
* @return string Camelized name
|
||||
*/
|
||||
protected function _camelize(string $name): string
|
||||
{
|
||||
return Inflector::camelize($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the plural human name used in views
|
||||
*
|
||||
* @param string $name Controller name
|
||||
* @return string Plural human name
|
||||
*/
|
||||
protected function _pluralHumanName(string $name): string
|
||||
{
|
||||
return Inflector::humanize(Inflector::underscore($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the correct path for a plugin. Scans $pluginPaths for the plugin you want.
|
||||
*
|
||||
* @param string $pluginName Name of the plugin you want ie. DebugKit
|
||||
* @return string path path to the correct plugin.
|
||||
*/
|
||||
protected function _pluginPath(string $pluginName): string
|
||||
{
|
||||
if (Plugin::isLoaded($pluginName)) {
|
||||
return Plugin::path($pluginName);
|
||||
}
|
||||
|
||||
return current(App::path('plugins')) . $pluginName . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return plugin's namespace
|
||||
*
|
||||
* @param string $pluginName Plugin name
|
||||
* @return string Plugin's namespace
|
||||
*/
|
||||
protected function _pluginNamespace(string $pluginName): string
|
||||
{
|
||||
return str_replace('/', '\\', $pluginName);
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
<?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\Core\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
/**
|
||||
* Base class that all CakePHP Exceptions extend.
|
||||
*
|
||||
* @method int getCode() Gets the Exception code.
|
||||
*/
|
||||
class CakeException extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* Array of attributes that are passed in from the constructor, and
|
||||
* made available in the view when a development error is displayed.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_attributes = [];
|
||||
|
||||
/**
|
||||
* Template string that has attributes sprintf()'ed into it.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_messageTemplate = '';
|
||||
|
||||
/**
|
||||
* Array of headers to be passed to {@link \Cake\Http\Response::withHeader()}
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
protected $_responseHeaders;
|
||||
|
||||
/**
|
||||
* Default exception code
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $_defaultCode = 0;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Allows you to create exceptions that are treated as framework errors and disabled
|
||||
* when debug mode is off.
|
||||
*
|
||||
* @param array|string $message Either the string of the error message, or an array of attributes
|
||||
* that are made available in the view, and sprintf()'d into Exception::$_messageTemplate
|
||||
* @param int|null $code The error code
|
||||
* @param \Throwable|null $previous the previous exception.
|
||||
*/
|
||||
public function __construct($message = '', ?int $code = null, ?Throwable $previous = null)
|
||||
{
|
||||
if (is_array($message)) {
|
||||
$this->_attributes = $message;
|
||||
$message = vsprintf($this->_messageTemplate, $message);
|
||||
}
|
||||
parent::__construct($message, $code ?? $this->_defaultCode, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the passed in attributes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAttributes(): array
|
||||
{
|
||||
return $this->_attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set the response header to be used
|
||||
*
|
||||
* See also {@link \Cake\Http\Response::withHeader()}
|
||||
*
|
||||
* @param array|string|null $header A single header string or an associative
|
||||
* array of "header name" => "header value"
|
||||
* @param string|null $value The header value.
|
||||
* @return array|null
|
||||
* @deprecated 4.2.0 Use `HttpException::setHeaders()` instead. Response headers
|
||||
* should be set for HttpException only.
|
||||
*/
|
||||
public function responseHeader($header = null, $value = null): ?array
|
||||
{
|
||||
if ($header === null) {
|
||||
return $this->_responseHeaders;
|
||||
}
|
||||
|
||||
deprecationWarning(
|
||||
'Setting HTTP response headers from Exception directly is deprecated. ' .
|
||||
'If your exceptions extend Exception, they must now extend HttpException. ' .
|
||||
'You should only set HTTP headers on HttpException instances via the `setHeaders()` method.'
|
||||
);
|
||||
if (is_array($header)) {
|
||||
return $this->_responseHeaders = $header;
|
||||
}
|
||||
|
||||
return $this->_responseHeaders = [$header => $value];
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
class_alias(
|
||||
'Cake\Core\Exception\CakeException',
|
||||
'Cake\Core\Exception\Exception'
|
||||
);
|
||||
// phpcs:enable
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
/**
|
||||
* 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 4.2.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
deprecationWarning(
|
||||
'Since 4.2.0: Cake\Core\Exception\Exception is deprecated.' .
|
||||
'Use Cake\Core\Exception\CakeException instead.'
|
||||
);
|
||||
class_exists('Cake\Core\Exception\CakeException');
|
||||
@@ -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
|
||||
* 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\Core\Exception;
|
||||
|
||||
/**
|
||||
* Exception raised when a plugin could not be found
|
||||
*/
|
||||
class MissingPluginException extends CakeException
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected $_messageTemplate = 'Plugin %s could not be found.';
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright 2005-2011, 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)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 3.5.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core;
|
||||
|
||||
use Cake\Http\MiddlewareQueue;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
/**
|
||||
* An interface defining the methods that the
|
||||
* http server depend on.
|
||||
*/
|
||||
interface HttpApplicationInterface extends RequestHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Load all the application configuration and bootstrap logic.
|
||||
*
|
||||
* Override this method to add additional bootstrap logic for your application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function bootstrap(): void;
|
||||
|
||||
/**
|
||||
* Define the HTTP middleware layers for an application.
|
||||
*
|
||||
* @param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to set in your App Class
|
||||
* @return \Cake\Http\MiddlewareQueue
|
||||
*/
|
||||
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue;
|
||||
}
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
<?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\Core;
|
||||
|
||||
use Cake\Core\Exception\CakeException;
|
||||
use Cake\Utility\Hash;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* A trait for reading and writing instance config
|
||||
*
|
||||
* Implementing objects are expected to declare a `$_defaultConfig` property.
|
||||
*/
|
||||
trait InstanceConfigTrait
|
||||
{
|
||||
/**
|
||||
* Runtime config
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected $_config = [];
|
||||
|
||||
/**
|
||||
* Whether the config property has already been configured with defaults
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_configInitialized = false;
|
||||
|
||||
/**
|
||||
* Sets the config.
|
||||
*
|
||||
* ### Usage
|
||||
*
|
||||
* Setting a specific value:
|
||||
*
|
||||
* ```
|
||||
* $this->setConfig('key', $value);
|
||||
* ```
|
||||
*
|
||||
* Setting a nested value:
|
||||
*
|
||||
* ```
|
||||
* $this->setConfig('some.nested.key', $value);
|
||||
* ```
|
||||
*
|
||||
* Updating multiple config settings at the same time:
|
||||
*
|
||||
* ```
|
||||
* $this->setConfig(['one' => 'value', 'another' => 'value']);
|
||||
* ```
|
||||
*
|
||||
* @param array<string, mixed>|string $key The key to set, or a complete array of configs.
|
||||
* @param mixed|null $value The value to set.
|
||||
* @param bool $merge Whether to recursively merge or overwrite existing config, defaults to true.
|
||||
* @return $this
|
||||
* @throws \Cake\Core\Exception\CakeException When trying to set a key that is invalid.
|
||||
*/
|
||||
public function setConfig($key, $value = null, $merge = true)
|
||||
{
|
||||
if (!$this->_configInitialized) {
|
||||
$this->_config = $this->_defaultConfig;
|
||||
$this->_configInitialized = true;
|
||||
}
|
||||
|
||||
$this->_configWrite($key, $value, $merge);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the config.
|
||||
*
|
||||
* ### Usage
|
||||
*
|
||||
* Reading the whole config:
|
||||
*
|
||||
* ```
|
||||
* $this->getConfig();
|
||||
* ```
|
||||
*
|
||||
* Reading a specific value:
|
||||
*
|
||||
* ```
|
||||
* $this->getConfig('key');
|
||||
* ```
|
||||
*
|
||||
* Reading a nested value:
|
||||
*
|
||||
* ```
|
||||
* $this->getConfig('some.nested.key');
|
||||
* ```
|
||||
*
|
||||
* Reading with default value:
|
||||
*
|
||||
* ```
|
||||
* $this->getConfig('some-key', 'default-value');
|
||||
* ```
|
||||
*
|
||||
* @param string|null $key The key to get or null for the whole config.
|
||||
* @param mixed $default The return value when the key does not exist.
|
||||
* @return mixed Configuration data at the named key or null if the key does not exist.
|
||||
*/
|
||||
public function getConfig(?string $key = null, $default = null)
|
||||
{
|
||||
if (!$this->_configInitialized) {
|
||||
$this->_config = $this->_defaultConfig;
|
||||
$this->_configInitialized = true;
|
||||
}
|
||||
|
||||
$return = $this->_configRead($key);
|
||||
|
||||
return $return ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the config for this specific key.
|
||||
*
|
||||
* The config value for this key must exist, it can never be null.
|
||||
*
|
||||
* @param string $key The key to get.
|
||||
* @return mixed Configuration data at the named key
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function getConfigOrFail(string $key)
|
||||
{
|
||||
$config = $this->getConfig($key);
|
||||
if ($config === null) {
|
||||
throw new InvalidArgumentException(sprintf('Expected configuration `%s` not found.', $key));
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge provided config with existing config. Unlike `config()` which does
|
||||
* a recursive merge for nested keys, this method does a simple merge.
|
||||
*
|
||||
* Setting a specific value:
|
||||
*
|
||||
* ```
|
||||
* $this->configShallow('key', $value);
|
||||
* ```
|
||||
*
|
||||
* Setting a nested value:
|
||||
*
|
||||
* ```
|
||||
* $this->configShallow('some.nested.key', $value);
|
||||
* ```
|
||||
*
|
||||
* Updating multiple config settings at the same time:
|
||||
*
|
||||
* ```
|
||||
* $this->configShallow(['one' => 'value', 'another' => 'value']);
|
||||
* ```
|
||||
*
|
||||
* @param array<string, mixed>|string $key The key to set, or a complete array of configs.
|
||||
* @param mixed|null $value The value to set.
|
||||
* @return $this
|
||||
*/
|
||||
public function configShallow($key, $value = null)
|
||||
{
|
||||
if (!$this->_configInitialized) {
|
||||
$this->_config = $this->_defaultConfig;
|
||||
$this->_configInitialized = true;
|
||||
}
|
||||
|
||||
$this->_configWrite($key, $value, 'shallow');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a config key.
|
||||
*
|
||||
* @param string|null $key Key to read.
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _configRead(?string $key)
|
||||
{
|
||||
if ($key === null) {
|
||||
return $this->_config;
|
||||
}
|
||||
|
||||
if (strpos($key, '.') === false) {
|
||||
return $this->_config[$key] ?? null;
|
||||
}
|
||||
|
||||
$return = $this->_config;
|
||||
|
||||
foreach (explode('.', $key) as $k) {
|
||||
if (!is_array($return) || !isset($return[$k])) {
|
||||
$return = null;
|
||||
break;
|
||||
}
|
||||
|
||||
$return = $return[$k];
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a config key.
|
||||
*
|
||||
* @param array<string, mixed>|string $key Key to write to.
|
||||
* @param mixed $value Value to write.
|
||||
* @param string|bool $merge True to merge recursively, 'shallow' for simple merge,
|
||||
* false to overwrite, defaults to false.
|
||||
* @return void
|
||||
* @throws \Cake\Core\Exception\CakeException if attempting to clobber existing config
|
||||
*/
|
||||
protected function _configWrite($key, $value, $merge = false): void
|
||||
{
|
||||
if (is_string($key) && $value === null) {
|
||||
$this->_configDelete($key);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($merge) {
|
||||
$update = is_array($key) ? $key : [$key => $value];
|
||||
if ($merge === 'shallow') {
|
||||
$this->_config = array_merge($this->_config, Hash::expand($update));
|
||||
} else {
|
||||
$this->_config = Hash::merge($this->_config, Hash::expand($update));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_array($key)) {
|
||||
foreach ($key as $k => $val) {
|
||||
$this->_configWrite($k, $val);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (strpos($key, '.') === false) {
|
||||
$this->_config[$key] = $value;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$update = &$this->_config;
|
||||
$stack = explode('.', $key);
|
||||
|
||||
foreach ($stack as $k) {
|
||||
if (!is_array($update)) {
|
||||
throw new CakeException(sprintf('Cannot set %s value', $key));
|
||||
}
|
||||
|
||||
$update[$k] = $update[$k] ?? [];
|
||||
|
||||
$update = &$update[$k];
|
||||
}
|
||||
|
||||
$update = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a single config key.
|
||||
*
|
||||
* @param string $key Key to delete.
|
||||
* @return void
|
||||
* @throws \Cake\Core\Exception\CakeException if attempting to clobber existing config
|
||||
*/
|
||||
protected function _configDelete(string $key): void
|
||||
{
|
||||
if (strpos($key, '.') === false) {
|
||||
unset($this->_config[$key]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$update = &$this->_config;
|
||||
$stack = explode('.', $key);
|
||||
$length = count($stack);
|
||||
|
||||
foreach ($stack as $i => $k) {
|
||||
if (!is_array($update)) {
|
||||
throw new CakeException(sprintf('Cannot unset %s value', $key));
|
||||
}
|
||||
|
||||
if (!isset($update[$k])) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($i === $length - 1) {
|
||||
unset($update[$k]);
|
||||
break;
|
||||
}
|
||||
|
||||
$update = &$update[$k];
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+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.
|
||||
+416
@@ -0,0 +1,416 @@
|
||||
<?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\Core;
|
||||
|
||||
use ArrayIterator;
|
||||
use Cake\Event\EventDispatcherInterface;
|
||||
use Cake\Event\EventListenerInterface;
|
||||
use Countable;
|
||||
use IteratorAggregate;
|
||||
use RuntimeException;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Acts as a registry/factory for objects.
|
||||
*
|
||||
* Provides registry & factory functionality for object types. Used
|
||||
* as a super class for various composition based re-use features in CakePHP.
|
||||
*
|
||||
* Each subclass needs to implement the various abstract methods to complete
|
||||
* the template method load().
|
||||
*
|
||||
* The ObjectRegistry is EventManager aware, but each extending class will need to use
|
||||
* \Cake\Event\EventDispatcherTrait to attach and detach on set and bind
|
||||
*
|
||||
* @see \Cake\Controller\ComponentRegistry
|
||||
* @see \Cake\View\HelperRegistry
|
||||
* @see \Cake\Console\TaskRegistry
|
||||
* @template TObject of object
|
||||
* @template-implements \IteratorAggregate<string, TObject>
|
||||
*/
|
||||
abstract class ObjectRegistry implements Countable, IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* Map of loaded objects.
|
||||
*
|
||||
* @var array<object>
|
||||
* @psalm-var array<array-key, TObject>
|
||||
*/
|
||||
protected $_loaded = [];
|
||||
|
||||
/**
|
||||
* Loads/constructs an object instance.
|
||||
*
|
||||
* Will return the instance in the registry if it already exists.
|
||||
* If a subclass provides event support, you can use `$config['enabled'] = false`
|
||||
* to exclude constructed objects from being registered for events.
|
||||
*
|
||||
* Using {@link \Cake\Controller\Component::$components} as an example. You can alias
|
||||
* an object by setting the 'className' key, i.e.,
|
||||
*
|
||||
* ```
|
||||
* protected $components = [
|
||||
* 'Email' => [
|
||||
* 'className' => 'App\Controller\Component\AliasedEmailComponent'
|
||||
* ];
|
||||
* ];
|
||||
* ```
|
||||
*
|
||||
* All calls to the `Email` component would use `AliasedEmail` instead.
|
||||
*
|
||||
* @param string $name The name/class of the object to load.
|
||||
* @param array<string, mixed> $config Additional settings to use when loading the object.
|
||||
* @return mixed
|
||||
* @psalm-return TObject
|
||||
* @throws \Exception If the class cannot be found.
|
||||
*/
|
||||
public function load(string $name, array $config = [])
|
||||
{
|
||||
if (isset($config['className'])) {
|
||||
$objName = $name;
|
||||
$name = $config['className'];
|
||||
} else {
|
||||
[, $objName] = pluginSplit($name);
|
||||
}
|
||||
|
||||
$loaded = isset($this->_loaded[$objName]);
|
||||
if ($loaded && !empty($config)) {
|
||||
$this->_checkDuplicate($objName, $config);
|
||||
}
|
||||
if ($loaded) {
|
||||
return $this->_loaded[$objName];
|
||||
}
|
||||
|
||||
$className = $name;
|
||||
if (is_string($name)) {
|
||||
$className = $this->_resolveClassName($name);
|
||||
if ($className === null) {
|
||||
[$plugin, $name] = pluginSplit($name);
|
||||
$this->_throwMissingClassError($name, $plugin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-var TObject $instance
|
||||
* @psalm-suppress PossiblyNullArgument
|
||||
**/
|
||||
$instance = $this->_create($className, $objName, $config);
|
||||
$this->_loaded[$objName] = $instance;
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for duplicate object loading.
|
||||
*
|
||||
* If a duplicate is being loaded and has different configuration, that is
|
||||
* bad and an exception will be raised.
|
||||
*
|
||||
* An exception is raised, as replacing the object will not update any
|
||||
* references other objects may have. Additionally, simply updating the runtime
|
||||
* configuration is not a good option as we may be missing important constructor
|
||||
* logic dependent on the configuration.
|
||||
*
|
||||
* @param string $name The name of the alias in the registry.
|
||||
* @param array<string, mixed> $config The config data for the new instance.
|
||||
* @return void
|
||||
* @throws \RuntimeException When a duplicate is found.
|
||||
*/
|
||||
protected function _checkDuplicate(string $name, array $config): void
|
||||
{
|
||||
$existing = $this->_loaded[$name];
|
||||
$msg = sprintf('The "%s" alias has already been loaded.', $name);
|
||||
$hasConfig = method_exists($existing, 'getConfig');
|
||||
if (!$hasConfig) {
|
||||
throw new RuntimeException($msg);
|
||||
}
|
||||
if (empty($config)) {
|
||||
return;
|
||||
}
|
||||
$existingConfig = $existing->getConfig();
|
||||
unset($config['enabled'], $existingConfig['enabled']);
|
||||
|
||||
$failure = null;
|
||||
foreach ($config as $key => $value) {
|
||||
if (!array_key_exists($key, $existingConfig)) {
|
||||
$failure = " The `{$key}` was not defined in the previous configuration data.";
|
||||
break;
|
||||
}
|
||||
if (isset($existingConfig[$key]) && $existingConfig[$key] !== $value) {
|
||||
$failure = sprintf(
|
||||
' The `%s` key has a value of `%s` but previously had a value of `%s`',
|
||||
$key,
|
||||
json_encode($value),
|
||||
json_encode($existingConfig[$key])
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($failure) {
|
||||
throw new RuntimeException($msg . $failure);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should resolve the classname for a given object type.
|
||||
*
|
||||
* @param string $class The class to resolve.
|
||||
* @return string|null The resolved name or null for failure.
|
||||
* @psalm-return class-string|null
|
||||
*/
|
||||
abstract protected function _resolveClassName(string $class): ?string;
|
||||
|
||||
/**
|
||||
* Throw an exception when the requested object name is missing.
|
||||
*
|
||||
* @param string $class The class that is missing.
|
||||
* @param string|null $plugin The plugin $class is missing from.
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
abstract protected function _throwMissingClassError(string $class, ?string $plugin): void;
|
||||
|
||||
/**
|
||||
* Create an instance of a given classname.
|
||||
*
|
||||
* This method should construct and do any other initialization logic
|
||||
* required.
|
||||
*
|
||||
* @param object|string $class The class to build.
|
||||
* @param string $alias The alias of the object.
|
||||
* @param array<string, mixed> $config The Configuration settings for construction
|
||||
* @return object
|
||||
* @psalm-param TObject|string $class
|
||||
* @psalm-return TObject
|
||||
*/
|
||||
abstract protected function _create($class, string $alias, array $config);
|
||||
|
||||
/**
|
||||
* Get the list of loaded objects.
|
||||
*
|
||||
* @return array<string> List of object names.
|
||||
*/
|
||||
public function loaded(): array
|
||||
{
|
||||
return array_keys($this->_loaded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a given object is loaded.
|
||||
*
|
||||
* @param string $name The object name to check for.
|
||||
* @return bool True is object is loaded else false.
|
||||
*/
|
||||
public function has(string $name): bool
|
||||
{
|
||||
return isset($this->_loaded[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get loaded object instance.
|
||||
*
|
||||
* @param string $name Name of object.
|
||||
* @return object Object instance.
|
||||
* @throws \RuntimeException If not loaded or found.
|
||||
* @psalm-return TObject
|
||||
*/
|
||||
public function get(string $name)
|
||||
{
|
||||
if (!isset($this->_loaded[$name])) {
|
||||
throw new RuntimeException(sprintf('Unknown object "%s"', $name));
|
||||
}
|
||||
|
||||
return $this->_loaded[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide public read access to the loaded objects
|
||||
*
|
||||
* @param string $name Name of property to read
|
||||
* @return object|null
|
||||
* @psalm-return TObject|null
|
||||
*/
|
||||
public function __get(string $name)
|
||||
{
|
||||
return $this->_loaded[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide isset access to _loaded
|
||||
*
|
||||
* @param string $name Name of object being checked.
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset(string $name): bool
|
||||
{
|
||||
return $this->has($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an object.
|
||||
*
|
||||
* @param string $name Name of a property to set.
|
||||
* @param object $object Object to set.
|
||||
* @psalm-param TObject $object
|
||||
* @return void
|
||||
*/
|
||||
public function __set(string $name, $object): void
|
||||
{
|
||||
$this->set($name, $object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets an object.
|
||||
*
|
||||
* @param string $name Name of a property to unset.
|
||||
* @return void
|
||||
*/
|
||||
public function __unset(string $name): void
|
||||
{
|
||||
$this->unload($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes an object array, creates an array that makes lazy loading
|
||||
* easier
|
||||
*
|
||||
* @param array $objects Array of child objects to normalize.
|
||||
* @return array<string, array> Array of normalized objects.
|
||||
*/
|
||||
public function normalizeArray(array $objects): array
|
||||
{
|
||||
$normal = [];
|
||||
foreach ($objects as $i => $objectName) {
|
||||
$config = [];
|
||||
if (!is_int($i)) {
|
||||
$config = (array)$objectName;
|
||||
$objectName = $i;
|
||||
}
|
||||
[, $name] = pluginSplit($objectName);
|
||||
if (isset($config['class'])) {
|
||||
$normal[$name] = $config + ['config' => []];
|
||||
} else {
|
||||
$normal[$name] = ['class' => $objectName, 'config' => $config];
|
||||
}
|
||||
}
|
||||
|
||||
return $normal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear loaded instances in the registry.
|
||||
*
|
||||
* If the registry subclass has an event manager, the objects will be detached from events as well.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
foreach (array_keys($this->_loaded) as $name) {
|
||||
$this->unload((string)$name);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an object directly into the registry by name.
|
||||
*
|
||||
* If this collection implements events, the passed object will
|
||||
* be attached into the event manager
|
||||
*
|
||||
* @param string $name The name of the object to set in the registry.
|
||||
* @param object $object instance to store in the registry
|
||||
* @return $this
|
||||
* @psalm-param TObject $object
|
||||
*/
|
||||
public function set(string $name, object $object)
|
||||
{
|
||||
[, $objName] = pluginSplit($name);
|
||||
|
||||
// Just call unload if the object was loaded before
|
||||
if (array_key_exists($name, $this->_loaded)) {
|
||||
$this->unload($name);
|
||||
}
|
||||
if ($this instanceof EventDispatcherInterface && $object instanceof EventListenerInterface) {
|
||||
$this->getEventManager()->on($object);
|
||||
}
|
||||
$this->_loaded[$objName] = $object;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an object from the registry.
|
||||
*
|
||||
* If this registry has an event manager, the object will be detached from any events as well.
|
||||
*
|
||||
* @param string $name The name of the object to remove from the registry.
|
||||
* @return $this
|
||||
*/
|
||||
public function unload(string $name)
|
||||
{
|
||||
if (empty($this->_loaded[$name])) {
|
||||
[$plugin, $name] = pluginSplit($name);
|
||||
$this->_throwMissingClassError($name, $plugin);
|
||||
}
|
||||
|
||||
$object = $this->_loaded[$name];
|
||||
if ($this instanceof EventDispatcherInterface && $object instanceof EventListenerInterface) {
|
||||
$this->getEventManager()->off($object);
|
||||
}
|
||||
unset($this->_loaded[$name]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array iterator.
|
||||
*
|
||||
* @return \Traversable
|
||||
* @psalm-return \Traversable<string, TObject>
|
||||
*/
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
return new ArrayIterator($this->_loaded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of loaded objects.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->_loaded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug friendly object properties.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
$properties = get_object_vars($this);
|
||||
if (isset($properties['_loaded'])) {
|
||||
$properties['_loaded'] = array_keys($properties['_loaded']);
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
}
|
||||
Vendored
+136
@@ -0,0 +1,136 @@
|
||||
<?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 2.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core;
|
||||
|
||||
/**
|
||||
* Plugin is used to load and locate plugins.
|
||||
*
|
||||
* It also can retrieve plugin paths and load their bootstrap and routes files.
|
||||
*
|
||||
* @link https://book.cakephp.org/4/en/plugins.html
|
||||
*/
|
||||
class Plugin
|
||||
{
|
||||
/**
|
||||
* Holds a list of all loaded plugins and their configuration
|
||||
*
|
||||
* @var \Cake\Core\PluginCollection|null
|
||||
*/
|
||||
protected static $plugins;
|
||||
|
||||
/**
|
||||
* Returns the filesystem path for a plugin
|
||||
*
|
||||
* @param string $name name of the plugin in CamelCase format
|
||||
* @return string path to the plugin folder
|
||||
* @throws \Cake\Core\Exception\MissingPluginException If the folder for plugin was not found
|
||||
* or plugin has not been loaded.
|
||||
*/
|
||||
public static function path(string $name): string
|
||||
{
|
||||
$plugin = static::getCollection()->get($name);
|
||||
|
||||
return $plugin->getPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filesystem path for plugin's folder containing class files.
|
||||
*
|
||||
* @param string $name name of the plugin in CamelCase format.
|
||||
* @return string Path to the plugin folder containing class files.
|
||||
* @throws \Cake\Core\Exception\MissingPluginException If plugin has not been loaded.
|
||||
*/
|
||||
public static function classPath(string $name): string
|
||||
{
|
||||
$plugin = static::getCollection()->get($name);
|
||||
|
||||
return $plugin->getClassPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filesystem path for plugin's folder containing config files.
|
||||
*
|
||||
* @param string $name name of the plugin in CamelCase format.
|
||||
* @return string Path to the plugin folder containing config files.
|
||||
* @throws \Cake\Core\Exception\MissingPluginException If plugin has not been loaded.
|
||||
*/
|
||||
public static function configPath(string $name): string
|
||||
{
|
||||
$plugin = static::getCollection()->get($name);
|
||||
|
||||
return $plugin->getConfigPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filesystem path for plugin's folder containing template files.
|
||||
*
|
||||
* @param string $name name of the plugin in CamelCase format.
|
||||
* @return string Path to the plugin folder containing template files.
|
||||
* @throws \Cake\Core\Exception\MissingPluginException If plugin has not been loaded.
|
||||
*/
|
||||
public static function templatePath(string $name): string
|
||||
{
|
||||
$plugin = static::getCollection()->get($name);
|
||||
|
||||
return $plugin->getTemplatePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the plugin $plugin is already loaded.
|
||||
*
|
||||
* @param string $plugin Plugin name.
|
||||
* @return bool
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public static function isLoaded(string $plugin): bool
|
||||
{
|
||||
return static::getCollection()->has($plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of loaded plugins.
|
||||
*
|
||||
* @return array<string> A list of plugins that have been loaded
|
||||
*/
|
||||
public static function loaded(): array
|
||||
{
|
||||
$names = [];
|
||||
foreach (static::getCollection() as $plugin) {
|
||||
$names[] = $plugin->getName();
|
||||
}
|
||||
sort($names);
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the shared plugin collection.
|
||||
*
|
||||
* This method should generally not be used during application
|
||||
* runtime as plugins should be set during Application startup.
|
||||
*
|
||||
* @return \Cake\Core\PluginCollection
|
||||
*/
|
||||
public static function getCollection(): PluginCollection
|
||||
{
|
||||
if (!isset(static::$plugins)) {
|
||||
static::$plugins = new PluginCollection();
|
||||
}
|
||||
|
||||
return static::$plugins;
|
||||
}
|
||||
}
|
||||
@@ -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.6.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core;
|
||||
|
||||
use Cake\Console\CommandCollection;
|
||||
use Cake\Event\EventDispatcherInterface;
|
||||
use Cake\Http\MiddlewareQueue;
|
||||
use Cake\Routing\RouteBuilder;
|
||||
|
||||
/**
|
||||
* Interface for Applications that leverage plugins & events.
|
||||
*
|
||||
* Events can be bound to the application event manager during
|
||||
* the application's bootstrap and plugin bootstrap.
|
||||
*/
|
||||
interface PluginApplicationInterface extends EventDispatcherInterface
|
||||
{
|
||||
/**
|
||||
* Add a plugin to the loaded plugin set.
|
||||
*
|
||||
* If the named plugin does not exist, or does not define a Plugin class, an
|
||||
* instance of `Cake\Core\BasePlugin` will be used. This generated class will have
|
||||
* all plugin hooks enabled.
|
||||
*
|
||||
* @param \Cake\Core\PluginInterface|string $name The plugin name or plugin object.
|
||||
* @param array<string, mixed> $config The configuration data for the plugin if using a string for $name
|
||||
* @return $this
|
||||
*/
|
||||
public function addPlugin($name, array $config = []);
|
||||
|
||||
/**
|
||||
* Run bootstrap logic for loaded plugins.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function pluginBootstrap(): void;
|
||||
|
||||
/**
|
||||
* Run routes hooks for loaded plugins
|
||||
*
|
||||
* @param \Cake\Routing\RouteBuilder $routes The route builder to use.
|
||||
* @return \Cake\Routing\RouteBuilder
|
||||
*/
|
||||
public function pluginRoutes(RouteBuilder $routes): RouteBuilder;
|
||||
|
||||
/**
|
||||
* Run middleware hooks for plugins
|
||||
*
|
||||
* @param \Cake\Http\MiddlewareQueue $middleware The MiddlewareQueue to use.
|
||||
* @return \Cake\Http\MiddlewareQueue
|
||||
*/
|
||||
public function pluginMiddleware(MiddlewareQueue $middleware): MiddlewareQueue;
|
||||
|
||||
/**
|
||||
* Run console hooks for plugins
|
||||
*
|
||||
* @param \Cake\Console\CommandCollection $commands The CommandCollection to use.
|
||||
* @return \Cake\Console\CommandCollection
|
||||
*/
|
||||
public function pluginConsole(CommandCollection $commands): CommandCollection;
|
||||
}
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright 2005-2011, 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)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 3.6.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core;
|
||||
|
||||
use Cake\Core\Exception\CakeException;
|
||||
use Cake\Core\Exception\MissingPluginException;
|
||||
use Countable;
|
||||
use Generator;
|
||||
use InvalidArgumentException;
|
||||
use Iterator;
|
||||
|
||||
/**
|
||||
* Plugin Collection
|
||||
*
|
||||
* Holds onto plugin objects loaded into an application, and
|
||||
* provides methods for iterating, and finding plugins based
|
||||
* on criteria.
|
||||
*
|
||||
* This class implements the Iterator interface to allow plugins
|
||||
* to be iterated, handling the situation where a plugin's hook
|
||||
* method (usually bootstrap) loads another plugin during iteration.
|
||||
*
|
||||
* While its implementation supported nested iteration it does not
|
||||
* support using `continue` or `break` inside loops.
|
||||
*
|
||||
* @template-implements \Iterator<string, \Cake\Core\PluginInterface>
|
||||
*/
|
||||
class PluginCollection implements Iterator, Countable
|
||||
{
|
||||
/**
|
||||
* Plugin list
|
||||
*
|
||||
* @var array<\Cake\Core\PluginInterface>
|
||||
*/
|
||||
protected $plugins = [];
|
||||
|
||||
/**
|
||||
* Names of plugins
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $names = [];
|
||||
|
||||
/**
|
||||
* Iterator position stack.
|
||||
*
|
||||
* @var array<int>
|
||||
*/
|
||||
protected $positions = [];
|
||||
|
||||
/**
|
||||
* Loop depth
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $loopDepth = -1;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array<\Cake\Core\PluginInterface> $plugins The map of plugins to add to the collection.
|
||||
*/
|
||||
public function __construct(array $plugins = [])
|
||||
{
|
||||
foreach ($plugins as $plugin) {
|
||||
$this->add($plugin);
|
||||
}
|
||||
$this->loadConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the path information stored in vendor/cakephp-plugins.php
|
||||
*
|
||||
* This file is generated by the cakephp/plugin-installer package and used
|
||||
* to locate plugins on the filesystem as applications can use `extra.plugin-paths`
|
||||
* in their composer.json file to move plugin outside of vendor/
|
||||
*
|
||||
* @internal
|
||||
* @return void
|
||||
*/
|
||||
protected function loadConfig(): void
|
||||
{
|
||||
if (Configure::check('plugins')) {
|
||||
return;
|
||||
}
|
||||
$vendorFile = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'cakephp-plugins.php';
|
||||
if (!is_file($vendorFile)) {
|
||||
$vendorFile = dirname(dirname(dirname(dirname(__DIR__)))) . DIRECTORY_SEPARATOR . 'cakephp-plugins.php';
|
||||
if (!is_file($vendorFile)) {
|
||||
Configure::write(['plugins' => []]);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$config = require $vendorFile;
|
||||
Configure::write($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate a plugin path by looking at configuration data.
|
||||
*
|
||||
* This will use the `plugins` Configure key, and fallback to enumerating `App::path('plugins')`
|
||||
*
|
||||
* This method is not part of the official public API as plugins with
|
||||
* no plugin class are being phased out.
|
||||
*
|
||||
* @param string $name The plugin name to locate a path for.
|
||||
* @return string
|
||||
* @throws \Cake\Core\Exception\MissingPluginException when a plugin path cannot be resolved.
|
||||
* @internal
|
||||
*/
|
||||
public function findPath(string $name): string
|
||||
{
|
||||
// Ensure plugin config is loaded each time. This is necessary primarily
|
||||
// for testing because the Configure::clear() call in TestCase::tearDown()
|
||||
// wipes out all configuration including plugin paths config.
|
||||
$this->loadConfig();
|
||||
|
||||
$path = Configure::read('plugins.' . $name);
|
||||
if ($path) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
$pluginPath = str_replace('/', DIRECTORY_SEPARATOR, $name);
|
||||
$paths = App::path('plugins');
|
||||
foreach ($paths as $path) {
|
||||
if (is_dir($path . $pluginPath)) {
|
||||
return $path . $pluginPath . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
}
|
||||
|
||||
throw new MissingPluginException(['plugin' => $name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a plugin to the collection
|
||||
*
|
||||
* Plugins will be keyed by their names.
|
||||
*
|
||||
* @param \Cake\Core\PluginInterface $plugin The plugin to load.
|
||||
* @return $this
|
||||
*/
|
||||
public function add(PluginInterface $plugin)
|
||||
{
|
||||
$name = $plugin->getName();
|
||||
$this->plugins[$name] = $plugin;
|
||||
$this->names = array_keys($this->plugins);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a plugin from the collection if it exists.
|
||||
*
|
||||
* @param string $name The named plugin.
|
||||
* @return $this
|
||||
*/
|
||||
public function remove(string $name)
|
||||
{
|
||||
unset($this->plugins[$name]);
|
||||
$this->names = array_keys($this->plugins);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all plugins from the collection
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->plugins = [];
|
||||
$this->names = [];
|
||||
$this->positions = [];
|
||||
$this->loopDepth = -1;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the named plugin exists in the collection.
|
||||
*
|
||||
* @param string $name The named plugin.
|
||||
* @return bool
|
||||
*/
|
||||
public function has(string $name): bool
|
||||
{
|
||||
return isset($this->plugins[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the a plugin by name.
|
||||
*
|
||||
* If a plugin isn't already loaded it will be autoloaded on first access
|
||||
* and that plugins loaded this way may miss some hook methods.
|
||||
*
|
||||
* @param string $name The plugin to get.
|
||||
* @return \Cake\Core\PluginInterface The plugin.
|
||||
* @throws \Cake\Core\Exception\MissingPluginException when unknown plugins are fetched.
|
||||
*/
|
||||
public function get(string $name): PluginInterface
|
||||
{
|
||||
if ($this->has($name)) {
|
||||
return $this->plugins[$name];
|
||||
}
|
||||
|
||||
$plugin = $this->create($name);
|
||||
$this->add($plugin);
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a plugin instance from a name/classname and configuration.
|
||||
*
|
||||
* @param string $name The plugin name or classname
|
||||
* @param array<string, mixed> $config Configuration options for the plugin.
|
||||
* @return \Cake\Core\PluginInterface
|
||||
* @throws \Cake\Core\Exception\MissingPluginException When plugin instance could not be created.
|
||||
*/
|
||||
public function create(string $name, array $config = []): PluginInterface
|
||||
{
|
||||
if ($name === '') {
|
||||
throw new CakeException('Cannot create a plugin with empty name');
|
||||
}
|
||||
|
||||
if (strpos($name, '\\') !== false) {
|
||||
/** @var \Cake\Core\PluginInterface */
|
||||
return new $name($config);
|
||||
}
|
||||
|
||||
$config += ['name' => $name];
|
||||
$namespace = str_replace('/', '\\', $name);
|
||||
|
||||
$className = $namespace . '\\' . 'Plugin';
|
||||
// Check for [Vendor/]Foo/Plugin class
|
||||
if (!class_exists($className)) {
|
||||
$pos = strpos($name, '/');
|
||||
if ($pos === false) {
|
||||
$className = $namespace . '\\' . $name . 'Plugin';
|
||||
} else {
|
||||
$className = $namespace . '\\' . substr($name, $pos + 1) . 'Plugin';
|
||||
}
|
||||
|
||||
// Check for [Vendor/]Foo/FooPlugin
|
||||
if (!class_exists($className)) {
|
||||
$className = BasePlugin::class;
|
||||
if (empty($config['path'])) {
|
||||
$config['path'] = $this->findPath($name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @var class-string<\Cake\Core\PluginInterface> $className */
|
||||
return new $className($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Countable.
|
||||
*
|
||||
* Get the number of plugins in the collection.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->plugins);
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of Iterator Interface
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function next(): void
|
||||
{
|
||||
$this->positions[$this->loopDepth]++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of Iterator Interface
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function key(): string
|
||||
{
|
||||
return $this->names[$this->positions[$this->loopDepth]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of Iterator Interface
|
||||
*
|
||||
* @return \Cake\Core\PluginInterface
|
||||
*/
|
||||
public function current(): PluginInterface
|
||||
{
|
||||
$position = $this->positions[$this->loopDepth];
|
||||
$name = $this->names[$position];
|
||||
|
||||
return $this->plugins[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of Iterator Interface
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->positions[] = 0;
|
||||
$this->loopDepth += 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of Iterator Interface
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function valid(): bool
|
||||
{
|
||||
$valid = isset($this->names[$this->positions[$this->loopDepth]]);
|
||||
if (!$valid) {
|
||||
array_pop($this->positions);
|
||||
$this->loopDepth -= 1;
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the plugins to those with the named hook enabled.
|
||||
*
|
||||
* @param string $hook The hook to filter plugins by
|
||||
* @return \Generator<\Cake\Core\PluginInterface> A generator containing matching plugins.
|
||||
* @throws \InvalidArgumentException on invalid hooks
|
||||
*/
|
||||
public function with(string $hook): Generator
|
||||
{
|
||||
if (!in_array($hook, PluginInterface::VALID_HOOKS, true)) {
|
||||
throw new InvalidArgumentException("The `{$hook}` hook is not a known plugin hook.");
|
||||
}
|
||||
foreach ($this as $plugin) {
|
||||
if ($plugin->isEnabled($hook)) {
|
||||
yield $plugin;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright 2005-2011, 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)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 3.6.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core;
|
||||
|
||||
use Cake\Console\CommandCollection;
|
||||
use Cake\Http\MiddlewareQueue;
|
||||
use Cake\Routing\RouteBuilder;
|
||||
|
||||
/**
|
||||
* Plugin Interface
|
||||
*
|
||||
* @method void services(\Cake\Core\ContainerInterface $container) Register plugin services to
|
||||
* the application's container
|
||||
*/
|
||||
interface PluginInterface
|
||||
{
|
||||
/**
|
||||
* List of valid hooks.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
public const VALID_HOOKS = ['bootstrap', 'console', 'middleware', 'routes', 'services'];
|
||||
|
||||
/**
|
||||
* Get the name of this plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* Get the filesystem path to this plugin
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPath(): string;
|
||||
|
||||
/**
|
||||
* Get the filesystem path to configuration for this plugin
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getConfigPath(): string;
|
||||
|
||||
/**
|
||||
* Get the filesystem path to configuration for this plugin
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getClassPath(): string;
|
||||
|
||||
/**
|
||||
* Get the filesystem path to templates for this plugin
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTemplatePath(): string;
|
||||
|
||||
/**
|
||||
* Load all the application configuration and bootstrap logic.
|
||||
*
|
||||
* The default implementation of this method will include the `config/bootstrap.php` in the plugin if it exist. You
|
||||
* can override this method to replace that behavior.
|
||||
*
|
||||
* The host application is provided as an argument. This allows you to load additional
|
||||
* plugin dependencies, or attach events.
|
||||
*
|
||||
* @param \Cake\Core\PluginApplicationInterface $app The host application
|
||||
* @return void
|
||||
*/
|
||||
public function bootstrap(PluginApplicationInterface $app): void;
|
||||
|
||||
/**
|
||||
* Add console commands for the plugin.
|
||||
*
|
||||
* @param \Cake\Console\CommandCollection $commands The command collection to update
|
||||
* @return \Cake\Console\CommandCollection
|
||||
*/
|
||||
public function console(CommandCollection $commands): CommandCollection;
|
||||
|
||||
/**
|
||||
* Add middleware for the plugin.
|
||||
*
|
||||
* @param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to update.
|
||||
* @return \Cake\Http\MiddlewareQueue
|
||||
*/
|
||||
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue;
|
||||
|
||||
/**
|
||||
* Add routes for the plugin.
|
||||
*
|
||||
* The default implementation of this method will include the `config/routes.php` in the plugin if it exists. You
|
||||
* can override this method to replace that behavior.
|
||||
*
|
||||
* @param \Cake\Routing\RouteBuilder $routes The route builder to update.
|
||||
* @return void
|
||||
*/
|
||||
public function routes(RouteBuilder $routes): void;
|
||||
|
||||
/**
|
||||
* Disables the named hook
|
||||
*
|
||||
* @param string $hook The hook to disable
|
||||
* @return $this
|
||||
*/
|
||||
public function disable(string $hook);
|
||||
|
||||
/**
|
||||
* Enables the named hook
|
||||
*
|
||||
* @param string $hook The hook to disable
|
||||
* @return $this
|
||||
*/
|
||||
public function enable(string $hook);
|
||||
|
||||
/**
|
||||
* Check if the named hook is enabled
|
||||
*
|
||||
* @param string $hook The hook to check
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnabled(string $hook): bool;
|
||||
}
|
||||
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
[](https://packagist.org/packages/cakephp/core)
|
||||
[](LICENSE.txt)
|
||||
|
||||
# CakePHP Core Classes
|
||||
|
||||
A set of classes used for configuration files reading and storing.
|
||||
This repository contains the classes that are used as glue for creating the CakePHP framework.
|
||||
|
||||
## Usage
|
||||
|
||||
You can use the `Configure` class to store arbitrary configuration data:
|
||||
|
||||
```php
|
||||
use Cake\Core\Configure;
|
||||
use Cake\Core\Configure\Engine\PhpConfig;
|
||||
|
||||
Configure::write('Company.name','Pizza, Inc.');
|
||||
Configure::read('Company.name'); // Returns: 'Pizza, Inc.'
|
||||
```
|
||||
|
||||
It also possible to load configuration from external files:
|
||||
|
||||
```php
|
||||
Configure::config('default', new PhpConfig('/path/to/config/folder'));
|
||||
Configure::load('app', 'default', false);
|
||||
Configure::load('other_config', 'default');
|
||||
```
|
||||
|
||||
And write the configuration back into files:
|
||||
|
||||
```php
|
||||
Configure::dump('my_config', 'default');
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Please make sure you check the [official documentation](https://book.cakephp.org/4/en/development/configuration.html)
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
<?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.6.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core\Retry;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Allows any action to be retried in case of an exception.
|
||||
*
|
||||
* This class can be parametrized with a strategy, which will be followed
|
||||
* to determine whether the action should be retried.
|
||||
*/
|
||||
class CommandRetry
|
||||
{
|
||||
/**
|
||||
* The strategy to follow should the executed action fail.
|
||||
*
|
||||
* @var \Cake\Core\Retry\RetryStrategyInterface
|
||||
*/
|
||||
protected $strategy;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $maxRetries;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $numRetries;
|
||||
|
||||
/**
|
||||
* Creates the CommandRetry object with the given strategy and retry count
|
||||
*
|
||||
* @param \Cake\Core\Retry\RetryStrategyInterface $strategy The strategy to follow should the action fail
|
||||
* @param int $maxRetries The maximum number of retry attempts allowed
|
||||
*/
|
||||
public function __construct(RetryStrategyInterface $strategy, int $maxRetries = 1)
|
||||
{
|
||||
$this->strategy = $strategy;
|
||||
$this->maxRetries = $maxRetries;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of retries to perform in case of failure
|
||||
*
|
||||
* @param callable $action The callable action to execute with a retry strategy
|
||||
* @return mixed The return value of the passed action callable
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function run(callable $action)
|
||||
{
|
||||
$this->numRetries = 0;
|
||||
while (true) {
|
||||
try {
|
||||
return $action();
|
||||
} catch (Exception $e) {
|
||||
if (
|
||||
$this->numRetries < $this->maxRetries &&
|
||||
$this->strategy->shouldRetry($e, $this->numRetries)
|
||||
) {
|
||||
$this->numRetries++;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last number of retry attemps.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getRetries(): int
|
||||
{
|
||||
return $this->numRetries;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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.6.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core\Retry;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Used to instruct a CommandRetry object on whether a retry
|
||||
* for an action should be performed
|
||||
*/
|
||||
interface RetryStrategyInterface
|
||||
{
|
||||
/**
|
||||
* Returns true if the action can be retried, false otherwise.
|
||||
*
|
||||
* @param \Exception $exception The exception that caused the action to fail
|
||||
* @param int $retryCount The number of times action has been retried
|
||||
* @return bool Whether it is OK to retry the action
|
||||
*/
|
||||
public function shouldRetry(Exception $exception, int $retryCount): bool;
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
<?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.2.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core;
|
||||
|
||||
/**
|
||||
* Read-only wrapper for configuration data
|
||||
*
|
||||
* Intended for use with {@link \Cake\Core\Container} as
|
||||
* a typehintable way for services to have application
|
||||
* configuration injected as arrays cannot be typehinted.
|
||||
*/
|
||||
class ServiceConfig
|
||||
{
|
||||
/**
|
||||
* Read a configuration key
|
||||
*
|
||||
* @param string $path The path to read.
|
||||
* @param mixed $default The default value to use if $path does not exist.
|
||||
* @return mixed The configuration data or $default value.
|
||||
*/
|
||||
public function get(string $path, $default = null)
|
||||
{
|
||||
return Configure::read($path, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if $path exists and has a non-null value.
|
||||
*
|
||||
* @param string $path The path to check.
|
||||
* @return bool True if the configuration data exists.
|
||||
*/
|
||||
public function has(string $path): bool
|
||||
{
|
||||
return Configure::check($path);
|
||||
}
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
<?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.2.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core;
|
||||
|
||||
use League\Container\DefinitionContainerInterface;
|
||||
use League\Container\ServiceProvider\AbstractServiceProvider;
|
||||
use League\Container\ServiceProvider\BootableServiceProviderInterface;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Container ServiceProvider
|
||||
*
|
||||
* Service provider bundle related services together helping
|
||||
* to organize your application's dependencies. They also help
|
||||
* improve performance of applications with many services by
|
||||
* allowing service registration to be deferred until services are needed.
|
||||
*/
|
||||
abstract class ServiceProvider extends AbstractServiceProvider implements BootableServiceProviderInterface
|
||||
{
|
||||
/**
|
||||
* List of ids of services this provider provides.
|
||||
*
|
||||
* @var array<string>
|
||||
* @see ServiceProvider::provides()
|
||||
*/
|
||||
protected $provides = [];
|
||||
|
||||
/**
|
||||
* Get the container.
|
||||
*
|
||||
* This method's actual return type and documented return type differ
|
||||
* because PHP 7.2 doesn't support return type narrowing.
|
||||
*
|
||||
* @return \Cake\Core\ContainerInterface
|
||||
*/
|
||||
public function getContainer(): DefinitionContainerInterface
|
||||
{
|
||||
$container = parent::getContainer();
|
||||
|
||||
if (!($container instanceof ContainerInterface)) {
|
||||
$message = sprintf(
|
||||
'Unexpected container type. Expected `%s` got `%s` instead.',
|
||||
ContainerInterface::class,
|
||||
getTypeName($container)
|
||||
);
|
||||
throw new RuntimeException($message);
|
||||
}
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to the bootstrap() method
|
||||
*
|
||||
* This method wraps the league/container function so users
|
||||
* only need to use the CakePHP bootstrap() interface.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$this->bootstrap($this->getContainer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap hook for ServiceProviders
|
||||
*
|
||||
* This hook should be implemented if your service provider
|
||||
* needs to register additional service providers, load configuration
|
||||
* files or do any other work when the service provider is added to the
|
||||
* container.
|
||||
*
|
||||
* @param \Cake\Core\ContainerInterface $container The container to add services to.
|
||||
* @return void
|
||||
*/
|
||||
public function bootstrap(ContainerInterface $container): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the abstract services() method.
|
||||
*
|
||||
* This method primarily exists as a shim between the interface
|
||||
* that league/container has and the one we want to offer in CakePHP.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->services($this->getContainer());
|
||||
}
|
||||
|
||||
/**
|
||||
* The provides method is a way to let the container know that a service
|
||||
* is provided by this service provider.
|
||||
*
|
||||
* Every service that is registered via this service provider must have an
|
||||
* alias added to this array or it will be ignored.
|
||||
*
|
||||
* @param string $id Identifier.
|
||||
* @return bool
|
||||
*/
|
||||
public function provides(string $id): bool
|
||||
{
|
||||
return in_array($id, $this->provides, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the services in a provider.
|
||||
*
|
||||
* All services registered in this method should also be included in the $provides
|
||||
* property so that services can be located.
|
||||
*
|
||||
* @param \Cake\Core\ContainerInterface $container The container to add services to.
|
||||
* @return void
|
||||
*/
|
||||
abstract public function services(ContainerInterface $container): void;
|
||||
}
|
||||
+325
@@ -0,0 +1,325 @@
|
||||
<?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\Core;
|
||||
|
||||
use BadMethodCallException;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* A trait that provides a set of static methods to manage configuration
|
||||
* for classes that provide an adapter facade or need to have sets of
|
||||
* configuration data registered and manipulated.
|
||||
*
|
||||
* Implementing objects are expected to declare a static `$_dsnClassMap` property.
|
||||
*/
|
||||
trait StaticConfigTrait
|
||||
{
|
||||
/**
|
||||
* Configuration sets.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected static $_config = [];
|
||||
|
||||
/**
|
||||
* This method can be used to define configuration adapters for an application.
|
||||
*
|
||||
* To change an adapter's configuration at runtime, first drop the adapter and then
|
||||
* reconfigure it.
|
||||
*
|
||||
* Adapters will not be constructed until the first operation is done.
|
||||
*
|
||||
* ### Usage
|
||||
*
|
||||
* Assuming that the class' name is `Cache` the following scenarios
|
||||
* are supported:
|
||||
*
|
||||
* Setting a cache engine up.
|
||||
*
|
||||
* ```
|
||||
* Cache::setConfig('default', $settings);
|
||||
* ```
|
||||
*
|
||||
* Injecting a constructed adapter in:
|
||||
*
|
||||
* ```
|
||||
* Cache::setConfig('default', $instance);
|
||||
* ```
|
||||
*
|
||||
* Configure multiple adapters at once:
|
||||
*
|
||||
* ```
|
||||
* Cache::setConfig($arrayOfConfig);
|
||||
* ```
|
||||
*
|
||||
* @param array<string, mixed>|string $key The name of the configuration, or an array of multiple configs.
|
||||
* @param mixed $config Configuration value. Generally an array of name => configuration data for adapter.
|
||||
* @throws \BadMethodCallException When trying to modify an existing config.
|
||||
* @throws \LogicException When trying to store an invalid structured config array.
|
||||
* @return void
|
||||
*/
|
||||
public static function setConfig($key, $config = null): void
|
||||
{
|
||||
if ($config === null) {
|
||||
if (!is_array($key)) {
|
||||
throw new LogicException('If config is null, key must be an array.');
|
||||
}
|
||||
foreach ($key as $name => $settings) {
|
||||
static::setConfig($name, $settings);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset(static::$_config[$key])) {
|
||||
/** @psalm-suppress PossiblyInvalidArgument */
|
||||
throw new BadMethodCallException(sprintf('Cannot reconfigure existing key "%s"', $key));
|
||||
}
|
||||
|
||||
if (is_object($config)) {
|
||||
$config = ['className' => $config];
|
||||
}
|
||||
|
||||
if (is_array($config) && isset($config['url'])) {
|
||||
$parsed = static::parseDsn($config['url']);
|
||||
unset($config['url']);
|
||||
$config = $parsed + $config;
|
||||
}
|
||||
|
||||
if (isset($config['engine']) && empty($config['className'])) {
|
||||
$config['className'] = $config['engine'];
|
||||
unset($config['engine']);
|
||||
}
|
||||
/** @psalm-suppress InvalidPropertyAssignmentValue */
|
||||
static::$_config[$key] = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads existing configuration.
|
||||
*
|
||||
* @param string $key The name of the configuration.
|
||||
* @return mixed|null Configuration data at the named key or null if the key does not exist.
|
||||
*/
|
||||
public static function getConfig(string $key)
|
||||
{
|
||||
return static::$_config[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads existing configuration for a specific key.
|
||||
*
|
||||
* The config value for this key must exist, it can never be null.
|
||||
*
|
||||
* @param string $key The name of the configuration.
|
||||
* @return mixed Configuration data at the named key.
|
||||
* @throws \InvalidArgumentException If value does not exist.
|
||||
*/
|
||||
public static function getConfigOrFail(string $key)
|
||||
{
|
||||
if (!isset(static::$_config[$key])) {
|
||||
throw new InvalidArgumentException(sprintf('Expected configuration `%s` not found.', $key));
|
||||
}
|
||||
|
||||
return static::$_config[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops a constructed adapter.
|
||||
*
|
||||
* If you wish to modify an existing configuration, you should drop it,
|
||||
* change configuration and then re-add it.
|
||||
*
|
||||
* If the implementing objects supports a `$_registry` object the named configuration
|
||||
* will also be unloaded from the registry.
|
||||
*
|
||||
* @param string $config An existing configuration you wish to remove.
|
||||
* @return bool Success of the removal, returns false when the config does not exist.
|
||||
*/
|
||||
public static function drop(string $config): bool
|
||||
{
|
||||
if (!isset(static::$_config[$config])) {
|
||||
return false;
|
||||
}
|
||||
/** @psalm-suppress RedundantPropertyInitializationCheck */
|
||||
if (isset(static::$_registry)) {
|
||||
static::$_registry->unload($config);
|
||||
}
|
||||
unset(static::$_config[$config]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the named configurations
|
||||
*
|
||||
* @return array<string> Array of configurations.
|
||||
*/
|
||||
public static function configured(): array
|
||||
{
|
||||
$configurations = array_keys(static::$_config);
|
||||
|
||||
return array_map(function ($key) {
|
||||
return (string)$key;
|
||||
}, $configurations);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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\Log\Engine\FileLog://?types=notice,info,debug&file=debug&path=LOGS';
|
||||
* $config = Log::parseDsn($dsn);
|
||||
*
|
||||
* $dsn = 'smtp://user:secret@localhost:25?timeout=30&client=null&tls=null';
|
||||
* $config = Email::parseDsn($dsn);
|
||||
*
|
||||
* $dsn = 'file:///?className=\My\Cache\Engine\FileEngine';
|
||||
* $config = Cache::parseDsn($dsn);
|
||||
*
|
||||
* $dsn = 'File://?prefix=myapp_cake_core_&serialize=true&duration=+2 minutes&path=/tmp/persistent/';
|
||||
* $config = Cache::parseDsn($dsn);
|
||||
* ```
|
||||
*
|
||||
* For all classes, the value of `scheme` is set as the value of both the `className`
|
||||
* unless they have been otherwise specified.
|
||||
*
|
||||
* Note that querystring 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<string, mixed> The configuration array to be stored after parsing the DSN
|
||||
* @throws \InvalidArgumentException If not passed a string, or passed an invalid string
|
||||
*/
|
||||
public static function parseDsn(string $dsn): array
|
||||
{
|
||||
if (empty($dsn)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$pattern = <<<'REGEXP'
|
||||
{
|
||||
^
|
||||
(?P<_scheme>
|
||||
(?P<scheme>[\w\\\\]+)://
|
||||
)
|
||||
(?P<_username>
|
||||
(?P<username>.*?)
|
||||
(?P<_password>
|
||||
:(?P<password>.*?)
|
||||
)?
|
||||
@
|
||||
)?
|
||||
(?P<_host>
|
||||
(?P<host>[^?#/:@]+)
|
||||
(?P<_port>
|
||||
:(?P<port>\d+)
|
||||
)?
|
||||
)?
|
||||
(?P<_path>
|
||||
(?P<path>/[^?#]*)
|
||||
)?
|
||||
(?P<_query>
|
||||
\?(?P<query>[^#]*)
|
||||
)?
|
||||
(?P<_fragment>
|
||||
\#(?P<fragment>.*)
|
||||
)?
|
||||
$
|
||||
}x
|
||||
REGEXP;
|
||||
|
||||
preg_match($pattern, $dsn, $parsed);
|
||||
|
||||
if (!$parsed) {
|
||||
throw new InvalidArgumentException("The DSN string '{$dsn}' could not be parsed.");
|
||||
}
|
||||
|
||||
$exists = [];
|
||||
foreach ($parsed as $k => $v) {
|
||||
if (is_int($k)) {
|
||||
unset($parsed[$k]);
|
||||
} elseif (strpos($k, '_') === 0) {
|
||||
$exists[substr($k, 1)] = ($v !== '');
|
||||
unset($parsed[$k]);
|
||||
} elseif ($v === '' && !$exists[$k]) {
|
||||
unset($parsed[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
$query = '';
|
||||
|
||||
if (isset($parsed['query'])) {
|
||||
$query = $parsed['query'];
|
||||
unset($parsed['query']);
|
||||
}
|
||||
|
||||
parse_str($query, $queryArgs);
|
||||
|
||||
foreach ($queryArgs as $key => $value) {
|
||||
if ($value === 'true') {
|
||||
$queryArgs[$key] = true;
|
||||
} elseif ($value === 'false') {
|
||||
$queryArgs[$key] = false;
|
||||
} elseif ($value === 'null') {
|
||||
$queryArgs[$key] = null;
|
||||
}
|
||||
}
|
||||
|
||||
$parsed = $queryArgs + $parsed;
|
||||
|
||||
if (empty($parsed['className'])) {
|
||||
$classMap = static::getDsnClassMap();
|
||||
|
||||
$parsed['className'] = $parsed['scheme'];
|
||||
if (isset($classMap[$parsed['scheme']])) {
|
||||
/** @psalm-suppress PossiblyNullArrayOffset */
|
||||
$parsed['className'] = $classMap[$parsed['scheme']];
|
||||
}
|
||||
}
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the DSN class map for this class.
|
||||
*
|
||||
* @param array<string, string> $map Additions/edits to the class map to apply.
|
||||
* @return void
|
||||
* @psalm-param array<string, class-string> $map
|
||||
*/
|
||||
public static function setDsnClassMap(array $map): void
|
||||
{
|
||||
static::$_dsnClassMap = $map + static::$_dsnClassMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DSN class map for this class.
|
||||
*
|
||||
* @return array<string, string>
|
||||
* @psalm-return array<string, class-string>
|
||||
*/
|
||||
public static function getDsnClassMap(): array
|
||||
{
|
||||
return static::$_dsnClassMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
<?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)
|
||||
* @since 4.2.0
|
||||
* @license https://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Core\TestSuite;
|
||||
|
||||
use Cake\Core\Configure;
|
||||
use Cake\Core\ContainerInterface;
|
||||
use Cake\Event\EventInterface;
|
||||
use Closure;
|
||||
use League\Container\Exception\NotFoundException;
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* A set of methods used for defining container services
|
||||
* in test cases.
|
||||
*
|
||||
* This trait leverages the `Application.buildContainer` event
|
||||
* to inject the mocked services into the container that the
|
||||
* application uses.
|
||||
*/
|
||||
trait ContainerStubTrait
|
||||
{
|
||||
/**
|
||||
* The customized application class name.
|
||||
*
|
||||
* @psalm-var class-string<\Cake\Core\HttpApplicationInterface>|class-string<\Cake\Core\ConsoleApplicationInterface>|null
|
||||
* @var string|null
|
||||
*/
|
||||
protected $_appClass;
|
||||
|
||||
/**
|
||||
* The customized application constructor arguments.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
protected $_appArgs;
|
||||
|
||||
/**
|
||||
* The collection of container services.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $containerServices = [];
|
||||
|
||||
/**
|
||||
* Configure the application class to use in integration tests.
|
||||
*
|
||||
* @param string $class The application class name.
|
||||
* @param array|null $constructorArgs The constructor arguments for your application class.
|
||||
* @return void
|
||||
* @psalm-param class-string<\Cake\Core\HttpApplicationInterface>|class-string<\Cake\Core\ConsoleApplicationInterface> $class
|
||||
*/
|
||||
public function configApplication(string $class, ?array $constructorArgs): void
|
||||
{
|
||||
$this->_appClass = $class;
|
||||
$this->_appArgs = $constructorArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an application instance.
|
||||
*
|
||||
* Uses the configuration set in `configApplication()`.
|
||||
*
|
||||
* @return \Cake\Core\HttpApplicationInterface|\Cake\Core\ConsoleApplicationInterface
|
||||
*/
|
||||
protected function createApp()
|
||||
{
|
||||
if ($this->_appClass) {
|
||||
$appClass = $this->_appClass;
|
||||
} else {
|
||||
/** @psalm-var class-string<\Cake\Http\BaseApplication> */
|
||||
$appClass = Configure::read('App.namespace') . '\Application';
|
||||
}
|
||||
if (!class_exists($appClass)) {
|
||||
throw new LogicException("Cannot load `{$appClass}` for use in integration testing.");
|
||||
}
|
||||
$appArgs = $this->_appArgs ?: [CONFIG];
|
||||
|
||||
$app = new $appClass(...$appArgs);
|
||||
if (!empty($this->containerServices) && method_exists($app, 'getEventManager')) {
|
||||
$app->getEventManager()->on('Application.buildContainer', [$this, 'modifyContainer']);
|
||||
}
|
||||
|
||||
return $app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a mocked service to the container.
|
||||
*
|
||||
* When the container is created the provided classname
|
||||
* will be mapped to the factory function. The factory
|
||||
* function will be used to create mocked services.
|
||||
*
|
||||
* @param string $class The class or interface you want to define.
|
||||
* @param \Closure $factory The factory function for mocked services.
|
||||
* @return $this
|
||||
*/
|
||||
public function mockService(string $class, Closure $factory)
|
||||
{
|
||||
$this->containerServices[$class] = $factory;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a mocked service to the container.
|
||||
*
|
||||
* @param string $class The class or interface you want to remove.
|
||||
* @return $this
|
||||
*/
|
||||
public function removeMockService(string $class)
|
||||
{
|
||||
unset($this->containerServices[$class]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the application's container with one containing mocks.
|
||||
*
|
||||
* If any mocked services are defined, the application's container
|
||||
* will be replaced with one containing mocks. The original
|
||||
* container will be set as a delegate to the mock container.
|
||||
*
|
||||
* @param \Cake\Event\EventInterface $event The event
|
||||
* @param \Cake\Core\ContainerInterface $container The container to wrap.
|
||||
* @return \Cake\Core\ContainerInterface|null
|
||||
*/
|
||||
public function modifyContainer(EventInterface $event, ContainerInterface $container): ?ContainerInterface
|
||||
{
|
||||
if (empty($this->containerServices)) {
|
||||
return null;
|
||||
}
|
||||
foreach ($this->containerServices as $key => $factory) {
|
||||
if ($container->has($key)) {
|
||||
try {
|
||||
$container->extend($key)->setConcrete($factory);
|
||||
} catch (NotFoundException $e) {
|
||||
$container->add($key, $factory);
|
||||
}
|
||||
} else {
|
||||
$container->add($key, $factory);
|
||||
}
|
||||
}
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any mocks that were defined and cleans
|
||||
* up application class configuration.
|
||||
*
|
||||
* @after
|
||||
* @return void
|
||||
*/
|
||||
public function cleanupContainer(): void
|
||||
{
|
||||
$this->_appArgs = null;
|
||||
$this->_appClass = null;
|
||||
$this->containerServices = [];
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
class_alias(
|
||||
'Cake\Core\TestSuite\ContainerStubTrait',
|
||||
'Cake\TestSuite\ContainerStubTrait'
|
||||
);
|
||||
// phpcs:enable
|
||||
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "cakephp/core",
|
||||
"description": "CakePHP Framework Core classes",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"cakephp",
|
||||
"framework",
|
||||
"core"
|
||||
],
|
||||
"homepage": "https://cakephp.org",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "CakePHP Community",
|
||||
"homepage": "https://github.com/cakephp/core/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/core"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.4.0",
|
||||
"cakephp/utility": "^4.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/container-implementation": "^1.0 || ^2.0"
|
||||
},
|
||||
"suggest": {
|
||||
"cakephp/event": "To use PluginApplicationInterface or plugin applications.",
|
||||
"cakephp/cache": "To use Configure::store() and restore().",
|
||||
"league/container": "To use Container and ServiceProvider classes"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Cake\\Core\\": "."
|
||||
},
|
||||
"files": [
|
||||
"functions.php"
|
||||
]
|
||||
}
|
||||
}
|
||||
Vendored
+340
@@ -0,0 +1,340 @@
|
||||
<?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.5.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
namespace Cake\Core;
|
||||
|
||||
if (!function_exists('Cake\Core\h')) {
|
||||
/**
|
||||
* Convenience method for htmlspecialchars.
|
||||
*
|
||||
* @param mixed $text Text to wrap through htmlspecialchars. Also works with arrays, and objects.
|
||||
* Arrays will be mapped and have all their elements escaped. Objects will be string cast if they
|
||||
* implement a `__toString` method. Otherwise, the class name will be used.
|
||||
* Other scalar types will be returned unchanged.
|
||||
* @param bool $double Encode existing html entities.
|
||||
* @param string|null $charset Character set to use when escaping.
|
||||
* Defaults to config value in `mb_internal_encoding()` or 'UTF-8'.
|
||||
* @return mixed Wrapped text.
|
||||
* @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#h
|
||||
*/
|
||||
function h($text, bool $double = true, ?string $charset = null)
|
||||
{
|
||||
if (is_string($text)) {
|
||||
//optimize for strings
|
||||
} elseif (is_array($text)) {
|
||||
$texts = [];
|
||||
foreach ($text as $k => $t) {
|
||||
$texts[$k] = h($t, $double, $charset);
|
||||
}
|
||||
|
||||
return $texts;
|
||||
} elseif (is_object($text)) {
|
||||
if (method_exists($text, '__toString')) {
|
||||
$text = $text->__toString();
|
||||
} else {
|
||||
$text = '(object)' . get_class($text);
|
||||
}
|
||||
} elseif ($text === null || is_scalar($text)) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
static $defaultCharset = false;
|
||||
if ($defaultCharset === false) {
|
||||
$defaultCharset = mb_internal_encoding() ?: 'UTF-8';
|
||||
}
|
||||
|
||||
return htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, $charset ?: $defaultCharset, $double);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('Cake\Core\pluginSplit')) {
|
||||
/**
|
||||
* Splits a dot syntax plugin name into its plugin and class name.
|
||||
* If $name does not have a dot, then index 0 will be null.
|
||||
*
|
||||
* Commonly used like
|
||||
* ```
|
||||
* list($plugin, $name) = pluginSplit($name);
|
||||
* ```
|
||||
*
|
||||
* @param string $name The name you want to plugin split.
|
||||
* @param bool $dotAppend Set to true if you want the plugin to have a '.' appended to it.
|
||||
* @param string|null $plugin Optional default plugin to use if no plugin is found. Defaults to null.
|
||||
* @return array Array with 2 indexes. 0 => plugin name, 1 => class name.
|
||||
* @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#pluginSplit
|
||||
* @psalm-return array{string|null, string}
|
||||
*/
|
||||
function pluginSplit(string $name, bool $dotAppend = false, ?string $plugin = null): array
|
||||
{
|
||||
if (strpos($name, '.') !== false) {
|
||||
$parts = explode('.', $name, 2);
|
||||
if ($dotAppend) {
|
||||
$parts[0] .= '.';
|
||||
}
|
||||
|
||||
/** @psalm-var array{string, string} */
|
||||
return $parts;
|
||||
}
|
||||
|
||||
return [$plugin, $name];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('Cake\Core\namespaceSplit')) {
|
||||
/**
|
||||
* Split the namespace from the classname.
|
||||
*
|
||||
* Commonly used like `list($namespace, $className) = namespaceSplit($class);`.
|
||||
*
|
||||
* @param string $class The full class name, ie `Cake\Core\App`.
|
||||
* @return array<string> Array with 2 indexes. 0 => namespace, 1 => classname.
|
||||
*/
|
||||
function namespaceSplit(string $class): array
|
||||
{
|
||||
$pos = strrpos($class, '\\');
|
||||
if ($pos === false) {
|
||||
return ['', $class];
|
||||
}
|
||||
|
||||
return [substr($class, 0, $pos), substr($class, $pos + 1)];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('Cake\Core\pr')) {
|
||||
/**
|
||||
* print_r() convenience function.
|
||||
*
|
||||
* In terminals this will act similar to using print_r() directly, when not run on CLI
|
||||
* print_r() will also wrap `<pre>` tags around the output of given variable. Similar to debug().
|
||||
*
|
||||
* This function returns the same variable that was passed.
|
||||
*
|
||||
* @param mixed $var Variable to print out.
|
||||
* @return mixed the same $var that was passed to this function
|
||||
* @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#pr
|
||||
* @see debug()
|
||||
*/
|
||||
function pr($var)
|
||||
{
|
||||
if (!Configure::read('debug')) {
|
||||
return $var;
|
||||
}
|
||||
|
||||
$template = PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' ? '<pre class="pr">%s</pre>' : "\n%s\n\n";
|
||||
printf($template, trim(print_r($var, true)));
|
||||
|
||||
return $var;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('Cake\Core\pj')) {
|
||||
/**
|
||||
* JSON pretty print convenience function.
|
||||
*
|
||||
* In terminals this will act similar to using json_encode() with JSON_PRETTY_PRINT directly, when not run on CLI
|
||||
* will also wrap `<pre>` tags around the output of given variable. Similar to pr().
|
||||
*
|
||||
* This function returns the same variable that was passed.
|
||||
*
|
||||
* @param mixed $var Variable to print out.
|
||||
* @return mixed the same $var that was passed to this function
|
||||
* @see pr()
|
||||
* @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#pj
|
||||
*/
|
||||
function pj($var)
|
||||
{
|
||||
if (!Configure::read('debug')) {
|
||||
return $var;
|
||||
}
|
||||
|
||||
$template = PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' ? '<pre class="pj">%s</pre>' : "\n%s\n\n";
|
||||
printf($template, trim(json_encode($var, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)));
|
||||
|
||||
return $var;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('Cake\Core\env')) {
|
||||
/**
|
||||
* Gets an environment variable from available sources, and provides emulation
|
||||
* for unsupported or inconsistent environment variables (i.e. DOCUMENT_ROOT on
|
||||
* IIS, or SCRIPT_NAME in CGI mode). Also exposes some additional custom
|
||||
* environment information.
|
||||
*
|
||||
* @param string $key Environment variable name.
|
||||
* @param string|bool|null $default Specify a default value in case the environment variable is not defined.
|
||||
* @return string|bool|null Environment variable setting.
|
||||
* @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#env
|
||||
*/
|
||||
function env(string $key, $default = null)
|
||||
{
|
||||
if ($key === 'HTTPS') {
|
||||
if (isset($_SERVER['HTTPS'])) {
|
||||
return !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
|
||||
}
|
||||
|
||||
return strpos((string)env('SCRIPT_URI'), 'https://') === 0;
|
||||
}
|
||||
|
||||
if ($key === 'SCRIPT_NAME' && env('CGI_MODE') && isset($_ENV['SCRIPT_URL'])) {
|
||||
$key = 'SCRIPT_URL';
|
||||
}
|
||||
|
||||
/** @var string|null $val */
|
||||
$val = $_SERVER[$key] ?? $_ENV[$key] ?? null;
|
||||
if ($val == null && getenv($key) !== false) {
|
||||
/** @var string|false $val */
|
||||
$val = getenv($key);
|
||||
}
|
||||
|
||||
if ($key === 'REMOTE_ADDR' && $val === env('SERVER_ADDR')) {
|
||||
$addr = env('HTTP_PC_REMOTE_ADDR');
|
||||
if ($addr !== null) {
|
||||
$val = $addr;
|
||||
}
|
||||
}
|
||||
|
||||
if ($val !== null) {
|
||||
return $val;
|
||||
}
|
||||
|
||||
switch ($key) {
|
||||
case 'DOCUMENT_ROOT':
|
||||
$name = (string)env('SCRIPT_NAME');
|
||||
$filename = (string)env('SCRIPT_FILENAME');
|
||||
$offset = 0;
|
||||
if (!strpos($name, '.php')) {
|
||||
$offset = 4;
|
||||
}
|
||||
|
||||
return substr($filename, 0, -(strlen($name) + $offset));
|
||||
case 'PHP_SELF':
|
||||
return str_replace((string)env('DOCUMENT_ROOT'), '', (string)env('SCRIPT_FILENAME'));
|
||||
case 'CGI_MODE':
|
||||
return PHP_SAPI === 'cgi';
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('Cake\Core\triggerWarning')) {
|
||||
/**
|
||||
* Triggers an E_USER_WARNING.
|
||||
*
|
||||
* @param string $message The warning message.
|
||||
* @return void
|
||||
*/
|
||||
function triggerWarning(string $message): void
|
||||
{
|
||||
$trace = debug_backtrace();
|
||||
if (isset($trace[1])) {
|
||||
$frame = $trace[1];
|
||||
$frame += ['file' => '[internal]', 'line' => '??'];
|
||||
$message = sprintf(
|
||||
'%s - %s, line: %s',
|
||||
$message,
|
||||
$frame['file'],
|
||||
$frame['line']
|
||||
);
|
||||
}
|
||||
trigger_error($message, E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('Cake\Core\deprecationWarning')) {
|
||||
/**
|
||||
* Helper method for outputting deprecation warnings
|
||||
*
|
||||
* @param string $message The message to output as a deprecation warning.
|
||||
* @param int $stackFrame The stack frame to include in the error. Defaults to 1
|
||||
* as that should point to application/plugin code.
|
||||
* @return void
|
||||
*/
|
||||
function deprecationWarning(string $message, int $stackFrame = 1): void
|
||||
{
|
||||
if (!(error_reporting() & E_USER_DEPRECATED)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$trace = debug_backtrace();
|
||||
if (isset($trace[$stackFrame])) {
|
||||
$frame = $trace[$stackFrame];
|
||||
$frame += ['file' => '[internal]', 'line' => '??'];
|
||||
|
||||
// Assuming we're installed in vendor/cakephp/cakephp/src/Core/functions.php
|
||||
$root = dirname(__DIR__, 5);
|
||||
if (defined('ROOT')) {
|
||||
$root = ROOT;
|
||||
}
|
||||
$relative = str_replace(
|
||||
DIRECTORY_SEPARATOR,
|
||||
'/',
|
||||
substr($frame['file'], strlen($root) + 1)
|
||||
);
|
||||
$patterns = (array)Configure::read('Error.ignoredDeprecationPaths');
|
||||
foreach ($patterns as $pattern) {
|
||||
$pattern = str_replace(DIRECTORY_SEPARATOR, '/', $pattern);
|
||||
if (fnmatch($pattern, $relative)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$message = sprintf(
|
||||
"%s\n%s, line: %s\n" . 'You can disable all deprecation warnings by setting `Error.errorLevel` to ' .
|
||||
'`E_ALL & ~E_USER_DEPRECATED`. Adding `%s` to `Error.ignoredDeprecationPaths` ' .
|
||||
'in your `config/app.php` config will mute deprecations from that file only.',
|
||||
$message,
|
||||
$frame['file'],
|
||||
$frame['line'],
|
||||
$relative
|
||||
);
|
||||
}
|
||||
|
||||
static $errors = [];
|
||||
$checksum = md5($message);
|
||||
$duplicate = (bool)Configure::read('Error.allowDuplicateDeprecations', false);
|
||||
if (isset($errors[$checksum]) && !$duplicate) {
|
||||
return;
|
||||
}
|
||||
if (!$duplicate) {
|
||||
$errors[$checksum] = true;
|
||||
}
|
||||
|
||||
trigger_error($message, E_USER_DEPRECATED);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('Cake\Core\getTypeName')) {
|
||||
/**
|
||||
* Returns the objects class or var type of it's not an object
|
||||
*
|
||||
* @param mixed $var Variable to check
|
||||
* @return string Returns the class name or variable type
|
||||
*/
|
||||
function getTypeName($var): string
|
||||
{
|
||||
return is_object($var) ? get_class($var) : gettype($var);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Include global functions.
|
||||
*/
|
||||
if (!getenv('CAKE_DISABLE_GLOBAL_FUNCS')) {
|
||||
include 'functions_global.php';
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
<?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
|
||||
*/
|
||||
|
||||
use function Cake\Core\deprecationWarning as cakeDeprecationWarning;
|
||||
use function Cake\Core\env as cakeEnv;
|
||||
use function Cake\Core\getTypeName as cakeGetTypeName;
|
||||
use function Cake\Core\h as cakeH;
|
||||
use function Cake\Core\namespaceSplit as cakeNamespaceSplit;
|
||||
use function Cake\Core\pj as cakePj;
|
||||
use function Cake\Core\pluginSplit as cakePluginSplit;
|
||||
use function Cake\Core\pr as cakePr;
|
||||
use function Cake\Core\triggerWarning as cakeTriggerWarning;
|
||||
|
||||
if (!defined('DS')) {
|
||||
/**
|
||||
* Defines DS as short form of DIRECTORY_SEPARATOR.
|
||||
*/
|
||||
define('DS', DIRECTORY_SEPARATOR);
|
||||
}
|
||||
|
||||
if (!function_exists('h')) {
|
||||
/**
|
||||
* Convenience method for htmlspecialchars.
|
||||
*
|
||||
* @param mixed $text Text to wrap through htmlspecialchars. Also works with arrays, and objects.
|
||||
* Arrays will be mapped and have all their elements escaped. Objects will be string cast if they
|
||||
* implement a `__toString` method. Otherwise, the class name will be used.
|
||||
* Other scalar types will be returned unchanged.
|
||||
* @param bool $double Encode existing html entities.
|
||||
* @param string|null $charset Character set to use when escaping.
|
||||
* Defaults to config value in `mb_internal_encoding()` or 'UTF-8'.
|
||||
* @return mixed Wrapped text.
|
||||
* @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#h
|
||||
*/
|
||||
function h($text, bool $double = true, ?string $charset = null)
|
||||
{
|
||||
return cakeH($text, $double, $charset);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('pluginSplit')) {
|
||||
/**
|
||||
* Splits a dot syntax plugin name into its plugin and class name.
|
||||
* If $name does not have a dot, then index 0 will be null.
|
||||
*
|
||||
* Commonly used like
|
||||
* ```
|
||||
* list($plugin, $name) = pluginSplit($name);
|
||||
* ```
|
||||
*
|
||||
* @param string $name The name you want to plugin split.
|
||||
* @param bool $dotAppend Set to true if you want the plugin to have a '.' appended to it.
|
||||
* @param string|null $plugin Optional default plugin to use if no plugin is found. Defaults to null.
|
||||
* @return array Array with 2 indexes. 0 => plugin name, 1 => class name.
|
||||
* @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#pluginSplit
|
||||
* @psalm-return array{string|null, string}
|
||||
*/
|
||||
function pluginSplit(string $name, bool $dotAppend = false, ?string $plugin = null): array
|
||||
{
|
||||
return cakePluginSplit($name, $dotAppend, $plugin);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('namespaceSplit')) {
|
||||
/**
|
||||
* Split the namespace from the classname.
|
||||
*
|
||||
* Commonly used like `list($namespace, $className) = namespaceSplit($class);`.
|
||||
*
|
||||
* @param string $class The full class name, ie `Cake\Core\App`.
|
||||
* @return array<string> Array with 2 indexes. 0 => namespace, 1 => classname.
|
||||
*/
|
||||
function namespaceSplit(string $class): array
|
||||
{
|
||||
return cakeNamespaceSplit($class);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('pr')) {
|
||||
/**
|
||||
* print_r() convenience function.
|
||||
*
|
||||
* In terminals this will act similar to using print_r() directly, when not run on CLI
|
||||
* print_r() will also wrap `<pre>` tags around the output of given variable. Similar to debug().
|
||||
*
|
||||
* This function returns the same variable that was passed.
|
||||
*
|
||||
* @param mixed $var Variable to print out.
|
||||
* @return mixed the same $var that was passed to this function
|
||||
* @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#pr
|
||||
* @see debug()
|
||||
*/
|
||||
function pr($var)
|
||||
{
|
||||
return cakePr($var);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('pj')) {
|
||||
/**
|
||||
* JSON pretty print convenience function.
|
||||
*
|
||||
* In terminals this will act similar to using json_encode() with JSON_PRETTY_PRINT directly, when not run on CLI
|
||||
* will also wrap `<pre>` tags around the output of given variable. Similar to pr().
|
||||
*
|
||||
* This function returns the same variable that was passed.
|
||||
*
|
||||
* @param mixed $var Variable to print out.
|
||||
* @return mixed the same $var that was passed to this function
|
||||
* @see pr()
|
||||
* @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#pj
|
||||
*/
|
||||
function pj($var)
|
||||
{
|
||||
return cakePj($var);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('env')) {
|
||||
/**
|
||||
* Gets an environment variable from available sources, and provides emulation
|
||||
* for unsupported or inconsistent environment variables (i.e. DOCUMENT_ROOT on
|
||||
* IIS, or SCRIPT_NAME in CGI mode). Also exposes some additional custom
|
||||
* environment information.
|
||||
*
|
||||
* @param string $key Environment variable name.
|
||||
* @param string|bool|null $default Specify a default value in case the environment variable is not defined.
|
||||
* @return string|bool|null Environment variable setting.
|
||||
* @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#env
|
||||
*/
|
||||
function env(string $key, $default = null)
|
||||
{
|
||||
return cakeEnv($key, $default);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('triggerWarning')) {
|
||||
/**
|
||||
* Triggers an E_USER_WARNING.
|
||||
*
|
||||
* @param string $message The warning message.
|
||||
* @return void
|
||||
*/
|
||||
function triggerWarning(string $message): void
|
||||
{
|
||||
cakeTriggerWarning($message);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('deprecationWarning')) {
|
||||
/**
|
||||
* Helper method for outputting deprecation warnings
|
||||
*
|
||||
* @param string $message The message to output as a deprecation warning.
|
||||
* @param int $stackFrame The stack frame to include in the error. Defaults to 1
|
||||
* as that should point to application/plugin code.
|
||||
* @return void
|
||||
*/
|
||||
function deprecationWarning(string $message, int $stackFrame = 1): void
|
||||
{
|
||||
cakeDeprecationWarning($message, $stackFrame + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('getTypeName')) {
|
||||
/**
|
||||
* Returns the objects class or var type of it's not an object
|
||||
*
|
||||
* @param mixed $var Variable to check
|
||||
* @return string Returns the class name or variable type
|
||||
*/
|
||||
function getTypeName($var): string
|
||||
{
|
||||
return cakeGetTypeName($var);
|
||||
}
|
||||
}
|
||||
+1197
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
||||
<?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.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database;
|
||||
|
||||
use Cake\Datasource\ConnectionInterface;
|
||||
|
||||
/**
|
||||
* Defines the interface for a fixture that needs to manage constraints.
|
||||
*
|
||||
* If an implementation of `Cake\Datasource\FixtureInterface` also implements
|
||||
* this interface, the FixtureManager will use these methods to manage
|
||||
* a fixtures constraints.
|
||||
*/
|
||||
interface ConstraintsInterface
|
||||
{
|
||||
/**
|
||||
* Build and execute SQL queries necessary to create the constraints for the
|
||||
* fixture
|
||||
*
|
||||
* @param \Cake\Datasource\ConnectionInterface $connection An instance of the database
|
||||
* into which the constraints will be created.
|
||||
* @return bool on success or if there are no constraints to create, or false on failure
|
||||
*/
|
||||
public function createConstraints(ConnectionInterface $connection): bool;
|
||||
|
||||
/**
|
||||
* Build and execute SQL queries necessary to drop the constraints for the
|
||||
* fixture
|
||||
*
|
||||
* @param \Cake\Datasource\ConnectionInterface $connection An instance of the database
|
||||
* into which the constraints will be dropped.
|
||||
* @return bool on success or if there are no constraints to drop, or false on failure
|
||||
*/
|
||||
public function dropConstraints(ConnectionInterface $connection): bool;
|
||||
}
|
||||
Vendored
+559
@@ -0,0 +1,559 @@
|
||||
<?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\Database;
|
||||
|
||||
use Cake\Core\App;
|
||||
use Cake\Core\Retry\CommandRetry;
|
||||
use Cake\Database\Exception\MissingConnectionException;
|
||||
use Cake\Database\Retry\ErrorCodeWaitStrategy;
|
||||
use Cake\Database\Schema\SchemaDialect;
|
||||
use Cake\Database\Schema\TableSchema;
|
||||
use Cake\Database\Statement\PDOStatement;
|
||||
use Closure;
|
||||
use InvalidArgumentException;
|
||||
use PDO;
|
||||
use PDOException;
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
/**
|
||||
* Represents a database driver containing all specificities for
|
||||
* a database engine including its SQL dialect.
|
||||
*/
|
||||
abstract class Driver implements DriverInterface
|
||||
{
|
||||
/**
|
||||
* @var int|null Maximum alias length or null if no limit
|
||||
*/
|
||||
protected const MAX_ALIAS_LENGTH = null;
|
||||
|
||||
/**
|
||||
* @var array<int> DB-specific error codes that allow connect retry
|
||||
*/
|
||||
protected const RETRY_ERROR_CODES = [];
|
||||
|
||||
/**
|
||||
* Instance of PDO.
|
||||
*
|
||||
* @var \PDO
|
||||
*/
|
||||
protected $_connection;
|
||||
|
||||
/**
|
||||
* Configuration data.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected $_config;
|
||||
|
||||
/**
|
||||
* Base configuration that is merged into the user
|
||||
* supplied configuration data.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected $_baseConfig = [];
|
||||
|
||||
/**
|
||||
* Indicates whether the driver is doing automatic identifier quoting
|
||||
* for all queries
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_autoQuoting = false;
|
||||
|
||||
/**
|
||||
* The server version
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $_version;
|
||||
|
||||
/**
|
||||
* The last number of connection retry attempts.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $connectRetries = 0;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array<string, mixed> $config The configuration for the driver.
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
if (empty($config['username']) && !empty($config['login'])) {
|
||||
throw new InvalidArgumentException(
|
||||
'Please pass "username" instead of "login" for connecting to the database'
|
||||
);
|
||||
}
|
||||
$config += $this->_baseConfig;
|
||||
$this->_config = $config;
|
||||
if (!empty($config['quoteIdentifiers'])) {
|
||||
$this->enableAutoQuoting();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configuration data used to create the driver.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function config(): array
|
||||
{
|
||||
return $this->_config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes a connection to the database server
|
||||
*
|
||||
* @param string $dsn A Driver-specific PDO-DSN
|
||||
* @param array<string, mixed> $config configuration to be used for creating connection
|
||||
* @return bool true on success
|
||||
*/
|
||||
protected function _connect(string $dsn, array $config): bool
|
||||
{
|
||||
$action = function () use ($dsn, $config) {
|
||||
$this->setConnection(new PDO(
|
||||
$dsn,
|
||||
$config['username'] ?: null,
|
||||
$config['password'] ?: null,
|
||||
$config['flags']
|
||||
));
|
||||
};
|
||||
|
||||
$retry = new CommandRetry(new ErrorCodeWaitStrategy(static::RETRY_ERROR_CODES, 5), 4);
|
||||
try {
|
||||
$retry->run($action);
|
||||
} catch (PDOException $e) {
|
||||
throw new MissingConnectionException(
|
||||
[
|
||||
'driver' => App::shortName(static::class, 'Database/Driver'),
|
||||
'reason' => $e->getMessage(),
|
||||
],
|
||||
null,
|
||||
$e
|
||||
);
|
||||
} finally {
|
||||
$this->connectRetries = $retry->getRetries();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
abstract public function connect(): bool;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function disconnect(): void
|
||||
{
|
||||
/** @psalm-suppress PossiblyNullPropertyAssignmentValue */
|
||||
$this->_connection = null;
|
||||
$this->_version = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns connected server version.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function version(): string
|
||||
{
|
||||
if ($this->_version === null) {
|
||||
$this->connect();
|
||||
$this->_version = (string)$this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
|
||||
}
|
||||
|
||||
return $this->_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internal PDO connection instance.
|
||||
*
|
||||
* @return \PDO
|
||||
*/
|
||||
public function getConnection()
|
||||
{
|
||||
if ($this->_connection === null) {
|
||||
throw new MissingConnectionException([
|
||||
'driver' => App::shortName(static::class, 'Database/Driver'),
|
||||
'reason' => 'Unknown',
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->_connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the internal PDO connection instance.
|
||||
*
|
||||
* @param \PDO $connection PDO instance.
|
||||
* @return $this
|
||||
* @psalm-suppress MoreSpecificImplementedParamType
|
||||
*/
|
||||
public function setConnection($connection)
|
||||
{
|
||||
$this->_connection = $connection;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
abstract public function enabled(): bool;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function prepare($query): StatementInterface
|
||||
{
|
||||
$this->connect();
|
||||
$statement = $this->_connection->prepare($query instanceof Query ? $query->sql() : $query);
|
||||
|
||||
return new PDOStatement($statement, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function beginTransaction(): bool
|
||||
{
|
||||
$this->connect();
|
||||
if ($this->_connection->inTransaction()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->_connection->beginTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function commitTransaction(): bool
|
||||
{
|
||||
$this->connect();
|
||||
if (!$this->_connection->inTransaction()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->_connection->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function rollbackTransaction(): bool
|
||||
{
|
||||
$this->connect();
|
||||
if (!$this->_connection->inTransaction()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->_connection->rollBack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a transaction is active for connection.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function inTransaction(): bool
|
||||
{
|
||||
$this->connect();
|
||||
|
||||
return $this->_connection->inTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function supportsSavePoints(): bool
|
||||
{
|
||||
deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.');
|
||||
|
||||
return $this->supports(static::FEATURE_SAVEPOINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the server supports common table expressions.
|
||||
*
|
||||
* @return bool
|
||||
* @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_QUOTE)` instead
|
||||
*/
|
||||
public function supportsCTEs(): bool
|
||||
{
|
||||
deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.');
|
||||
|
||||
return $this->supports(static::FEATURE_CTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function quote($value, $type = PDO::PARAM_STR): string
|
||||
{
|
||||
$this->connect();
|
||||
|
||||
return $this->_connection->quote((string)$value, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the driver supports quoting, as PDO_ODBC does not support it.
|
||||
*
|
||||
* @return bool
|
||||
* @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_QUOTE)` instead
|
||||
*/
|
||||
public function supportsQuoting(): bool
|
||||
{
|
||||
deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.');
|
||||
|
||||
return $this->supports(static::FEATURE_QUOTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
abstract public function queryTranslator(string $type): Closure;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
abstract public function schemaDialect(): SchemaDialect;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
abstract public function quoteIdentifier(string $identifier): string;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function schemaValue($value): string
|
||||
{
|
||||
if ($value === null) {
|
||||
return 'NULL';
|
||||
}
|
||||
if ($value === false) {
|
||||
return 'FALSE';
|
||||
}
|
||||
if ($value === true) {
|
||||
return 'TRUE';
|
||||
}
|
||||
if (is_float($value)) {
|
||||
return str_replace(',', '.', (string)$value);
|
||||
}
|
||||
/** @psalm-suppress InvalidArgument */
|
||||
if (
|
||||
(
|
||||
is_int($value) ||
|
||||
$value === '0'
|
||||
) ||
|
||||
(
|
||||
is_numeric($value) &&
|
||||
strpos($value, ',') === false &&
|
||||
substr($value, 0, 1) !== '0' &&
|
||||
strpos($value, 'e') === false
|
||||
)
|
||||
) {
|
||||
return (string)$value;
|
||||
}
|
||||
|
||||
return $this->_connection->quote((string)$value, PDO::PARAM_STR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function schema(): string
|
||||
{
|
||||
return $this->_config['schema'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function lastInsertId(?string $table = null, ?string $column = null)
|
||||
{
|
||||
$this->connect();
|
||||
|
||||
if ($this->_connection instanceof PDO) {
|
||||
return $this->_connection->lastInsertId($table);
|
||||
}
|
||||
|
||||
return $this->_connection->lastInsertId($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function isConnected(): bool
|
||||
{
|
||||
if ($this->_connection === null) {
|
||||
$connected = false;
|
||||
} else {
|
||||
try {
|
||||
$connected = (bool)$this->_connection->query('SELECT 1');
|
||||
} catch (PDOException $e) {
|
||||
$connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function enableAutoQuoting(bool $enable = true)
|
||||
{
|
||||
$this->_autoQuoting = $enable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function disableAutoQuoting()
|
||||
{
|
||||
$this->_autoQuoting = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function isAutoQuotingEnabled(): bool
|
||||
{
|
||||
return $this->_autoQuoting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the driver supports the feature.
|
||||
*
|
||||
* Defaults to true for FEATURE_QUOTE and FEATURE_SAVEPOINT.
|
||||
*
|
||||
* @param string $feature Driver feature name
|
||||
* @return bool
|
||||
*/
|
||||
public function supports(string $feature): bool
|
||||
{
|
||||
switch ($feature) {
|
||||
case static::FEATURE_DISABLE_CONSTRAINT_WITHOUT_TRANSACTION:
|
||||
case static::FEATURE_QUOTE:
|
||||
case static::FEATURE_SAVEPOINT:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function compileQuery(Query $query, ValueBinder $binder): array
|
||||
{
|
||||
$processor = $this->newCompiler();
|
||||
$translator = $this->queryTranslator($query->type());
|
||||
$query = $translator($query);
|
||||
|
||||
return [$query, $processor->compile($query, $binder)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function newCompiler(): QueryCompiler
|
||||
{
|
||||
return new QueryCompiler();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function newTableSchema(string $table, array $columns = []): TableSchema
|
||||
{
|
||||
$className = TableSchema::class;
|
||||
if (isset($this->_config['tableSchema'])) {
|
||||
/** @var class-string<\Cake\Database\Schema\TableSchema> $className */
|
||||
$className = $this->_config['tableSchema'];
|
||||
}
|
||||
|
||||
return new $className($table, $columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum alias length allowed.
|
||||
* This can be different from the maximum identifier length for columns.
|
||||
*
|
||||
* @return int|null Maximum alias length or null if no limit
|
||||
*/
|
||||
public function getMaxAliasLength(): ?int
|
||||
{
|
||||
return static::MAX_ALIAS_LENGTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of connection retry attempts made.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getConnectRetries(): int
|
||||
{
|
||||
return $this->connectRetries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection role this driver performs.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRole(): string
|
||||
{
|
||||
return $this->_config['_role'] ?? Connection::ROLE_WRITE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
/** @psalm-suppress PossiblyNullPropertyAssignmentValue */
|
||||
$this->_connection = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array that can be used to describe the internal state of this
|
||||
* object.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
return [
|
||||
'connected' => $this->_connection !== null,
|
||||
'role' => $this->getRole(),
|
||||
];
|
||||
}
|
||||
}
|
||||
+345
@@ -0,0 +1,345 @@
|
||||
<?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\Database\Driver;
|
||||
|
||||
use Cake\Database\Driver;
|
||||
use Cake\Database\Query;
|
||||
use Cake\Database\Schema\MysqlSchemaDialect;
|
||||
use Cake\Database\Schema\SchemaDialect;
|
||||
use Cake\Database\Statement\MysqlStatement;
|
||||
use Cake\Database\StatementInterface;
|
||||
use PDO;
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
/**
|
||||
* MySQL Driver
|
||||
*/
|
||||
class Mysql extends Driver
|
||||
{
|
||||
use SqlDialectTrait;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected const MAX_ALIAS_LENGTH = 256;
|
||||
|
||||
/**
|
||||
* Server type MySQL
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected const SERVER_TYPE_MYSQL = 'mysql';
|
||||
|
||||
/**
|
||||
* Server type MariaDB
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected const SERVER_TYPE_MARIADB = 'mariadb';
|
||||
|
||||
/**
|
||||
* Base configuration settings for MySQL driver
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected $_baseConfig = [
|
||||
'persistent' => true,
|
||||
'host' => 'localhost',
|
||||
'username' => 'root',
|
||||
'password' => '',
|
||||
'database' => 'cake',
|
||||
'port' => '3306',
|
||||
'flags' => [],
|
||||
'encoding' => 'utf8mb4',
|
||||
'timezone' => null,
|
||||
'init' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* The schema dialect for this driver
|
||||
*
|
||||
* @var \Cake\Database\Schema\MysqlSchemaDialect|null
|
||||
*/
|
||||
protected $_schemaDialect;
|
||||
|
||||
/**
|
||||
* String used to start a database identifier quoting to make it safe
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_startQuote = '`';
|
||||
|
||||
/**
|
||||
* String used to end a database identifier quoting to make it safe
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_endQuote = '`';
|
||||
|
||||
/**
|
||||
* Server type.
|
||||
*
|
||||
* If the underlying server is MariaDB, its value will get set to `'mariadb'`
|
||||
* after `version()` method is called.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $serverType = self::SERVER_TYPE_MYSQL;
|
||||
|
||||
/**
|
||||
* Mapping of feature to db server version for feature availability checks.
|
||||
*
|
||||
* @var array<string, array<string, string>>
|
||||
*/
|
||||
protected $featureVersions = [
|
||||
'mysql' => [
|
||||
'json' => '5.7.0',
|
||||
'cte' => '8.0.0',
|
||||
'window' => '8.0.0',
|
||||
],
|
||||
'mariadb' => [
|
||||
'json' => '10.2.7',
|
||||
'cte' => '10.2.1',
|
||||
'window' => '10.2.0',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Establishes a connection to the database server
|
||||
*
|
||||
* @return bool true on success
|
||||
*/
|
||||
public function connect(): bool
|
||||
{
|
||||
if ($this->_connection) {
|
||||
return true;
|
||||
}
|
||||
$config = $this->_config;
|
||||
|
||||
if ($config['timezone'] === 'UTC') {
|
||||
$config['timezone'] = '+0:00';
|
||||
}
|
||||
|
||||
if (!empty($config['timezone'])) {
|
||||
$config['init'][] = sprintf("SET time_zone = '%s'", $config['timezone']);
|
||||
}
|
||||
|
||||
$config['flags'] += [
|
||||
PDO::ATTR_PERSISTENT => $config['persistent'],
|
||||
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
];
|
||||
|
||||
if (!empty($config['ssl_key']) && !empty($config['ssl_cert'])) {
|
||||
$config['flags'][PDO::MYSQL_ATTR_SSL_KEY] = $config['ssl_key'];
|
||||
$config['flags'][PDO::MYSQL_ATTR_SSL_CERT] = $config['ssl_cert'];
|
||||
}
|
||||
if (!empty($config['ssl_ca'])) {
|
||||
$config['flags'][PDO::MYSQL_ATTR_SSL_CA] = $config['ssl_ca'];
|
||||
}
|
||||
|
||||
if (empty($config['unix_socket'])) {
|
||||
$dsn = "mysql:host={$config['host']};port={$config['port']};dbname={$config['database']}";
|
||||
} else {
|
||||
$dsn = "mysql:unix_socket={$config['unix_socket']};dbname={$config['database']}";
|
||||
}
|
||||
|
||||
if (!empty($config['encoding'])) {
|
||||
$dsn .= ";charset={$config['encoding']}";
|
||||
}
|
||||
|
||||
$this->_connect($dsn, $config);
|
||||
|
||||
if (!empty($config['init'])) {
|
||||
$connection = $this->getConnection();
|
||||
foreach ((array)$config['init'] as $command) {
|
||||
$connection->exec($command);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether php is able to use this driver for connecting to database
|
||||
*
|
||||
* @return bool true if it is valid to use this driver
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
return in_array('mysql', PDO::getAvailableDrivers(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a sql statement to be executed
|
||||
*
|
||||
* @param \Cake\Database\Query|string $query The query to prepare.
|
||||
* @return \Cake\Database\StatementInterface
|
||||
*/
|
||||
public function prepare($query): StatementInterface
|
||||
{
|
||||
$this->connect();
|
||||
$isObject = $query instanceof Query;
|
||||
/**
|
||||
* @psalm-suppress PossiblyInvalidMethodCall
|
||||
* @psalm-suppress PossiblyInvalidArgument
|
||||
*/
|
||||
$statement = $this->_connection->prepare($isObject ? $query->sql() : $query);
|
||||
$result = new MysqlStatement($statement, $this);
|
||||
/** @psalm-suppress PossiblyInvalidMethodCall */
|
||||
if ($isObject && $query->isBufferedResultsEnabled() === false) {
|
||||
$result->bufferResults(false);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function schemaDialect(): SchemaDialect
|
||||
{
|
||||
if ($this->_schemaDialect === null) {
|
||||
$this->_schemaDialect = new MysqlSchemaDialect($this);
|
||||
}
|
||||
|
||||
return $this->_schemaDialect;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function schema(): string
|
||||
{
|
||||
return $this->_config['database'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function disableForeignKeySQL(): string
|
||||
{
|
||||
return 'SET foreign_key_checks = 0';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function enableForeignKeySQL(): string
|
||||
{
|
||||
return 'SET foreign_key_checks = 1';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function supports(string $feature): bool
|
||||
{
|
||||
switch ($feature) {
|
||||
case static::FEATURE_CTE:
|
||||
case static::FEATURE_JSON:
|
||||
case static::FEATURE_WINDOW:
|
||||
return version_compare(
|
||||
$this->version(),
|
||||
$this->featureVersions[$this->serverType][$feature],
|
||||
'>='
|
||||
);
|
||||
}
|
||||
|
||||
return parent::supports($feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function supportsDynamicConstraints(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the connected server is MariaDB.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isMariadb(): bool
|
||||
{
|
||||
$this->version();
|
||||
|
||||
return $this->serverType === static::SERVER_TYPE_MARIADB;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns connected server version.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function version(): string
|
||||
{
|
||||
if ($this->_version === null) {
|
||||
$this->connect();
|
||||
$this->_version = (string)$this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
|
||||
|
||||
if (strpos($this->_version, 'MariaDB') !== false) {
|
||||
$this->serverType = static::SERVER_TYPE_MARIADB;
|
||||
preg_match('/^(?:5\.5\.5-)?(\d+\.\d+\.\d+.*-MariaDB[^:]*)/', $this->_version, $matches);
|
||||
$this->_version = $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the server supports common table expressions.
|
||||
*
|
||||
* @return bool
|
||||
* @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_CTE)` instead
|
||||
*/
|
||||
public function supportsCTEs(): bool
|
||||
{
|
||||
deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.');
|
||||
|
||||
return $this->supports(static::FEATURE_CTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the server supports native JSON columns
|
||||
*
|
||||
* @return bool
|
||||
* @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_JSON)` instead
|
||||
*/
|
||||
public function supportsNativeJson(): bool
|
||||
{
|
||||
deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.');
|
||||
|
||||
return $this->supports(static::FEATURE_JSON);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the connected server supports window functions.
|
||||
*
|
||||
* @return bool
|
||||
* @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_WINDOW)` instead
|
||||
*/
|
||||
public function supportsWindowFunctions(): bool
|
||||
{
|
||||
deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.');
|
||||
|
||||
return $this->supports(static::FEATURE_WINDOW);
|
||||
}
|
||||
}
|
||||
+348
@@ -0,0 +1,348 @@
|
||||
<?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\Database\Driver;
|
||||
|
||||
use Cake\Database\Driver;
|
||||
use Cake\Database\Expression\FunctionExpression;
|
||||
use Cake\Database\Expression\IdentifierExpression;
|
||||
use Cake\Database\Expression\StringExpression;
|
||||
use Cake\Database\PostgresCompiler;
|
||||
use Cake\Database\Query;
|
||||
use Cake\Database\QueryCompiler;
|
||||
use Cake\Database\Schema\PostgresSchemaDialect;
|
||||
use Cake\Database\Schema\SchemaDialect;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* Class Postgres
|
||||
*/
|
||||
class Postgres extends Driver
|
||||
{
|
||||
use SqlDialectTrait;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected const MAX_ALIAS_LENGTH = 63;
|
||||
|
||||
/**
|
||||
* Base configuration settings for Postgres driver
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected $_baseConfig = [
|
||||
'persistent' => true,
|
||||
'host' => 'localhost',
|
||||
'username' => 'root',
|
||||
'password' => '',
|
||||
'database' => 'cake',
|
||||
'schema' => 'public',
|
||||
'port' => 5432,
|
||||
'encoding' => 'utf8',
|
||||
'timezone' => null,
|
||||
'flags' => [],
|
||||
'init' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* The schema dialect class for this driver
|
||||
*
|
||||
* @var \Cake\Database\Schema\PostgresSchemaDialect|null
|
||||
*/
|
||||
protected $_schemaDialect;
|
||||
|
||||
/**
|
||||
* String used to start a database identifier quoting to make it safe
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_startQuote = '"';
|
||||
|
||||
/**
|
||||
* String used to end a database identifier quoting to make it safe
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_endQuote = '"';
|
||||
|
||||
/**
|
||||
* Establishes a connection to the database server
|
||||
*
|
||||
* @return bool true on success
|
||||
*/
|
||||
public function connect(): bool
|
||||
{
|
||||
if ($this->_connection) {
|
||||
return true;
|
||||
}
|
||||
$config = $this->_config;
|
||||
$config['flags'] += [
|
||||
PDO::ATTR_PERSISTENT => $config['persistent'],
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
];
|
||||
if (empty($config['unix_socket'])) {
|
||||
$dsn = "pgsql:host={$config['host']};port={$config['port']};dbname={$config['database']}";
|
||||
} else {
|
||||
$dsn = "pgsql:dbname={$config['database']}";
|
||||
}
|
||||
|
||||
$this->_connect($dsn, $config);
|
||||
$this->_connection = $connection = $this->getConnection();
|
||||
if (!empty($config['encoding'])) {
|
||||
$this->setEncoding($config['encoding']);
|
||||
}
|
||||
|
||||
if (!empty($config['schema'])) {
|
||||
$this->setSchema($config['schema']);
|
||||
}
|
||||
|
||||
if (!empty($config['timezone'])) {
|
||||
$config['init'][] = sprintf('SET timezone = %s', $connection->quote($config['timezone']));
|
||||
}
|
||||
|
||||
foreach ($config['init'] as $command) {
|
||||
$connection->exec($command);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether php is able to use this driver for connecting to database
|
||||
*
|
||||
* @return bool true if it is valid to use this driver
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
return in_array('pgsql', PDO::getAvailableDrivers(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function schemaDialect(): SchemaDialect
|
||||
{
|
||||
if ($this->_schemaDialect === null) {
|
||||
$this->_schemaDialect = new PostgresSchemaDialect($this);
|
||||
}
|
||||
|
||||
return $this->_schemaDialect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets connection encoding
|
||||
*
|
||||
* @param string $encoding The encoding to use.
|
||||
* @return void
|
||||
*/
|
||||
public function setEncoding(string $encoding): void
|
||||
{
|
||||
$this->connect();
|
||||
$this->_connection->exec('SET NAMES ' . $this->_connection->quote($encoding));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets connection default schema, if any relation defined in a query is not fully qualified
|
||||
* postgres will fallback to looking the relation into defined default schema
|
||||
*
|
||||
* @param string $schema The schema names to set `search_path` to.
|
||||
* @return void
|
||||
*/
|
||||
public function setSchema(string $schema): void
|
||||
{
|
||||
$this->connect();
|
||||
$this->_connection->exec('SET search_path TO ' . $this->_connection->quote($schema));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function disableForeignKeySQL(): string
|
||||
{
|
||||
return 'SET CONSTRAINTS ALL DEFERRED';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function enableForeignKeySQL(): string
|
||||
{
|
||||
return 'SET CONSTRAINTS ALL IMMEDIATE';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function supports(string $feature): bool
|
||||
{
|
||||
switch ($feature) {
|
||||
case static::FEATURE_CTE:
|
||||
case static::FEATURE_JSON:
|
||||
case static::FEATURE_TRUNCATE_WITH_CONSTRAINTS:
|
||||
case static::FEATURE_WINDOW:
|
||||
return true;
|
||||
|
||||
case static::FEATURE_DISABLE_CONSTRAINT_WITHOUT_TRANSACTION:
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::supports($feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function supportsDynamicConstraints(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function _transformDistinct(Query $query): Query
|
||||
{
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function _insertQueryTranslator(Query $query): Query
|
||||
{
|
||||
if (!$query->clause('epilog')) {
|
||||
$query->epilog('RETURNING *');
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function _expressionTranslators(): array
|
||||
{
|
||||
return [
|
||||
IdentifierExpression::class => '_transformIdentifierExpression',
|
||||
FunctionExpression::class => '_transformFunctionExpression',
|
||||
StringExpression::class => '_transformStringExpression',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes identifer expression into postgresql format.
|
||||
*
|
||||
* @param \Cake\Database\Expression\IdentifierExpression $expression The expression to tranform.
|
||||
* @return void
|
||||
*/
|
||||
protected function _transformIdentifierExpression(IdentifierExpression $expression): void
|
||||
{
|
||||
$collation = $expression->getCollation();
|
||||
if ($collation) {
|
||||
// use trim() to work around expression being transformed multiple times
|
||||
$expression->setCollation('"' . trim($collation, '"') . '"');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives a FunctionExpression and changes it so that it conforms to this
|
||||
* SQL dialect.
|
||||
*
|
||||
* @param \Cake\Database\Expression\FunctionExpression $expression The function expression to convert
|
||||
* to postgres SQL.
|
||||
* @return void
|
||||
*/
|
||||
protected function _transformFunctionExpression(FunctionExpression $expression): void
|
||||
{
|
||||
switch ($expression->getName()) {
|
||||
case 'CONCAT':
|
||||
// CONCAT function is expressed as exp1 || exp2
|
||||
$expression->setName('')->setConjunction(' ||');
|
||||
break;
|
||||
case 'DATEDIFF':
|
||||
$expression
|
||||
->setName('')
|
||||
->setConjunction('-')
|
||||
->iterateParts(function ($p) {
|
||||
if (is_string($p)) {
|
||||
$p = ['value' => [$p => 'literal'], 'type' => null];
|
||||
} else {
|
||||
$p['value'] = [$p['value']];
|
||||
}
|
||||
|
||||
return new FunctionExpression('DATE', $p['value'], [$p['type']]);
|
||||
});
|
||||
break;
|
||||
case 'CURRENT_DATE':
|
||||
$time = new FunctionExpression('LOCALTIMESTAMP', [' 0 ' => 'literal']);
|
||||
$expression->setName('CAST')->setConjunction(' AS ')->add([$time, 'date' => 'literal']);
|
||||
break;
|
||||
case 'CURRENT_TIME':
|
||||
$time = new FunctionExpression('LOCALTIMESTAMP', [' 0 ' => 'literal']);
|
||||
$expression->setName('CAST')->setConjunction(' AS ')->add([$time, 'time' => 'literal']);
|
||||
break;
|
||||
case 'NOW':
|
||||
$expression->setName('LOCALTIMESTAMP')->add([' 0 ' => 'literal']);
|
||||
break;
|
||||
case 'RAND':
|
||||
$expression->setName('RANDOM');
|
||||
break;
|
||||
case 'DATE_ADD':
|
||||
$expression
|
||||
->setName('')
|
||||
->setConjunction(' + INTERVAL')
|
||||
->iterateParts(function ($p, $key) {
|
||||
if ($key === 1) {
|
||||
$p = sprintf("'%s'", $p);
|
||||
}
|
||||
|
||||
return $p;
|
||||
});
|
||||
break;
|
||||
case 'DAYOFWEEK':
|
||||
$expression
|
||||
->setName('EXTRACT')
|
||||
->setConjunction(' ')
|
||||
->add(['DOW FROM' => 'literal'], [], true)
|
||||
->add([') + (1' => 'literal']); // Postgres starts on index 0 but Sunday should be 1
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes string expression into postgresql format.
|
||||
*
|
||||
* @param \Cake\Database\Expression\StringExpression $expression The string expression to tranform.
|
||||
* @return void
|
||||
*/
|
||||
protected function _transformStringExpression(StringExpression $expression): void
|
||||
{
|
||||
// use trim() to work around expression being transformed multiple times
|
||||
$expression->setCollation('"' . trim($expression->getCollation(), '"') . '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return \Cake\Database\PostgresCompiler
|
||||
*/
|
||||
public function newCompiler(): QueryCompiler
|
||||
{
|
||||
return new PostgresCompiler();
|
||||
}
|
||||
}
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
<?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\Database\Driver;
|
||||
|
||||
use Cake\Database\Expression\ComparisonExpression;
|
||||
use Cake\Database\Expression\IdentifierExpression;
|
||||
use Cake\Database\IdentifierQuoter;
|
||||
use Cake\Database\Query;
|
||||
use Closure;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Sql dialect trait
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait SqlDialectTrait
|
||||
{
|
||||
/**
|
||||
* Quotes a database identifier (a column name, table name, etc..) to
|
||||
* be used safely in queries without the risk of using reserved words
|
||||
*
|
||||
* @param string $identifier The identifier to quote.
|
||||
* @return string
|
||||
*/
|
||||
public function quoteIdentifier(string $identifier): string
|
||||
{
|
||||
$identifier = trim($identifier);
|
||||
|
||||
if ($identifier === '*' || $identifier === '') {
|
||||
return $identifier;
|
||||
}
|
||||
|
||||
// string
|
||||
if (preg_match('/^[\w-]+$/u', $identifier)) {
|
||||
return $this->_startQuote . $identifier . $this->_endQuote;
|
||||
}
|
||||
|
||||
// string.string
|
||||
if (preg_match('/^[\w-]+\.[^ \*]*$/u', $identifier)) {
|
||||
$items = explode('.', $identifier);
|
||||
|
||||
return $this->_startQuote . implode($this->_endQuote . '.' . $this->_startQuote, $items) . $this->_endQuote;
|
||||
}
|
||||
|
||||
// string.*
|
||||
if (preg_match('/^[\w-]+\.\*$/u', $identifier)) {
|
||||
return $this->_startQuote . str_replace('.*', $this->_endQuote . '.*', $identifier);
|
||||
}
|
||||
|
||||
// Functions
|
||||
if (preg_match('/^([\w-]+)\((.*)\)$/', $identifier, $matches)) {
|
||||
return $matches[1] . '(' . $this->quoteIdentifier($matches[2]) . ')';
|
||||
}
|
||||
|
||||
// Alias.field AS thing
|
||||
if (preg_match('/^([\w-]+(\.[\w\s-]+|\(.*\))*)\s+AS\s*([\w-]+)$/ui', $identifier, $matches)) {
|
||||
return $this->quoteIdentifier($matches[1]) . ' AS ' . $this->quoteIdentifier($matches[3]);
|
||||
}
|
||||
|
||||
// string.string with spaces
|
||||
if (preg_match('/^([\w-]+\.[\w][\w\s-]*[\w])(.*)/u', $identifier, $matches)) {
|
||||
$items = explode('.', $matches[1]);
|
||||
$field = implode($this->_endQuote . '.' . $this->_startQuote, $items);
|
||||
|
||||
return $this->_startQuote . $field . $this->_endQuote . $matches[2];
|
||||
}
|
||||
|
||||
if (preg_match('/^[\w\s-]*[\w-]+/u', $identifier)) {
|
||||
return $this->_startQuote . $identifier . $this->_endQuote;
|
||||
}
|
||||
|
||||
return $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a callable function that will be used to transform a passed Query object.
|
||||
* This function, in turn, will return an instance of a Query object that has been
|
||||
* transformed to accommodate any specificities of the SQL dialect in use.
|
||||
*
|
||||
* @param string $type the type of query to be transformed
|
||||
* (select, insert, update, delete)
|
||||
* @return \Closure
|
||||
*/
|
||||
public function queryTranslator(string $type): Closure
|
||||
{
|
||||
return function ($query) use ($type) {
|
||||
if ($this->isAutoQuotingEnabled()) {
|
||||
$query = (new IdentifierQuoter($this))->quote($query);
|
||||
}
|
||||
|
||||
/** @var \Cake\ORM\Query $query */
|
||||
$query = $this->{'_' . $type . 'QueryTranslator'}($query);
|
||||
$translators = $this->_expressionTranslators();
|
||||
if (!$translators) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
$query->traverseExpressions(function ($expression) use ($translators, $query): void {
|
||||
foreach ($translators as $class => $method) {
|
||||
if ($expression instanceof $class) {
|
||||
$this->{$method}($expression, $query);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return $query;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an associative array of methods that will transform Expression
|
||||
* objects to conform with the specific SQL dialect. Keys are class names
|
||||
* and values a method in this class.
|
||||
*
|
||||
* @psalm-return array<class-string, string>
|
||||
* @return array<string>
|
||||
*/
|
||||
protected function _expressionTranslators(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply translation steps to select queries.
|
||||
*
|
||||
* @param \Cake\Database\Query $query The query to translate
|
||||
* @return \Cake\Database\Query The modified query
|
||||
*/
|
||||
protected function _selectQueryTranslator(Query $query): Query
|
||||
{
|
||||
return $this->_transformDistinct($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the passed query after rewriting the DISTINCT clause, so that drivers
|
||||
* that do not support the "ON" part can provide the actual way it should be done
|
||||
*
|
||||
* @param \Cake\Database\Query $query The query to be transformed
|
||||
* @return \Cake\Database\Query
|
||||
*/
|
||||
protected function _transformDistinct(Query $query): Query
|
||||
{
|
||||
if (is_array($query->clause('distinct'))) {
|
||||
$query->group($query->clause('distinct'), true);
|
||||
$query->distinct(false);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply translation steps to delete queries.
|
||||
*
|
||||
* Chops out aliases on delete query conditions as most database dialects do not
|
||||
* support aliases in delete queries. This also removes aliases
|
||||
* in table names as they frequently don't work either.
|
||||
*
|
||||
* We are intentionally not supporting deletes with joins as they have even poorer support.
|
||||
*
|
||||
* @param \Cake\Database\Query $query The query to translate
|
||||
* @return \Cake\Database\Query The modified query
|
||||
*/
|
||||
protected function _deleteQueryTranslator(Query $query): Query
|
||||
{
|
||||
$hadAlias = false;
|
||||
$tables = [];
|
||||
foreach ($query->clause('from') as $alias => $table) {
|
||||
if (is_string($alias)) {
|
||||
$hadAlias = true;
|
||||
}
|
||||
$tables[] = $table;
|
||||
}
|
||||
if ($hadAlias) {
|
||||
$query->from($tables, true);
|
||||
}
|
||||
|
||||
if (!$hadAlias) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
return $this->_removeAliasesFromConditions($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply translation steps to update queries.
|
||||
*
|
||||
* Chops out aliases on update query conditions as not all database dialects do support
|
||||
* aliases in update queries.
|
||||
*
|
||||
* Just like for delete queries, joins are currently not supported for update queries.
|
||||
*
|
||||
* @param \Cake\Database\Query $query The query to translate
|
||||
* @return \Cake\Database\Query The modified query
|
||||
*/
|
||||
protected function _updateQueryTranslator(Query $query): Query
|
||||
{
|
||||
return $this->_removeAliasesFromConditions($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes aliases from the `WHERE` clause of a query.
|
||||
*
|
||||
* @param \Cake\Database\Query $query The query to process.
|
||||
* @return \Cake\Database\Query The modified query.
|
||||
* @throws \RuntimeException In case the processed query contains any joins, as removing
|
||||
* aliases from the conditions can break references to the joined tables.
|
||||
*/
|
||||
protected function _removeAliasesFromConditions(Query $query): Query
|
||||
{
|
||||
if ($query->clause('join')) {
|
||||
throw new RuntimeException(
|
||||
'Aliases are being removed from conditions for UPDATE/DELETE queries, ' .
|
||||
'this can break references to joined tables.'
|
||||
);
|
||||
}
|
||||
|
||||
$conditions = $query->clause('where');
|
||||
if ($conditions) {
|
||||
$conditions->traverse(function ($expression) {
|
||||
if ($expression instanceof ComparisonExpression) {
|
||||
$field = $expression->getField();
|
||||
if (
|
||||
is_string($field) &&
|
||||
strpos($field, '.') !== false
|
||||
) {
|
||||
[, $unaliasedField] = explode('.', $field, 2);
|
||||
$expression->setField($unaliasedField);
|
||||
}
|
||||
|
||||
return $expression;
|
||||
}
|
||||
|
||||
if ($expression instanceof IdentifierExpression) {
|
||||
$identifier = $expression->getIdentifier();
|
||||
if (strpos($identifier, '.') !== false) {
|
||||
[, $unaliasedIdentifier] = explode('.', $identifier, 2);
|
||||
$expression->setIdentifier($unaliasedIdentifier);
|
||||
}
|
||||
|
||||
return $expression;
|
||||
}
|
||||
|
||||
return $expression;
|
||||
});
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply translation steps to insert queries.
|
||||
*
|
||||
* @param \Cake\Database\Query $query The query to translate
|
||||
* @return \Cake\Database\Query The modified query
|
||||
*/
|
||||
protected function _insertQueryTranslator(Query $query): Query
|
||||
{
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a SQL snippet for creating a new transaction savepoint
|
||||
*
|
||||
* @param string|int $name save point name
|
||||
* @return string
|
||||
*/
|
||||
public function savePointSQL($name): string
|
||||
{
|
||||
return 'SAVEPOINT LEVEL' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a SQL snippet for releasing a previously created save point
|
||||
*
|
||||
* @param string|int $name save point name
|
||||
* @return string
|
||||
*/
|
||||
public function releaseSavePointSQL($name): string
|
||||
{
|
||||
return 'RELEASE SAVEPOINT LEVEL' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a SQL snippet for rollbacking a previously created save point
|
||||
*
|
||||
* @param string|int $name save point name
|
||||
* @return string
|
||||
*/
|
||||
public function rollbackSavePointSQL($name): string
|
||||
{
|
||||
return 'ROLLBACK TO SAVEPOINT LEVEL' . $name;
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
class_alias(
|
||||
'Cake\Database\Driver\SqlDialectTrait',
|
||||
'Cake\Database\SqlDialectTrait'
|
||||
);
|
||||
// phpcs:enable
|
||||
+385
@@ -0,0 +1,385 @@
|
||||
<?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\Database\Driver;
|
||||
|
||||
use Cake\Database\Driver;
|
||||
use Cake\Database\Expression\FunctionExpression;
|
||||
use Cake\Database\Expression\TupleComparison;
|
||||
use Cake\Database\Query;
|
||||
use Cake\Database\QueryCompiler;
|
||||
use Cake\Database\Schema\SchemaDialect;
|
||||
use Cake\Database\Schema\SqliteSchemaDialect;
|
||||
use Cake\Database\SqliteCompiler;
|
||||
use Cake\Database\Statement\PDOStatement;
|
||||
use Cake\Database\Statement\SqliteStatement;
|
||||
use Cake\Database\StatementInterface;
|
||||
use InvalidArgumentException;
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
/**
|
||||
* Class Sqlite
|
||||
*/
|
||||
class Sqlite extends Driver
|
||||
{
|
||||
use SqlDialectTrait;
|
||||
use TupleComparisonTranslatorTrait;
|
||||
|
||||
/**
|
||||
* Base configuration settings for Sqlite driver
|
||||
*
|
||||
* - `mask` The mask used for created database
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected $_baseConfig = [
|
||||
'persistent' => false,
|
||||
'username' => null,
|
||||
'password' => null,
|
||||
'database' => ':memory:',
|
||||
'encoding' => 'utf8',
|
||||
'mask' => 0644,
|
||||
'cache' => null,
|
||||
'mode' => null,
|
||||
'flags' => [],
|
||||
'init' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* The schema dialect class for this driver
|
||||
*
|
||||
* @var \Cake\Database\Schema\SqliteSchemaDialect|null
|
||||
*/
|
||||
protected $_schemaDialect;
|
||||
|
||||
/**
|
||||
* Whether the connected server supports window functions.
|
||||
*
|
||||
* @var bool|null
|
||||
*/
|
||||
protected $_supportsWindowFunctions;
|
||||
|
||||
/**
|
||||
* String used to start a database identifier quoting to make it safe
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_startQuote = '"';
|
||||
|
||||
/**
|
||||
* String used to end a database identifier quoting to make it safe
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_endQuote = '"';
|
||||
|
||||
/**
|
||||
* Mapping of date parts.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $_dateParts = [
|
||||
'day' => 'd',
|
||||
'hour' => 'H',
|
||||
'month' => 'm',
|
||||
'minute' => 'M',
|
||||
'second' => 'S',
|
||||
'week' => 'W',
|
||||
'year' => 'Y',
|
||||
];
|
||||
|
||||
/**
|
||||
* Mapping of feature to db server version for feature availability checks.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $featureVersions = [
|
||||
'cte' => '3.8.3',
|
||||
'window' => '3.28.0',
|
||||
];
|
||||
|
||||
/**
|
||||
* Establishes a connection to the database server
|
||||
*
|
||||
* @return bool true on success
|
||||
*/
|
||||
public function connect(): bool
|
||||
{
|
||||
if ($this->_connection) {
|
||||
return true;
|
||||
}
|
||||
$config = $this->_config;
|
||||
$config['flags'] += [
|
||||
PDO::ATTR_PERSISTENT => $config['persistent'],
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
];
|
||||
if (!is_string($config['database']) || $config['database'] === '') {
|
||||
$name = $config['name'] ?? 'unknown';
|
||||
throw new InvalidArgumentException(
|
||||
"The `database` key for the `{$name}` SQLite connection needs to be a non-empty string."
|
||||
);
|
||||
}
|
||||
|
||||
$chmodFile = false;
|
||||
if ($config['database'] !== ':memory:' && $config['mode'] !== 'memory') {
|
||||
$chmodFile = !file_exists($config['database']);
|
||||
}
|
||||
|
||||
$params = [];
|
||||
if ($config['cache']) {
|
||||
$params[] = 'cache=' . $config['cache'];
|
||||
}
|
||||
if ($config['mode']) {
|
||||
$params[] = 'mode=' . $config['mode'];
|
||||
}
|
||||
|
||||
if ($params) {
|
||||
if (PHP_VERSION_ID < 80100) {
|
||||
throw new RuntimeException('SQLite URI support requires PHP 8.1.');
|
||||
}
|
||||
$dsn = 'sqlite:file:' . $config['database'] . '?' . implode('&', $params);
|
||||
} else {
|
||||
$dsn = 'sqlite:' . $config['database'];
|
||||
}
|
||||
|
||||
$this->_connect($dsn, $config);
|
||||
if ($chmodFile) {
|
||||
// phpcs:disable
|
||||
@chmod($config['database'], $config['mask']);
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
if (!empty($config['init'])) {
|
||||
foreach ((array)$config['init'] as $command) {
|
||||
$this->getConnection()->exec($command);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether php is able to use this driver for connecting to database
|
||||
*
|
||||
* @return bool true if it is valid to use this driver
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
return in_array('sqlite', PDO::getAvailableDrivers(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a sql statement to be executed
|
||||
*
|
||||
* @param \Cake\Database\Query|string $query The query to prepare.
|
||||
* @return \Cake\Database\StatementInterface
|
||||
*/
|
||||
public function prepare($query): StatementInterface
|
||||
{
|
||||
$this->connect();
|
||||
$isObject = $query instanceof Query;
|
||||
/**
|
||||
* @psalm-suppress PossiblyInvalidMethodCall
|
||||
* @psalm-suppress PossiblyInvalidArgument
|
||||
*/
|
||||
$statement = $this->_connection->prepare($isObject ? $query->sql() : $query);
|
||||
$result = new SqliteStatement(new PDOStatement($statement, $this), $this);
|
||||
/** @psalm-suppress PossiblyInvalidMethodCall */
|
||||
if ($isObject && $query->isBufferedResultsEnabled() === false) {
|
||||
$result->bufferResults(false);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function disableForeignKeySQL(): string
|
||||
{
|
||||
return 'PRAGMA foreign_keys = OFF';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function enableForeignKeySQL(): string
|
||||
{
|
||||
return 'PRAGMA foreign_keys = ON';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function supports(string $feature): bool
|
||||
{
|
||||
switch ($feature) {
|
||||
case static::FEATURE_CTE:
|
||||
case static::FEATURE_WINDOW:
|
||||
return version_compare(
|
||||
$this->version(),
|
||||
$this->featureVersions[$feature],
|
||||
'>='
|
||||
);
|
||||
|
||||
case static::FEATURE_TRUNCATE_WITH_CONSTRAINTS:
|
||||
return true;
|
||||
}
|
||||
|
||||
return parent::supports($feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function supportsDynamicConstraints(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function schemaDialect(): SchemaDialect
|
||||
{
|
||||
if ($this->_schemaDialect === null) {
|
||||
$this->_schemaDialect = new SqliteSchemaDialect($this);
|
||||
}
|
||||
|
||||
return $this->_schemaDialect;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function newCompiler(): QueryCompiler
|
||||
{
|
||||
return new SqliteCompiler();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function _expressionTranslators(): array
|
||||
{
|
||||
return [
|
||||
FunctionExpression::class => '_transformFunctionExpression',
|
||||
TupleComparison::class => '_transformTupleComparison',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives a FunctionExpression and changes it so that it conforms to this
|
||||
* SQL dialect.
|
||||
*
|
||||
* @param \Cake\Database\Expression\FunctionExpression $expression The function expression to convert to TSQL.
|
||||
* @return void
|
||||
*/
|
||||
protected function _transformFunctionExpression(FunctionExpression $expression): void
|
||||
{
|
||||
switch ($expression->getName()) {
|
||||
case 'CONCAT':
|
||||
// CONCAT function is expressed as exp1 || exp2
|
||||
$expression->setName('')->setConjunction(' ||');
|
||||
break;
|
||||
case 'DATEDIFF':
|
||||
$expression
|
||||
->setName('ROUND')
|
||||
->setConjunction('-')
|
||||
->iterateParts(function ($p) {
|
||||
return new FunctionExpression('JULIANDAY', [$p['value']], [$p['type']]);
|
||||
});
|
||||
break;
|
||||
case 'NOW':
|
||||
$expression->setName('DATETIME')->add(["'now'" => 'literal']);
|
||||
break;
|
||||
case 'RAND':
|
||||
$expression
|
||||
->setName('ABS')
|
||||
->add(['RANDOM() % 1' => 'literal'], [], true);
|
||||
break;
|
||||
case 'CURRENT_DATE':
|
||||
$expression->setName('DATE')->add(["'now'" => 'literal']);
|
||||
break;
|
||||
case 'CURRENT_TIME':
|
||||
$expression->setName('TIME')->add(["'now'" => 'literal']);
|
||||
break;
|
||||
case 'EXTRACT':
|
||||
$expression
|
||||
->setName('STRFTIME')
|
||||
->setConjunction(' ,')
|
||||
->iterateParts(function ($p, $key) {
|
||||
if ($key === 0) {
|
||||
$value = rtrim(strtolower($p), 's');
|
||||
if (isset($this->_dateParts[$value])) {
|
||||
$p = ['value' => '%' . $this->_dateParts[$value], 'type' => null];
|
||||
}
|
||||
}
|
||||
|
||||
return $p;
|
||||
});
|
||||
break;
|
||||
case 'DATE_ADD':
|
||||
$expression
|
||||
->setName('DATE')
|
||||
->setConjunction(',')
|
||||
->iterateParts(function ($p, $key) {
|
||||
if ($key === 1) {
|
||||
$p = ['value' => $p, 'type' => null];
|
||||
}
|
||||
|
||||
return $p;
|
||||
});
|
||||
break;
|
||||
case 'DAYOFWEEK':
|
||||
$expression
|
||||
->setName('STRFTIME')
|
||||
->setConjunction(' ')
|
||||
->add(["'%w', " => 'literal'], [], true)
|
||||
->add([') + (1' => 'literal']); // Sqlite starts on index 0 but Sunday should be 1
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the server supports common table expressions.
|
||||
*
|
||||
* @return bool
|
||||
* @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_CTE)` instead
|
||||
*/
|
||||
public function supportsCTEs(): bool
|
||||
{
|
||||
deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.');
|
||||
|
||||
return $this->supports(static::FEATURE_CTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the connected server supports window functions.
|
||||
*
|
||||
* @return bool
|
||||
* @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_WINDOW)` instead
|
||||
*/
|
||||
public function supportsWindowFunctions(): bool
|
||||
{
|
||||
deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.');
|
||||
|
||||
return $this->supports(static::FEATURE_WINDOW);
|
||||
}
|
||||
}
|
||||
+569
@@ -0,0 +1,569 @@
|
||||
<?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\Database\Driver;
|
||||
|
||||
use Cake\Database\Driver;
|
||||
use Cake\Database\Expression\FunctionExpression;
|
||||
use Cake\Database\Expression\OrderByExpression;
|
||||
use Cake\Database\Expression\OrderClauseExpression;
|
||||
use Cake\Database\Expression\TupleComparison;
|
||||
use Cake\Database\Expression\UnaryExpression;
|
||||
use Cake\Database\ExpressionInterface;
|
||||
use Cake\Database\Query;
|
||||
use Cake\Database\QueryCompiler;
|
||||
use Cake\Database\Schema\SchemaDialect;
|
||||
use Cake\Database\Schema\SqlserverSchemaDialect;
|
||||
use Cake\Database\SqlserverCompiler;
|
||||
use Cake\Database\Statement\SqlserverStatement;
|
||||
use Cake\Database\StatementInterface;
|
||||
use InvalidArgumentException;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* SQLServer driver.
|
||||
*/
|
||||
class Sqlserver extends Driver
|
||||
{
|
||||
use SqlDialectTrait;
|
||||
use TupleComparisonTranslatorTrait;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected const MAX_ALIAS_LENGTH = 128;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected const RETRY_ERROR_CODES = [
|
||||
40613, // Azure Sql Database paused
|
||||
];
|
||||
|
||||
/**
|
||||
* Base configuration settings for Sqlserver driver
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected $_baseConfig = [
|
||||
'host' => 'localhost\SQLEXPRESS',
|
||||
'username' => '',
|
||||
'password' => '',
|
||||
'database' => 'cake',
|
||||
'port' => '',
|
||||
// PDO::SQLSRV_ENCODING_UTF8
|
||||
'encoding' => 65001,
|
||||
'flags' => [],
|
||||
'init' => [],
|
||||
'settings' => [],
|
||||
'attributes' => [],
|
||||
'app' => null,
|
||||
'connectionPooling' => null,
|
||||
'failoverPartner' => null,
|
||||
'loginTimeout' => null,
|
||||
'multiSubnetFailover' => null,
|
||||
'encrypt' => null,
|
||||
'trustServerCertificate' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* The schema dialect class for this driver
|
||||
*
|
||||
* @var \Cake\Database\Schema\SqlserverSchemaDialect|null
|
||||
*/
|
||||
protected $_schemaDialect;
|
||||
|
||||
/**
|
||||
* String used to start a database identifier quoting to make it safe
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_startQuote = '[';
|
||||
|
||||
/**
|
||||
* String used to end a database identifier quoting to make it safe
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_endQuote = ']';
|
||||
|
||||
/**
|
||||
* Establishes a connection to the database server.
|
||||
*
|
||||
* Please note that the PDO::ATTR_PERSISTENT attribute is not supported by
|
||||
* the SQL Server PHP PDO drivers. As a result you cannot use the
|
||||
* persistent config option when connecting to a SQL Server (for more
|
||||
* information see: https://github.com/Microsoft/msphpsql/issues/65).
|
||||
*
|
||||
* @throws \InvalidArgumentException if an unsupported setting is in the driver config
|
||||
* @return bool true on success
|
||||
*/
|
||||
public function connect(): bool
|
||||
{
|
||||
if ($this->_connection) {
|
||||
return true;
|
||||
}
|
||||
$config = $this->_config;
|
||||
|
||||
if (isset($config['persistent']) && $config['persistent']) {
|
||||
throw new InvalidArgumentException(
|
||||
'Config setting "persistent" cannot be set to true, '
|
||||
. 'as the Sqlserver PDO driver does not support PDO::ATTR_PERSISTENT'
|
||||
);
|
||||
}
|
||||
|
||||
$config['flags'] += [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
];
|
||||
|
||||
if (!empty($config['encoding'])) {
|
||||
$config['flags'][PDO::SQLSRV_ATTR_ENCODING] = $config['encoding'];
|
||||
}
|
||||
$port = '';
|
||||
if ($config['port']) {
|
||||
$port = ',' . $config['port'];
|
||||
}
|
||||
|
||||
$dsn = "sqlsrv:Server={$config['host']}{$port};Database={$config['database']};MultipleActiveResultSets=false";
|
||||
if ($config['app'] !== null) {
|
||||
$dsn .= ";APP={$config['app']}";
|
||||
}
|
||||
if ($config['connectionPooling'] !== null) {
|
||||
$dsn .= ";ConnectionPooling={$config['connectionPooling']}";
|
||||
}
|
||||
if ($config['failoverPartner'] !== null) {
|
||||
$dsn .= ";Failover_Partner={$config['failoverPartner']}";
|
||||
}
|
||||
if ($config['loginTimeout'] !== null) {
|
||||
$dsn .= ";LoginTimeout={$config['loginTimeout']}";
|
||||
}
|
||||
if ($config['multiSubnetFailover'] !== null) {
|
||||
$dsn .= ";MultiSubnetFailover={$config['multiSubnetFailover']}";
|
||||
}
|
||||
if ($config['encrypt'] !== null) {
|
||||
$dsn .= ";Encrypt={$config['encrypt']}";
|
||||
}
|
||||
if ($config['trustServerCertificate'] !== null) {
|
||||
$dsn .= ";TrustServerCertificate={$config['trustServerCertificate']}";
|
||||
}
|
||||
$this->_connect($dsn, $config);
|
||||
|
||||
$connection = $this->getConnection();
|
||||
if (!empty($config['init'])) {
|
||||
foreach ((array)$config['init'] as $command) {
|
||||
$connection->exec($command);
|
||||
}
|
||||
}
|
||||
if (!empty($config['settings']) && is_array($config['settings'])) {
|
||||
foreach ($config['settings'] as $key => $value) {
|
||||
$connection->exec("SET {$key} {$value}");
|
||||
}
|
||||
}
|
||||
if (!empty($config['attributes']) && is_array($config['attributes'])) {
|
||||
foreach ($config['attributes'] as $key => $value) {
|
||||
$connection->setAttribute($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether PHP is able to use this driver for connecting to database
|
||||
*
|
||||
* @return bool true if it is valid to use this driver
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
return in_array('sqlsrv', PDO::getAvailableDrivers(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a sql statement to be executed
|
||||
*
|
||||
* @param \Cake\Database\Query|string $query The query to prepare.
|
||||
* @return \Cake\Database\StatementInterface
|
||||
*/
|
||||
public function prepare($query): StatementInterface
|
||||
{
|
||||
$this->connect();
|
||||
|
||||
$sql = $query;
|
||||
$options = [
|
||||
PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL,
|
||||
PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED,
|
||||
];
|
||||
if ($query instanceof Query) {
|
||||
$sql = $query->sql();
|
||||
if (count($query->getValueBinder()->bindings()) > 2100) {
|
||||
throw new InvalidArgumentException(
|
||||
'Exceeded maximum number of parameters (2100) for prepared statements in Sql Server. ' .
|
||||
'This is probably due to a very large WHERE IN () clause which generates a parameter ' .
|
||||
'for each value in the array. ' .
|
||||
'If using an Association, try changing the `strategy` from select to subquery.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!$query->isBufferedResultsEnabled()) {
|
||||
$options = [];
|
||||
}
|
||||
}
|
||||
|
||||
/** @psalm-suppress PossiblyInvalidArgument */
|
||||
$statement = $this->_connection->prepare($sql, $options);
|
||||
|
||||
return new SqlserverStatement($statement, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function savePointSQL($name): string
|
||||
{
|
||||
return 'SAVE TRANSACTION t' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function releaseSavePointSQL($name): string
|
||||
{
|
||||
// SQLServer has no release save point operation.
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function rollbackSavePointSQL($name): string
|
||||
{
|
||||
return 'ROLLBACK TRANSACTION t' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function disableForeignKeySQL(): string
|
||||
{
|
||||
return 'EXEC sp_MSforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT all"';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function enableForeignKeySQL(): string
|
||||
{
|
||||
return 'EXEC sp_MSforeachtable "ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all"';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function supports(string $feature): bool
|
||||
{
|
||||
switch ($feature) {
|
||||
case static::FEATURE_CTE:
|
||||
case static::FEATURE_TRUNCATE_WITH_CONSTRAINTS:
|
||||
case static::FEATURE_WINDOW:
|
||||
return true;
|
||||
|
||||
case static::FEATURE_QUOTE:
|
||||
$this->connect();
|
||||
|
||||
return $this->_connection->getAttribute(PDO::ATTR_DRIVER_NAME) !== 'odbc';
|
||||
}
|
||||
|
||||
return parent::supports($feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function supportsDynamicConstraints(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function schemaDialect(): SchemaDialect
|
||||
{
|
||||
if ($this->_schemaDialect === null) {
|
||||
$this->_schemaDialect = new SqlserverSchemaDialect($this);
|
||||
}
|
||||
|
||||
return $this->_schemaDialect;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return \Cake\Database\SqlserverCompiler
|
||||
*/
|
||||
public function newCompiler(): QueryCompiler
|
||||
{
|
||||
return new SqlserverCompiler();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function _selectQueryTranslator(Query $query): Query
|
||||
{
|
||||
$limit = $query->clause('limit');
|
||||
$offset = $query->clause('offset');
|
||||
|
||||
if ($limit && $offset === null) {
|
||||
$query->modifier(['_auto_top_' => sprintf('TOP %d', $limit)]);
|
||||
}
|
||||
|
||||
if ($offset !== null && !$query->clause('order')) {
|
||||
$query->order($query->newExpr()->add('(SELECT NULL)'));
|
||||
}
|
||||
|
||||
if ($this->version() < 11 && $offset !== null) {
|
||||
return $this->_pagingSubquery($query, $limit, $offset);
|
||||
}
|
||||
|
||||
return $this->_transformDistinct($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a paging subquery for older versions of SQLserver.
|
||||
*
|
||||
* Prior to SQLServer 2012 there was no equivalent to LIMIT OFFSET, so a subquery must
|
||||
* be used.
|
||||
*
|
||||
* @param \Cake\Database\Query $original The query to wrap in a subquery.
|
||||
* @param int|null $limit The number of rows to fetch.
|
||||
* @param int|null $offset The number of rows to offset.
|
||||
* @return \Cake\Database\Query Modified query object.
|
||||
*/
|
||||
protected function _pagingSubquery(Query $original, ?int $limit, ?int $offset): Query
|
||||
{
|
||||
$field = '_cake_paging_._cake_page_rownum_';
|
||||
|
||||
if ($original->clause('order')) {
|
||||
// SQL server does not support column aliases in OVER clauses. But
|
||||
// the only practical way to specify the use of calculated columns
|
||||
// is with their alias. So substitute the select SQL in place of
|
||||
// any column aliases for those entries in the order clause.
|
||||
$select = $original->clause('select');
|
||||
$order = new OrderByExpression();
|
||||
$original
|
||||
->clause('order')
|
||||
->iterateParts(function ($direction, $orderBy) use ($select, $order) {
|
||||
$key = $orderBy;
|
||||
if (
|
||||
isset($select[$orderBy]) &&
|
||||
$select[$orderBy] instanceof ExpressionInterface
|
||||
) {
|
||||
$order->add(new OrderClauseExpression($select[$orderBy], $direction));
|
||||
} else {
|
||||
$order->add([$key => $direction]);
|
||||
}
|
||||
|
||||
// Leave original order clause unchanged.
|
||||
return $orderBy;
|
||||
});
|
||||
} else {
|
||||
$order = new OrderByExpression('(SELECT NULL)');
|
||||
}
|
||||
|
||||
$query = clone $original;
|
||||
$query->select([
|
||||
'_cake_page_rownum_' => new UnaryExpression('ROW_NUMBER() OVER', $order),
|
||||
])->limit(null)
|
||||
->offset(null)
|
||||
->order([], true);
|
||||
|
||||
$outer = new Query($query->getConnection());
|
||||
$outer->select('*')
|
||||
->from(['_cake_paging_' => $query]);
|
||||
|
||||
if ($offset) {
|
||||
$outer->where(["$field > " . $offset]);
|
||||
}
|
||||
if ($limit) {
|
||||
$value = (int)$offset + $limit;
|
||||
$outer->where(["$field <= $value"]);
|
||||
}
|
||||
|
||||
// Decorate the original query as that is what the
|
||||
// end developer will be calling execute() on originally.
|
||||
$original->decorateResults(function ($row) {
|
||||
if (isset($row['_cake_page_rownum_'])) {
|
||||
unset($row['_cake_page_rownum_']);
|
||||
}
|
||||
|
||||
return $row;
|
||||
});
|
||||
|
||||
return $outer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function _transformDistinct(Query $query): Query
|
||||
{
|
||||
if (!is_array($query->clause('distinct'))) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
$original = $query;
|
||||
$query = clone $original;
|
||||
|
||||
$distinct = $query->clause('distinct');
|
||||
$query->distinct(false);
|
||||
|
||||
$order = new OrderByExpression($distinct);
|
||||
$query
|
||||
->select(function ($q) use ($distinct, $order) {
|
||||
$over = $q->newExpr('ROW_NUMBER() OVER')
|
||||
->add('(PARTITION BY')
|
||||
->add($q->newExpr()->add($distinct)->setConjunction(','))
|
||||
->add($order)
|
||||
->add(')')
|
||||
->setConjunction(' ');
|
||||
|
||||
return [
|
||||
'_cake_distinct_pivot_' => $over,
|
||||
];
|
||||
})
|
||||
->limit(null)
|
||||
->offset(null)
|
||||
->order([], true);
|
||||
|
||||
$outer = new Query($query->getConnection());
|
||||
$outer->select('*')
|
||||
->from(['_cake_distinct_' => $query])
|
||||
->where(['_cake_distinct_pivot_' => 1]);
|
||||
|
||||
// Decorate the original query as that is what the
|
||||
// end developer will be calling execute() on originally.
|
||||
$original->decorateResults(function ($row) {
|
||||
if (isset($row['_cake_distinct_pivot_'])) {
|
||||
unset($row['_cake_distinct_pivot_']);
|
||||
}
|
||||
|
||||
return $row;
|
||||
});
|
||||
|
||||
return $outer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function _expressionTranslators(): array
|
||||
{
|
||||
return [
|
||||
FunctionExpression::class => '_transformFunctionExpression',
|
||||
TupleComparison::class => '_transformTupleComparison',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives a FunctionExpression and changes it so that it conforms to this
|
||||
* SQL dialect.
|
||||
*
|
||||
* @param \Cake\Database\Expression\FunctionExpression $expression The function expression to convert to TSQL.
|
||||
* @return void
|
||||
*/
|
||||
protected function _transformFunctionExpression(FunctionExpression $expression): void
|
||||
{
|
||||
switch ($expression->getName()) {
|
||||
case 'CONCAT':
|
||||
// CONCAT function is expressed as exp1 + exp2
|
||||
$expression->setName('')->setConjunction(' +');
|
||||
break;
|
||||
case 'DATEDIFF':
|
||||
/** @var bool $hasDay */
|
||||
$hasDay = false;
|
||||
$visitor = function ($value) use (&$hasDay) {
|
||||
if ($value === 'day') {
|
||||
$hasDay = true;
|
||||
}
|
||||
|
||||
return $value;
|
||||
};
|
||||
$expression->iterateParts($visitor);
|
||||
|
||||
if (!$hasDay) {
|
||||
$expression->add(['day' => 'literal'], [], true);
|
||||
}
|
||||
break;
|
||||
case 'CURRENT_DATE':
|
||||
$time = new FunctionExpression('GETUTCDATE');
|
||||
$expression->setName('CONVERT')->add(['date' => 'literal', $time]);
|
||||
break;
|
||||
case 'CURRENT_TIME':
|
||||
$time = new FunctionExpression('GETUTCDATE');
|
||||
$expression->setName('CONVERT')->add(['time' => 'literal', $time]);
|
||||
break;
|
||||
case 'NOW':
|
||||
$expression->setName('GETUTCDATE');
|
||||
break;
|
||||
case 'EXTRACT':
|
||||
$expression->setName('DATEPART')->setConjunction(' ,');
|
||||
break;
|
||||
case 'DATE_ADD':
|
||||
$params = [];
|
||||
$visitor = function ($p, $key) use (&$params) {
|
||||
if ($key === 0) {
|
||||
$params[2] = $p;
|
||||
} else {
|
||||
$valueUnit = explode(' ', $p);
|
||||
$params[0] = rtrim($valueUnit[1], 's');
|
||||
$params[1] = $valueUnit[0];
|
||||
}
|
||||
|
||||
return $p;
|
||||
};
|
||||
$manipulator = function ($p, $key) use (&$params) {
|
||||
return $params[$key];
|
||||
};
|
||||
|
||||
$expression
|
||||
->setName('DATEADD')
|
||||
->setConjunction(',')
|
||||
->iterateParts($visitor)
|
||||
->iterateParts($manipulator)
|
||||
->add([$params[2] => 'literal']);
|
||||
break;
|
||||
case 'DAYOFWEEK':
|
||||
$expression
|
||||
->setName('DATEPART')
|
||||
->setConjunction(' ')
|
||||
->add(['weekday, ' => 'literal'], [], true);
|
||||
break;
|
||||
case 'SUBSTR':
|
||||
$expression->setName('SUBSTRING');
|
||||
if (count($expression) < 4) {
|
||||
$params = [];
|
||||
$expression
|
||||
->iterateParts(function ($p) use (&$params) {
|
||||
return $params[] = $p;
|
||||
})
|
||||
->add([new FunctionExpression('LEN', [$params[0]]), ['string']]);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?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\Database\Driver;
|
||||
|
||||
use Cake\Database\Expression\IdentifierExpression;
|
||||
use Cake\Database\Expression\QueryExpression;
|
||||
use Cake\Database\Expression\TupleComparison;
|
||||
use Cake\Database\Query;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Provides a translator method for tuple comparisons
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait TupleComparisonTranslatorTrait
|
||||
{
|
||||
/**
|
||||
* Receives a TupleExpression and changes it so that it conforms to this
|
||||
* SQL dialect.
|
||||
*
|
||||
* It transforms expressions looking like '(a, b) IN ((c, d), (e, f))' into an
|
||||
* equivalent expression of the form '((a = c) AND (b = d)) OR ((a = e) AND (b = f))'.
|
||||
*
|
||||
* It can also transform transform expressions where the right hand side is a query
|
||||
* selecting the same amount of columns as the elements in the left hand side of
|
||||
* the expression:
|
||||
*
|
||||
* (a, b) IN (SELECT c, d FROM a_table) is transformed into
|
||||
*
|
||||
* 1 = (SELECT 1 FROM a_table WHERE (a = c) AND (b = d))
|
||||
*
|
||||
* @param \Cake\Database\Expression\TupleComparison $expression The expression to transform
|
||||
* @param \Cake\Database\Query $query The query to update.
|
||||
* @return void
|
||||
*/
|
||||
protected function _transformTupleComparison(TupleComparison $expression, Query $query): void
|
||||
{
|
||||
$fields = $expression->getField();
|
||||
|
||||
if (!is_array($fields)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$operator = strtoupper($expression->getOperator());
|
||||
if (!in_array($operator, ['IN', '='])) {
|
||||
throw new RuntimeException(
|
||||
sprintf(
|
||||
'Tuple comparison transform only supports the `IN` and `=` operators, `%s` given.',
|
||||
$operator
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$value = $expression->getValue();
|
||||
$true = new QueryExpression('1');
|
||||
|
||||
if ($value instanceof Query) {
|
||||
$selected = array_values($value->clause('select'));
|
||||
foreach ($fields as $i => $field) {
|
||||
$value->andWhere([$field => new IdentifierExpression($selected[$i])]);
|
||||
}
|
||||
$value->select($true, true);
|
||||
$expression->setField($true);
|
||||
$expression->setOperator('=');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$type = $expression->getType();
|
||||
if ($type) {
|
||||
/** @var array<string, string> $typeMap */
|
||||
$typeMap = array_combine($fields, $type) ?: [];
|
||||
} else {
|
||||
$typeMap = [];
|
||||
}
|
||||
|
||||
$surrogate = $query->getConnection()
|
||||
->selectQuery($true);
|
||||
|
||||
if (!is_array(current($value))) {
|
||||
$value = [$value];
|
||||
}
|
||||
|
||||
$conditions = ['OR' => []];
|
||||
foreach ($value as $tuple) {
|
||||
$item = [];
|
||||
foreach (array_values($tuple) as $i => $value2) {
|
||||
$item[] = [$fields[$i] => $value2];
|
||||
}
|
||||
$conditions['OR'][] = $item;
|
||||
}
|
||||
$surrogate->where($conditions, $typeMap);
|
||||
|
||||
$expression->setField($true);
|
||||
$expression->setValue($surrogate);
|
||||
$expression->setOperator('=');
|
||||
}
|
||||
}
|
||||
+336
@@ -0,0 +1,336 @@
|
||||
<?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.6.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database;
|
||||
|
||||
use Cake\Database\Schema\SchemaDialect;
|
||||
use Cake\Database\Schema\TableSchema;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* Interface for database driver.
|
||||
*
|
||||
* @method int|null getMaxAliasLength() Returns the maximum alias length allowed.
|
||||
* @method int getConnectRetries() Returns the number of connection retry attempts made.
|
||||
* @method bool supports(string $feature) Checks whether a feature is supported by the driver.
|
||||
* @method bool inTransaction() Returns whether a transaction is active.
|
||||
* @method array config() Get the configuration data used to create the driver.
|
||||
* @method string getRole() Returns the connection role this driver prforms.
|
||||
*/
|
||||
interface DriverInterface
|
||||
{
|
||||
/**
|
||||
* Common Table Expressions (with clause) support.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const FEATURE_CTE = 'cte';
|
||||
|
||||
/**
|
||||
* Disabling constraints without being in transaction support.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const FEATURE_DISABLE_CONSTRAINT_WITHOUT_TRANSACTION = 'disable-constraint-without-transaction';
|
||||
|
||||
/**
|
||||
* Native JSON data type support.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const FEATURE_JSON = 'json';
|
||||
|
||||
/**
|
||||
* PDO::quote() support.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const FEATURE_QUOTE = 'quote';
|
||||
|
||||
/**
|
||||
* Transaction savepoint support.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const FEATURE_SAVEPOINT = 'savepoint';
|
||||
|
||||
/**
|
||||
* Truncate with foreign keys attached support.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const FEATURE_TRUNCATE_WITH_CONSTRAINTS = 'truncate-with-constraints';
|
||||
|
||||
/**
|
||||
* Window function support (all or partial clauses).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const FEATURE_WINDOW = 'window';
|
||||
|
||||
/**
|
||||
* Establishes a connection to the database server.
|
||||
*
|
||||
* @throws \Cake\Database\Exception\MissingConnectionException If database connection could not be established.
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
public function connect(): bool;
|
||||
|
||||
/**
|
||||
* Disconnects from database server.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function disconnect(): void;
|
||||
|
||||
/**
|
||||
* Returns correct connection resource or object that is internally used.
|
||||
*
|
||||
* @return object Connection object used internally.
|
||||
*/
|
||||
public function getConnection();
|
||||
|
||||
/**
|
||||
* Set the internal connection object.
|
||||
*
|
||||
* @param object $connection The connection instance.
|
||||
* @return $this
|
||||
*/
|
||||
public function setConnection($connection);
|
||||
|
||||
/**
|
||||
* Returns whether php is able to use this driver for connecting to database.
|
||||
*
|
||||
* @return bool True if it is valid to use this driver.
|
||||
*/
|
||||
public function enabled(): bool;
|
||||
|
||||
/**
|
||||
* Prepares a sql statement to be executed.
|
||||
*
|
||||
* @param \Cake\Database\Query|string $query The query to turn into a prepared statement.
|
||||
* @return \Cake\Database\StatementInterface
|
||||
*/
|
||||
public function prepare($query): StatementInterface;
|
||||
|
||||
/**
|
||||
* Starts a transaction.
|
||||
*
|
||||
* @return bool True on success, false otherwise.
|
||||
*/
|
||||
public function beginTransaction(): bool;
|
||||
|
||||
/**
|
||||
* Commits a transaction.
|
||||
*
|
||||
* @return bool True on success, false otherwise.
|
||||
*/
|
||||
public function commitTransaction(): bool;
|
||||
|
||||
/**
|
||||
* Rollbacks a transaction.
|
||||
*
|
||||
* @return bool True on success, false otherwise.
|
||||
*/
|
||||
public function rollbackTransaction(): bool;
|
||||
|
||||
/**
|
||||
* Get the SQL for releasing a save point.
|
||||
*
|
||||
* @param string|int $name Save point name or id
|
||||
* @return string
|
||||
*/
|
||||
public function releaseSavePointSQL($name): string;
|
||||
|
||||
/**
|
||||
* Get the SQL for creating a save point.
|
||||
*
|
||||
* @param string|int $name Save point name or id
|
||||
* @return string
|
||||
*/
|
||||
public function savePointSQL($name): string;
|
||||
|
||||
/**
|
||||
* Get the SQL for rollingback a save point.
|
||||
*
|
||||
* @param string|int $name Save point name or id
|
||||
* @return string
|
||||
*/
|
||||
public function rollbackSavePointSQL($name): string;
|
||||
|
||||
/**
|
||||
* Get the SQL for disabling foreign keys.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function disableForeignKeySQL(): string;
|
||||
|
||||
/**
|
||||
* Get the SQL for enabling foreign keys.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function enableForeignKeySQL(): string;
|
||||
|
||||
/**
|
||||
* Returns whether the driver supports adding or dropping constraints
|
||||
* to already created tables.
|
||||
*
|
||||
* @return bool True if driver supports dynamic constraints.
|
||||
* @deprecated 4.3.0 Fixtures no longer dynamically drop and create constraints.
|
||||
*/
|
||||
public function supportsDynamicConstraints(): bool;
|
||||
|
||||
/**
|
||||
* Returns whether this driver supports save points for nested transactions.
|
||||
*
|
||||
* @return bool True if save points are supported, false otherwise.
|
||||
* @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_SAVEPOINT)` instead
|
||||
*/
|
||||
public function supportsSavePoints(): bool;
|
||||
|
||||
/**
|
||||
* Returns a value in a safe representation to be used in a query string
|
||||
*
|
||||
* @param mixed $value The value to quote.
|
||||
* @param int $type Must be one of the \PDO::PARAM_* constants
|
||||
* @return string
|
||||
*/
|
||||
public function quote($value, $type): string;
|
||||
|
||||
/**
|
||||
* Checks if the driver supports quoting.
|
||||
*
|
||||
* @return bool
|
||||
* @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_QUOTE)` instead
|
||||
*/
|
||||
public function supportsQuoting(): bool;
|
||||
|
||||
/**
|
||||
* Returns a callable function that will be used to transform a passed Query object.
|
||||
* This function, in turn, will return an instance of a Query object that has been
|
||||
* transformed to accommodate any specificities of the SQL dialect in use.
|
||||
*
|
||||
* @param string $type The type of query to be transformed
|
||||
* (select, insert, update, delete).
|
||||
* @return \Closure
|
||||
*/
|
||||
public function queryTranslator(string $type): Closure;
|
||||
|
||||
/**
|
||||
* Get the schema dialect.
|
||||
*
|
||||
* Used by {@link \Cake\Database\Schema} package to reflect schema and
|
||||
* generate schema.
|
||||
*
|
||||
* If all the tables that use this Driver specify their
|
||||
* own schemas, then this may return null.
|
||||
*
|
||||
* @return \Cake\Database\Schema\SchemaDialect
|
||||
*/
|
||||
public function schemaDialect(): SchemaDialect;
|
||||
|
||||
/**
|
||||
* Quotes a database identifier (a column name, table name, etc..) to
|
||||
* be used safely in queries without the risk of using reserved words.
|
||||
*
|
||||
* @param string $identifier The identifier expression to quote.
|
||||
* @return string
|
||||
*/
|
||||
public function quoteIdentifier(string $identifier): string;
|
||||
|
||||
/**
|
||||
* Escapes values for use in schema definitions.
|
||||
*
|
||||
* @param mixed $value The value to escape.
|
||||
* @return string String for use in schema definitions.
|
||||
*/
|
||||
public function schemaValue($value): string;
|
||||
|
||||
/**
|
||||
* Returns the schema name that's being used.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function schema(): string;
|
||||
|
||||
/**
|
||||
* Returns last id generated for a table or sequence in database.
|
||||
*
|
||||
* @param string|null $table table name or sequence to get last insert value from.
|
||||
* @param string|null $column the name of the column representing the primary key.
|
||||
* @return string|int
|
||||
*/
|
||||
public function lastInsertId(?string $table = null, ?string $column = null);
|
||||
|
||||
/**
|
||||
* Checks whether the driver is connected.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isConnected(): bool;
|
||||
|
||||
/**
|
||||
* Sets whether this driver should automatically quote identifiers
|
||||
* in queries.
|
||||
*
|
||||
* @param bool $enable Whether to enable auto quoting
|
||||
* @return $this
|
||||
*/
|
||||
public function enableAutoQuoting(bool $enable = true);
|
||||
|
||||
/**
|
||||
* Disable auto quoting of identifiers in queries.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function disableAutoQuoting();
|
||||
|
||||
/**
|
||||
* Returns whether this driver should automatically quote identifiers
|
||||
* in queries.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isAutoQuotingEnabled(): bool;
|
||||
|
||||
/**
|
||||
* Transforms the passed query to this Driver's dialect and returns an instance
|
||||
* of the transformed query and the full compiled SQL string.
|
||||
*
|
||||
* @param \Cake\Database\Query $query The query to compile.
|
||||
* @param \Cake\Database\ValueBinder $binder The value binder to use.
|
||||
* @return array containing 2 entries. The first entity is the transformed query
|
||||
* and the second one the compiled SQL.
|
||||
*/
|
||||
public function compileQuery(Query $query, ValueBinder $binder): array;
|
||||
|
||||
/**
|
||||
* Returns an instance of a QueryCompiler.
|
||||
*
|
||||
* @return \Cake\Database\QueryCompiler
|
||||
*/
|
||||
public function newCompiler(): QueryCompiler;
|
||||
|
||||
/**
|
||||
* Constructs new TableSchema.
|
||||
*
|
||||
* @param string $table The table name.
|
||||
* @param array $columns The list of columns for the schema.
|
||||
* @return \Cake\Database\Schema\TableSchema
|
||||
*/
|
||||
public function newTableSchema(string $table, array $columns = []): TableSchema;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
deprecationWarning(
|
||||
'Since 4.2.0: Cake\Database\Exception is deprecated. ' .
|
||||
'Use Cake\Database\Exception\DatabaseException instead.'
|
||||
);
|
||||
class_exists('Cake\Database\Exception\DatabaseException');
|
||||
@@ -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. (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\Database\Exception;
|
||||
|
||||
use Cake\Core\Exception\CakeException;
|
||||
|
||||
/**
|
||||
* Exception for the database package.
|
||||
*/
|
||||
class DatabaseException extends CakeException
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected $_messageTemplate = '%s';
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
class_alias(
|
||||
'Cake\Database\Exception\DatabaseException',
|
||||
'Cake\Database\Exception'
|
||||
);
|
||||
// phpcs:enable
|
||||
@@ -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\Database\Exception;
|
||||
|
||||
use Cake\Core\Exception\CakeException;
|
||||
|
||||
/**
|
||||
* Class MissingConnectionException
|
||||
*/
|
||||
class MissingConnectionException extends CakeException
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected $_messageTemplate = 'Connection to %s could not be established: %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\Database\Exception;
|
||||
|
||||
use Cake\Core\Exception\CakeException;
|
||||
|
||||
/**
|
||||
* Class MissingDriverException
|
||||
*/
|
||||
class MissingDriverException extends CakeException
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected $_messageTemplate = 'Could not find driver `%s` for connection `%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\Database\Exception;
|
||||
|
||||
use Cake\Core\Exception\CakeException;
|
||||
|
||||
/**
|
||||
* Class MissingExtensionException
|
||||
*/
|
||||
class MissingExtensionException extends CakeException
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
// phpcs:ignore Generic.Files.LineLength
|
||||
protected $_messageTemplate = 'Database driver %s cannot be used due to a missing PHP extension or unmet dependency. Requested by connection "%s"';
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?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.4.3
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Exception;
|
||||
|
||||
use Cake\Core\Exception\CakeException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Class NestedTransactionRollbackException
|
||||
*/
|
||||
class NestedTransactionRollbackException extends CakeException
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string|null $message If no message is given a default meesage will be used.
|
||||
* @param int|null $code Status code, defaults to 500.
|
||||
* @param \Throwable|null $previous the previous exception.
|
||||
*/
|
||||
public function __construct(?string $message = null, ?int $code = 500, ?Throwable $previous = null)
|
||||
{
|
||||
if ($message === null) {
|
||||
$message = 'Cannot commit transaction - rollback() has been already called in the nested transaction';
|
||||
}
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
<?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\Database\Expression;
|
||||
|
||||
use Cake\Database\ValueBinder;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* This represents an SQL aggregate function expression in an SQL statement.
|
||||
* Calls can be constructed by passing the name of the function and a list of params.
|
||||
* For security reasons, all params passed are quoted by default unless
|
||||
* explicitly told otherwise.
|
||||
*/
|
||||
class AggregateExpression extends FunctionExpression implements WindowInterface
|
||||
{
|
||||
/**
|
||||
* @var \Cake\Database\Expression\QueryExpression
|
||||
*/
|
||||
protected $filter;
|
||||
|
||||
/**
|
||||
* @var \Cake\Database\Expression\WindowExpression
|
||||
*/
|
||||
protected $window;
|
||||
|
||||
/**
|
||||
* Adds conditions to the FILTER clause. The conditions are the same format as
|
||||
* `Query::where()`.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|\Closure|array|string $conditions The conditions to filter on.
|
||||
* @param array<string, string> $types Associative array of type names used to bind values to query
|
||||
* @return $this
|
||||
* @see \Cake\Database\Query::where()
|
||||
*/
|
||||
public function filter($conditions, array $types = [])
|
||||
{
|
||||
if ($this->filter === null) {
|
||||
$this->filter = new QueryExpression();
|
||||
}
|
||||
|
||||
if ($conditions instanceof Closure) {
|
||||
$conditions = $conditions(new QueryExpression());
|
||||
}
|
||||
|
||||
$this->filter->add($conditions, $types);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an empty `OVER()` window expression or a named window epression.
|
||||
*
|
||||
* @param string|null $name Window name
|
||||
* @return $this
|
||||
*/
|
||||
public function over(?string $name = null)
|
||||
{
|
||||
if ($this->window === null) {
|
||||
$this->window = new WindowExpression();
|
||||
}
|
||||
if ($name) {
|
||||
// Set name manually in case this was chained from FunctionsBuilder wrapper
|
||||
$this->window->name($name);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function partition($partitions)
|
||||
{
|
||||
$this->over();
|
||||
$this->window->partition($partitions);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function order($fields)
|
||||
{
|
||||
$this->over();
|
||||
$this->window->order($fields);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function range($start, $end = 0)
|
||||
{
|
||||
$this->over();
|
||||
$this->window->range($start, $end);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function rows(?int $start, ?int $end = 0)
|
||||
{
|
||||
$this->over();
|
||||
$this->window->rows($start, $end);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function groups(?int $start, ?int $end = 0)
|
||||
{
|
||||
$this->over();
|
||||
$this->window->groups($start, $end);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function frame(
|
||||
string $type,
|
||||
$startOffset,
|
||||
string $startDirection,
|
||||
$endOffset,
|
||||
string $endDirection
|
||||
) {
|
||||
$this->over();
|
||||
$this->window->frame($type, $startOffset, $startDirection, $endOffset, $endDirection);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function excludeCurrent()
|
||||
{
|
||||
$this->over();
|
||||
$this->window->excludeCurrent();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function excludeGroup()
|
||||
{
|
||||
$this->over();
|
||||
$this->window->excludeGroup();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function excludeTies()
|
||||
{
|
||||
$this->over();
|
||||
$this->window->excludeTies();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function sql(ValueBinder $binder): string
|
||||
{
|
||||
$sql = parent::sql($binder);
|
||||
if ($this->filter !== null) {
|
||||
$sql .= ' FILTER (WHERE ' . $this->filter->sql($binder) . ')';
|
||||
}
|
||||
if ($this->window !== null) {
|
||||
if ($this->window->isNamedOnly()) {
|
||||
$sql .= ' OVER ' . $this->window->sql($binder);
|
||||
} else {
|
||||
$sql .= ' OVER (' . $this->window->sql($binder) . ')';
|
||||
}
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function traverse(Closure $callback)
|
||||
{
|
||||
parent::traverse($callback);
|
||||
if ($this->filter !== null) {
|
||||
$callback($this->filter);
|
||||
$this->filter->traverse($callback);
|
||||
}
|
||||
if ($this->window !== null) {
|
||||
$callback($this->window);
|
||||
$this->window->traverse($callback);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
$count = parent::count();
|
||||
if ($this->window !== null) {
|
||||
$count = $count + 1;
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone this object and its subtree of expressions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
parent::__clone();
|
||||
if ($this->filter !== null) {
|
||||
$this->filter = clone $this->filter;
|
||||
}
|
||||
if ($this->window !== null) {
|
||||
$this->window = clone $this->window;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?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\Database\Expression;
|
||||
|
||||
use Cake\Database\ExpressionInterface;
|
||||
use Cake\Database\Type\ExpressionTypeCasterTrait;
|
||||
use Cake\Database\ValueBinder;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* An expression object that represents a SQL BETWEEN snippet
|
||||
*/
|
||||
class BetweenExpression implements ExpressionInterface, FieldInterface
|
||||
{
|
||||
use ExpressionTypeCasterTrait;
|
||||
use FieldTrait;
|
||||
|
||||
/**
|
||||
* The first value in the expression
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $_from;
|
||||
|
||||
/**
|
||||
* The second value in the expression
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $_to;
|
||||
|
||||
/**
|
||||
* The data type for the from and to arguments
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $_type;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $field The field name to compare for values inbetween the range.
|
||||
* @param mixed $from The initial value of the range.
|
||||
* @param mixed $to The ending value in the comparison range.
|
||||
* @param string|null $type The data type name to bind the values with.
|
||||
*/
|
||||
public function __construct($field, $from, $to, $type = null)
|
||||
{
|
||||
if ($type !== null) {
|
||||
$from = $this->_castToExpression($from, $type);
|
||||
$to = $this->_castToExpression($to, $type);
|
||||
}
|
||||
|
||||
$this->_field = $field;
|
||||
$this->_from = $from;
|
||||
$this->_to = $to;
|
||||
$this->_type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function sql(ValueBinder $binder): string
|
||||
{
|
||||
$parts = [
|
||||
'from' => $this->_from,
|
||||
'to' => $this->_to,
|
||||
];
|
||||
|
||||
/** @var \Cake\Database\ExpressionInterface|string $field */
|
||||
$field = $this->_field;
|
||||
if ($field instanceof ExpressionInterface) {
|
||||
$field = $field->sql($binder);
|
||||
}
|
||||
|
||||
foreach ($parts as $name => $part) {
|
||||
if ($part instanceof ExpressionInterface) {
|
||||
$parts[$name] = $part->sql($binder);
|
||||
continue;
|
||||
}
|
||||
$parts[$name] = $this->_bindValue($part, $binder, $this->_type);
|
||||
}
|
||||
|
||||
return sprintf('%s BETWEEN %s AND %s', $field, $parts['from'], $parts['to']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function traverse(Closure $callback)
|
||||
{
|
||||
foreach ([$this->_field, $this->_from, $this->_to] as $part) {
|
||||
if ($part instanceof ExpressionInterface) {
|
||||
$callback($part);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a value in the placeholder generator and returns the generated placeholder
|
||||
*
|
||||
* @param mixed $value The value to bind
|
||||
* @param \Cake\Database\ValueBinder $binder The value binder to use
|
||||
* @param string $type The type of $value
|
||||
* @return string generated placeholder
|
||||
*/
|
||||
protected function _bindValue($value, $binder, $type): string
|
||||
{
|
||||
$placeholder = $binder->placeholder('c');
|
||||
$binder->bind($placeholder, $value, $type);
|
||||
|
||||
return $placeholder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a deep clone of this expression.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach (['_field', '_from', '_to'] as $part) {
|
||||
if ($this->{$part} instanceof ExpressionInterface) {
|
||||
$this->{$part} = clone $this->{$part};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
<?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\Database\Expression;
|
||||
|
||||
use Cake\Database\ExpressionInterface;
|
||||
use Cake\Database\Type\ExpressionTypeCasterTrait;
|
||||
use Cake\Database\ValueBinder;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* This class represents a SQL Case statement
|
||||
*
|
||||
* @deprecated 4.3.0 Use QueryExpression::case() or CaseStatementExpression instead
|
||||
*/
|
||||
class CaseExpression implements ExpressionInterface
|
||||
{
|
||||
use ExpressionTypeCasterTrait;
|
||||
|
||||
/**
|
||||
* A list of strings or other expression objects that represent the conditions of
|
||||
* the case statement. For example one key of the array might look like "sum > :value"
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_conditions = [];
|
||||
|
||||
/**
|
||||
* Values that are associated with the conditions in the $_conditions array.
|
||||
* Each value represents the 'true' value for the condition with the corresponding key.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_values = [];
|
||||
|
||||
/**
|
||||
* The `ELSE` value for the case statement. If null then no `ELSE` will be included.
|
||||
*
|
||||
* @var \Cake\Database\ExpressionInterface|array|string|null
|
||||
*/
|
||||
protected $_elseValue;
|
||||
|
||||
/**
|
||||
* Constructs the case expression
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|array $conditions The conditions to test. Must be a ExpressionInterface
|
||||
* instance, or an array of ExpressionInterface instances.
|
||||
* @param \Cake\Database\ExpressionInterface|array $values Associative array of values to be associated with the
|
||||
* conditions passed in $conditions. If there are more $values than $conditions,
|
||||
* the last $value is used as the `ELSE` value.
|
||||
* @param array<string> $types Associative array of types to be associated with the values
|
||||
* passed in $values
|
||||
*/
|
||||
public function __construct($conditions = [], $values = [], $types = [])
|
||||
{
|
||||
$conditions = is_array($conditions) ? $conditions : [$conditions];
|
||||
$values = is_array($values) ? $values : [$values];
|
||||
$types = is_array($types) ? $types : [$types];
|
||||
|
||||
if (!empty($conditions)) {
|
||||
$this->add($conditions, $values, $types);
|
||||
}
|
||||
|
||||
if (count($values) > count($conditions)) {
|
||||
end($values);
|
||||
$key = key($values);
|
||||
$this->elseValue($values[$key], $types[$key] ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one or more conditions and their respective true values to the case object.
|
||||
* Conditions must be a one dimensional array or a QueryExpression.
|
||||
* The trueValues must be a similar structure, but may contain a string value.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|array $conditions Must be a ExpressionInterface instance,
|
||||
* or an array of ExpressionInterface instances.
|
||||
* @param \Cake\Database\ExpressionInterface|array $values Associative array of values of each condition
|
||||
* @param array<string> $types Associative array of types to be associated with the values
|
||||
* @return $this
|
||||
*/
|
||||
public function add($conditions = [], $values = [], $types = [])
|
||||
{
|
||||
$conditions = is_array($conditions) ? $conditions : [$conditions];
|
||||
$values = is_array($values) ? $values : [$values];
|
||||
$types = is_array($types) ? $types : [$types];
|
||||
|
||||
$this->_addExpressions($conditions, $values, $types);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over the passed in conditions and ensures that there is a matching true value for each.
|
||||
* If no matching true value, then it is defaulted to '1'.
|
||||
*
|
||||
* @param array $conditions Array of ExpressionInterface instances.
|
||||
* @param array<mixed> $values Associative array of values of each condition
|
||||
* @param array<string> $types Associative array of types to be associated with the values
|
||||
* @return void
|
||||
*/
|
||||
protected function _addExpressions(array $conditions, array $values, array $types): void
|
||||
{
|
||||
$rawValues = array_values($values);
|
||||
$keyValues = array_keys($values);
|
||||
|
||||
foreach ($conditions as $k => $c) {
|
||||
$numericKey = is_numeric($k);
|
||||
|
||||
if ($numericKey && empty($c)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$c instanceof ExpressionInterface) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->_conditions[] = $c;
|
||||
$value = $rawValues[$k] ?? 1;
|
||||
|
||||
if ($value === 'literal') {
|
||||
$value = $keyValues[$k];
|
||||
$this->_values[] = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value === 'identifier') {
|
||||
/** @var string $identifier */
|
||||
$identifier = $keyValues[$k];
|
||||
$value = new IdentifierExpression($identifier);
|
||||
$this->_values[] = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $types[$k] ?? null;
|
||||
|
||||
if ($type !== null && !$value instanceof ExpressionInterface) {
|
||||
$value = $this->_castToExpression($value, $type);
|
||||
}
|
||||
|
||||
if ($value instanceof ExpressionInterface) {
|
||||
$this->_values[] = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->_values[] = ['value' => $value, 'type' => $type];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default value
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|array|string|null $value Value to set
|
||||
* @param string|null $type Type of value
|
||||
* @return void
|
||||
*/
|
||||
public function elseValue($value = null, ?string $type = null): void
|
||||
{
|
||||
if (is_array($value)) {
|
||||
end($value);
|
||||
$value = key($value);
|
||||
}
|
||||
|
||||
if ($value !== null && !$value instanceof ExpressionInterface) {
|
||||
$value = $this->_castToExpression($value, $type);
|
||||
}
|
||||
|
||||
if (!$value instanceof ExpressionInterface) {
|
||||
$value = ['value' => $value, 'type' => $type];
|
||||
}
|
||||
|
||||
$this->_elseValue = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the relevant parts into sql
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|array|string $part The part to compile
|
||||
* @param \Cake\Database\ValueBinder $binder Sql generator
|
||||
* @return string
|
||||
*/
|
||||
protected function _compile($part, ValueBinder $binder): string
|
||||
{
|
||||
if ($part instanceof ExpressionInterface) {
|
||||
$part = $part->sql($binder);
|
||||
} elseif (is_array($part)) {
|
||||
$placeholder = $binder->placeholder('param');
|
||||
$binder->bind($placeholder, $part['value'], $part['type']);
|
||||
$part = $placeholder;
|
||||
}
|
||||
|
||||
return $part;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the Node into a SQL string fragment.
|
||||
*
|
||||
* @param \Cake\Database\ValueBinder $binder Placeholder generator object
|
||||
* @return string
|
||||
*/
|
||||
public function sql(ValueBinder $binder): string
|
||||
{
|
||||
$parts = [];
|
||||
$parts[] = 'CASE';
|
||||
foreach ($this->_conditions as $k => $part) {
|
||||
$value = $this->_values[$k];
|
||||
$parts[] = 'WHEN ' . $this->_compile($part, $binder) . ' THEN ' . $this->_compile($value, $binder);
|
||||
}
|
||||
if ($this->_elseValue !== null) {
|
||||
$parts[] = 'ELSE';
|
||||
$parts[] = $this->_compile($this->_elseValue, $binder);
|
||||
}
|
||||
$parts[] = 'END';
|
||||
|
||||
return implode(' ', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function traverse(Closure $callback)
|
||||
{
|
||||
foreach (['_conditions', '_values'] as $part) {
|
||||
foreach ($this->{$part} as $c) {
|
||||
if ($c instanceof ExpressionInterface) {
|
||||
$callback($c);
|
||||
$c->traverse($callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($this->_elseValue instanceof ExpressionInterface) {
|
||||
$callback($this->_elseValue);
|
||||
$this->_elseValue->traverse($callback);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?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.3.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Expression;
|
||||
|
||||
use Cake\Chronos\ChronosDate;
|
||||
use Cake\Chronos\MutableDate;
|
||||
use Cake\Database\ExpressionInterface;
|
||||
use Cake\Database\Query;
|
||||
use Cake\Database\TypedResultInterface;
|
||||
use Cake\Database\ValueBinder;
|
||||
use DateTimeInterface;
|
||||
|
||||
/**
|
||||
* Trait that holds shared functionality for case related expressions.
|
||||
*
|
||||
* @property \Cake\Database\TypeMap $_typeMap The type map to use when using an array of conditions for the `WHEN`
|
||||
* value.
|
||||
* @internal
|
||||
*/
|
||||
trait CaseExpressionTrait
|
||||
{
|
||||
/**
|
||||
* Infers the abstract type for the given value.
|
||||
*
|
||||
* @param mixed $value The value for which to infer the type.
|
||||
* @return string|null The abstract type, or `null` if it could not be inferred.
|
||||
*/
|
||||
protected function inferType($value): ?string
|
||||
{
|
||||
$type = null;
|
||||
|
||||
if (is_string($value)) {
|
||||
$type = 'string';
|
||||
} elseif (is_int($value)) {
|
||||
$type = 'integer';
|
||||
} elseif (is_float($value)) {
|
||||
$type = 'float';
|
||||
} elseif (is_bool($value)) {
|
||||
$type = 'boolean';
|
||||
} elseif (
|
||||
$value instanceof ChronosDate ||
|
||||
$value instanceof MutableDate
|
||||
) {
|
||||
$type = 'date';
|
||||
} elseif ($value instanceof DateTimeInterface) {
|
||||
$type = 'datetime';
|
||||
} elseif (
|
||||
is_object($value) &&
|
||||
method_exists($value, '__toString')
|
||||
) {
|
||||
$type = 'string';
|
||||
} elseif (
|
||||
$this->_typeMap !== null &&
|
||||
$value instanceof IdentifierExpression
|
||||
) {
|
||||
$type = $this->_typeMap->type($value->getIdentifier());
|
||||
} elseif ($value instanceof TypedResultInterface) {
|
||||
$type = $value->getReturnType();
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a nullable value to SQL.
|
||||
*
|
||||
* @param \Cake\Database\ValueBinder $binder The value binder to use.
|
||||
* @param \Cake\Database\ExpressionInterface|object|scalar|null $value The value to compile.
|
||||
* @param string|null $type The value type.
|
||||
* @return string
|
||||
*/
|
||||
protected function compileNullableValue(ValueBinder $binder, $value, ?string $type = null): string
|
||||
{
|
||||
if (
|
||||
$type !== null &&
|
||||
!($value instanceof ExpressionInterface)
|
||||
) {
|
||||
$value = $this->_castToExpression($value, $type);
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
$value = 'NULL';
|
||||
} elseif ($value instanceof Query) {
|
||||
$value = sprintf('(%s)', $value->sql($binder));
|
||||
} elseif ($value instanceof ExpressionInterface) {
|
||||
$value = $value->sql($binder);
|
||||
} else {
|
||||
$placeholder = $binder->placeholder('c');
|
||||
$binder->bind($placeholder, $value, $type);
|
||||
$value = $placeholder;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,597 @@
|
||||
<?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.3.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Expression;
|
||||
|
||||
use Cake\Database\ExpressionInterface;
|
||||
use Cake\Database\Type\ExpressionTypeCasterTrait;
|
||||
use Cake\Database\TypedResultInterface;
|
||||
use Cake\Database\TypeMapTrait;
|
||||
use Cake\Database\ValueBinder;
|
||||
use Closure;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use function Cake\Core\getTypeName;
|
||||
|
||||
/**
|
||||
* Represents a SQL case statement with a fluid API
|
||||
*/
|
||||
class CaseStatementExpression implements ExpressionInterface, TypedResultInterface
|
||||
{
|
||||
use CaseExpressionTrait;
|
||||
use ExpressionTypeCasterTrait;
|
||||
use TypeMapTrait;
|
||||
|
||||
/**
|
||||
* The names of the clauses that are valid for use with the
|
||||
* `clause()` method.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $validClauseNames = [
|
||||
'value',
|
||||
'when',
|
||||
'else',
|
||||
];
|
||||
|
||||
/**
|
||||
* Whether this is a simple case expression.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isSimpleVariant = false;
|
||||
|
||||
/**
|
||||
* The case value.
|
||||
*
|
||||
* @var \Cake\Database\ExpressionInterface|object|scalar|null
|
||||
*/
|
||||
protected $value = null;
|
||||
|
||||
/**
|
||||
* The case value type.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $valueType = null;
|
||||
|
||||
/**
|
||||
* The `WHEN ... THEN ...` expressions.
|
||||
*
|
||||
* @var array<\Cake\Database\Expression\WhenThenExpression>
|
||||
*/
|
||||
protected $when = [];
|
||||
|
||||
/**
|
||||
* Buffer that holds values and types for use with `then()`.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
protected $whenBuffer = null;
|
||||
|
||||
/**
|
||||
* The else part result value.
|
||||
*
|
||||
* @var \Cake\Database\ExpressionInterface|object|scalar|null
|
||||
*/
|
||||
protected $else = null;
|
||||
|
||||
/**
|
||||
* The else part result type.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $elseType = null;
|
||||
|
||||
/**
|
||||
* The return type.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $returnType = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* When a value is set, the syntax generated is
|
||||
* `CASE case_value WHEN when_value ... END` (simple case),
|
||||
* where the `when_value`'s are compared against the
|
||||
* `case_value`.
|
||||
*
|
||||
* When no value is set, the syntax generated is
|
||||
* `CASE WHEN when_conditions ... END` (searched case),
|
||||
* where the conditions hold the comparisons.
|
||||
*
|
||||
* Note that `null` is a valid case value, and thus should
|
||||
* only be passed if you actually want to create the simple
|
||||
* case expression variant!
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|object|scalar|null $value The case value.
|
||||
* @param string|null $type The case value type. If no type is provided, the type will be tried to be inferred
|
||||
* from the value.
|
||||
*/
|
||||
public function __construct($value = null, ?string $type = null)
|
||||
{
|
||||
if (func_num_args() > 0) {
|
||||
if (
|
||||
$value !== null &&
|
||||
!is_scalar($value) &&
|
||||
!(is_object($value) && !($value instanceof Closure))
|
||||
) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'The `$value` argument must be either `null`, a scalar value, an object, ' .
|
||||
'or an instance of `\%s`, `%s` given.',
|
||||
ExpressionInterface::class,
|
||||
getTypeName($value)
|
||||
));
|
||||
}
|
||||
|
||||
$this->value = $value;
|
||||
|
||||
if (
|
||||
$value !== null &&
|
||||
$type === null &&
|
||||
!($value instanceof ExpressionInterface)
|
||||
) {
|
||||
$type = $this->inferType($value);
|
||||
}
|
||||
$this->valueType = $type;
|
||||
|
||||
$this->isSimpleVariant = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the `WHEN` value for a `WHEN ... THEN ...` expression, or a
|
||||
* self-contained expression that holds both the value for `WHEN`
|
||||
* and the value for `THEN`.
|
||||
*
|
||||
* ### Order based syntax
|
||||
*
|
||||
* When passing a value other than a self-contained
|
||||
* `\Cake\Database\Expression\WhenThenExpression`,
|
||||
* instance, the `WHEN ... THEN ...` statement must be closed off with
|
||||
* a call to `then()` before invoking `when()` again or `else()`:
|
||||
*
|
||||
* ```
|
||||
* $queryExpression
|
||||
* ->case($query->identifier('Table.column'))
|
||||
* ->when(true)
|
||||
* ->then('Yes')
|
||||
* ->when(false)
|
||||
* ->then('No')
|
||||
* ->else('Maybe');
|
||||
* ```
|
||||
*
|
||||
* ### Self-contained expressions
|
||||
*
|
||||
* When passing an instance of `\Cake\Database\Expression\WhenThenExpression`,
|
||||
* being it directly, or via a callable, then there is no need to close
|
||||
* using `then()` on this object, instead the statement will be closed
|
||||
* on the `\Cake\Database\Expression\WhenThenExpression`
|
||||
* object using
|
||||
* `\Cake\Database\Expression\WhenThenExpression::then()`.
|
||||
*
|
||||
* Callables will receive an instance of `\Cake\Database\Expression\WhenThenExpression`,
|
||||
* and must return one, being it the same object, or a custom one:
|
||||
*
|
||||
* ```
|
||||
* $queryExpression
|
||||
* ->case()
|
||||
* ->when(function (\Cake\Database\Expression\WhenThenExpression $whenThen) {
|
||||
* return $whenThen
|
||||
* ->when(['Table.column' => true])
|
||||
* ->then('Yes');
|
||||
* })
|
||||
* ->when(function (\Cake\Database\Expression\WhenThenExpression $whenThen) {
|
||||
* return $whenThen
|
||||
* ->when(['Table.column' => false])
|
||||
* ->then('No');
|
||||
* })
|
||||
* ->else('Maybe');
|
||||
* ```
|
||||
*
|
||||
* ### Type handling
|
||||
*
|
||||
* The types provided via the `$type` argument will be merged with the
|
||||
* type map set for this expression. When using callables for `$when`,
|
||||
* the `\Cake\Database\Expression\WhenThenExpression`
|
||||
* instance received by the callables will inherit that type map, however
|
||||
* the types passed here will _not_ be merged in case of using callables,
|
||||
* instead the types must be passed in
|
||||
* `\Cake\Database\Expression\WhenThenExpression::when()`:
|
||||
*
|
||||
* ```
|
||||
* $queryExpression
|
||||
* ->case()
|
||||
* ->when(function (\Cake\Database\Expression\WhenThenExpression $whenThen) {
|
||||
* return $whenThen
|
||||
* ->when(['unmapped_column' => true], ['unmapped_column' => 'bool'])
|
||||
* ->then('Yes');
|
||||
* })
|
||||
* ->when(function (\Cake\Database\Expression\WhenThenExpression $whenThen) {
|
||||
* return $whenThen
|
||||
* ->when(['unmapped_column' => false], ['unmapped_column' => 'bool'])
|
||||
* ->then('No');
|
||||
* })
|
||||
* ->else('Maybe');
|
||||
* ```
|
||||
*
|
||||
* ### User data safety
|
||||
*
|
||||
* When passing user data, be aware that allowing a user defined array
|
||||
* to be passed, is a potential SQL injection vulnerability, as it
|
||||
* allows for raw SQL to slip in!
|
||||
*
|
||||
* The following is _unsafe_ usage that must be avoided:
|
||||
*
|
||||
* ```
|
||||
* $case
|
||||
* ->when($userData)
|
||||
* ```
|
||||
*
|
||||
* A safe variant for the above would be to define a single type for
|
||||
* the value:
|
||||
*
|
||||
* ```
|
||||
* $case
|
||||
* ->when($userData, 'integer')
|
||||
* ```
|
||||
*
|
||||
* This way an exception would be triggered when an array is passed for
|
||||
* the value, thus preventing raw SQL from slipping in, and all other
|
||||
* types of values would be forced to be bound as an integer.
|
||||
*
|
||||
* Another way to safely pass user data is when using a conditions
|
||||
* array, and passing user data only on the value side of the array
|
||||
* entries, which will cause them to be bound:
|
||||
*
|
||||
* ```
|
||||
* $case
|
||||
* ->when([
|
||||
* 'Table.column' => $userData,
|
||||
* ])
|
||||
* ```
|
||||
*
|
||||
* Lastly, data can also be bound manually:
|
||||
*
|
||||
* ```
|
||||
* $query
|
||||
* ->select([
|
||||
* 'val' => $query->newExpr()
|
||||
* ->case()
|
||||
* ->when($query->newExpr(':userData'))
|
||||
* ->then(123)
|
||||
* ])
|
||||
* ->bind(':userData', $userData, 'integer')
|
||||
* ```
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|\Closure|object|array|scalar $when The `WHEN` value. When using an
|
||||
* array of conditions, it must be compatible with `\Cake\Database\Query::where()`. Note that this argument is
|
||||
* _not_ completely safe for use with user data, as a user supplied array would allow for raw SQL to slip in! If
|
||||
* you plan to use user data, either pass a single type for the `$type` argument (which forces the `$when` value to
|
||||
* be a non-array, and then always binds the data), use a conditions array where the user data is only passed on
|
||||
* the value side of the array entries, or custom bindings!
|
||||
* @param array<string, string>|string|null $type The when value type. Either an associative array when using array style
|
||||
* conditions, or else a string. If no type is provided, the type will be tried to be inferred from the value.
|
||||
* @return $this
|
||||
* @throws \LogicException In case this a closing `then()` call is required before calling this method.
|
||||
* @throws \LogicException In case the callable doesn't return an instance of
|
||||
* `\Cake\Database\Expression\WhenThenExpression`.
|
||||
*/
|
||||
public function when($when, $type = null)
|
||||
{
|
||||
if ($this->whenBuffer !== null) {
|
||||
throw new LogicException('Cannot call `when()` between `when()` and `then()`.');
|
||||
}
|
||||
|
||||
if ($when instanceof Closure) {
|
||||
$when = $when(new WhenThenExpression($this->getTypeMap()));
|
||||
if (!($when instanceof WhenThenExpression)) {
|
||||
throw new LogicException(sprintf(
|
||||
'`when()` callables must return an instance of `\%s`, `%s` given.',
|
||||
WhenThenExpression::class,
|
||||
getTypeName($when)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if ($when instanceof WhenThenExpression) {
|
||||
$this->when[] = $when;
|
||||
} else {
|
||||
$this->whenBuffer = ['when' => $when, 'type' => $type];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the `THEN` result value for the last `WHEN ... THEN ...`
|
||||
* statement that was opened using `when()`.
|
||||
*
|
||||
* ### Order based syntax
|
||||
*
|
||||
* This method can only be invoked in case `when()` was previously
|
||||
* used with a value other than a closure or an instance of
|
||||
* `\Cake\Database\Expression\WhenThenExpression`:
|
||||
*
|
||||
* ```
|
||||
* $case
|
||||
* ->when(['Table.column' => true])
|
||||
* ->then('Yes')
|
||||
* ->when(['Table.column' => false])
|
||||
* ->then('No')
|
||||
* ->else('Maybe');
|
||||
* ```
|
||||
*
|
||||
* The following would all fail with an exception:
|
||||
*
|
||||
* ```
|
||||
* $case
|
||||
* ->when(['Table.column' => true])
|
||||
* ->when(['Table.column' => false])
|
||||
* // ...
|
||||
* ```
|
||||
*
|
||||
* ```
|
||||
* $case
|
||||
* ->when(['Table.column' => true])
|
||||
* ->else('Maybe')
|
||||
* // ...
|
||||
* ```
|
||||
*
|
||||
* ```
|
||||
* $case
|
||||
* ->then('Yes')
|
||||
* // ...
|
||||
* ```
|
||||
*
|
||||
* ```
|
||||
* $case
|
||||
* ->when(['Table.column' => true])
|
||||
* ->then('Yes')
|
||||
* ->then('No')
|
||||
* // ...
|
||||
* ```
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|object|scalar|null $result The result value.
|
||||
* @param string|null $type The result type. If no type is provided, the type will be tried to be inferred from the
|
||||
* value.
|
||||
* @return $this
|
||||
* @throws \LogicException In case `when()` wasn't previously called with a value other than a closure or an
|
||||
* instance of `\Cake\Database\Expression\WhenThenExpression`.
|
||||
*/
|
||||
public function then($result, ?string $type = null)
|
||||
{
|
||||
if ($this->whenBuffer === null) {
|
||||
throw new LogicException('Cannot call `then()` before `when()`.');
|
||||
}
|
||||
|
||||
$whenThen = (new WhenThenExpression($this->getTypeMap()))
|
||||
->when($this->whenBuffer['when'], $this->whenBuffer['type'])
|
||||
->then($result, $type);
|
||||
|
||||
$this->whenBuffer = null;
|
||||
|
||||
$this->when[] = $whenThen;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the `ELSE` result value.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|object|scalar|null $result The result value.
|
||||
* @param string|null $type The result type. If no type is provided, the type will be tried to be inferred from the
|
||||
* value.
|
||||
* @return $this
|
||||
* @throws \LogicException In case a closing `then()` call is required before calling this method.
|
||||
* @throws \InvalidArgumentException In case the `$result` argument is neither a scalar value, nor an object, an
|
||||
* instance of `\Cake\Database\ExpressionInterface`, or `null`.
|
||||
*/
|
||||
public function else($result, ?string $type = null)
|
||||
{
|
||||
if ($this->whenBuffer !== null) {
|
||||
throw new LogicException('Cannot call `else()` between `when()` and `then()`.');
|
||||
}
|
||||
|
||||
if (
|
||||
$result !== null &&
|
||||
!is_scalar($result) &&
|
||||
!(is_object($result) && !($result instanceof Closure))
|
||||
) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'The `$result` argument must be either `null`, a scalar value, an object, ' .
|
||||
'or an instance of `\%s`, `%s` given.',
|
||||
ExpressionInterface::class,
|
||||
getTypeName($result)
|
||||
));
|
||||
}
|
||||
|
||||
if ($type === null) {
|
||||
$type = $this->inferType($result);
|
||||
}
|
||||
|
||||
$this->else = $result;
|
||||
$this->elseType = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the abstract type that this expression will return.
|
||||
*
|
||||
* If no type has been explicitly set via `setReturnType()`, this
|
||||
* method will try to obtain the type from the result types of the
|
||||
* `then()` and `else() `calls. All types must be identical in order
|
||||
* for this to work, otherwise the type will default to `string`.
|
||||
*
|
||||
* @return string
|
||||
* @see CaseStatementExpression::then()
|
||||
*/
|
||||
public function getReturnType(): string
|
||||
{
|
||||
if ($this->returnType !== null) {
|
||||
return $this->returnType;
|
||||
}
|
||||
|
||||
$types = [];
|
||||
foreach ($this->when as $when) {
|
||||
$type = $when->getResultType();
|
||||
if ($type !== null) {
|
||||
$types[] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->elseType !== null) {
|
||||
$types[] = $this->elseType;
|
||||
}
|
||||
|
||||
$types = array_unique($types);
|
||||
if (count($types) === 1) {
|
||||
return $types[0];
|
||||
}
|
||||
|
||||
return 'string';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the abstract type that this expression will return.
|
||||
*
|
||||
* If no type is being explicitly set via this method, then the
|
||||
* `getReturnType()` method will try to infer the type from the
|
||||
* result types of the `then()` and `else() `calls.
|
||||
*
|
||||
* @param string $type The type name to use.
|
||||
* @return $this
|
||||
*/
|
||||
public function setReturnType(string $type)
|
||||
{
|
||||
$this->returnType = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the available data for the given clause.
|
||||
*
|
||||
* ### Available clauses
|
||||
*
|
||||
* The following clause names are available:
|
||||
*
|
||||
* * `value`: The case value for a `CASE case_value WHEN ...` expression.
|
||||
* * `when`: An array of `WHEN ... THEN ...` expressions.
|
||||
* * `else`: The `ELSE` result value.
|
||||
*
|
||||
* @param string $clause The name of the clause to obtain.
|
||||
* @return \Cake\Database\ExpressionInterface|object|array<\Cake\Database\Expression\WhenThenExpression>|scalar|null
|
||||
* @throws \InvalidArgumentException In case the given clause name is invalid.
|
||||
*/
|
||||
public function clause(string $clause)
|
||||
{
|
||||
if (!in_array($clause, $this->validClauseNames, true)) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'The `$clause` argument must be one of `%s`, the given value `%s` is invalid.',
|
||||
implode('`, `', $this->validClauseNames),
|
||||
$clause
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->{$clause};
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function sql(ValueBinder $binder): string
|
||||
{
|
||||
if ($this->whenBuffer !== null) {
|
||||
throw new LogicException('Case expression has incomplete when clause. Missing `then()` after `when()`.');
|
||||
}
|
||||
|
||||
if (empty($this->when)) {
|
||||
throw new LogicException('Case expression must have at least one when statement.');
|
||||
}
|
||||
|
||||
$value = '';
|
||||
if ($this->isSimpleVariant) {
|
||||
$value = $this->compileNullableValue($binder, $this->value, $this->valueType) . ' ';
|
||||
}
|
||||
|
||||
$whenThenExpressions = [];
|
||||
foreach ($this->when as $whenThen) {
|
||||
$whenThenExpressions[] = $whenThen->sql($binder);
|
||||
}
|
||||
$whenThen = implode(' ', $whenThenExpressions);
|
||||
|
||||
$else = $this->compileNullableValue($binder, $this->else, $this->elseType);
|
||||
|
||||
return "CASE {$value}{$whenThen} ELSE $else END";
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function traverse(Closure $callback)
|
||||
{
|
||||
if ($this->whenBuffer !== null) {
|
||||
throw new LogicException('Case expression has incomplete when clause. Missing `then()` after `when()`.');
|
||||
}
|
||||
|
||||
if ($this->value instanceof ExpressionInterface) {
|
||||
$callback($this->value);
|
||||
$this->value->traverse($callback);
|
||||
}
|
||||
|
||||
foreach ($this->when as $when) {
|
||||
$callback($when);
|
||||
$when->traverse($callback);
|
||||
}
|
||||
|
||||
if ($this->else instanceof ExpressionInterface) {
|
||||
$callback($this->else);
|
||||
$this->else->traverse($callback);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the inner expression objects.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->whenBuffer !== null) {
|
||||
throw new LogicException('Case expression has incomplete when clause. Missing `then()` after `when()`.');
|
||||
}
|
||||
|
||||
if ($this->value instanceof ExpressionInterface) {
|
||||
$this->value = clone $this->value;
|
||||
}
|
||||
|
||||
foreach ($this->when as $key => $when) {
|
||||
$this->when[$key] = clone $this->when[$key];
|
||||
}
|
||||
|
||||
if ($this->else instanceof ExpressionInterface) {
|
||||
$this->else = clone $this->else;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
<?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\Database\Expression;
|
||||
|
||||
use Cake\Database\ExpressionInterface;
|
||||
use Cake\Database\ValueBinder;
|
||||
use Closure;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* An expression that represents a common table expression definition.
|
||||
*/
|
||||
class CommonTableExpression implements ExpressionInterface
|
||||
{
|
||||
/**
|
||||
* The CTE name.
|
||||
*
|
||||
* @var \Cake\Database\Expression\IdentifierExpression
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The field names to use for the CTE.
|
||||
*
|
||||
* @var array<\Cake\Database\Expression\IdentifierExpression>
|
||||
*/
|
||||
protected $fields = [];
|
||||
|
||||
/**
|
||||
* The CTE query definition.
|
||||
*
|
||||
* @var \Cake\Database\ExpressionInterface|null
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* Whether the CTE is materialized or not materialized.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $materialized = null;
|
||||
|
||||
/**
|
||||
* Whether the CTE is recursive.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $recursive = false;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $name The CTE name.
|
||||
* @param \Cake\Database\ExpressionInterface|\Closure $query CTE query
|
||||
*/
|
||||
public function __construct(string $name = '', $query = null)
|
||||
{
|
||||
$this->name = new IdentifierExpression($name);
|
||||
if ($query) {
|
||||
$this->query($query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of this CTE.
|
||||
*
|
||||
* This is the named you used to reference the expression
|
||||
* in select, insert, etc queries.
|
||||
*
|
||||
* @param string $name The CTE name.
|
||||
* @return $this
|
||||
*/
|
||||
public function name(string $name)
|
||||
{
|
||||
$this->name = new IdentifierExpression($name);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the query for this CTE.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|\Closure $query CTE query
|
||||
* @return $this
|
||||
*/
|
||||
public function query($query)
|
||||
{
|
||||
if ($query instanceof Closure) {
|
||||
$query = $query();
|
||||
if (!($query instanceof ExpressionInterface)) {
|
||||
throw new RuntimeException(
|
||||
'You must return an `ExpressionInterface` from a Closure passed to `query()`.'
|
||||
);
|
||||
}
|
||||
}
|
||||
$this->query = $query;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one or more fields (arguments) to the CTE.
|
||||
*
|
||||
* @param \Cake\Database\Expression\IdentifierExpression|array<\Cake\Database\Expression\IdentifierExpression>|array<string>|string $fields Field names
|
||||
* @return $this
|
||||
*/
|
||||
public function field($fields)
|
||||
{
|
||||
$fields = (array)$fields;
|
||||
foreach ($fields as &$field) {
|
||||
if (!($field instanceof IdentifierExpression)) {
|
||||
$field = new IdentifierExpression($field);
|
||||
}
|
||||
}
|
||||
$this->fields = array_merge($this->fields, $fields);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this CTE as materialized.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function materialized()
|
||||
{
|
||||
$this->materialized = 'MATERIALIZED';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this CTE as not materialized.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function notMaterialized()
|
||||
{
|
||||
$this->materialized = 'NOT MATERIALIZED';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this CTE is recursive.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isRecursive(): bool
|
||||
{
|
||||
return $this->recursive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this CTE as recursive.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function recursive()
|
||||
{
|
||||
$this->recursive = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function sql(ValueBinder $binder): string
|
||||
{
|
||||
$fields = '';
|
||||
if ($this->fields) {
|
||||
$expressions = array_map(function (IdentifierExpression $e) use ($binder) {
|
||||
return $e->sql($binder);
|
||||
}, $this->fields);
|
||||
$fields = sprintf('(%s)', implode(', ', $expressions));
|
||||
}
|
||||
|
||||
$suffix = $this->materialized ? $this->materialized . ' ' : '';
|
||||
|
||||
return sprintf(
|
||||
'%s%s AS %s(%s)',
|
||||
$this->name->sql($binder),
|
||||
$fields,
|
||||
$suffix,
|
||||
$this->query ? $this->query->sql($binder) : ''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function traverse(Closure $callback)
|
||||
{
|
||||
$callback($this->name);
|
||||
foreach ($this->fields as $field) {
|
||||
$callback($field);
|
||||
$field->traverse($callback);
|
||||
}
|
||||
|
||||
if ($this->query) {
|
||||
$callback($this->query);
|
||||
$this->query->traverse($callback);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the inner expression objects.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$this->name = clone $this->name;
|
||||
if ($this->query) {
|
||||
$this->query = clone $this->query;
|
||||
}
|
||||
|
||||
foreach ($this->fields as $key => $field) {
|
||||
$this->fields[$key] = clone $field;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
deprecationWarning('Since 4.1.0: `Comparison` deprecated. Use `ComparisonExpression` instead.');
|
||||
class_exists('Cake\Database\Expression\ComparisonExpression');
|
||||
@@ -0,0 +1,324 @@
|
||||
<?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\Database\Expression;
|
||||
|
||||
use Cake\Database\Exception\DatabaseException;
|
||||
use Cake\Database\ExpressionInterface;
|
||||
use Cake\Database\Type\ExpressionTypeCasterTrait;
|
||||
use Cake\Database\ValueBinder;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* A Comparison is a type of query expression that represents an operation
|
||||
* involving a field an operator and a value. In its most common form the
|
||||
* string representation of a comparison is `field = value`
|
||||
*/
|
||||
class ComparisonExpression implements ExpressionInterface, FieldInterface
|
||||
{
|
||||
use ExpressionTypeCasterTrait;
|
||||
use FieldTrait;
|
||||
|
||||
/**
|
||||
* The value to be used in the right hand side of the operation
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $_value;
|
||||
|
||||
/**
|
||||
* The type to be used for casting the value to a database representation
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $_type;
|
||||
|
||||
/**
|
||||
* The operator used for comparing field and value
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_operator = '=';
|
||||
|
||||
/**
|
||||
* Whether the value in this expression is a traversable
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_isMultiple = false;
|
||||
|
||||
/**
|
||||
* A cached list of ExpressionInterface objects that were
|
||||
* found in the value for this expression.
|
||||
*
|
||||
* @var array<\Cake\Database\ExpressionInterface>
|
||||
*/
|
||||
protected $_valueExpressions = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $field the field name to compare to a value
|
||||
* @param mixed $value The value to be used in comparison
|
||||
* @param string|null $type the type name used to cast the value
|
||||
* @param string $operator the operator used for comparing field and value
|
||||
*/
|
||||
public function __construct($field, $value, ?string $type = null, string $operator = '=')
|
||||
{
|
||||
$this->_type = $type;
|
||||
$this->setField($field);
|
||||
$this->setValue($value);
|
||||
$this->_operator = $operator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value
|
||||
*
|
||||
* @param mixed $value The value to compare
|
||||
* @return void
|
||||
*/
|
||||
public function setValue($value): void
|
||||
{
|
||||
$value = $this->_castToExpression($value, $this->_type);
|
||||
|
||||
$isMultiple = $this->_type && strpos($this->_type, '[]') !== false;
|
||||
if ($isMultiple) {
|
||||
[$value, $this->_valueExpressions] = $this->_collectExpressions($value);
|
||||
}
|
||||
|
||||
$this->_isMultiple = $isMultiple;
|
||||
$this->_value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value used for comparison
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the operator to use for the comparison
|
||||
*
|
||||
* @param string $operator The operator to be used for the comparison.
|
||||
* @return void
|
||||
*/
|
||||
public function setOperator(string $operator): void
|
||||
{
|
||||
$this->_operator = $operator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the operator used for comparison
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOperator(): string
|
||||
{
|
||||
return $this->_operator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function sql(ValueBinder $binder): string
|
||||
{
|
||||
/** @var \Cake\Database\ExpressionInterface|string $field */
|
||||
$field = $this->_field;
|
||||
|
||||
if ($field instanceof ExpressionInterface) {
|
||||
$field = $field->sql($binder);
|
||||
}
|
||||
|
||||
if ($this->_value instanceof IdentifierExpression) {
|
||||
$template = '%s %s %s';
|
||||
$value = $this->_value->sql($binder);
|
||||
} elseif ($this->_value instanceof ExpressionInterface) {
|
||||
$template = '%s %s (%s)';
|
||||
$value = $this->_value->sql($binder);
|
||||
} else {
|
||||
[$template, $value] = $this->_stringExpression($binder);
|
||||
}
|
||||
|
||||
return sprintf($template, $field, $this->_operator, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function traverse(Closure $callback)
|
||||
{
|
||||
if ($this->_field instanceof ExpressionInterface) {
|
||||
$callback($this->_field);
|
||||
$this->_field->traverse($callback);
|
||||
}
|
||||
|
||||
if ($this->_value instanceof ExpressionInterface) {
|
||||
$callback($this->_value);
|
||||
$this->_value->traverse($callback);
|
||||
}
|
||||
|
||||
foreach ($this->_valueExpressions as $v) {
|
||||
$callback($v);
|
||||
$v->traverse($callback);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a deep clone.
|
||||
*
|
||||
* Clones the field and value if they are expression objects.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach (['_value', '_field'] as $prop) {
|
||||
if ($this->{$prop} instanceof ExpressionInterface) {
|
||||
$this->{$prop} = clone $this->{$prop};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a template and a placeholder for the value after registering it
|
||||
* with the placeholder $binder
|
||||
*
|
||||
* @param \Cake\Database\ValueBinder $binder The value binder to use.
|
||||
* @return array First position containing the template and the second a placeholder
|
||||
*/
|
||||
protected function _stringExpression(ValueBinder $binder): array
|
||||
{
|
||||
$template = '%s ';
|
||||
|
||||
if ($this->_field instanceof ExpressionInterface && !$this->_field instanceof IdentifierExpression) {
|
||||
$template = '(%s) ';
|
||||
}
|
||||
|
||||
if ($this->_isMultiple) {
|
||||
$template .= '%s (%s)';
|
||||
$type = $this->_type;
|
||||
if ($type !== null) {
|
||||
$type = str_replace('[]', '', $type);
|
||||
}
|
||||
$value = $this->_flattenValue($this->_value, $binder, $type);
|
||||
|
||||
// To avoid SQL errors when comparing a field to a list of empty values,
|
||||
// better just throw an exception here
|
||||
if ($value === '') {
|
||||
$field = $this->_field instanceof ExpressionInterface ? $this->_field->sql($binder) : $this->_field;
|
||||
/** @psalm-suppress PossiblyInvalidCast */
|
||||
throw new DatabaseException(
|
||||
"Impossible to generate condition with empty list of values for field ($field)"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$template .= '%s %s';
|
||||
$value = $this->_bindValue($this->_value, $binder, $this->_type);
|
||||
}
|
||||
|
||||
return [$template, $value];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a value in the placeholder generator and returns the generated placeholder
|
||||
*
|
||||
* @param mixed $value The value to bind
|
||||
* @param \Cake\Database\ValueBinder $binder The value binder to use
|
||||
* @param string|null $type The type of $value
|
||||
* @return string generated placeholder
|
||||
*/
|
||||
protected function _bindValue($value, ValueBinder $binder, ?string $type = null): string
|
||||
{
|
||||
$placeholder = $binder->placeholder('c');
|
||||
$binder->bind($placeholder, $value, $type);
|
||||
|
||||
return $placeholder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a traversable value into a set of placeholders generated by
|
||||
* $binder and separated by `,`
|
||||
*
|
||||
* @param iterable $value the value to flatten
|
||||
* @param \Cake\Database\ValueBinder $binder The value binder to use
|
||||
* @param string|null $type the type to cast values to
|
||||
* @return string
|
||||
*/
|
||||
protected function _flattenValue(iterable $value, ValueBinder $binder, ?string $type = null): string
|
||||
{
|
||||
$parts = [];
|
||||
if (is_array($value)) {
|
||||
foreach ($this->_valueExpressions as $k => $v) {
|
||||
$parts[$k] = $v->sql($binder);
|
||||
unset($value[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($value)) {
|
||||
$parts += $binder->generateManyNamed($value, $type);
|
||||
}
|
||||
|
||||
return implode(',', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with the original $values in the first position
|
||||
* and all ExpressionInterface objects that could be found in the second
|
||||
* position.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|iterable $values The rows to insert
|
||||
* @return array
|
||||
*/
|
||||
protected function _collectExpressions($values): array
|
||||
{
|
||||
if ($values instanceof ExpressionInterface) {
|
||||
return [$values, []];
|
||||
}
|
||||
|
||||
$expressions = $result = [];
|
||||
$isArray = is_array($values);
|
||||
|
||||
if ($isArray) {
|
||||
/** @var array $result */
|
||||
$result = $values;
|
||||
}
|
||||
|
||||
foreach ($values as $k => $v) {
|
||||
if ($v instanceof ExpressionInterface) {
|
||||
$expressions[$k] = $v;
|
||||
}
|
||||
|
||||
if ($isArray) {
|
||||
$result[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
return [$result, $expressions];
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
class_alias(
|
||||
'Cake\Database\Expression\ComparisonExpression',
|
||||
'Cake\Database\Expression\Comparison'
|
||||
);
|
||||
// phpcs:enable
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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\Database\Expression;
|
||||
|
||||
/**
|
||||
* Describes a getter and a setter for the a field property. Useful for expressions
|
||||
* that contain an identifier to compare against.
|
||||
*/
|
||||
interface FieldInterface
|
||||
{
|
||||
/**
|
||||
* Sets the field name
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|array|string $field The field to compare with.
|
||||
* @return void
|
||||
*/
|
||||
public function setField($field): void;
|
||||
|
||||
/**
|
||||
* Returns the field name
|
||||
*
|
||||
* @return \Cake\Database\ExpressionInterface|array|string
|
||||
*/
|
||||
public function getField();
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?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\Database\Expression;
|
||||
|
||||
/**
|
||||
* Contains the field property with a getter and a setter for it
|
||||
*/
|
||||
trait FieldTrait
|
||||
{
|
||||
/**
|
||||
* The field name or expression to be used in the left hand side of the operator
|
||||
*
|
||||
* @var \Cake\Database\ExpressionInterface|array|string
|
||||
*/
|
||||
protected $_field;
|
||||
|
||||
/**
|
||||
* Sets the field name
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|array|string $field The field to compare with.
|
||||
* @return void
|
||||
*/
|
||||
public function setField($field): void
|
||||
{
|
||||
$this->_field = $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field name
|
||||
*
|
||||
* @return \Cake\Database\ExpressionInterface|array|string
|
||||
*/
|
||||
public function getField()
|
||||
{
|
||||
return $this->_field;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
<?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\Database\Expression;
|
||||
|
||||
use Cake\Database\ExpressionInterface;
|
||||
use Cake\Database\Query;
|
||||
use Cake\Database\Type\ExpressionTypeCasterTrait;
|
||||
use Cake\Database\TypedResultInterface;
|
||||
use Cake\Database\TypedResultTrait;
|
||||
use Cake\Database\ValueBinder;
|
||||
|
||||
/**
|
||||
* This class represents a function call string in a SQL statement. Calls can be
|
||||
* constructed by passing the name of the function and a list of params.
|
||||
* For security reasons, all params passed are quoted by default unless
|
||||
* explicitly told otherwise.
|
||||
*/
|
||||
class FunctionExpression extends QueryExpression implements TypedResultInterface
|
||||
{
|
||||
use ExpressionTypeCasterTrait;
|
||||
use TypedResultTrait;
|
||||
|
||||
/**
|
||||
* The name of the function to be constructed when generating the SQL string
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_name;
|
||||
|
||||
/**
|
||||
* Constructor. Takes a name for the function to be invoked and a list of params
|
||||
* to be passed into the function. Optionally you can pass a list of types to
|
||||
* be used for each bound param.
|
||||
*
|
||||
* By default, all params that are passed will be quoted. If you wish to use
|
||||
* literal arguments, you need to explicitly hint this function.
|
||||
*
|
||||
* ### Examples:
|
||||
*
|
||||
* `$f = new FunctionExpression('CONCAT', ['CakePHP', ' rules']);`
|
||||
*
|
||||
* Previous line will generate `CONCAT('CakePHP', ' rules')`
|
||||
*
|
||||
* `$f = new FunctionExpression('CONCAT', ['name' => 'literal', ' rules']);`
|
||||
*
|
||||
* Will produce `CONCAT(name, ' rules')`
|
||||
*
|
||||
* @param string $name the name of the function to be constructed
|
||||
* @param array $params list of arguments to be passed to the function
|
||||
* If associative the key would be used as argument when value is 'literal'
|
||||
* @param array<string, string>|array<string|null> $types Associative array of types to be associated with the
|
||||
* passed arguments
|
||||
* @param string $returnType The return type of this expression
|
||||
*/
|
||||
public function __construct(string $name, array $params = [], array $types = [], string $returnType = 'string')
|
||||
{
|
||||
$this->_name = $name;
|
||||
$this->_returnType = $returnType;
|
||||
parent::__construct($params, $types, ',');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the SQL function to be invoke in this expression.
|
||||
*
|
||||
* @param string $name The name of the function
|
||||
* @return $this
|
||||
*/
|
||||
public function setName(string $name)
|
||||
{
|
||||
$this->_name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the SQL function to be invoke in this expression.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one or more arguments for the function call.
|
||||
*
|
||||
* @param array $conditions list of arguments to be passed to the function
|
||||
* If associative the key would be used as argument when value is 'literal'
|
||||
* @param array<string, string> $types Associative array of types to be associated with the
|
||||
* passed arguments
|
||||
* @param bool $prepend Whether to prepend or append to the list of arguments
|
||||
* @see \Cake\Database\Expression\FunctionExpression::__construct() for more details.
|
||||
* @return $this
|
||||
* @psalm-suppress MoreSpecificImplementedParamType
|
||||
*/
|
||||
public function add($conditions, array $types = [], bool $prepend = false)
|
||||
{
|
||||
$put = $prepend ? 'array_unshift' : 'array_push';
|
||||
$typeMap = $this->getTypeMap()->setTypes($types);
|
||||
foreach ($conditions as $k => $p) {
|
||||
if ($p === 'literal') {
|
||||
$put($this->_conditions, $k);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($p === 'identifier') {
|
||||
$put($this->_conditions, new IdentifierExpression($k));
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $typeMap->type($k);
|
||||
|
||||
if ($type !== null && !$p instanceof ExpressionInterface) {
|
||||
$p = $this->_castToExpression($p, $type);
|
||||
}
|
||||
|
||||
if ($p instanceof ExpressionInterface) {
|
||||
$put($this->_conditions, $p);
|
||||
continue;
|
||||
}
|
||||
|
||||
$put($this->_conditions, ['value' => $p, 'type' => $type]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function sql(ValueBinder $binder): string
|
||||
{
|
||||
$parts = [];
|
||||
foreach ($this->_conditions as $condition) {
|
||||
if ($condition instanceof Query) {
|
||||
$condition = sprintf('(%s)', $condition->sql($binder));
|
||||
} elseif ($condition instanceof ExpressionInterface) {
|
||||
$condition = $condition->sql($binder);
|
||||
} elseif (is_array($condition)) {
|
||||
$p = $binder->placeholder('param');
|
||||
$binder->bind($p, $condition['value'], $condition['type']);
|
||||
$condition = $p;
|
||||
}
|
||||
$parts[] = $condition;
|
||||
}
|
||||
|
||||
return $this->_name . sprintf('(%s)', implode(
|
||||
$this->_conjunction . ' ',
|
||||
$parts
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the function is in itself an expression to generate, thus
|
||||
* always adding 1 to the amount of expressions stored in this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return 1 + count($this->_conditions);
|
||||
}
|
||||
}
|
||||
@@ -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 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Expression;
|
||||
|
||||
use Cake\Database\ExpressionInterface;
|
||||
use Cake\Database\ValueBinder;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* Represents a single identifier name in the database.
|
||||
*
|
||||
* Identifier values are unsafe with user supplied data.
|
||||
* Values will be quoted when identifier quoting is enabled.
|
||||
*
|
||||
* @see \Cake\Database\Query::identifier()
|
||||
*/
|
||||
class IdentifierExpression implements ExpressionInterface
|
||||
{
|
||||
/**
|
||||
* Holds the identifier string
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_identifier;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $collation;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $identifier The identifier this expression represents
|
||||
* @param string|null $collation The identifier collation
|
||||
*/
|
||||
public function __construct(string $identifier, ?string $collation = null)
|
||||
{
|
||||
$this->_identifier = $identifier;
|
||||
$this->collation = $collation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the identifier this expression represents
|
||||
*
|
||||
* @param string $identifier The identifier
|
||||
* @return void
|
||||
*/
|
||||
public function setIdentifier(string $identifier): void
|
||||
{
|
||||
$this->_identifier = $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier this expression represents
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return $this->_identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the collation.
|
||||
*
|
||||
* @param string $collation Identifier collation
|
||||
* @return void
|
||||
*/
|
||||
public function setCollation(string $collation): void
|
||||
{
|
||||
$this->collation = $collation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the collation.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCollation(): ?string
|
||||
{
|
||||
return $this->collation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function sql(ValueBinder $binder): string
|
||||
{
|
||||
$sql = $this->_identifier;
|
||||
if ($this->collation) {
|
||||
$sql .= ' COLLATE ' . $this->collation;
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function traverse(Closure $callback)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?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\Database\Expression;
|
||||
|
||||
use Cake\Database\ExpressionInterface;
|
||||
use Cake\Database\ValueBinder;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* An expression object for ORDER BY clauses
|
||||
*/
|
||||
class OrderByExpression extends QueryExpression
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|array|string $conditions The sort columns
|
||||
* @param \Cake\Database\TypeMap|array<string, string> $types The types for each column.
|
||||
* @param string $conjunction The glue used to join conditions together.
|
||||
*/
|
||||
public function __construct($conditions = [], $types = [], $conjunction = '')
|
||||
{
|
||||
parent::__construct($conditions, $types, $conjunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function sql(ValueBinder $binder): string
|
||||
{
|
||||
$order = [];
|
||||
foreach ($this->_conditions as $k => $direction) {
|
||||
if ($direction instanceof ExpressionInterface) {
|
||||
$direction = $direction->sql($binder);
|
||||
}
|
||||
$order[] = is_numeric($k) ? $direction : sprintf('%s %s', $k, $direction);
|
||||
}
|
||||
|
||||
return sprintf('ORDER BY %s', implode(', ', $order));
|
||||
}
|
||||
|
||||
/**
|
||||
* Auxiliary function used for decomposing a nested array of conditions and
|
||||
* building a tree structure inside this object to represent the full SQL expression.
|
||||
*
|
||||
* New order by expressions are merged to existing ones
|
||||
*
|
||||
* @param array $conditions list of order by expressions
|
||||
* @param array $types list of types associated on fields referenced in $conditions
|
||||
* @return void
|
||||
*/
|
||||
protected function _addConditions(array $conditions, array $types): void
|
||||
{
|
||||
foreach ($conditions as $key => $val) {
|
||||
if (
|
||||
is_string($key) &&
|
||||
is_string($val) &&
|
||||
!in_array(strtoupper($val), ['ASC', 'DESC'], true)
|
||||
) {
|
||||
throw new RuntimeException(
|
||||
sprintf(
|
||||
'Passing extra expressions by associative array (`\'%s\' => \'%s\'`) ' .
|
||||
'is not allowed to avoid potential SQL injection. ' .
|
||||
'Use QueryExpression or numeric array instead.',
|
||||
$key,
|
||||
$val
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->_conditions = array_merge($this->_conditions, $conditions);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?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\Database\Expression;
|
||||
|
||||
use Cake\Database\ExpressionInterface;
|
||||
use Cake\Database\Query;
|
||||
use Cake\Database\ValueBinder;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* An expression object for complex ORDER BY clauses
|
||||
*/
|
||||
class OrderClauseExpression implements ExpressionInterface, FieldInterface
|
||||
{
|
||||
use FieldTrait;
|
||||
|
||||
/**
|
||||
* The direction of sorting.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_direction;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $field The field to order on.
|
||||
* @param string $direction The direction to sort on.
|
||||
*/
|
||||
public function __construct($field, $direction)
|
||||
{
|
||||
$this->_field = $field;
|
||||
$this->_direction = strtolower($direction) === 'asc' ? 'ASC' : 'DESC';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function sql(ValueBinder $binder): string
|
||||
{
|
||||
/** @var \Cake\Database\ExpressionInterface|string $field */
|
||||
$field = $this->_field;
|
||||
if ($field instanceof Query) {
|
||||
$field = sprintf('(%s)', $field->sql($binder));
|
||||
} elseif ($field instanceof ExpressionInterface) {
|
||||
$field = $field->sql($binder);
|
||||
}
|
||||
|
||||
return sprintf('%s %s', $field, $this->_direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function traverse(Closure $callback)
|
||||
{
|
||||
if ($this->_field instanceof ExpressionInterface) {
|
||||
$callback($this->_field);
|
||||
$this->_field->traverse($callback);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a deep clone of the order clause.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->_field instanceof ExpressionInterface) {
|
||||
$this->_field = clone $this->_field;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,876 @@
|
||||
<?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\Database\Expression;
|
||||
|
||||
use Cake\Database\ExpressionInterface;
|
||||
use Cake\Database\Query;
|
||||
use Cake\Database\TypeMapTrait;
|
||||
use Cake\Database\ValueBinder;
|
||||
use Closure;
|
||||
use Countable;
|
||||
use InvalidArgumentException;
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
/**
|
||||
* Represents a SQL Query expression. Internally it stores a tree of
|
||||
* expressions that can be compiled by converting this object to string
|
||||
* and will contain a correctly parenthesized and nested expression.
|
||||
*/
|
||||
class QueryExpression implements ExpressionInterface, Countable
|
||||
{
|
||||
use TypeMapTrait;
|
||||
|
||||
/**
|
||||
* String to be used for joining each of the internal expressions
|
||||
* this object internally stores for example "AND", "OR", etc.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_conjunction;
|
||||
|
||||
/**
|
||||
* A list of strings or other expression objects that represent the "branches" of
|
||||
* the expression tree. For example one key of the array might look like "sum > :value"
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_conditions = [];
|
||||
|
||||
/**
|
||||
* Constructor. A new expression object can be created without any params and
|
||||
* be built dynamically. Otherwise, it is possible to pass an array of conditions
|
||||
* containing either a tree-like array structure to be parsed and/or other
|
||||
* expression objects. Optionally, you can set the conjunction keyword to be used
|
||||
* for joining each part of this level of the expression tree.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|array|string $conditions Tree like array structure
|
||||
* containing all the conditions to be added or nested inside this expression object.
|
||||
* @param \Cake\Database\TypeMap|array $types Associative array of types to be associated with the values
|
||||
* passed in $conditions.
|
||||
* @param string $conjunction the glue that will join all the string conditions at this
|
||||
* level of the expression tree. For example "AND", "OR", "XOR"...
|
||||
* @see \Cake\Database\Expression\QueryExpression::add() for more details on $conditions and $types
|
||||
*/
|
||||
public function __construct($conditions = [], $types = [], $conjunction = 'AND')
|
||||
{
|
||||
$this->setTypeMap($types);
|
||||
$this->setConjunction(strtoupper($conjunction));
|
||||
if (!empty($conditions)) {
|
||||
$this->add($conditions, $this->getTypeMap()->getTypes());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the conjunction for the conditions at this level of the expression tree.
|
||||
*
|
||||
* @param string $conjunction Value to be used for joining conditions
|
||||
* @return $this
|
||||
*/
|
||||
public function setConjunction(string $conjunction)
|
||||
{
|
||||
$this->_conjunction = strtoupper($conjunction);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the currently configured conjunction for the conditions at this level of the expression tree.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getConjunction(): string
|
||||
{
|
||||
return $this->_conjunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one or more conditions to this expression object. Conditions can be
|
||||
* expressed in a one dimensional array, that will cause all conditions to
|
||||
* be added directly at this level of the tree or they can be nested arbitrarily
|
||||
* making it create more expression objects that will be nested inside and
|
||||
* configured to use the specified conjunction.
|
||||
*
|
||||
* If the type passed for any of the fields is expressed "type[]" (note braces)
|
||||
* then it will cause the placeholder to be re-written dynamically so if the
|
||||
* value is an array, it will create as many placeholders as values are in it.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|array|string $conditions single or multiple conditions to
|
||||
* be added. When using an array and the key is 'OR' or 'AND' a new expression
|
||||
* object will be created with that conjunction and internal array value passed
|
||||
* as conditions.
|
||||
* @param array<int|string, string> $types Associative array of fields pointing to the type of the
|
||||
* values that are being passed. Used for correctly binding values to statements.
|
||||
* @see \Cake\Database\Query::where() for examples on conditions
|
||||
* @return $this
|
||||
*/
|
||||
public function add($conditions, array $types = [])
|
||||
{
|
||||
if (is_string($conditions) || $conditions instanceof ExpressionInterface) {
|
||||
$this->_conditions[] = $conditions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->_addConditions($conditions, $types);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new condition to the expression object in the form "field = value".
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $field Database field to be compared against value
|
||||
* @param mixed $value The value to be bound to $field for comparison
|
||||
* @param string|null $type the type name for $value as configured using the Type map.
|
||||
* If it is suffixed with "[]" and the value is an array then multiple placeholders
|
||||
* will be created, one per each value in the array.
|
||||
* @return $this
|
||||
*/
|
||||
public function eq($field, $value, ?string $type = null)
|
||||
{
|
||||
if ($type === null) {
|
||||
$type = $this->_calculateType($field);
|
||||
}
|
||||
|
||||
return $this->add(new ComparisonExpression($field, $value, $type, '='));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new condition to the expression object in the form "field != value".
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $field Database field to be compared against value
|
||||
* @param mixed $value The value to be bound to $field for comparison
|
||||
* @param string|null $type the type name for $value as configured using the Type map.
|
||||
* If it is suffixed with "[]" and the value is an array then multiple placeholders
|
||||
* will be created, one per each value in the array.
|
||||
* @return $this
|
||||
*/
|
||||
public function notEq($field, $value, $type = null)
|
||||
{
|
||||
if ($type === null) {
|
||||
$type = $this->_calculateType($field);
|
||||
}
|
||||
|
||||
return $this->add(new ComparisonExpression($field, $value, $type, '!='));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new condition to the expression object in the form "field > value".
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $field Database field to be compared against value
|
||||
* @param mixed $value The value to be bound to $field for comparison
|
||||
* @param string|null $type the type name for $value as configured using the Type map.
|
||||
* @return $this
|
||||
*/
|
||||
public function gt($field, $value, $type = null)
|
||||
{
|
||||
if ($type === null) {
|
||||
$type = $this->_calculateType($field);
|
||||
}
|
||||
|
||||
return $this->add(new ComparisonExpression($field, $value, $type, '>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new condition to the expression object in the form "field < value".
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $field Database field to be compared against value
|
||||
* @param mixed $value The value to be bound to $field for comparison
|
||||
* @param string|null $type the type name for $value as configured using the Type map.
|
||||
* @return $this
|
||||
*/
|
||||
public function lt($field, $value, $type = null)
|
||||
{
|
||||
if ($type === null) {
|
||||
$type = $this->_calculateType($field);
|
||||
}
|
||||
|
||||
return $this->add(new ComparisonExpression($field, $value, $type, '<'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new condition to the expression object in the form "field >= value".
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $field Database field to be compared against value
|
||||
* @param mixed $value The value to be bound to $field for comparison
|
||||
* @param string|null $type the type name for $value as configured using the Type map.
|
||||
* @return $this
|
||||
*/
|
||||
public function gte($field, $value, $type = null)
|
||||
{
|
||||
if ($type === null) {
|
||||
$type = $this->_calculateType($field);
|
||||
}
|
||||
|
||||
return $this->add(new ComparisonExpression($field, $value, $type, '>='));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new condition to the expression object in the form "field <= value".
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $field Database field to be compared against value
|
||||
* @param mixed $value The value to be bound to $field for comparison
|
||||
* @param string|null $type the type name for $value as configured using the Type map.
|
||||
* @return $this
|
||||
*/
|
||||
public function lte($field, $value, $type = null)
|
||||
{
|
||||
if ($type === null) {
|
||||
$type = $this->_calculateType($field);
|
||||
}
|
||||
|
||||
return $this->add(new ComparisonExpression($field, $value, $type, '<='));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new condition to the expression object in the form "field IS NULL".
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $field database field to be
|
||||
* tested for null
|
||||
* @return $this
|
||||
*/
|
||||
public function isNull($field)
|
||||
{
|
||||
if (!($field instanceof ExpressionInterface)) {
|
||||
$field = new IdentifierExpression($field);
|
||||
}
|
||||
|
||||
return $this->add(new UnaryExpression('IS NULL', $field, UnaryExpression::POSTFIX));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new condition to the expression object in the form "field IS NOT NULL".
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $field database field to be
|
||||
* tested for not null
|
||||
* @return $this
|
||||
*/
|
||||
public function isNotNull($field)
|
||||
{
|
||||
if (!($field instanceof ExpressionInterface)) {
|
||||
$field = new IdentifierExpression($field);
|
||||
}
|
||||
|
||||
return $this->add(new UnaryExpression('IS NOT NULL', $field, UnaryExpression::POSTFIX));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new condition to the expression object in the form "field LIKE value".
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $field Database field to be compared against value
|
||||
* @param mixed $value The value to be bound to $field for comparison
|
||||
* @param string|null $type the type name for $value as configured using the Type map.
|
||||
* @return $this
|
||||
*/
|
||||
public function like($field, $value, $type = null)
|
||||
{
|
||||
if ($type === null) {
|
||||
$type = $this->_calculateType($field);
|
||||
}
|
||||
|
||||
return $this->add(new ComparisonExpression($field, $value, $type, 'LIKE'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new condition to the expression object in the form "field NOT LIKE value".
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $field Database field to be compared against value
|
||||
* @param mixed $value The value to be bound to $field for comparison
|
||||
* @param string|null $type the type name for $value as configured using the Type map.
|
||||
* @return $this
|
||||
*/
|
||||
public function notLike($field, $value, $type = null)
|
||||
{
|
||||
if ($type === null) {
|
||||
$type = $this->_calculateType($field);
|
||||
}
|
||||
|
||||
return $this->add(new ComparisonExpression($field, $value, $type, 'NOT LIKE'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new condition to the expression object in the form
|
||||
* "field IN (value1, value2)".
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $field Database field to be compared against value
|
||||
* @param \Cake\Database\ExpressionInterface|array|string $values the value to be bound to $field for comparison
|
||||
* @param string|null $type the type name for $value as configured using the Type map.
|
||||
* @return $this
|
||||
*/
|
||||
public function in($field, $values, $type = null)
|
||||
{
|
||||
if ($type === null) {
|
||||
$type = $this->_calculateType($field);
|
||||
}
|
||||
$type = $type ?: 'string';
|
||||
$type .= '[]';
|
||||
$values = $values instanceof ExpressionInterface ? $values : (array)$values;
|
||||
|
||||
return $this->add(new ComparisonExpression($field, $values, $type, 'IN'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new case expression to the expression object
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|array $conditions The conditions to test. Must be a ExpressionInterface
|
||||
* instance, or an array of ExpressionInterface instances.
|
||||
* @param \Cake\Database\ExpressionInterface|array $values Associative array of values to be associated with the
|
||||
* conditions passed in $conditions. If there are more $values than $conditions,
|
||||
* the last $value is used as the `ELSE` value.
|
||||
* @param array<string> $types Associative array of types to be associated with the values
|
||||
* passed in $values
|
||||
* @return $this
|
||||
* @deprecated 4.3.0 Use QueryExpression::case() or CaseStatementExpression instead
|
||||
*/
|
||||
public function addCase($conditions, $values = [], $types = [])
|
||||
{
|
||||
deprecationWarning('QueryExpression::addCase() is deprecated, use case() instead.');
|
||||
|
||||
return $this->add(new CaseExpression($conditions, $values, $types));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new case expression object.
|
||||
*
|
||||
* When a value is set, the syntax generated is
|
||||
* `CASE case_value WHEN when_value ... END` (simple case),
|
||||
* where the `when_value`'s are compared against the
|
||||
* `case_value`.
|
||||
*
|
||||
* When no value is set, the syntax generated is
|
||||
* `CASE WHEN when_conditions ... END` (searched case),
|
||||
* where the conditions hold the comparisons.
|
||||
*
|
||||
* Note that `null` is a valid case value, and thus should
|
||||
* only be passed if you actually want to create the simple
|
||||
* case expression variant!
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|object|scalar|null $value The case value.
|
||||
* @param string|null $type The case value type. If no type is provided, the type will be tried to be inferred
|
||||
* from the value.
|
||||
* @return \Cake\Database\Expression\CaseStatementExpression
|
||||
*/
|
||||
public function case($value = null, ?string $type = null): CaseStatementExpression
|
||||
{
|
||||
if (func_num_args() > 0) {
|
||||
$expression = new CaseStatementExpression($value, $type);
|
||||
} else {
|
||||
$expression = new CaseStatementExpression();
|
||||
}
|
||||
|
||||
return $expression->setTypeMap($this->getTypeMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new condition to the expression object in the form
|
||||
* "field NOT IN (value1, value2)".
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $field Database field to be compared against value
|
||||
* @param \Cake\Database\ExpressionInterface|array|string $values the value to be bound to $field for comparison
|
||||
* @param string|null $type the type name for $value as configured using the Type map.
|
||||
* @return $this
|
||||
*/
|
||||
public function notIn($field, $values, $type = null)
|
||||
{
|
||||
if ($type === null) {
|
||||
$type = $this->_calculateType($field);
|
||||
}
|
||||
$type = $type ?: 'string';
|
||||
$type .= '[]';
|
||||
$values = $values instanceof ExpressionInterface ? $values : (array)$values;
|
||||
|
||||
return $this->add(new ComparisonExpression($field, $values, $type, 'NOT IN'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new condition to the expression object in the form
|
||||
* "(field NOT IN (value1, value2) OR field IS NULL".
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $field Database field to be compared against value
|
||||
* @param \Cake\Database\ExpressionInterface|array|string $values the value to be bound to $field for comparison
|
||||
* @param string|null $type the type name for $value as configured using the Type map.
|
||||
* @return $this
|
||||
*/
|
||||
public function notInOrNull($field, $values, ?string $type = null)
|
||||
{
|
||||
$or = new static([], [], 'OR');
|
||||
$or
|
||||
->notIn($field, $values, $type)
|
||||
->isNull($field);
|
||||
|
||||
return $this->add($or);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new condition to the expression object in the form "EXISTS (...)".
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface $expression the inner query
|
||||
* @return $this
|
||||
*/
|
||||
public function exists(ExpressionInterface $expression)
|
||||
{
|
||||
return $this->add(new UnaryExpression('EXISTS', $expression, UnaryExpression::PREFIX));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new condition to the expression object in the form "NOT EXISTS (...)".
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface $expression the inner query
|
||||
* @return $this
|
||||
*/
|
||||
public function notExists(ExpressionInterface $expression)
|
||||
{
|
||||
return $this->add(new UnaryExpression('NOT EXISTS', $expression, UnaryExpression::PREFIX));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new condition to the expression object in the form
|
||||
* "field BETWEEN from AND to".
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $field The field name to compare for values inbetween the range.
|
||||
* @param mixed $from The initial value of the range.
|
||||
* @param mixed $to The ending value in the comparison range.
|
||||
* @param string|null $type the type name for $value as configured using the Type map.
|
||||
* @return $this
|
||||
*/
|
||||
public function between($field, $from, $to, $type = null)
|
||||
{
|
||||
if ($type === null) {
|
||||
$type = $this->_calculateType($field);
|
||||
}
|
||||
|
||||
return $this->add(new BetweenExpression($field, $from, $to, $type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new QueryExpression object containing all the conditions passed
|
||||
* and set up the conjunction to be "AND"
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|\Closure|array|string $conditions to be joined with AND
|
||||
* @param array<string, string> $types Associative array of fields pointing to the type of the
|
||||
* values that are being passed. Used for correctly binding values to statements.
|
||||
* @return \Cake\Database\Expression\QueryExpression
|
||||
*/
|
||||
public function and($conditions, $types = [])
|
||||
{
|
||||
if ($conditions instanceof Closure) {
|
||||
return $conditions(new static([], $this->getTypeMap()->setTypes($types)));
|
||||
}
|
||||
|
||||
return new static($conditions, $this->getTypeMap()->setTypes($types));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new QueryExpression object containing all the conditions passed
|
||||
* and set up the conjunction to be "OR"
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|\Closure|array|string $conditions to be joined with OR
|
||||
* @param array<string, string> $types Associative array of fields pointing to the type of the
|
||||
* values that are being passed. Used for correctly binding values to statements.
|
||||
* @return \Cake\Database\Expression\QueryExpression
|
||||
*/
|
||||
public function or($conditions, $types = [])
|
||||
{
|
||||
if ($conditions instanceof Closure) {
|
||||
return $conditions(new static([], $this->getTypeMap()->setTypes($types), 'OR'));
|
||||
}
|
||||
|
||||
return new static($conditions, $this->getTypeMap()->setTypes($types), 'OR');
|
||||
}
|
||||
|
||||
// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
|
||||
/**
|
||||
* Returns a new QueryExpression object containing all the conditions passed
|
||||
* and set up the conjunction to be "AND"
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|\Closure|array|string $conditions to be joined with AND
|
||||
* @param array<string, string> $types Associative array of fields pointing to the type of the
|
||||
* values that are being passed. Used for correctly binding values to statements.
|
||||
* @return \Cake\Database\Expression\QueryExpression
|
||||
* @deprecated 4.0.0 Use {@link and()} instead.
|
||||
*/
|
||||
public function and_($conditions, $types = [])
|
||||
{
|
||||
deprecationWarning('QueryExpression::and_() is deprecated use and() instead.');
|
||||
|
||||
return $this->and($conditions, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new QueryExpression object containing all the conditions passed
|
||||
* and set up the conjunction to be "OR"
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|\Closure|array|string $conditions to be joined with OR
|
||||
* @param array<string, string> $types Associative array of fields pointing to the type of the
|
||||
* values that are being passed. Used for correctly binding values to statements.
|
||||
* @return \Cake\Database\Expression\QueryExpression
|
||||
* @deprecated 4.0.0 Use {@link or()} instead.
|
||||
*/
|
||||
public function or_($conditions, $types = [])
|
||||
{
|
||||
deprecationWarning('QueryExpression::or_() is deprecated use or() instead.');
|
||||
|
||||
return $this->or($conditions, $types);
|
||||
}
|
||||
|
||||
// phpcs:enable
|
||||
|
||||
/**
|
||||
* Adds a new set of conditions to this level of the tree and negates
|
||||
* the final result by prepending a NOT, it will look like
|
||||
* "NOT ( (condition1) AND (conditions2) )" conjunction depends on the one
|
||||
* currently configured for this object.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|\Closure|array|string $conditions to be added and negated
|
||||
* @param array<string, string> $types Associative array of fields pointing to the type of the
|
||||
* values that are being passed. Used for correctly binding values to statements.
|
||||
* @return $this
|
||||
*/
|
||||
public function not($conditions, $types = [])
|
||||
{
|
||||
return $this->add(['NOT' => $conditions], $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of internal conditions that are stored in this expression.
|
||||
* Useful to determine if this expression object is void or it will generate
|
||||
* a non-empty string when compiled
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->_conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds equal condition or assignment with identifier wrapping.
|
||||
*
|
||||
* @param string $leftField Left join condition field name.
|
||||
* @param string $rightField Right join condition field name.
|
||||
* @return $this
|
||||
*/
|
||||
public function equalFields(string $leftField, string $rightField)
|
||||
{
|
||||
$wrapIdentifier = function ($field) {
|
||||
if ($field instanceof ExpressionInterface) {
|
||||
return $field;
|
||||
}
|
||||
|
||||
return new IdentifierExpression($field);
|
||||
};
|
||||
|
||||
return $this->eq($wrapIdentifier($leftField), $wrapIdentifier($rightField));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function sql(ValueBinder $binder): string
|
||||
{
|
||||
$len = $this->count();
|
||||
if ($len === 0) {
|
||||
return '';
|
||||
}
|
||||
$conjunction = $this->_conjunction;
|
||||
$template = $len === 1 ? '%s' : '(%s)';
|
||||
$parts = [];
|
||||
foreach ($this->_conditions as $part) {
|
||||
if ($part instanceof Query) {
|
||||
$part = '(' . $part->sql($binder) . ')';
|
||||
} elseif ($part instanceof ExpressionInterface) {
|
||||
$part = $part->sql($binder);
|
||||
}
|
||||
if ($part !== '') {
|
||||
$parts[] = $part;
|
||||
}
|
||||
}
|
||||
|
||||
return sprintf($template, implode(" $conjunction ", $parts));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function traverse(Closure $callback)
|
||||
{
|
||||
foreach ($this->_conditions as $c) {
|
||||
if ($c instanceof ExpressionInterface) {
|
||||
$callback($c);
|
||||
$c->traverse($callback);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a callable function for each of the parts that form this expression.
|
||||
*
|
||||
* The callable function is required to return a value with which the currently
|
||||
* visited part will be replaced. If the callable function returns null then
|
||||
* the part will be discarded completely from this expression.
|
||||
*
|
||||
* The callback function will receive each of the conditions as first param and
|
||||
* the key as second param. It is possible to declare the second parameter as
|
||||
* passed by reference, this will enable you to change the key under which the
|
||||
* modified part is stored.
|
||||
*
|
||||
* @param callable $callback The callable to apply to each part.
|
||||
* @return $this
|
||||
*/
|
||||
public function iterateParts(callable $callback)
|
||||
{
|
||||
$parts = [];
|
||||
foreach ($this->_conditions as $k => $c) {
|
||||
$key = &$k;
|
||||
$part = $callback($c, $key);
|
||||
if ($part !== null) {
|
||||
$parts[$key] = $part;
|
||||
}
|
||||
}
|
||||
$this->_conditions = $parts;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a callable is acceptable.
|
||||
*
|
||||
* We don't accept ['class', 'method'] style callbacks,
|
||||
* as they often contain user input and arrays of strings
|
||||
* are easy to sneak in.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|callable|array|string $callable The callable to check.
|
||||
* @return bool Valid callable.
|
||||
* @deprecated 4.2.0 This method is unused.
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function isCallable($callable): bool
|
||||
{
|
||||
if (is_string($callable)) {
|
||||
return false;
|
||||
}
|
||||
if (is_object($callable) && is_callable($callable)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return is_array($callable) && isset($callable[0]) && is_object($callable[0]) && is_callable($callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this expression contains any other nested
|
||||
* ExpressionInterface objects
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasNestedExpression(): bool
|
||||
{
|
||||
foreach ($this->_conditions as $c) {
|
||||
if ($c instanceof ExpressionInterface) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auxiliary function used for decomposing a nested array of conditions and build
|
||||
* a tree structure inside this object to represent the full SQL expression.
|
||||
* String conditions are stored directly in the conditions, while any other
|
||||
* representation is wrapped around an adequate instance or of this class.
|
||||
*
|
||||
* @param array $conditions list of conditions to be stored in this object
|
||||
* @param array<int|string, string> $types list of types associated on fields referenced in $conditions
|
||||
* @return void
|
||||
*/
|
||||
protected function _addConditions(array $conditions, array $types): void
|
||||
{
|
||||
$operators = ['and', 'or', 'xor'];
|
||||
|
||||
$typeMap = $this->getTypeMap()->setTypes($types);
|
||||
|
||||
foreach ($conditions as $k => $c) {
|
||||
$numericKey = is_numeric($k);
|
||||
|
||||
if ($c instanceof Closure) {
|
||||
$expr = new static([], $typeMap);
|
||||
$c = $c($expr, $this);
|
||||
}
|
||||
|
||||
if ($numericKey && empty($c)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isArray = is_array($c);
|
||||
$isOperator = $isNot = false;
|
||||
if (!$numericKey) {
|
||||
$normalizedKey = strtolower($k);
|
||||
$isOperator = in_array($normalizedKey, $operators);
|
||||
$isNot = $normalizedKey === 'not';
|
||||
}
|
||||
|
||||
if (($isOperator || $isNot) && ($isArray || $c instanceof Countable) && count($c) === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($numericKey && $c instanceof ExpressionInterface) {
|
||||
$this->_conditions[] = $c;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($numericKey && is_string($c)) {
|
||||
$this->_conditions[] = $c;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($numericKey && $isArray || $isOperator) {
|
||||
$this->_conditions[] = new static($c, $typeMap, $numericKey ? 'AND' : $k);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($isNot) {
|
||||
$this->_conditions[] = new UnaryExpression('NOT', new static($c, $typeMap));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$numericKey) {
|
||||
$this->_conditions[] = $this->_parseCondition($k, $c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string conditions by trying to extract the operator inside it if any
|
||||
* and finally returning either an adequate QueryExpression object or a plain
|
||||
* string representation of the condition. This function is responsible for
|
||||
* generating the placeholders and replacing the values by them, while storing
|
||||
* the value elsewhere for future binding.
|
||||
*
|
||||
* @param string $field The value from which the actual field and operator will
|
||||
* be extracted.
|
||||
* @param mixed $value The value to be bound to a placeholder for the field
|
||||
* @return \Cake\Database\ExpressionInterface
|
||||
* @throws \InvalidArgumentException If operator is invalid or missing on NULL usage.
|
||||
*/
|
||||
protected function _parseCondition(string $field, $value)
|
||||
{
|
||||
$field = trim($field);
|
||||
$operator = '=';
|
||||
$expression = $field;
|
||||
|
||||
$spaces = substr_count($field, ' ');
|
||||
// Handle field values that contain multiple spaces, such as
|
||||
// operators with a space in them like `field IS NOT` and
|
||||
// `field NOT LIKE`, or combinations with function expressions
|
||||
// like `CONCAT(first_name, ' ', last_name) IN`.
|
||||
if ($spaces > 1) {
|
||||
$parts = explode(' ', $field);
|
||||
if (preg_match('/(is not|not \w+)$/i', $field)) {
|
||||
$last = array_pop($parts);
|
||||
$second = array_pop($parts);
|
||||
$parts[] = "{$second} {$last}";
|
||||
}
|
||||
$operator = array_pop($parts);
|
||||
$expression = implode(' ', $parts);
|
||||
} elseif ($spaces == 1) {
|
||||
$parts = explode(' ', $field, 2);
|
||||
[$expression, $operator] = $parts;
|
||||
}
|
||||
$operator = strtolower(trim($operator));
|
||||
$type = $this->getTypeMap()->type($expression);
|
||||
|
||||
$typeMultiple = (is_string($type) && strpos($type, '[]') !== false);
|
||||
if (in_array($operator, ['in', 'not in']) || $typeMultiple) {
|
||||
$type = $type ?: 'string';
|
||||
if (!$typeMultiple) {
|
||||
$type .= '[]';
|
||||
}
|
||||
$operator = $operator === '=' ? 'IN' : $operator;
|
||||
$operator = $operator === '!=' ? 'NOT IN' : $operator;
|
||||
$typeMultiple = true;
|
||||
}
|
||||
|
||||
if ($typeMultiple) {
|
||||
$value = $value instanceof ExpressionInterface ? $value : (array)$value;
|
||||
}
|
||||
|
||||
if ($operator === 'is' && $value === null) {
|
||||
return new UnaryExpression(
|
||||
'IS NULL',
|
||||
new IdentifierExpression($expression),
|
||||
UnaryExpression::POSTFIX
|
||||
);
|
||||
}
|
||||
|
||||
if ($operator === 'is not' && $value === null) {
|
||||
return new UnaryExpression(
|
||||
'IS NOT NULL',
|
||||
new IdentifierExpression($expression),
|
||||
UnaryExpression::POSTFIX
|
||||
);
|
||||
}
|
||||
|
||||
if ($operator === 'is' && $value !== null) {
|
||||
$operator = '=';
|
||||
}
|
||||
|
||||
if ($operator === 'is not' && $value !== null) {
|
||||
$operator = '!=';
|
||||
}
|
||||
|
||||
if ($value === null && $this->_conjunction !== ',') {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf('Expression `%s` is missing operator (IS, IS NOT) with `null` value.', $expression)
|
||||
);
|
||||
}
|
||||
|
||||
return new ComparisonExpression($expression, $value, $type, $operator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type name for the passed field if it was stored in the typeMap
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $field The field name to get a type for.
|
||||
* @return string|null The computed type or null, if the type is unknown.
|
||||
*/
|
||||
protected function _calculateType($field): ?string
|
||||
{
|
||||
$field = $field instanceof IdentifierExpression ? $field->getIdentifier() : $field;
|
||||
if (is_string($field)) {
|
||||
return $this->getTypeMap()->type($field);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone this object and its subtree of expressions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach ($this->_conditions as $i => $condition) {
|
||||
if ($condition instanceof ExpressionInterface) {
|
||||
$this->_conditions[$i] = clone $condition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?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.2.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Expression;
|
||||
|
||||
use Cake\Database\ExpressionInterface;
|
||||
use Cake\Database\ValueBinder;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* String expression with collation.
|
||||
*/
|
||||
class StringExpression implements ExpressionInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $string;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $collation;
|
||||
|
||||
/**
|
||||
* @param string $string String value
|
||||
* @param string $collation String collation
|
||||
*/
|
||||
public function __construct(string $string, string $collation)
|
||||
{
|
||||
$this->string = $string;
|
||||
$this->collation = $collation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string collation.
|
||||
*
|
||||
* @param string $collation String collation
|
||||
* @return void
|
||||
*/
|
||||
public function setCollation(string $collation): void
|
||||
{
|
||||
$this->collation = $collation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string collation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCollation(): string
|
||||
{
|
||||
return $this->collation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function sql(ValueBinder $binder): string
|
||||
{
|
||||
$placeholder = $binder->placeholder('c');
|
||||
$binder->bind($placeholder, $this->string, 'string');
|
||||
|
||||
return $placeholder . ' COLLATE ' . $this->collation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function traverse(Closure $callback)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
<?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\Database\Expression;
|
||||
|
||||
use Cake\Database\ExpressionInterface;
|
||||
use Cake\Database\ValueBinder;
|
||||
use Closure;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* This expression represents SQL fragments that are used for comparing one tuple
|
||||
* to another, one tuple to a set of other tuples or one tuple to an expression
|
||||
*/
|
||||
class TupleComparison extends ComparisonExpression
|
||||
{
|
||||
/**
|
||||
* The type to be used for casting the value to a database representation
|
||||
*
|
||||
* @var array<string|null>
|
||||
* @psalm-suppress NonInvariantDocblockPropertyType
|
||||
*/
|
||||
protected $_type;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|array|string $fields the fields to use to form a tuple
|
||||
* @param \Cake\Database\ExpressionInterface|array $values the values to use to form a tuple
|
||||
* @param array<string|null> $types the types names to use for casting each of the values, only
|
||||
* one type per position in the value array in needed
|
||||
* @param string $conjunction the operator used for comparing field and value
|
||||
*/
|
||||
public function __construct($fields, $values, array $types = [], string $conjunction = '=')
|
||||
{
|
||||
$this->_type = $types;
|
||||
$this->setField($fields);
|
||||
$this->_operator = $conjunction;
|
||||
$this->setValue($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type to be used for casting the value to a database representation
|
||||
*
|
||||
* @return array<string|null>
|
||||
*/
|
||||
public function getType(): array
|
||||
{
|
||||
return $this->_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value
|
||||
*
|
||||
* @param mixed $value The value to compare
|
||||
* @return void
|
||||
*/
|
||||
public function setValue($value): void
|
||||
{
|
||||
if ($this->isMulti()) {
|
||||
if (is_array($value) && !is_array(current($value))) {
|
||||
throw new InvalidArgumentException(
|
||||
'Multi-tuple comparisons require a multi-tuple value, single-tuple given.'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (is_array($value) && is_array(current($value))) {
|
||||
throw new InvalidArgumentException(
|
||||
'Single-tuple comparisons require a single-tuple value, multi-tuple given.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->_value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function sql(ValueBinder $binder): string
|
||||
{
|
||||
$template = '(%s) %s (%s)';
|
||||
$fields = [];
|
||||
$originalFields = $this->getField();
|
||||
|
||||
if (!is_array($originalFields)) {
|
||||
$originalFields = [$originalFields];
|
||||
}
|
||||
|
||||
foreach ($originalFields as $field) {
|
||||
$fields[] = $field instanceof ExpressionInterface ? $field->sql($binder) : $field;
|
||||
}
|
||||
|
||||
$values = $this->_stringifyValues($binder);
|
||||
|
||||
$field = implode(', ', $fields);
|
||||
|
||||
return sprintf($template, $field, $this->_operator, $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string with the values as placeholders in a string to be used
|
||||
* for the SQL version of this expression
|
||||
*
|
||||
* @param \Cake\Database\ValueBinder $binder The value binder to convert expressions with.
|
||||
* @return string
|
||||
*/
|
||||
protected function _stringifyValues(ValueBinder $binder): string
|
||||
{
|
||||
$values = [];
|
||||
$parts = $this->getValue();
|
||||
|
||||
if ($parts instanceof ExpressionInterface) {
|
||||
return $parts->sql($binder);
|
||||
}
|
||||
|
||||
foreach ($parts as $i => $value) {
|
||||
if ($value instanceof ExpressionInterface) {
|
||||
$values[] = $value->sql($binder);
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $this->_type;
|
||||
$isMultiOperation = $this->isMulti();
|
||||
if (empty($type)) {
|
||||
$type = null;
|
||||
}
|
||||
|
||||
if ($isMultiOperation) {
|
||||
$bound = [];
|
||||
foreach ($value as $k => $val) {
|
||||
/** @var string $valType */
|
||||
$valType = $type && isset($type[$k]) ? $type[$k] : $type;
|
||||
$bound[] = $this->_bindValue($val, $binder, $valType);
|
||||
}
|
||||
|
||||
$values[] = sprintf('(%s)', implode(',', $bound));
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var string $valType */
|
||||
$valType = $type && isset($type[$i]) ? $type[$i] : $type;
|
||||
$values[] = $this->_bindValue($value, $binder, $valType);
|
||||
}
|
||||
|
||||
return implode(', ', $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function _bindValue($value, ValueBinder $binder, ?string $type = null): string
|
||||
{
|
||||
$placeholder = $binder->placeholder('tuple');
|
||||
$binder->bind($placeholder, $value, $type);
|
||||
|
||||
return $placeholder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function traverse(Closure $callback)
|
||||
{
|
||||
/** @var array<string> $fields */
|
||||
$fields = $this->getField();
|
||||
foreach ($fields as $field) {
|
||||
$this->_traverseValue($field, $callback);
|
||||
}
|
||||
|
||||
$value = $this->getValue();
|
||||
if ($value instanceof ExpressionInterface) {
|
||||
$callback($value);
|
||||
$value->traverse($callback);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
foreach ($value as $val) {
|
||||
if ($this->isMulti()) {
|
||||
foreach ($val as $v) {
|
||||
$this->_traverseValue($v, $callback);
|
||||
}
|
||||
} else {
|
||||
$this->_traverseValue($val, $callback);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditionally executes the callback for the passed value if
|
||||
* it is an ExpressionInterface
|
||||
*
|
||||
* @param mixed $value The value to traverse
|
||||
* @param \Closure $callback The callable to use when traversing
|
||||
* @return void
|
||||
*/
|
||||
protected function _traverseValue($value, Closure $callback): void
|
||||
{
|
||||
if ($value instanceof ExpressionInterface) {
|
||||
$callback($value);
|
||||
$value->traverse($callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if each of the values in this expressions is a tuple in
|
||||
* itself
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isMulti(): bool
|
||||
{
|
||||
return in_array(strtolower($this->_operator), ['in', 'not in']);
|
||||
}
|
||||
}
|
||||
@@ -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 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Expression;
|
||||
|
||||
use Cake\Database\ExpressionInterface;
|
||||
use Cake\Database\ValueBinder;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* An expression object that represents an expression with only a single operand.
|
||||
*/
|
||||
class UnaryExpression implements ExpressionInterface
|
||||
{
|
||||
/**
|
||||
* Indicates that the operation is in pre-order
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const PREFIX = 0;
|
||||
|
||||
/**
|
||||
* Indicates that the operation is in post-order
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const POSTFIX = 1;
|
||||
|
||||
/**
|
||||
* The operator this unary expression represents
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_operator;
|
||||
|
||||
/**
|
||||
* Holds the value which the unary expression operates
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $_value;
|
||||
|
||||
/**
|
||||
* Where to place the operator
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $position;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $operator The operator to used for the expression
|
||||
* @param mixed $value the value to use as the operand for the expression
|
||||
* @param int $position either UnaryExpression::PREFIX or UnaryExpression::POSTFIX
|
||||
*/
|
||||
public function __construct(string $operator, $value, $position = self::PREFIX)
|
||||
{
|
||||
$this->_operator = $operator;
|
||||
$this->_value = $value;
|
||||
$this->position = $position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function sql(ValueBinder $binder): string
|
||||
{
|
||||
$operand = $this->_value;
|
||||
if ($operand instanceof ExpressionInterface) {
|
||||
$operand = $operand->sql($binder);
|
||||
}
|
||||
|
||||
if ($this->position === self::POSTFIX) {
|
||||
return '(' . $operand . ') ' . $this->_operator;
|
||||
}
|
||||
|
||||
return $this->_operator . ' (' . $operand . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function traverse(Closure $callback)
|
||||
{
|
||||
if ($this->_value instanceof ExpressionInterface) {
|
||||
$callback($this->_value);
|
||||
$this->_value->traverse($callback);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a deep clone of the inner expression.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->_value instanceof ExpressionInterface) {
|
||||
$this->_value = clone $this->_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
<?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\Database\Expression;
|
||||
|
||||
use Cake\Database\Exception\DatabaseException;
|
||||
use Cake\Database\ExpressionInterface;
|
||||
use Cake\Database\Query;
|
||||
use Cake\Database\Type\ExpressionTypeCasterTrait;
|
||||
use Cake\Database\TypeMap;
|
||||
use Cake\Database\TypeMapTrait;
|
||||
use Cake\Database\ValueBinder;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* An expression object to contain values being inserted.
|
||||
*
|
||||
* Helps generate SQL with the correct number of placeholders and bind
|
||||
* values correctly into the statement.
|
||||
*/
|
||||
class ValuesExpression implements ExpressionInterface
|
||||
{
|
||||
use ExpressionTypeCasterTrait;
|
||||
use TypeMapTrait;
|
||||
|
||||
/**
|
||||
* Array of values to insert.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_values = [];
|
||||
|
||||
/**
|
||||
* List of columns to ensure are part of the insert.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_columns = [];
|
||||
|
||||
/**
|
||||
* The Query object to use as a values expression
|
||||
*
|
||||
* @var \Cake\Database\Query|null
|
||||
*/
|
||||
protected $_query;
|
||||
|
||||
/**
|
||||
* Whether values have been casted to expressions
|
||||
* already.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_castedExpressions = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $columns The list of columns that are going to be part of the values.
|
||||
* @param \Cake\Database\TypeMap $typeMap A dictionary of column -> type names
|
||||
*/
|
||||
public function __construct(array $columns, TypeMap $typeMap)
|
||||
{
|
||||
$this->_columns = $columns;
|
||||
$this->setTypeMap($typeMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a row of data to be inserted.
|
||||
*
|
||||
* @param \Cake\Database\Query|array $values Array of data to append into the insert, or
|
||||
* a query for doing INSERT INTO .. SELECT style commands
|
||||
* @return void
|
||||
* @throws \Cake\Database\Exception\DatabaseException When mixing array + Query data types.
|
||||
*/
|
||||
public function add($values): void
|
||||
{
|
||||
if (
|
||||
(
|
||||
count($this->_values) &&
|
||||
$values instanceof Query
|
||||
) ||
|
||||
(
|
||||
$this->_query &&
|
||||
is_array($values)
|
||||
)
|
||||
) {
|
||||
throw new DatabaseException(
|
||||
'You cannot mix subqueries and array values in inserts.'
|
||||
);
|
||||
}
|
||||
if ($values instanceof Query) {
|
||||
$this->setQuery($values);
|
||||
|
||||
return;
|
||||
}
|
||||
$this->_values[] = $values;
|
||||
$this->_castedExpressions = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the columns to be inserted.
|
||||
*
|
||||
* @param array $columns Array with columns to be inserted.
|
||||
* @return $this
|
||||
*/
|
||||
public function setColumns(array $columns)
|
||||
{
|
||||
$this->_columns = $columns;
|
||||
$this->_castedExpressions = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the columns to be inserted.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getColumns(): array
|
||||
{
|
||||
return $this->_columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bare column names.
|
||||
*
|
||||
* Because column names could be identifier quoted, we
|
||||
* need to strip the identifiers off of the columns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function _columnNames(): array
|
||||
{
|
||||
$columns = [];
|
||||
foreach ($this->_columns as $col) {
|
||||
if (is_string($col)) {
|
||||
$col = trim($col, '`[]"');
|
||||
}
|
||||
$columns[] = $col;
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the values to be inserted.
|
||||
*
|
||||
* @param array $values Array with values to be inserted.
|
||||
* @return $this
|
||||
*/
|
||||
public function setValues(array $values)
|
||||
{
|
||||
$this->_values = $values;
|
||||
$this->_castedExpressions = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the values to be inserted.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValues(): array
|
||||
{
|
||||
if (!$this->_castedExpressions) {
|
||||
$this->_processExpressions();
|
||||
}
|
||||
|
||||
return $this->_values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the query object to be used as the values expression to be evaluated
|
||||
* to insert records in the table.
|
||||
*
|
||||
* @param \Cake\Database\Query $query The query to set
|
||||
* @return $this
|
||||
*/
|
||||
public function setQuery(Query $query)
|
||||
{
|
||||
$this->_query = $query;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the query object to be used as the values expression to be evaluated
|
||||
* to insert records in the table.
|
||||
*
|
||||
* @return \Cake\Database\Query|null
|
||||
*/
|
||||
public function getQuery(): ?Query
|
||||
{
|
||||
return $this->_query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function sql(ValueBinder $binder): string
|
||||
{
|
||||
if (empty($this->_values) && empty($this->_query)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!$this->_castedExpressions) {
|
||||
$this->_processExpressions();
|
||||
}
|
||||
|
||||
$columns = $this->_columnNames();
|
||||
$defaults = array_fill_keys($columns, null);
|
||||
$placeholders = [];
|
||||
|
||||
$types = [];
|
||||
$typeMap = $this->getTypeMap();
|
||||
foreach ($defaults as $col => $v) {
|
||||
$types[$col] = $typeMap->type($col);
|
||||
}
|
||||
|
||||
foreach ($this->_values as $row) {
|
||||
$row += $defaults;
|
||||
$rowPlaceholders = [];
|
||||
|
||||
foreach ($columns as $column) {
|
||||
$value = $row[$column];
|
||||
|
||||
if ($value instanceof ExpressionInterface) {
|
||||
$rowPlaceholders[] = '(' . $value->sql($binder) . ')';
|
||||
continue;
|
||||
}
|
||||
|
||||
$placeholder = $binder->placeholder('c');
|
||||
$rowPlaceholders[] = $placeholder;
|
||||
$binder->bind($placeholder, $value, $types[$column]);
|
||||
}
|
||||
|
||||
$placeholders[] = implode(', ', $rowPlaceholders);
|
||||
}
|
||||
|
||||
$query = $this->getQuery();
|
||||
if ($query) {
|
||||
return ' ' . $query->sql($binder);
|
||||
}
|
||||
|
||||
return sprintf(' VALUES (%s)', implode('), (', $placeholders));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function traverse(Closure $callback)
|
||||
{
|
||||
if ($this->_query) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (!$this->_castedExpressions) {
|
||||
$this->_processExpressions();
|
||||
}
|
||||
|
||||
foreach ($this->_values as $v) {
|
||||
if ($v instanceof ExpressionInterface) {
|
||||
$v->traverse($callback);
|
||||
}
|
||||
if (!is_array($v)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($v as $field) {
|
||||
if ($field instanceof ExpressionInterface) {
|
||||
$callback($field);
|
||||
$field->traverse($callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts values that need to be casted to expressions
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _processExpressions(): void
|
||||
{
|
||||
$types = [];
|
||||
$typeMap = $this->getTypeMap();
|
||||
|
||||
$columns = $this->_columnNames();
|
||||
foreach ($columns as $c) {
|
||||
if (!is_string($c) && !is_int($c)) {
|
||||
continue;
|
||||
}
|
||||
$types[$c] = $typeMap->type($c);
|
||||
}
|
||||
|
||||
$types = $this->_requiresToExpressionCasting($types);
|
||||
|
||||
if (empty($types)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->_values as $row => $values) {
|
||||
foreach ($types as $col => $type) {
|
||||
/** @var \Cake\Database\Type\ExpressionTypeInterface $type */
|
||||
$this->_values[$row][$col] = $type->toExpression($values[$col]);
|
||||
}
|
||||
}
|
||||
$this->_castedExpressions = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
<?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.3.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Expression;
|
||||
|
||||
use Cake\Database\ExpressionInterface;
|
||||
use Cake\Database\Query;
|
||||
use Cake\Database\Type\ExpressionTypeCasterTrait;
|
||||
use Cake\Database\TypeMap;
|
||||
use Cake\Database\ValueBinder;
|
||||
use Closure;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use function Cake\Core\getTypeName;
|
||||
|
||||
/**
|
||||
* Represents a SQL when/then clause with a fluid API
|
||||
*/
|
||||
class WhenThenExpression implements ExpressionInterface
|
||||
{
|
||||
use CaseExpressionTrait;
|
||||
use ExpressionTypeCasterTrait;
|
||||
|
||||
/**
|
||||
* The names of the clauses that are valid for use with the
|
||||
* `clause()` method.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $validClauseNames = [
|
||||
'when',
|
||||
'then',
|
||||
];
|
||||
|
||||
/**
|
||||
* The type map to use when using an array of conditions for the
|
||||
* `WHEN` value.
|
||||
*
|
||||
* @var \Cake\Database\TypeMap
|
||||
*/
|
||||
protected $_typeMap;
|
||||
|
||||
/**
|
||||
* Then `WHEN` value.
|
||||
*
|
||||
* @var \Cake\Database\ExpressionInterface|object|scalar|null
|
||||
*/
|
||||
protected $when = null;
|
||||
|
||||
/**
|
||||
* The `WHEN` value type.
|
||||
*
|
||||
* @var array|string|null
|
||||
*/
|
||||
protected $whenType = null;
|
||||
|
||||
/**
|
||||
* The `THEN` value.
|
||||
*
|
||||
* @var \Cake\Database\ExpressionInterface|object|scalar|null
|
||||
*/
|
||||
protected $then = null;
|
||||
|
||||
/**
|
||||
* Whether the `THEN` value has been defined, eg whether `then()`
|
||||
* has been invoked.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasThenBeenDefined = false;
|
||||
|
||||
/**
|
||||
* The `THEN` result type.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $thenType = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Cake\Database\TypeMap|null $typeMap The type map to use when using an array of conditions for the `WHEN`
|
||||
* value.
|
||||
*/
|
||||
public function __construct(?TypeMap $typeMap = null)
|
||||
{
|
||||
if ($typeMap === null) {
|
||||
$typeMap = new TypeMap();
|
||||
}
|
||||
$this->_typeMap = $typeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the `WHEN` value.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|object|array|scalar $when The `WHEN` value. When using an array of
|
||||
* conditions, it must be compatible with `\Cake\Database\Query::where()`. Note that this argument is _not_
|
||||
* completely safe for use with user data, as a user supplied array would allow for raw SQL to slip in! If you
|
||||
* plan to use user data, either pass a single type for the `$type` argument (which forces the `$when` value to be
|
||||
* a non-array, and then always binds the data), use a conditions array where the user data is only passed on the
|
||||
* value side of the array entries, or custom bindings!
|
||||
* @param array<string, string>|string|null $type The when value type. Either an associative array when using array style
|
||||
* conditions, or else a string. If no type is provided, the type will be tried to be inferred from the value.
|
||||
* @return $this
|
||||
* @throws \InvalidArgumentException In case the `$when` argument is neither a non-empty array, nor a scalar value,
|
||||
* an object, or an instance of `\Cake\Database\ExpressionInterface`.
|
||||
* @throws \InvalidArgumentException In case the `$type` argument is neither an array, a string, nor null.
|
||||
* @throws \InvalidArgumentException In case the `$when` argument is an array, and the `$type` argument is neither
|
||||
* an array, nor null.
|
||||
* @throws \InvalidArgumentException In case the `$when` argument is a non-array value, and the `$type` argument is
|
||||
* neither a string, nor null.
|
||||
* @see CaseStatementExpression::when() for a more detailed usage explanation.
|
||||
*/
|
||||
public function when($when, $type = null)
|
||||
{
|
||||
if (
|
||||
!(is_array($when) && !empty($when)) &&
|
||||
!is_scalar($when) &&
|
||||
!is_object($when)
|
||||
) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'The `$when` argument must be either a non-empty array, a scalar value, an object, ' .
|
||||
'or an instance of `\%s`, `%s` given.',
|
||||
ExpressionInterface::class,
|
||||
is_array($when) ? '[]' : getTypeName($when)
|
||||
));
|
||||
}
|
||||
|
||||
if (
|
||||
$type !== null &&
|
||||
!is_array($type) &&
|
||||
!is_string($type)
|
||||
) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'The `$type` argument must be either an array, a string, or `null`, `%s` given.',
|
||||
getTypeName($type)
|
||||
));
|
||||
}
|
||||
|
||||
if (is_array($when)) {
|
||||
if (
|
||||
$type !== null &&
|
||||
!is_array($type)
|
||||
) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'When using an array for the `$when` argument, the `$type` argument must be an ' .
|
||||
'array too, `%s` given.',
|
||||
getTypeName($type)
|
||||
));
|
||||
}
|
||||
|
||||
// avoid dirtying the type map for possible consecutive `when()` calls
|
||||
$typeMap = clone $this->_typeMap;
|
||||
if (
|
||||
is_array($type) &&
|
||||
count($type) > 0
|
||||
) {
|
||||
$typeMap = $typeMap->setTypes($type);
|
||||
}
|
||||
|
||||
$when = new QueryExpression($when, $typeMap);
|
||||
} else {
|
||||
if (
|
||||
$type !== null &&
|
||||
!is_string($type)
|
||||
) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'When using a non-array value for the `$when` argument, the `$type` argument must ' .
|
||||
'be a string, `%s` given.',
|
||||
getTypeName($type)
|
||||
));
|
||||
}
|
||||
|
||||
if (
|
||||
$type === null &&
|
||||
!($when instanceof ExpressionInterface)
|
||||
) {
|
||||
$type = $this->inferType($when);
|
||||
}
|
||||
}
|
||||
|
||||
$this->when = $when;
|
||||
$this->whenType = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the `THEN` result value.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|object|scalar|null $result The result value.
|
||||
* @param string|null $type The result type. If no type is provided, the type will be inferred from the given
|
||||
* result value.
|
||||
* @return $this
|
||||
*/
|
||||
public function then($result, ?string $type = null)
|
||||
{
|
||||
if (
|
||||
$result !== null &&
|
||||
!is_scalar($result) &&
|
||||
!(is_object($result) && !($result instanceof Closure))
|
||||
) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'The `$result` argument must be either `null`, a scalar value, an object, ' .
|
||||
'or an instance of `\%s`, `%s` given.',
|
||||
ExpressionInterface::class,
|
||||
getTypeName($result)
|
||||
));
|
||||
}
|
||||
|
||||
$this->then = $result;
|
||||
|
||||
if ($type === null) {
|
||||
$type = $this->inferType($result);
|
||||
}
|
||||
|
||||
$this->thenType = $type;
|
||||
|
||||
$this->hasThenBeenDefined = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the expression's result value type.
|
||||
*
|
||||
* @return string|null
|
||||
* @see WhenThenExpression::then()
|
||||
*/
|
||||
public function getResultType(): ?string
|
||||
{
|
||||
return $this->thenType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the available data for the given clause.
|
||||
*
|
||||
* ### Available clauses
|
||||
*
|
||||
* The following clause names are available:
|
||||
*
|
||||
* * `when`: The `WHEN` value.
|
||||
* * `then`: The `THEN` result value.
|
||||
*
|
||||
* @param string $clause The name of the clause to obtain.
|
||||
* @return \Cake\Database\ExpressionInterface|object|scalar|null
|
||||
* @throws \InvalidArgumentException In case the given clause name is invalid.
|
||||
*/
|
||||
public function clause(string $clause)
|
||||
{
|
||||
if (!in_array($clause, $this->validClauseNames, true)) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'The `$clause` argument must be one of `%s`, the given value `%s` is invalid.',
|
||||
implode('`, `', $this->validClauseNames),
|
||||
$clause
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->{$clause};
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function sql(ValueBinder $binder): string
|
||||
{
|
||||
if ($this->when === null) {
|
||||
throw new LogicException('Case expression has incomplete when clause. Missing `when()`.');
|
||||
}
|
||||
|
||||
if (!$this->hasThenBeenDefined) {
|
||||
throw new LogicException('Case expression has incomplete when clause. Missing `then()` after `when()`.');
|
||||
}
|
||||
|
||||
$when = $this->when;
|
||||
if (
|
||||
is_string($this->whenType) &&
|
||||
!($when instanceof ExpressionInterface)
|
||||
) {
|
||||
$when = $this->_castToExpression($when, $this->whenType);
|
||||
}
|
||||
if ($when instanceof Query) {
|
||||
$when = sprintf('(%s)', $when->sql($binder));
|
||||
} elseif ($when instanceof ExpressionInterface) {
|
||||
$when = $when->sql($binder);
|
||||
} else {
|
||||
$placeholder = $binder->placeholder('c');
|
||||
if (is_string($this->whenType)) {
|
||||
$whenType = $this->whenType;
|
||||
} else {
|
||||
$whenType = null;
|
||||
}
|
||||
$binder->bind($placeholder, $when, $whenType);
|
||||
$when = $placeholder;
|
||||
}
|
||||
|
||||
$then = $this->compileNullableValue($binder, $this->then, $this->thenType);
|
||||
|
||||
return "WHEN $when THEN $then";
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function traverse(Closure $callback)
|
||||
{
|
||||
if ($this->when instanceof ExpressionInterface) {
|
||||
$callback($this->when);
|
||||
$this->when->traverse($callback);
|
||||
}
|
||||
|
||||
if ($this->then instanceof ExpressionInterface) {
|
||||
$callback($this->then);
|
||||
$this->then->traverse($callback);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the inner expression objects.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->when instanceof ExpressionInterface) {
|
||||
$this->when = clone $this->when;
|
||||
}
|
||||
|
||||
if ($this->then instanceof ExpressionInterface) {
|
||||
$this->then = clone $this->then;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
<?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\Database\Expression;
|
||||
|
||||
use Cake\Database\ExpressionInterface;
|
||||
use Cake\Database\ValueBinder;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* This represents a SQL window expression used by aggregate and window functions.
|
||||
*/
|
||||
class WindowExpression implements ExpressionInterface, WindowInterface
|
||||
{
|
||||
/**
|
||||
* @var \Cake\Database\Expression\IdentifierExpression
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var array<\Cake\Database\ExpressionInterface>
|
||||
*/
|
||||
protected $partitions = [];
|
||||
|
||||
/**
|
||||
* @var \Cake\Database\Expression\OrderByExpression|null
|
||||
*/
|
||||
protected $order;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
protected $frame;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $exclusion;
|
||||
|
||||
/**
|
||||
* @param string $name Window name
|
||||
*/
|
||||
public function __construct(string $name = '')
|
||||
{
|
||||
$this->name = new IdentifierExpression($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether is only a named window expression.
|
||||
*
|
||||
* These window expressions only specify a named window and do not
|
||||
* specify their own partitions, frame or order.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isNamedOnly(): bool
|
||||
{
|
||||
return $this->name->getIdentifier() && (!$this->partitions && !$this->frame && !$this->order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the window name.
|
||||
*
|
||||
* @param string $name Window name
|
||||
* @return $this
|
||||
*/
|
||||
public function name(string $name)
|
||||
{
|
||||
$this->name = new IdentifierExpression($name);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function partition($partitions)
|
||||
{
|
||||
if (!$partitions) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($partitions instanceof Closure) {
|
||||
$partitions = $partitions(new QueryExpression([], [], ''));
|
||||
}
|
||||
|
||||
if (!is_array($partitions)) {
|
||||
$partitions = [$partitions];
|
||||
}
|
||||
|
||||
foreach ($partitions as &$partition) {
|
||||
if (is_string($partition)) {
|
||||
$partition = new IdentifierExpression($partition);
|
||||
}
|
||||
}
|
||||
|
||||
$this->partitions = array_merge($this->partitions, $partitions);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function order($fields)
|
||||
{
|
||||
if (!$fields) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($this->order === null) {
|
||||
$this->order = new OrderByExpression();
|
||||
}
|
||||
|
||||
if ($fields instanceof Closure) {
|
||||
$fields = $fields(new QueryExpression([], [], ''));
|
||||
}
|
||||
|
||||
$this->order->add($fields);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function range($start, $end = 0)
|
||||
{
|
||||
return $this->frame(self::RANGE, $start, self::PRECEDING, $end, self::FOLLOWING);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function rows(?int $start, ?int $end = 0)
|
||||
{
|
||||
return $this->frame(self::ROWS, $start, self::PRECEDING, $end, self::FOLLOWING);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function groups(?int $start, ?int $end = 0)
|
||||
{
|
||||
return $this->frame(self::GROUPS, $start, self::PRECEDING, $end, self::FOLLOWING);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function frame(
|
||||
string $type,
|
||||
$startOffset,
|
||||
string $startDirection,
|
||||
$endOffset,
|
||||
string $endDirection
|
||||
) {
|
||||
$this->frame = [
|
||||
'type' => $type,
|
||||
'start' => [
|
||||
'offset' => $startOffset,
|
||||
'direction' => $startDirection,
|
||||
],
|
||||
'end' => [
|
||||
'offset' => $endOffset,
|
||||
'direction' => $endDirection,
|
||||
],
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function excludeCurrent()
|
||||
{
|
||||
$this->exclusion = 'CURRENT ROW';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function excludeGroup()
|
||||
{
|
||||
$this->exclusion = 'GROUP';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function excludeTies()
|
||||
{
|
||||
$this->exclusion = 'TIES';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function sql(ValueBinder $binder): string
|
||||
{
|
||||
$clauses = [];
|
||||
if ($this->name->getIdentifier()) {
|
||||
$clauses[] = $this->name->sql($binder);
|
||||
}
|
||||
|
||||
if ($this->partitions) {
|
||||
$expressions = [];
|
||||
foreach ($this->partitions as $partition) {
|
||||
$expressions[] = $partition->sql($binder);
|
||||
}
|
||||
|
||||
$clauses[] = 'PARTITION BY ' . implode(', ', $expressions);
|
||||
}
|
||||
|
||||
if ($this->order) {
|
||||
$clauses[] = $this->order->sql($binder);
|
||||
}
|
||||
|
||||
if ($this->frame) {
|
||||
$start = $this->buildOffsetSql(
|
||||
$binder,
|
||||
$this->frame['start']['offset'],
|
||||
$this->frame['start']['direction']
|
||||
);
|
||||
$end = $this->buildOffsetSql(
|
||||
$binder,
|
||||
$this->frame['end']['offset'],
|
||||
$this->frame['end']['direction']
|
||||
);
|
||||
|
||||
$frameSql = sprintf('%s BETWEEN %s AND %s', $this->frame['type'], $start, $end);
|
||||
|
||||
if ($this->exclusion !== null) {
|
||||
$frameSql .= ' EXCLUDE ' . $this->exclusion;
|
||||
}
|
||||
|
||||
$clauses[] = $frameSql;
|
||||
}
|
||||
|
||||
return implode(' ', $clauses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function traverse(Closure $callback)
|
||||
{
|
||||
$callback($this->name);
|
||||
foreach ($this->partitions as $partition) {
|
||||
$callback($partition);
|
||||
$partition->traverse($callback);
|
||||
}
|
||||
|
||||
if ($this->order) {
|
||||
$callback($this->order);
|
||||
$this->order->traverse($callback);
|
||||
}
|
||||
|
||||
if ($this->frame !== null) {
|
||||
$offset = $this->frame['start']['offset'];
|
||||
if ($offset instanceof ExpressionInterface) {
|
||||
$callback($offset);
|
||||
$offset->traverse($callback);
|
||||
}
|
||||
$offset = $this->frame['end']['offset'] ?? null;
|
||||
if ($offset instanceof ExpressionInterface) {
|
||||
$callback($offset);
|
||||
$offset->traverse($callback);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds frame offset sql.
|
||||
*
|
||||
* @param \Cake\Database\ValueBinder $binder Value binder
|
||||
* @param \Cake\Database\ExpressionInterface|string|int|null $offset Frame offset
|
||||
* @param string $direction Frame offset direction
|
||||
* @return string
|
||||
*/
|
||||
protected function buildOffsetSql(ValueBinder $binder, $offset, string $direction): string
|
||||
{
|
||||
if ($offset === 0) {
|
||||
return 'CURRENT ROW';
|
||||
}
|
||||
|
||||
if ($offset instanceof ExpressionInterface) {
|
||||
$offset = $offset->sql($binder);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'%s %s',
|
||||
$offset ?? 'UNBOUNDED',
|
||||
$direction
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone this object and its subtree of expressions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$this->name = clone $this->name;
|
||||
foreach ($this->partitions as $i => $partition) {
|
||||
$this->partitions[$i] = clone $partition;
|
||||
}
|
||||
if ($this->order !== null) {
|
||||
$this->order = clone $this->order;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<?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\Database\Expression;
|
||||
|
||||
/**
|
||||
* This defines the functions used for building window expressions.
|
||||
*/
|
||||
interface WindowInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const PRECEDING = 'PRECEDING';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const FOLLOWING = 'FOLLOWING';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const RANGE = 'RANGE';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const ROWS = 'ROWS';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const GROUPS = 'GROUPS';
|
||||
|
||||
/**
|
||||
* Adds one or more partition expressions to the window.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string $partitions Partition expressions
|
||||
* @return $this
|
||||
*/
|
||||
public function partition($partitions);
|
||||
|
||||
/**
|
||||
* Adds one or more order clauses to the window.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string $fields Order expressions
|
||||
* @return $this
|
||||
*/
|
||||
public function order($fields);
|
||||
|
||||
/**
|
||||
* Adds a simple range frame to the window.
|
||||
*
|
||||
* `$start`:
|
||||
* - `0` - 'CURRENT ROW'
|
||||
* - `null` - 'UNBOUNDED PRECEDING'
|
||||
* - offset - 'offset PRECEDING'
|
||||
*
|
||||
* `$end`:
|
||||
* - `0` - 'CURRENT ROW'
|
||||
* - `null` - 'UNBOUNDED FOLLOWING'
|
||||
* - offset - 'offset FOLLOWING'
|
||||
*
|
||||
* If you need to use 'FOLLOWING' with frame start or
|
||||
* 'PRECEDING' with frame end, use `frame()` instead.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string|int|null $start Frame start
|
||||
* @param \Cake\Database\ExpressionInterface|string|int|null $end Frame end
|
||||
* If not passed in, only frame start SQL will be generated.
|
||||
* @return $this
|
||||
*/
|
||||
public function range($start, $end = 0);
|
||||
|
||||
/**
|
||||
* Adds a simple rows frame to the window.
|
||||
*
|
||||
* See `range()` for details.
|
||||
*
|
||||
* @param int|null $start Frame start
|
||||
* @param int|null $end Frame end
|
||||
* If not passed in, only frame start SQL will be generated.
|
||||
* @return $this
|
||||
*/
|
||||
public function rows(?int $start, ?int $end = 0);
|
||||
|
||||
/**
|
||||
* Adds a simple groups frame to the window.
|
||||
*
|
||||
* See `range()` for details.
|
||||
*
|
||||
* @param int|null $start Frame start
|
||||
* @param int|null $end Frame end
|
||||
* If not passed in, only frame start SQL will be generated.
|
||||
* @return $this
|
||||
*/
|
||||
public function groups(?int $start, ?int $end = 0);
|
||||
|
||||
/**
|
||||
* Adds a frame to the window.
|
||||
*
|
||||
* Use the `range()`, `rows()` or `groups()` helpers if you need simple
|
||||
* 'BETWEEN offset PRECEDING and offset FOLLOWING' frames.
|
||||
*
|
||||
* You can specify any direction for both frame start and frame end.
|
||||
*
|
||||
* With both `$startOffset` and `$endOffset`:
|
||||
* - `0` - 'CURRENT ROW'
|
||||
* - `null` - 'UNBOUNDED'
|
||||
*
|
||||
* @param string $type Frame type
|
||||
* @param \Cake\Database\ExpressionInterface|string|int|null $startOffset Frame start offset
|
||||
* @param string $startDirection Frame start direction
|
||||
* @param \Cake\Database\ExpressionInterface|string|int|null $endOffset Frame end offset
|
||||
* @param string $endDirection Frame end direction
|
||||
* @return $this
|
||||
* @throws \InvalidArgumentException WHen offsets are negative.
|
||||
* @psalm-param self::RANGE|self::ROWS|self::GROUPS $type
|
||||
* @psalm-param self::PRECEDING|self::FOLLOWING $startDirection
|
||||
* @psalm-param self::PRECEDING|self::FOLLOWING $endDirection
|
||||
*/
|
||||
public function frame(
|
||||
string $type,
|
||||
$startOffset,
|
||||
string $startDirection,
|
||||
$endOffset,
|
||||
string $endDirection
|
||||
);
|
||||
|
||||
/**
|
||||
* Adds current row frame exclusion.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function excludeCurrent();
|
||||
|
||||
/**
|
||||
* Adds group frame exclusion.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function excludeGroup();
|
||||
|
||||
/**
|
||||
* Adds ties frame exclusion.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function excludeTies();
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?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\Database;
|
||||
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* An interface used by Expression objects.
|
||||
*/
|
||||
interface ExpressionInterface
|
||||
{
|
||||
/**
|
||||
* Converts the Node into a SQL string fragment.
|
||||
*
|
||||
* @param \Cake\Database\ValueBinder $binder Parameter binder
|
||||
* @return string
|
||||
*/
|
||||
public function sql(ValueBinder $binder): string;
|
||||
|
||||
/**
|
||||
* Iterates over each part of the expression recursively for every
|
||||
* level of the expressions tree and executes the $callback callable
|
||||
* passing as first parameter the instance of the expression currently
|
||||
* being iterated.
|
||||
*
|
||||
* @param \Closure $callback The callable to apply to all nodes.
|
||||
* @return $this
|
||||
*/
|
||||
public function traverse(Closure $callback);
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
<?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\Database;
|
||||
|
||||
use Cake\Database\Type\BatchCastingInterface;
|
||||
use Cake\Database\Type\OptionalConvertInterface;
|
||||
|
||||
/**
|
||||
* A callable class to be used for processing each of the rows in a statement
|
||||
* result, so that the values are converted to the right PHP types.
|
||||
*/
|
||||
class FieldTypeConverter
|
||||
{
|
||||
/**
|
||||
* An array containing the name of the fields and the Type objects
|
||||
* each should use when converting them.
|
||||
*
|
||||
* @var array<\Cake\Database\TypeInterface>
|
||||
*/
|
||||
protected $_typeMap;
|
||||
|
||||
/**
|
||||
* An array containing the name of the fields and the Type objects
|
||||
* each should use when converting them using batching.
|
||||
*
|
||||
* @var array<string, array>
|
||||
*/
|
||||
protected $batchingTypeMap;
|
||||
|
||||
/**
|
||||
* An array containing all the types registered in the Type system
|
||||
* at the moment this object is created. Used so that the types list
|
||||
* is not fetched on each single row of the results.
|
||||
*
|
||||
* @var array<\Cake\Database\TypeInterface|\Cake\Database\Type\BatchCastingInterface>
|
||||
*/
|
||||
protected $types;
|
||||
|
||||
/**
|
||||
* The driver object to be used in the type conversion
|
||||
*
|
||||
* @var \Cake\Database\DriverInterface
|
||||
*/
|
||||
protected $_driver;
|
||||
|
||||
/**
|
||||
* Builds the type map
|
||||
*
|
||||
* @param \Cake\Database\TypeMap $typeMap Contains the types to use for converting results
|
||||
* @param \Cake\Database\DriverInterface $driver The driver to use for the type conversion
|
||||
*/
|
||||
public function __construct(TypeMap $typeMap, DriverInterface $driver)
|
||||
{
|
||||
$this->_driver = $driver;
|
||||
$map = $typeMap->toArray();
|
||||
$types = TypeFactory::buildAll();
|
||||
|
||||
$simpleMap = $batchingMap = [];
|
||||
$simpleResult = $batchingResult = [];
|
||||
|
||||
foreach ($types as $k => $type) {
|
||||
if ($type instanceof OptionalConvertInterface && !$type->requiresToPhpCast()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type instanceof BatchCastingInterface) {
|
||||
$batchingMap[$k] = $type;
|
||||
continue;
|
||||
}
|
||||
|
||||
$simpleMap[$k] = $type;
|
||||
}
|
||||
|
||||
foreach ($map as $field => $type) {
|
||||
if (isset($simpleMap[$type])) {
|
||||
$simpleResult[$field] = $simpleMap[$type];
|
||||
continue;
|
||||
}
|
||||
if (isset($batchingMap[$type])) {
|
||||
$batchingResult[$type][] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
// Using batching when there is only a couple for the type is actually slower,
|
||||
// so, let's check for that case here.
|
||||
foreach ($batchingResult as $type => $fields) {
|
||||
if (count($fields) > 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($fields as $f) {
|
||||
$simpleResult[$f] = $batchingMap[$type];
|
||||
}
|
||||
unset($batchingResult[$type]);
|
||||
}
|
||||
|
||||
$this->types = $types;
|
||||
$this->_typeMap = $simpleResult;
|
||||
$this->batchingTypeMap = $batchingResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts each of the fields in the array that are present in the type map
|
||||
* using the corresponding Type class.
|
||||
*
|
||||
* @param array $row The array with the fields to be casted
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function __invoke(array $row): array
|
||||
{
|
||||
if (!empty($this->_typeMap)) {
|
||||
foreach ($this->_typeMap as $field => $type) {
|
||||
$row[$field] = $type->toPHP($row[$field], $this->_driver);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($this->batchingTypeMap)) {
|
||||
foreach ($this->batchingTypeMap as $t => $fields) {
|
||||
/** @psalm-suppress PossiblyUndefinedMethod */
|
||||
$row = $this->types[$t]->manyToPHP($row, $fields, $this->_driver);
|
||||
}
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
+376
@@ -0,0 +1,376 @@
|
||||
<?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\Database;
|
||||
|
||||
use Cake\Database\Expression\AggregateExpression;
|
||||
use Cake\Database\Expression\FunctionExpression;
|
||||
use InvalidArgumentException;
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
/**
|
||||
* Contains methods related to generating FunctionExpression objects
|
||||
* with most commonly used SQL functions.
|
||||
* This acts as a factory for FunctionExpression objects.
|
||||
*/
|
||||
class FunctionsBuilder
|
||||
{
|
||||
/**
|
||||
* Returns a FunctionExpression representing a call to SQL RAND function.
|
||||
*
|
||||
* @return \Cake\Database\Expression\FunctionExpression
|
||||
*/
|
||||
public function rand(): FunctionExpression
|
||||
{
|
||||
return new FunctionExpression('RAND', [], [], 'float');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a AggregateExpression representing a call to SQL SUM function.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $expression the function argument
|
||||
* @param array $types list of types to bind to the arguments
|
||||
* @return \Cake\Database\Expression\AggregateExpression
|
||||
*/
|
||||
public function sum($expression, $types = []): AggregateExpression
|
||||
{
|
||||
$returnType = 'float';
|
||||
if (current($types) === 'integer') {
|
||||
$returnType = 'integer';
|
||||
}
|
||||
|
||||
return $this->aggregate('SUM', $this->toLiteralParam($expression), $types, $returnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a AggregateExpression representing a call to SQL AVG function.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $expression the function argument
|
||||
* @param array $types list of types to bind to the arguments
|
||||
* @return \Cake\Database\Expression\AggregateExpression
|
||||
*/
|
||||
public function avg($expression, $types = []): AggregateExpression
|
||||
{
|
||||
return $this->aggregate('AVG', $this->toLiteralParam($expression), $types, 'float');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a AggregateExpression representing a call to SQL MAX function.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $expression the function argument
|
||||
* @param array $types list of types to bind to the arguments
|
||||
* @return \Cake\Database\Expression\AggregateExpression
|
||||
*/
|
||||
public function max($expression, $types = []): AggregateExpression
|
||||
{
|
||||
return $this->aggregate('MAX', $this->toLiteralParam($expression), $types, current($types) ?: 'float');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a AggregateExpression representing a call to SQL MIN function.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $expression the function argument
|
||||
* @param array $types list of types to bind to the arguments
|
||||
* @return \Cake\Database\Expression\AggregateExpression
|
||||
*/
|
||||
public function min($expression, $types = []): AggregateExpression
|
||||
{
|
||||
return $this->aggregate('MIN', $this->toLiteralParam($expression), $types, current($types) ?: 'float');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a AggregateExpression representing a call to SQL COUNT function.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $expression the function argument
|
||||
* @param array $types list of types to bind to the arguments
|
||||
* @return \Cake\Database\Expression\AggregateExpression
|
||||
*/
|
||||
public function count($expression, $types = []): AggregateExpression
|
||||
{
|
||||
return $this->aggregate('COUNT', $this->toLiteralParam($expression), $types, 'integer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FunctionExpression representing a string concatenation
|
||||
*
|
||||
* @param array $args List of strings or expressions to concatenate
|
||||
* @param array $types list of types to bind to the arguments
|
||||
* @return \Cake\Database\Expression\FunctionExpression
|
||||
*/
|
||||
public function concat(array $args, array $types = []): FunctionExpression
|
||||
{
|
||||
return new FunctionExpression('CONCAT', $args, $types, 'string');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FunctionExpression representing a call to SQL COALESCE function.
|
||||
*
|
||||
* @param array $args List of expressions to evaluate as function parameters
|
||||
* @param array $types list of types to bind to the arguments
|
||||
* @return \Cake\Database\Expression\FunctionExpression
|
||||
*/
|
||||
public function coalesce(array $args, array $types = []): FunctionExpression
|
||||
{
|
||||
return new FunctionExpression('COALESCE', $args, $types, current($types) ?: 'string');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FunctionExpression representing a SQL CAST.
|
||||
*
|
||||
* The `$type` parameter is a SQL type. The return type for the returned expression
|
||||
* is the default type name. Use `setReturnType()` to update it.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $field Field or expression to cast.
|
||||
* @param string $type The SQL data type
|
||||
* @return \Cake\Database\Expression\FunctionExpression
|
||||
*/
|
||||
public function cast($field, string $type = ''): FunctionExpression
|
||||
{
|
||||
if (is_array($field)) {
|
||||
deprecationWarning(
|
||||
'Build cast function by FunctionsBuilder::cast(array $args) is deprecated. ' .
|
||||
'Use FunctionsBuilder::cast($field, string $type) instead.'
|
||||
);
|
||||
|
||||
return new FunctionExpression('CAST', $field);
|
||||
}
|
||||
|
||||
if (empty($type)) {
|
||||
throw new InvalidArgumentException('The `$type` in a cast cannot be empty.');
|
||||
}
|
||||
|
||||
$expression = new FunctionExpression('CAST', $this->toLiteralParam($field));
|
||||
$expression->setConjunction(' AS')->add([$type => 'literal']);
|
||||
|
||||
return $expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FunctionExpression representing the difference in days between
|
||||
* two dates.
|
||||
*
|
||||
* @param array $args List of expressions to obtain the difference in days.
|
||||
* @param array $types list of types to bind to the arguments
|
||||
* @return \Cake\Database\Expression\FunctionExpression
|
||||
*/
|
||||
public function dateDiff(array $args, array $types = []): FunctionExpression
|
||||
{
|
||||
return new FunctionExpression('DATEDIFF', $args, $types, 'integer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified date part from the SQL expression.
|
||||
*
|
||||
* @param string $part Part of the date to return.
|
||||
* @param \Cake\Database\ExpressionInterface|string $expression Expression to obtain the date part from.
|
||||
* @param array $types list of types to bind to the arguments
|
||||
* @return \Cake\Database\Expression\FunctionExpression
|
||||
*/
|
||||
public function datePart(string $part, $expression, array $types = []): FunctionExpression
|
||||
{
|
||||
return $this->extract($part, $expression, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified date part from the SQL expression.
|
||||
*
|
||||
* @param string $part Part of the date to return.
|
||||
* @param \Cake\Database\ExpressionInterface|string $expression Expression to obtain the date part from.
|
||||
* @param array $types list of types to bind to the arguments
|
||||
* @return \Cake\Database\Expression\FunctionExpression
|
||||
*/
|
||||
public function extract(string $part, $expression, array $types = []): FunctionExpression
|
||||
{
|
||||
$expression = new FunctionExpression('EXTRACT', $this->toLiteralParam($expression), $types, 'integer');
|
||||
$expression->setConjunction(' FROM')->add([$part => 'literal'], [], true);
|
||||
|
||||
return $expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the time unit to the date expression
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $expression Expression to obtain the date part from.
|
||||
* @param string|int $value Value to be added. Use negative to subtract.
|
||||
* @param string $unit Unit of the value e.g. hour or day.
|
||||
* @param array $types list of types to bind to the arguments
|
||||
* @return \Cake\Database\Expression\FunctionExpression
|
||||
*/
|
||||
public function dateAdd($expression, $value, string $unit, array $types = []): FunctionExpression
|
||||
{
|
||||
if (!is_numeric($value)) {
|
||||
$value = 0;
|
||||
}
|
||||
$interval = $value . ' ' . $unit;
|
||||
$expression = new FunctionExpression('DATE_ADD', $this->toLiteralParam($expression), $types, 'datetime');
|
||||
$expression->setConjunction(', INTERVAL')->add([$interval => 'literal']);
|
||||
|
||||
return $expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FunctionExpression representing a call to SQL WEEKDAY function.
|
||||
* 1 - Sunday, 2 - Monday, 3 - Tuesday...
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $expression the function argument
|
||||
* @param array $types list of types to bind to the arguments
|
||||
* @return \Cake\Database\Expression\FunctionExpression
|
||||
*/
|
||||
public function dayOfWeek($expression, $types = []): FunctionExpression
|
||||
{
|
||||
return new FunctionExpression('DAYOFWEEK', $this->toLiteralParam($expression), $types, 'integer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FunctionExpression representing a call to SQL WEEKDAY function.
|
||||
* 1 - Sunday, 2 - Monday, 3 - Tuesday...
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $expression the function argument
|
||||
* @param array $types list of types to bind to the arguments
|
||||
* @return \Cake\Database\Expression\FunctionExpression
|
||||
*/
|
||||
public function weekday($expression, $types = []): FunctionExpression
|
||||
{
|
||||
return $this->dayOfWeek($expression, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FunctionExpression representing a call that will return the current
|
||||
* date and time. By default it returns both date and time, but you can also
|
||||
* make it generate only the date or only the time.
|
||||
*
|
||||
* @param string $type (datetime|date|time)
|
||||
* @return \Cake\Database\Expression\FunctionExpression
|
||||
*/
|
||||
public function now(string $type = 'datetime'): FunctionExpression
|
||||
{
|
||||
if ($type === 'datetime') {
|
||||
return new FunctionExpression('NOW', [], [], 'datetime');
|
||||
}
|
||||
if ($type === 'date') {
|
||||
return new FunctionExpression('CURRENT_DATE', [], [], 'date');
|
||||
}
|
||||
if ($type === 'time') {
|
||||
return new FunctionExpression('CURRENT_TIME', [], [], 'time');
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Invalid argument for FunctionsBuilder::now(): ' . $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an AggregateExpression representing call to SQL ROW_NUMBER().
|
||||
*
|
||||
* @return \Cake\Database\Expression\AggregateExpression
|
||||
*/
|
||||
public function rowNumber(): AggregateExpression
|
||||
{
|
||||
return (new AggregateExpression('ROW_NUMBER', [], [], 'integer'))->over();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an AggregateExpression representing call to SQL LAG().
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $expression The value evaluated at offset
|
||||
* @param int $offset The row offset
|
||||
* @param mixed $default The default value if offset doesn't exist
|
||||
* @param string $type The output type of the lag expression. Defaults to float.
|
||||
* @return \Cake\Database\Expression\AggregateExpression
|
||||
*/
|
||||
public function lag($expression, int $offset, $default = null, $type = null): AggregateExpression
|
||||
{
|
||||
$params = $this->toLiteralParam($expression) + [$offset => 'literal'];
|
||||
if ($default !== null) {
|
||||
$params[] = $default;
|
||||
}
|
||||
|
||||
$types = [];
|
||||
if ($type !== null) {
|
||||
$types = [$type, 'integer', $type];
|
||||
}
|
||||
|
||||
return (new AggregateExpression('LAG', $params, $types, $type ?? 'float'))->over();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an AggregateExpression representing call to SQL LEAD().
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $expression The value evaluated at offset
|
||||
* @param int $offset The row offset
|
||||
* @param mixed $default The default value if offset doesn't exist
|
||||
* @param string $type The output type of the lead expression. Defaults to float.
|
||||
* @return \Cake\Database\Expression\AggregateExpression
|
||||
*/
|
||||
public function lead($expression, int $offset, $default = null, $type = null): AggregateExpression
|
||||
{
|
||||
$params = $this->toLiteralParam($expression) + [$offset => 'literal'];
|
||||
if ($default !== null) {
|
||||
$params[] = $default;
|
||||
}
|
||||
|
||||
$types = [];
|
||||
if ($type !== null) {
|
||||
$types = [$type, 'integer', $type];
|
||||
}
|
||||
|
||||
return (new AggregateExpression('LEAD', $params, $types, $type ?? 'float'))->over();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create arbitrary SQL aggregate function calls.
|
||||
*
|
||||
* @param string $name The SQL aggregate function name
|
||||
* @param array $params Array of arguments to be passed to the function.
|
||||
* Can be an associative array with the literal value or identifier:
|
||||
* `['value' => 'literal']` or `['value' => 'identifier']
|
||||
* @param array $types Array of types that match the names used in `$params`:
|
||||
* `['name' => 'type']`
|
||||
* @param string $return Return type of the entire expression. Defaults to float.
|
||||
* @return \Cake\Database\Expression\AggregateExpression
|
||||
*/
|
||||
public function aggregate(string $name, array $params = [], array $types = [], string $return = 'float')
|
||||
{
|
||||
return new AggregateExpression($name, $params, $types, $return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method dispatcher to create custom SQL function calls
|
||||
*
|
||||
* @param string $name the SQL function name to construct
|
||||
* @param array $args list with up to 3 arguments, first one being an array with
|
||||
* parameters for the SQL function, the second one a list of types to bind to those
|
||||
* params, and the third one the return type of the function
|
||||
* @return \Cake\Database\Expression\FunctionExpression
|
||||
*/
|
||||
public function __call(string $name, array $args): FunctionExpression
|
||||
{
|
||||
return new FunctionExpression($name, ...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates function parameter array from expression or string literal.
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface|string $expression function argument
|
||||
* @return array<\Cake\Database\ExpressionInterface|string>
|
||||
*/
|
||||
protected function toLiteralParam($expression)
|
||||
{
|
||||
if (is_string($expression)) {
|
||||
return [$expression => 'literal'];
|
||||
}
|
||||
|
||||
return [$expression];
|
||||
}
|
||||
}
|
||||
+269
@@ -0,0 +1,269 @@
|
||||
<?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\Database;
|
||||
|
||||
use Cake\Database\Expression\FieldInterface;
|
||||
use Cake\Database\Expression\IdentifierExpression;
|
||||
use Cake\Database\Expression\OrderByExpression;
|
||||
|
||||
/**
|
||||
* Contains all the logic related to quoting identifiers in a Query object
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class IdentifierQuoter
|
||||
{
|
||||
/**
|
||||
* The driver instance used to do the identifier quoting
|
||||
*
|
||||
* @var \Cake\Database\Driver
|
||||
*/
|
||||
protected $_driver;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \Cake\Database\Driver $driver The driver instance used to do the identifier quoting
|
||||
*/
|
||||
public function __construct(Driver $driver)
|
||||
{
|
||||
$this->_driver = $driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over each of the clauses in a query looking for identifiers and
|
||||
* quotes them
|
||||
*
|
||||
* @param \Cake\Database\Query $query The query to have its identifiers quoted
|
||||
* @return \Cake\Database\Query
|
||||
*/
|
||||
public function quote(Query $query): Query
|
||||
{
|
||||
$binder = $query->getValueBinder();
|
||||
$query->setValueBinder(null);
|
||||
|
||||
if ($query->type() === 'insert') {
|
||||
$this->_quoteInsert($query);
|
||||
} elseif ($query->type() === 'update') {
|
||||
$this->_quoteUpdate($query);
|
||||
} else {
|
||||
$this->_quoteParts($query);
|
||||
}
|
||||
|
||||
$query->traverseExpressions([$this, 'quoteExpression']);
|
||||
$query->setValueBinder($binder);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quotes identifiers inside expression objects
|
||||
*
|
||||
* @param \Cake\Database\ExpressionInterface $expression The expression object to walk and quote.
|
||||
* @return void
|
||||
*/
|
||||
public function quoteExpression(ExpressionInterface $expression): void
|
||||
{
|
||||
if ($expression instanceof FieldInterface) {
|
||||
$this->_quoteComparison($expression);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($expression instanceof OrderByExpression) {
|
||||
$this->_quoteOrderBy($expression);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($expression instanceof IdentifierExpression) {
|
||||
$this->_quoteIdentifierExpression($expression);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quotes all identifiers in each of the clauses of a query
|
||||
*
|
||||
* @param \Cake\Database\Query $query The query to quote.
|
||||
* @return void
|
||||
*/
|
||||
protected function _quoteParts(Query $query): void
|
||||
{
|
||||
foreach (['distinct', 'select', 'from', 'group'] as $part) {
|
||||
$contents = $query->clause($part);
|
||||
|
||||
if (!is_array($contents)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result = $this->_basicQuoter($contents);
|
||||
if (!empty($result)) {
|
||||
$query->{$part}($result, true);
|
||||
}
|
||||
}
|
||||
|
||||
$joins = $query->clause('join');
|
||||
if ($joins) {
|
||||
$joins = $this->_quoteJoins($joins);
|
||||
$query->join($joins, [], true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic identifier quoting function used for various parts of the query
|
||||
*
|
||||
* @param array<string, mixed> $part the part of the query to quote
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function _basicQuoter(array $part): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($part as $alias => $value) {
|
||||
$value = !is_string($value) ? $value : $this->_driver->quoteIdentifier($value);
|
||||
$alias = is_numeric($alias) ? $alias : $this->_driver->quoteIdentifier($alias);
|
||||
$result[$alias] = $value;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quotes both the table and alias for an array of joins as stored in a Query
|
||||
* object
|
||||
*
|
||||
* @param array $joins The joins to quote.
|
||||
* @return array<string, array>
|
||||
*/
|
||||
protected function _quoteJoins(array $joins): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($joins as $value) {
|
||||
$alias = '';
|
||||
if (!empty($value['alias'])) {
|
||||
$alias = $this->_driver->quoteIdentifier($value['alias']);
|
||||
$value['alias'] = $alias;
|
||||
}
|
||||
|
||||
if (is_string($value['table'])) {
|
||||
$value['table'] = $this->_driver->quoteIdentifier($value['table']);
|
||||
}
|
||||
|
||||
$result[$alias] = $value;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quotes the table name and columns for an insert query
|
||||
*
|
||||
* @param \Cake\Database\Query $query The insert query to quote.
|
||||
* @return void
|
||||
*/
|
||||
protected function _quoteInsert(Query $query): void
|
||||
{
|
||||
$insert = $query->clause('insert');
|
||||
if (!isset($insert[0]) || !isset($insert[1])) {
|
||||
return;
|
||||
}
|
||||
[$table, $columns] = $insert;
|
||||
$table = $this->_driver->quoteIdentifier($table);
|
||||
foreach ($columns as &$column) {
|
||||
if (is_scalar($column)) {
|
||||
$column = $this->_driver->quoteIdentifier((string)$column);
|
||||
}
|
||||
}
|
||||
$query->insert($columns)->into($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quotes the table name for an update query
|
||||
*
|
||||
* @param \Cake\Database\Query $query The update query to quote.
|
||||
* @return void
|
||||
*/
|
||||
protected function _quoteUpdate(Query $query): void
|
||||
{
|
||||
$table = $query->clause('update')[0];
|
||||
|
||||
if (is_string($table)) {
|
||||
$query->update($this->_driver->quoteIdentifier($table));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quotes identifiers in expression objects implementing the field interface
|
||||
*
|
||||
* @param \Cake\Database\Expression\FieldInterface $expression The expression to quote.
|
||||
* @return void
|
||||
*/
|
||||
protected function _quoteComparison(FieldInterface $expression): void
|
||||
{
|
||||
$field = $expression->getField();
|
||||
if (is_string($field)) {
|
||||
$expression->setField($this->_driver->quoteIdentifier($field));
|
||||
} elseif (is_array($field)) {
|
||||
$quoted = [];
|
||||
foreach ($field as $f) {
|
||||
$quoted[] = $this->_driver->quoteIdentifier($f);
|
||||
}
|
||||
$expression->setField($quoted);
|
||||
} elseif ($field instanceof ExpressionInterface) {
|
||||
$this->quoteExpression($field);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quotes identifiers in "order by" expression objects
|
||||
*
|
||||
* Strings with spaces are treated as literal expressions
|
||||
* and will not have identifiers quoted.
|
||||
*
|
||||
* @param \Cake\Database\Expression\OrderByExpression $expression The expression to quote.
|
||||
* @return void
|
||||
*/
|
||||
protected function _quoteOrderBy(OrderByExpression $expression): void
|
||||
{
|
||||
$expression->iterateParts(function ($part, &$field) {
|
||||
if (is_string($field)) {
|
||||
$field = $this->_driver->quoteIdentifier($field);
|
||||
|
||||
return $part;
|
||||
}
|
||||
if (is_string($part) && strpos($part, ' ') === false) {
|
||||
return $this->_driver->quoteIdentifier($part);
|
||||
}
|
||||
|
||||
return $part;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Quotes identifiers in "order by" expression objects
|
||||
*
|
||||
* @param \Cake\Database\Expression\IdentifierExpression $expression The identifiers to quote.
|
||||
* @return void
|
||||
*/
|
||||
protected function _quoteIdentifierExpression(IdentifierExpression $expression): void
|
||||
{
|
||||
$expression->setIdentifier(
|
||||
$this->_driver->quoteIdentifier($expression->getIdentifier())
|
||||
);
|
||||
}
|
||||
}
|
||||
Vendored
+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.
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
<?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\Database\Log;
|
||||
|
||||
use Cake\Database\Driver\Sqlserver;
|
||||
use JsonSerializable;
|
||||
|
||||
/**
|
||||
* Contains a query string, the params used to executed it, time taken to do it
|
||||
* and the number of rows found or affected by its execution.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class LoggedQuery implements JsonSerializable
|
||||
{
|
||||
/**
|
||||
* Driver executing the query
|
||||
*
|
||||
* @var \Cake\Database\DriverInterface|null
|
||||
*/
|
||||
public $driver = null;
|
||||
|
||||
/**
|
||||
* Query string that was executed
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $query = '';
|
||||
|
||||
/**
|
||||
* Number of milliseconds this query took to complete
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
public $took = 0;
|
||||
|
||||
/**
|
||||
* Associative array with the params bound to the query string
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $params = [];
|
||||
|
||||
/**
|
||||
* Number of rows affected or returned by the query execution
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $numRows = 0;
|
||||
|
||||
/**
|
||||
* The exception that was thrown by the execution of this query
|
||||
*
|
||||
* @var \Exception|null
|
||||
*/
|
||||
public $error;
|
||||
|
||||
/**
|
||||
* Helper function used to replace query placeholders by the real
|
||||
* params used to execute the query
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function interpolate(): string
|
||||
{
|
||||
$params = array_map(function ($p) {
|
||||
if ($p === null) {
|
||||
return 'NULL';
|
||||
}
|
||||
|
||||
if (is_bool($p)) {
|
||||
if ($this->driver instanceof Sqlserver) {
|
||||
return $p ? '1' : '0';
|
||||
}
|
||||
|
||||
return $p ? 'TRUE' : 'FALSE';
|
||||
}
|
||||
|
||||
if (is_string($p)) {
|
||||
// Likely binary data like a blob or binary uuid.
|
||||
// pattern matches ascii control chars.
|
||||
if (preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $p) !== $p) {
|
||||
$p = bin2hex($p);
|
||||
}
|
||||
|
||||
$replacements = [
|
||||
'$' => '\\$',
|
||||
'\\' => '\\\\\\\\',
|
||||
"'" => "''",
|
||||
];
|
||||
|
||||
$p = strtr($p, $replacements);
|
||||
|
||||
return "'$p'";
|
||||
}
|
||||
|
||||
return $p;
|
||||
}, $this->params);
|
||||
|
||||
$keys = [];
|
||||
$limit = is_int(key($params)) ? 1 : -1;
|
||||
foreach ($params as $key => $param) {
|
||||
$keys[] = is_string($key) ? "/:$key\b/" : '/[?]/';
|
||||
}
|
||||
|
||||
return preg_replace($keys, $params, $this->query, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the logging context data for a query.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getContext(): array
|
||||
{
|
||||
return [
|
||||
'numRows' => $this->numRows,
|
||||
'took' => $this->took,
|
||||
'role' => $this->driver ? $this->driver->getRole() : '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns data that will be serialized as JSON
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$error = $this->error;
|
||||
if ($error !== null) {
|
||||
$error = [
|
||||
'class' => get_class($error),
|
||||
'message' => $error->getMessage(),
|
||||
'code' => $error->getCode(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'query' => $this->query,
|
||||
'numRows' => $this->numRows,
|
||||
'params' => $this->params,
|
||||
'took' => $this->took,
|
||||
'error' => $error,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string representation of this logged query
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
$sql = $this->query;
|
||||
if (!empty($this->params)) {
|
||||
$sql = $this->interpolate();
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
<?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\Database\Log;
|
||||
|
||||
use Cake\Core\Configure;
|
||||
use Cake\Database\Exception\DatabaseException;
|
||||
use Cake\Database\Statement\StatementDecorator;
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
/**
|
||||
* Statement decorator used to
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class LoggingStatement extends StatementDecorator
|
||||
{
|
||||
/**
|
||||
* Logger instance responsible for actually doing the logging task
|
||||
*
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $_logger;
|
||||
|
||||
/**
|
||||
* Holds bound params
|
||||
*
|
||||
* @var array<array>
|
||||
*/
|
||||
protected $_compiledParams = [];
|
||||
|
||||
/**
|
||||
* Query execution start time.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
protected $startTime = 0.0;
|
||||
|
||||
/**
|
||||
* Logged query
|
||||
*
|
||||
* @var \Cake\Database\Log\LoggedQuery|null
|
||||
*/
|
||||
protected $loggedQuery;
|
||||
|
||||
/**
|
||||
* Wrapper for the execute function to calculate time spent
|
||||
* and log the query afterwards.
|
||||
*
|
||||
* @param array|null $params List of values to be bound to query
|
||||
* @return bool True on success, false otherwise
|
||||
* @throws \Exception Re-throws any exception raised during query execution.
|
||||
*/
|
||||
public function execute(?array $params = null): bool
|
||||
{
|
||||
$this->startTime = microtime(true);
|
||||
|
||||
$this->loggedQuery = new LoggedQuery();
|
||||
$this->loggedQuery->driver = $this->_driver;
|
||||
$this->loggedQuery->params = $params ?: $this->_compiledParams;
|
||||
|
||||
try {
|
||||
$result = parent::execute($params);
|
||||
$this->loggedQuery->took = (int)round((microtime(true) - $this->startTime) * 1000, 0);
|
||||
} catch (Exception $e) {
|
||||
$this->loggedQuery->error = $e;
|
||||
$this->_log();
|
||||
|
||||
if (Configure::read('Error.convertStatementToDatabaseException', false) === true) {
|
||||
$code = $e->getCode();
|
||||
if (!is_int($code)) {
|
||||
$code = null;
|
||||
}
|
||||
|
||||
throw new DatabaseException([
|
||||
'message' => $e->getMessage(),
|
||||
'queryString' => $this->queryString,
|
||||
], $code, $e);
|
||||
}
|
||||
|
||||
if (version_compare(PHP_VERSION, '8.2.0', '<')) {
|
||||
deprecationWarning(
|
||||
'4.4.12 - Having queryString set on exceptions is deprecated.' .
|
||||
'If you are not using this attribute there is no action to take.' .
|
||||
'Otherwise, enable Error.convertStatementToDatabaseException.'
|
||||
);
|
||||
/** @psalm-suppress UndefinedPropertyAssignment */
|
||||
$e->queryString = $this->queryString;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if (preg_match('/^(?!SELECT)/i', $this->queryString)) {
|
||||
$this->rowCount();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function fetch($type = self::FETCH_TYPE_NUM)
|
||||
{
|
||||
$record = parent::fetch($type);
|
||||
|
||||
if ($this->loggedQuery) {
|
||||
$this->rowCount();
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function fetchAll($type = self::FETCH_TYPE_NUM)
|
||||
{
|
||||
$results = parent::fetchAll($type);
|
||||
|
||||
if ($this->loggedQuery) {
|
||||
$this->rowCount();
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function rowCount(): int
|
||||
{
|
||||
$result = parent::rowCount();
|
||||
|
||||
if ($this->loggedQuery) {
|
||||
$this->loggedQuery->numRows = $result;
|
||||
$this->_log();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the logging data to the passed LoggedQuery and sends it
|
||||
* to the logging system.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _log(): void
|
||||
{
|
||||
if ($this->loggedQuery === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->loggedQuery->query = $this->queryString;
|
||||
$this->getLogger()->debug((string)$this->loggedQuery, ['query' => $this->loggedQuery]);
|
||||
|
||||
$this->loggedQuery = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for bindValue function to gather each parameter to be later used
|
||||
* in the logger function.
|
||||
*
|
||||
* @param string|int $column Name or param position to be bound
|
||||
* @param mixed $value The value to bind to variable in query
|
||||
* @param string|int|null $type PDO type or name of configured Type class
|
||||
* @return void
|
||||
*/
|
||||
public function bindValue($column, $value, $type = 'string'): void
|
||||
{
|
||||
parent::bindValue($column, $value, $type);
|
||||
|
||||
if ($type === null) {
|
||||
$type = 'string';
|
||||
}
|
||||
if (!ctype_digit($type)) {
|
||||
$value = $this->cast($value, $type)[0];
|
||||
}
|
||||
$this->_compiledParams[$column] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a logger
|
||||
*
|
||||
* @param \Psr\Log\LoggerInterface $logger Logger object
|
||||
* @return void
|
||||
*/
|
||||
public function setLogger(LoggerInterface $logger): void
|
||||
{
|
||||
$this->_logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the logger object
|
||||
*
|
||||
* @return \Psr\Log\LoggerInterface logger instance
|
||||
*/
|
||||
public function getLogger(): LoggerInterface
|
||||
{
|
||||
return $this->_logger;
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
<?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\Database\Log;
|
||||
|
||||
use Cake\Log\Engine\BaseLog;
|
||||
use Cake\Log\Log;
|
||||
|
||||
/**
|
||||
* This class is a bridge used to write LoggedQuery objects into a real log.
|
||||
* by default this class use the built-in CakePHP Log class to accomplish this
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class QueryLogger extends BaseLog
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array<string, mixed> $config Configuration array
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->_defaultConfig['scopes'] = ['queriesLog'];
|
||||
$this->_defaultConfig['connection'] = '';
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function log($level, $message, array $context = [])
|
||||
{
|
||||
$context['scope'] = $this->scopes() ?: ['queriesLog'];
|
||||
$context['connection'] = $this->getConfig('connection');
|
||||
|
||||
if ($context['query'] instanceof LoggedQuery) {
|
||||
$context = $context['query']->getContext() + $context;
|
||||
$message = 'connection={connection} role={role} duration={took} rows={numRows} ' . $message;
|
||||
}
|
||||
Log::write('debug', (string)$message, $context);
|
||||
}
|
||||
}
|
||||
+93
@@ -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 4.0.3
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database;
|
||||
|
||||
use Cake\Database\Expression\FunctionExpression;
|
||||
|
||||
/**
|
||||
* Responsible for compiling a Query object into its SQL representation
|
||||
* for Postgres
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PostgresCompiler extends QueryCompiler
|
||||
{
|
||||
/**
|
||||
* Always quote aliases in SELECT clause.
|
||||
*
|
||||
* Postgres auto converts unquoted identifiers to lower case.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_quotedSelectAliases = true;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected $_templates = [
|
||||
'delete' => 'DELETE',
|
||||
'where' => ' WHERE %s',
|
||||
'group' => ' GROUP BY %s',
|
||||
'order' => ' %s',
|
||||
'limit' => ' LIMIT %s',
|
||||
'offset' => ' OFFSET %s',
|
||||
'epilog' => ' %s',
|
||||
];
|
||||
|
||||
/**
|
||||
* Helper function used to build the string representation of a HAVING clause,
|
||||
* it constructs the field list taking care of aliasing and
|
||||
* converting expression objects to string.
|
||||
*
|
||||
* @param array $parts list of fields to be transformed to string
|
||||
* @param \Cake\Database\Query $query The query that is being compiled
|
||||
* @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder
|
||||
* @return string
|
||||
*/
|
||||
protected function _buildHavingPart($parts, $query, $binder)
|
||||
{
|
||||
$selectParts = $query->clause('select');
|
||||
|
||||
foreach ($selectParts as $selectKey => $selectPart) {
|
||||
if (!$selectPart instanceof FunctionExpression) {
|
||||
continue;
|
||||
}
|
||||
foreach ($parts as $k => $p) {
|
||||
if (!is_string($p)) {
|
||||
continue;
|
||||
}
|
||||
preg_match_all(
|
||||
'/\b' . trim($selectKey, '"') . '\b/i',
|
||||
$p,
|
||||
$matches
|
||||
);
|
||||
|
||||
if (empty($matches[0])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parts[$k] = preg_replace(
|
||||
['/"/', '/\b' . trim($selectKey, '"') . '\b/i'],
|
||||
['', $selectPart->sql($binder)],
|
||||
$p
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return sprintf(' HAVING %s', implode(', ', $parts));
|
||||
}
|
||||
}
|
||||
Vendored
+2516
File diff suppressed because it is too large
Load Diff
+222
@@ -0,0 +1,222 @@
|
||||
<?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.5.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Query;
|
||||
|
||||
use Cake\Database\Query;
|
||||
|
||||
/**
|
||||
* Delete Query forward compatibility shim.
|
||||
*/
|
||||
class DeleteQuery extends Query
|
||||
{
|
||||
/**
|
||||
* Type of this query (select, insert, update, delete).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_type = 'delete';
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function select($fields = [], bool $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('select()', 'Create your query with selectQuery() instead.');
|
||||
|
||||
return parent::select($fields, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function distinct($on = [], $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('distint()');
|
||||
|
||||
return parent::distinct($on, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function modifier($modifiers, $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('modifier()');
|
||||
|
||||
return parent::modifier($modifiers, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function order($fields, $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('order()');
|
||||
|
||||
return parent::order($fields, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function orderAsc($field, $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('orderAsc()');
|
||||
|
||||
return parent::orderAsc($field, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function orderDesc($field, $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('orderDesc()');
|
||||
|
||||
return parent::orderDesc($field, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function group($fields, $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('group()');
|
||||
|
||||
return parent::group($fields, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function having($conditions = null, $types = [], $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('having()');
|
||||
|
||||
return parent::having($conditions, $types, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function andHaving($conditions, $types = [])
|
||||
{
|
||||
$this->_deprecatedMethod('andHaving()');
|
||||
|
||||
return parent::andHaving($conditions, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function page(int $num, ?int $limit = null)
|
||||
{
|
||||
$this->_deprecatedMethod('page()');
|
||||
|
||||
return parent::page($num, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function limit($limit)
|
||||
{
|
||||
$this->_deprecatedMethod('limit()');
|
||||
|
||||
return parent::limit($limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function offset($offset)
|
||||
{
|
||||
$this->_deprecatedMethod('offset()');
|
||||
|
||||
return parent::offset($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function union($query, $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('union()');
|
||||
|
||||
return parent::union($query, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function unionAll($query, $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('unionAll()');
|
||||
|
||||
return parent::unionAll($query, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function insert(array $columns, array $types = [])
|
||||
{
|
||||
$this->_deprecatedMethod('insert()', 'Create your query with insertQuery() instead.');
|
||||
|
||||
return parent::insert($columns, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function into(string $table)
|
||||
{
|
||||
$this->_deprecatedMethod('into()');
|
||||
|
||||
return parent::into($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function values($data)
|
||||
{
|
||||
$this->_deprecatedMethod('values()');
|
||||
|
||||
return parent::values($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function update($table)
|
||||
{
|
||||
$this->_deprecatedMethod('update()', 'Create your query with updateQuery() instead.');
|
||||
|
||||
return parent::update($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function set($key, $value = null, $types = [])
|
||||
{
|
||||
$this->_deprecatedMethod('set()');
|
||||
|
||||
return parent::set($key, $value, $types);
|
||||
}
|
||||
}
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
<?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.5.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Query;
|
||||
|
||||
use Cake\Database\Query;
|
||||
|
||||
/**
|
||||
* Insert Query forward compatibility shim.
|
||||
*/
|
||||
class InsertQuery extends Query
|
||||
{
|
||||
/**
|
||||
* Type of this query (select, insert, update, delete).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_type = 'insert';
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function delete(?string $table = null)
|
||||
{
|
||||
$this->_deprecatedMethod('delete()', 'Create your query with deleteQuery() instead.');
|
||||
|
||||
return parent::delete($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function select($fields = [], bool $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('select()', 'Create your query with selectQuery() instead.');
|
||||
|
||||
return parent::select($fields, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function distinct($on = [], $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('distinct()');
|
||||
|
||||
return parent::distinct($on, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function modifier($modifiers, $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('modifier()');
|
||||
|
||||
return parent::modifier($modifiers, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function join($tables, $types = [], $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('join()');
|
||||
|
||||
return parent::join($tables, $types, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function removeJoin(string $name)
|
||||
{
|
||||
$this->_deprecatedMethod('removeJoin()');
|
||||
|
||||
return parent::removeJoin($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function leftJoin($table, $conditions = [], $types = [])
|
||||
{
|
||||
$this->_deprecatedMethod('leftJoin()');
|
||||
|
||||
return parent::leftJoin($table, $conditions, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function rightJoin($table, $conditions = [], $types = [])
|
||||
{
|
||||
$this->_deprecatedMethod('rightJoin()');
|
||||
|
||||
return parent::rightJoin($table, $conditions, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function innerJoin($table, $conditions = [], $types = [])
|
||||
{
|
||||
$this->_deprecatedMethod('innerJoin()');
|
||||
|
||||
return parent::innerJoin($table, $conditions, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function group($fields, $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('group()');
|
||||
|
||||
return parent::group($fields, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function having($conditions = null, $types = [], $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('having()');
|
||||
|
||||
return parent::having($conditions, $types, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function andHaving($conditions, $types = [])
|
||||
{
|
||||
$this->_deprecatedMethod('andHaving()');
|
||||
|
||||
return parent::andHaving($conditions, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function page(int $num, ?int $limit = null)
|
||||
{
|
||||
$this->_deprecatedMethod('page()');
|
||||
|
||||
return parent::page($num, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function offset($offset)
|
||||
{
|
||||
$this->_deprecatedMethod('offset()');
|
||||
|
||||
return parent::offset($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function union($query, $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('union()');
|
||||
|
||||
return parent::union($query, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function unionAll($query, $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('union()');
|
||||
|
||||
return parent::unionAll($query, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function from($tables = [], $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('from()', 'Use into() instead.');
|
||||
|
||||
return parent::from($tables, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function update($table)
|
||||
{
|
||||
$this->_deprecatedMethod('update()', 'Create your query with updateQuery() instead.');
|
||||
|
||||
return parent::update($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function set($key, $value = null, $types = [])
|
||||
{
|
||||
$this->_deprecatedMethod('set()');
|
||||
|
||||
return parent::set($key, $value, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function where($conditions = null, array $types = [], bool $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('where()');
|
||||
|
||||
return parent::where($conditions, $types, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function whereNotNull($fields)
|
||||
{
|
||||
$this->_deprecatedMethod('whereNotNull()');
|
||||
|
||||
return parent::whereNotNull($fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function whereNull($fields)
|
||||
{
|
||||
$this->_deprecatedMethod('whereNull()');
|
||||
|
||||
return parent::whereNull($fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function whereInList(string $field, array $values, array $options = [])
|
||||
{
|
||||
$this->_deprecatedMethod('whereInList()');
|
||||
|
||||
return parent::whereInList($field, $values, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function whereNotInList(string $field, array $values, array $options = [])
|
||||
{
|
||||
$this->_deprecatedMethod('whereNotInList()');
|
||||
|
||||
return parent::whereNotInList($field, $values, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function whereNotInListOrNull(string $field, array $values, array $options = [])
|
||||
{
|
||||
$this->_deprecatedMethod('whereNotInListOrNull()');
|
||||
|
||||
return parent::whereNotInListOrNull($field, $values, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function andWhere($conditions, array $types = [])
|
||||
{
|
||||
$this->_deprecatedMethod('andWhere()');
|
||||
|
||||
return parent::andWhere($conditions, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function order($fields, $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('order()');
|
||||
|
||||
return parent::order($fields, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function orderAsc($field, $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('orderAsc()');
|
||||
|
||||
return parent::orderAsc($field, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function orderDesc($field, $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('orderDesc()');
|
||||
|
||||
return parent::orderDesc($field, $overwrite);
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
<?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.5.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Query;
|
||||
|
||||
use Cake\Database\Connection;
|
||||
use Cake\Database\Query;
|
||||
|
||||
/**
|
||||
* Select Query forward compatibility shim.
|
||||
*/
|
||||
class SelectQuery extends Query
|
||||
{
|
||||
/**
|
||||
* Type of this query (select, insert, update, delete).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_type = 'select';
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function delete(?string $table = null)
|
||||
{
|
||||
$this->_deprecatedMethod('delete()', 'Create your query with deleteQuery() instead.');
|
||||
|
||||
return parent::delete($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function insert(array $columns, array $types = [])
|
||||
{
|
||||
$this->_deprecatedMethod('insert()', 'Create your query with insertQuery() instead.');
|
||||
|
||||
return parent::insert($columns, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function into(string $table)
|
||||
{
|
||||
$this->_deprecatedMethod('into()', 'Use from() instead.');
|
||||
|
||||
return parent::into($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function values($data)
|
||||
{
|
||||
$this->_deprecatedMethod('values()');
|
||||
|
||||
return parent::values($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function update($table)
|
||||
{
|
||||
$this->_deprecatedMethod('update()', 'Create your query with updateQuery() instead.');
|
||||
|
||||
return parent::update($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function set($key, $value = null, $types = [])
|
||||
{
|
||||
$this->_deprecatedMethod('set()');
|
||||
|
||||
return parent::set($key, $value, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the connection role.
|
||||
*
|
||||
* @param string $role Connection role ('read' or 'write')
|
||||
* @return $this
|
||||
*/
|
||||
public function setConnectionRole(string $role)
|
||||
{
|
||||
assert($role === Connection::ROLE_READ || $role === Connection::ROLE_WRITE);
|
||||
$this->connectionRole = $role;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the connection role to read.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function useReadRole()
|
||||
{
|
||||
return $this->setConnectionRole(Connection::ROLE_READ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the connection role to write.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function useWriteRole()
|
||||
{
|
||||
return $this->setConnectionRole(Connection::ROLE_WRITE);
|
||||
}
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
<?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.5.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Query;
|
||||
|
||||
use Cake\Database\Query;
|
||||
|
||||
/**
|
||||
* Update Query forward compatibility shim.
|
||||
*/
|
||||
class UpdateQuery extends Query
|
||||
{
|
||||
/**
|
||||
* Type of this query (select, insert, update, delete).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_type = 'update';
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function delete(?string $table = null)
|
||||
{
|
||||
$this->_deprecatedMethod('delete()', 'Create your query with deleteQuery() instead.');
|
||||
|
||||
return parent::delete($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function select($fields = [], bool $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('select()', 'Create your query with selectQuery() instead.');
|
||||
|
||||
return parent::select($fields, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function distinct($on = [], $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('distinct()');
|
||||
|
||||
return parent::distinct($on, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function modifier($modifiers, $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('modifier()');
|
||||
|
||||
return parent::modifier($modifiers, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function group($fields, $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('group()');
|
||||
|
||||
return parent::group($fields, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function having($conditions = null, $types = [], $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('having()');
|
||||
|
||||
return parent::having($conditions, $types, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function andHaving($conditions, $types = [])
|
||||
{
|
||||
$this->_deprecatedMethod('andHaving()');
|
||||
|
||||
return parent::andHaving($conditions, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function page(int $num, ?int $limit = null)
|
||||
{
|
||||
$this->_deprecatedMethod('page()');
|
||||
|
||||
return parent::page($num, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function offset($offset)
|
||||
{
|
||||
$this->_deprecatedMethod('offset()');
|
||||
|
||||
return parent::offset($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function union($query, $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('union()');
|
||||
|
||||
return parent::union($query, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function unionAll($query, $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('union()');
|
||||
|
||||
return parent::unionAll($query, $overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function insert(array $columns, array $types = [])
|
||||
{
|
||||
$this->_deprecatedMethod('insert()', 'Create your query with insertQuery() instead.');
|
||||
|
||||
return parent::insert($columns, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function into(string $table)
|
||||
{
|
||||
$this->_deprecatedMethod('into()', 'Use update() instead.');
|
||||
|
||||
return parent::into($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function values($data)
|
||||
{
|
||||
$this->_deprecatedMethod('values()');
|
||||
|
||||
return parent::values($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function from($tables = [], $overwrite = false)
|
||||
{
|
||||
$this->_deprecatedMethod('from()', 'Use update() instead.');
|
||||
|
||||
return parent::from($tables, $overwrite);
|
||||
}
|
||||
}
|
||||
+462
@@ -0,0 +1,462 @@
|
||||
<?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\Database;
|
||||
|
||||
use Cake\Database\Exception\DatabaseException;
|
||||
use Closure;
|
||||
use Countable;
|
||||
|
||||
/**
|
||||
* Responsible for compiling a Query object into its SQL representation
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class QueryCompiler
|
||||
{
|
||||
/**
|
||||
* List of sprintf templates that will be used for compiling the SQL for
|
||||
* this query. There are some clauses that can be built as just as the
|
||||
* direct concatenation of the internal parts, those are listed here.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $_templates = [
|
||||
'delete' => 'DELETE',
|
||||
'where' => ' WHERE %s',
|
||||
'group' => ' GROUP BY %s ',
|
||||
'having' => ' HAVING %s ',
|
||||
'order' => ' %s',
|
||||
'limit' => ' LIMIT %s',
|
||||
'offset' => ' OFFSET %s',
|
||||
'epilog' => ' %s',
|
||||
];
|
||||
|
||||
/**
|
||||
* The list of query clauses to traverse for generating a SELECT statement
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $_selectParts = [
|
||||
'with', 'select', 'from', 'join', 'where', 'group', 'having', 'window', 'order',
|
||||
'limit', 'offset', 'union', 'epilog',
|
||||
];
|
||||
|
||||
/**
|
||||
* The list of query clauses to traverse for generating an UPDATE statement
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $_updateParts = ['with', 'update', 'set', 'where', 'epilog'];
|
||||
|
||||
/**
|
||||
* The list of query clauses to traverse for generating a DELETE statement
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $_deleteParts = ['with', 'delete', 'modifier', 'from', 'where', 'epilog'];
|
||||
|
||||
/**
|
||||
* The list of query clauses to traverse for generating an INSERT statement
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $_insertParts = ['with', 'insert', 'values', 'epilog'];
|
||||
|
||||
/**
|
||||
* Indicate whether this query dialect supports ordered unions.
|
||||
*
|
||||
* Overridden in subclasses.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_orderedUnion = true;
|
||||
|
||||
/**
|
||||
* Indicate whether aliases in SELECT clause need to be always quoted.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_quotedSelectAliases = false;
|
||||
|
||||
/**
|
||||
* Returns the SQL representation of the provided query after generating
|
||||
* the placeholders for the bound values using the provided generator
|
||||
*
|
||||
* @param \Cake\Database\Query $query The query that is being compiled
|
||||
* @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholders
|
||||
* @return string
|
||||
*/
|
||||
public function compile(Query $query, ValueBinder $binder): string
|
||||
{
|
||||
$sql = '';
|
||||
$type = $query->type();
|
||||
$query->traverseParts(
|
||||
$this->_sqlCompiler($sql, $query, $binder),
|
||||
$this->{"_{$type}Parts"}
|
||||
);
|
||||
|
||||
// Propagate bound parameters from sub-queries if the
|
||||
// placeholders can be found in the SQL statement.
|
||||
if ($query->getValueBinder() !== $binder) {
|
||||
foreach ($query->getValueBinder()->bindings() as $binding) {
|
||||
$placeholder = ':' . $binding['placeholder'];
|
||||
if (preg_match('/' . $placeholder . '(?:\W|$)/', $sql) > 0) {
|
||||
$binder->bind($placeholder, $binding['value'], $binding['type']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a callable object that can be used to compile a SQL string representation
|
||||
* of this query.
|
||||
*
|
||||
* @param string $sql initial sql string to append to
|
||||
* @param \Cake\Database\Query $query The query that is being compiled
|
||||
* @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder
|
||||
* @return \Closure
|
||||
*/
|
||||
protected function _sqlCompiler(string &$sql, Query $query, ValueBinder $binder): Closure
|
||||
{
|
||||
return function ($part, $partName) use (&$sql, $query, $binder) {
|
||||
if (
|
||||
$part === null ||
|
||||
(is_array($part) && empty($part)) ||
|
||||
($part instanceof Countable && count($part) === 0)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($part instanceof ExpressionInterface) {
|
||||
$part = [$part->sql($binder)];
|
||||
}
|
||||
if (isset($this->_templates[$partName])) {
|
||||
$part = $this->_stringifyExpressions((array)$part, $binder);
|
||||
$sql .= sprintf($this->_templates[$partName], implode(', ', $part));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$sql .= $this->{'_build' . $partName . 'Part'}($part, $query, $binder);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function used to build the string representation of a `WITH` clause,
|
||||
* it constructs the CTE definitions list and generates the `RECURSIVE`
|
||||
* keyword when required.
|
||||
*
|
||||
* @param array $parts List of CTEs to be transformed to string
|
||||
* @param \Cake\Database\Query $query The query that is being compiled
|
||||
* @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder
|
||||
* @return string
|
||||
*/
|
||||
protected function _buildWithPart(array $parts, Query $query, ValueBinder $binder): string
|
||||
{
|
||||
$recursive = false;
|
||||
$expressions = [];
|
||||
foreach ($parts as $cte) {
|
||||
$recursive = $recursive || $cte->isRecursive();
|
||||
$expressions[] = $cte->sql($binder);
|
||||
}
|
||||
|
||||
$recursive = $recursive ? 'RECURSIVE ' : '';
|
||||
|
||||
return sprintf('WITH %s%s ', $recursive, implode(', ', $expressions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function used to build the string representation of a SELECT clause,
|
||||
* it constructs the field list taking care of aliasing and
|
||||
* converting expression objects to string. This function also constructs the
|
||||
* DISTINCT clause for the query.
|
||||
*
|
||||
* @param array $parts list of fields to be transformed to string
|
||||
* @param \Cake\Database\Query $query The query that is being compiled
|
||||
* @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder
|
||||
* @return string
|
||||
*/
|
||||
protected function _buildSelectPart(array $parts, Query $query, ValueBinder $binder): string
|
||||
{
|
||||
$select = 'SELECT%s %s%s';
|
||||
if ($this->_orderedUnion && $query->clause('union')) {
|
||||
$select = '(SELECT%s %s%s';
|
||||
}
|
||||
$distinct = $query->clause('distinct');
|
||||
$modifiers = $this->_buildModifierPart($query->clause('modifier'), $query, $binder);
|
||||
|
||||
$driver = $query->getConnection()->getDriver($query->getConnectionRole());
|
||||
$quoteIdentifiers = $driver->isAutoQuotingEnabled() || $this->_quotedSelectAliases;
|
||||
$normalized = [];
|
||||
$parts = $this->_stringifyExpressions($parts, $binder);
|
||||
foreach ($parts as $k => $p) {
|
||||
if (!is_numeric($k)) {
|
||||
$p = $p . ' AS ';
|
||||
if ($quoteIdentifiers) {
|
||||
$p .= $driver->quoteIdentifier($k);
|
||||
} else {
|
||||
$p .= $k;
|
||||
}
|
||||
}
|
||||
$normalized[] = $p;
|
||||
}
|
||||
|
||||
if ($distinct === true) {
|
||||
$distinct = 'DISTINCT ';
|
||||
}
|
||||
|
||||
if (is_array($distinct)) {
|
||||
$distinct = $this->_stringifyExpressions($distinct, $binder);
|
||||
$distinct = sprintf('DISTINCT ON (%s) ', implode(', ', $distinct));
|
||||
}
|
||||
|
||||
return sprintf($select, $modifiers, $distinct, implode(', ', $normalized));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function used to build the string representation of a FROM clause,
|
||||
* it constructs the tables list taking care of aliasing and
|
||||
* converting expression objects to string.
|
||||
*
|
||||
* @param array $parts list of tables to be transformed to string
|
||||
* @param \Cake\Database\Query $query The query that is being compiled
|
||||
* @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder
|
||||
* @return string
|
||||
*/
|
||||
protected function _buildFromPart(array $parts, Query $query, ValueBinder $binder): string
|
||||
{
|
||||
$select = ' FROM %s';
|
||||
$normalized = [];
|
||||
$parts = $this->_stringifyExpressions($parts, $binder);
|
||||
foreach ($parts as $k => $p) {
|
||||
if (!is_numeric($k)) {
|
||||
$p = $p . ' ' . $k;
|
||||
}
|
||||
$normalized[] = $p;
|
||||
}
|
||||
|
||||
return sprintf($select, implode(', ', $normalized));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function used to build the string representation of multiple JOIN clauses,
|
||||
* it constructs the joins list taking care of aliasing and converting
|
||||
* expression objects to string in both the table to be joined and the conditions
|
||||
* to be used.
|
||||
*
|
||||
* @param array $parts list of joins to be transformed to string
|
||||
* @param \Cake\Database\Query $query The query that is being compiled
|
||||
* @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder
|
||||
* @return string
|
||||
*/
|
||||
protected function _buildJoinPart(array $parts, Query $query, ValueBinder $binder): string
|
||||
{
|
||||
$joins = '';
|
||||
foreach ($parts as $join) {
|
||||
if (!isset($join['table'])) {
|
||||
throw new DatabaseException(sprintf(
|
||||
'Could not compile join clause for alias `%s`. No table was specified. ' .
|
||||
'Use the `table` key to define a table.',
|
||||
$join['alias']
|
||||
));
|
||||
}
|
||||
if ($join['table'] instanceof ExpressionInterface) {
|
||||
$join['table'] = '(' . $join['table']->sql($binder) . ')';
|
||||
}
|
||||
|
||||
$joins .= sprintf(' %s JOIN %s %s', $join['type'], $join['table'], $join['alias']);
|
||||
|
||||
$condition = '';
|
||||
if (isset($join['conditions']) && $join['conditions'] instanceof ExpressionInterface) {
|
||||
$condition = $join['conditions']->sql($binder);
|
||||
}
|
||||
if ($condition === '') {
|
||||
$joins .= ' ON 1 = 1';
|
||||
} else {
|
||||
$joins .= " ON {$condition}";
|
||||
}
|
||||
}
|
||||
|
||||
return $joins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to build the string representation of a window clause.
|
||||
*
|
||||
* @param array $parts List of windows to be transformed to string
|
||||
* @param \Cake\Database\Query $query The query that is being compiled
|
||||
* @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder
|
||||
* @return string
|
||||
*/
|
||||
protected function _buildWindowPart(array $parts, Query $query, ValueBinder $binder): string
|
||||
{
|
||||
$windows = [];
|
||||
foreach ($parts as $window) {
|
||||
$windows[] = $window['name']->sql($binder) . ' AS (' . $window['window']->sql($binder) . ')';
|
||||
}
|
||||
|
||||
return ' WINDOW ' . implode(', ', $windows);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to generate SQL for SET expressions.
|
||||
*
|
||||
* @param array $parts List of keys & values to set.
|
||||
* @param \Cake\Database\Query $query The query that is being compiled
|
||||
* @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder
|
||||
* @return string
|
||||
*/
|
||||
protected function _buildSetPart(array $parts, Query $query, ValueBinder $binder): string
|
||||
{
|
||||
$set = [];
|
||||
foreach ($parts as $part) {
|
||||
if ($part instanceof ExpressionInterface) {
|
||||
$part = $part->sql($binder);
|
||||
}
|
||||
if ($part[0] === '(') {
|
||||
$part = substr($part, 1, -1);
|
||||
}
|
||||
$set[] = $part;
|
||||
}
|
||||
|
||||
return ' SET ' . implode('', $set);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the SQL string for all the UNION clauses in this query, when dealing
|
||||
* with query objects it will also transform them using their configured SQL
|
||||
* dialect.
|
||||
*
|
||||
* @param array $parts list of queries to be operated with UNION
|
||||
* @param \Cake\Database\Query $query The query that is being compiled
|
||||
* @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder
|
||||
* @return string
|
||||
*/
|
||||
protected function _buildUnionPart(array $parts, Query $query, ValueBinder $binder): string
|
||||
{
|
||||
$parts = array_map(function ($p) use ($binder) {
|
||||
$p['query'] = $p['query']->sql($binder);
|
||||
$p['query'] = $p['query'][0] === '(' ? trim($p['query'], '()') : $p['query'];
|
||||
$prefix = $p['all'] ? 'ALL ' : '';
|
||||
if ($this->_orderedUnion) {
|
||||
return "{$prefix}({$p['query']})";
|
||||
}
|
||||
|
||||
return $prefix . $p['query'];
|
||||
}, $parts);
|
||||
|
||||
if ($this->_orderedUnion) {
|
||||
return sprintf(")\nUNION %s", implode("\nUNION ", $parts));
|
||||
}
|
||||
|
||||
return sprintf("\nUNION %s", implode("\nUNION ", $parts));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the SQL fragment for INSERT INTO.
|
||||
*
|
||||
* @param array $parts The insert parts.
|
||||
* @param \Cake\Database\Query $query The query that is being compiled
|
||||
* @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder
|
||||
* @return string SQL fragment.
|
||||
*/
|
||||
protected function _buildInsertPart(array $parts, Query $query, ValueBinder $binder): string
|
||||
{
|
||||
if (!isset($parts[0])) {
|
||||
throw new DatabaseException(
|
||||
'Could not compile insert query. No table was specified. ' .
|
||||
'Use `into()` to define a table.'
|
||||
);
|
||||
}
|
||||
$table = $parts[0];
|
||||
$columns = $this->_stringifyExpressions($parts[1], $binder);
|
||||
$modifiers = $this->_buildModifierPart($query->clause('modifier'), $query, $binder);
|
||||
|
||||
return sprintf('INSERT%s INTO %s (%s)', $modifiers, $table, implode(', ', $columns));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the SQL fragment for INSERT INTO.
|
||||
*
|
||||
* @param array $parts The values parts.
|
||||
* @param \Cake\Database\Query $query The query that is being compiled
|
||||
* @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder
|
||||
* @return string SQL fragment.
|
||||
*/
|
||||
protected function _buildValuesPart(array $parts, Query $query, ValueBinder $binder): string
|
||||
{
|
||||
return implode('', $this->_stringifyExpressions($parts, $binder));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the SQL fragment for UPDATE.
|
||||
*
|
||||
* @param array $parts The update parts.
|
||||
* @param \Cake\Database\Query $query The query that is being compiled
|
||||
* @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder
|
||||
* @return string SQL fragment.
|
||||
*/
|
||||
protected function _buildUpdatePart(array $parts, Query $query, ValueBinder $binder): string
|
||||
{
|
||||
$table = $this->_stringifyExpressions($parts, $binder);
|
||||
$modifiers = $this->_buildModifierPart($query->clause('modifier'), $query, $binder);
|
||||
|
||||
return sprintf('UPDATE%s %s', $modifiers, implode(',', $table));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the SQL modifier fragment
|
||||
*
|
||||
* @param array $parts The query modifier parts
|
||||
* @param \Cake\Database\Query $query The query that is being compiled
|
||||
* @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder
|
||||
* @return string SQL fragment.
|
||||
*/
|
||||
protected function _buildModifierPart(array $parts, Query $query, ValueBinder $binder): string
|
||||
{
|
||||
if ($parts === []) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return ' ' . implode(' ', $this->_stringifyExpressions($parts, $binder, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function used to covert ExpressionInterface objects inside an array
|
||||
* into their string representation.
|
||||
*
|
||||
* @param array $expressions list of strings and ExpressionInterface objects
|
||||
* @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder
|
||||
* @param bool $wrap Whether to wrap each expression object with parenthesis
|
||||
* @return array
|
||||
*/
|
||||
protected function _stringifyExpressions(array $expressions, ValueBinder $binder, bool $wrap = true): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($expressions as $k => $expression) {
|
||||
if ($expression instanceof ExpressionInterface) {
|
||||
$value = $expression->sql($binder);
|
||||
$expression = $wrap ? '(' . $value . ')' : $value;
|
||||
}
|
||||
$result[$k] = $expression;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
Vendored
+358
@@ -0,0 +1,358 @@
|
||||
[](https://packagist.org/packages/cakephp/database)
|
||||
[](LICENSE.txt)
|
||||
|
||||
# A flexible and lightweight Database Library for PHP
|
||||
|
||||
This library abstracts and provides help with most aspects of dealing with relational
|
||||
databases such as keeping connections to the server, building queries,
|
||||
preventing SQL injections, inspecting and altering schemas, and with debugging and
|
||||
profiling queries sent to the database.
|
||||
|
||||
It adopts the API from the native PDO extension in PHP for familiarity, but solves many of the
|
||||
inconsistencies PDO has, while also providing several features that extend PDO's capabilities.
|
||||
|
||||
A distinguishing factor of this library when compared to similar database connection packages,
|
||||
is that it takes the concept of "data types" to its core. It lets you work with complex PHP objects
|
||||
or structures that can be passed as query conditions or to be inserted in the database.
|
||||
|
||||
The typing system will intelligently convert the PHP structures when passing them to the database, and
|
||||
convert them back when retrieving.
|
||||
|
||||
|
||||
## Connecting to the database
|
||||
|
||||
This library is able to work with the following databases:
|
||||
|
||||
* MySQL
|
||||
* Postgres
|
||||
* SQLite
|
||||
* Microsoft SQL Server (2008 and above)
|
||||
|
||||
The first thing you need to do when using this library is create a connection object.
|
||||
Before performing any operations with the connection, you need to specify a driver
|
||||
to use:
|
||||
|
||||
```php
|
||||
use Cake\Database\Connection;
|
||||
use Cake\Database\Driver\Mysql;
|
||||
use Cake\Database\Driver\Sqlite;
|
||||
|
||||
$connection = new Connection([
|
||||
'driver' => Mysql::class,
|
||||
'database' => 'test',
|
||||
'username' => 'root',
|
||||
'password' => 'secret',
|
||||
]);
|
||||
|
||||
$connection2 = new Connection([
|
||||
'driver' => Sqlite::class,
|
||||
'database' => '/path/to/file.db'
|
||||
]);
|
||||
```
|
||||
|
||||
Drivers are classes responsible for actually executing the commands to the database and
|
||||
correctly building the SQL according to the database specific dialect.
|
||||
|
||||
### Connection options
|
||||
|
||||
This is a list of possible options that can be passed when creating a connection:
|
||||
|
||||
* `driver`: Driver class name
|
||||
* `persistent`: Creates a persistent connection
|
||||
* `host`: The server host
|
||||
* `database`: The database name
|
||||
* `username`: Login credential
|
||||
* `password`: Connection secret
|
||||
* `encoding`: The connection encoding (or charset)
|
||||
* `timezone`: The connection timezone or time offset
|
||||
|
||||
## Using connections
|
||||
|
||||
After creating a connection, you can immediately interact with the database. You can choose
|
||||
either to use the shorthand methods `execute()`, `insert()`, `update()`, `delete()` or use the
|
||||
`newQuery()` for using a query builder.
|
||||
|
||||
The easiest way of executing queries is by using the `execute()` method, it will return a
|
||||
`Cake\Database\StatementInterface` that you can use to get the data back:
|
||||
|
||||
```php
|
||||
$statement = $connection->execute('SELECT * FROM articles');
|
||||
|
||||
while($row = $statement->fetch('assoc')) {
|
||||
echo $row['title'] . PHP_EOL;
|
||||
}
|
||||
```
|
||||
Binding values to parametrized arguments is also possible with the execute function:
|
||||
|
||||
```php
|
||||
$statement = $connection->execute('SELECT * FROM articles WHERE id = :id', ['id' => 1], ['id' => 'integer']);
|
||||
$results = $statement->fetch('assoc');
|
||||
```
|
||||
|
||||
The third parameter is the types the passed values should be converted to when passed to the database. If
|
||||
no types are passed, all arguments will be interpreted as a string.
|
||||
|
||||
Alternatively you can construct a statement manually and then fetch rows from it:
|
||||
|
||||
```php
|
||||
$statement = $connection->prepare('SELECT * from articles WHERE id != :id');
|
||||
$statement->bind(['id' => 1], ['id' => 'integer']);
|
||||
$results = $statement->fetchAll('assoc');
|
||||
```
|
||||
|
||||
The default types that are understood by this library and can be passed to the `bind()` function or to `execute()`
|
||||
are:
|
||||
|
||||
* biginteger
|
||||
* binary
|
||||
* date
|
||||
* float
|
||||
* decimal
|
||||
* integer
|
||||
* time
|
||||
* datetime
|
||||
* timestamp
|
||||
* uuid
|
||||
|
||||
More types can be added dynamically in a bit.
|
||||
|
||||
Statements can be reused by binding new values to the parameters in the query:
|
||||
|
||||
```php
|
||||
$statement = $connection->prepare('SELECT * from articles WHERE id = :id');
|
||||
$statement->bind(['id' => 1], ['id' => 'integer']);
|
||||
$results = $statement->fetchAll('assoc');
|
||||
|
||||
$statement->bind(['id' => 1], ['id' => 'integer']);
|
||||
$results = $statement->fetchAll('assoc');
|
||||
```
|
||||
|
||||
### Updating Rows
|
||||
|
||||
Updating can be done using the `update()` function in the connection object. In the following
|
||||
example we will update the title of the article with id = 1:
|
||||
|
||||
```php
|
||||
$connection->update('articles', ['title' => 'New title'], ['id' => 1]);
|
||||
```
|
||||
|
||||
The concept of data types is central to this library, so you can use the last parameter of the function
|
||||
to specify what types should be used:
|
||||
|
||||
```php
|
||||
$connection->update(
|
||||
'articles',
|
||||
['title' => 'New title'],
|
||||
['created >=' => new DateTime('-3 day'), 'created <' => new DateTime('now')],
|
||||
['created' => 'datetime']
|
||||
);
|
||||
```
|
||||
|
||||
The example above will execute the following SQL:
|
||||
|
||||
```sql
|
||||
UPDATE articles SET title = 'New Title' WHERE created >= '2014-10-10 00:00:00' AND created < '2014-10-13 00:00:00';
|
||||
```
|
||||
|
||||
More on creating complex where conditions or more complex update queries later.
|
||||
|
||||
### Deleting Rows
|
||||
|
||||
Similarly, the `delete()` method is used to delete rows from the database:
|
||||
|
||||
```php
|
||||
$connection->delete('articles', ['created <' => DateTime('now')], ['created' => 'date']);
|
||||
```
|
||||
|
||||
Will generate the following SQL
|
||||
|
||||
```sql
|
||||
DELETE FROM articles where created < '2014-10-10'
|
||||
```
|
||||
|
||||
### Inserting Rows
|
||||
|
||||
Rows can be inserted using the `insert()` method:
|
||||
|
||||
```php
|
||||
$connection->insert(
|
||||
'articles',
|
||||
['title' => 'My Title', 'body' => 'Some paragraph', 'created' => new DateTime()],
|
||||
['created' => 'datetime']
|
||||
);
|
||||
```
|
||||
|
||||
More complex updates, deletes and insert queries can be generated using the `Query` class.
|
||||
|
||||
## Query Builder
|
||||
|
||||
One of the goals of this library is to allow the generation of both simple and complex queries with
|
||||
ease. The query builder can be accessed by getting a new instance of a query:
|
||||
|
||||
```php
|
||||
$query = $connection->newQuery();
|
||||
```
|
||||
|
||||
### Selecting Fields
|
||||
|
||||
Adding fields to the `SELECT` clause:
|
||||
|
||||
```php
|
||||
$query->select(['id', 'title', 'body']);
|
||||
|
||||
// Results in SELECT id AS pk, title AS aliased_title, body ...
|
||||
$query->select(['pk' => 'id', 'aliased_title' => 'title', 'body']);
|
||||
|
||||
// Use a closure
|
||||
$query->select(function ($query) {
|
||||
return ['id', 'title', 'body'];
|
||||
});
|
||||
```
|
||||
|
||||
### Where Conditions
|
||||
|
||||
Generating conditions:
|
||||
|
||||
```php
|
||||
// WHERE id = 1
|
||||
$query->where(['id' => 1]);
|
||||
|
||||
// WHERE id > 2
|
||||
$query->where(['id >' => 1]);
|
||||
```
|
||||
|
||||
As you can see you can use any operator by placing it with a space after the field name.
|
||||
Adding multiple conditions is easy as well:
|
||||
|
||||
```php
|
||||
$query->where(['id >' => 1])->andWhere(['title' => 'My Title']);
|
||||
|
||||
// Equivalent to
|
||||
$query->where(['id >' => 1, 'title' => 'My title']);
|
||||
```
|
||||
|
||||
It is possible to generate `OR` conditions as well
|
||||
|
||||
```php
|
||||
$query->where(['OR' => ['id >' => 1, 'title' => 'My title']]);
|
||||
```
|
||||
|
||||
For even more complex conditions you can use closures and expression objects:
|
||||
|
||||
```php
|
||||
$query->where(function ($exp) {
|
||||
return $exp
|
||||
->eq('author_id', 2)
|
||||
->eq('published', true)
|
||||
->notEq('spam', true)
|
||||
->gt('view_count', 10);
|
||||
});
|
||||
```
|
||||
|
||||
Which results in:
|
||||
|
||||
```sql
|
||||
SELECT * FROM articles
|
||||
WHERE
|
||||
author_id = 2
|
||||
AND published = 1
|
||||
AND spam != 1
|
||||
AND view_count > 10
|
||||
```
|
||||
|
||||
Combining expressions is also possible:
|
||||
|
||||
```php
|
||||
$query->where(function ($exp) {
|
||||
$orConditions = $exp->or(['author_id' => 2])
|
||||
->eq('author_id', 5);
|
||||
return $exp
|
||||
->not($orConditions)
|
||||
->lte('view_count', 10);
|
||||
});
|
||||
```
|
||||
|
||||
That generates:
|
||||
|
||||
```sql
|
||||
SELECT *
|
||||
FROM articles
|
||||
WHERE
|
||||
NOT (author_id = 2 OR author_id = 5)
|
||||
AND view_count <= 10
|
||||
```
|
||||
|
||||
When using the expression objects you can use the following methods to create conditions:
|
||||
|
||||
* `eq()` Creates an equality condition.
|
||||
* `notEq()` Create an inequality condition
|
||||
* `like()` Create a condition using the LIKE operator.
|
||||
* `notLike()` Create a negated LIKE condition.
|
||||
* `in()` Create a condition using IN.
|
||||
* `notIn()` Create a negated condition using IN.
|
||||
* `gt()` Create a > condition.
|
||||
* `gte()` Create a >= condition.
|
||||
* `lt()` Create a < condition.
|
||||
* `lte()` Create a <= condition.
|
||||
* `isNull()` Create an IS NULL condition.
|
||||
* `isNotNull()` Create a negated IS NULL condition.
|
||||
|
||||
### Aggregates and SQL Functions
|
||||
|
||||
```php
|
||||
// Results in SELECT COUNT(*) count FROM ...
|
||||
$query->select(['count' => $query->func()->count('*')]);
|
||||
```
|
||||
|
||||
A number of commonly used functions can be created with the func() method:
|
||||
|
||||
* `sum()` Calculate a sum. The arguments will be treated as literal values.
|
||||
* `avg()` Calculate an average. The arguments will be treated as literal values.
|
||||
* `min()` Calculate the min of a column. The arguments will be treated as literal values.
|
||||
* `max()` Calculate the max of a column. The arguments will be treated as literal values.
|
||||
* `count()` Calculate the count. The arguments will be treated as literal values.
|
||||
* `concat()` Concatenate two values together. The arguments are treated as bound parameters unless marked as literal.
|
||||
* `coalesce()` Coalesce values. The arguments are treated as bound parameters unless marked as literal.
|
||||
* `dateDiff()` Get the difference between two dates/times. The arguments are treated as bound parameters unless marked as literal.
|
||||
* `now()` Take either 'time' or 'date' as an argument allowing you to get either the current time, or current date.
|
||||
|
||||
When providing arguments for SQL functions, there are two kinds of parameters you can use, literal arguments and bound parameters. Literal
|
||||
parameters allow you to reference columns or other SQL literals. Bound parameters can be used to safely add user data to SQL functions.
|
||||
For example:
|
||||
|
||||
```php
|
||||
$concat = $query->func()->concat([
|
||||
'title' => 'literal',
|
||||
' NEW'
|
||||
]);
|
||||
$query->select(['title' => $concat]);
|
||||
```
|
||||
|
||||
The above generates:
|
||||
|
||||
```sql
|
||||
SELECT CONCAT(title, :c0) ...;
|
||||
```
|
||||
|
||||
### Other SQL Clauses
|
||||
|
||||
Read of all other SQL clauses that the builder is capable of generating in the [official API docs](https://api.cakephp.org/4.x/class-Cake.Database.Query.html)
|
||||
|
||||
### Getting Results out of a Query
|
||||
|
||||
Once you’ve made your query, you’ll want to retrieve rows from it. There are a few ways of doing this:
|
||||
|
||||
```php
|
||||
// Iterate the query
|
||||
foreach ($query as $row) {
|
||||
// Do stuff.
|
||||
}
|
||||
|
||||
// Get the statement and fetch all results
|
||||
$results = $query->execute()->fetchAll('assoc');
|
||||
```
|
||||
|
||||
## Official API
|
||||
|
||||
You can read the official [official API docs](https://api.cakephp.org/4.x/namespace-Cake.Database.html) to learn more of what this library
|
||||
has to offer.
|
||||
@@ -0,0 +1,69 @@
|
||||
<?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.2.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Retry;
|
||||
|
||||
use Cake\Core\Retry\RetryStrategyInterface;
|
||||
use Exception;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
* Implements retry strategy based on db error codes and wait interval.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ErrorCodeWaitStrategy implements RetryStrategyInterface
|
||||
{
|
||||
/**
|
||||
* @var array<int>
|
||||
*/
|
||||
protected $errorCodes;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $retryInterval;
|
||||
|
||||
/**
|
||||
* @param array<int> $errorCodes DB-specific error codes that allow retrying
|
||||
* @param int $retryInterval Seconds to wait before allowing next retry, 0 for no wait.
|
||||
*/
|
||||
public function __construct(array $errorCodes, int $retryInterval)
|
||||
{
|
||||
$this->errorCodes = $errorCodes;
|
||||
$this->retryInterval = $retryInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function shouldRetry(Exception $exception, int $retryCount): bool
|
||||
{
|
||||
if (
|
||||
$exception instanceof PDOException &&
|
||||
$exception->errorInfo &&
|
||||
in_array($exception->errorInfo[1], $this->errorCodes)
|
||||
) {
|
||||
if ($this->retryInterval > 0) {
|
||||
sleep($this->retryInterval);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
<?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.6.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Retry;
|
||||
|
||||
use Cake\Core\Retry\RetryStrategyInterface;
|
||||
use Cake\Database\Connection;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Makes sure the connection to the database is alive before authorizing
|
||||
* the retry of an action.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ReconnectStrategy implements RetryStrategyInterface
|
||||
{
|
||||
/**
|
||||
* The list of error strings to match when looking for a disconnection error.
|
||||
*
|
||||
* This is a static variable to enable opcache to inline the values.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected static $causes = [
|
||||
'gone away',
|
||||
'Lost connection',
|
||||
'Transaction() on null',
|
||||
'closed the connection unexpectedly',
|
||||
'closed unexpectedly',
|
||||
'deadlock avoided',
|
||||
'decryption failed or bad record mac',
|
||||
'is dead or not enabled',
|
||||
'no connection to the server',
|
||||
'query_wait_timeout',
|
||||
'reset by peer',
|
||||
'terminate due to client_idle_limit',
|
||||
'while sending',
|
||||
'writing data to the connection',
|
||||
];
|
||||
|
||||
/**
|
||||
* The connection to check for validity
|
||||
*
|
||||
* @var \Cake\Database\Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* Creates the ReconnectStrategy object by storing a reference to the
|
||||
* passed connection. This reference will be used to automatically
|
||||
* reconnect to the server in case of failure.
|
||||
*
|
||||
* @param \Cake\Database\Connection $connection The connection to check
|
||||
*/
|
||||
public function __construct(Connection $connection)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Checks whether the exception was caused by a lost connection,
|
||||
* and returns true if it was able to successfully reconnect.
|
||||
*/
|
||||
public function shouldRetry(Exception $exception, int $retryCount): bool
|
||||
{
|
||||
$message = $exception->getMessage();
|
||||
|
||||
foreach (static::$causes as $cause) {
|
||||
if (strstr($message, $cause) !== false) {
|
||||
return $this->reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to re-establish the connection to the server, if it is safe to do so
|
||||
*
|
||||
* @return bool Whether the connection was re-established
|
||||
*/
|
||||
protected function reconnect(): bool
|
||||
{
|
||||
if ($this->connection->inTransaction()) {
|
||||
// It is not safe to blindly reconnect in the middle of a transaction
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Make sure we free any resources associated with the old connection
|
||||
$this->connection->getDriver()->disconnect();
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
|
||||
try {
|
||||
$this->connection->connect();
|
||||
if ($this->connection->isQueryLoggingEnabled()) {
|
||||
$this->connection->log('[RECONNECT]');
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
// If there was an error connecting again, don't report it back,
|
||||
// let the retry handler do it.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
deprecationWarning(
|
||||
'Since 4.1.0: Cake\Database\Schema\BaseSchema is deprecated. ' .
|
||||
'Use Cake\Database\Schema\SchemaDialect instead.'
|
||||
);
|
||||
class_exists('Cake\Database\Schema\SchemaDialect');
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
<?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\Database\Schema;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
|
||||
/**
|
||||
* Decorates a schema collection and adds caching
|
||||
*/
|
||||
class CachedCollection implements CollectionInterface
|
||||
{
|
||||
/**
|
||||
* Cacher instance.
|
||||
*
|
||||
* @var \Psr\SimpleCache\CacheInterface
|
||||
*/
|
||||
protected $cacher;
|
||||
|
||||
/**
|
||||
* The decorated schema collection
|
||||
*
|
||||
* @var \Cake\Database\Schema\CollectionInterface
|
||||
*/
|
||||
protected $collection;
|
||||
|
||||
/**
|
||||
* The cache key prefix
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $prefix;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Cake\Database\Schema\CollectionInterface $collection The collection to wrap.
|
||||
* @param string $prefix The cache key prefix to use. Typically the connection name.
|
||||
* @param \Psr\SimpleCache\CacheInterface $cacher Cacher instance.
|
||||
*/
|
||||
public function __construct(CollectionInterface $collection, string $prefix, CacheInterface $cacher)
|
||||
{
|
||||
$this->collection = $collection;
|
||||
$this->prefix = $prefix;
|
||||
$this->cacher = $cacher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function listTablesWithoutViews(): array
|
||||
{
|
||||
return $this->collection->listTablesWithoutViews();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function listTables(): array
|
||||
{
|
||||
return $this->collection->listTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describe(string $name, array $options = []): TableSchemaInterface
|
||||
{
|
||||
$options += ['forceRefresh' => false];
|
||||
$cacheKey = $this->cacheKey($name);
|
||||
|
||||
if (!$options['forceRefresh']) {
|
||||
$cached = $this->cacher->get($cacheKey);
|
||||
if ($cached !== null) {
|
||||
return $cached;
|
||||
}
|
||||
}
|
||||
|
||||
$table = $this->collection->describe($name, $options);
|
||||
$this->cacher->set($cacheKey, $table);
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache key for a given name.
|
||||
*
|
||||
* @param string $name The name to get a cache key for.
|
||||
* @return string The cache key.
|
||||
*/
|
||||
public function cacheKey(string $name): string
|
||||
{
|
||||
return $this->prefix . '_' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cacher.
|
||||
*
|
||||
* @param \Psr\SimpleCache\CacheInterface $cacher Cacher object
|
||||
* @return $this
|
||||
*/
|
||||
public function setCacher(CacheInterface $cacher)
|
||||
{
|
||||
$this->cacher = $cacher;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a cacher.
|
||||
*
|
||||
* @return \Psr\SimpleCache\CacheInterface $cacher Cacher object
|
||||
*/
|
||||
public function getCacher(): CacheInterface
|
||||
{
|
||||
return $this->cacher;
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
<?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\Database\Schema;
|
||||
|
||||
use Cake\Database\Connection;
|
||||
use Cake\Database\Exception\DatabaseException;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
* Represents a database schema collection
|
||||
*
|
||||
* Used to access information about the tables,
|
||||
* and other data in a database.
|
||||
*/
|
||||
class Collection implements CollectionInterface
|
||||
{
|
||||
/**
|
||||
* Connection object
|
||||
*
|
||||
* @var \Cake\Database\Connection
|
||||
*/
|
||||
protected $_connection;
|
||||
|
||||
/**
|
||||
* Schema dialect instance.
|
||||
*
|
||||
* @var \Cake\Database\Schema\SchemaDialect
|
||||
*/
|
||||
protected $_dialect;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Cake\Database\Connection $connection The connection instance.
|
||||
*/
|
||||
public function __construct(Connection $connection)
|
||||
{
|
||||
$this->_connection = $connection;
|
||||
$this->_dialect = $connection->getDriver()->schemaDialect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of tables, excluding any views, available in the current connection.
|
||||
*
|
||||
* @return array<string> The list of tables in the connected database/schema.
|
||||
*/
|
||||
public function listTablesWithoutViews(): array
|
||||
{
|
||||
[$sql, $params] = $this->_dialect->listTablesWithoutViewsSql($this->_connection->getDriver()->config());
|
||||
$result = [];
|
||||
$statement = $this->_connection->execute($sql, $params);
|
||||
while ($row = $statement->fetch()) {
|
||||
$result[] = $row[0];
|
||||
}
|
||||
$statement->closeCursor();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of tables and views available in the current connection.
|
||||
*
|
||||
* @return array<string> The list of tables and views in the connected database/schema.
|
||||
*/
|
||||
public function listTables(): array
|
||||
{
|
||||
[$sql, $params] = $this->_dialect->listTablesSql($this->_connection->getDriver()->config());
|
||||
$result = [];
|
||||
$statement = $this->_connection->execute($sql, $params);
|
||||
while ($row = $statement->fetch()) {
|
||||
$result[] = $row[0];
|
||||
}
|
||||
$statement->closeCursor();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the column metadata for a table.
|
||||
*
|
||||
* The name can include a database schema name in the form 'schema.table'.
|
||||
*
|
||||
* Caching will be applied if `cacheMetadata` key is present in the Connection
|
||||
* configuration options. Defaults to _cake_model_ when true.
|
||||
*
|
||||
* ### Options
|
||||
*
|
||||
* - `forceRefresh` - Set to true to force rebuilding the cached metadata.
|
||||
* Defaults to false.
|
||||
*
|
||||
* @param string $name The name of the table to describe.
|
||||
* @param array<string, mixed> $options The options to use, see above.
|
||||
* @return \Cake\Database\Schema\TableSchema Object with column metadata.
|
||||
* @throws \Cake\Database\Exception\DatabaseException when table cannot be described.
|
||||
*/
|
||||
public function describe(string $name, array $options = []): TableSchemaInterface
|
||||
{
|
||||
$config = $this->_connection->getDriver()->config();
|
||||
if (strpos($name, '.')) {
|
||||
[$config['schema'], $name] = explode('.', $name);
|
||||
}
|
||||
$table = $this->_connection->getDriver()->newTableSchema($name);
|
||||
|
||||
$this->_reflect('Column', $name, $config, $table);
|
||||
if (count($table->columns()) === 0) {
|
||||
throw new DatabaseException(sprintf('Cannot describe %s. It has 0 columns.', $name));
|
||||
}
|
||||
|
||||
$this->_reflect('Index', $name, $config, $table);
|
||||
$this->_reflect('ForeignKey', $name, $config, $table);
|
||||
$this->_reflect('Options', $name, $config, $table);
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for running each step of the reflection process.
|
||||
*
|
||||
* @param string $stage The stage name.
|
||||
* @param string $name The table name.
|
||||
* @param array<string, mixed> $config The config data.
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table schema instance.
|
||||
* @return void
|
||||
* @throws \Cake\Database\Exception\DatabaseException on query failure.
|
||||
* @uses \Cake\Database\Schema\SchemaDialect::describeColumnSql
|
||||
* @uses \Cake\Database\Schema\SchemaDialect::describeIndexSql
|
||||
* @uses \Cake\Database\Schema\SchemaDialect::describeForeignKeySql
|
||||
* @uses \Cake\Database\Schema\SchemaDialect::describeOptionsSql
|
||||
* @uses \Cake\Database\Schema\SchemaDialect::convertColumnDescription
|
||||
* @uses \Cake\Database\Schema\SchemaDialect::convertIndexDescription
|
||||
* @uses \Cake\Database\Schema\SchemaDialect::convertForeignKeyDescription
|
||||
* @uses \Cake\Database\Schema\SchemaDialect::convertOptionsDescription
|
||||
*/
|
||||
protected function _reflect(string $stage, string $name, array $config, TableSchema $schema): void
|
||||
{
|
||||
$describeMethod = "describe{$stage}Sql";
|
||||
$convertMethod = "convert{$stage}Description";
|
||||
|
||||
[$sql, $params] = $this->_dialect->{$describeMethod}($name, $config);
|
||||
if (empty($sql)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$statement = $this->_connection->execute($sql, $params);
|
||||
} catch (PDOException $e) {
|
||||
throw new DatabaseException($e->getMessage(), 500, $e);
|
||||
}
|
||||
/** @psalm-suppress PossiblyFalseIterator */
|
||||
foreach ($statement->fetchAll('assoc') as $row) {
|
||||
$this->_dialect->{$convertMethod}($schema, $row);
|
||||
}
|
||||
$statement->closeCursor();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?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.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Schema;
|
||||
|
||||
/**
|
||||
* Represents a database schema collection
|
||||
*
|
||||
* Used to access information about the tables,
|
||||
* and other data in a database.
|
||||
*
|
||||
* @method array<string> listTablesWithoutViews() Get the list of tables available in the current connection.
|
||||
* This will exclude any views in the schema.
|
||||
*/
|
||||
interface CollectionInterface
|
||||
{
|
||||
/**
|
||||
* Get the list of tables available in the current connection.
|
||||
*
|
||||
* @return array<string> The list of tables in the connected database/schema.
|
||||
*/
|
||||
public function listTables(): array;
|
||||
|
||||
/**
|
||||
* Get the column metadata for a table.
|
||||
*
|
||||
* Caching will be applied if `cacheMetadata` key is present in the Connection
|
||||
* configuration options. Defaults to _cake_model_ when true.
|
||||
*
|
||||
* ### Options
|
||||
*
|
||||
* - `forceRefresh` - Set to true to force rebuilding the cached metadata.
|
||||
* Defaults to false.
|
||||
*
|
||||
* @param string $name The name of the table to describe.
|
||||
* @param array<string, mixed> $options The options to use, see above.
|
||||
* @return \Cake\Database\Schema\TableSchemaInterface Object with column metadata.
|
||||
* @throws \Cake\Database\Exception\DatabaseException when table cannot be described.
|
||||
*/
|
||||
public function describe(string $name, array $options = []): TableSchemaInterface;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
deprecationWarning(
|
||||
'Since 4.1.0: Cake\Database\Schema\MysqlSchema is deprecated. ' .
|
||||
'Use Cake\Database\Schema\MysqlSchemaDialect instead.'
|
||||
);
|
||||
class_exists('Cake\Database\Schema\MysqlSchemaDialect');
|
||||
@@ -0,0 +1,669 @@
|
||||
<?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\Database\Schema;
|
||||
|
||||
use Cake\Database\DriverInterface;
|
||||
use Cake\Database\Exception\DatabaseException;
|
||||
|
||||
/**
|
||||
* Schema generation/reflection features for MySQL
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class MysqlSchemaDialect extends SchemaDialect
|
||||
{
|
||||
/**
|
||||
* The driver instance being used.
|
||||
*
|
||||
* @var \Cake\Database\Driver\Mysql
|
||||
*/
|
||||
protected $_driver;
|
||||
|
||||
/**
|
||||
* Generate the SQL to list the tables and views.
|
||||
*
|
||||
* @param array<string, mixed> $config The connection configuration to use for
|
||||
* getting tables from.
|
||||
* @return array<mixed> An array of (sql, params) to execute.
|
||||
*/
|
||||
public function listTablesSql(array $config): array
|
||||
{
|
||||
return ['SHOW FULL TABLES FROM ' . $this->_driver->quoteIdentifier($config['database']), []];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the SQL to list the tables, excluding all views.
|
||||
*
|
||||
* @param array<string, mixed> $config The connection configuration to use for
|
||||
* getting tables from.
|
||||
* @return array<mixed> An array of (sql, params) to execute.
|
||||
*/
|
||||
public function listTablesWithoutViewsSql(array $config): array
|
||||
{
|
||||
return [
|
||||
'SHOW FULL TABLES FROM ' . $this->_driver->quoteIdentifier($config['database'])
|
||||
. ' WHERE TABLE_TYPE = "BASE TABLE"'
|
||||
, []];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeColumnSql(string $tableName, array $config): array
|
||||
{
|
||||
return ['SHOW FULL COLUMNS FROM ' . $this->_driver->quoteIdentifier($tableName), []];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeIndexSql(string $tableName, array $config): array
|
||||
{
|
||||
return ['SHOW INDEXES FROM ' . $this->_driver->quoteIdentifier($tableName), []];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeOptionsSql(string $tableName, array $config): array
|
||||
{
|
||||
return ['SHOW TABLE STATUS WHERE Name = ?', [$tableName]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertOptionsDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$schema->setOptions([
|
||||
'engine' => $row['Engine'],
|
||||
'collation' => $row['Collation'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a MySQL column type into an abstract type.
|
||||
*
|
||||
* The returned type will be a type that Cake\Database\TypeFactory can handle.
|
||||
*
|
||||
* @param string $column The column type + length
|
||||
* @return array<string, mixed> Array of column information.
|
||||
* @throws \Cake\Database\Exception\DatabaseException When column type cannot be parsed.
|
||||
*/
|
||||
protected function _convertColumn(string $column): array
|
||||
{
|
||||
preg_match('/([a-z]+)(?:\(([0-9,]+)\))?\s*([a-z]+)?/i', $column, $matches);
|
||||
if (empty($matches)) {
|
||||
throw new DatabaseException(sprintf('Unable to parse column type from "%s"', $column));
|
||||
}
|
||||
|
||||
$col = strtolower($matches[1]);
|
||||
$length = $precision = $scale = null;
|
||||
if (isset($matches[2]) && strlen($matches[2])) {
|
||||
$length = $matches[2];
|
||||
if (strpos($matches[2], ',') !== false) {
|
||||
[$length, $precision] = explode(',', $length);
|
||||
}
|
||||
$length = (int)$length;
|
||||
$precision = (int)$precision;
|
||||
}
|
||||
|
||||
$type = $this->_applyTypeSpecificColumnConversion(
|
||||
$col,
|
||||
compact('length', 'precision', 'scale')
|
||||
);
|
||||
if ($type !== null) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
if (in_array($col, ['date', 'time'])) {
|
||||
return ['type' => $col, 'length' => null];
|
||||
}
|
||||
if (in_array($col, ['datetime', 'timestamp'])) {
|
||||
$typeName = $col;
|
||||
if ($length > 0) {
|
||||
$typeName = $col . 'fractional';
|
||||
}
|
||||
|
||||
return ['type' => $typeName, 'length' => null, 'precision' => $length];
|
||||
}
|
||||
|
||||
if (($col === 'tinyint' && $length === 1) || $col === 'boolean') {
|
||||
return ['type' => TableSchema::TYPE_BOOLEAN, 'length' => null];
|
||||
}
|
||||
|
||||
$unsigned = (isset($matches[3]) && strtolower($matches[3]) === 'unsigned');
|
||||
if (strpos($col, 'bigint') !== false || $col === 'bigint') {
|
||||
return ['type' => TableSchema::TYPE_BIGINTEGER, 'length' => null, 'unsigned' => $unsigned];
|
||||
}
|
||||
if ($col === 'tinyint') {
|
||||
return ['type' => TableSchema::TYPE_TINYINTEGER, 'length' => null, 'unsigned' => $unsigned];
|
||||
}
|
||||
if ($col === 'smallint') {
|
||||
return ['type' => TableSchema::TYPE_SMALLINTEGER, 'length' => null, 'unsigned' => $unsigned];
|
||||
}
|
||||
if (in_array($col, ['int', 'integer', 'mediumint'])) {
|
||||
return ['type' => TableSchema::TYPE_INTEGER, 'length' => null, 'unsigned' => $unsigned];
|
||||
}
|
||||
if ($col === 'char' && $length === 36) {
|
||||
return ['type' => TableSchema::TYPE_UUID, 'length' => null];
|
||||
}
|
||||
if ($col === 'char') {
|
||||
return ['type' => TableSchema::TYPE_CHAR, 'length' => $length];
|
||||
}
|
||||
if (strpos($col, 'char') !== false) {
|
||||
return ['type' => TableSchema::TYPE_STRING, 'length' => $length];
|
||||
}
|
||||
if (strpos($col, 'text') !== false) {
|
||||
$lengthName = substr($col, 0, -4);
|
||||
$length = TableSchema::$columnLengths[$lengthName] ?? null;
|
||||
|
||||
return ['type' => TableSchema::TYPE_TEXT, 'length' => $length];
|
||||
}
|
||||
if ($col === 'binary' && $length === 16) {
|
||||
return ['type' => TableSchema::TYPE_BINARY_UUID, 'length' => null];
|
||||
}
|
||||
if (strpos($col, 'blob') !== false || in_array($col, ['binary', 'varbinary'])) {
|
||||
$lengthName = substr($col, 0, -4);
|
||||
$length = TableSchema::$columnLengths[$lengthName] ?? $length;
|
||||
|
||||
return ['type' => TableSchema::TYPE_BINARY, 'length' => $length];
|
||||
}
|
||||
if (strpos($col, 'float') !== false || strpos($col, 'double') !== false) {
|
||||
return [
|
||||
'type' => TableSchema::TYPE_FLOAT,
|
||||
'length' => $length,
|
||||
'precision' => $precision,
|
||||
'unsigned' => $unsigned,
|
||||
];
|
||||
}
|
||||
if (strpos($col, 'decimal') !== false) {
|
||||
return [
|
||||
'type' => TableSchema::TYPE_DECIMAL,
|
||||
'length' => $length,
|
||||
'precision' => $precision,
|
||||
'unsigned' => $unsigned,
|
||||
];
|
||||
}
|
||||
|
||||
if (strpos($col, 'json') !== false) {
|
||||
return ['type' => TableSchema::TYPE_JSON, 'length' => null];
|
||||
}
|
||||
|
||||
return ['type' => TableSchema::TYPE_STRING, 'length' => null];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertColumnDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$field = $this->_convertColumn($row['Type']);
|
||||
$field += [
|
||||
'null' => $row['Null'] === 'YES',
|
||||
'default' => $row['Default'],
|
||||
'collate' => $row['Collation'],
|
||||
'comment' => $row['Comment'],
|
||||
];
|
||||
if (isset($row['Extra']) && $row['Extra'] === 'auto_increment') {
|
||||
$field['autoIncrement'] = true;
|
||||
}
|
||||
$schema->addColumn($row['Field'], $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertIndexDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$type = null;
|
||||
$columns = $length = [];
|
||||
|
||||
$name = $row['Key_name'];
|
||||
if ($name === 'PRIMARY') {
|
||||
$name = $type = TableSchema::CONSTRAINT_PRIMARY;
|
||||
}
|
||||
|
||||
if (!empty($row['Column_name'])) {
|
||||
$columns[] = $row['Column_name'];
|
||||
}
|
||||
|
||||
if ($row['Index_type'] === 'FULLTEXT') {
|
||||
$type = TableSchema::INDEX_FULLTEXT;
|
||||
} elseif ((int)$row['Non_unique'] === 0 && $type !== 'primary') {
|
||||
$type = TableSchema::CONSTRAINT_UNIQUE;
|
||||
} elseif ($type !== 'primary') {
|
||||
$type = TableSchema::INDEX_INDEX;
|
||||
}
|
||||
|
||||
if (!empty($row['Sub_part'])) {
|
||||
$length[$row['Column_name']] = $row['Sub_part'];
|
||||
}
|
||||
$isIndex = (
|
||||
$type === TableSchema::INDEX_INDEX ||
|
||||
$type === TableSchema::INDEX_FULLTEXT
|
||||
);
|
||||
if ($isIndex) {
|
||||
$existing = $schema->getIndex($name);
|
||||
} else {
|
||||
$existing = $schema->getConstraint($name);
|
||||
}
|
||||
|
||||
// MySQL multi column indexes come back as multiple rows.
|
||||
if (!empty($existing)) {
|
||||
$columns = array_merge($existing['columns'], $columns);
|
||||
$length = array_merge($existing['length'], $length);
|
||||
}
|
||||
if ($isIndex) {
|
||||
$schema->addIndex($name, [
|
||||
'type' => $type,
|
||||
'columns' => $columns,
|
||||
'length' => $length,
|
||||
]);
|
||||
} else {
|
||||
$schema->addConstraint($name, [
|
||||
'type' => $type,
|
||||
'columns' => $columns,
|
||||
'length' => $length,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeForeignKeySql(string $tableName, array $config): array
|
||||
{
|
||||
$sql = 'SELECT * FROM information_schema.key_column_usage AS kcu
|
||||
INNER JOIN information_schema.referential_constraints AS rc
|
||||
ON (
|
||||
kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
|
||||
AND kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
|
||||
)
|
||||
WHERE kcu.TABLE_SCHEMA = ? AND kcu.TABLE_NAME = ? AND rc.TABLE_NAME = ?
|
||||
ORDER BY kcu.ORDINAL_POSITION ASC';
|
||||
|
||||
return [$sql, [$config['database'], $tableName, $tableName]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertForeignKeyDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$data = [
|
||||
'type' => TableSchema::CONSTRAINT_FOREIGN,
|
||||
'columns' => [$row['COLUMN_NAME']],
|
||||
'references' => [$row['REFERENCED_TABLE_NAME'], $row['REFERENCED_COLUMN_NAME']],
|
||||
'update' => $this->_convertOnClause($row['UPDATE_RULE']),
|
||||
'delete' => $this->_convertOnClause($row['DELETE_RULE']),
|
||||
];
|
||||
$name = $row['CONSTRAINT_NAME'];
|
||||
$schema->addConstraint($name, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function truncateTableSql(TableSchema $schema): array
|
||||
{
|
||||
return [sprintf('TRUNCATE TABLE `%s`', $schema->name())];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function createTableSql(TableSchema $schema, array $columns, array $constraints, array $indexes): array
|
||||
{
|
||||
$content = implode(",\n", array_merge($columns, $constraints, $indexes));
|
||||
$temporary = $schema->isTemporary() ? ' TEMPORARY ' : ' ';
|
||||
$content = sprintf("CREATE%sTABLE `%s` (\n%s\n)", $temporary, $schema->name(), $content);
|
||||
$options = $schema->getOptions();
|
||||
if (isset($options['engine'])) {
|
||||
$content .= sprintf(' ENGINE=%s', $options['engine']);
|
||||
}
|
||||
if (isset($options['charset'])) {
|
||||
$content .= sprintf(' DEFAULT CHARSET=%s', $options['charset']);
|
||||
}
|
||||
if (isset($options['collate'])) {
|
||||
$content .= sprintf(' COLLATE=%s', $options['collate']);
|
||||
}
|
||||
|
||||
return [$content];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function columnSql(TableSchema $schema, string $name): string
|
||||
{
|
||||
/** @var array $data */
|
||||
$data = $schema->getColumn($name);
|
||||
|
||||
$sql = $this->_getTypeSpecificColumnSql($data['type'], $schema, $name);
|
||||
if ($sql !== null) {
|
||||
return $sql;
|
||||
}
|
||||
|
||||
$out = $this->_driver->quoteIdentifier($name);
|
||||
$nativeJson = $this->_driver->supports(DriverInterface::FEATURE_JSON);
|
||||
|
||||
$typeMap = [
|
||||
TableSchema::TYPE_TINYINTEGER => ' TINYINT',
|
||||
TableSchema::TYPE_SMALLINTEGER => ' SMALLINT',
|
||||
TableSchema::TYPE_INTEGER => ' INTEGER',
|
||||
TableSchema::TYPE_BIGINTEGER => ' BIGINT',
|
||||
TableSchema::TYPE_BINARY_UUID => ' BINARY(16)',
|
||||
TableSchema::TYPE_BOOLEAN => ' BOOLEAN',
|
||||
TableSchema::TYPE_FLOAT => ' FLOAT',
|
||||
TableSchema::TYPE_DECIMAL => ' DECIMAL',
|
||||
TableSchema::TYPE_DATE => ' DATE',
|
||||
TableSchema::TYPE_TIME => ' TIME',
|
||||
TableSchema::TYPE_DATETIME => ' DATETIME',
|
||||
TableSchema::TYPE_DATETIME_FRACTIONAL => ' DATETIME',
|
||||
TableSchema::TYPE_TIMESTAMP => ' TIMESTAMP',
|
||||
TableSchema::TYPE_TIMESTAMP_FRACTIONAL => ' TIMESTAMP',
|
||||
TableSchema::TYPE_TIMESTAMP_TIMEZONE => ' TIMESTAMP',
|
||||
TableSchema::TYPE_CHAR => ' CHAR',
|
||||
TableSchema::TYPE_UUID => ' CHAR(36)',
|
||||
TableSchema::TYPE_JSON => $nativeJson ? ' JSON' : ' LONGTEXT',
|
||||
];
|
||||
$specialMap = [
|
||||
'string' => true,
|
||||
'text' => true,
|
||||
'char' => true,
|
||||
'binary' => true,
|
||||
];
|
||||
if (isset($typeMap[$data['type']])) {
|
||||
$out .= $typeMap[$data['type']];
|
||||
}
|
||||
if (isset($specialMap[$data['type']])) {
|
||||
switch ($data['type']) {
|
||||
case TableSchema::TYPE_STRING:
|
||||
$out .= ' VARCHAR';
|
||||
if (!isset($data['length'])) {
|
||||
$data['length'] = 255;
|
||||
}
|
||||
break;
|
||||
case TableSchema::TYPE_TEXT:
|
||||
$isKnownLength = in_array($data['length'], TableSchema::$columnLengths);
|
||||
if (empty($data['length']) || !$isKnownLength) {
|
||||
$out .= ' TEXT';
|
||||
break;
|
||||
}
|
||||
|
||||
/** @var string $length */
|
||||
$length = array_search($data['length'], TableSchema::$columnLengths);
|
||||
$out .= ' ' . strtoupper($length) . 'TEXT';
|
||||
|
||||
break;
|
||||
case TableSchema::TYPE_BINARY:
|
||||
$isKnownLength = in_array($data['length'], TableSchema::$columnLengths);
|
||||
if ($isKnownLength) {
|
||||
/** @var string $length */
|
||||
$length = array_search($data['length'], TableSchema::$columnLengths);
|
||||
$out .= ' ' . strtoupper($length) . 'BLOB';
|
||||
break;
|
||||
}
|
||||
|
||||
if (empty($data['length'])) {
|
||||
$out .= ' BLOB';
|
||||
break;
|
||||
}
|
||||
|
||||
if ($data['length'] > 2) {
|
||||
$out .= ' VARBINARY(' . $data['length'] . ')';
|
||||
} else {
|
||||
$out .= ' BINARY(' . $data['length'] . ')';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
$hasLength = [
|
||||
TableSchema::TYPE_INTEGER,
|
||||
TableSchema::TYPE_CHAR,
|
||||
TableSchema::TYPE_SMALLINTEGER,
|
||||
TableSchema::TYPE_TINYINTEGER,
|
||||
TableSchema::TYPE_STRING,
|
||||
];
|
||||
if (in_array($data['type'], $hasLength, true) && isset($data['length'])) {
|
||||
$out .= '(' . $data['length'] . ')';
|
||||
}
|
||||
|
||||
$lengthAndPrecisionTypes = [TableSchema::TYPE_FLOAT, TableSchema::TYPE_DECIMAL];
|
||||
if (in_array($data['type'], $lengthAndPrecisionTypes, true) && isset($data['length'])) {
|
||||
if (isset($data['precision'])) {
|
||||
$out .= '(' . (int)$data['length'] . ',' . (int)$data['precision'] . ')';
|
||||
} else {
|
||||
$out .= '(' . (int)$data['length'] . ')';
|
||||
}
|
||||
}
|
||||
|
||||
$precisionTypes = [TableSchema::TYPE_DATETIME_FRACTIONAL, TableSchema::TYPE_TIMESTAMP_FRACTIONAL];
|
||||
if (in_array($data['type'], $precisionTypes, true) && isset($data['precision'])) {
|
||||
$out .= '(' . (int)$data['precision'] . ')';
|
||||
}
|
||||
|
||||
$hasUnsigned = [
|
||||
TableSchema::TYPE_TINYINTEGER,
|
||||
TableSchema::TYPE_SMALLINTEGER,
|
||||
TableSchema::TYPE_INTEGER,
|
||||
TableSchema::TYPE_BIGINTEGER,
|
||||
TableSchema::TYPE_FLOAT,
|
||||
TableSchema::TYPE_DECIMAL,
|
||||
];
|
||||
if (
|
||||
in_array($data['type'], $hasUnsigned, true) &&
|
||||
isset($data['unsigned']) &&
|
||||
$data['unsigned'] === true
|
||||
) {
|
||||
$out .= ' UNSIGNED';
|
||||
}
|
||||
|
||||
$hasCollate = [
|
||||
TableSchema::TYPE_TEXT,
|
||||
TableSchema::TYPE_CHAR,
|
||||
TableSchema::TYPE_STRING,
|
||||
];
|
||||
if (in_array($data['type'], $hasCollate, true) && isset($data['collate']) && $data['collate'] !== '') {
|
||||
$out .= ' COLLATE ' . $data['collate'];
|
||||
}
|
||||
|
||||
if (isset($data['null']) && $data['null'] === false) {
|
||||
$out .= ' NOT NULL';
|
||||
}
|
||||
$addAutoIncrement = (
|
||||
$schema->getPrimaryKey() === [$name] &&
|
||||
!$schema->hasAutoincrement() &&
|
||||
!isset($data['autoIncrement'])
|
||||
);
|
||||
if (
|
||||
in_array($data['type'], [TableSchema::TYPE_INTEGER, TableSchema::TYPE_BIGINTEGER]) &&
|
||||
(
|
||||
$data['autoIncrement'] === true ||
|
||||
$addAutoIncrement
|
||||
)
|
||||
) {
|
||||
$out .= ' AUTO_INCREMENT';
|
||||
}
|
||||
|
||||
$timestampTypes = [
|
||||
TableSchema::TYPE_TIMESTAMP,
|
||||
TableSchema::TYPE_TIMESTAMP_FRACTIONAL,
|
||||
TableSchema::TYPE_TIMESTAMP_TIMEZONE,
|
||||
];
|
||||
if (isset($data['null']) && $data['null'] === true && in_array($data['type'], $timestampTypes, true)) {
|
||||
$out .= ' NULL';
|
||||
unset($data['default']);
|
||||
}
|
||||
|
||||
$dateTimeTypes = [
|
||||
TableSchema::TYPE_DATETIME,
|
||||
TableSchema::TYPE_DATETIME_FRACTIONAL,
|
||||
TableSchema::TYPE_TIMESTAMP,
|
||||
TableSchema::TYPE_TIMESTAMP_FRACTIONAL,
|
||||
TableSchema::TYPE_TIMESTAMP_TIMEZONE,
|
||||
];
|
||||
if (
|
||||
isset($data['default']) &&
|
||||
in_array($data['type'], $dateTimeTypes) &&
|
||||
strpos(strtolower($data['default']), 'current_timestamp') !== false
|
||||
) {
|
||||
$out .= ' DEFAULT CURRENT_TIMESTAMP';
|
||||
if (isset($data['precision'])) {
|
||||
$out .= '(' . $data['precision'] . ')';
|
||||
}
|
||||
unset($data['default']);
|
||||
}
|
||||
if (isset($data['default'])) {
|
||||
$out .= ' DEFAULT ' . $this->_driver->schemaValue($data['default']);
|
||||
unset($data['default']);
|
||||
}
|
||||
if (isset($data['comment']) && $data['comment'] !== '') {
|
||||
$out .= ' COMMENT ' . $this->_driver->schemaValue($data['comment']);
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function constraintSql(TableSchema $schema, string $name): string
|
||||
{
|
||||
/** @var array $data */
|
||||
$data = $schema->getConstraint($name);
|
||||
if ($data['type'] === TableSchema::CONSTRAINT_PRIMARY) {
|
||||
$columns = array_map(
|
||||
[$this->_driver, 'quoteIdentifier'],
|
||||
$data['columns']
|
||||
);
|
||||
|
||||
return sprintf('PRIMARY KEY (%s)', implode(', ', $columns));
|
||||
}
|
||||
|
||||
$out = '';
|
||||
if ($data['type'] === TableSchema::CONSTRAINT_UNIQUE) {
|
||||
$out = 'UNIQUE KEY ';
|
||||
}
|
||||
if ($data['type'] === TableSchema::CONSTRAINT_FOREIGN) {
|
||||
$out = 'CONSTRAINT ';
|
||||
}
|
||||
$out .= $this->_driver->quoteIdentifier($name);
|
||||
|
||||
return $this->_keySql($out, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function addConstraintSql(TableSchema $schema): array
|
||||
{
|
||||
$sqlPattern = 'ALTER TABLE %s ADD %s;';
|
||||
$sql = [];
|
||||
|
||||
foreach ($schema->constraints() as $name) {
|
||||
/** @var array $constraint */
|
||||
$constraint = $schema->getConstraint($name);
|
||||
if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) {
|
||||
$tableName = $this->_driver->quoteIdentifier($schema->name());
|
||||
$sql[] = sprintf($sqlPattern, $tableName, $this->constraintSql($schema, $name));
|
||||
}
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function dropConstraintSql(TableSchema $schema): array
|
||||
{
|
||||
$sqlPattern = 'ALTER TABLE %s DROP FOREIGN KEY %s;';
|
||||
$sql = [];
|
||||
|
||||
foreach ($schema->constraints() as $name) {
|
||||
/** @var array $constraint */
|
||||
$constraint = $schema->getConstraint($name);
|
||||
if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) {
|
||||
$tableName = $this->_driver->quoteIdentifier($schema->name());
|
||||
$constraintName = $this->_driver->quoteIdentifier($name);
|
||||
$sql[] = sprintf($sqlPattern, $tableName, $constraintName);
|
||||
}
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function indexSql(TableSchema $schema, string $name): string
|
||||
{
|
||||
/** @var array $data */
|
||||
$data = $schema->getIndex($name);
|
||||
$out = '';
|
||||
if ($data['type'] === TableSchema::INDEX_INDEX) {
|
||||
$out = 'KEY ';
|
||||
}
|
||||
if ($data['type'] === TableSchema::INDEX_FULLTEXT) {
|
||||
$out = 'FULLTEXT KEY ';
|
||||
}
|
||||
$out .= $this->_driver->quoteIdentifier($name);
|
||||
|
||||
return $this->_keySql($out, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for generating key SQL snippets.
|
||||
*
|
||||
* @param string $prefix The key prefix
|
||||
* @param array $data Key data.
|
||||
* @return string
|
||||
*/
|
||||
protected function _keySql(string $prefix, array $data): string
|
||||
{
|
||||
$columns = array_map(
|
||||
[$this->_driver, 'quoteIdentifier'],
|
||||
$data['columns']
|
||||
);
|
||||
foreach ($data['columns'] as $i => $column) {
|
||||
if (isset($data['length'][$column])) {
|
||||
$columns[$i] .= sprintf('(%d)', $data['length'][$column]);
|
||||
}
|
||||
}
|
||||
if ($data['type'] === TableSchema::CONSTRAINT_FOREIGN) {
|
||||
return $prefix . sprintf(
|
||||
' FOREIGN KEY (%s) REFERENCES %s (%s) ON UPDATE %s ON DELETE %s',
|
||||
implode(', ', $columns),
|
||||
$this->_driver->quoteIdentifier($data['references'][0]),
|
||||
$this->_convertConstraintColumns($data['references'][1]),
|
||||
$this->_foreignOnClause($data['update']),
|
||||
$this->_foreignOnClause($data['delete'])
|
||||
);
|
||||
}
|
||||
|
||||
return $prefix . ' (' . implode(', ', $columns) . ')';
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
class_alias(
|
||||
'Cake\Database\Schema\MysqlSchemaDialect',
|
||||
'Cake\Database\Schema\MysqlSchema'
|
||||
);
|
||||
// phpcs:enable
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
deprecationWarning(
|
||||
'Since 4.1.0: Cake\Database\Schema\PostgresSchema is deprecated. ' .
|
||||
'Use Cake\Database\Schema\PostgresSchemaDialect instead.'
|
||||
);
|
||||
class_exists('Cake\Database\Schema\PostgresSchemaDialect');
|
||||
@@ -0,0 +1,704 @@
|
||||
<?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\Database\Schema;
|
||||
|
||||
use Cake\Database\Exception\DatabaseException;
|
||||
|
||||
/**
|
||||
* Schema management/reflection features for Postgres.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PostgresSchemaDialect extends SchemaDialect
|
||||
{
|
||||
/**
|
||||
* Generate the SQL to list the tables and views.
|
||||
*
|
||||
* @param array<string, mixed> $config The connection configuration to use for
|
||||
* getting tables from.
|
||||
* @return array An array of (sql, params) to execute.
|
||||
*/
|
||||
public function listTablesSql(array $config): array
|
||||
{
|
||||
$sql = 'SELECT table_name as name FROM information_schema.tables
|
||||
WHERE table_schema = ? ORDER BY name';
|
||||
$schema = empty($config['schema']) ? 'public' : $config['schema'];
|
||||
|
||||
return [$sql, [$schema]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the SQL to list the tables, excluding all views.
|
||||
*
|
||||
* @param array<string, mixed> $config The connection configuration to use for
|
||||
* getting tables from.
|
||||
* @return array<mixed> An array of (sql, params) to execute.
|
||||
*/
|
||||
public function listTablesWithoutViewsSql(array $config): array
|
||||
{
|
||||
$sql = 'SELECT table_name as name FROM information_schema.tables
|
||||
WHERE table_schema = ? AND table_type = \'BASE TABLE\' ORDER BY name';
|
||||
$schema = empty($config['schema']) ? 'public' : $config['schema'];
|
||||
|
||||
return [$sql, [$schema]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeColumnSql(string $tableName, array $config): array
|
||||
{
|
||||
$sql = 'SELECT DISTINCT table_schema AS schema,
|
||||
column_name AS name,
|
||||
data_type AS type,
|
||||
is_nullable AS null, column_default AS default,
|
||||
character_maximum_length AS char_length,
|
||||
c.collation_name,
|
||||
d.description as comment,
|
||||
ordinal_position,
|
||||
c.datetime_precision,
|
||||
c.numeric_precision as column_precision,
|
||||
c.numeric_scale as column_scale,
|
||||
pg_get_serial_sequence(attr.attrelid::regclass::text, attr.attname) IS NOT NULL AS has_serial
|
||||
FROM information_schema.columns c
|
||||
INNER JOIN pg_catalog.pg_namespace ns ON (ns.nspname = table_schema)
|
||||
INNER JOIN pg_catalog.pg_class cl ON (cl.relnamespace = ns.oid AND cl.relname = table_name)
|
||||
LEFT JOIN pg_catalog.pg_index i ON (i.indrelid = cl.oid AND i.indkey[0] = c.ordinal_position)
|
||||
LEFT JOIN pg_catalog.pg_description d on (cl.oid = d.objoid AND d.objsubid = c.ordinal_position)
|
||||
LEFT JOIN pg_catalog.pg_attribute attr ON (cl.oid = attr.attrelid AND column_name = attr.attname)
|
||||
WHERE table_name = ? AND table_schema = ? AND table_catalog = ?
|
||||
ORDER BY ordinal_position';
|
||||
|
||||
$schema = empty($config['schema']) ? 'public' : $config['schema'];
|
||||
|
||||
return [$sql, [$tableName, $schema, $config['database']]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a column definition to the abstract types.
|
||||
*
|
||||
* The returned type will be a type that
|
||||
* Cake\Database\TypeFactory can handle.
|
||||
*
|
||||
* @param string $column The column type + length
|
||||
* @throws \Cake\Database\Exception\DatabaseException when column cannot be parsed.
|
||||
* @return array<string, mixed> Array of column information.
|
||||
*/
|
||||
protected function _convertColumn(string $column): array
|
||||
{
|
||||
preg_match('/([a-z\s]+)(?:\(([0-9,]+)\))?/i', $column, $matches);
|
||||
if (empty($matches)) {
|
||||
throw new DatabaseException(sprintf('Unable to parse column type from "%s"', $column));
|
||||
}
|
||||
|
||||
$col = strtolower($matches[1]);
|
||||
$length = $precision = $scale = null;
|
||||
if (isset($matches[2])) {
|
||||
$length = (int)$matches[2];
|
||||
}
|
||||
|
||||
$type = $this->_applyTypeSpecificColumnConversion(
|
||||
$col,
|
||||
compact('length', 'precision', 'scale')
|
||||
);
|
||||
if ($type !== null) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
if (in_array($col, ['date', 'time', 'boolean'], true)) {
|
||||
return ['type' => $col, 'length' => null];
|
||||
}
|
||||
if (in_array($col, ['timestamptz', 'timestamp with time zone'], true)) {
|
||||
return ['type' => TableSchema::TYPE_TIMESTAMP_TIMEZONE, 'length' => null];
|
||||
}
|
||||
if (strpos($col, 'timestamp') !== false) {
|
||||
return ['type' => TableSchema::TYPE_TIMESTAMP_FRACTIONAL, 'length' => null];
|
||||
}
|
||||
if (strpos($col, 'time') !== false) {
|
||||
return ['type' => TableSchema::TYPE_TIME, 'length' => null];
|
||||
}
|
||||
if ($col === 'serial' || $col === 'integer') {
|
||||
return ['type' => TableSchema::TYPE_INTEGER, 'length' => 10];
|
||||
}
|
||||
if ($col === 'bigserial' || $col === 'bigint') {
|
||||
return ['type' => TableSchema::TYPE_BIGINTEGER, 'length' => 20];
|
||||
}
|
||||
if ($col === 'smallint') {
|
||||
return ['type' => TableSchema::TYPE_SMALLINTEGER, 'length' => 5];
|
||||
}
|
||||
if ($col === 'inet') {
|
||||
return ['type' => TableSchema::TYPE_STRING, 'length' => 39];
|
||||
}
|
||||
if ($col === 'uuid') {
|
||||
return ['type' => TableSchema::TYPE_UUID, 'length' => null];
|
||||
}
|
||||
if ($col === 'char') {
|
||||
return ['type' => TableSchema::TYPE_CHAR, 'length' => $length];
|
||||
}
|
||||
if (strpos($col, 'character') !== false) {
|
||||
return ['type' => TableSchema::TYPE_STRING, 'length' => $length];
|
||||
}
|
||||
// money is 'string' as it includes arbitrary text content
|
||||
// before the number value.
|
||||
if (strpos($col, 'money') !== false || $col === 'string') {
|
||||
return ['type' => TableSchema::TYPE_STRING, 'length' => $length];
|
||||
}
|
||||
if (strpos($col, 'text') !== false) {
|
||||
return ['type' => TableSchema::TYPE_TEXT, 'length' => null];
|
||||
}
|
||||
if ($col === 'bytea') {
|
||||
return ['type' => TableSchema::TYPE_BINARY, 'length' => null];
|
||||
}
|
||||
if ($col === 'real' || strpos($col, 'double') !== false) {
|
||||
return ['type' => TableSchema::TYPE_FLOAT, 'length' => null];
|
||||
}
|
||||
if (
|
||||
strpos($col, 'numeric') !== false ||
|
||||
strpos($col, 'decimal') !== false
|
||||
) {
|
||||
return ['type' => TableSchema::TYPE_DECIMAL, 'length' => null];
|
||||
}
|
||||
|
||||
if (strpos($col, 'json') !== false) {
|
||||
return ['type' => TableSchema::TYPE_JSON, 'length' => null];
|
||||
}
|
||||
|
||||
$length = is_numeric($length) ? $length : null;
|
||||
|
||||
return ['type' => TableSchema::TYPE_STRING, 'length' => $length];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertColumnDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$field = $this->_convertColumn($row['type']);
|
||||
|
||||
if ($field['type'] === TableSchema::TYPE_BOOLEAN) {
|
||||
if ($row['default'] === 'true') {
|
||||
$row['default'] = 1;
|
||||
}
|
||||
if ($row['default'] === 'false') {
|
||||
$row['default'] = 0;
|
||||
}
|
||||
}
|
||||
if (!empty($row['has_serial'])) {
|
||||
$field['autoIncrement'] = true;
|
||||
}
|
||||
|
||||
$field += [
|
||||
'default' => $this->_defaultValue($row['default']),
|
||||
'null' => $row['null'] === 'YES',
|
||||
'collate' => $row['collation_name'],
|
||||
'comment' => $row['comment'],
|
||||
];
|
||||
$field['length'] = $row['char_length'] ?: $field['length'];
|
||||
|
||||
if ($field['type'] === 'numeric' || $field['type'] === 'decimal') {
|
||||
$field['length'] = $row['column_precision'];
|
||||
$field['precision'] = $row['column_scale'] ?: null;
|
||||
}
|
||||
|
||||
if ($field['type'] === TableSchema::TYPE_TIMESTAMP_FRACTIONAL) {
|
||||
$field['precision'] = $row['datetime_precision'];
|
||||
if ($field['precision'] === 0) {
|
||||
$field['type'] = TableSchema::TYPE_TIMESTAMP;
|
||||
}
|
||||
}
|
||||
|
||||
if ($field['type'] === TableSchema::TYPE_TIMESTAMP_TIMEZONE) {
|
||||
$field['precision'] = $row['datetime_precision'];
|
||||
}
|
||||
|
||||
$schema->addColumn($row['name'], $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manipulate the default value.
|
||||
*
|
||||
* Postgres includes sequence data and casting information in default values.
|
||||
* We need to remove those.
|
||||
*
|
||||
* @param string|int|null $default The default value.
|
||||
* @return string|int|null
|
||||
*/
|
||||
protected function _defaultValue($default)
|
||||
{
|
||||
if (is_numeric($default) || $default === null) {
|
||||
return $default;
|
||||
}
|
||||
// Sequences
|
||||
if (strpos($default, 'nextval') === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (strpos($default, 'NULL::') === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove quotes and postgres casts
|
||||
return preg_replace(
|
||||
"/^'(.*)'(?:::.*)$/",
|
||||
'$1',
|
||||
$default
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeIndexSql(string $tableName, array $config): array
|
||||
{
|
||||
$sql = 'SELECT
|
||||
c2.relname,
|
||||
a.attname,
|
||||
i.indisprimary,
|
||||
i.indisunique
|
||||
FROM pg_catalog.pg_namespace n
|
||||
INNER JOIN pg_catalog.pg_class c ON (n.oid = c.relnamespace)
|
||||
INNER JOIN pg_catalog.pg_index i ON (c.oid = i.indrelid)
|
||||
INNER JOIN pg_catalog.pg_class c2 ON (c2.oid = i.indexrelid)
|
||||
INNER JOIN pg_catalog.pg_attribute a ON (a.attrelid = c.oid AND i.indrelid::regclass = a.attrelid::regclass)
|
||||
WHERE n.nspname = ?
|
||||
AND a.attnum = ANY(i.indkey)
|
||||
AND c.relname = ?
|
||||
ORDER BY i.indisprimary DESC, i.indisunique DESC, c.relname, a.attnum';
|
||||
|
||||
$schema = 'public';
|
||||
if (!empty($config['schema'])) {
|
||||
$schema = $config['schema'];
|
||||
}
|
||||
|
||||
return [$sql, [$schema, $tableName]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertIndexDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$type = TableSchema::INDEX_INDEX;
|
||||
$name = $row['relname'];
|
||||
if ($row['indisprimary']) {
|
||||
$name = $type = TableSchema::CONSTRAINT_PRIMARY;
|
||||
}
|
||||
if ($row['indisunique'] && $type === TableSchema::INDEX_INDEX) {
|
||||
$type = TableSchema::CONSTRAINT_UNIQUE;
|
||||
}
|
||||
if ($type === TableSchema::CONSTRAINT_PRIMARY || $type === TableSchema::CONSTRAINT_UNIQUE) {
|
||||
$this->_convertConstraint($schema, $name, $type, $row);
|
||||
|
||||
return;
|
||||
}
|
||||
$index = $schema->getIndex($name);
|
||||
if (!$index) {
|
||||
$index = [
|
||||
'type' => $type,
|
||||
'columns' => [],
|
||||
];
|
||||
}
|
||||
$index['columns'][] = $row['attname'];
|
||||
$schema->addIndex($name, $index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/update a constraint into the schema object.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table to update.
|
||||
* @param string $name The index name.
|
||||
* @param string $type The index type.
|
||||
* @param array $row The metadata record to update with.
|
||||
* @return void
|
||||
*/
|
||||
protected function _convertConstraint(TableSchema $schema, string $name, string $type, array $row): void
|
||||
{
|
||||
$constraint = $schema->getConstraint($name);
|
||||
if (!$constraint) {
|
||||
$constraint = [
|
||||
'type' => $type,
|
||||
'columns' => [],
|
||||
];
|
||||
}
|
||||
$constraint['columns'][] = $row['attname'];
|
||||
$schema->addConstraint($name, $constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeForeignKeySql(string $tableName, array $config): array
|
||||
{
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
$sql = 'SELECT
|
||||
c.conname AS name,
|
||||
c.contype AS type,
|
||||
a.attname AS column_name,
|
||||
c.confmatchtype AS match_type,
|
||||
c.confupdtype AS on_update,
|
||||
c.confdeltype AS on_delete,
|
||||
c.confrelid::regclass AS references_table,
|
||||
ab.attname AS references_field
|
||||
FROM pg_catalog.pg_namespace n
|
||||
INNER JOIN pg_catalog.pg_class cl ON (n.oid = cl.relnamespace)
|
||||
INNER JOIN pg_catalog.pg_constraint c ON (n.oid = c.connamespace)
|
||||
INNER JOIN pg_catalog.pg_attribute a ON (a.attrelid = cl.oid AND c.conrelid = a.attrelid AND a.attnum = ANY(c.conkey))
|
||||
INNER JOIN pg_catalog.pg_attribute ab ON (a.attrelid = cl.oid AND c.confrelid = ab.attrelid AND ab.attnum = ANY(c.confkey))
|
||||
WHERE n.nspname = ?
|
||||
AND cl.relname = ?
|
||||
ORDER BY name, a.attnum, ab.attnum DESC';
|
||||
// phpcs:enable Generic.Files.LineLength
|
||||
|
||||
$schema = empty($config['schema']) ? 'public' : $config['schema'];
|
||||
|
||||
return [$sql, [$schema, $tableName]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertForeignKeyDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$data = [
|
||||
'type' => TableSchema::CONSTRAINT_FOREIGN,
|
||||
'columns' => $row['column_name'],
|
||||
'references' => [$row['references_table'], $row['references_field']],
|
||||
'update' => $this->_convertOnClause($row['on_update']),
|
||||
'delete' => $this->_convertOnClause($row['on_delete']),
|
||||
];
|
||||
$schema->addConstraint($row['name'], $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function _convertOnClause(string $clause): string
|
||||
{
|
||||
if ($clause === 'r') {
|
||||
return TableSchema::ACTION_RESTRICT;
|
||||
}
|
||||
if ($clause === 'a') {
|
||||
return TableSchema::ACTION_NO_ACTION;
|
||||
}
|
||||
if ($clause === 'c') {
|
||||
return TableSchema::ACTION_CASCADE;
|
||||
}
|
||||
|
||||
return TableSchema::ACTION_SET_NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function columnSql(TableSchema $schema, string $name): string
|
||||
{
|
||||
/** @var array $data */
|
||||
$data = $schema->getColumn($name);
|
||||
|
||||
$sql = $this->_getTypeSpecificColumnSql($data['type'], $schema, $name);
|
||||
if ($sql !== null) {
|
||||
return $sql;
|
||||
}
|
||||
|
||||
$out = $this->_driver->quoteIdentifier($name);
|
||||
$typeMap = [
|
||||
TableSchema::TYPE_TINYINTEGER => ' SMALLINT',
|
||||
TableSchema::TYPE_SMALLINTEGER => ' SMALLINT',
|
||||
TableSchema::TYPE_BINARY_UUID => ' UUID',
|
||||
TableSchema::TYPE_BOOLEAN => ' BOOLEAN',
|
||||
TableSchema::TYPE_FLOAT => ' FLOAT',
|
||||
TableSchema::TYPE_DECIMAL => ' DECIMAL',
|
||||
TableSchema::TYPE_DATE => ' DATE',
|
||||
TableSchema::TYPE_TIME => ' TIME',
|
||||
TableSchema::TYPE_DATETIME => ' TIMESTAMP',
|
||||
TableSchema::TYPE_DATETIME_FRACTIONAL => ' TIMESTAMP',
|
||||
TableSchema::TYPE_TIMESTAMP => ' TIMESTAMP',
|
||||
TableSchema::TYPE_TIMESTAMP_FRACTIONAL => ' TIMESTAMP',
|
||||
TableSchema::TYPE_TIMESTAMP_TIMEZONE => ' TIMESTAMPTZ',
|
||||
TableSchema::TYPE_UUID => ' UUID',
|
||||
TableSchema::TYPE_CHAR => ' CHAR',
|
||||
TableSchema::TYPE_JSON => ' JSONB',
|
||||
];
|
||||
|
||||
if (isset($typeMap[$data['type']])) {
|
||||
$out .= $typeMap[$data['type']];
|
||||
}
|
||||
|
||||
if ($data['type'] === TableSchema::TYPE_INTEGER || $data['type'] === TableSchema::TYPE_BIGINTEGER) {
|
||||
$type = $data['type'] === TableSchema::TYPE_INTEGER ? ' INTEGER' : ' BIGINT';
|
||||
if ($schema->getPrimaryKey() === [$name] || $data['autoIncrement'] === true) {
|
||||
$type = $data['type'] === TableSchema::TYPE_INTEGER ? ' SERIAL' : ' BIGSERIAL';
|
||||
unset($data['null'], $data['default']);
|
||||
}
|
||||
$out .= $type;
|
||||
}
|
||||
|
||||
if ($data['type'] === TableSchema::TYPE_TEXT && $data['length'] !== TableSchema::LENGTH_TINY) {
|
||||
$out .= ' TEXT';
|
||||
}
|
||||
if ($data['type'] === TableSchema::TYPE_BINARY) {
|
||||
$out .= ' BYTEA';
|
||||
}
|
||||
|
||||
if ($data['type'] === TableSchema::TYPE_CHAR) {
|
||||
$out .= '(' . $data['length'] . ')';
|
||||
}
|
||||
|
||||
if (
|
||||
$data['type'] === TableSchema::TYPE_STRING ||
|
||||
(
|
||||
$data['type'] === TableSchema::TYPE_TEXT &&
|
||||
$data['length'] === TableSchema::LENGTH_TINY
|
||||
)
|
||||
) {
|
||||
$out .= ' VARCHAR';
|
||||
if (isset($data['length']) && $data['length'] !== '') {
|
||||
$out .= '(' . $data['length'] . ')';
|
||||
}
|
||||
}
|
||||
|
||||
$hasCollate = [TableSchema::TYPE_TEXT, TableSchema::TYPE_STRING, TableSchema::TYPE_CHAR];
|
||||
if (in_array($data['type'], $hasCollate, true) && isset($data['collate']) && $data['collate'] !== '') {
|
||||
$out .= ' COLLATE "' . $data['collate'] . '"';
|
||||
}
|
||||
|
||||
$hasPrecision = [
|
||||
TableSchema::TYPE_FLOAT,
|
||||
TableSchema::TYPE_DATETIME,
|
||||
TableSchema::TYPE_DATETIME_FRACTIONAL,
|
||||
TableSchema::TYPE_TIMESTAMP,
|
||||
TableSchema::TYPE_TIMESTAMP_FRACTIONAL,
|
||||
TableSchema::TYPE_TIMESTAMP_TIMEZONE,
|
||||
];
|
||||
if (in_array($data['type'], $hasPrecision) && isset($data['precision'])) {
|
||||
$out .= '(' . $data['precision'] . ')';
|
||||
}
|
||||
|
||||
if (
|
||||
$data['type'] === TableSchema::TYPE_DECIMAL &&
|
||||
(
|
||||
isset($data['length']) ||
|
||||
isset($data['precision'])
|
||||
)
|
||||
) {
|
||||
$out .= '(' . $data['length'] . ',' . (int)$data['precision'] . ')';
|
||||
}
|
||||
|
||||
if (isset($data['null']) && $data['null'] === false) {
|
||||
$out .= ' NOT NULL';
|
||||
}
|
||||
|
||||
$datetimeTypes = [
|
||||
TableSchema::TYPE_DATETIME,
|
||||
TableSchema::TYPE_DATETIME_FRACTIONAL,
|
||||
TableSchema::TYPE_TIMESTAMP,
|
||||
TableSchema::TYPE_TIMESTAMP_FRACTIONAL,
|
||||
TableSchema::TYPE_TIMESTAMP_TIMEZONE,
|
||||
];
|
||||
if (
|
||||
isset($data['default']) &&
|
||||
in_array($data['type'], $datetimeTypes) &&
|
||||
strtolower($data['default']) === 'current_timestamp'
|
||||
) {
|
||||
$out .= ' DEFAULT CURRENT_TIMESTAMP';
|
||||
} elseif (isset($data['default'])) {
|
||||
$defaultValue = $data['default'];
|
||||
if ($data['type'] === 'boolean') {
|
||||
$defaultValue = (bool)$defaultValue;
|
||||
}
|
||||
$out .= ' DEFAULT ' . $this->_driver->schemaValue($defaultValue);
|
||||
} elseif (isset($data['null']) && $data['null'] !== false) {
|
||||
$out .= ' DEFAULT NULL';
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function addConstraintSql(TableSchema $schema): array
|
||||
{
|
||||
$sqlPattern = 'ALTER TABLE %s ADD %s;';
|
||||
$sql = [];
|
||||
|
||||
foreach ($schema->constraints() as $name) {
|
||||
/** @var array $constraint */
|
||||
$constraint = $schema->getConstraint($name);
|
||||
if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) {
|
||||
$tableName = $this->_driver->quoteIdentifier($schema->name());
|
||||
$sql[] = sprintf($sqlPattern, $tableName, $this->constraintSql($schema, $name));
|
||||
}
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function dropConstraintSql(TableSchema $schema): array
|
||||
{
|
||||
$sqlPattern = 'ALTER TABLE %s DROP CONSTRAINT %s;';
|
||||
$sql = [];
|
||||
|
||||
foreach ($schema->constraints() as $name) {
|
||||
/** @var array $constraint */
|
||||
$constraint = $schema->getConstraint($name);
|
||||
if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) {
|
||||
$tableName = $this->_driver->quoteIdentifier($schema->name());
|
||||
$constraintName = $this->_driver->quoteIdentifier($name);
|
||||
$sql[] = sprintf($sqlPattern, $tableName, $constraintName);
|
||||
}
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function indexSql(TableSchema $schema, string $name): string
|
||||
{
|
||||
/** @var array $data */
|
||||
$data = $schema->getIndex($name);
|
||||
$columns = array_map(
|
||||
[$this->_driver, 'quoteIdentifier'],
|
||||
$data['columns']
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
'CREATE INDEX %s ON %s (%s)',
|
||||
$this->_driver->quoteIdentifier($name),
|
||||
$this->_driver->quoteIdentifier($schema->name()),
|
||||
implode(', ', $columns)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function constraintSql(TableSchema $schema, string $name): string
|
||||
{
|
||||
/** @var array<string, mixed> $data */
|
||||
$data = $schema->getConstraint($name);
|
||||
$out = 'CONSTRAINT ' . $this->_driver->quoteIdentifier($name);
|
||||
if ($data['type'] === TableSchema::CONSTRAINT_PRIMARY) {
|
||||
$out = 'PRIMARY KEY';
|
||||
}
|
||||
if ($data['type'] === TableSchema::CONSTRAINT_UNIQUE) {
|
||||
$out .= ' UNIQUE';
|
||||
}
|
||||
|
||||
return $this->_keySql($out, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for generating key SQL snippets.
|
||||
*
|
||||
* @param string $prefix The key prefix
|
||||
* @param array<string, mixed> $data Key data.
|
||||
* @return string
|
||||
*/
|
||||
protected function _keySql(string $prefix, array $data): string
|
||||
{
|
||||
$columns = array_map(
|
||||
[$this->_driver, 'quoteIdentifier'],
|
||||
$data['columns']
|
||||
);
|
||||
if ($data['type'] === TableSchema::CONSTRAINT_FOREIGN) {
|
||||
return $prefix . sprintf(
|
||||
' FOREIGN KEY (%s) REFERENCES %s (%s) ON UPDATE %s ON DELETE %s DEFERRABLE INITIALLY IMMEDIATE',
|
||||
implode(', ', $columns),
|
||||
$this->_driver->quoteIdentifier($data['references'][0]),
|
||||
$this->_convertConstraintColumns($data['references'][1]),
|
||||
$this->_foreignOnClause($data['update']),
|
||||
$this->_foreignOnClause($data['delete'])
|
||||
);
|
||||
}
|
||||
|
||||
return $prefix . ' (' . implode(', ', $columns) . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function createTableSql(TableSchema $schema, array $columns, array $constraints, array $indexes): array
|
||||
{
|
||||
$content = array_merge($columns, $constraints);
|
||||
$content = implode(",\n", array_filter($content));
|
||||
$tableName = $this->_driver->quoteIdentifier($schema->name());
|
||||
$dbSchema = $this->_driver->schema();
|
||||
if ($dbSchema != 'public') {
|
||||
$tableName = $this->_driver->quoteIdentifier($dbSchema) . '.' . $tableName;
|
||||
}
|
||||
$temporary = $schema->isTemporary() ? ' TEMPORARY ' : ' ';
|
||||
$out = [];
|
||||
$out[] = sprintf("CREATE%sTABLE %s (\n%s\n)", $temporary, $tableName, $content);
|
||||
foreach ($indexes as $index) {
|
||||
$out[] = $index;
|
||||
}
|
||||
foreach ($schema->columns() as $column) {
|
||||
$columnData = $schema->getColumn($column);
|
||||
if (isset($columnData['comment'])) {
|
||||
$out[] = sprintf(
|
||||
'COMMENT ON COLUMN %s.%s IS %s',
|
||||
$tableName,
|
||||
$this->_driver->quoteIdentifier($column),
|
||||
$this->_driver->schemaValue($columnData['comment'])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function truncateTableSql(TableSchema $schema): array
|
||||
{
|
||||
$name = $this->_driver->quoteIdentifier($schema->name());
|
||||
|
||||
return [
|
||||
sprintf('TRUNCATE %s RESTART IDENTITY CASCADE', $name),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the SQL to drop a table.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema Table instance
|
||||
* @return array SQL statements to drop a table.
|
||||
*/
|
||||
public function dropTableSql(TableSchema $schema): array
|
||||
{
|
||||
$sql = sprintf(
|
||||
'DROP TABLE %s CASCADE',
|
||||
$this->_driver->quoteIdentifier($schema->name())
|
||||
);
|
||||
|
||||
return [$sql];
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
class_alias(
|
||||
'Cake\Database\Schema\PostgresSchemaDialect',
|
||||
'Cake\Database\Schema\PostgresSchema'
|
||||
);
|
||||
// phpcs:enable
|
||||
+346
@@ -0,0 +1,346 @@
|
||||
<?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\Database\Schema;
|
||||
|
||||
use Cake\Database\DriverInterface;
|
||||
use Cake\Database\Type\ColumnSchemaAwareInterface;
|
||||
use Cake\Database\TypeFactory;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Base class for schema implementations.
|
||||
*
|
||||
* This class contains methods that are common across
|
||||
* the various SQL dialects.
|
||||
*
|
||||
* @method array<mixed> listTablesWithoutViewsSql(array $config) Generate the SQL to list the tables, excluding all views.
|
||||
*/
|
||||
abstract class SchemaDialect
|
||||
{
|
||||
/**
|
||||
* The driver instance being used.
|
||||
*
|
||||
* @var \Cake\Database\DriverInterface
|
||||
*/
|
||||
protected $_driver;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* This constructor will connect the driver so that methods like columnSql() and others
|
||||
* will fail when the driver has not been connected.
|
||||
*
|
||||
* @param \Cake\Database\DriverInterface $driver The driver to use.
|
||||
*/
|
||||
public function __construct(DriverInterface $driver)
|
||||
{
|
||||
$driver->connect();
|
||||
$this->_driver = $driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an ON clause for a foreign key.
|
||||
*
|
||||
* @param string $on The on clause
|
||||
* @return string
|
||||
*/
|
||||
protected function _foreignOnClause(string $on): string
|
||||
{
|
||||
if ($on === TableSchema::ACTION_SET_NULL) {
|
||||
return 'SET NULL';
|
||||
}
|
||||
if ($on === TableSchema::ACTION_SET_DEFAULT) {
|
||||
return 'SET DEFAULT';
|
||||
}
|
||||
if ($on === TableSchema::ACTION_CASCADE) {
|
||||
return 'CASCADE';
|
||||
}
|
||||
if ($on === TableSchema::ACTION_RESTRICT) {
|
||||
return 'RESTRICT';
|
||||
}
|
||||
if ($on === TableSchema::ACTION_NO_ACTION) {
|
||||
return 'NO ACTION';
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Invalid value for "on": ' . $on);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert string on clauses to the abstract ones.
|
||||
*
|
||||
* @param string $clause The on clause to convert.
|
||||
* @return string
|
||||
*/
|
||||
protected function _convertOnClause(string $clause): string
|
||||
{
|
||||
if ($clause === 'CASCADE' || $clause === 'RESTRICT') {
|
||||
return strtolower($clause);
|
||||
}
|
||||
if ($clause === 'NO ACTION') {
|
||||
return TableSchema::ACTION_NO_ACTION;
|
||||
}
|
||||
|
||||
return TableSchema::ACTION_SET_NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert foreign key constraints references to a valid
|
||||
* stringified list
|
||||
*
|
||||
* @param array<string>|string $references The referenced columns of a foreign key constraint statement
|
||||
* @return string
|
||||
*/
|
||||
protected function _convertConstraintColumns($references): string
|
||||
{
|
||||
if (is_string($references)) {
|
||||
return $this->_driver->quoteIdentifier($references);
|
||||
}
|
||||
|
||||
return implode(', ', array_map(
|
||||
[$this->_driver, 'quoteIdentifier'],
|
||||
$references
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to use a matching database type to generate the SQL
|
||||
* fragment for a single column in a table.
|
||||
*
|
||||
* @param string $columnType The column type.
|
||||
* @param \Cake\Database\Schema\TableSchemaInterface $schema The table schema instance the column is in.
|
||||
* @param string $column The name of the column.
|
||||
* @return string|null An SQL fragment, or `null` in case no corresponding type was found or the type didn't provide
|
||||
* custom column SQL.
|
||||
*/
|
||||
protected function _getTypeSpecificColumnSql(
|
||||
string $columnType,
|
||||
TableSchemaInterface $schema,
|
||||
string $column
|
||||
): ?string {
|
||||
if (!TypeFactory::getMap($columnType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = TypeFactory::build($columnType);
|
||||
if (!($type instanceof ColumnSchemaAwareInterface)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $type->getColumnSql($schema, $column, $this->_driver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to use a matching database type to convert a SQL column
|
||||
* definition to an abstract type definition.
|
||||
*
|
||||
* @param string $columnType The column type.
|
||||
* @param array $definition The column definition.
|
||||
* @return array|null Array of column information, or `null` in case no corresponding type was found or the type
|
||||
* didn't provide custom column information.
|
||||
*/
|
||||
protected function _applyTypeSpecificColumnConversion(string $columnType, array $definition): ?array
|
||||
{
|
||||
if (!TypeFactory::getMap($columnType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = TypeFactory::build($columnType);
|
||||
if (!($type instanceof ColumnSchemaAwareInterface)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $type->convertColumnDefinition($definition, $this->_driver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the SQL to drop a table.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema Schema instance
|
||||
* @return array SQL statements to drop a table.
|
||||
*/
|
||||
public function dropTableSql(TableSchema $schema): array
|
||||
{
|
||||
$sql = sprintf(
|
||||
'DROP TABLE %s',
|
||||
$this->_driver->quoteIdentifier($schema->name())
|
||||
);
|
||||
|
||||
return [$sql];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the SQL to list the tables.
|
||||
*
|
||||
* @param array<string, mixed> $config The connection configuration to use for
|
||||
* getting tables from.
|
||||
* @return array An array of (sql, params) to execute.
|
||||
*/
|
||||
abstract public function listTablesSql(array $config): array;
|
||||
|
||||
/**
|
||||
* Generate the SQL to describe a table.
|
||||
*
|
||||
* @param string $tableName The table name to get information on.
|
||||
* @param array<string, mixed> $config The connection configuration.
|
||||
* @return array An array of (sql, params) to execute.
|
||||
*/
|
||||
abstract public function describeColumnSql(string $tableName, array $config): array;
|
||||
|
||||
/**
|
||||
* Generate the SQL to describe the indexes in a table.
|
||||
*
|
||||
* @param string $tableName The table name to get information on.
|
||||
* @param array<string, mixed> $config The connection configuration.
|
||||
* @return array An array of (sql, params) to execute.
|
||||
*/
|
||||
abstract public function describeIndexSql(string $tableName, array $config): array;
|
||||
|
||||
/**
|
||||
* Generate the SQL to describe the foreign keys in a table.
|
||||
*
|
||||
* @param string $tableName The table name to get information on.
|
||||
* @param array<string, mixed> $config The connection configuration.
|
||||
* @return array An array of (sql, params) to execute.
|
||||
*/
|
||||
abstract public function describeForeignKeySql(string $tableName, array $config): array;
|
||||
|
||||
/**
|
||||
* Generate the SQL to describe table options
|
||||
*
|
||||
* @param string $tableName Table name.
|
||||
* @param array<string, mixed> $config The connection configuration.
|
||||
* @return array SQL statements to get options for a table.
|
||||
*/
|
||||
public function describeOptionsSql(string $tableName, array $config): array
|
||||
{
|
||||
return ['', ''];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert field description results into abstract schema fields.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table object to append fields to.
|
||||
* @param array $row The row data from `describeColumnSql`.
|
||||
* @return void
|
||||
*/
|
||||
abstract public function convertColumnDescription(TableSchema $schema, array $row): void;
|
||||
|
||||
/**
|
||||
* Convert an index description results into abstract schema indexes or constraints.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table object to append
|
||||
* an index or constraint to.
|
||||
* @param array $row The row data from `describeIndexSql`.
|
||||
* @return void
|
||||
*/
|
||||
abstract public function convertIndexDescription(TableSchema $schema, array $row): void;
|
||||
|
||||
/**
|
||||
* Convert a foreign key description into constraints on the Table object.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table object to append
|
||||
* a constraint to.
|
||||
* @param array $row The row data from `describeForeignKeySql`.
|
||||
* @return void
|
||||
*/
|
||||
abstract public function convertForeignKeyDescription(TableSchema $schema, array $row): void;
|
||||
|
||||
/**
|
||||
* Convert options data into table options.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema Table instance.
|
||||
* @param array $row The row of data.
|
||||
* @return void
|
||||
*/
|
||||
public function convertOptionsDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the SQL to create a table.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema Table instance.
|
||||
* @param array<string> $columns The columns to go inside the table.
|
||||
* @param array<string> $constraints The constraints for the table.
|
||||
* @param array<string> $indexes The indexes for the table.
|
||||
* @return array<string> SQL statements to create a table.
|
||||
*/
|
||||
abstract public function createTableSql(
|
||||
TableSchema $schema,
|
||||
array $columns,
|
||||
array $constraints,
|
||||
array $indexes
|
||||
): array;
|
||||
|
||||
/**
|
||||
* Generate the SQL fragment for a single column in a table.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table instance the column is in.
|
||||
* @param string $name The name of the column.
|
||||
* @return string SQL fragment.
|
||||
*/
|
||||
abstract public function columnSql(TableSchema $schema, string $name): string;
|
||||
|
||||
/**
|
||||
* Generate the SQL queries needed to add foreign key constraints to the table
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table instance the foreign key constraints are.
|
||||
* @return array SQL fragment.
|
||||
*/
|
||||
abstract public function addConstraintSql(TableSchema $schema): array;
|
||||
|
||||
/**
|
||||
* Generate the SQL queries needed to drop foreign key constraints from the table
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table instance the foreign key constraints are.
|
||||
* @return array SQL fragment.
|
||||
*/
|
||||
abstract public function dropConstraintSql(TableSchema $schema): array;
|
||||
|
||||
/**
|
||||
* Generate the SQL fragments for defining table constraints.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table instance the column is in.
|
||||
* @param string $name The name of the column.
|
||||
* @return string SQL fragment.
|
||||
*/
|
||||
abstract public function constraintSql(TableSchema $schema, string $name): string;
|
||||
|
||||
/**
|
||||
* Generate the SQL fragment for a single index in a table.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table object the column is in.
|
||||
* @param string $name The name of the column.
|
||||
* @return string SQL fragment.
|
||||
*/
|
||||
abstract public function indexSql(TableSchema $schema, string $name): string;
|
||||
|
||||
/**
|
||||
* Generate the SQL to truncate a table.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema Table instance.
|
||||
* @return array SQL statements to truncate a table.
|
||||
*/
|
||||
abstract public function truncateTableSql(TableSchema $schema): array;
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
class_alias(
|
||||
'Cake\Database\Schema\SchemaDialect',
|
||||
'Cake\Database\Schema\BaseSchema'
|
||||
);
|
||||
// phpcs:enable
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user