FILES_CI4_SYSTEM_ALL_FILES_PUSH : RV

This commit is contained in:
VENKATESHWARAN 2024-02-03 16:09:28 +05:30
parent e83f2dad9e
commit 1ea98b2b26
222 changed files with 1867 additions and 1282 deletions

View File

@ -1,4 +1,59 @@
# nHance # CodeIgniter 4 Framework
## Tech Stack ## What is CodeIgniter?
**Please** read the dev guide
CodeIgniter is a PHP full-stack web framework that is light, fast, flexible and secure.
More information can be found at the [official site](https://codeigniter.com).
This repository holds the distributable version of the framework.
It has been built from the
[development repository](https://github.com/codeigniter4/CodeIgniter4).
More information about the plans for version 4 can be found in [CodeIgniter 4](https://forum.codeigniter.com/forumdisplay.php?fid=28) on the forums.
The user guide corresponding to the latest version of the framework can be found
[here](https://codeigniter4.github.io/userguide/).
## Important Change with index.php
`index.php` is no longer in the root of the project! It has been moved inside the *public* folder,
for better security and separation of components.
This means that you should configure your web server to "point" to your project's *public* folder, and
not to the project root. A better practice would be to configure a virtual host to point there. A poor practice would be to point your web server to the project root and expect to enter *public/...*, as the rest of your logic and the
framework are exposed.
**Please** read the user guide for a better explanation of how CI4 works!
## Repository Management
We use GitHub issues, in our main repository, to track **BUGS** and to track approved **DEVELOPMENT** work packages.
We use our [forum](http://forum.codeigniter.com) to provide SUPPORT and to discuss
FEATURE REQUESTS.
This repository is a "distribution" one, built by our release preparation script.
Problems with it can be raised on our forum, or as issues in the main repository.
## Contributing
We welcome contributions from the community.
Please read the [*Contributing to CodeIgniter*](https://github.com/codeigniter4/CodeIgniter4/blob/develop/CONTRIBUTING.md) section in the development repository.
## Server Requirements
PHP version 7.4 or higher is required, with the following extensions installed:
- [intl](http://php.net/manual/en/intl.requirements.php)
- [mbstring](http://php.net/manual/en/mbstring.installation.php)
> **Warning**
> The end of life date for PHP 7.4 was November 28, 2022. If you are
> still using PHP 7.4, you should upgrade immediately. The end of life date
> for PHP 8.0 will be November 26, 2023.
Additionally, make sure that the following extensions are enabled in your PHP:
- json (enabled by default - don't turn it off)
- [mysqlnd](http://php.net/manual/en/mysqlnd.install.php) if you plan to use MySQL
- [libcurl](http://php.net/manual/en/curl.requirements.php) if you plan to use the HTTP\CURLRequest library

View File

@ -16,7 +16,7 @@ class App extends BaseConfig
* *
* http://example.com/ * http://example.com/
*/ */
public string $baseURL = 'http://localhost:8080/'; public string $baseURL = '';
/** /**
* Allowed Hostnames in the Site URL other than the hostname in the baseURL. * Allowed Hostnames in the Site URL other than the hostname in the baseURL.
@ -27,8 +27,7 @@ class App extends BaseConfig
* 'http://accounts.example.com/': * 'http://accounts.example.com/':
* ['media.example.com', 'accounts.example.com'] * ['media.example.com', 'accounts.example.com']
* *
* @var string[] * @var list<string>
* @phpstan-var list<string>
*/ */
public array $allowedHostnames = []; public array $allowedHostnames = [];
@ -41,7 +40,7 @@ class App extends BaseConfig
* something else. If you are using mod_rewrite to remove the page set this * something else. If you are using mod_rewrite to remove the page set this
* variable so that it is blank. * variable so that it is blank.
*/ */
public string $indexPage = ''; public string $indexPage = 'index.php';
/** /**
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------

0
app/Config/Autoload.php Normal file → Executable file
View File

View File

@ -10,7 +10,7 @@
| |
| If you set 'display_errors' to '1', CI4's detailed error report will show. | If you set 'display_errors' to '1', CI4's detailed error report will show.
*/ */
error_reporting(-1); error_reporting(E_ALL);
ini_set('display_errors', '1'); ini_set('display_errors', '1');
/* /*

View File

@ -14,7 +14,7 @@
| make sure they don't make it to production. And save us hours of | make sure they don't make it to production. And save us hours of
| painful debugging. | painful debugging.
*/ */
error_reporting(-1); error_reporting(E_ALL);
ini_set('display_errors', '1'); ini_set('display_errors', '1');
/* /*

View File

@ -158,8 +158,7 @@ class Cache extends BaseConfig
* This is an array of cache engine alias' and class names. Only engines * This is an array of cache engine alias' and class names. Only engines
* that are listed here are allowed to be used. * that are listed here are allowed to be used.
* *
* @var array<string, string> * @var array<string, class-string<CacheInterface>>
* @phpstan-var array<string, class-string<CacheInterface>>
*/ */
public array $validHandlers = [ public array $validHandlers = [
'dummy' => DummyHandler::class, 'dummy' => DummyHandler::class,

0
app/Config/DocTypes.php Normal file → Executable file
View File

View File

@ -26,7 +26,7 @@ class Email extends BaseConfig
public string $mailPath = '/usr/sbin/sendmail'; public string $mailPath = '/usr/sbin/sendmail';
/** /**
* SMTP Server Address * SMTP Server Hostname
*/ */
public string $SMTPHost = ''; public string $SMTPHost = '';

View File

@ -27,8 +27,7 @@ class Kint extends BaseConfig
*/ */
/** /**
* @var array<int, ConstructablePluginInterface|string> * @var list<class-string<ConstructablePluginInterface>|ConstructablePluginInterface>|null
* @phpstan-var list<class-string<ConstructablePluginInterface>|ConstructablePluginInterface>
*/ */
public $plugins; public $plugins;
@ -46,14 +45,12 @@ class Kint extends BaseConfig
public int $richSort = AbstractRenderer::SORT_FULL; public int $richSort = AbstractRenderer::SORT_FULL;
/** /**
* @var array<string, string> * @var array<string, class-string<ValuePluginInterface>>|null
* @phpstan-var array<string, class-string<ValuePluginInterface>>
*/ */
public $richObjectPlugins; public $richObjectPlugins;
/** /**
* @var array<string, string> * @var array<string, class-string<TabPluginInterface>>|null
* @phpstan-var array<string, class-string<TabPluginInterface>>
*/ */
public $richTabPlugins; public $richTabPlugins;

View File

@ -58,7 +58,7 @@ class Modules extends BaseModules
* ], * ],
* ] * ]
* *
* @var array * @var array{only?: list<string>, exclude?: list<string>}
*/ */
public $composerPackages = []; public $composerPackages = [];
@ -72,7 +72,7 @@ class Modules extends BaseModules
* *
* If it is not listed, only the base application elements will be used. * If it is not listed, only the base application elements will be used.
* *
* @var string[] * @var list<string>
*/ */
public $aliases = [ public $aliases = [
'events', 'events',

View File

@ -19,7 +19,7 @@ class Publisher extends BasePublisher
* result in a PublisherException. Files that do no fit the * result in a PublisherException. Files that do no fit the
* pattern will cause copy/merge to fail. * pattern will cause copy/merge to fail.
* *
* @var array<string,string> * @var array<string, string>
*/ */
public $restrictions = [ public $restrictions = [
ROOTPATH => '*', ROOTPATH => '*',

View File

@ -19,7 +19,7 @@ class Session extends BaseConfig
* - `CodeIgniter\Session\Handlers\MemcachedHandler` * - `CodeIgniter\Session\Handlers\MemcachedHandler`
* - `CodeIgniter\Session\Handlers\RedisHandler` * - `CodeIgniter\Session\Handlers\RedisHandler`
* *
* @phpstan-var class-string<BaseHandler> * @var class-string<BaseHandler>
*/ */
public string $driver = FileHandler::class; public string $driver = FileHandler::class;

View File

@ -13,6 +13,7 @@ class Home extends PublicController
{ {
echo base_url(); echo base_url();
//echo ($_SERVER['SERVER_PORT'] == 443 ? 'https' : 'http') . "://{$_SERVER['SERVER_NAME']}".str_replace(basename($_SERVER['SCRIPT_NAME']),"",$_SERVER['SCRIPT_NAME']); //echo ($_SERVER['SERVER_PORT'] == 443 ? 'https' : 'http') . "://{$_SERVER['SERVER_NAME']}".str_replace(basename($_SERVER['SCRIPT_NAME']),"",$_SERVER['SCRIPT_NAME']);
return view('welcome_message');
return view('test'); return view('test');
} }

View File

@ -18,8 +18,8 @@ class LoginController extends BaseController
public function login() public function login()
{ {
$this->session->set('isLoggedIn',true); // $this->session->set('isLoggedIn',true);
$this->session->set('userid','10'); // $this->session->set('userid','10');
return view('login'); return view('login');
} }

View File

@ -46,7 +46,7 @@ class UserController extends AdminController
// Get the ID of the inserted record // Get the ID of the inserted record
$insertedId = $userModel->insertID(); $insertedId = $userModel->insertID();
return view('userlist'); redirect('user/list');
} }
@ -87,9 +87,9 @@ class UserController extends AdminController
// Fetch all users from the 'users' table // Fetch all users from the 'users' table
$users = $userModel $users = $userModel
->join('users as updater', 'updater.id = users.updated_by', 'left') ->join('users3 as updater', 'updater.id = users3.updated_by', 'left')
->join('users as creator', 'creator.id = users.created_by', 'left') ->join('users3 as creator', 'creator.id = users3.created_by', 'left')
->select('users.*, updater.username as updated_by_name,creator.username as created_by_name'); // Assuming ->select('users3.*, updater.username as updated_by_name,creator.username as created_by_name'); // Assuming
if(!empty($userid) && is_numeric($userid)) if(!empty($userid) && is_numeric($userid))
{ {
$result = $users->find($userid); $result = $users->find($userid);

View File

@ -11,7 +11,7 @@ class AuthMVC implements FilterInterface
{ {
if (!check_session()) { if (!check_session()) {
return redirect()->to(site_url('login')); return redirect()->to(base_url('login'));
} }
} }

View File

@ -6,7 +6,7 @@ use CodeIgniter\Model;
class User extends Model class User extends Model
{ {
protected $DBGroup = 'default'; protected $DBGroup = 'default';
protected $table = 'users'; protected $table = 'users3';
protected $primaryKey = 'id'; protected $primaryKey = 'id';
protected $useAutoIncrement = true; protected $useAutoIncrement = true;
protected $insertID = 0; protected $insertID = 0;

View File

@ -106,28 +106,28 @@ span.psw {
<div class="container-fluid"> <div class="container-fluid">
<h1 class="mt-4">New/Edit User</h1> <h1 class="mt-4">New/Edit User</h1>
<div class="container"> <div class="container">
<form action=<?php echo base_url()."user/edit" ?> method="post"> <form action=<?php echo base_url()."user/create" ?> method="post">
<label for="username">Username:</label> <label for="username">Username:</label>
<input type="text" id="id" name="id" value= <?=$user['id']?> > <input type="text" id="id" name="id" value= >
<input type="text" id="username" name="username" value= <?=$user['username']?> required><br> <input type="text" id="username" name="username" value= required><br>
<label for="firstname">First Name:</label> <label for="firstname">First Name:</label>
<input type="text" id="firstname" name="firstname" value= <?=$user['firstname']?> required><br> <input type="text" id="firstname" name="firstname" value= required><br>
<label for="lastname">Last Name:</label> <label for="lastname">Last Name:</label>
<input type="text" id="lastname" name="lastname" value= <?=$user['lastname']?> required><br> <input type="text" id="lastname" name="lastname" value= required><br>
<label for="email">Email:</label> <label for="email">Email:</label>
<input type="email" id="email" name="email" value= <?=$user['email']?> required><br> <input type="email" id="email" name="email" value= required><br>
<label for="mobile_no">Mobile Number:</label> <label for="mobile_no">Mobile Number:</label>
<input type="text" id="mobile_no" name="mobile_no" value= <?=$user['mobile_no']?>><br> <input type="text" id="mobile_no" name="mobile_no" value= ><br>
<label for="password">Password:</label> <label for="password">Password:</label>
<input type="password" id="password" name="password" value="" required><br> <input type="password" id="password" name="password" value="" required><br>
<label for="is_active">Active:</label> <label for="is_active">Active:</label>
<input type="checkbox" id="is_active" name="is_active" checked= <?=$user['is_active']?> ><br> <input type="checkbox" id="is_active" name="is_active" checked= ><br>
<button type="submit" value="Submit">submit</button> <button type="submit" value="Submit">submit</button>
</form> </form>

View File

@ -18,9 +18,9 @@
"psr/log": "^1.1" "psr/log": "^1.1"
}, },
"require-dev": { "require-dev": {
"codeigniter/coding-standard": "^1.5", "codeigniter/coding-standard": "^1.7",
"fakerphp/faker": "^1.9", "fakerphp/faker": "^1.9",
"friendsofphp/php-cs-fixer": "3.13.0", "friendsofphp/php-cs-fixer": "^3.47.1",
"kint-php/kint": "^5.0.4", "kint-php/kint": "^5.0.4",
"mikey179/vfsstream": "^1.6", "mikey179/vfsstream": "^1.6",
"nexusphp/cs-config": "^3.6", "nexusphp/cs-config": "^3.6",
@ -43,6 +43,7 @@
"ext-readline": "Improves CLI::input() usability", "ext-readline": "Improves CLI::input() usability",
"ext-redis": "If you use Cache class RedisHandler", "ext-redis": "If you use Cache class RedisHandler",
"ext-simplexml": "If you format XML", "ext-simplexml": "If you format XML",
"ext-sodium": "If you use Encryption SodiumHandler",
"ext-sqlite3": "If you use SQLite3", "ext-sqlite3": "If you use SQLite3",
"ext-sqlsrv": "If you use SQL Server", "ext-sqlsrv": "If you use SQL Server",
"ext-xdebug": "If you use CIUnitTestCase::assertHeaderEmitted()" "ext-xdebug": "If you use CIUnitTestCase::assertHeaderEmitted()"

View File

@ -14,6 +14,9 @@ if (version_compare(PHP_VERSION, $minPhpVersion, '<')) {
// Path to the front controller (this file) // Path to the front controller (this file)
define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR); define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR);
// echo FCPATH;
// echo getcwd();
// die();
// Ensure the current directory is pointing to the front controller's directory // Ensure the current directory is pointing to the front controller's directory
if (getcwd() . DIRECTORY_SEPARATOR !== FCPATH) { if (getcwd() . DIRECTORY_SEPARATOR !== FCPATH) {
@ -35,6 +38,7 @@ require FCPATH . '../app/Config/Paths.php';
// ^^^ Change this line if you move your application folder // ^^^ Change this line if you move your application folder
$paths = new Config\Paths(); $paths = new Config\Paths();
// print_r($paths);
// Location of the framework bootstrap file. // Location of the framework bootstrap file.
require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php'; require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php';
@ -43,12 +47,11 @@ require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstra
require_once SYSTEMPATH . 'Config/DotEnv.php'; require_once SYSTEMPATH . 'Config/DotEnv.php';
(new CodeIgniter\Config\DotEnv(ROOTPATH))->load(); (new CodeIgniter\Config\DotEnv(ROOTPATH))->load();
// Define ENVIRONMENT // Define ENVIRONMENT
if (! defined('ENVIRONMENT')) { if (! defined('ENVIRONMENT')) {
define('ENVIRONMENT', env('CI_ENVIRONMENT', 'production')); define('ENVIRONMENT', env('CI_ENVIRONMENT', 'development'));
} }
// init_set('display_errors', 1); error_reporting(E_ALL ^ E_NOTICE);
// Load Config Cache // Load Config Cache
// $factoriesCache = new \CodeIgniter\Cache\FactoriesCache(); // $factoriesCache = new \CodeIgniter\Cache\FactoriesCache();
// $factoriesCache->load('config'); // $factoriesCache->load('config');
@ -63,7 +66,7 @@ if (! defined('ENVIRONMENT')) {
* the application run, and does all the dirty work to get * the application run, and does all the dirty work to get
* the pieces all working together. * the pieces all working together.
*/ */
// echo ENVIRONMENT;
$app = Config\Services::codeigniter(); $app = Config\Services::codeigniter();
$app->initialize(); $app->initialize();
$context = is_cli() ? 'php-cli' : 'web'; $context = is_cli() ? 'php-cli' : 'web';
@ -76,7 +79,7 @@ $app->setContext($context);
* Now that everything is set up, it's time to actually fire * Now that everything is set up, it's time to actually fire
* up the engines and make this app do its thang. * up the engines and make this app do its thang.
*/ */
//echo 'run';die('sample');
$app->run(); $app->run();
// Save Config Cache // Save Config Cache

2
spark Normal file → Executable file
View File

@ -39,7 +39,7 @@ if (version_compare(PHP_VERSION, $minPhpVersion, '<')) {
} }
// We want errors to be shown when using it from the CLI. // We want errors to be shown when using it from the CLI.
error_reporting(-1); error_reporting(E_ALL);
ini_set('display_errors', '1'); ini_set('display_errors', '1');
/** /**

View File

@ -94,7 +94,7 @@ trait ResponseTrait
$output = null; $output = null;
$this->format($data); $this->format($data);
} else { } else {
$status = empty($status) ? 200 : $status; $status ??= 200;
$output = $this->format($data); $output = $this->format($data);
} }
@ -318,7 +318,7 @@ trait ResponseTrait
// Determine correct response type through content negotiation if not explicitly declared // Determine correct response type through content negotiation if not explicitly declared
if ( if (
(empty($this->format) || ! in_array($this->format, ['json', 'xml'], true)) ! in_array($this->format, ['json', 'xml'], true)
&& $this->request instanceof IncomingRequest && $this->request instanceof IncomingRequest
) { ) {
$mime = $this->request->negotiate( $mime = $this->request->negotiate(

View File

@ -75,8 +75,7 @@ class Autoloader
/** /**
* Stores files as a list. * Stores files as a list.
* *
* @var string[] * @var list<string>
* @phpstan-var list<string>
*/ */
protected $files = []; protected $files = [];
@ -84,8 +83,7 @@ class Autoloader
* Stores helper list. * Stores helper list.
* Always load the URL helper, it should be used in most apps. * Always load the URL helper, it should be used in most apps.
* *
* @var string[] * @var list<string>
* @phpstan-var list<string>
*/ */
protected $helpers = ['url']; protected $helpers = ['url'];
@ -119,7 +117,7 @@ class Autoloader
$this->files = $config->files; $this->files = $config->files;
} }
if (isset($config->helpers)) { // @phpstan-ignore-line if (isset($config->helpers)) {
$this->helpers = [...$this->helpers, ...$config->helpers]; $this->helpers = [...$this->helpers, ...$config->helpers];
} }
@ -185,8 +183,7 @@ class Autoloader
/** /**
* Registers namespaces with the autoloader. * Registers namespaces with the autoloader.
* *
* @param array<string, array<int, string>|string>|string $namespace * @param array<string, list<string>|string>|string $namespace
* @phpstan-param array<string, list<string>|string>|string $namespace
* *
* @return $this * @return $this
*/ */

View File

@ -35,14 +35,14 @@ class FileLocator
* Attempts to locate a file by examining the name for a namespace * Attempts to locate a file by examining the name for a namespace
* and looking through the PSR-4 namespaced files that we know about. * and looking through the PSR-4 namespaced files that we know about.
* *
* @param string $file The relative file path or namespaced file to * @param string $file The relative file path or namespaced file to
* locate. If not namespaced, search in the app * locate. If not namespaced, search in the app
* folder. * folder.
* @param string|null $folder The folder within the namespace that we should * @param non-empty-string|null $folder The folder within the namespace that we should
* look for the file. If $file does not contain * look for the file. If $file does not contain
* this value, it will be appended to the namespace * this value, it will be appended to the namespace
* folder. * folder.
* @param string $ext The file extension the file should have. * @param string $ext The file extension the file should have.
* *
* @return false|string The path to the file, or false if not found. * @return false|string The path to the file, or false if not found.
*/ */
@ -51,7 +51,7 @@ class FileLocator
$file = $this->ensureExt($file, $ext); $file = $this->ensureExt($file, $ext);
// Clears the folder name if it is at the beginning of the filename // Clears the folder name if it is at the beginning of the filename
if (! empty($folder) && strpos($file, $folder) === 0) { if ($folder !== null && strpos($file, $folder) === 0) {
$file = substr($file, strlen($folder . '/')); $file = substr($file, strlen($folder . '/'));
} }
@ -67,7 +67,7 @@ class FileLocator
$segments = explode('\\', $file); $segments = explode('\\', $file);
// The first segment will be empty if a slash started the filename. // The first segment will be empty if a slash started the filename.
if (empty($segments[0])) { if ($segments[0] === '') {
unset($segments[0]); unset($segments[0]);
} }
@ -89,7 +89,7 @@ class FileLocator
} }
// if no namespaces matched then quit // if no namespaces matched then quit
if (empty($paths)) { if ($paths === []) {
return false; return false;
} }
@ -101,7 +101,7 @@ class FileLocator
// If we have a folder name, then the calling function // If we have a folder name, then the calling function
// expects this file to be within that folder, like 'Views', // expects this file to be within that folder, like 'Views',
// or 'libraries'. // or 'libraries'.
if (! empty($folder) && strpos($path . $filename, '/' . $folder . '/') === false) { if ($folder !== null && strpos($path . $filename, '/' . $folder . '/') === false) {
$path .= trim($folder, '/') . '/'; $path .= trim($folder, '/') . '/';
} }
@ -119,6 +119,10 @@ class FileLocator
*/ */
public function getClassname(string $file): string public function getClassname(string $file): string
{ {
if (is_dir($file)) {
return '';
}
$php = file_get_contents($file); $php = file_get_contents($file);
$tokens = token_get_all($php); $tokens = token_get_all($php);
$dlm = false; $dlm = false;
@ -150,7 +154,7 @@ class FileLocator
} }
} }
if (empty($className)) { if ($className === '') {
return ''; return '';
} }
@ -192,7 +196,7 @@ class FileLocator
} }
} }
if (! $prioritizeApp && ! empty($appPaths)) { if (! $prioritizeApp && $appPaths !== []) {
$foundPaths = [...$foundPaths, ...$appPaths]; $foundPaths = [...$foundPaths, ...$appPaths];
} }
@ -268,7 +272,7 @@ class FileLocator
foreach ($this->getNamespaces() as $namespace) { foreach ($this->getNamespaces() as $namespace) {
$namespace['path'] = realpath($namespace['path']) ?: $namespace['path']; $namespace['path'] = realpath($namespace['path']) ?: $namespace['path'];
if (empty($namespace['path'])) { if ($namespace['path'] === '') {
continue; continue;
} }
@ -301,7 +305,7 @@ class FileLocator
*/ */
public function listFiles(string $path): array public function listFiles(string $path): array
{ {
if (empty($path)) { if ($path === '') {
return []; return [];
} }
@ -318,7 +322,7 @@ class FileLocator
$tempFiles = get_filenames($fullPath, true, false, false); $tempFiles = get_filenames($fullPath, true, false, false);
if (! empty($tempFiles)) { if ($tempFiles !== []) {
$files = array_merge($files, $tempFiles); $files = array_merge($files, $tempFiles);
} }
} }
@ -334,7 +338,7 @@ class FileLocator
*/ */
public function listNamespaceFiles(string $prefix, string $path): array public function listNamespaceFiles(string $prefix, string $path): array
{ {
if (empty($path) || empty($prefix)) { if ($path === '' || ($prefix === '')) {
return []; return [];
} }
@ -352,7 +356,7 @@ class FileLocator
$tempFiles = get_filenames($fullPath, true, false, false); $tempFiles = get_filenames($fullPath, true, false, false);
if (! empty($tempFiles)) { if ($tempFiles !== []) {
$files = array_merge($files, $tempFiles); $files = array_merge($files, $tempFiles);
} }
} }
@ -364,11 +368,13 @@ class FileLocator
* Checks the app folder to see if the file can be found. * Checks the app folder to see if the file can be found.
* Only for use with filenames that DO NOT include namespacing. * Only for use with filenames that DO NOT include namespacing.
* *
* @param non-empty-string|null $folder
*
* @return false|string The path to the file, or false if not found. * @return false|string The path to the file, or false if not found.
*/ */
protected function legacyLocate(string $file, ?string $folder = null) protected function legacyLocate(string $file, ?string $folder = null)
{ {
$path = APPPATH . (empty($folder) ? $file : $folder . '/' . $file); $path = APPPATH . ($folder === null ? $file : $folder . '/' . $file);
$path = realpath($path) ?: $path; $path = realpath($path) ?: $path;
if (is_file($path)) { if (is_file($path)) {

View File

@ -41,6 +41,16 @@ use stdClass;
* - ensure validation is run against objects when saving items * - ensure validation is run against objects when saving items
* - process various callbacks * - process various callbacks
* - allow intermingling calls to the db connection * - allow intermingling calls to the db connection
*
* @phpstan-type row_array array<int|string, float|int|null|string>
* @phpstan-type event_data_beforeinsert array{data: row_array}
* @phpstan-type event_data_afterinsert array{id: int|string, data: row_array, result: bool}
* @phpstan-type event_data_beforefind array{id?: int|string, method: string, singleton: bool, limit?: int, offset?: int}
* @phpstan-type event_data_afterfind array{id: int|string|null|list<int|string>, data: row_array|list<row_array>|object|null, method: string, singleton: bool}
* @phpstan-type event_data_beforeupdate array{id: null|list<int|string>, data: row_array}
* @phpstan-type event_data_afterupdate array{id: null|list<int|string>, data: row_array|object, result: bool}
* @phpstan-type event_data_beforedelete array{id: null|list<int|string>, purge: bool}
* @phpstan-type event_data_afterdelete array{id: null|list<int|string>, data: null, purge: bool, result: bool}
*/ */
abstract class BaseModel abstract class BaseModel
{ {
@ -63,8 +73,7 @@ abstract class BaseModel
* The Database connection group that * The Database connection group that
* should be instantiated. * should be instantiated.
* *
* @var string|null * @var non-empty-string|null
* @phpstan-var non-empty-string|null
*/ */
protected $DBGroup; protected $DBGroup;
@ -391,11 +400,12 @@ abstract class BaseModel
* Inserts data into the current database. * Inserts data into the current database.
* This method works only with dbCalls. * This method works only with dbCalls.
* *
* @param array $data Data * @param array $row Row data
* @phpstan-param row_array $row
* *
* @return bool * @return bool
*/ */
abstract protected function doInsert(array $data); abstract protected function doInsert(array $row);
/** /**
* Compiles batch insert and runs the queries, validating each row prior. * Compiles batch insert and runs the queries, validating each row prior.
@ -414,10 +424,11 @@ abstract class BaseModel
* Updates a single record in the database. * Updates a single record in the database.
* This method works only with dbCalls. * This method works only with dbCalls.
* *
* @param array|int|string|null $id ID * @param array|int|string|null $id ID
* @param array|null $data Data * @param array|null $row Row data
* @phpstan-param row_array|null $row
*/ */
abstract protected function doUpdate($id = null, $data = null): bool; abstract protected function doUpdate($id = null, $row = null): bool;
/** /**
* Compiles an update and runs the query. * Compiles an update and runs the query.
@ -469,12 +480,13 @@ abstract class BaseModel
* Compiles a replace and runs the query. * Compiles a replace and runs the query.
* This method works only with dbCalls. * This method works only with dbCalls.
* *
* @param array|null $data Data * @param array|null $row Row data
* @param bool $returnSQL Set to true to return Query String * @phpstan-param row_array|null $row
* @param bool $returnSQL Set to true to return Query String
* *
* @return BaseResult|false|Query|string * @return BaseResult|false|Query|string
*/ */
abstract protected function doReplace(?array $data = null, bool $returnSQL = false); abstract protected function doReplace(?array $row = null, bool $returnSQL = false);
/** /**
* Grabs the last error(s) that occurred from the Database connection. * Grabs the last error(s) that occurred from the Database connection.
@ -499,15 +511,16 @@ abstract class BaseModel
* Public getter to return the id value using the idValue() method. * Public getter to return the id value using the idValue() method.
* For example with SQL this will return $data->$this->primaryKey. * For example with SQL this will return $data->$this->primaryKey.
* *
* @param array|object $data * @param array|object $row Row data
* @phpstan-param row_array|object $row
* *
* @return array|int|string|null * @return array|int|string|null
* *
* @todo: Make abstract in version 5.0 * @todo: Make abstract in version 5.0
*/ */
public function getIdValue($data) public function getIdValue($row)
{ {
return $this->idValue($data); return $this->idValue($row);
} }
/** /**
@ -540,6 +553,7 @@ abstract class BaseModel
* @param array|int|string|null $id One primary key or an array of primary keys * @param array|int|string|null $id One primary key or an array of primary keys
* *
* @return array|object|null The resulting row of data, or null. * @return array|object|null The resulting row of data, or null.
* @phpstan-return ($id is int|string ? row_array|object|null : list<row_array|object>)
*/ */
public function find($id = null) public function find($id = null)
{ {
@ -553,7 +567,7 @@ abstract class BaseModel
'singleton' => $singleton, 'singleton' => $singleton,
]); ]);
if (! empty($eventData['returnData'])) { if (isset($eventData['returnData']) && $eventData['returnData'] === true) {
return $eventData['data']; return $eventData['data'];
} }
} }
@ -615,7 +629,7 @@ abstract class BaseModel
'singleton' => false, 'singleton' => false,
]); ]);
if (! empty($eventData['returnData'])) { if (isset($eventData['returnData']) && $eventData['returnData'] === true) {
return $eventData['data']; return $eventData['data'];
} }
} }
@ -653,7 +667,7 @@ abstract class BaseModel
'singleton' => true, 'singleton' => true,
]); ]);
if (! empty($eventData['returnData'])) { if (isset($eventData['returnData']) && $eventData['returnData'] === true) {
return $eventData['data']; return $eventData['data'];
} }
} }
@ -682,20 +696,21 @@ abstract class BaseModel
* you must ensure that the class will provide access to the class * you must ensure that the class will provide access to the class
* variables, even if through a magic method. * variables, even if through a magic method.
* *
* @param array|object $data Data * @param array|object $row Row data
* @phpstan-param row_array|object $row
* *
* @throws ReflectionException * @throws ReflectionException
*/ */
public function save($data): bool public function save($row): bool
{ {
if (empty($data)) { if ((array) $row === []) {
return true; return true;
} }
if ($this->shouldUpdate($data)) { if ($this->shouldUpdate($row)) {
$response = $this->update($this->getIdValue($data), $data); $response = $this->update($this->getIdValue($row), $row);
} else { } else {
$response = $this->insert($data, false); $response = $this->insert($row, false);
if ($response !== false) { if ($response !== false) {
$response = true; $response = true;
@ -709,11 +724,14 @@ abstract class BaseModel
* This method is called on save to determine if entry have to be updated. * This method is called on save to determine if entry have to be updated.
* If this method returns false insert operation will be executed * If this method returns false insert operation will be executed
* *
* @param array|object $data Data * @param array|object $row Row data
* @phpstan-param row_array|object $row
*/ */
protected function shouldUpdate($data): bool protected function shouldUpdate($row): bool
{ {
return ! empty($this->getIdValue($data)); $id = $this->getIdValue($row);
return ! ($id === null || $id === []);
} }
/** /**
@ -730,14 +748,16 @@ abstract class BaseModel
* Inserts data into the database. If an object is provided, * Inserts data into the database. If an object is provided,
* it will attempt to convert it to an array. * it will attempt to convert it to an array.
* *
* @param array|object|null $data Data * @param array|object|null $row Row data
* @param bool $returnID Whether insert ID should be returned or not. * @phpstan-param row_array|object|null $row
* @param bool $returnID Whether insert ID should be returned or not.
* *
* @return bool|int|string insert ID or true on success. false on failure. * @return bool|int|string insert ID or true on success. false on failure.
* @phpstan-return ($returnID is true ? int|string|false : bool)
* *
* @throws ReflectionException * @throws ReflectionException
*/ */
public function insert($data = null, bool $returnID = true) public function insert($row = null, bool $returnID = true)
{ {
$this->insertID = 0; $this->insertID = 0;
@ -745,10 +765,10 @@ abstract class BaseModel
$cleanValidationRules = $this->cleanValidationRules; $cleanValidationRules = $this->cleanValidationRules;
$this->cleanValidationRules = false; $this->cleanValidationRules = false;
$data = $this->transformDataToArray($data, 'insert'); $row = $this->transformDataToArray($row, 'insert');
// Validate data before saving. // Validate data before saving.
if (! $this->skipValidation && ! $this->validate($data)) { if (! $this->skipValidation && ! $this->validate($row)) {
// Restore $cleanValidationRules // Restore $cleanValidationRules
$this->cleanValidationRules = $cleanValidationRules; $this->cleanValidationRules = $cleanValidationRules;
@ -760,26 +780,20 @@ abstract class BaseModel
// Must be called first, so we don't // Must be called first, so we don't
// strip out created_at values. // strip out created_at values.
$data = $this->doProtectFieldsForInsert($data); $row = $this->doProtectFieldsForInsert($row);
// doProtectFields() can further remove elements from // doProtectFields() can further remove elements from
// $data so we need to check for empty dataset again // $row, so we need to check for empty dataset again
if (! $this->allowEmptyInserts && empty($data)) { if (! $this->allowEmptyInserts && $row === []) {
throw DataException::forEmptyDataset('insert'); throw DataException::forEmptyDataset('insert');
} }
// Set created_at and updated_at with same time // Set created_at and updated_at with same time
$date = $this->setDate(); $date = $this->setDate();
$row = $this->setCreatedField($row, $date);
$row = $this->setUpdatedField($row, $date);
if ($this->useTimestamps && $this->createdField && ! array_key_exists($this->createdField, $data)) { $eventData = ['data' => $row];
$data[$this->createdField] = $date;
}
if ($this->useTimestamps && $this->updatedField && ! array_key_exists($this->updatedField, $data)) {
$data[$this->updatedField] = $date;
}
$eventData = ['data' => $data];
if ($this->tempAllowCallbacks) { if ($this->tempAllowCallbacks) {
$eventData = $this->trigger('beforeInsert', $eventData); $eventData = $this->trigger('beforeInsert', $eventData);
@ -809,13 +823,44 @@ abstract class BaseModel
return $returnID ? $this->insertID : $result; return $returnID ? $this->insertID : $result;
} }
/**
* Set datetime to created field.
*
* @phpstan-param row_array $row
* @param int|string $date timestamp or datetime string
*/
protected function setCreatedField(array $row, $date): array
{
if ($this->useTimestamps && $this->createdField !== '' && ! array_key_exists($this->createdField, $row)) {
$row[$this->createdField] = $date;
}
return $row;
}
/**
* Set datetime to updated field.
*
* @phpstan-param row_array $row
* @param int|string $date timestamp or datetime string
*/
protected function setUpdatedField(array $row, $date): array
{
if ($this->useTimestamps && $this->updatedField !== '' && ! array_key_exists($this->updatedField, $row)) {
$row[$this->updatedField] = $date;
}
return $row;
}
/** /**
* Compiles batch insert runs the queries, validating each row prior. * Compiles batch insert runs the queries, validating each row prior.
* *
* @param array|null $set an associative array of insert values * @param list<array|object>|null $set an associative array of insert values
* @param bool|null $escape Whether to escape values * @phpstan-param list<row_array|object>|null $set
* @param int $batchSize The size of the batch to run * @param bool|null $escape Whether to escape values
* @param bool $testing True means only number of records is returned, false will execute the query * @param int $batchSize The size of the batch to run
* @param bool $testing True means only number of records is returned, false will execute the query
* *
* @return bool|int Number of rows inserted or FALSE on failure * @return bool|int Number of rows inserted or FALSE on failure
* *
@ -829,7 +874,7 @@ abstract class BaseModel
if (is_array($set)) { if (is_array($set)) {
foreach ($set as &$row) { foreach ($set as &$row) {
// If $data is using a custom class with public or protected // If $row is using a custom class with public or protected
// properties representing the collection elements, we need to grab // properties representing the collection elements, we need to grab
// them as an array. // them as an array.
if (is_object($row) && ! $row instanceof stdClass) { if (is_object($row) && ! $row instanceof stdClass) {
@ -857,14 +902,8 @@ abstract class BaseModel
// Set created_at and updated_at with same time // Set created_at and updated_at with same time
$date = $this->setDate(); $date = $this->setDate();
$row = $this->setCreatedField($row, $date);
if ($this->useTimestamps && $this->createdField && ! array_key_exists($this->createdField, $row)) { $row = $this->setUpdatedField($row, $date);
$row[$this->createdField] = $date;
}
if ($this->useTimestamps && $this->updatedField && ! array_key_exists($this->updatedField, $row)) {
$row[$this->updatedField] = $date;
}
} }
} }
@ -899,11 +938,12 @@ abstract class BaseModel
* it will attempt to convert it into an array. * it will attempt to convert it into an array.
* *
* @param array|int|string|null $id * @param array|int|string|null $id
* @param array|object|null $data * @param array|object|null $row Row data
* @phpstan-param row_array|object|null $row
* *
* @throws ReflectionException * @throws ReflectionException
*/ */
public function update($id = null, $data = null): bool public function update($id = null, $row = null): bool
{ {
if (is_bool($id)) { if (is_bool($id)) {
throw new InvalidArgumentException('update(): argument #1 ($id) should not be boolean.'); throw new InvalidArgumentException('update(): argument #1 ($id) should not be boolean.');
@ -913,30 +953,28 @@ abstract class BaseModel
$id = [$id]; $id = [$id];
} }
$data = $this->transformDataToArray($data, 'update'); $row = $this->transformDataToArray($row, 'update');
// Validate data before saving. // Validate data before saving.
if (! $this->skipValidation && ! $this->validate($data)) { if (! $this->skipValidation && ! $this->validate($row)) {
return false; return false;
} }
// Must be called first, so we don't // Must be called first, so we don't
// strip out updated_at values. // strip out updated_at values.
$data = $this->doProtectFields($data); $row = $this->doProtectFields($row);
// doProtectFields() can further remove elements from // doProtectFields() can further remove elements from
// $data, so we need to check for empty dataset again // $row, so we need to check for empty dataset again
if (empty($data)) { if ($row === []) {
throw DataException::forEmptyDataset('update'); throw DataException::forEmptyDataset('update');
} }
if ($this->useTimestamps && $this->updatedField && ! array_key_exists($this->updatedField, $data)) { $row = $this->setUpdatedField($row, $this->setDate());
$data[$this->updatedField] = $this->setDate();
}
$eventData = [ $eventData = [
'id' => $id, 'id' => $id,
'data' => $data, 'data' => $row,
]; ];
if ($this->tempAllowCallbacks) { if ($this->tempAllowCallbacks) {
@ -961,7 +999,8 @@ abstract class BaseModel
/** /**
* Compiles an update and runs the query. * Compiles an update and runs the query.
* *
* @param array|null $set An associative array of update values * @param list<array|object>|null $set an associative array of insert values
* @phpstan-param list<row_array|object>|null $set
* @param string|null $index The where key * @param string|null $index The where key
* @param int $batchSize The size of the batch to run * @param int $batchSize The size of the batch to run
* @param bool $returnSQL True means SQL is returned, false will execute the query * @param bool $returnSQL True means SQL is returned, false will execute the query
@ -975,7 +1014,7 @@ abstract class BaseModel
{ {
if (is_array($set)) { if (is_array($set)) {
foreach ($set as &$row) { foreach ($set as &$row) {
// If $data is using a custom class with public or protected // If $row is using a custom class with public or protected
// properties representing the collection elements, we need to grab // properties representing the collection elements, we need to grab
// them as an array. // them as an array.
if (is_object($row) && ! $row instanceof stdClass) { if (is_object($row) && ! $row instanceof stdClass) {
@ -1015,9 +1054,7 @@ abstract class BaseModel
$row[$index] = $updateIndex; $row[$index] = $updateIndex;
} }
if ($this->useTimestamps && $this->updatedField && ! array_key_exists($this->updatedField, $row)) { $row = $this->setUpdatedField($row, $this->setDate());
$row[$this->updatedField] = $this->setDate();
}
} }
} }
@ -1136,23 +1173,22 @@ abstract class BaseModel
/** /**
* Compiles a replace and runs the query. * Compiles a replace and runs the query.
* *
* @param array|null $data Data * @param array|null $row Row data
* @param bool $returnSQL Set to true to return Query String * @phpstan-param row_array|null $row
* @param bool $returnSQL Set to true to return Query String
* *
* @return BaseResult|false|Query|string * @return BaseResult|false|Query|string
*/ */
public function replace(?array $data = null, bool $returnSQL = false) public function replace(?array $row = null, bool $returnSQL = false)
{ {
// Validate data before saving. // Validate data before saving.
if ($data && ! $this->skipValidation && ! $this->validate($data)) { if (($row !== null) && ! $this->skipValidation && ! $this->validate($row)) {
return false; return false;
} }
if ($this->useTimestamps && $this->updatedField && ! array_key_exists($this->updatedField, (array) $data)) { $row = $this->setUpdatedField((array) $row, $this->setDate());
$data[$this->updatedField] = $this->setDate();
}
return $this->doReplace($data, $returnSQL); return $this->doReplace($row, $returnSQL);
} }
/** /**
@ -1240,46 +1276,48 @@ abstract class BaseModel
* Ensures that only the fields that are allowed to be updated are * Ensures that only the fields that are allowed to be updated are
* in the data array. * in the data array.
* *
* Used by update() and updateBatch() to protect against mass assignment * @used-by update() to protect against mass assignment vulnerabilities.
* vulnerabilities. * @used-by updateBatch() to protect against mass assignment vulnerabilities.
* *
* @param array $data Data * @param array $row Row data
* @phpstan-param row_array $row
* *
* @throws DataException * @throws DataException
*/ */
protected function doProtectFields(array $data): array protected function doProtectFields(array $row): array
{ {
if (! $this->protectFields) { if (! $this->protectFields) {
return $data; return $row;
} }
if (empty($this->allowedFields)) { if ($this->allowedFields === []) {
throw DataException::forInvalidAllowedFields(static::class); throw DataException::forInvalidAllowedFields(static::class);
} }
foreach (array_keys($data) as $key) { foreach (array_keys($row) as $key) {
if (! in_array($key, $this->allowedFields, true)) { if (! in_array($key, $this->allowedFields, true)) {
unset($data[$key]); unset($row[$key]);
} }
} }
return $data; return $row;
} }
/** /**
* Ensures that only the fields that are allowed to be inserted are in * Ensures that only the fields that are allowed to be inserted are in
* the data array. * the data array.
* *
* Used by insert() and insertBatch() to protect against mass assignment * @used-by insert() to protect against mass assignment vulnerabilities.
* vulnerabilities. * @used-by insertBatch() to protect against mass assignment vulnerabilities.
* *
* @param array $data Data * @param array $row Row data
* @phpstan-param row_array $row
* *
* @throws DataException * @throws DataException
*/ */
protected function doProtectFieldsForInsert(array $data): array protected function doProtectFieldsForInsert(array $row): array
{ {
return $this->doProtectFields($data); return $this->doProtectFields($row);
} }
/** /**
@ -1464,35 +1502,36 @@ abstract class BaseModel
} }
/** /**
* Validate the data against the validation rules (or the validation group) * Validate the row data against the validation rules (or the validation group)
* specified in the class property, $validationRules. * specified in the class property, $validationRules.
* *
* @param array|object $data Data * @param array|object $row Row data
* @phpstan-param row_array|object $row
*/ */
public function validate($data): bool public function validate($row): bool
{ {
$rules = $this->getValidationRules(); $rules = $this->getValidationRules();
if ($this->skipValidation || empty($rules) || empty($data)) { // Validation requires array, so cast away.
if (is_object($row)) {
$row = (array) $row;
}
if ($this->skipValidation || $rules === [] || $row === []) {
return true; return true;
} }
// Validation requires array, so cast away. $rules = $this->cleanValidationRules ? $this->cleanValidationRules($rules, $row) : $rules;
if (is_object($data)) {
$data = (array) $data;
}
$rules = $this->cleanValidationRules ? $this->cleanValidationRules($rules, $data) : $rules;
// If no data existed that needs validation // If no data existed that needs validation
// our job is done here. // our job is done here.
if (empty($rules)) { if ($rules === []) {
return true; return true;
} }
$this->validation->reset()->setRules($rules, $this->validationMessages); $this->validation->reset()->setRules($rules, $this->validationMessages);
return $this->validation->run($data, null, $this->DBGroup); return $this->validation->run($row, null, $this->DBGroup);
} }
/** /**
@ -1536,17 +1575,18 @@ abstract class BaseModel
* currently so that rules don't block updating when only updating * currently so that rules don't block updating when only updating
* a partial row. * a partial row.
* *
* @param array $rules Array containing field name and rule * @param array $rules Array containing field name and rule
* @param array|null $data Data * @param array $row Row data (@TODO Remove null in param type)
* @phpstan-param row_array $row
*/ */
protected function cleanValidationRules(array $rules, ?array $data = null): array protected function cleanValidationRules(array $rules, ?array $row = null): array
{ {
if (empty($data)) { if ($row === null || $row === []) {
return []; return [];
} }
foreach (array_keys($rules) as $field) { foreach (array_keys($rules) as $field) {
if (! array_key_exists($field, $data)) { if (! array_key_exists($field, $row)) {
unset($rules[$field]); unset($rules[$field]);
} }
} }
@ -1594,7 +1634,7 @@ abstract class BaseModel
protected function trigger(string $event, array $eventData) protected function trigger(string $event, array $eventData)
{ {
// Ensure it's a valid event // Ensure it's a valid event
if (! isset($this->{$event}) || empty($this->{$event})) { if (! isset($this->{$event}) || $this->{$event} === []) {
return $eventData; return $eventData;
} }
@ -1644,52 +1684,63 @@ abstract class BaseModel
* This method uses objectToRawArray() internally and does conversion * This method uses objectToRawArray() internally and does conversion
* to string on all Time instances * to string on all Time instances
* *
* @param object|string $data Data * @param object $object Object
* @param bool $onlyChanged Only Changed Property * @param bool $onlyChanged Only Changed Property
* @param bool $recursive If true, inner entities will be cast as array as well * @param bool $recursive If true, inner entities will be cast as array as well
* *
* @return array Array * @return array<string, mixed>
* *
* @throws ReflectionException * @throws ReflectionException
*/ */
protected function objectToArray($data, bool $onlyChanged = true, bool $recursive = false): array protected function objectToArray($object, bool $onlyChanged = true, bool $recursive = false): array
{ {
$properties = $this->objectToRawArray($data, $onlyChanged, $recursive); $properties = $this->objectToRawArray($object, $onlyChanged, $recursive);
assert(is_array($properties));
// Convert any Time instances to appropriate $dateFormat // Convert any Time instances to appropriate $dateFormat
if ($properties !== []) { return $this->timeToString($properties);
$properties = array_map(function ($value) { }
if ($value instanceof Time) {
return $this->timeToDate($value);
}
return $value; /**
}, $properties); * Convert any Time instances to appropriate $dateFormat.
*
* @param array<string, mixed> $properties
*
* @return array<string, mixed>
*/
protected function timeToString(array $properties): array
{
if ($properties === []) {
return [];
} }
return $properties; return array_map(function ($value) {
if ($value instanceof Time) {
return $this->timeToDate($value);
}
return $value;
}, $properties);
} }
/** /**
* Takes a class and returns an array of its public and protected * Takes a class and returns an array of its public and protected
* properties as an array with raw values. * properties as an array with raw values.
* *
* @param object|string $data Data * @param object $object Object
* @param bool $onlyChanged Only Changed Property * @param bool $onlyChanged Only Changed Property
* @param bool $recursive If true, inner entities will be casted as array as well * @param bool $recursive If true, inner entities will be casted as array as well
* *
* @return array|null Array * @return array<string, mixed>
* *
* @throws ReflectionException * @throws ReflectionException
*/ */
protected function objectToRawArray($data, bool $onlyChanged = true, bool $recursive = false): ?array protected function objectToRawArray($object, bool $onlyChanged = true, bool $recursive = false): array
{ {
if (method_exists($data, 'toRawArray')) { // Entity::toRawArray() returns array.
$properties = $data->toRawArray($onlyChanged, $recursive); if (method_exists($object, 'toRawArray')) {
$properties = $object->toRawArray($onlyChanged, $recursive);
} else { } else {
$mirror = new ReflectionClass($data); $mirror = new ReflectionClass($object);
$props = $mirror->getProperties(ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED); $props = $mirror->getProperties(ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED);
$properties = []; $properties = [];
@ -1699,7 +1750,7 @@ abstract class BaseModel
foreach ($props as $prop) { foreach ($props as $prop) {
// Must make protected values accessible. // Must make protected values accessible.
$prop->setAccessible(true); $prop->setAccessible(true);
$properties[$prop->getName()] = $prop->getValue($data); $properties[$prop->getName()] = $prop->getValue($object);
} }
} }
@ -1709,47 +1760,48 @@ abstract class BaseModel
/** /**
* Transform data to array. * Transform data to array.
* *
* @param array|object|null $data Data * @param array|object|null $row Row data
* @param string $type Type of data (insert|update) * @phpstan-param row_array|object|null $row
* @param string $type Type of data (insert|update)
* *
* @throws DataException * @throws DataException
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @throws ReflectionException * @throws ReflectionException
*/ */
protected function transformDataToArray($data, string $type): array protected function transformDataToArray($row, string $type): array
{ {
if (! in_array($type, ['insert', 'update'], true)) { if (! in_array($type, ['insert', 'update'], true)) {
throw new InvalidArgumentException(sprintf('Invalid type "%s" used upon transforming data to array.', $type)); throw new InvalidArgumentException(sprintf('Invalid type "%s" used upon transforming data to array.', $type));
} }
if (! $this->allowEmptyInserts && empty($data)) { if (! $this->allowEmptyInserts && ($row === null || (array) $row === [])) {
throw DataException::forEmptyDataset($type); throw DataException::forEmptyDataset($type);
} }
// If $data is using a custom class with public or protected // If $row is using a custom class with public or protected
// properties representing the collection elements, we need to grab // properties representing the collection elements, we need to grab
// them as an array. // them as an array.
if (is_object($data) && ! $data instanceof stdClass) { if (is_object($row) && ! $row instanceof stdClass) {
// If it validates with entire rules, all fields are needed. // If it validates with entire rules, all fields are needed.
$onlyChanged = ($this->skipValidation === false && $this->cleanValidationRules === false) $onlyChanged = ($this->skipValidation === false && $this->cleanValidationRules === false)
? false : ($type === 'update'); ? false : ($type === 'update');
$data = $this->objectToArray($data, $onlyChanged, true); $row = $this->objectToArray($row, $onlyChanged, true);
} }
// If it's still a stdClass, go ahead and convert to // If it's still a stdClass, go ahead and convert to
// an array so doProtectFields and other model methods // an array so doProtectFields and other model methods
// don't have to do special checks. // don't have to do special checks.
if (is_object($data)) { if (is_object($row)) {
$data = (array) $data; $row = (array) $row;
} }
// If it's still empty here, means $data is no change or is empty object // If it's still empty here, means $row is no change or is empty object
if (! $this->allowEmptyInserts && empty($data)) { if (! $this->allowEmptyInserts && ($row === null || $row === [])) {
throw DataException::forEmptyDataset($type); throw DataException::forEmptyDataset($type);
} }
return $data; return $row;
} }
/** /**
@ -1829,7 +1881,7 @@ abstract class BaseModel
$replacements['{' . $key . '}'] = $value; $replacements['{' . $key . '}'] = $value;
} }
if (! empty($replacements)) { if ($replacements !== []) {
foreach ($rules as &$rule) { foreach ($rules as &$rule) {
if (is_array($rule)) { if (is_array($rule)) {
foreach ($rule as &$row) { foreach ($rule as &$row) {

View File

@ -19,14 +19,14 @@ use Throwable;
/** /**
* BaseCommand is the base class used in creating CLI commands. * BaseCommand is the base class used in creating CLI commands.
* *
* @property array $arguments * @property array<string, string> $arguments
* @property Commands $commands * @property Commands $commands
* @property string $description * @property string $description
* @property string $group * @property string $group
* @property LoggerInterface $logger * @property LoggerInterface $logger
* @property string $name * @property string $name
* @property array $options * @property array<string, string> $options
* @property string $usage * @property string $usage
*/ */
abstract class BaseCommand abstract class BaseCommand
{ {
@ -62,14 +62,14 @@ abstract class BaseCommand
/** /**
* the Command's options description * the Command's options description
* *
* @var array * @var array<string, string>
*/ */
protected $options = []; protected $options = [];
/** /**
* the Command's Arguments description * the Command's Arguments description
* *
* @var array * @var array<string, string>
*/ */
protected $arguments = []; protected $arguments = [];
@ -143,7 +143,7 @@ abstract class BaseCommand
} else { } else {
$usage = $this->name; $usage = $this->name;
if (! empty($this->arguments)) { if ($this->arguments !== []) {
$usage .= ' [arguments]'; $usage .= ' [arguments]';
} }
} }
@ -156,7 +156,7 @@ abstract class BaseCommand
CLI::write($this->setPad($this->description, 0, 0, 2)); CLI::write($this->setPad($this->description, 0, 0, 2));
} }
if (! empty($this->arguments)) { if ($this->arguments !== []) {
CLI::newLine(); CLI::newLine();
CLI::write(lang('CLI.helpArguments'), 'yellow'); CLI::write(lang('CLI.helpArguments'), 'yellow');
$length = max(array_map('strlen', array_keys($this->arguments))); $length = max(array_map('strlen', array_keys($this->arguments)));
@ -166,7 +166,7 @@ abstract class BaseCommand
} }
} }
if (! empty($this->options)) { if ($this->options !== []) {
CLI::newLine(); CLI::newLine();
CLI::write(lang('CLI.helpOptions'), 'yellow'); CLI::write(lang('CLI.helpOptions'), 'yellow');
$length = max(array_map('strlen', array_keys($this->options))); $length = max(array_map('strlen', array_keys($this->options)));

View File

@ -252,7 +252,7 @@ class CLI
unset($opts[0]); unset($opts[0]);
if (empty($opts)) { if ($opts === []) {
$extraOutput = $extraOutputDefault; $extraOutput = $extraOutputDefault;
} else { } else {
$extraOutput = '[' . $extraOutputDefault . ', ' . implode(', ', $opts) . ']'; $extraOutput = '[' . $extraOutputDefault . ', ' . implode(', ', $opts) . ']';
@ -324,7 +324,7 @@ class CLI
$opts = $options; $opts = $options;
unset($opts[0]); unset($opts[0]);
if (empty($opts)) { if ($opts === []) {
$extraOutput = $extraOutputDefault; $extraOutput = $extraOutputDefault;
} else { } else {
$optsKey = []; $optsKey = [];
@ -849,7 +849,7 @@ class CLI
*/ */
public static function wrap(?string $string = null, int $max = 0, int $padLeft = 0): string public static function wrap(?string $string = null, int $max = 0, int $padLeft = 0): string
{ {
if (empty($string)) { if ($string === null || $string === '') {
return ''; return '';
} }
@ -1000,7 +1000,7 @@ class CLI
*/ */
public static function getOptionString(bool $useLongOpts = false, bool $trim = false): string public static function getOptionString(bool $useLongOpts = false, bool $trim = false): string
{ {
if (empty(static::$options)) { if (static::$options === []) {
return ''; return '';
} }
@ -1041,7 +1041,7 @@ class CLI
$tableRows = []; $tableRows = [];
// We need only indexes and not keys // We need only indexes and not keys
if (! empty($thead)) { if ($thead !== []) {
$tableRows[] = array_values($thead); $tableRows[] = array_values($thead);
} }
@ -1115,7 +1115,7 @@ class CLI
$table .= '| ' . implode(' | ', $tableRows[$row]) . ' |' . PHP_EOL; $table .= '| ' . implode(' | ', $tableRows[$row]) . ' |' . PHP_EOL;
// Set the thead and table borders-bottom // Set the thead and table borders-bottom
if (($row === 0 && ! empty($thead)) || ($row + 1 === $totalRows)) { if (($row === 0 && $thead !== []) || ($row + 1 === $totalRows)) {
$table .= $cols . PHP_EOL; $table .= $cols . PHP_EOL;
} }
} }

View File

@ -40,6 +40,9 @@ class CacheFactory
/** /**
* Attempts to create the desired cache handler, based upon the * Attempts to create the desired cache handler, based upon the
* *
* @param non-empty-string|null $handler
* @param non-empty-string|null $backup
*
* @return CacheInterface * @return CacheInterface
*/ */
public static function getHandler(Cache $config, ?string $handler = null, ?string $backup = null) public static function getHandler(Cache $config, ?string $handler = null, ?string $backup = null)
@ -52,8 +55,8 @@ class CacheFactory
throw CacheException::forNoBackup(); throw CacheException::forNoBackup();
} }
$handler = ! empty($handler) ? $handler : $config->handler; $handler ??= $config->handler;
$backup = ! empty($backup) ? $backup : $config->backupHandler; $backup ??= $config->backupHandler;
if (! array_key_exists($handler, $config->validHandlers) || ! array_key_exists($backup, $config->validHandlers)) { if (! array_key_exists($handler, $config->validHandlers) || ! array_key_exists($backup, $config->validHandlers)) {
throw CacheException::forHandlerNotFound(); throw CacheException::forHandlerNotFound();

View File

@ -76,10 +76,9 @@ abstract class BaseHandler implements CacheInterface
/** /**
* Get an item from the cache, or execute the given Closure and store the result. * Get an item from the cache, or execute the given Closure and store the result.
* *
* @param string $key Cache item name * @param string $key Cache item name
* @param int $ttl Time to live * @param int $ttl Time to live
* @param Closure $callback Callback return value * @param Closure(): mixed $callback Callback return value
* @phpstan-param Closure(): mixed $callback
* *
* @return array|bool|float|int|object|string|null * @return array|bool|float|int|object|string|null
*/ */

View File

@ -150,8 +150,8 @@ class FileHandler extends BaseHandler
*/ */
public function increment(string $key, int $offset = 1) public function increment(string $key, int $offset = 1)
{ {
$key = static::validateKey($key, $this->prefix); $prefixedKey = static::validateKey($key, $this->prefix);
$tmp = $this->getItem($key); $tmp = $this->getItem($prefixedKey);
if ($tmp === false) { if ($tmp === false) {
$tmp = ['data' => 0, 'ttl' => 60]; $tmp = ['data' => 0, 'ttl' => 60];
@ -222,8 +222,7 @@ class FileHandler extends BaseHandler
* Does the heavy lifting of actually retrieving the file and * Does the heavy lifting of actually retrieving the file and
* verifying it's age. * verifying it's age.
* *
* @return array<string, mixed>|false * @return array{data: mixed, ttl: int, time: int}|false
* @phpstan-return array{data: mixed, ttl: int, time: int}|false
*/ */
protected function getItem(string $filename) protected function getItem(string $filename)
{ {

View File

@ -245,6 +245,7 @@ class RedisHandler extends BaseHandler
if ($value !== null) { if ($value !== null) {
$time = Time::now()->getTimestamp(); $time = Time::now()->getTimestamp();
$ttl = $this->redis->ttl(static::validateKey($key, $this->prefix)); $ttl = $this->redis->ttl(static::validateKey($key, $this->prefix));
assert(is_int($ttl));
return [ return [
'expire' => $ttl > 0 ? $time + $ttl : null, 'expire' => $ttl > 0 ? $time + $ttl : null,

View File

@ -54,7 +54,7 @@ class CodeIgniter
/** /**
* The current version of CodeIgniter Framework * The current version of CodeIgniter Framework
*/ */
public const CI_VERSION = '4.4.3'; public const CI_VERSION = '4.4.5';
/** /**
* App startup time. * App startup time.
@ -108,7 +108,7 @@ class CodeIgniter
/** /**
* Controller to use. * Controller to use.
* *
* @var Closure|string * @var (Closure(mixed...): ResponseInterface|string)|string|null
*/ */
protected $controller; protected $controller;
@ -138,7 +138,7 @@ class CodeIgniter
/** /**
* Request path to use. * Request path to use.
* *
* @var string * @var string|null
* *
* @deprecated No longer used. * @deprecated No longer used.
*/ */
@ -302,7 +302,7 @@ class CodeIgniter
Kint::$display_called_from = $config->displayCalledFrom; Kint::$display_called_from = $config->displayCalledFrom;
Kint::$expanded = $config->expanded; Kint::$expanded = $config->expanded;
if (! empty($config->plugins) && is_array($config->plugins)) { if (isset($config->plugins) && is_array($config->plugins)) {
Kint::$plugins = $config->plugins; Kint::$plugins = $config->plugins;
} }
@ -315,10 +315,10 @@ class CodeIgniter
RichRenderer::$theme = $config->richTheme; RichRenderer::$theme = $config->richTheme;
RichRenderer::$folder = $config->richFolder; RichRenderer::$folder = $config->richFolder;
RichRenderer::$sort = $config->richSort; RichRenderer::$sort = $config->richSort;
if (! empty($config->richObjectPlugins) && is_array($config->richObjectPlugins)) { if (isset($config->richObjectPlugins) && is_array($config->richObjectPlugins)) {
RichRenderer::$value_plugins = $config->richObjectPlugins; RichRenderer::$value_plugins = $config->richObjectPlugins;
} }
if (! empty($config->richTabPlugins) && is_array($config->richTabPlugins)) { if (isset($config->richTabPlugins) && is_array($config->richTabPlugins)) {
RichRenderer::$tab_plugins = $config->richTabPlugins; RichRenderer::$tab_plugins = $config->richTabPlugins;
} }
@ -848,11 +848,10 @@ class CodeIgniter
*/ */
protected function determinePath() protected function determinePath()
{ {
if (! empty($this->path)) { return $this->path ??
return $this->path; (method_exists($this->request, 'getPath')
} ? $this->request->getPath()
: $this->request->getUri()->getPath());
return method_exists($this->request, 'getPath') ? $this->request->getPath() : $this->request->getUri()->getPath();
} }
/** /**
@ -892,7 +891,7 @@ class CodeIgniter
} }
// No controller specified - we don't know what to do now. // No controller specified - we don't know what to do now.
if (empty($this->controller)) { if (! isset($this->controller)) {
throw PageNotFoundException::forEmptyController(); throw PageNotFoundException::forEmptyController();
} }
@ -1091,7 +1090,7 @@ class CodeIgniter
$method = $this->request->getPost('_method'); $method = $this->request->getPost('_method');
if (empty($method)) { if ($method === null) {
return; return;
} }

View File

@ -52,7 +52,7 @@ class ClearCache extends BaseCommand
/** /**
* the Command's Arguments * the Command's Arguments
* *
* @var array * @var array<string, string>
*/ */
protected $arguments = [ protected $arguments = [
'driver' => 'The cache driver to use', 'driver' => 'The cache driver to use',

View File

@ -53,7 +53,7 @@ class Migrate extends BaseCommand
/** /**
* the Command's Options * the Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'-n' => 'Set migration namespace', '-n' => 'Set migration namespace',

View File

@ -52,7 +52,7 @@ class MigrateRefresh extends BaseCommand
/** /**
* the Command's Options * the Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'-n' => 'Set migration namespace', '-n' => 'Set migration namespace',

View File

@ -54,7 +54,7 @@ class MigrateRollback extends BaseCommand
/** /**
* the Command's Options * the Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'-b' => 'Specify a batch to roll back to; e.g. "3" to return to batch #3', '-b' => 'Specify a batch to roll back to; e.g. "3" to return to batch #3',

View File

@ -84,7 +84,7 @@ class ShowTableInfo extends BaseCommand
]; ];
/** /**
* @phpstan-var list<list<string|int>> Table Data. * @var list<list<int|string>> Table Data.
*/ */
private array $tbody; private array $tbody;
@ -125,7 +125,7 @@ class ShowTableInfo extends BaseCommand
$limitRows = (int) ($params['limit-rows'] ?? 10); $limitRows = (int) ($params['limit-rows'] ?? 10);
$limitFieldValue = (int) ($params['limit-field-value'] ?? 15); $limitFieldValue = (int) ($params['limit-field-value'] ?? 15);
if (! in_array($tableName, $tables, true)) { while (! in_array($tableName, $tables, true)) {
$tableNameNo = CLI::promptByKey( $tableNameNo = CLI::promptByKey(
['Here is the list of your database tables:', 'Which table do you want to see?'], ['Here is the list of your database tables:', 'Which table do you want to see?'],
$tables, $tables,
@ -133,7 +133,7 @@ class ShowTableInfo extends BaseCommand
); );
CLI::newLine(); CLI::newLine();
$tableName = $tables[$tableNameNo]; $tableName = $tables[$tableNameNo] ?? null;
} }
if (array_key_exists('metadata', $params)) { if (array_key_exists('metadata', $params)) {

View File

@ -52,7 +52,7 @@ class GenerateKey extends BaseCommand
/** /**
* The command's options * The command's options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'--force' => 'Force overwrite existing key in `.env` file.', '--force' => 'Force overwrite existing key in `.env` file.',

View File

@ -40,7 +40,7 @@ class CellGenerator extends BaseCommand
* *
* @var string * @var string
*/ */
protected $description = 'Generates a new Cell file and its view.'; protected $description = 'Generates a new Controlled Cell file and its view.';
/** /**
* The Command's Usage * The Command's Usage
@ -52,16 +52,16 @@ class CellGenerator extends BaseCommand
/** /**
* The Command's Arguments * The Command's Arguments
* *
* @var array * @var array<string, string>
*/ */
protected $arguments = [ protected $arguments = [
'name' => 'The cell class name.', 'name' => 'The Controlled Cell class name.',
]; ];
/** /**
* The Command's Options * The Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".', '--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',

View File

@ -53,7 +53,7 @@ class CommandGenerator extends BaseCommand
/** /**
* The Command's Arguments * The Command's Arguments
* *
* @var array * @var array<string, string>
*/ */
protected $arguments = [ protected $arguments = [
'name' => 'The command class name.', 'name' => 'The command class name.',
@ -62,12 +62,12 @@ class CommandGenerator extends BaseCommand
/** /**
* The Command's Options * The Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'--command' => 'The command name. Default: "command:name"', '--command' => 'The command name. Default: "command:name"',
'--type' => 'The command type. Options [basic, generator]. Default: "basic".', '--type' => 'The command type. Options [basic, generator]. Default: "basic".',
'--group' => 'The command group. Default: [basic -> "CodeIgniter", generator -> "Generators"].', '--group' => 'The command group. Default: [basic -> "App", generator -> "Generators"].',
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".', '--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',
'--suffix' => 'Append the component title to the class name (e.g. User => UserCommand).', '--suffix' => 'Append the component title to the class name (e.g. User => UserCommand).',
'--force' => 'Force overwrite existing file.', '--force' => 'Force overwrite existing file.',
@ -106,7 +106,7 @@ class CommandGenerator extends BaseCommand
} }
if (! is_string($group)) { if (! is_string($group)) {
$group = $type === 'generator' ? 'Generators' : 'CodeIgniter'; $group = $type === 'generator' ? 'Generators' : 'App';
} }
return $this->parseTemplate( return $this->parseTemplate(

View File

@ -52,7 +52,7 @@ class ConfigGenerator extends BaseCommand
/** /**
* The Command's Arguments * The Command's Arguments
* *
* @var array * @var array<string, string>
*/ */
protected $arguments = [ protected $arguments = [
'name' => 'The config class name.', 'name' => 'The config class name.',
@ -61,7 +61,7 @@ class ConfigGenerator extends BaseCommand
/** /**
* The Command's Options * The Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".', '--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',

View File

@ -56,7 +56,7 @@ class ControllerGenerator extends BaseCommand
/** /**
* The Command's Arguments * The Command's Arguments
* *
* @var array * @var array<string, string>
*/ */
protected $arguments = [ protected $arguments = [
'name' => 'The controller class name.', 'name' => 'The controller class name.',
@ -65,7 +65,7 @@ class ControllerGenerator extends BaseCommand
/** /**
* The Command's Options * The Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'--bare' => 'Extends from CodeIgniter\Controller instead of BaseController.', '--bare' => 'Extends from CodeIgniter\Controller instead of BaseController.',

View File

@ -52,7 +52,7 @@ class EntityGenerator extends BaseCommand
/** /**
* The Command's Arguments * The Command's Arguments
* *
* @var array * @var array<string, string>
*/ */
protected $arguments = [ protected $arguments = [
'name' => 'The entity class name.', 'name' => 'The entity class name.',
@ -61,7 +61,7 @@ class EntityGenerator extends BaseCommand
/** /**
* The Command's Options * The Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".', '--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',

View File

@ -52,7 +52,7 @@ class FilterGenerator extends BaseCommand
/** /**
* The Command's Arguments * The Command's Arguments
* *
* @var array * @var array<string, string>
*/ */
protected $arguments = [ protected $arguments = [
'name' => 'The filter class name.', 'name' => 'The filter class name.',
@ -61,7 +61,7 @@ class FilterGenerator extends BaseCommand
/** /**
* The Command's Options * The Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".', '--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',

View File

@ -55,7 +55,7 @@ class MigrateCreate extends BaseCommand
/** /**
* The Command's arguments. * The Command's arguments.
* *
* @var array * @var array<string, string>
*/ */
protected $arguments = [ protected $arguments = [
'name' => 'The migration file name.', 'name' => 'The migration file name.',
@ -64,7 +64,7 @@ class MigrateCreate extends BaseCommand
/** /**
* The Command's options. * The Command's options.
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'--namespace' => 'Set root namespace. Defaults to APP_NAMESPACE', '--namespace' => 'Set root namespace. Defaults to APP_NAMESPACE',

View File

@ -56,7 +56,7 @@ class MigrationGenerator extends BaseCommand
/** /**
* The Command's Arguments * The Command's Arguments
* *
* @var array * @var array<string, string>
*/ */
protected $arguments = [ protected $arguments = [
'name' => 'The migration class name.', 'name' => 'The migration class name.',
@ -65,7 +65,7 @@ class MigrationGenerator extends BaseCommand
/** /**
* The Command's Options * The Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'--session' => 'Generates the migration file for database sessions.', '--session' => 'Generates the migration file for database sessions.',

View File

@ -53,7 +53,7 @@ class ModelGenerator extends BaseCommand
/** /**
* The Command's Arguments * The Command's Arguments
* *
* @var array * @var array<string, string>
*/ */
protected $arguments = [ protected $arguments = [
'name' => 'The model class name.', 'name' => 'The model class name.',
@ -62,7 +62,7 @@ class ModelGenerator extends BaseCommand
/** /**
* The Command's Options * The Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'--table' => 'Supply a table name. Default: "the lowercased plural of the class name".', '--table' => 'Supply a table name. Default: "the lowercased plural of the class name".',

View File

@ -53,7 +53,7 @@ class ScaffoldGenerator extends BaseCommand
/** /**
* The Command's Arguments * The Command's Arguments
* *
* @var array * @var array<string, string>
*/ */
protected $arguments = [ protected $arguments = [
'name' => 'The class name', 'name' => 'The class name',
@ -62,7 +62,7 @@ class ScaffoldGenerator extends BaseCommand
/** /**
* The Command's Options * The Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'--bare' => 'Add the "--bare" option to controller component.', '--bare' => 'Add the "--bare" option to controller component.',

View File

@ -52,7 +52,7 @@ class SeederGenerator extends BaseCommand
/** /**
* The Command's Arguments * The Command's Arguments
* *
* @var array * @var array<string, string>
*/ */
protected $arguments = [ protected $arguments = [
'name' => 'The seeder class name.', 'name' => 'The seeder class name.',
@ -61,7 +61,7 @@ class SeederGenerator extends BaseCommand
/** /**
* The Command's Options * The Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".', '--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',

View File

@ -59,7 +59,7 @@ class SessionMigrationGenerator extends BaseCommand
/** /**
* The Command's Options * The Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'-t' => 'Supply a table name.', '-t' => 'Supply a table name.',

View File

@ -52,7 +52,7 @@ class ValidationGenerator extends BaseCommand
/** /**
* The Command's Arguments * The Command's Arguments
* *
* @var array * @var array<string, string>
*/ */
protected $arguments = [ protected $arguments = [
'name' => 'The validation class name.', 'name' => 'The validation class name.',
@ -61,7 +61,7 @@ class ValidationGenerator extends BaseCommand
/** /**
* The Command's Options * The Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".', '--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".',

View File

@ -3,6 +3,7 @@
namespace {namespace}; namespace {namespace};
use {useStatement}; use {useStatement};
use CodeIgniter\HTTP\ResponseInterface;
class {class} extends {extends} class {class} extends {extends}
{ {
@ -10,7 +11,7 @@ class {class} extends {extends}
/** /**
* Return an array of resource objects, themselves in array format * Return an array of resource objects, themselves in array format
* *
* @return mixed * @return ResponseInterface
*/ */
public function index() public function index()
{ {
@ -20,7 +21,7 @@ class {class} extends {extends}
/** /**
* Return the properties of a resource object * Return the properties of a resource object
* *
* @return mixed * @return ResponseInterface
*/ */
public function show($id = null) public function show($id = null)
{ {
@ -30,7 +31,7 @@ class {class} extends {extends}
/** /**
* Return a new resource object, with default properties * Return a new resource object, with default properties
* *
* @return mixed * @return ResponseInterface
*/ */
public function new() public function new()
{ {
@ -40,7 +41,7 @@ class {class} extends {extends}
/** /**
* Create a new resource object, from "posted" parameters * Create a new resource object, from "posted" parameters
* *
* @return mixed * @return ResponseInterface
*/ */
public function create() public function create()
{ {
@ -50,7 +51,7 @@ class {class} extends {extends}
/** /**
* Return the editable properties of a resource object * Return the editable properties of a resource object
* *
* @return mixed * @return ResponseInterface
*/ */
public function edit($id = null) public function edit($id = null)
{ {
@ -60,7 +61,7 @@ class {class} extends {extends}
/** /**
* Add or update a model resource, from "posted" properties * Add or update a model resource, from "posted" properties
* *
* @return mixed * @return ResponseInterface
*/ */
public function update($id = null) public function update($id = null)
{ {
@ -70,7 +71,7 @@ class {class} extends {extends}
/** /**
* Delete the designated resource object from the model * Delete the designated resource object from the model
* *
* @return mixed * @return ResponseInterface
*/ */
public function delete($id = null) public function delete($id = null)
{ {
@ -80,7 +81,7 @@ class {class} extends {extends}
/** /**
* Present a view of resource objects * Present a view of resource objects
* *
* @return mixed * @return ResponseInterface
*/ */
public function index() public function index()
{ {
@ -90,9 +91,9 @@ class {class} extends {extends}
/** /**
* Present a view to present a specific resource object * Present a view to present a specific resource object
* *
* @param mixed $id * @param string $id
* *
* @return mixed * @return ResponseInterface
*/ */
public function show($id = null) public function show($id = null)
{ {

View File

@ -21,7 +21,7 @@ class {class} implements FilterInterface
* @param RequestInterface $request * @param RequestInterface $request
* @param array|null $arguments * @param array|null $arguments
* *
* @return mixed * @return RequestInterface|ResponseInterface|string|void
*/ */
public function before(RequestInterface $request, $arguments = null) public function before(RequestInterface $request, $arguments = null)
{ {
@ -38,7 +38,7 @@ class {class} implements FilterInterface
* @param ResponseInterface $response * @param ResponseInterface $response
* @param array|null $arguments * @param array|null $arguments
* *
* @return mixed * @return ResponseInterface|void
*/ */
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{ {

View File

@ -17,6 +17,8 @@ class {class} extends Model
protected $protectFields = true; protected $protectFields = true;
protected $allowedFields = []; protected $allowedFields = [];
protected bool $allowEmptyInserts = false;
// Dates // Dates
protected $useTimestamps = false; protected $useTimestamps = false;
protected $dateFormat = 'datetime'; protected $dateFormat = 'datetime';

View File

@ -53,7 +53,7 @@ class Help extends BaseCommand
/** /**
* the Command's Arguments * the Command's Arguments
* *
* @var array * @var array<string, string>
*/ */
protected $arguments = [ protected $arguments = [
'command_name' => 'The command name [default: "help"]', 'command_name' => 'The command name [default: "help"]',
@ -62,7 +62,7 @@ class Help extends BaseCommand
/** /**
* the Command's Options * the Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = []; protected $options = [];

View File

@ -51,7 +51,7 @@ class ClearLogs extends BaseCommand
/** /**
* The Command's options * The Command's options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'--force' => 'Force delete of all logs files without prompting.', '--force' => 'Force delete of all logs files without prompting.',

View File

@ -54,14 +54,14 @@ class ListCommands extends BaseCommand
/** /**
* the Command's Arguments * the Command's Arguments
* *
* @var array * @var array<string, string>
*/ */
protected $arguments = []; protected $arguments = [];
/** /**
* the Command's Options * the Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'--simple' => 'Prints a list of the commands with no other info', '--simple' => 'Prints a list of the commands with no other info',

View File

@ -54,7 +54,7 @@ class Serve extends BaseCommand
/** /**
* Arguments * Arguments
* *
* @var array * @var array<string, string>
*/ */
protected $arguments = []; protected $arguments = [];
@ -75,7 +75,7 @@ class Serve extends BaseCommand
/** /**
* Options * Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'--php' => 'The PHP Binary [default: "PHP_BINARY"]', '--php' => 'The PHP Binary [default: "PHP_BINARY"]',

View File

@ -62,7 +62,7 @@ final class Environment extends BaseCommand
/** /**
* The Command's options * The Command's options
* *
* @var array * @var array<string, string>
*/ */
protected $options = []; protected $options = [];

View File

@ -57,13 +57,13 @@ class FilterCheck extends BaseCommand
*/ */
protected $arguments = [ protected $arguments = [
'method' => 'The HTTP method. get, post, put, etc.', 'method' => 'The HTTP method. get, post, put, etc.',
'route' => 'The route (URI path) to check filtes.', 'route' => 'The route (URI path) to check filters.',
]; ];
/** /**
* the Command's Options * the Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = []; protected $options = [];

View File

@ -57,14 +57,14 @@ class Namespaces extends BaseCommand
/** /**
* the Command's Arguments * the Command's Arguments
* *
* @var array * @var array<string, string>
*/ */
protected $arguments = []; protected $arguments = [];
/** /**
* the Command's Options * the Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = [ protected $options = [
'-c' => 'Show only CodeIgniter config namespaces.', '-c' => 'Show only CodeIgniter config namespaces.',

View File

@ -63,7 +63,7 @@ class Publish extends BaseCommand
/** /**
* the Command's Options * the Command's Options
* *
* @var array * @var array<string, string>
*/ */
protected $options = []; protected $options = [];

View File

@ -61,7 +61,7 @@ class Routes extends BaseCommand
/** /**
* the Command's Arguments * the Command's Arguments
* *
* @var array * @var array<string, string>
*/ */
protected $arguments = []; protected $arguments = [];

View File

@ -37,8 +37,7 @@ final class AutoRouteCollector
} }
/** /**
* @return array<int, array<int, string>> * @return list<list<string>>
* @phpstan-return list<list<string>>
*/ */
public function get(): array public function get(): array
{ {

View File

@ -62,8 +62,7 @@ final class AutoRouteCollector
} }
/** /**
* @return array<int, array<int, string>> * @return list<list<string>>
* @phpstan-return list<list<string>>
*/ */
public function get(): array public function get(): array
{ {

View File

@ -11,6 +11,7 @@
namespace CodeIgniter\Commands\Utilities\Routes\AutoRouterImproved; namespace CodeIgniter\Commands\Utilities\Routes\AutoRouterImproved;
use Config\Routing;
use ReflectionClass; use ReflectionClass;
use ReflectionMethod; use ReflectionMethod;
@ -27,11 +28,12 @@ final class ControllerMethodReader
private string $namespace; private string $namespace;
/** /**
* @var array<int, string> * @var list<string>
* @phpstan-var list<string>
*/ */
private array $httpMethods; private array $httpMethods;
private bool $translateURIDashes;
/** /**
* @param string $namespace the default namespace * @param string $namespace the default namespace
*/ */
@ -39,15 +41,17 @@ final class ControllerMethodReader
{ {
$this->namespace = $namespace; $this->namespace = $namespace;
$this->httpMethods = $httpMethods; $this->httpMethods = $httpMethods;
$config = config(Routing::class);
$this->translateURIDashes = $config->translateURIDashes;
} }
/** /**
* Returns found route info in the controller. * Returns found route info in the controller.
* *
* @phpstan-param class-string $class * @param class-string $class
* *
* @return array<int, array<string, array|string>> * @return list<array<string, array|string>>
* @phpstan-return list<array<string, string|array>>
*/ */
public function read(string $class, string $defaultController = 'Home', string $defaultMethod = 'index'): array public function read(string $class, string $defaultController = 'Home', string $defaultMethod = 'index'): array
{ {
@ -69,7 +73,7 @@ final class ControllerMethodReader
foreach ($this->httpMethods as $httpVerb) { foreach ($this->httpMethods as $httpVerb) {
if (strpos($methodName, $httpVerb) === 0) { if (strpos($methodName, $httpVerb) === 0) {
// Remove HTTP verb prefix. // Remove HTTP verb prefix.
$methodInUri = lcfirst(substr($methodName, strlen($httpVerb))); $methodInUri = $this->getUriByMethod($httpVerb, $methodName);
// Check if it is the default method. // Check if it is the default method.
if ($methodInUri === $defaultMethod) { if ($methodInUri === $defaultMethod) {
@ -154,7 +158,7 @@ final class ControllerMethodReader
} }
/** /**
* @phpstan-param class-string $classname * @param class-string $classname
* *
* @return string URI path part from the folder(s) and controller * @return string URI path part from the folder(s) and controller
*/ */
@ -173,13 +177,33 @@ final class ControllerMethodReader
$classPath .= lcfirst($part) . '/'; $classPath .= lcfirst($part) . '/';
} }
return rtrim($classPath, '/'); $classUri = rtrim($classPath, '/');
if ($this->translateURIDashes) {
$classUri = str_replace('_', '-', $classUri);
}
return $classUri;
}
/**
* @return string URI path part from the method
*/
private function getUriByMethod(string $httpVerb, string $methodName): string
{
$methodUri = lcfirst(substr($methodName, strlen($httpVerb)));
if ($this->translateURIDashes) {
$methodUri = str_replace('_', '-', $methodUri);
}
return $methodUri;
} }
/** /**
* Gets a route for the default controller. * Gets a route for the default controller.
* *
* @phpstan-return list<array> * @return list<array>
*/ */
private function getRouteForDefaultController( private function getRouteForDefaultController(
string $classShortname, string $classShortname,

View File

@ -38,8 +38,7 @@ final class ControllerFinder
} }
/** /**
* @return string[] * @return class-string[]
* @phpstan-return class-string[]
*/ */
public function find(): array public function find(): array
{ {
@ -66,7 +65,7 @@ final class ControllerFinder
$classnameOrEmpty = $this->locator->getClassname($file); $classnameOrEmpty = $this->locator->getClassname($file);
if ($classnameOrEmpty !== '') { if ($classnameOrEmpty !== '') {
/** @phpstan-var class-string $classname */ /** @var class-string $classname */
$classname = $classnameOrEmpty; $classname = $classnameOrEmpty;
$classes[] = $classname; $classes[] = $classname;

View File

@ -35,10 +35,9 @@ final class ControllerMethodReader
} }
/** /**
* @phpstan-param class-string $class * @param class-string $class
* *
* @return array<int, array{route: string, handler: string}> * @return list<array{route: string, handler: string}>
* @phpstan-return list<array{route: string, handler: string}>
*/ */
public function read(string $class, string $defaultController = 'Home', string $defaultMethod = 'index'): array public function read(string $class, string $defaultController = 'Home', string $defaultMethod = 'index'): array
{ {
@ -128,7 +127,7 @@ final class ControllerMethodReader
} }
/** /**
* @phpstan-param class-string $classname * @param class-string $classname
* *
* @return string URI path part from the folder(s) and controller * @return string URI path part from the folder(s) and controller
*/ */

View File

@ -13,6 +13,7 @@ namespace CodeIgniter\Commands\Utilities\Routes;
use CodeIgniter\Config\Services; use CodeIgniter\Config\Services;
use CodeIgniter\Router\RouteCollection; use CodeIgniter\Router\RouteCollection;
use Config\App;
/** /**
* Generate a sample URI path from route key regex. * Generate a sample URI path from route key regex.
@ -51,6 +52,14 @@ final class SampleURIGenerator
{ {
$sampleUri = $routeKey; $sampleUri = $routeKey;
if (strpos($routeKey, '{locale}') !== false) {
$sampleUri = str_replace(
'{locale}',
config(App::class)->defaultLocale,
$routeKey
);
}
foreach ($this->routes->getPlaceholders() as $placeholder => $regex) { foreach ($this->routes->getPlaceholders() as $placeholder => $regex) {
$sample = $this->samples[$placeholder] ?? '::unknown::'; $sample = $this->samples[$placeholder] ?? '::unknown::';

View File

@ -25,9 +25,7 @@ use CodeIgniter\HTTP\Exceptions\RedirectException;
use CodeIgniter\HTTP\IncomingRequest; use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\Response;
use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\HTTP\URI;
use CodeIgniter\Model; use CodeIgniter\Model;
use CodeIgniter\Session\Session; use CodeIgniter\Session\Session;
use CodeIgniter\Test\TestLogger; use CodeIgniter\Test\TestLogger;
@ -290,20 +288,24 @@ if (! function_exists('csrf_hash')) {
if (! function_exists('csrf_field')) { if (! function_exists('csrf_field')) {
/** /**
* Generates a hidden input field for use within manually generated forms. * Generates a hidden input field for use within manually generated forms.
*
* @param non-empty-string|null $id
*/ */
function csrf_field(?string $id = null): string function csrf_field(?string $id = null): string
{ {
return '<input type="hidden"' . (! empty($id) ? ' id="' . esc($id, 'attr') . '"' : '') . ' name="' . csrf_token() . '" value="' . csrf_hash() . '"' . _solidus() . '>'; return '<input type="hidden"' . ($id !== null ? ' id="' . esc($id, 'attr') . '"' : '') . ' name="' . csrf_token() . '" value="' . csrf_hash() . '"' . _solidus() . '>';
} }
} }
if (! function_exists('csrf_meta')) { if (! function_exists('csrf_meta')) {
/** /**
* Generates a meta tag for use within javascript calls. * Generates a meta tag for use within javascript calls.
*
* @param non-empty-string|null $id
*/ */
function csrf_meta(?string $id = null): string function csrf_meta(?string $id = null): string
{ {
return '<meta' . (! empty($id) ? ' id="' . esc($id, 'attr') . '"' : '') . ' name="' . csrf_header() . '" content="' . csrf_hash() . '"' . _solidus() . '>'; return '<meta' . ($id !== null ? ' id="' . esc($id, 'attr') . '"' : '') . ' name="' . csrf_header() . '" content="' . csrf_hash() . '"' . _solidus() . '>';
} }
} }
@ -502,27 +504,11 @@ if (! function_exists('force_https')) {
Services::session()->regenerate(); // @codeCoverageIgnore Services::session()->regenerate(); // @codeCoverageIgnore
} }
$baseURL = config(App::class)->baseURL; $uri = $request->getUri()->withScheme('https');
if (strpos($baseURL, 'https://') === 0) {
$authority = substr($baseURL, strlen('https://'));
} elseif (strpos($baseURL, 'http://') === 0) {
$authority = substr($baseURL, strlen('http://'));
} else {
$authority = $baseURL;
}
$uri = URI::createURIString(
'https',
$authority,
$request->getUri()->getPath(), // Absolute URIs should use a "/" for an empty path
$request->getUri()->getQuery(),
$request->getUri()->getFragment()
);
// Set an HSTS header // Set an HSTS header
$response->setHeader('Strict-Transport-Security', 'max-age=' . $duration) $response->setHeader('Strict-Transport-Security', 'max-age=' . $duration)
->redirect($uri) ->redirect((string) $uri)
->setStatusCode(307) ->setStatusCode(307)
->setBody('') ->setBody('')
->getCookieStore() ->getCookieStore()
@ -755,7 +741,7 @@ if (! function_exists('lang')) {
* A convenience method to translate a string or array of them and format * A convenience method to translate a string or array of them and format
* the result with the intl extension's MessageFormatter. * the result with the intl extension's MessageFormatter.
* *
* @return string * @return list<string>|string
*/ */
function lang(string $line, array $args = [], ?string $locale = null) function lang(string $line, array $args = [], ?string $locale = null)
{ {
@ -868,13 +854,13 @@ if (! function_exists('redirect')) {
* *
* If more control is needed, you must use $response->redirect explicitly. * If more control is needed, you must use $response->redirect explicitly.
* *
* @param string|null $route Route name or Controller::method * @param non-empty-string|null $route Route name or Controller::method
*/ */
function redirect(?string $route = null): RedirectResponse function redirect(?string $route = null): RedirectResponse
{ {
$response = Services::redirectresponse(null, true); $response = Services::redirectresponse(null, true);
if (! empty($route)) { if ($route !== null) {
return $response->route($route); return $response->route($route);
} }
@ -1139,15 +1125,17 @@ if (! function_exists('timer')) {
* returns its return value if any. * returns its return value if any.
* Otherwise will start or stop the timer intelligently. * Otherwise will start or stop the timer intelligently.
* *
* @phpstan-param (callable(): mixed)|null $callable * @param non-empty-string|null $name
* @param (callable(): mixed)|null $callable
* *
* @return Timer * @return mixed|Timer
* @phpstan-return ($name is null ? Timer : ($callable is (callable(): mixed) ? mixed : Timer))
*/ */
function timer(?string $name = null, ?callable $callable = null) function timer(?string $name = null, ?callable $callable = null)
{ {
$timer = Services::timer(); $timer = Services::timer();
if (empty($name)) { if ($name === null) {
return $timer; return $timer;
} }

View File

@ -45,8 +45,7 @@ class AutoloadConfig
* but this should be done prior to creating any namespaced classes, * but this should be done prior to creating any namespaced classes,
* else you will need to modify all of those classes for this to work. * else you will need to modify all of those classes for this to work.
* *
* @var array<string, array<int, string>|string> * @var array<string, list<string>|string>
* @phpstan-var array<string, string|list<string>>
*/ */
public $psr4 = []; public $psr4 = [];
@ -72,8 +71,7 @@ class AutoloadConfig
* that will be autoloaded. This can be useful for bootstrap operations * that will be autoloaded. This can be useful for bootstrap operations
* or for loading functions. * or for loading functions.
* *
* @var array<int, string> * @var list<string>
* @phpstan-var list<string>
*/ */
public $files = []; public $files = [];

View File

@ -343,7 +343,7 @@ class BaseService
foreach ($files as $file) { foreach ($files as $file) {
$classname = $locator->getClassname($file); $classname = $locator->getClassname($file);
if (! in_array($classname, [Services::class], true)) { if ($classname !== Services::class) {
static::$services[] = new $classname(); static::$services[] = new $classname();
} }
} }

View File

@ -62,8 +62,7 @@ class Factories
* *
* [component => [alias => FQCN]] * [component => [alias => FQCN]]
* *
* @var array<string, array<string, string>> * @var array<string, array<string, class-string>>
* @phpstan-var array<string, array<string, class-string>>
*/ */
protected static $aliases = []; protected static $aliases = [];
@ -76,8 +75,7 @@ class Factories
* *
* [component => [FQCN => instance]] * [component => [FQCN => instance]]
* *
* @var array<string, array<string, object>> * @var array<string, array<class-string, object>>
* @phpstan-var array<string, array<class-string, object>>
*/ */
protected static $instances = []; protected static $instances = [];
@ -93,10 +91,9 @@ class Factories
/** /**
* Define the class to load. You can *override* the concrete class. * Define the class to load. You can *override* the concrete class.
* *
* @param string $component Lowercase, plural component name * @param string $component Lowercase, plural component name
* @param string $alias Class alias. See the $aliases property. * @param string $alias Class alias. See the $aliases property.
* @param string $classname FQCN to be loaded * @param class-string $classname FQCN to be loaded
* @phpstan-param class-string $classname FQCN to be loaded
*/ */
public static function define(string $component, string $alias, string $classname): void public static function define(string $component, string $alias, string $classname): void
{ {

View File

@ -26,7 +26,7 @@ class Publisher extends BaseConfig
* result in a PublisherException. Files that do no fit the * result in a PublisherException. Files that do no fit the
* pattern will cause copy/merge to fail. * pattern will cause copy/merge to fail.
* *
* @var array<string,string> * @var array<string, string>
*/ */
public $restrictions = [ public $restrictions = [
ROOTPATH => '*', ROOTPATH => '*',

View File

@ -24,7 +24,7 @@ class Routing extends BaseConfig
* Default: APPPATH . 'Config/Routes.php' * Default: APPPATH . 'Config/Routes.php'
*/ */
public array $routeFiles = [ public array $routeFiles = [
APPPATH . 'Routes.php', APPPATH . 'Config/Routes.php',
]; ];
/** /**
@ -95,4 +95,17 @@ class Routing extends BaseConfig
* Default: false * Default: false
*/ */
public bool $prioritize = false; public bool $prioritize = false;
/**
* Map of URI segments and namespaces. For Auto Routing (Improved).
*
* The key is the first URI segment. The value is the controller namespace.
* E.g.,
* [
* 'blog' => 'Acme\Blog\Controllers',
* ]
*
* @var array [ uri_segment => namespace ]
*/
public array $moduleRoutes = [];
} }

View File

@ -16,8 +16,8 @@ use CodeIgniter\View\ViewDecoratorInterface;
/** /**
* View configuration * View configuration
* *
* @phpstan-type ParserCallable (callable(mixed): mixed) * @phpstan-type parser_callable (callable(mixed): mixed)
* @phpstan-type ParserCallableString (callable(mixed): mixed)&string * @phpstan-type parser_callable_string (callable(mixed): mixed)&string
*/ */
class View extends BaseConfig class View extends BaseConfig
{ {
@ -40,7 +40,7 @@ class View extends BaseConfig
* @psalm-suppress UndefinedDocblockClass * @psalm-suppress UndefinedDocblockClass
* *
* @var array<string, string> * @var array<string, string>
* @phpstan-var array<string, ParserCallableString> * @phpstan-var array<string, parser_callable_string>
*/ */
public $filters = []; public $filters = [];
@ -52,7 +52,7 @@ class View extends BaseConfig
* @psalm-suppress UndefinedDocblockClass * @psalm-suppress UndefinedDocblockClass
* *
* @var array<string, array<string>|callable|string> * @var array<string, array<string>|callable|string>
* @phpstan-var array<string, array<ParserCallableString>|ParserCallableString|ParserCallable> * @phpstan-var array<string, array<parser_callable_string>|parser_callable_string|parser_callable>
*/ */
public $plugins = []; public $plugins = [];
@ -60,7 +60,7 @@ class View extends BaseConfig
* Built-in View filters. * Built-in View filters.
* *
* @var array<string, string> * @var array<string, string>
* @phpstan-var array<string, ParserCallableString> * @phpstan-var array<string, parser_callable_string>
*/ */
protected $coreFilters = [ protected $coreFilters = [
'abs' => '\abs', 'abs' => '\abs',
@ -90,7 +90,7 @@ class View extends BaseConfig
* Built-in View plugins. * Built-in View plugins.
* *
* @var array<string, array<string>|callable|string> * @var array<string, array<string>|callable|string>
* @phpstan-var array<string, array<ParserCallableString>|ParserCallableString|ParserCallable> * @phpstan-var array<string, array<parser_callable_string>|parser_callable_string|parser_callable>
*/ */
protected $corePlugins = [ protected $corePlugins = [
'csp_script_nonce' => '\CodeIgniter\View\Plugins::cspScriptNonce', 'csp_script_nonce' => '\CodeIgniter\View\Plugins::cspScriptNonce',

View File

@ -65,7 +65,7 @@ class Controller
/** /**
* Once validation has been run, will hold the Validation instance. * Once validation has been run, will hold the Validation instance.
* *
* @var ValidationInterface * @var ValidationInterface|null
*/ */
protected $validator; protected $validator;
@ -132,7 +132,7 @@ class Controller
*/ */
protected function loadHelpers() protected function loadHelpers()
{ {
if (empty($this->helpers)) { if ($this->helpers === []) {
return; return;
} }

View File

@ -88,8 +88,7 @@ class BaseBuilder
* QB keys * QB keys
* list of column names. * list of column names.
* *
* @var string[] * @var list<string>
* @phpstan-var list<string>
*/ */
protected $QBKeys = []; protected $QBKeys = [];
@ -131,8 +130,7 @@ class BaseBuilder
/** /**
* QB data sets * QB data sets
* *
* @var array[]|string[] * @var array<string, string>|list<list<int|string>>
* @phpstan-var array<string, string>|list<list<string|int>>
*/ */
protected $QBSet = []; protected $QBSet = [];
@ -164,16 +162,18 @@ class BaseBuilder
* Holds additional options and data used to render SQL * Holds additional options and data used to render SQL
* and is reset by resetWrite() * and is reset by resetWrite()
* *
* @phpstan-var array{ * @var array{
* updateFieldsAdditional?: array, * updateFieldsAdditional?: array,
* tableIdentity?: string, * tableIdentity?: string,
* updateFields?: array, * updateFields?: array,
* constraints?: array, * constraints?: array,
* setQueryAsData?: string, * setQueryAsData?: string,
* sql?: string, * sql?: string,
* alias?: string * alias?: string,
* fieldTypes?: array<string, array<string, string>>
* } * }
* @var array *
* fieldTypes: [ProtectedTableName => [FieldName => Type]]
*/ */
protected $QBOptions; protected $QBOptions;
@ -321,7 +321,7 @@ class BaseBuilder
$this->from($tableName); $this->from($tableName);
if (! empty($options)) { if ($options !== null && $options !== []) {
foreach ($options as $key => $value) { foreach ($options as $key => $value) {
if (property_exists($this, $key)) { if (property_exists($this, $key)) {
$this->{$key} = $value; $this->{$key} = $value;
@ -923,6 +923,7 @@ class BaseBuilder
* @used-by whereNotIn() * @used-by whereNotIn()
* @used-by orWhereNotIn() * @used-by orWhereNotIn()
* *
* @param non-empty-string|null $key
* @param array|BaseBuilder|Closure|null $values The values searched on, or anonymous function with subquery * @param array|BaseBuilder|Closure|null $values The values searched on, or anonymous function with subquery
* *
* @return $this * @return $this
@ -931,7 +932,7 @@ class BaseBuilder
*/ */
protected function _whereIn(?string $key = null, $values = null, bool $not = false, string $type = 'AND ', ?bool $escape = null, string $clause = 'QBWhere') protected function _whereIn(?string $key = null, $values = null, bool $not = false, string $type = 'AND ', ?bool $escape = null, string $clause = 'QBWhere')
{ {
if (empty($key) || ! is_string($key)) { if ($key === null || $key === '') {
throw new InvalidArgumentException(sprintf('%s() expects $key to be a non-empty string', debug_backtrace(0, 2)[1]['function'])); throw new InvalidArgumentException(sprintf('%s() expects $key to be a non-empty string', debug_backtrace(0, 2)[1]['function']));
} }
@ -1437,7 +1438,7 @@ class BaseBuilder
public function orderBy(string $orderBy, string $direction = '', ?bool $escape = null) public function orderBy(string $orderBy, string $direction = '', ?bool $escape = null)
{ {
$qbOrderBy = []; $qbOrderBy = [];
if (empty($orderBy)) { if ($orderBy === '') {
return $this; return $this;
} }
@ -1493,7 +1494,7 @@ class BaseBuilder
$this->QBLimit = $value; $this->QBLimit = $value;
} }
if (! empty($offset)) { if ($offset !== null && $offset !== 0) {
$this->QBOffset = $offset; $this->QBOffset = $offset;
} }
@ -1507,8 +1508,8 @@ class BaseBuilder
*/ */
public function offset(int $offset) public function offset(int $offset)
{ {
if (! empty($offset)) { if ($offset !== 0) {
$this->QBOffset = (int) $offset; $this->QBOffset = $offset;
} }
return $this; return $this;
@ -1739,7 +1740,7 @@ class BaseBuilder
$this->where($where); $this->where($where);
} }
if (! empty($limit)) { if ($limit !== null && $limit !== 0) {
$this->limit($limit, $offset); $this->limit($limit, $offset);
} }
@ -1760,6 +1761,8 @@ class BaseBuilder
/** /**
* Compiles batch insert/update/upsert strings and runs the queries * Compiles batch insert/update/upsert strings and runs the queries
* *
* @param '_deleteBatch'|'_insertBatch'|'_updateBatch'|'_upsertBatch' $renderMethod
*
* @return false|int|string[] Number of rows inserted or FALSE on failure, SQL array when testMode * @return false|int|string[] Number of rows inserted or FALSE on failure, SQL array when testMode
* *
* @throws DatabaseException * @throws DatabaseException
@ -1967,11 +1970,9 @@ class BaseBuilder
* *
* @used-by batchExecute * @used-by batchExecute
* *
* @param string $table Protected table name * @param string $table Protected table name
* @param string[] $keys QBKeys * @param list<string> $keys QBKeys
* @phpstan-param list<string> $keys QBKeys * @param list<list<int|string>> $values QBSet
* @param array<array<int|string>> $values QBSet
* @phpstan-param list<list<string|int>> $values QBSet
*/ */
protected function _upsertBatch(string $table, array $keys, array $values): string protected function _upsertBatch(string $table, array $keys, array $values): string
{ {
@ -2196,11 +2197,9 @@ class BaseBuilder
* *
* @used-by batchExecute * @used-by batchExecute
* *
* @param string $table Protected table name * @param string $table Protected table name
* @param string[] $keys QBKeys * @param list<string> $keys QBKeys
* @phpstan-param list<string> $keys QBKeys * @param list<list<int|string>> $values QBSet
* @param array<array<int|string>> $values QBSet
* @phpstan-param list<list<string|int>> $values QBSet
*/ */
protected function _insertBatch(string $table, array $keys, array $values): string protected function _insertBatch(string $table, array $keys, array $values): string
{ {
@ -2277,7 +2276,7 @@ class BaseBuilder
* *
* @param array|object|null $set * @param array|object|null $set
* *
* @return bool * @return BaseResult|bool|Query
* *
* @throws DatabaseException * @throws DatabaseException
*/ */
@ -2459,7 +2458,7 @@ class BaseBuilder
$this->where($where); $this->where($where);
} }
if (! empty($limit)) { if ($limit !== null && $limit !== 0) {
if (! $this->canLimitWhereUpdates) { if (! $this->canLimitWhereUpdates) {
throw new DatabaseException('This driver does not allow LIMITs on UPDATE queries using WHERE.'); throw new DatabaseException('This driver does not allow LIMITs on UPDATE queries using WHERE.');
} }
@ -2566,11 +2565,9 @@ class BaseBuilder
* *
* @used-by batchExecute * @used-by batchExecute
* *
* @param string $table Protected table name * @param string $table Protected table name
* @param string[] $keys QBKeys * @param list<string> $keys QBKeys
* @phpstan-param list<string> $keys QBKeys * @param list<list<int|string>> $values QBSet
* @param array[] $values QBSet
* @phpstan-param list<list<string|int>> $values QBSet
*/ */
protected function _updateBatch(string $table, array $keys, array $values): string protected function _updateBatch(string $table, array $keys, array $values): string
{ {
@ -2747,9 +2744,9 @@ class BaseBuilder
/** /**
* Compiles a delete string and runs the query * Compiles a delete string and runs the query
* *
* @param mixed $where * @param array|RawSql|string $where
* *
* @return bool|string Returns a string if in test mode. * @return bool|string Returns a SQL string if in test mode.
* *
* @throws DatabaseException * @throws DatabaseException
*/ */
@ -2771,7 +2768,7 @@ class BaseBuilder
$sql = $this->_delete($this->removeAlias($table)); $sql = $this->_delete($this->removeAlias($table));
if (! empty($limit)) { if ($limit !== null && $limit !== 0) {
$this->QBLimit = $limit; $this->QBLimit = $limit;
} }
@ -2830,11 +2827,9 @@ class BaseBuilder
* *
* @used-by batchExecute * @used-by batchExecute
* *
* @param string $table Protected table name * @param string $table Protected table name
* @param string[] $keys QBKeys * @param list<string> $keys QBKeys
* @phpstan-param list<string> $keys QBKeys * @paramst<string|int>> $values QBSet
* @param array<array<int|string>> $values QBSet
* @phpstan-param list<list<string|int>> $values QBSet
*/ */
protected function _deleteBatch(string $table, array $keys, array $values): string protected function _deleteBatch(string $table, array $keys, array $values): string
{ {
@ -3183,7 +3178,7 @@ class BaseBuilder
*/ */
protected function compileOrderBy(): string protected function compileOrderBy(): string
{ {
if (is_array($this->QBOrderBy) && ! empty($this->QBOrderBy)) { if (is_array($this->QBOrderBy) && $this->QBOrderBy !== []) {
foreach ($this->QBOrderBy as &$orderBy) { foreach ($this->QBOrderBy as &$orderBy) {
if ($orderBy['escape'] !== false && ! $this->isLiteral($orderBy['field'])) { if ($orderBy['escape'] !== false && ! $this->isLiteral($orderBy['field'])) {
$orderBy['field'] = $this->db->protectIdentifiers($orderBy['field']); $orderBy['field'] = $this->db->protectIdentifiers($orderBy['field']);
@ -3276,7 +3271,7 @@ class BaseBuilder
{ {
$str = trim($str); $str = trim($str);
if (empty($str) if ($str === ''
|| ctype_digit($str) || ctype_digit($str)
|| (string) (float) $str === $str || (string) (float) $str === $str
|| in_array(strtoupper($str), ['TRUE', 'FALSE'], true) || in_array(strtoupper($str), ['TRUE', 'FALSE'], true)
@ -3412,8 +3407,7 @@ class BaseBuilder
/** /**
* Returns the SQL string operator from where key * Returns the SQL string operator from where key
* *
* @return array<int, string>|false * @return false|list<string>
* @phpstan-return list<string>|false
*/ */
private function getOperatorFromWhereKey(string $whereKey) private function getOperatorFromWhereKey(string $whereKey)
{ {

View File

@ -174,7 +174,7 @@ abstract class BaseResult implements ResultInterface
*/ */
public function getResultArray(): array public function getResultArray(): array
{ {
if (! empty($this->resultArray)) { if ($this->resultArray !== []) {
return $this->resultArray; return $this->resultArray;
} }
@ -214,7 +214,7 @@ abstract class BaseResult implements ResultInterface
*/ */
public function getResultObject(): array public function getResultObject(): array
{ {
if (! empty($this->resultObject)) { if ($this->resultObject !== []) {
return $this->resultObject; return $this->resultObject;
} }
@ -254,14 +254,16 @@ abstract class BaseResult implements ResultInterface
* *
* If row doesn't exist, returns null. * If row doesn't exist, returns null.
* *
* @param int $n The index of the results to return * @param int|string $n The index of the results to return, or column name.
* @param string $type The type of result object. 'array', 'object' or class name. * @param string $type The type of result object. 'array', 'object' or class name.
* @phpstan-param class-string|'array'|'object' $type
* *
* @return array|object|stdClass|null * @return array|object|stdClass|null
* @phpstan-return ($type is 'object' ? stdClass|null : ($type is 'array' ? array|null : object|null)) * @phpstan-return ($type is 'object' ? stdClass|null : ($type is 'array' ? array|null : object|null))
*/ */
public function getRow($n = 0, string $type = 'object') public function getRow($n = 0, string $type = 'object')
{ {
// $n is a column name.
if (! is_numeric($n)) { if (! is_numeric($n)) {
// We cache the row data for subsequent uses // We cache the row data for subsequent uses
if (! is_array($this->rowData)) { if (! is_array($this->rowData)) {
@ -321,7 +323,7 @@ abstract class BaseResult implements ResultInterface
public function getRowArray(int $n = 0) public function getRowArray(int $n = 0)
{ {
$result = $this->getResultArray(); $result = $this->getResultArray();
if (empty($result)) { if ($result === []) {
return null; return null;
} }
@ -342,7 +344,7 @@ abstract class BaseResult implements ResultInterface
public function getRowObject(int $n = 0) public function getRowObject(int $n = 0)
{ {
$result = $this->getResultObject(); $result = $this->getResultObject();
if (empty($result)) { if ($result === []) {
return null; return null;
} }
@ -390,7 +392,7 @@ abstract class BaseResult implements ResultInterface
{ {
$result = $this->getResult($type); $result = $this->getResult($type);
return (empty($result)) ? null : $result[0]; return ($result === []) ? null : $result[0];
} }
/** /**
@ -402,7 +404,7 @@ abstract class BaseResult implements ResultInterface
{ {
$result = $this->getResult($type); $result = $this->getResult($type);
return (empty($result)) ? null : $result[count($result) - 1]; return ($result === []) ? null : $result[count($result) - 1];
} }
/** /**
@ -413,7 +415,7 @@ abstract class BaseResult implements ResultInterface
public function getNextRow(string $type = 'object') public function getNextRow(string $type = 'object')
{ {
$result = $this->getResult($type); $result = $this->getResult($type);
if (empty($result)) { if ($result === []) {
return null; return null;
} }
@ -428,7 +430,7 @@ abstract class BaseResult implements ResultInterface
public function getPreviousRow(string $type = 'object') public function getPreviousRow(string $type = 'object')
{ {
$result = $this->getResult($type); $result = $this->getResult($type);
if (empty($result)) { if ($result === []) {
return null; return null;
} }

View File

@ -41,10 +41,9 @@ class Config extends BaseConfig
/** /**
* Returns the database connection * Returns the database connection
* *
* @param array|BaseConnection|string|null $group The name of the connection group to use, * @param array|BaseConnection|non-empty-string|null $group The name of the connection group to use,
* or an array of configuration settings. * or an array of configuration settings.
* @phpstan-param array|BaseConnection|non-empty-string|null $group * @param bool $getShared Whether to return a shared instance of the connection.
* @param bool $getShared Whether to return a shared instance of the connection.
* *
* @return BaseConnection * @return BaseConnection
*/ */
@ -127,7 +126,7 @@ class Config extends BaseConfig
/** /**
* Returns a new instance of the Database Seeder. * Returns a new instance of the Database Seeder.
* *
* @phpstan-param null|non-empty-string $group * @param non-empty-string|null $group
* *
* @return Seeder * @return Seeder
*/ */

View File

@ -104,7 +104,7 @@ class Database
'database' => isset($dsn['path']) ? rawurldecode(substr($dsn['path'], 1)) : '', 'database' => isset($dsn['path']) ? rawurldecode(substr($dsn['path'], 1)) : '',
]; ];
if (! empty($dsn['query'])) { if (isset($dsn['query']) && ($dsn['query'] !== '')) {
parse_str($dsn['query'], $extra); parse_str($dsn['query'], $extra);
foreach ($extra as $key => $val) { foreach ($extra as $key => $val) {

View File

@ -32,14 +32,14 @@ class Forge
/** /**
* List of fields. * List of fields.
* *
* @var array * @var array<string, array|string> [name => attributes]
*/ */
protected $fields = []; protected $fields = [];
/** /**
* List of keys. * List of keys.
* *
* @phpstan-var array{}|list<array{fields: string[], keyName: string}> * @var list<array{fields?: string[], keyName?: string}>
*/ */
protected $keys = []; protected $keys = [];
@ -53,7 +53,7 @@ class Forge
/** /**
* Primary keys. * Primary keys.
* *
* @phpstan-var array{}|array{fields: string[], keyName: string} * @var array{fields?: string[], keyName?: string}
*/ */
protected $primaryKeys = []; protected $primaryKeys = [];
@ -351,14 +351,14 @@ class Forge
/** /**
* Add Field * Add Field
* *
* @param array|string $field * @param array<string, array|string>|string $fields Field array or Field string
* *
* @return Forge * @return Forge
*/ */
public function addField($field) public function addField($fields)
{ {
if (is_string($field)) { if (is_string($fields)) {
if ($field === 'id') { if ($fields === 'id') {
$this->addField([ $this->addField([
'id' => [ 'id' => [
'type' => 'INT', 'type' => 'INT',
@ -368,27 +368,27 @@ class Forge
]); ]);
$this->addKey('id', true); $this->addKey('id', true);
} else { } else {
if (strpos($field, ' ') === false) { if (strpos($fields, ' ') === false) {
throw new InvalidArgumentException('Field information is required for that operation.'); throw new InvalidArgumentException('Field information is required for that operation.');
} }
$fieldName = explode(' ', $field, 2)[0]; $fieldName = explode(' ', $fields, 2)[0];
$fieldName = trim($fieldName, '`\'"'); $fieldName = trim($fieldName, '`\'"');
$this->fields[$fieldName] = $field; $this->fields[$fieldName] = $fields;
} }
} }
if (is_array($field)) { if (is_array($fields)) {
foreach ($field as $idx => $f) { foreach ($fields as $name => $attributes) {
if (is_string($f)) { if (is_string($attributes)) {
$this->addField($f); $this->addField($attributes);
continue; continue;
} }
if (is_array($f)) { if (is_array($attributes)) {
$this->fields = array_merge($this->fields, [$idx => $f]); $this->fields = array_merge($this->fields, [$name => $attributes]);
} }
} }
} }
@ -404,8 +404,14 @@ class Forge
* *
* @throws DatabaseException * @throws DatabaseException
*/ */
public function addForeignKey($fieldName = '', string $tableName = '', $tableField = '', string $onUpdate = '', string $onDelete = '', string $fkName = ''): Forge public function addForeignKey(
{ $fieldName = '',
string $tableName = '',
$tableField = '',
string $onUpdate = '',
string $onDelete = '',
string $fkName = ''
): Forge {
$fieldName = (array) $fieldName; $fieldName = (array) $fieldName;
$tableField = (array) $tableField; $tableField = (array) $tableField;
@ -428,8 +434,9 @@ class Forge
*/ */
public function dropKey(string $table, string $keyName, bool $prefixKeyName = true): bool public function dropKey(string $table, string $keyName, bool $prefixKeyName = true): bool
{ {
$keyName = $this->db->escapeIdentifiers(($prefixKeyName === true ? $this->db->DBPrefix : '') . $keyName); $keyName = $this->db->escapeIdentifiers(($prefixKeyName === true ? $this->db->DBPrefix : '') . $keyName);
$table = $this->db->escapeIdentifiers($this->db->DBPrefix . $table); $table = $this->db->escapeIdentifiers($this->db->DBPrefix . $table);
$dropKeyAsConstraint = $this->dropKeyAsConstraint($table, $keyName); $dropKeyAsConstraint = $this->dropKeyAsConstraint($table, $keyName);
if ($dropKeyAsConstraint === true) { if ($dropKeyAsConstraint === true) {
@ -458,7 +465,7 @@ class Forge
} }
/** /**
* Checks if if key needs to be dropped as a constraint. * Checks if key needs to be dropped as a constraint.
*/ */
protected function dropKeyAsConstraint(string $table, string $constraintName): bool protected function dropKeyAsConstraint(string $table, string $constraintName): bool
{ {
@ -494,7 +501,7 @@ class Forge
} }
/** /**
* @return BaseResult|bool|false|mixed|Query * @return bool
* *
* @throws DatabaseException * @throws DatabaseException
*/ */
@ -518,7 +525,9 @@ class Forge
} }
/** /**
* @return mixed * @param array $attributes Table attributes
*
* @return bool
* *
* @throws DatabaseException * @throws DatabaseException
*/ */
@ -562,28 +571,30 @@ class Forge
} }
/** /**
* @param array $attributes Table attributes
*
* @return string SQL string * @return string SQL string
* *
* @deprecated $ifNotExists is no longer used, and will be removed. * @deprecated $ifNotExists is no longer used, and will be removed.
*/ */
protected function _createTable(string $table, bool $ifNotExists, array $attributes) protected function _createTable(string $table, bool $ifNotExists, array $attributes)
{ {
$columns = $this->_processFields(true); $processedFields = $this->_processFields(true);
for ($i = 0, $c = count($columns); $i < $c; $i++) { for ($i = 0, $c = count($processedFields); $i < $c; $i++) {
$columns[$i] = ($columns[$i]['_literal'] !== false) ? "\n\t" . $columns[$i]['_literal'] $processedFields[$i] = ($processedFields[$i]['_literal'] !== false) ? "\n\t" . $processedFields[$i]['_literal']
: "\n\t" . $this->_processColumn($columns[$i]); : "\n\t" . $this->_processColumn($processedFields[$i]);
} }
$columns = implode(',', $columns); $processedFields = implode(',', $processedFields);
$columns .= $this->_processPrimaryKeys($table); $processedFields .= $this->_processPrimaryKeys($table);
$columns .= current($this->_processForeignKeys($table)); $processedFields .= current($this->_processForeignKeys($table));
if ($this->createTableKeys === true) { if ($this->createTableKeys === true) {
$indexes = current($this->_processIndexes($table)); $indexes = current($this->_processIndexes($table));
if (is_string($indexes)) { if (is_string($indexes)) {
$columns .= $indexes; $processedFields .= $indexes;
} }
} }
@ -591,7 +602,7 @@ class Forge
$this->createTableStr . '%s', $this->createTableStr . '%s',
'CREATE TABLE', 'CREATE TABLE',
$this->db->escapeIdentifiers($table), $this->db->escapeIdentifiers($table),
$columns, $processedFields,
$this->_createTableAttributes($attributes) $this->_createTableAttributes($attributes)
); );
} }
@ -610,7 +621,7 @@ class Forge
} }
/** /**
* @return mixed * @return bool
* *
* @throws DatabaseException * @throws DatabaseException
*/ */
@ -676,7 +687,7 @@ class Forge
} }
/** /**
* @return mixed * @return bool
* *
* @throws DatabaseException * @throws DatabaseException
*/ */
@ -716,23 +727,24 @@ class Forge
} }
/** /**
* @param array|string $field * @param array<string, array|string>|string $fields Field array or Field string
* *
* @throws DatabaseException * @throws DatabaseException
*/ */
public function addColumn(string $table, $field): bool public function addColumn(string $table, $fields): bool
{ {
// Work-around for literal column definitions // Work-around for literal column definitions
if (! is_array($field)) { if (is_string($fields)) {
$field = [$field]; $fields = [$fields];
} }
foreach (array_keys($field) as $k) { foreach (array_keys($fields) as $name) {
$this->addField([$k => $field[$k]]); $this->addField([$name => $fields[$name]]);
} }
$sqls = $this->_alterTable('ADD', $this->db->DBPrefix . $table, $this->_processFields()); $sqls = $this->_alterTable('ADD', $this->db->DBPrefix . $table, $this->_processFields());
$this->reset(); $this->reset();
if ($sqls === false) { if ($sqls === false) {
if ($this->db->DBDebug) { if ($this->db->DBDebug) {
throw new DatabaseException('This feature is not available for the database you are using.'); throw new DatabaseException('This feature is not available for the database you are using.');
@ -751,15 +763,16 @@ class Forge
} }
/** /**
* @param array|string $columnName * @param array|string $columnNames column names to DROP
* *
* @return mixed * @return bool
* *
* @throws DatabaseException * @throws DatabaseException
*/ */
public function dropColumn(string $table, $columnName) public function dropColumn(string $table, $columnNames)
{ {
$sql = $this->_alterTable('DROP', $this->db->DBPrefix . $table, $columnName); $sql = $this->_alterTable('DROP', $this->db->DBPrefix . $table, $columnNames);
if ($sql === false) { if ($sql === false) {
if ($this->db->DBDebug) { if ($this->db->DBDebug) {
throw new DatabaseException('This feature is not available for the database you are using.'); throw new DatabaseException('This feature is not available for the database you are using.');
@ -772,19 +785,19 @@ class Forge
} }
/** /**
* @param array|string $field * @param array<string, array|string>|string $fields Field array or Field string
* *
* @throws DatabaseException * @throws DatabaseException
*/ */
public function modifyColumn(string $table, $field): bool public function modifyColumn(string $table, $fields): bool
{ {
// Work-around for literal column definitions // Work-around for literal column definitions
if (! is_array($field)) { if (is_string($fields)) {
$field = [$field]; $fields = [$fields];
} }
foreach (array_keys($field) as $k) { foreach (array_keys($fields) as $name) {
$this->addField([$k => $field[$k]]); $this->addField([$name => $fields[$name]]);
} }
if ($this->fields === []) { if ($this->fields === []) {
@ -793,6 +806,7 @@ class Forge
$sqls = $this->_alterTable('CHANGE', $this->db->DBPrefix . $table, $this->_processFields()); $sqls = $this->_alterTable('CHANGE', $this->db->DBPrefix . $table, $this->_processFields());
$this->reset(); $this->reset();
if ($sqls === false) { if ($sqls === false) {
if ($this->db->DBDebug) { if ($this->db->DBDebug) {
throw new DatabaseException('This feature is not available for the database you are using.'); throw new DatabaseException('This feature is not available for the database you are using.');
@ -813,48 +827,53 @@ class Forge
} }
/** /**
* @param array|string $fields * @param 'ADD'|'CHANGE'|'DROP' $alterType
* @param array|string $processedFields Processed column definitions
* or column names to DROP
* *
* @return false|string|string[] * @return false|list<string>|string|null SQL string
* @phpstan-return ($alterType is 'DROP' ? string : list<string>|false|null)
*/ */
protected function _alterTable(string $alterType, string $table, $fields) protected function _alterTable(string $alterType, string $table, $processedFields)
{ {
$sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table) . ' '; $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table) . ' ';
// DROP has everything it needs now. // DROP has everything it needs now.
if ($alterType === 'DROP') { if ($alterType === 'DROP') {
if (is_string($fields)) { $columnNamesToDrop = $processedFields;
$fields = explode(',', $fields);
if (is_string($columnNamesToDrop)) {
$columnNamesToDrop = explode(',', $columnNamesToDrop);
} }
$fields = array_map(fn ($field) => 'DROP COLUMN ' . $this->db->escapeIdentifiers(trim($field)), $fields); $columnNamesToDrop = array_map(fn ($field) => 'DROP COLUMN ' . $this->db->escapeIdentifiers(trim($field)), $columnNamesToDrop);
return $sql . implode(', ', $fields); return $sql . implode(', ', $columnNamesToDrop);
} }
$sql .= ($alterType === 'ADD') ? 'ADD ' : $alterType . ' COLUMN '; $sql .= ($alterType === 'ADD') ? 'ADD ' : $alterType . ' COLUMN ';
$sqls = []; $sqls = [];
foreach ($fields as $data) { foreach ($processedFields as $field) {
$sqls[] = $sql . ($data['_literal'] !== false $sqls[] = $sql . ($field['_literal'] !== false
? $data['_literal'] ? $field['_literal']
: $this->_processColumn($data)); : $this->_processColumn($field));
} }
return $sqls; return $sqls;
} }
/** /**
* Process fields * Returns $processedFields array from $this->fields data.
*/ */
protected function _processFields(bool $createTable = false): array protected function _processFields(bool $createTable = false): array
{ {
$fields = []; $processedFields = [];
foreach ($this->fields as $key => $attributes) { foreach ($this->fields as $name => $attributes) {
if (! is_array($attributes)) { if (! is_array($attributes)) {
$fields[] = ['_literal' => $attributes]; $processedFields[] = ['_literal' => $attributes];
continue; continue;
} }
@ -870,7 +889,7 @@ class Forge
} }
$field = [ $field = [
'name' => $key, 'name' => $name,
'new_name' => $attributes['NAME'] ?? null, 'new_name' => $attributes['NAME'] ?? null,
'type' => $attributes['TYPE'] ?? null, 'type' => $attributes['TYPE'] ?? null,
'length' => '', 'length' => '',
@ -928,24 +947,24 @@ class Forge
$field['length'] = '(' . $attributes['CONSTRAINT'] . ')'; $field['length'] = '(' . $attributes['CONSTRAINT'] . ')';
} }
$fields[] = $field; $processedFields[] = $field;
} }
return $fields; return $processedFields;
} }
/** /**
* Process column * Converts $processedField array to field definition string.
*/ */
protected function _processColumn(array $field): string protected function _processColumn(array $processedField): string
{ {
return $this->db->escapeIdentifiers($field['name']) return $this->db->escapeIdentifiers($processedField['name'])
. ' ' . $field['type'] . $field['length'] . ' ' . $processedField['type'] . $processedField['length']
. $field['unsigned'] . $processedField['unsigned']
. $field['default'] . $processedField['default']
. $field['null'] . $processedField['null']
. $field['auto_increment'] . $processedField['auto_increment']
. $field['unique']; . $processedField['unique'];
} }
/** /**
@ -1163,10 +1182,10 @@ class Forge
{ {
$errorNames = []; $errorNames = [];
foreach ($this->foreignKeys as $name) { foreach ($this->foreignKeys as $fkeyInfo) {
foreach ($name['field'] as $f) { foreach ($fkeyInfo['field'] as $fieldName) {
if (! isset($this->fields[$f])) { if (! isset($this->fields[$fieldName])) {
$errorNames[] = $f; $errorNames[] = $fieldName;
} }
} }
} }

View File

@ -21,7 +21,7 @@ abstract class Migration
/** /**
* The name of the database group to use. * The name of the database group to use.
* *
* @var string * @var string|null
*/ */
protected $DBGroup; protected $DBGroup;
@ -39,12 +39,15 @@ abstract class Migration
*/ */
protected $forge; protected $forge;
/**
* Constructor.
*/
public function __construct(?Forge $forge = null) public function __construct(?Forge $forge = null)
{ {
$this->forge = $forge ?? Database::forge($this->DBGroup ?? config(Database::class)->defaultGroup); if (isset($this->DBGroup)) {
$this->forge = Database::forge($this->DBGroup);
} elseif ($forge !== null) {
$this->forge = $forge;
} else {
$this->forge = Database::forge(config(Database::class)->defaultGroup);
}
$this->db = $this->forge->getConnection(); $this->db = $this->forge->getConnection();
} }

View File

@ -122,14 +122,11 @@ class MigrationRunner
protected $groupSkip = false; protected $groupSkip = false;
/** /**
* Constructor. * The migration can manage multiple databases. So it should always use the
* default DB group so that it creates the `migrations` table in the default
* DB group. Therefore, passing $db is for testing purposes only.
* *
* When passing in $db, you may pass any of the following to connect: * @param array|ConnectionInterface|string|null $db DB group. For testing purposes only.
* - group name
* - existing connection instance
* - array of database configuration values
*
* @param array|ConnectionInterface|string|null $db
* *
* @throws ConfigException * @throws ConfigException
*/ */
@ -138,16 +135,12 @@ class MigrationRunner
$this->enabled = $config->enabled ?? false; $this->enabled = $config->enabled ?? false;
$this->table = $config->table ?? 'migrations'; $this->table = $config->table ?? 'migrations';
// Default name space is the app namespace
$this->namespace = APP_NAMESPACE; $this->namespace = APP_NAMESPACE;
// get default database group // Even if a DB connection is passed, since it is a test,
$config = config(Database::class); // it is assumed to use the default group name
$this->group = $config->defaultGroup; $this->group = is_string($db) ? $db : config(Database::class)->defaultGroup;
unset($config);
// If no db connection passed in, use
// default database group.
$this->db = db_connect($db); $this->db = db_connect($db);
} }
@ -174,7 +167,7 @@ class MigrationRunner
$migrations = $this->findMigrations(); $migrations = $this->findMigrations();
if (empty($migrations)) { if ($migrations === []) {
return true; return true;
} }
@ -242,7 +235,7 @@ class MigrationRunner
$targetBatch = $batches[count($batches) - 1 + $targetBatch] ?? 0; $targetBatch = $batches[count($batches) - 1 + $targetBatch] ?? 0;
} }
if (empty($batches) && $targetBatch === 0) { if ($batches === [] && $targetBatch === 0) {
return true; return true;
} }
@ -646,7 +639,7 @@ class MigrationRunner
$builder = $this->db->table($this->table); $builder = $this->db->table($this->table);
// If group was specified then use it // If group was specified then use it
if (! empty($group)) { if ($group !== '') {
$builder->where('group', $group); $builder->where('group', $group);
} }
@ -839,8 +832,9 @@ class MigrationRunner
throw new RuntimeException($message); throw new RuntimeException($message);
} }
$instance = new $class(); /** @var Migration $instance */
$group = $instance->getDBGroup() ?? config(Database::class)->defaultGroup; $instance = new $class(Database::forge($this->db));
$group = $instance->getDBGroup() ?? $this->group;
if (ENVIRONMENT !== 'testing' && $group === 'tests' && $this->groupFilter !== 'tests') { if (ENVIRONMENT !== 'testing' && $group === 'tests' && $this->groupFilter !== 'tests') {
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
@ -856,8 +850,6 @@ class MigrationRunner
return true; return true;
} }
$this->setGroup($group);
if (! is_callable([$instance, $direction])) { if (! is_callable([$instance, $direction])) {
$message = sprintf(lang('Migrations.missingMethod'), $direction); $message = sprintf(lang('Migrations.missingMethod'), $direction);

View File

@ -49,7 +49,7 @@ class Builder extends BaseBuilder
*/ */
protected function _fromTables(): string protected function _fromTables(): string
{ {
if (! empty($this->QBJoin) && count($this->QBFrom) > 1) { if ($this->QBJoin !== [] && count($this->QBFrom) > 1) {
return '(' . implode(', ', $this->QBFrom) . ')'; return '(' . implode(', ', $this->QBFrom) . ')';
} }

View File

@ -150,7 +150,7 @@ class Connection extends BaseConnection
$ssl['cipher'] = $this->encrypt['ssl_cipher']; $ssl['cipher'] = $this->encrypt['ssl_cipher'];
} }
if (! empty($ssl)) { if ($ssl !== []) {
if (isset($this->encrypt['ssl_verify'])) { if (isset($this->encrypt['ssl_verify'])) {
if ($this->encrypt['ssl_verify']) { if ($this->encrypt['ssl_verify']) {
if (defined('MYSQLI_OPT_SSL_VERIFY_SERVER_CERT')) { if (defined('MYSQLI_OPT_SSL_VERIFY_SERVER_CERT')) {

View File

@ -128,57 +128,59 @@ class Forge extends BaseForge
/** /**
* ALTER TABLE * ALTER TABLE
* *
* @param string $alterType ALTER type * @param string $alterType ALTER type
* @param string $table Table name * @param string $table Table name
* @param array|string $field Column definition * @param array|string $processedFields Processed column definitions
* or column names to DROP
* *
* @return string|string[] * @return list<string>|string SQL string
* @phpstan-return ($alterType is 'DROP' ? string : list<string>)
*/ */
protected function _alterTable(string $alterType, string $table, $field) protected function _alterTable(string $alterType, string $table, $processedFields)
{ {
if ($alterType === 'DROP') { if ($alterType === 'DROP') {
return parent::_alterTable($alterType, $table, $field); return parent::_alterTable($alterType, $table, $processedFields);
} }
$sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table);
foreach ($field as $i => $data) { foreach ($processedFields as $i => $field) {
if ($data['_literal'] !== false) { if ($field['_literal'] !== false) {
$field[$i] = ($alterType === 'ADD') ? "\n\tADD " . $data['_literal'] : "\n\tMODIFY " . $data['_literal']; $processedFields[$i] = ($alterType === 'ADD') ? "\n\tADD " . $field['_literal'] : "\n\tMODIFY " . $field['_literal'];
} else { } else {
if ($alterType === 'ADD') { if ($alterType === 'ADD') {
$field[$i]['_literal'] = "\n\tADD "; $processedFields[$i]['_literal'] = "\n\tADD ";
} else { } else {
$field[$i]['_literal'] = empty($data['new_name']) ? "\n\tMODIFY " : "\n\tCHANGE "; $processedFields[$i]['_literal'] = empty($field['new_name']) ? "\n\tMODIFY " : "\n\tCHANGE ";
} }
$field[$i] = $field[$i]['_literal'] . $this->_processColumn($field[$i]); $processedFields[$i] = $processedFields[$i]['_literal'] . $this->_processColumn($processedFields[$i]);
} }
} }
return [$sql . implode(',', $field)]; return [$sql . implode(',', $processedFields)];
} }
/** /**
* Process column * Process column
*/ */
protected function _processColumn(array $field): string protected function _processColumn(array $processedField): string
{ {
$extraClause = isset($field['after']) ? ' AFTER ' . $this->db->escapeIdentifiers($field['after']) : ''; $extraClause = isset($processedField['after']) ? ' AFTER ' . $this->db->escapeIdentifiers($processedField['after']) : '';
if (empty($extraClause) && isset($field['first']) && $field['first'] === true) { if (empty($extraClause) && isset($processedField['first']) && $processedField['first'] === true) {
$extraClause = ' FIRST'; $extraClause = ' FIRST';
} }
return $this->db->escapeIdentifiers($field['name']) return $this->db->escapeIdentifiers($processedField['name'])
. (empty($field['new_name']) ? '' : ' ' . $this->db->escapeIdentifiers($field['new_name'])) . (empty($processedField['new_name']) ? '' : ' ' . $this->db->escapeIdentifiers($processedField['new_name']))
. ' ' . $field['type'] . $field['length'] . ' ' . $processedField['type'] . $processedField['length']
. $field['unsigned'] . $processedField['unsigned']
. $field['null'] . $processedField['null']
. $field['default'] . $processedField['default']
. $field['auto_increment'] . $processedField['auto_increment']
. $field['unique'] . $processedField['unique']
. (empty($field['comment']) ? '' : ' COMMENT ' . $field['comment']) . (empty($processedField['comment']) ? '' : ' COMMENT ' . $processedField['comment'])
. $extraClause; . $extraClause;
} }

View File

@ -166,7 +166,7 @@ class Builder extends BaseBuilder
*/ */
public function delete($where = '', ?int $limit = null, bool $resetData = true) public function delete($where = '', ?int $limit = null, bool $resetData = true)
{ {
if (! empty($limit)) { if ($limit !== null && $limit !== 0) {
$this->QBLimit = $limit; $this->QBLimit = $limit;
} }

View File

@ -93,21 +93,29 @@ class Forge extends BaseForge
/** /**
* ALTER TABLE * ALTER TABLE
* *
* @param string $alterType ALTER type * @param string $alterType ALTER type
* @param string $table Table name * @param string $table Table name
* @param array|string $field Column definition * @param array|string $processedFields Processed column definitions
* or column names to DROP
* *
* @return string|string[] * @return list<string>|string SQL string
* @phpstan-return ($alterType is 'DROP' ? string : list<string>)
*/ */
protected function _alterTable(string $alterType, string $table, $field) protected function _alterTable(string $alterType, string $table, $processedFields)
{ {
$sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table);
if ($alterType === 'DROP') { if ($alterType === 'DROP') {
$fields = array_map(fn ($field) => $this->db->escapeIdentifiers(trim($field)), is_string($field) ? explode(',', $field) : $field); $columnNamesToDrop = $processedFields;
$fields = array_map(
fn ($field) => $this->db->escapeIdentifiers(trim($field)),
is_string($columnNamesToDrop) ? explode(',', $columnNamesToDrop) : $columnNamesToDrop
);
return $sql . ' DROP (' . implode(',', $fields) . ') CASCADE CONSTRAINT INVALIDATE'; return $sql . ' DROP (' . implode(',', $fields) . ') CASCADE CONSTRAINT INVALIDATE';
} }
if ($alterType === 'CHANGE') { if ($alterType === 'CHANGE') {
$alterType = 'MODIFY'; $alterType = 'MODIFY';
} }
@ -115,50 +123,50 @@ class Forge extends BaseForge
$nullableMap = array_column($this->db->getFieldData($table), 'nullable', 'name'); $nullableMap = array_column($this->db->getFieldData($table), 'nullable', 'name');
$sqls = []; $sqls = [];
for ($i = 0, $c = count($field); $i < $c; $i++) { for ($i = 0, $c = count($processedFields); $i < $c; $i++) {
if ($alterType === 'MODIFY') { if ($alterType === 'MODIFY') {
// If a null constraint is added to a column with a null constraint, // If a null constraint is added to a column with a null constraint,
// ORA-01451 will occur, // ORA-01451 will occur,
// so add null constraint is used only when it is different from the current null constraint. // so add null constraint is used only when it is different from the current null constraint.
// If a not null constraint is added to a column with a not null constraint, // If a not null constraint is added to a column with a not null constraint,
// ORA-01442 will occur. // ORA-01442 will occur.
$wantToAddNull = strpos($field[$i]['null'], ' NOT') === false; $wantToAddNull = strpos($processedFields[$i]['null'], ' NOT') === false;
$currentNullable = $nullableMap[$field[$i]['name']]; $currentNullable = $nullableMap[$processedFields[$i]['name']];
if ($wantToAddNull === true && $currentNullable === true) { if ($wantToAddNull === true && $currentNullable === true) {
$field[$i]['null'] = ''; $processedFields[$i]['null'] = '';
} elseif ($field[$i]['null'] === '' && $currentNullable === false) { } elseif ($processedFields[$i]['null'] === '' && $currentNullable === false) {
// Nullable by default // Nullable by default
$field[$i]['null'] = ' NULL'; $processedFields[$i]['null'] = ' NULL';
} elseif ($wantToAddNull === false && $currentNullable === false) { } elseif ($wantToAddNull === false && $currentNullable === false) {
$field[$i]['null'] = ''; $processedFields[$i]['null'] = '';
} }
} }
if ($field[$i]['_literal'] !== false) { if ($processedFields[$i]['_literal'] !== false) {
$field[$i] = "\n\t" . $field[$i]['_literal']; $processedFields[$i] = "\n\t" . $processedFields[$i]['_literal'];
} else { } else {
$field[$i]['_literal'] = "\n\t" . $this->_processColumn($field[$i]); $processedFields[$i]['_literal'] = "\n\t" . $this->_processColumn($processedFields[$i]);
if (! empty($field[$i]['comment'])) { if (! empty($processedFields[$i]['comment'])) {
$sqls[] = 'COMMENT ON COLUMN ' $sqls[] = 'COMMENT ON COLUMN '
. $this->db->escapeIdentifiers($table) . '.' . $this->db->escapeIdentifiers($field[$i]['name']) . $this->db->escapeIdentifiers($table) . '.' . $this->db->escapeIdentifiers($processedFields[$i]['name'])
. ' IS ' . $field[$i]['comment']; . ' IS ' . $processedFields[$i]['comment'];
} }
if ($alterType === 'MODIFY' && ! empty($field[$i]['new_name'])) { if ($alterType === 'MODIFY' && ! empty($processedFields[$i]['new_name'])) {
$sqls[] = $sql . ' RENAME COLUMN ' . $this->db->escapeIdentifiers($field[$i]['name']) $sqls[] = $sql . ' RENAME COLUMN ' . $this->db->escapeIdentifiers($processedFields[$i]['name'])
. ' TO ' . $this->db->escapeIdentifiers($field[$i]['new_name']); . ' TO ' . $this->db->escapeIdentifiers($processedFields[$i]['new_name']);
} }
$field[$i] = "\n\t" . $field[$i]['_literal']; $processedFields[$i] = "\n\t" . $processedFields[$i]['_literal'];
} }
} }
$sql .= ' ' . $alterType . ' '; $sql .= ' ' . $alterType . ' ';
$sql .= count($field) === 1 $sql .= count($processedFields) === 1
? $field[0] ? $processedFields[0]
: '(' . implode(',', $field) . ')'; : '(' . implode(',', $processedFields) . ')';
// RENAME COLUMN must be executed after MODIFY // RENAME COLUMN must be executed after MODIFY
array_unshift($sqls, $sql); array_unshift($sqls, $sql);
@ -184,26 +192,26 @@ class Forge extends BaseForge
/** /**
* Process column * Process column
*/ */
protected function _processColumn(array $field): string protected function _processColumn(array $processedField): string
{ {
$constraint = ''; $constraint = '';
// @todo: can't cover multi pattern when set type. // @todo: can't cover multi pattern when set type.
if ($field['type'] === 'VARCHAR2' && strpos($field['length'], "('") === 0) { if ($processedField['type'] === 'VARCHAR2' && strpos($processedField['length'], "('") === 0) {
$constraint = ' CHECK(' . $this->db->escapeIdentifiers($field['name']) $constraint = ' CHECK(' . $this->db->escapeIdentifiers($processedField['name'])
. ' IN ' . $field['length'] . ')'; . ' IN ' . $processedField['length'] . ')';
$field['length'] = '(' . max(array_map('mb_strlen', explode("','", mb_substr($field['length'], 2, -2)))) . ')' . $constraint; $processedField['length'] = '(' . max(array_map('mb_strlen', explode("','", mb_substr($processedField['length'], 2, -2)))) . ')' . $constraint;
} elseif (isset($this->primaryKeys['fields']) && count($this->primaryKeys['fields']) === 1 && $field['name'] === $this->primaryKeys['fields'][0]) { } elseif (isset($this->primaryKeys['fields']) && count($this->primaryKeys['fields']) === 1 && $processedField['name'] === $this->primaryKeys['fields'][0]) {
$field['unique'] = ''; $processedField['unique'] = '';
} }
return $this->db->escapeIdentifiers($field['name']) return $this->db->escapeIdentifiers($processedField['name'])
. ' ' . $field['type'] . $field['length'] . ' ' . $processedField['type'] . $processedField['length']
. $field['unsigned'] . $processedField['unsigned']
. $field['default'] . $processedField['default']
. $field['auto_increment'] . $processedField['auto_increment']
. $field['null'] . $processedField['null']
. $field['unique']; . $processedField['unique'];
} }
/** /**
@ -246,7 +254,6 @@ class Forge extends BaseForge
$attributes['TYPE'] = 'NUMBER'; $attributes['TYPE'] = 'NUMBER';
$attributes['CONSTRAINT'] = 1; $attributes['CONSTRAINT'] = 1;
$attributes['UNSIGNED'] = true; $attributes['UNSIGNED'] = true;
$attributes['NULL'] = false;
return; return;

View File

@ -146,7 +146,7 @@ class Builder extends BaseBuilder
$this->set($set); $this->set($set);
} }
if (! $this->QBSet) { if ($this->QBSet === []) {
if ($this->db->DBDebug) { if ($this->db->DBDebug) {
throw new DatabaseException('You must use the "set" method to update an entry.'); throw new DatabaseException('You must use the "set" method to update an entry.');
} }
@ -229,7 +229,7 @@ class Builder extends BaseBuilder
*/ */
public function delete($where = '', ?int $limit = null, bool $resetData = true) public function delete($where = '', ?int $limit = null, bool $resetData = true)
{ {
if (! empty($limit) || ! empty($this->QBLimit)) { if ($limit !== null && $limit !== 0 || ! empty($this->QBLimit)) {
throw new DatabaseException('PostgreSQL does not allow LIMITs on DELETE queries.'); throw new DatabaseException('PostgreSQL does not allow LIMITs on DELETE queries.');
} }
@ -312,6 +312,132 @@ class Builder extends BaseBuilder
return parent::join($table, $cond, $type, $escape); return parent::join($table, $cond, $type, $escape);
} }
/**
* Generates a platform-specific batch update string from the supplied data
*
* @used-by batchExecute
*
* @param string $table Protected table name
* @param list<string> $keys QBKeys
* @param list<list<int|string>> $values QBSet
*/
protected function _updateBatch(string $table, array $keys, array $values): string
{
$sql = $this->QBOptions['sql'] ?? '';
// if this is the first iteration of batch then we need to build skeleton sql
if ($sql === '') {
$constraints = $this->QBOptions['constraints'] ?? [];
if ($constraints === []) {
if ($this->db->DBDebug) {
throw new DatabaseException('You must specify a constraint to match on for batch updates.'); // @codeCoverageIgnore
}
return ''; // @codeCoverageIgnore
}
$updateFields = $this->QBOptions['updateFields'] ??
$this->updateFields($keys, false, $constraints)->QBOptions['updateFields'] ??
[];
$alias = $this->QBOptions['alias'] ?? '_u';
$sql = 'UPDATE ' . $this->compileIgnore('update') . $table . "\n";
$sql .= "SET\n";
$that = $this;
$sql .= implode(
",\n",
array_map(
static fn ($key, $value) => $key . ($value instanceof RawSql ?
' = ' . $value :
' = ' . $that->cast($alias . '.' . $value, $that->getFieldType($table, $key))),
array_keys($updateFields),
$updateFields
)
) . "\n";
$sql .= "FROM (\n{:_table_:}";
$sql .= ') ' . $alias . "\n";
$sql .= 'WHERE ' . implode(
' AND ',
array_map(
static function ($key, $value) use ($table, $alias, $that) {
if ($value instanceof RawSql && is_string($key)) {
return $table . '.' . $key . ' = ' . $value;
}
if ($value instanceof RawSql) {
return $value;
}
return $table . '.' . $value . ' = '
. $that->cast($alias . '.' . $value, $that->getFieldType($table, $value));
},
array_keys($constraints),
$constraints
)
);
$this->QBOptions['sql'] = $sql;
}
if (isset($this->QBOptions['setQueryAsData'])) {
$data = $this->QBOptions['setQueryAsData'];
} else {
$data = implode(
" UNION ALL\n",
array_map(
static fn ($value) => 'SELECT ' . implode(', ', array_map(
static fn ($key, $index) => $index . ' ' . $key,
$keys,
$value
)),
$values
)
) . "\n";
}
return str_replace('{:_table_:}', $data, $sql);
}
/**
* Returns cast expression.
*
* @TODO move this to BaseBuilder in 4.5.0
*
* @param float|int|string $expression
*/
private function cast($expression, ?string $type): string
{
return ($type === null) ? $expression : 'CAST(' . $expression . ' AS ' . strtoupper($type) . ')';
}
/**
* Returns the filed type from database meta data.
*
* @param string $table Protected table name.
* @param string $fieldName Field name. May be protected.
*/
private function getFieldType(string $table, string $fieldName): ?string
{
$fieldName = trim($fieldName, $this->db->escapeChar);
if (! isset($this->QBOptions['fieldTypes'][$table])) {
$this->QBOptions['fieldTypes'][$table] = [];
foreach ($this->db->getFieldData($table) as $field) {
$this->QBOptions['fieldTypes'][$table][$field->name] = $field->type;
}
}
return $this->QBOptions['fieldTypes'][$table][$fieldName] ?? null;
}
/** /**
* Generates a platform-specific upsertBatch string from the supplied data * Generates a platform-specific upsertBatch string from the supplied data
* *
@ -436,18 +562,25 @@ class Builder extends BaseBuilder
$sql .= ') ' . $alias . "\n"; $sql .= ') ' . $alias . "\n";
$that = $this;
$sql .= 'WHERE ' . implode( $sql .= 'WHERE ' . implode(
' AND ', ' AND ',
array_map( array_map(
static fn ($key, $value) => ( static function ($key, $value) use ($table, $alias, $that) {
$value instanceof RawSql ? if ($value instanceof RawSql) {
$value : return $value;
( }
is_string($key) ?
$table . '.' . $key . ' = ' . $alias . '.' . $value : if (is_string($key)) {
$table . '.' . $value . ' = ' . $alias . '.' . $value return $table . '.' . $key . ' = '
) . $that->cast(
), $alias . '.' . $value,
$that->getFieldType($table, $key)
);
}
return $table . '.' . $value . ' = ' . $alias . '.' . $value;
},
array_keys($constraints), array_keys($constraints),
$constraints $constraints
) )

View File

@ -81,50 +81,52 @@ class Forge extends BaseForge
} }
/** /**
* @param array|string $field * @param array|string $processedFields Processed column definitions
* or column names to DROP
* *
* @return array|bool|string * @return false|list<string>|string SQL string or false
* @phpstan-return ($alterType is 'DROP' ? string : list<string>|false)
*/ */
protected function _alterTable(string $alterType, string $table, $field) protected function _alterTable(string $alterType, string $table, $processedFields)
{ {
if (in_array($alterType, ['DROP', 'ADD'], true)) { if (in_array($alterType, ['DROP', 'ADD'], true)) {
return parent::_alterTable($alterType, $table, $field); return parent::_alterTable($alterType, $table, $processedFields);
} }
$sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table);
$sqls = []; $sqls = [];
foreach ($field as $data) { foreach ($processedFields as $field) {
if ($data['_literal'] !== false) { if ($field['_literal'] !== false) {
return false; return false;
} }
if (version_compare($this->db->getVersion(), '8', '>=') && isset($data['type'])) { if (version_compare($this->db->getVersion(), '8', '>=') && isset($field['type'])) {
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name']) $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($field['name'])
. " TYPE {$data['type']}{$data['length']}"; . " TYPE {$field['type']}{$field['length']}";
} }
if (! empty($data['default'])) { if (! empty($field['default'])) {
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name']) $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($field['name'])
. " SET DEFAULT {$data['default']}"; . " SET DEFAULT {$field['default']}";
} }
$nullable = true; // Nullable by default. $nullable = true; // Nullable by default.
if (isset($data['null']) && ($data['null'] === false || $data['null'] === ' NOT ' . $this->null)) { if (isset($field['null']) && ($field['null'] === false || $field['null'] === ' NOT ' . $this->null)) {
$nullable = false; $nullable = false;
} }
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name']) $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($field['name'])
. ($nullable === true ? ' DROP' : ' SET') . ' NOT NULL'; . ($nullable === true ? ' DROP' : ' SET') . ' NOT NULL';
if (! empty($data['new_name'])) { if (! empty($field['new_name'])) {
$sqls[] = $sql . ' RENAME COLUMN ' . $this->db->escapeIdentifiers($data['name']) $sqls[] = $sql . ' RENAME COLUMN ' . $this->db->escapeIdentifiers($field['name'])
. ' TO ' . $this->db->escapeIdentifiers($data['new_name']); . ' TO ' . $this->db->escapeIdentifiers($field['new_name']);
} }
if (! empty($data['comment'])) { if (! empty($field['comment'])) {
$sqls[] = 'COMMENT ON COLUMN' . $this->db->escapeIdentifiers($table) $sqls[] = 'COMMENT ON COLUMN' . $this->db->escapeIdentifiers($table)
. '.' . $this->db->escapeIdentifiers($data['name']) . '.' . $this->db->escapeIdentifiers($field['name'])
. " IS {$data['comment']}"; . " IS {$field['comment']}";
} }
} }
@ -134,14 +136,14 @@ class Forge extends BaseForge
/** /**
* Process column * Process column
*/ */
protected function _processColumn(array $field): string protected function _processColumn(array $processedField): string
{ {
return $this->db->escapeIdentifiers($field['name']) return $this->db->escapeIdentifiers($processedField['name'])
. ' ' . $field['type'] . ($field['type'] === 'text' ? '' : $field['length']) . ' ' . $processedField['type'] . ($processedField['type'] === 'text' ? '' : $processedField['length'])
. $field['default'] . $processedField['default']
. $field['null'] . $processedField['null']
. $field['auto_increment'] . $processedField['auto_increment']
. $field['unique']; . $processedField['unique'];
} }
/** /**

View File

@ -57,8 +57,9 @@ interface ResultInterface
* *
* If row doesn't exist, returns null. * If row doesn't exist, returns null.
* *
* @param int $n The index of the results to return * @param int|string $n The index of the results to return, or column name.
* @param string $type The type of result object. 'array', 'object' or class name. * @param string $type The type of result object. 'array', 'object' or class name.
* @phpstan-param class-string|'array'|'object' $type
* *
* @return array|object|stdClass|null * @return array|object|stdClass|null
* @phpstan-return ($type is 'object' ? stdClass|null : ($type is 'array' ? array|null : object|null)) * @phpstan-return ($type is 'object' ? stdClass|null : ($type is 'array' ? array|null : object|null))

12
system/Database/SQLSRV/Builder.php Normal file → Executable file
View File

@ -332,7 +332,7 @@ class Builder extends BaseBuilder
$this->set($set); $this->set($set);
} }
if (empty($this->QBSet)) { if ($this->QBSet === []) {
if ($this->db->DBDebug) { if ($this->db->DBDebug) {
throw new DatabaseException('You must use the "set" method to update an entry.'); throw new DatabaseException('You must use the "set" method to update an entry.');
} }
@ -514,7 +514,7 @@ class Builder extends BaseBuilder
$this->where($where); $this->where($where);
} }
if (empty($this->QBWhere)) { if ($this->QBWhere === []) {
if ($this->db->DBDebug) { if ($this->db->DBDebug) {
throw new DatabaseException('Deletes are not allowed unless they contain a "where" or "like" clause.'); throw new DatabaseException('Deletes are not allowed unless they contain a "where" or "like" clause.');
} }
@ -522,7 +522,7 @@ class Builder extends BaseBuilder
return false; // @codeCoverageIgnore return false; // @codeCoverageIgnore
} }
if (! empty($limit)) { if ($limit !== null && $limit !== 0) {
$this->QBLimit = $limit; $this->QBLimit = $limit;
} }
@ -551,7 +551,7 @@ class Builder extends BaseBuilder
$sql = (! $this->QBDistinct) ? 'SELECT ' : 'SELECT DISTINCT '; $sql = (! $this->QBDistinct) ? 'SELECT ' : 'SELECT DISTINCT ';
// SQL Server can't work with select * if group by is specified // SQL Server can't work with select * if group by is specified
if (empty($this->QBSelect) && ! empty($this->QBGroupBy) && is_array($this->QBGroupBy)) { if (empty($this->QBSelect) && $this->QBGroupBy !== [] && is_array($this->QBGroupBy)) {
foreach ($this->QBGroupBy as $field) { foreach ($this->QBGroupBy as $field) {
$this->QBSelect[] = is_array($field) ? $field['field'] : $field; $this->QBSelect[] = is_array($field) ? $field['field'] : $field;
} }
@ -573,7 +573,7 @@ class Builder extends BaseBuilder
} }
// Write the "FROM" portion of the query // Write the "FROM" portion of the query
if (! empty($this->QBFrom)) { if ($this->QBFrom !== []) {
$sql .= "\nFROM " . $this->_fromTables(); $sql .= "\nFROM " . $this->_fromTables();
} }
@ -660,7 +660,7 @@ class Builder extends BaseBuilder
}); });
// if no primary found then look for unique - since indexes have no order // if no primary found then look for unique - since indexes have no order
if (empty($uniqueIndexes)) { if ($uniqueIndexes === []) {
$uniqueIndexes = array_filter($tableIndexes, static function ($index) use ($fieldNames) { $uniqueIndexes = array_filter($tableIndexes, static function ($index) use ($fieldNames) {
$hasAllFields = count(array_intersect($index->fields, $fieldNames)) === count($index->fields); $hasAllFields = count(array_intersect($index->fields, $fieldNames)) === count($index->fields);

4
system/Database/SQLSRV/Connection.php Normal file → Executable file
View File

@ -444,7 +444,7 @@ class Connection extends BaseConnection
*/ */
public function setDatabase(?string $databaseName = null) public function setDatabase(?string $databaseName = null)
{ {
if (empty($databaseName)) { if ($databaseName === null || $databaseName === '') {
$databaseName = $this->database; $databaseName = $this->database;
} }
@ -538,7 +538,7 @@ class Connection extends BaseConnection
return $this->dataCache['version']; return $this->dataCache['version'];
} }
if (! $this->connID || empty($info = sqlsrv_server_info($this->connID))) { if (! $this->connID || ($info = sqlsrv_server_info($this->connID)) === []) {
$this->initialize(); $this->initialize();
} }

74
system/Database/SQLSRV/Forge.php Normal file → Executable file
View File

@ -126,26 +126,30 @@ class Forge extends BaseForge
} }
/** /**
* @param array|string $field * @param array|string $processedFields Processed column definitions
* or column names to DROP
* *
* @return false|string|string[] * @return false|list<string>|string SQL string or false
* @phpstan-return ($alterType is 'DROP' ? string : list<string>|false)
*/ */
protected function _alterTable(string $alterType, string $table, $field) protected function _alterTable(string $alterType, string $table, $processedFields)
{ {
// Handle DROP here // Handle DROP here
if ($alterType === 'DROP') { if ($alterType === 'DROP') {
$columnNamesToDrop = $processedFields;
// check if fields are part of any indexes // check if fields are part of any indexes
$indexData = $this->db->getIndexData($table); $indexData = $this->db->getIndexData($table);
foreach ($indexData as $index) { foreach ($indexData as $index) {
if (is_string($field)) { if (is_string($columnNamesToDrop)) {
$field = explode(',', $field); $columnNamesToDrop = explode(',', $columnNamesToDrop);
} }
$fld = array_intersect($field, $index->fields); $fld = array_intersect($columnNamesToDrop, $index->fields);
// Drop index if field is part of an index // Drop index if field is part of an index
if (! empty($fld)) { if ($fld !== []) {
$this->_dropIndex($table, $index); $this->_dropIndex($table, $index);
} }
} }
@ -153,7 +157,7 @@ class Forge extends BaseForge
$fullTable = $this->db->escapeIdentifiers($this->db->schema) . '.' . $this->db->escapeIdentifiers($table); $fullTable = $this->db->escapeIdentifiers($this->db->schema) . '.' . $this->db->escapeIdentifiers($table);
// Drop default constraints // Drop default constraints
$fields = implode(',', $this->db->escape((array) $field)); $fields = implode(',', $this->db->escape((array) $columnNamesToDrop));
$sql = <<<SQL $sql = <<<SQL
SELECT name SELECT name
@ -170,7 +174,7 @@ class Forge extends BaseForge
$sql = 'ALTER TABLE ' . $fullTable . ' DROP '; $sql = 'ALTER TABLE ' . $fullTable . ' DROP ';
$fields = array_map(static fn ($item) => 'COLUMN [' . trim($item) . ']', (array) $field); $fields = array_map(static fn ($item) => 'COLUMN [' . trim($item) . ']', (array) $columnNamesToDrop);
return $sql . implode(',', $fields); return $sql . implode(',', $fields);
} }
@ -181,45 +185,45 @@ class Forge extends BaseForge
$sqls = []; $sqls = [];
if ($alterType === 'ADD') { if ($alterType === 'ADD') {
foreach ($field as $data) { foreach ($processedFields as $field) {
$sqls[] = $sql . ($data['_literal'] !== false ? $data['_literal'] : $this->_processColumn($data)); $sqls[] = $sql . ($field['_literal'] !== false ? $field['_literal'] : $this->_processColumn($field));
} }
return $sqls; return $sqls;
} }
foreach ($field as $data) { foreach ($processedFields as $field) {
if ($data['_literal'] !== false) { if ($field['_literal'] !== false) {
return false; return false;
} }
if (isset($data['type'])) { if (isset($field['type'])) {
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name']) $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($field['name'])
. " {$data['type']}{$data['length']}"; . " {$field['type']}{$field['length']}";
} }
if (! empty($data['default'])) { if (! empty($field['default'])) {
$sqls[] = $sql . ' ALTER COLUMN ADD CONSTRAINT ' . $this->db->escapeIdentifiers($data['name']) . '_def' $sqls[] = $sql . ' ALTER COLUMN ADD CONSTRAINT ' . $this->db->escapeIdentifiers($field['name']) . '_def'
. " DEFAULT {$data['default']} FOR " . $this->db->escapeIdentifiers($data['name']); . " DEFAULT {$field['default']} FOR " . $this->db->escapeIdentifiers($field['name']);
} }
$nullable = true; // Nullable by default. $nullable = true; // Nullable by default.
if (isset($data['null']) && ($data['null'] === false || $data['null'] === ' NOT ' . $this->null)) { if (isset($field['null']) && ($field['null'] === false || $field['null'] === ' NOT ' . $this->null)) {
$nullable = false; $nullable = false;
} }
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name']) $sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($field['name'])
. " {$data['type']}{$data['length']} " . ($nullable === true ? '' : 'NOT') . ' NULL'; . " {$field['type']}{$field['length']} " . ($nullable === true ? '' : 'NOT') . ' NULL';
if (! empty($data['comment'])) { if (! empty($field['comment'])) {
$sqls[] = 'EXEC sys.sp_addextendedproperty ' $sqls[] = 'EXEC sys.sp_addextendedproperty '
. "@name=N'Caption', @value=N'" . $data['comment'] . "' , " . "@name=N'Caption', @value=N'" . $field['comment'] . "' , "
. "@level0type=N'SCHEMA',@level0name=N'" . $this->db->schema . "', " . "@level0type=N'SCHEMA',@level0name=N'" . $this->db->schema . "', "
. "@level1type=N'TABLE',@level1name=N'" . $this->db->escapeIdentifiers($table) . "', " . "@level1type=N'TABLE',@level1name=N'" . $this->db->escapeIdentifiers($table) . "', "
. "@level2type=N'COLUMN',@level2name=N'" . $this->db->escapeIdentifiers($data['name']) . "'"; . "@level2type=N'COLUMN',@level2name=N'" . $this->db->escapeIdentifiers($field['name']) . "'";
} }
if (! empty($data['new_name'])) { if (! empty($field['new_name'])) {
$sqls[] = "EXEC sp_rename '[" . $this->db->schema . '].[' . $table . '].[' . $data['name'] . "]' , '" . $data['new_name'] . "', 'COLUMN';"; $sqls[] = "EXEC sp_rename '[" . $this->db->schema . '].[' . $table . '].[' . $field['name'] . "]' , '" . $field['new_name'] . "', 'COLUMN';";
} }
} }
@ -287,16 +291,16 @@ class Forge extends BaseForge
/** /**
* Process column * Process column
*/ */
protected function _processColumn(array $field): string protected function _processColumn(array $processedField): string
{ {
return $this->db->escapeIdentifiers($field['name']) return $this->db->escapeIdentifiers($processedField['name'])
. (empty($field['new_name']) ? '' : ' ' . $this->db->escapeIdentifiers($field['new_name'])) . (empty($processedField['new_name']) ? '' : ' ' . $this->db->escapeIdentifiers($processedField['new_name']))
. ' ' . $field['type'] . ($field['type'] === 'text' ? '' : $field['length']) . ' ' . $processedField['type'] . ($processedField['type'] === 'text' ? '' : $processedField['length'])
. $field['default'] . $processedField['default']
. $field['null'] . $processedField['null']
. $field['auto_increment'] . $processedField['auto_increment']
. '' . ''
. $field['unique']; . $processedField['unique'];
} }
/** /**

0
system/Database/SQLSRV/PreparedQuery.php Normal file → Executable file
View File

0
system/Database/SQLSRV/Result.php Normal file → Executable file
View File

0
system/Database/SQLSRV/Utils.php Normal file → Executable file
View File

View File

@ -109,51 +109,56 @@ class Forge extends BaseForge
} }
/** /**
* @param array|string $field * @param array|string $processedFields Processed column definitions
* or column names to DROP
* *
* @return array|string|null * @return array|string|null
* @return list<string>|string|null SQL string or null
* @phpstan-return ($alterType is 'DROP' ? string : list<string>|null)
*/ */
protected function _alterTable(string $alterType, string $table, $field) protected function _alterTable(string $alterType, string $table, $processedFields)
{ {
switch ($alterType) { switch ($alterType) {
case 'DROP': case 'DROP':
$columnNamesToDrop = $processedFields;
$sqlTable = new Table($this->db, $this); $sqlTable = new Table($this->db, $this);
$sqlTable->fromTable($table) $sqlTable->fromTable($table)
->dropColumn($field) ->dropColumn($columnNamesToDrop)
->run(); ->run();
return ''; return ''; // Why empty string?
case 'CHANGE': case 'CHANGE':
(new Table($this->db, $this)) (new Table($this->db, $this))
->fromTable($table) ->fromTable($table)
->modifyColumn($field) ->modifyColumn($processedFields) // @TODO Bug: should be NOT processed fields
->run(); ->run();
return null; return null; // Why null?
default: default:
return parent::_alterTable($alterType, $table, $field); return parent::_alterTable($alterType, $table, $processedFields);
} }
} }
/** /**
* Process column * Process column
*/ */
protected function _processColumn(array $field): string protected function _processColumn(array $processedField): string
{ {
if ($field['type'] === 'TEXT' && strpos($field['length'], "('") === 0) { if ($processedField['type'] === 'TEXT' && strpos($processedField['length'], "('") === 0) {
$field['type'] .= ' CHECK(' . $this->db->escapeIdentifiers($field['name']) $processedField['type'] .= ' CHECK(' . $this->db->escapeIdentifiers($processedField['name'])
. ' IN ' . $field['length'] . ')'; . ' IN ' . $processedField['length'] . ')';
} }
return $this->db->escapeIdentifiers($field['name']) return $this->db->escapeIdentifiers($processedField['name'])
. ' ' . $field['type'] . ' ' . $processedField['type']
. $field['auto_increment'] . $processedField['auto_increment']
. $field['null'] . $processedField['null']
. $field['unique'] . $processedField['unique']
. $field['default']; . $processedField['default'];
} }
/** /**
@ -183,8 +188,11 @@ class Forge extends BaseForge
*/ */
protected function _attributeAutoIncrement(array &$attributes, array &$field) protected function _attributeAutoIncrement(array &$attributes, array &$field)
{ {
if (! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true if (
&& stripos($field['type'], 'int') !== false) { ! empty($attributes['AUTO_INCREMENT'])
&& $attributes['AUTO_INCREMENT'] === true
&& stripos($field['type'], 'int') !== false
) {
$field['type'] = 'INTEGER PRIMARY KEY'; $field['type'] = 'INTEGER PRIMARY KEY';
$field['default'] = ''; $field['default'] = '';
$field['null'] = ''; $field['null'] = '';

View File

@ -12,7 +12,7 @@
namespace CodeIgniter\Database\SQLite3; namespace CodeIgniter\Database\SQLite3;
use CodeIgniter\Database\Exceptions\DataException; use CodeIgniter\Database\Exceptions\DataException;
use stdclass; use stdClass;
/** /**
* Class Table * Class Table
@ -28,8 +28,7 @@ class Table
/** /**
* All of the fields this table represents. * All of the fields this table represents.
* *
* @var array * @var array<string, array<string, bool|int|string|null>> [name => attributes]
* @phpstan-var array<string, array<string, bool|int|string|null>>
*/ */
protected $fields = []; protected $fields = [];
@ -114,7 +113,7 @@ class Table
// if primary key index exists twice then remove psuedo index name 'primary'. // if primary key index exists twice then remove psuedo index name 'primary'.
$primaryIndexes = array_filter($this->keys, static fn ($index) => $index['type'] === 'primary'); $primaryIndexes = array_filter($this->keys, static fn ($index) => $index['type'] === 'primary');
if (! empty($primaryIndexes) && count($primaryIndexes) > 1 && array_key_exists('primary', $this->keys)) { if ($primaryIndexes !== [] && count($primaryIndexes) > 1 && array_key_exists('primary', $this->keys)) {
unset($this->keys['primary']); unset($this->keys['primary']);
} }
@ -157,7 +156,7 @@ class Table
/** /**
* Drops columns from the table. * Drops columns from the table.
* *
* @param array|string $columns * @param list<string>|string $columns Column names to drop.
* *
* @return Table * @return Table
*/ */
@ -178,14 +177,15 @@ class Table
} }
/** /**
* Modifies a field, including changing data type, * Modifies a field, including changing data type, renaming, etc.
* renaming, etc. *
* @param list<array<string, bool|int|string|null>> $fieldsToModify
* *
* @return Table * @return Table
*/ */
public function modifyColumn(array $fields) public function modifyColumn(array $fieldsToModify)
{ {
foreach ($fields as $field) { foreach ($fieldsToModify as $field) {
$oldName = $field['name']; $oldName = $field['name'];
unset($field['name']); unset($field['name']);

Some files were not shown because too many files have changed in this diff Show More