mirror of
https://github.com/codeigniter4/CodeIgniter4.git
synced 2025-02-20 11:44:28 +08:00
Merge remote-tracking branch 'upstream/develop' into 4.4
Conflicts: phpstan-baseline.neon.dist
This commit is contained in:
commit
50c225c54b
@ -13,7 +13,10 @@ use CodeIgniter\Config\AutoloadConfig;
|
||||
* can find the files as needed.
|
||||
*
|
||||
* NOTE: If you use an identical key in $psr4 or $classmap, then
|
||||
* the values in this file will overwrite the framework's values.
|
||||
* the values in this file will overwrite the framework's values.
|
||||
*
|
||||
* NOTE: This class is required prior to Autoloader instantiation,
|
||||
* and does not extend BaseConfig.
|
||||
*/
|
||||
class Autoload extends AutoloadConfig
|
||||
{
|
||||
|
@ -95,7 +95,8 @@ class Cache extends BaseConfig
|
||||
* A string of reserved characters that will not be allowed in keys or tags.
|
||||
* Strings that violate this restriction will cause handlers to throw.
|
||||
* Default: {}()/\@:
|
||||
* Note: The default set is required for PSR-6 compliance.
|
||||
*
|
||||
* NOTE: The default set is required for PSR-6 compliance.
|
||||
*/
|
||||
public string $reservedCharacters = '{}()/\@:';
|
||||
|
||||
|
@ -39,7 +39,7 @@ class ContentSecurityPolicy extends BaseConfig
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Sources allowed
|
||||
// Note: once you set a policy to 'none', it cannot be further restricted
|
||||
// NOTE: once you set a policy to 'none', it cannot be further restricted
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
|
@ -99,7 +99,7 @@ class Logger extends BaseConfig
|
||||
* An extension of 'php' allows for protecting the log files via basic
|
||||
* scripting, when they are to be stored under a publicly accessible directory.
|
||||
*
|
||||
* Note: Leaving it blank will default to 'log'.
|
||||
* NOTE: Leaving it blank will default to 'log'.
|
||||
*/
|
||||
'fileExtension' => '',
|
||||
|
||||
|
@ -40,7 +40,7 @@ class Migrations extends BaseConfig
|
||||
* using the CLI command:
|
||||
* > php spark make:migration
|
||||
*
|
||||
* Note: if you set an unsupported format, migration runner will not find
|
||||
* NOTE: if you set an unsupported format, migration runner will not find
|
||||
* your migration files.
|
||||
*
|
||||
* Supported formats:
|
||||
|
@ -4,6 +4,12 @@ namespace Config;
|
||||
|
||||
use CodeIgniter\Modules\Modules as BaseModules;
|
||||
|
||||
/**
|
||||
* Modules Configuration.
|
||||
*
|
||||
* NOTE: This class is required prior to Autoloader instantiation,
|
||||
* and does not extend BaseConfig.
|
||||
*/
|
||||
class Modules extends BaseModules
|
||||
{
|
||||
/**
|
||||
|
@ -12,6 +12,11 @@ namespace Config;
|
||||
* share a system folder between multiple applications, and more.
|
||||
*
|
||||
* All paths are relative to the project's root folder.
|
||||
*
|
||||
* NOTE: This class is required prior to Autoloader instantiation,
|
||||
* and does not extend BaseConfig.
|
||||
*
|
||||
* @immutable
|
||||
*/
|
||||
class Paths
|
||||
{
|
||||
|
@ -24,7 +24,7 @@
|
||||
"phpunit/phpcov": "^8.2",
|
||||
"phpunit/phpunit": "^9.1",
|
||||
"predis/predis": "^1.1 || ^2.0",
|
||||
"rector/rector": "0.17.3",
|
||||
"rector/rector": "0.17.6",
|
||||
"vimeo/psalm": "^5.0"
|
||||
},
|
||||
"suggest": {
|
||||
|
@ -10,11 +10,6 @@ parameters:
|
||||
count: 1
|
||||
path: system/Autoloader/Autoloader.php
|
||||
|
||||
-
|
||||
message: "#^Property CodeIgniter\\\\Cache\\\\Handlers\\\\RedisHandler\\:\\:\\$redis \\(Redis\\) in isset\\(\\) is not nullable\\.$#"
|
||||
count: 1
|
||||
path: system/Cache/Handlers/RedisHandler.php
|
||||
|
||||
-
|
||||
message: "#^Property CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:\\$db \\(CodeIgniter\\\\Database\\\\BaseConnection\\) in empty\\(\\) is not falsy\\.$#"
|
||||
count: 1
|
||||
@ -192,16 +187,11 @@ parameters:
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method CodeIgniter\\\\Router\\\\RouteCollectionInterface\\:\\:getRegisteredControllers\\(.*\\)\\.$#"
|
||||
count: 2
|
||||
path: system/Router/Router.php
|
||||
|
||||
-
|
||||
message: "#^Expression on left side of \\?\\? is not nullable\\.$#"
|
||||
count: 1
|
||||
path: system/Router/Router.php
|
||||
|
||||
-
|
||||
message: "#^Method CodeIgniter\\\\Router\\\\RouteCollectionInterface\\:\\:getRoutes\\(\\) invoked with 1 parameter, 0 required\\.$#"
|
||||
message: "#^Expression on left side of \\?\\? is not nullable\\.$#"
|
||||
count: 1
|
||||
path: system/Router/Router.php
|
||||
|
||||
|
@ -1345,7 +1345,7 @@ abstract class BaseModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to set validation messages.
|
||||
* Allows to set (and reset) validation messages.
|
||||
* It could be used when you have to change default or override current validate messages.
|
||||
*
|
||||
* @param array $validationMessages Value
|
||||
@ -1376,7 +1376,7 @@ abstract class BaseModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to set validation rules.
|
||||
* Allows to set (and reset) validation rules.
|
||||
* It could be used when you have to change default or override current validate rules.
|
||||
*
|
||||
* @param array $validationRules Value
|
||||
@ -1401,6 +1401,17 @@ abstract class BaseModel
|
||||
*/
|
||||
public function setValidationRule(string $field, $fieldRules)
|
||||
{
|
||||
$rules = $this->validationRules;
|
||||
|
||||
// ValidationRules can be either a string, which is the group name,
|
||||
// or an array of rules.
|
||||
if (is_string($rules)) {
|
||||
[$rules, $customErrors] = $this->validation->loadRuleGroup($rules);
|
||||
|
||||
$this->validationRules = $rules;
|
||||
$this->validationMessages = $this->validationMessages + $customErrors;
|
||||
}
|
||||
|
||||
$this->validationRules[$field] = $fieldRules;
|
||||
|
||||
return $this;
|
||||
@ -1466,7 +1477,9 @@ abstract class BaseModel
|
||||
// ValidationRules can be either a string, which is the group name,
|
||||
// or an array of rules.
|
||||
if (is_string($rules)) {
|
||||
$rules = $this->validation->loadRuleGroup($rules);
|
||||
[$rules, $customErrors] = $this->validation->loadRuleGroup($rules);
|
||||
|
||||
$this->validationMessages = $this->validationMessages + $customErrors;
|
||||
}
|
||||
|
||||
if (isset($options['except'])) {
|
||||
|
@ -38,7 +38,7 @@ class RedisHandler extends BaseHandler
|
||||
/**
|
||||
* Redis connection
|
||||
*
|
||||
* @var Redis
|
||||
* @var Redis|null
|
||||
*/
|
||||
protected $redis;
|
||||
|
||||
|
@ -484,7 +484,7 @@ class Services extends BaseService
|
||||
return static::getSharedInstance('parser', $viewPath, $config);
|
||||
}
|
||||
|
||||
$viewPath = $viewPath ?: config(Paths::class)->viewDirectory;
|
||||
$viewPath = $viewPath ?: (new Paths())->viewDirectory;
|
||||
$config ??= config(ViewConfig::class);
|
||||
|
||||
return new Parser($config, $viewPath, AppServices::locator(), CI_DEBUG, AppServices::logger());
|
||||
@ -503,7 +503,7 @@ class Services extends BaseService
|
||||
return static::getSharedInstance('renderer', $viewPath, $config);
|
||||
}
|
||||
|
||||
$viewPath = $viewPath ?: config(Paths::class)->viewDirectory;
|
||||
$viewPath = $viewPath ?: (new Paths())->viewDirectory;
|
||||
$config ??= config(ViewConfig::class);
|
||||
|
||||
return new View($config, $viewPath, AppServices::locator(), CI_DEBUG, AppServices::logger());
|
||||
|
@ -69,7 +69,9 @@ if (! function_exists('number_to_amount')) {
|
||||
*
|
||||
* @see https://simple.wikipedia.org/wiki/Names_for_large_numbers
|
||||
*
|
||||
* @param int|string $num
|
||||
* @param int|string $num Will be cast as int
|
||||
* @param int $precision [optional] The optional number of decimal digits to round to.
|
||||
* @param string|null $locale [optional]
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
@ -91,19 +93,19 @@ if (! function_exists('number_to_amount')) {
|
||||
$generalLocale = substr($locale, 0, $underscorePos);
|
||||
}
|
||||
|
||||
if ($num > 1_000_000_000_000_000) {
|
||||
if ($num >= 1_000_000_000_000_000) {
|
||||
$suffix = lang('Number.quadrillion', [], $generalLocale);
|
||||
$num = round(($num / 1_000_000_000_000_000), $precision);
|
||||
} elseif ($num > 1_000_000_000_000) {
|
||||
} elseif ($num >= 1_000_000_000_000) {
|
||||
$suffix = lang('Number.trillion', [], $generalLocale);
|
||||
$num = round(($num / 1_000_000_000_000), $precision);
|
||||
} elseif ($num > 1_000_000_000) {
|
||||
} elseif ($num >= 1_000_000_000) {
|
||||
$suffix = lang('Number.billion', [], $generalLocale);
|
||||
$num = round(($num / 1_000_000_000), $precision);
|
||||
} elseif ($num > 1_000_000) {
|
||||
} elseif ($num >= 1_000_000) {
|
||||
$suffix = lang('Number.million', [], $generalLocale);
|
||||
$num = round(($num / 1_000_000), $precision);
|
||||
} elseif ($num > 1000) {
|
||||
} elseif ($num >= 1000) {
|
||||
$suffix = lang('Number.thousand', [], $generalLocale);
|
||||
$num = round(($num / 1000), $precision);
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace CodeIgniter\Router;
|
||||
|
||||
use Closure;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
|
||||
/**
|
||||
@ -19,11 +20,11 @@ use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
final class AutoRouter implements AutoRouterInterface
|
||||
{
|
||||
/**
|
||||
* List of controllers registered for the CLI verb that should not be accessed in the web.
|
||||
* List of CLI routes that do not contain '*' routes.
|
||||
*
|
||||
* @var class-string[]
|
||||
* @var array<string, Closure|string> [routeKey => handler]
|
||||
*/
|
||||
private array $protectedControllers;
|
||||
private array $cliRoutes;
|
||||
|
||||
/**
|
||||
* Sub-directory that contains the requested controller class.
|
||||
@ -58,17 +59,17 @@ final class AutoRouter implements AutoRouterInterface
|
||||
private string $defaultNamespace;
|
||||
|
||||
public function __construct(
|
||||
array $protectedControllers,
|
||||
array $cliRoutes,
|
||||
string $defaultNamespace,
|
||||
string $defaultController,
|
||||
string $defaultMethod,
|
||||
bool $translateURIDashes,
|
||||
string $httpVerb
|
||||
) {
|
||||
$this->protectedControllers = $protectedControllers;
|
||||
$this->defaultNamespace = $defaultNamespace;
|
||||
$this->translateURIDashes = $translateURIDashes;
|
||||
$this->httpVerb = $httpVerb;
|
||||
$this->cliRoutes = $cliRoutes;
|
||||
$this->defaultNamespace = $defaultNamespace;
|
||||
$this->translateURIDashes = $translateURIDashes;
|
||||
$this->httpVerb = $httpVerb;
|
||||
|
||||
$this->controller = $defaultController;
|
||||
$this->method = $defaultMethod;
|
||||
@ -126,18 +127,31 @@ final class AutoRouter implements AutoRouterInterface
|
||||
$controller .= $controllerName;
|
||||
|
||||
$controller = strtolower($controller);
|
||||
$methodName = strtolower($this->methodName());
|
||||
|
||||
foreach ($this->protectedControllers as $controllerInRoute) {
|
||||
if (! is_string($controllerInRoute)) {
|
||||
continue;
|
||||
}
|
||||
if (strtolower($controllerInRoute) !== $controller) {
|
||||
continue;
|
||||
}
|
||||
foreach ($this->cliRoutes as $handler) {
|
||||
if (is_string($handler)) {
|
||||
$handler = strtolower($handler);
|
||||
|
||||
throw new PageNotFoundException(
|
||||
'Cannot access the controller in a CLI Route. Controller: ' . $controllerInRoute
|
||||
);
|
||||
// Like $routes->cli('hello/(:segment)', 'Home::$1')
|
||||
if (strpos($handler, '::$') !== false) {
|
||||
throw new PageNotFoundException(
|
||||
'Cannot access CLI Route: ' . $uri
|
||||
);
|
||||
}
|
||||
|
||||
if (strpos($handler, $controller . '::' . $methodName) === 0) {
|
||||
throw new PageNotFoundException(
|
||||
'Cannot access CLI Route: ' . $uri
|
||||
);
|
||||
}
|
||||
|
||||
if ($handler === $controller) {
|
||||
throw new PageNotFoundException(
|
||||
'Cannot access CLI Route: ' . $uri
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -550,8 +550,10 @@ class RouteCollection implements RouteCollectionInterface
|
||||
|
||||
/**
|
||||
* Returns the raw array of available routes.
|
||||
*
|
||||
* @param bool $includeWildcard Whether to include '*' routes.
|
||||
*/
|
||||
public function getRoutes(?string $verb = null): array
|
||||
public function getRoutes(?string $verb = null, bool $includeWildcard = true): array
|
||||
{
|
||||
if (empty($verb)) {
|
||||
$verb = $this->getHTTPVerb();
|
||||
@ -567,7 +569,7 @@ class RouteCollection implements RouteCollectionInterface
|
||||
if (isset($this->routes[$verb])) {
|
||||
// Keep current verb's routes at the beginning, so they're matched
|
||||
// before any of the generic, "add" routes.
|
||||
$collection = $this->routes[$verb] + ($this->routes['*'] ?? []);
|
||||
$collection = $includeWildcard ? $this->routes[$verb] + ($this->routes['*'] ?? []) : $this->routes[$verb];
|
||||
|
||||
foreach ($collection as $routeKey => $r) {
|
||||
$routes[$routeKey] = $r['handler'];
|
||||
|
@ -145,7 +145,7 @@ class Router implements RouterInterface
|
||||
);
|
||||
} else {
|
||||
$this->autoRouter = new AutoRouter(
|
||||
$this->collection->getRegisteredControllers('cli'),
|
||||
$this->collection->getRoutes('cli', false), // @phpstan-ignore-line
|
||||
$this->collection->getDefaultNamespace(),
|
||||
$this->collection->getDefaultController(),
|
||||
$this->collection->getDefaultMethod(),
|
||||
@ -393,6 +393,7 @@ class Router implements RouterInterface
|
||||
*/
|
||||
protected function checkRoutes(string $uri): bool
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
$routes = $this->collection->getRoutes($this->collection->getHTTPVerb());
|
||||
|
||||
// Don't waste any time
|
||||
|
@ -696,7 +696,7 @@ class Validation implements ValidationInterface
|
||||
* same format used with setRules(). Additionally, check
|
||||
* for {group}_errors for an array of custom error messages.
|
||||
*
|
||||
* @return array
|
||||
* @return array<int, array> [rules, customErrors]
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
@ -724,7 +724,7 @@ class Validation implements ValidationInterface
|
||||
$this->customErrors = $this->config->{$errorName};
|
||||
}
|
||||
|
||||
return $this->rules;
|
||||
return [$this->rules, $this->customErrors];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -178,7 +178,8 @@ class Cell
|
||||
}
|
||||
|
||||
// locate and return an instance of the cell
|
||||
$object = Factories::cells($class);
|
||||
// @TODO extend Factories to be able to load classes with the same short name.
|
||||
$object = class_exists($class) ? new $class() : Factories::cells($class);
|
||||
|
||||
if (! is_object($object)) {
|
||||
throw ViewException::forInvalidCellClass($class);
|
||||
|
32
tests/_support/Config/Validation.php
Normal file
32
tests/_support/Config/Validation.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Tests\Support\Config;
|
||||
|
||||
use Config\Validation as ValidationConfig;
|
||||
|
||||
class Validation extends ValidationConfig
|
||||
{
|
||||
public $signup = [
|
||||
'id' => 'permit_empty|is_natural_no_zero',
|
||||
'name' => [
|
||||
'required',
|
||||
'min_length[3]',
|
||||
],
|
||||
'token' => 'permit_empty|in_list[{id}]',
|
||||
];
|
||||
public $signup_errors = [
|
||||
'name' => [
|
||||
'required' => 'You forgot to name the baby.',
|
||||
'min_length' => 'Too short, man!',
|
||||
],
|
||||
];
|
||||
}
|
27
tests/_support/Models/ValidModelRuleGroup.php
Normal file
27
tests/_support/Models/ValidModelRuleGroup.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Tests\Support\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class ValidModelRuleGroup extends Model
|
||||
{
|
||||
protected $table = 'job';
|
||||
protected $returnType = 'object';
|
||||
protected $useSoftDeletes = false;
|
||||
protected $dateFormat = 'int';
|
||||
protected $allowedFields = [
|
||||
'name',
|
||||
'description',
|
||||
];
|
||||
protected $validationRules = 'signup';
|
||||
}
|
26
tests/_support/View/OtherCells/SampleClass.php
Normal file
26
tests/_support/View/OtherCells/SampleClass.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Tests\Support\View\OtherCells;
|
||||
|
||||
/**
|
||||
* Two classes with the same short name.
|
||||
*
|
||||
* - Tests\Support\View\SampleClass
|
||||
* - Tests\Support\View\OtherCells\SampleClass
|
||||
*/
|
||||
class SampleClass
|
||||
{
|
||||
public function hello()
|
||||
{
|
||||
return 'Good-bye!';
|
||||
}
|
||||
}
|
@ -101,26 +101,45 @@ final class NumberHelperTest extends CIUnitTestCase
|
||||
public function testThousands()
|
||||
{
|
||||
$this->assertSame('123 thousand', number_to_amount('123,000', 0, 'en_US'));
|
||||
$this->assertSame('1 thousand', number_to_amount('1000', 0, 'en_US'));
|
||||
$this->assertSame('999 thousand', number_to_amount('999499', 0, 'en_US'));
|
||||
$this->assertSame('1,000 thousand', number_to_amount('999500', 0, 'en_US'));
|
||||
$this->assertSame('1,000 thousand', number_to_amount('999999', 0, 'en_US'));
|
||||
}
|
||||
|
||||
public function testMillions()
|
||||
{
|
||||
$this->assertSame('123.4 million', number_to_amount('123,400,000', 1, 'en_US'));
|
||||
$this->assertSame('1 million', number_to_amount('1,000,000', 1, 'en_US'));
|
||||
$this->assertSame('1.5 million', number_to_amount('1,499,999', 1, 'en_US'));
|
||||
$this->assertSame('1.5 million', number_to_amount('1,500,000', 1, 'en_US'));
|
||||
$this->assertSame('1.5 million', number_to_amount('1,549,999', 1, 'en_US'));
|
||||
$this->assertSame('1.6 million', number_to_amount('1,550,000', 1, 'en_US'));
|
||||
$this->assertSame('999.5 million', number_to_amount('999,500,000', 1, 'en_US'));
|
||||
$this->assertSame('1,000 million', number_to_amount('999,500,000', 0, 'en_US'));
|
||||
$this->assertSame('1,000 million', number_to_amount('999,999,999', 1, 'en_US'));
|
||||
}
|
||||
|
||||
public function testBillions()
|
||||
{
|
||||
$this->assertSame('123.46 billion', number_to_amount('123,456,000,000', 2, 'en_US'));
|
||||
$this->assertSame('1 billion', number_to_amount('1,000,000,000', 2, 'en_US'));
|
||||
$this->assertSame('1,000 billion', number_to_amount('999,999,999,999', 2, 'en_US'));
|
||||
}
|
||||
|
||||
public function testTrillions()
|
||||
{
|
||||
$this->assertSame('123.457 trillion', number_to_amount('123,456,700,000,000', 3, 'en_US'));
|
||||
$this->assertSame('1 trillion', number_to_amount('1,000,000,000,000', 3, 'en_US'));
|
||||
$this->assertSame('1,000 trillion', number_to_amount('999,999,999,999,999', 3, 'en_US'));
|
||||
}
|
||||
|
||||
public function testQuadrillions()
|
||||
{
|
||||
$this->assertSame('123.5 quadrillion', number_to_amount('123,456,700,000,000,000', 1, 'en_US'));
|
||||
$this->assertSame('1 quadrillion', number_to_amount('1,000,000,000,000,000', 0, 'en_US'));
|
||||
$this->assertSame('1,000 quadrillion', number_to_amount('999,999,999,999,999,999', 0, 'en_US'));
|
||||
$this->assertSame('1,000 quadrillion', number_to_amount('1,000,000,000,000,000,000', 0, 'en_US'));
|
||||
}
|
||||
|
||||
public function testCurrencyCurrentLocale()
|
||||
|
476
tests/system/Models/ValidationModelRuleGroupTest.php
Normal file
476
tests/system/Models/ValidationModelRuleGroupTest.php
Normal file
@ -0,0 +1,476 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Models;
|
||||
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use CodeIgniter\Model;
|
||||
use Config\Services;
|
||||
use stdClass;
|
||||
use Tests\Support\Config\Validation;
|
||||
use Tests\Support\Models\JobModel;
|
||||
use Tests\Support\Models\SimpleEntity;
|
||||
use Tests\Support\Models\ValidErrorsModel;
|
||||
use Tests\Support\Models\ValidModelRuleGroup;
|
||||
|
||||
/**
|
||||
* @group DatabaseLive
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ValidationModelRuleGroupTest extends LiveModelTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->createModel(ValidModelRuleGroup::class);
|
||||
}
|
||||
|
||||
protected function createModel(string $modelName, ?BaseConnection $db = null): Model
|
||||
{
|
||||
$config = new Validation();
|
||||
$validation = new \CodeIgniter\Validation\Validation($config, Services::renderer());
|
||||
|
||||
$this->db = $db ?? $this->db;
|
||||
$this->model = new $modelName($this->db, $validation);
|
||||
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
public function testValid(): void
|
||||
{
|
||||
$data = [
|
||||
'name' => 'some name',
|
||||
'description' => 'some great marketing stuff',
|
||||
];
|
||||
|
||||
$this->assertIsInt($this->model->insert($data));
|
||||
|
||||
$errors = $this->model->errors();
|
||||
$this->assertSame([], $errors);
|
||||
}
|
||||
|
||||
public function testValidationBasics(): void
|
||||
{
|
||||
$data = [
|
||||
'name' => null,
|
||||
'description' => 'some great marketing stuff',
|
||||
];
|
||||
|
||||
$this->assertFalse($this->model->insert($data));
|
||||
|
||||
$errors = $this->model->errors();
|
||||
$this->assertSame('You forgot to name the baby.', $errors['name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/codeigniter4/CodeIgniter4/issues/5859
|
||||
*/
|
||||
public function testValidationTwice(): void
|
||||
{
|
||||
$data = [
|
||||
'name' => null,
|
||||
'description' => 'some great marketing stuff',
|
||||
];
|
||||
|
||||
$this->assertFalse($this->model->insert($data));
|
||||
|
||||
$errors = $this->model->errors();
|
||||
$this->assertSame('You forgot to name the baby.', $errors['name']);
|
||||
|
||||
$data = [
|
||||
'name' => 'some name',
|
||||
'description' => 'some great marketing stuff',
|
||||
];
|
||||
|
||||
$this->assertIsInt($this->model->insert($data));
|
||||
}
|
||||
|
||||
public function testValidationWithSetValidationRule(): void
|
||||
{
|
||||
$data = [
|
||||
'name' => 'some name',
|
||||
'description' => 'some great marketing stuff',
|
||||
];
|
||||
|
||||
$this->model->setValidationRule('description', [
|
||||
'rules' => 'required|min_length[50]',
|
||||
'errors' => [
|
||||
'min_length' => 'Description is too short baby.',
|
||||
],
|
||||
]);
|
||||
$this->assertFalse($this->model->insert($data));
|
||||
|
||||
$errors = $this->model->errors();
|
||||
$this->assertSame('Description is too short baby.', $errors['description']);
|
||||
}
|
||||
|
||||
public function testValidationWithSetValidationRules(): void
|
||||
{
|
||||
$data = [
|
||||
'name' => '',
|
||||
'description' => 'some great marketing stuff',
|
||||
];
|
||||
|
||||
$this->model->setValidationRules([
|
||||
'name' => [
|
||||
'rules' => 'required',
|
||||
'errors' => [
|
||||
'required' => 'Give me a name baby.',
|
||||
],
|
||||
],
|
||||
'description' => [
|
||||
'rules' => 'required|min_length[50]',
|
||||
'errors' => [
|
||||
'min_length' => 'Description is too short baby.',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->assertFalse($this->model->insert($data));
|
||||
|
||||
$errors = $this->model->errors();
|
||||
$this->assertSame('Give me a name baby.', $errors['name']);
|
||||
$this->assertSame('Description is too short baby.', $errors['description']);
|
||||
}
|
||||
|
||||
public function testValidationWithSetValidationMessage(): void
|
||||
{
|
||||
$data = [
|
||||
'name' => null,
|
||||
'description' => 'some great marketing stuff',
|
||||
];
|
||||
|
||||
$this->model->setValidationMessage('name', [
|
||||
'required' => 'Your baby name is missing.',
|
||||
'min_length' => 'Too short, man!',
|
||||
]);
|
||||
$this->assertFalse($this->model->insert($data));
|
||||
|
||||
$errors = $this->model->errors();
|
||||
$this->assertSame('Your baby name is missing.', $errors['name']);
|
||||
}
|
||||
|
||||
public function testValidationPlaceholdersSuccess(): void
|
||||
{
|
||||
$data = [
|
||||
'name' => 'abc',
|
||||
'id' => 13,
|
||||
'token' => 13,
|
||||
];
|
||||
|
||||
$this->assertTrue($this->model->validate($data));
|
||||
}
|
||||
|
||||
public function testValidationPlaceholdersFail(): void
|
||||
{
|
||||
$data = [
|
||||
'name' => 'abc',
|
||||
'id' => 13,
|
||||
'token' => 12,
|
||||
];
|
||||
|
||||
$this->assertFalse($this->model->validate($data));
|
||||
}
|
||||
|
||||
public function testSkipValidation(): void
|
||||
{
|
||||
$data = [
|
||||
'name' => '2',
|
||||
'description' => 'some great marketing stuff',
|
||||
];
|
||||
|
||||
$this->assertIsNumeric($this->model->skipValidation(true)->insert($data));
|
||||
}
|
||||
|
||||
public function testCleanValidationRemovesAllWhenNoDataProvided(): void
|
||||
{
|
||||
$cleaner = $this->getPrivateMethodInvoker($this->model, 'cleanValidationRules');
|
||||
|
||||
$rules = [
|
||||
'name' => 'required',
|
||||
'foo' => 'bar',
|
||||
];
|
||||
|
||||
$rules = $cleaner($rules, null);
|
||||
$this->assertEmpty($rules);
|
||||
}
|
||||
|
||||
public function testCleanValidationRemovesOnlyForFieldsNotProvided(): void
|
||||
{
|
||||
$cleaner = $this->getPrivateMethodInvoker($this->model, 'cleanValidationRules');
|
||||
|
||||
$rules = [
|
||||
'name' => 'required',
|
||||
'foo' => 'required',
|
||||
];
|
||||
|
||||
$data = [
|
||||
'foo' => 'bar',
|
||||
];
|
||||
|
||||
$rules = $cleaner($rules, $data);
|
||||
$this->assertArrayHasKey('foo', $rules);
|
||||
$this->assertArrayNotHasKey('name', $rules);
|
||||
}
|
||||
|
||||
public function testCleanValidationReturnsAllWhenAllExist(): void
|
||||
{
|
||||
$cleaner = $this->getPrivateMethodInvoker($this->model, 'cleanValidationRules');
|
||||
|
||||
$rules = [
|
||||
'name' => 'required',
|
||||
'foo' => 'required',
|
||||
];
|
||||
|
||||
$data = [
|
||||
'foo' => 'bar',
|
||||
'name' => null,
|
||||
];
|
||||
|
||||
$rules = $cleaner($rules, $data);
|
||||
$this->assertArrayHasKey('foo', $rules);
|
||||
$this->assertArrayHasKey('name', $rules);
|
||||
}
|
||||
|
||||
public function testValidationPassesWithMissingFields(): void
|
||||
{
|
||||
$data = [
|
||||
'foo' => 'bar',
|
||||
];
|
||||
|
||||
$result = $this->model->validate($data);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/codeigniter4/CodeIgniter4/issues/1584
|
||||
*/
|
||||
public function testUpdateWithValidation(): void
|
||||
{
|
||||
$data = [
|
||||
'description' => 'This is a first test!',
|
||||
'name' => 'valid',
|
||||
'id' => 42,
|
||||
'token' => 42,
|
||||
];
|
||||
|
||||
$id = $this->model->insert($data);
|
||||
$this->assertTrue((bool) $id);
|
||||
|
||||
$data['description'] = 'This is a second test!';
|
||||
unset($data['name']);
|
||||
|
||||
$result = $this->model->update($id, $data);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/codeigniter4/CodeIgniter4/issues/1717
|
||||
*/
|
||||
public function testRequiredWithValidationEmptyString(): void
|
||||
{
|
||||
$this->assertFalse($this->model->insert(['name' => '']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/codeigniter4/CodeIgniter4/issues/1717
|
||||
*/
|
||||
public function testRequiredWithValidationNull(): void
|
||||
{
|
||||
$this->assertFalse($this->model->insert(['name' => null]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/codeigniter4/CodeIgniter4/issues/1717
|
||||
*/
|
||||
public function testRequiredWithValidationTrue(): void
|
||||
{
|
||||
$data = [
|
||||
'name' => 'foobar',
|
||||
'description' => 'just because we have to',
|
||||
];
|
||||
|
||||
$this->assertNotFalse($this->model->insert($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/codeigniter4/CodeIgniter4/issues/1574
|
||||
*/
|
||||
public function testValidationIncludingErrors(): void
|
||||
{
|
||||
$data = [
|
||||
'description' => 'This is a first test!',
|
||||
'name' => 'valid',
|
||||
'id' => 42,
|
||||
'token' => 42,
|
||||
];
|
||||
|
||||
$this->createModel(ValidErrorsModel::class);
|
||||
|
||||
$id = $this->model->insert($data);
|
||||
$this->assertFalse((bool) $id);
|
||||
$this->assertSame('Minimum Length Error', $this->model->errors()['name']);
|
||||
}
|
||||
|
||||
public function testValidationByObject(): void
|
||||
{
|
||||
$data = new stdClass();
|
||||
|
||||
$data->name = 'abc';
|
||||
$data->id = '13';
|
||||
$data->token = '13';
|
||||
|
||||
$this->assertTrue($this->model->validate($data));
|
||||
}
|
||||
|
||||
public function testGetValidationRules(): void
|
||||
{
|
||||
$this->createModel(JobModel::class);
|
||||
$this->setPrivateProperty($this->model, 'validationRules', ['description' => 'required']);
|
||||
|
||||
$rules = $this->model->getValidationRules();
|
||||
$this->assertSame('required', $rules['description']);
|
||||
}
|
||||
|
||||
public function testGetValidationMessages(): void
|
||||
{
|
||||
$jobData = [
|
||||
[
|
||||
'name' => 'Comedian',
|
||||
'description' => null,
|
||||
],
|
||||
];
|
||||
|
||||
$this->createModel(JobModel::class);
|
||||
$this->setPrivateProperty($this->model, 'validationRules', ['description' => 'required']);
|
||||
$this->setPrivateProperty($this->model, 'validationMessages', ['description' => 'Description field is required.']);
|
||||
|
||||
$this->assertFalse($this->model->insertBatch($jobData));
|
||||
|
||||
$error = $this->model->getValidationMessages();
|
||||
$this->assertSame('Description field is required.', $error['description']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/codeigniter4/CodeIgniter4/issues/6577
|
||||
*/
|
||||
public function testUpdateEntityWithPropertyCleanValidationRulesTrueAndCallingCleanRulesFalse()
|
||||
{
|
||||
$model = new class () extends Model {
|
||||
protected $table = 'test';
|
||||
protected $allowedFields = ['field1', 'field2', 'field3', 'field4'];
|
||||
protected $returnType = SimpleEntity::class;
|
||||
protected $validationRules = [
|
||||
'field1' => 'required_with[field2,field3,field4]',
|
||||
'field2' => 'permit_empty',
|
||||
'field3' => 'permit_empty',
|
||||
'field4' => 'permit_empty',
|
||||
];
|
||||
};
|
||||
|
||||
// Simulate to get the entity from the database.
|
||||
$entity = new SimpleEntity();
|
||||
$entity->setAttributes([
|
||||
'id' => '1',
|
||||
'field1' => 'value1',
|
||||
'field2' => 'value2',
|
||||
'field3' => '',
|
||||
'field4' => '',
|
||||
]);
|
||||
|
||||
// Change field1 value.
|
||||
$entity->field1 = '';
|
||||
|
||||
// Set $cleanValidationRules to false by cleanRules()
|
||||
$model->cleanRules(false)->save($entity);
|
||||
|
||||
$errors = $model->errors();
|
||||
$this->assertCount(1, $errors);
|
||||
$this->assertSame(
|
||||
$errors['field1'],
|
||||
'The field1 field is required when field2,field3,field4 is present.'
|
||||
);
|
||||
}
|
||||
|
||||
public function testUpdateEntityWithPropertyCleanValidationRulesFalse()
|
||||
{
|
||||
$model = new class () extends Model {
|
||||
protected $table = 'test';
|
||||
protected $allowedFields = ['field1', 'field2', 'field3', 'field4'];
|
||||
protected $returnType = SimpleEntity::class;
|
||||
protected $validationRules = [
|
||||
'field1' => 'required_with[field2,field3,field4]',
|
||||
'field2' => 'permit_empty',
|
||||
'field3' => 'permit_empty',
|
||||
'field4' => 'permit_empty',
|
||||
];
|
||||
|
||||
// Set to false.
|
||||
protected $cleanValidationRules = false;
|
||||
};
|
||||
|
||||
// Simulate to get the entity from the database.
|
||||
$entity = new SimpleEntity();
|
||||
$entity->setAttributes([
|
||||
'id' => '1',
|
||||
'field1' => 'value1',
|
||||
'field2' => 'value2',
|
||||
'field3' => '',
|
||||
'field4' => '',
|
||||
]);
|
||||
|
||||
// Change field1 value.
|
||||
$entity->field1 = '';
|
||||
|
||||
$model->save($entity);
|
||||
|
||||
$errors = $model->errors();
|
||||
$this->assertCount(1, $errors);
|
||||
$this->assertSame(
|
||||
$errors['field1'],
|
||||
'The field1 field is required when field2,field3,field4 is present.'
|
||||
);
|
||||
}
|
||||
|
||||
public function testInsertEntityValidateEntireRules()
|
||||
{
|
||||
$model = new class () extends Model {
|
||||
protected $table = 'test';
|
||||
protected $allowedFields = ['field1', 'field2', 'field3', 'field4'];
|
||||
protected $returnType = SimpleEntity::class;
|
||||
protected $validationRules = [
|
||||
'field1' => 'required',
|
||||
'field2' => 'required',
|
||||
'field3' => 'permit_empty',
|
||||
'field4' => 'permit_empty',
|
||||
];
|
||||
};
|
||||
|
||||
$entity = new SimpleEntity();
|
||||
$entity->setAttributes([
|
||||
'field1' => 'value1',
|
||||
// field2 is missing
|
||||
'field3' => '',
|
||||
'field4' => '',
|
||||
]);
|
||||
|
||||
// Insert ignores $cleanValidationRules value.
|
||||
$model->insert($entity);
|
||||
|
||||
$errors = $model->errors();
|
||||
$this->assertCount(1, $errors);
|
||||
$this->assertSame(
|
||||
$errors['field2'],
|
||||
'The field2 field is required.'
|
||||
);
|
||||
}
|
||||
}
|
@ -343,7 +343,7 @@ final class FeatureTestTraitTest extends CIUnitTestCase
|
||||
public function testOpenCliRoutesFromHttpGot404($from, $to, $httpGet)
|
||||
{
|
||||
$this->expectException(PageNotFoundException::class);
|
||||
$this->expectExceptionMessage('Cannot access the controller in a CLI Route.');
|
||||
$this->expectExceptionMessage('Cannot access CLI Route: ');
|
||||
|
||||
$collection = Services::routes();
|
||||
$collection->setAutoRoute(true);
|
||||
|
@ -113,6 +113,17 @@ final class CellTest extends CIUnitTestCase
|
||||
$this->assertSame($expected, $this->cell->render('\Tests\Support\View\SampleClass::hello'));
|
||||
}
|
||||
|
||||
public function testDisplayRendersTwoCellsWithSameShortName()
|
||||
{
|
||||
$output = $this->cell->render('\Tests\Support\View\SampleClass::hello');
|
||||
|
||||
$this->assertSame('Hello', $output);
|
||||
|
||||
$output = $this->cell->render('\Tests\Support\View\OtherCells\SampleClass::hello');
|
||||
|
||||
$this->assertSame('Good-bye!', $output);
|
||||
}
|
||||
|
||||
public function testDisplayRendersWithValidParamString()
|
||||
{
|
||||
$params = 'one=two,three=four';
|
||||
|
@ -12,9 +12,16 @@ Release Date: Unreleased
|
||||
BREAKING
|
||||
********
|
||||
|
||||
- **RouteCollection:** The second parameter ``bool $includeWildcard = true`` has
|
||||
been added to the ``RouteCollection::getRoutes()`` method.
|
||||
- **AutoRouting Legacy:** The first parameter of the ``AutoRouter::__construct()``
|
||||
has been changed from ``$protectedControllers`` to ``$cliRoutes``.
|
||||
- **FeatureTestTrait:** When using :ref:`withBodyFormat() <feature-formatting-the-request>`,
|
||||
the priority of the request body has been changed.
|
||||
See :ref:`Upgrading Guide <upgrade-437-feature-testing>` for details.
|
||||
- **Validation:** The return value of ``Validation::loadRuleGroup()`` has been
|
||||
changed from "**rules array**" to "**array** of **rules array** and **customErrors array**"
|
||||
(``[rules, customErrors]``).
|
||||
|
||||
Message Changes
|
||||
***************
|
||||
@ -22,12 +29,20 @@ Message Changes
|
||||
Changes
|
||||
*******
|
||||
|
||||
- The number helper function :php:func:`number_to_amount()`, which previously
|
||||
returned "1000", has been corrected to return "1 thousand" when the number
|
||||
is exactly 1000, for example.
|
||||
|
||||
Deprecations
|
||||
************
|
||||
|
||||
Bugs Fixed
|
||||
**********
|
||||
|
||||
- **AutoRouting Legacy:** Fixed a bug that when you added a route with
|
||||
``$routes->add()``, the controller's other methods were inaccessible from the
|
||||
web browser.
|
||||
|
||||
See the repo's
|
||||
`CHANGELOG.md <https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.md>`_
|
||||
for a complete list of bugs fixed.
|
||||
|
@ -99,10 +99,12 @@ Convenience Functions
|
||||
|
||||
Two shortcut functions for Factories have been provided. These functions are always available.
|
||||
|
||||
.. _factories-config:
|
||||
|
||||
config()
|
||||
========
|
||||
|
||||
The first is ``config()`` which returns a new instance of a Config class. The only required parameter is the class name:
|
||||
The first is :php:func:`config()` which returns a new instance of a Config class. The only required parameter is the class name:
|
||||
|
||||
.. literalinclude:: factories/008.php
|
||||
|
||||
|
@ -30,6 +30,21 @@ Service Accessors
|
||||
|
||||
.. literalinclude:: common_functions/001.php
|
||||
|
||||
.. php:function:: config(string $name[, bool $getShared = true])
|
||||
|
||||
:param string $name: The config classname.
|
||||
:param bool $getShared: Whether to return a shared instance.
|
||||
:returns: The config instances.
|
||||
:rtype: object|null
|
||||
|
||||
More simple way of getting config instances from Factories.
|
||||
|
||||
See :ref:`Configuration <configuration-config>` and
|
||||
:ref:`Factories <factories-config>` for details.
|
||||
|
||||
The ``config()`` uses ``Factories::config()`` internally.
|
||||
See :ref:`factories-loading-class` for details on the first parameter ``$name``.
|
||||
|
||||
.. php:function:: cookie(string $name[, string $value = ''[, array $options = []]])
|
||||
|
||||
:param string $name: Cookie name
|
||||
|
@ -30,10 +30,12 @@ By using the ``new`` keyword to create an instance:
|
||||
|
||||
.. literalinclude:: configuration/001.php
|
||||
|
||||
.. _configuration-config:
|
||||
|
||||
config()
|
||||
--------
|
||||
|
||||
By using the ``config()`` function:
|
||||
By using the :php:func:`config()` function:
|
||||
|
||||
.. literalinclude:: configuration/002.php
|
||||
|
||||
|
@ -189,7 +189,7 @@ with the ``new`` command:
|
||||
|
||||
.. literalinclude:: modules/008.php
|
||||
|
||||
Config files are automatically discovered whenever using the ``config()`` function that is always available.
|
||||
Config files are automatically discovered whenever using the :php:func:`config()` function that is always available.
|
||||
|
||||
.. note:: We don't recommend you use the same short classname in modules.
|
||||
Modules that need to override or add to known configurations in **app/Config/** should use :ref:`registrars`.
|
||||
|
@ -324,10 +324,12 @@ Command-Line Only Routes
|
||||
.. note:: It is recommended to use Spark Commands for CLI scripts instead of calling controllers via CLI.
|
||||
See the :doc:`../cli/cli_commands` page for detailed information.
|
||||
|
||||
You can create routes that work only from the command-line, and are inaccessible from the web browser, with the
|
||||
``cli()`` method. Any route created by any of the HTTP-verb-based
|
||||
Any route created by any of the HTTP-verb-based
|
||||
route methods will also be inaccessible from the CLI, but routes created by the ``add()`` method will still be
|
||||
available from the command line:
|
||||
available from the command line.
|
||||
|
||||
You can create routes that work only from the command-line, and are inaccessible from the web browser, with the
|
||||
``cli()`` method:
|
||||
|
||||
.. literalinclude:: routing/032.php
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
################
|
||||
Running Your App
|
||||
################
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 2
|
||||
:depth: 3
|
||||
|
||||
A CodeIgniter 4 app can be run in a number of different ways: hosted on a web server,
|
||||
using virtualization, or using CodeIgniter's command line tool for testing.
|
||||
@ -20,8 +21,9 @@ section of the User Guide to begin learning how to build dynamic PHP application
|
||||
|
||||
.. _initial-configuration:
|
||||
|
||||
*********************
|
||||
Initial Configuration
|
||||
=====================
|
||||
*********************
|
||||
|
||||
#. Open the **app/Config/App.php** file with a text editor and
|
||||
set your base URL to ``$baseURL``. If you need more flexibility, the baseURL may
|
||||
@ -49,11 +51,12 @@ Initial Configuration
|
||||
your project, so that it is writable by the user or account used by your
|
||||
web server.
|
||||
|
||||
************************
|
||||
Local Development Server
|
||||
========================
|
||||
************************
|
||||
|
||||
CodeIgniter 4 comes with a local development server, leveraging PHP's built-in web server
|
||||
with CodeIgniter routing. You can launch it, with the following command line
|
||||
with CodeIgniter routing. You can launch it, with the following command line
|
||||
in the main directory::
|
||||
|
||||
> php spark serve
|
||||
@ -83,8 +86,9 @@ The local development server can be customized with three command line options:
|
||||
|
||||
> php spark serve --php /usr/bin/php7.6.5.4
|
||||
|
||||
*******************
|
||||
Hosting with Apache
|
||||
===================
|
||||
*******************
|
||||
|
||||
A CodeIgniter4 webapp is normally hosted on a web server.
|
||||
Apache HTTP Server is the "standard" platform, and assumed in much of our documentation.
|
||||
@ -92,19 +96,29 @@ Apache HTTP Server is the "standard" platform, and assumed in much of our docume
|
||||
Apache is bundled with many platforms, but can also be downloaded in a bundle
|
||||
with a database engine and PHP from `Bitnami <https://bitnami.com/stacks/infrastructure>`_.
|
||||
|
||||
.htaccess
|
||||
---------
|
||||
Configure Main Config File
|
||||
==========================
|
||||
|
||||
Enabling mod_rewrite
|
||||
--------------------
|
||||
|
||||
The "mod_rewrite" module enables URLs without "index.php" in them, and is assumed
|
||||
in our user guide.
|
||||
|
||||
Make sure that the rewrite module is enabled (uncommented) in the main
|
||||
configuration file, e.g., **apache2/conf/httpd.conf**::
|
||||
configuration file, e.g., **apache2/conf/httpd.conf**:
|
||||
|
||||
.. code-block:: apache
|
||||
|
||||
LoadModule rewrite_module modules/mod_rewrite.so
|
||||
|
||||
Setting Document Root
|
||||
---------------------
|
||||
|
||||
Also make sure that the default document root's ``<Directory>`` element enables this too,
|
||||
in the ``AllowOverride`` setting::
|
||||
in the ``AllowOverride`` setting:
|
||||
|
||||
.. code-block:: apache
|
||||
|
||||
<Directory "/opt/lamp/apache2/htdocs">
|
||||
Options Indexes FollowSymLinks
|
||||
@ -112,111 +126,61 @@ in the ``AllowOverride`` setting::
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
Removing the index.php
|
||||
----------------------
|
||||
|
||||
See :ref:`CodeIgniter URLs <urls-remove-index-php-apache>`.
|
||||
|
||||
Virtual Hosting
|
||||
---------------
|
||||
Hosting with VirtualHost
|
||||
========================
|
||||
|
||||
We recommend using "virtual hosting" to run your apps.
|
||||
You can set up different aliases for each of the apps you work on,
|
||||
|
||||
Enabling vhost_alias_module
|
||||
---------------------------
|
||||
|
||||
Make sure that the virtual hosting module is enabled (uncommented) in the main
|
||||
configuration file, e.g., **apache2/conf/httpd.conf**::
|
||||
configuration file, e.g., **apache2/conf/httpd.conf**:
|
||||
|
||||
.. code-block:: apache
|
||||
|
||||
LoadModule vhost_alias_module modules/mod_vhost_alias.so
|
||||
|
||||
Adding Host Alias
|
||||
-----------------
|
||||
|
||||
Add a host alias in your "hosts" file, typically **/etc/hosts** on unix-type platforms,
|
||||
or **c:/Windows/System32/drivers/etc/hosts** on Windows.
|
||||
Add a line to the file. This could be ``myproject.local`` or ``myproject.test``, for instance::
|
||||
or **c:\Windows\System32\drivers\etc\hosts** on Windows.
|
||||
|
||||
Add a line to the file.
|
||||
This could be ``myproject.local`` or ``myproject.test``, for instance::
|
||||
|
||||
127.0.0.1 myproject.local
|
||||
|
||||
Add a ``<VirtualHost>`` element for your webapp inside the virtual hosting configuration,
|
||||
e.g., **apache2/conf/extra/httpd-vhost.conf**::
|
||||
|
||||
<VirtualHost *:80>
|
||||
DocumentRoot "/opt/lamp/apache2/htdocs/myproject/public"
|
||||
ServerName myproject.local
|
||||
ErrorLog "logs/myproject-error_log"
|
||||
CustomLog "logs/myproject-access_log" common
|
||||
</VirtualHost>
|
||||
|
||||
If your project folder is not a subfolder of the Apache document root, then your
|
||||
``<VirtualHost>`` element may need a nested ``<Directory>`` element to grant the web server access to the files.
|
||||
|
||||
With mod_userdir (Shared Hosts)
|
||||
--------------------------------
|
||||
|
||||
A common practice in shared hosting environments is to use the Apache module "mod_userdir" to enable per-user Virtual Hosts automatically. Additional configuration is required to allow CodeIgniter4 to be run from these per-user directories.
|
||||
|
||||
The following assumes that the server is already configured for mod_userdir. A guide to enabling this module is available `in the Apache documentation <https://httpd.apache.org/docs/2.4/howto/public_html.html>`_.
|
||||
|
||||
Because CodeIgniter4 expects the server to find the framework front controller at **public/index.php** by default, you must specify this location as an alternative to search for the request (even if CodeIgniter4 is installed within the per-user web directory).
|
||||
|
||||
The default user web directory **~/public_html** is specified by the ``UserDir`` directive, typically in **apache2/mods-available/userdir.conf** or **apache2/conf/extra/httpd-userdir.conf**::
|
||||
|
||||
UserDir public_html
|
||||
|
||||
So you will need to configure Apache to look for CodeIgniter's public directory first before trying to serve the default::
|
||||
|
||||
UserDir "public_html/public" "public_html"
|
||||
|
||||
Be sure to specify options and permissions for the CodeIgniter public directory as well. A **userdir.conf** might look like::
|
||||
|
||||
<IfModule mod_userdir.c>
|
||||
UserDir "public_html/public" "public_html"
|
||||
UserDir disabled root
|
||||
|
||||
<Directory /home/*/public_html>
|
||||
AllowOverride All
|
||||
Options MultiViews Indexes FollowSymLinks
|
||||
<Limit GET POST OPTIONS>
|
||||
# Apache <= 2.2:
|
||||
# Order allow,deny
|
||||
# Allow from all
|
||||
|
||||
# Apache >= 2.4:
|
||||
Require all granted
|
||||
</Limit>
|
||||
<LimitExcept GET POST OPTIONS>
|
||||
# Apache <= 2.2:
|
||||
# Order deny,allow
|
||||
# Deny from all
|
||||
|
||||
# Apache >= 2.4:
|
||||
Require all denied
|
||||
</LimitExcept>
|
||||
</Directory>
|
||||
|
||||
<Directory /home/*/public_html/public>
|
||||
AllowOverride All
|
||||
Options MultiViews Indexes FollowSymLinks
|
||||
<Limit GET POST OPTIONS>
|
||||
# Apache <= 2.2:
|
||||
# Order allow,deny
|
||||
# Allow from all
|
||||
|
||||
# Apache >= 2.4:
|
||||
Require all granted
|
||||
</Limit>
|
||||
<LimitExcept GET POST OPTIONS>
|
||||
# Apache <= 2.2:
|
||||
# Order deny,allow
|
||||
# Deny from all
|
||||
|
||||
# Apache >= 2.4:
|
||||
Require all denied
|
||||
</LimitExcept>
|
||||
</Directory>
|
||||
</IfModule>
|
||||
|
||||
Setting Environment
|
||||
Setting VirtualHost
|
||||
-------------------
|
||||
|
||||
See :ref:`Handling Multiple Environments <environment-apache>`.
|
||||
Add a ``<VirtualHost>`` element for your webapp inside the virtual hosting configuration,
|
||||
e.g., **apache2/conf/extra/httpd-vhost.conf**:
|
||||
|
||||
.. code-block:: apache
|
||||
|
||||
<VirtualHost *:80>
|
||||
DocumentRoot "/opt/lamp/apache2/myproject/public"
|
||||
ServerName myproject.local
|
||||
ErrorLog "logs/myproject-error_log"
|
||||
CustomLog "logs/myproject-access_log" common
|
||||
|
||||
<Directory "/opt/lamp/apache2/myproject/public">
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
|
||||
The above configuration assumes the project folder is located as follows:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
apache2/
|
||||
├── myproject/ (Project Folder)
|
||||
│ └── public/ (DocumentRoot for myproject.local)
|
||||
└── htdocs/
|
||||
|
||||
Testing
|
||||
-------
|
||||
@ -225,14 +189,97 @@ With the above configuration, your webapp would be accessed with the URL **http:
|
||||
|
||||
Apache needs to be restarted whenever you change its configuration.
|
||||
|
||||
Hosting with mod_userdir (Shared Hosts)
|
||||
=======================================
|
||||
|
||||
A common practice in shared hosting environments is to use the Apache module "mod_userdir" to enable per-user Virtual Hosts automatically. Additional configuration is required to allow CodeIgniter4 to be run from these per-user directories.
|
||||
|
||||
The following assumes that the server is already configured for mod_userdir. A guide to enabling this module is available `in the Apache documentation <https://httpd.apache.org/docs/2.4/howto/public_html.html>`_.
|
||||
|
||||
Because CodeIgniter4 expects the server to find the framework front controller at **public/index.php** by default, you must specify this location as an alternative to search for the request (even if CodeIgniter4 is installed within the per-user web directory).
|
||||
|
||||
The default user web directory **~/public_html** is specified by the ``UserDir`` directive, typically in **apache2/mods-available/userdir.conf** or **apache2/conf/extra/httpd-userdir.conf**:
|
||||
|
||||
.. code-block:: apache
|
||||
|
||||
UserDir public_html
|
||||
|
||||
So you will need to configure Apache to look for CodeIgniter's public directory first before trying to serve the default:
|
||||
|
||||
.. code-block:: apache
|
||||
|
||||
UserDir "public_html/public" "public_html"
|
||||
|
||||
Be sure to specify options and permissions for the CodeIgniter public directory as well. A **userdir.conf** might look like:
|
||||
|
||||
.. code-block:: apache
|
||||
|
||||
<IfModule mod_userdir.c>
|
||||
UserDir "public_html/public" "public_html"
|
||||
UserDir disabled root
|
||||
|
||||
<Directory /home/*/public_html>
|
||||
AllowOverride All
|
||||
Options MultiViews Indexes FollowSymLinks
|
||||
<Limit GET POST OPTIONS>
|
||||
# Apache <= 2.2:
|
||||
# Order allow,deny
|
||||
# Allow from all
|
||||
|
||||
# Apache >= 2.4:
|
||||
Require all granted
|
||||
</Limit>
|
||||
<LimitExcept GET POST OPTIONS>
|
||||
# Apache <= 2.2:
|
||||
# Order deny,allow
|
||||
# Deny from all
|
||||
|
||||
# Apache >= 2.4:
|
||||
Require all denied
|
||||
</LimitExcept>
|
||||
</Directory>
|
||||
|
||||
<Directory /home/*/public_html/public>
|
||||
AllowOverride All
|
||||
Options MultiViews Indexes FollowSymLinks
|
||||
<Limit GET POST OPTIONS>
|
||||
# Apache <= 2.2:
|
||||
# Order allow,deny
|
||||
# Allow from all
|
||||
|
||||
# Apache >= 2.4:
|
||||
Require all granted
|
||||
</Limit>
|
||||
<LimitExcept GET POST OPTIONS>
|
||||
# Apache <= 2.2:
|
||||
# Order deny,allow
|
||||
# Deny from all
|
||||
|
||||
# Apache >= 2.4:
|
||||
Require all denied
|
||||
</LimitExcept>
|
||||
</Directory>
|
||||
</IfModule>
|
||||
|
||||
Removing the index.php
|
||||
======================
|
||||
|
||||
See :ref:`CodeIgniter URLs <urls-remove-index-php-apache>`.
|
||||
|
||||
Setting Environment
|
||||
===================
|
||||
|
||||
See :ref:`Handling Multiple Environments <environment-apache>`.
|
||||
|
||||
******************
|
||||
Hosting with Nginx
|
||||
==================
|
||||
******************
|
||||
|
||||
Nginx is the second most widely used HTTP server for web hosting.
|
||||
Here you can find an example configuration using PHP 8.1 FPM (unix sockets) under Ubuntu Server.
|
||||
|
||||
default.conf
|
||||
------------
|
||||
============
|
||||
|
||||
This configuration enables URLs without "index.php" in them and using CodeIgniter's "404 - File Not Found" for URLs ending with ".php".
|
||||
|
||||
@ -269,12 +316,13 @@ This configuration enables URLs without "index.php" in them and using CodeIgnite
|
||||
}
|
||||
|
||||
Setting Environment
|
||||
-------------------
|
||||
===================
|
||||
|
||||
See :ref:`Handling Multiple Environments <environment-nginx>`.
|
||||
|
||||
*********************
|
||||
Bootstrapping the App
|
||||
=====================
|
||||
*********************
|
||||
|
||||
In some scenarios you will want to load the framework without actually running the whole
|
||||
application. This is particularly useful for unit testing your project, but may also be
|
||||
|
@ -39,6 +39,19 @@ is not used::
|
||||
|
||||
Previously, the ``$body`` was used for the request body.
|
||||
|
||||
Return value of Validation::loadRuleGroup()
|
||||
===========================================
|
||||
|
||||
The return value of ``Validation::loadRuleGroup()`` has been changed from
|
||||
"**rules array**" to "**array** of **rules array** and **customErrors array**"
|
||||
(``[rules, customErrors]``).
|
||||
|
||||
If you use the method, update the code like the following::
|
||||
|
||||
$rules = $this->validation->loadRuleGroup($rules);
|
||||
↓
|
||||
[$rules, $customErrors] = $this->validation->loadRuleGroup($rules);
|
||||
|
||||
Breaking Enhancements
|
||||
*********************
|
||||
|
||||
|
@ -2,7 +2,9 @@
|
||||
|
||||
namespace Config;
|
||||
|
||||
class Validation
|
||||
// ...
|
||||
|
||||
class Validation extends BaseConfig
|
||||
{
|
||||
// ...
|
||||
|
||||
|
@ -2,9 +2,13 @@
|
||||
|
||||
namespace Config;
|
||||
|
||||
class Validation
|
||||
// ...
|
||||
|
||||
class Validation extends BaseConfig
|
||||
{
|
||||
public $signup = [
|
||||
// ...
|
||||
|
||||
public array $signup = [
|
||||
'username' => 'required|max_length[30]',
|
||||
'password' => 'required|max_length[255]',
|
||||
'pass_confirm' => 'required|max_length[255]|matches[password]',
|
||||
|
@ -2,16 +2,20 @@
|
||||
|
||||
namespace Config;
|
||||
|
||||
class Validation
|
||||
// ...
|
||||
|
||||
class Validation extends BaseConfig
|
||||
{
|
||||
public $signup = [
|
||||
// ...
|
||||
|
||||
public array $signup = [
|
||||
'username' => 'required|max_length[30]',
|
||||
'password' => 'required|max_length[255]',
|
||||
'pass_confirm' => 'required|max_length[255]|matches[password]',
|
||||
'email' => 'required|max_length[254]|valid_email',
|
||||
];
|
||||
|
||||
public $signup_errors = [
|
||||
public array $signup_errors = [
|
||||
'username' => [
|
||||
'required' => 'You must choose a username.',
|
||||
],
|
||||
|
@ -2,9 +2,13 @@
|
||||
|
||||
namespace Config;
|
||||
|
||||
class Validation
|
||||
// ...
|
||||
|
||||
class Validation extends BaseConfig
|
||||
{
|
||||
public $signup = [
|
||||
// ...
|
||||
|
||||
public array $signup = [
|
||||
'username' => [
|
||||
'rules' => 'required|max_length[30]',
|
||||
'errors' => [
|
||||
@ -18,5 +22,6 @@ class Validation
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// ...
|
||||
}
|
||||
|
@ -2,9 +2,13 @@
|
||||
|
||||
namespace Config;
|
||||
|
||||
class Validation
|
||||
// ...
|
||||
|
||||
class Validation extends BaseConfig
|
||||
{
|
||||
public $templates = [
|
||||
// ...
|
||||
|
||||
public array $templates = [
|
||||
'list' => 'CodeIgniter\Validation\Views\list',
|
||||
'single' => 'CodeIgniter\Validation\Views\single',
|
||||
'my_list' => '_errors_list',
|
||||
|
@ -2,12 +2,13 @@
|
||||
|
||||
namespace Config;
|
||||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use CodeIgniter\Validation\CreditCardRules;
|
||||
use CodeIgniter\Validation\FileRules;
|
||||
use CodeIgniter\Validation\FormatRules;
|
||||
use CodeIgniter\Validation\Rules;
|
||||
|
||||
class Validation
|
||||
class Validation extends BaseConfig
|
||||
{
|
||||
// ...
|
||||
|
||||
|
@ -150,7 +150,7 @@ Ensure that a header or cookie was actually emitted:
|
||||
.. literalinclude:: overview/009.php
|
||||
|
||||
.. note:: the test case with this should be `run as a separate process
|
||||
in PHPunit <https://phpunit.readthedocs.io/en/9.5/annotations.html#runinseparateprocess>`_.
|
||||
in PHPunit <https://docs.phpunit.de/en/9.6/annotations.html#runinseparateprocess>`_.
|
||||
|
||||
assertHeaderNotEmitted($header, $ignoreCase = false)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -160,7 +160,7 @@ Ensure that a header or cookie was not emitted:
|
||||
.. literalinclude:: overview/010.php
|
||||
|
||||
.. note:: the test case with this should be `run as a separate process
|
||||
in PHPunit <https://phpunit.readthedocs.io/en/9.5/annotations.html#runinseparateprocess>`_.
|
||||
in PHPunit <https://docs.phpunit.de/en/9.6/annotations.html#runinseparateprocess>`_.
|
||||
|
||||
assertCloseEnough($expected, $actual, $message = '', $tolerance = 1)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
Loading…
x
Reference in New Issue
Block a user