Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Improve Registrars #9445

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions app/Config/Modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ class Modules extends BaseModules
*/
public $composerPackages = [];

/**
* If set to `true`, Registrars may have previous config values as an array.
* This is useful when updating arrays or checking properties.
*
* NOTE: Enable this option only for trusted modules/packages.
*/
public bool $registrarHasData = false;

/**
* --------------------------------------------------------------------------
* Auto-Discovery Rules
Expand Down
19 changes: 17 additions & 2 deletions system/Config/BaseConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -274,21 +274,36 @@ protected function registerProperties()

$shortName = (new ReflectionClass($this))->getShortName();

if (static::$moduleConfig->registrarHasData) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new property on patch PR will cause undefined property unless isset() is used.

imo, new option should go to 4.7 branch.

// Get all public properties for this config
$worker = new class () {
/**
* @return array<string, mixed>
*/
public function getProperties(BaseConfig $obj): array
{
return get_object_vars($obj);
}
};
}

// Check the registrar class for a method named after this class' shortName
foreach (static::$registrars as $callable) {
// ignore non-applicable registrars
if (! method_exists($callable, $shortName)) {
continue; // @codeCoverageIgnore
}

$properties = $callable::$shortName();
$currentProps = static::$moduleConfig->registrarHasData ? $worker->getProperties($this) : [];
$properties = $callable::$shortName($currentProps);

if (! is_array($properties)) {
throw new RuntimeException('Registrars must return an array of properties and their values.');
}

foreach ($properties as $property => $value) {
if (isset($this->{$property}) && is_array($this->{$property}) && is_array($value)) {
// TODO: The array check can be removed if the option `registrarHasData` is accepted.
if (isset($this->{$property}) && is_array($this->{$property}) && is_array($value) && ! static::$moduleConfig->registrarHasData) {
$this->{$property} = array_merge($this->{$property}, $value);
} else {
$this->{$property} = $value;
Expand Down
42 changes: 41 additions & 1 deletion tests/_support/Config/TestRegistrar.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,53 @@
*/
class TestRegistrar
{
public static function RegistrarConfig()
/**
* @param array<int|string, mixed> $previous
*/
public static function RegistrarConfig(array $previous = [])
{
if ($previous === []) {
return [
'bar' => [
'first',
'second',
],
'cars' => [
'Trucks' => [
'Volvo' => [
'year' => 2019,
'color' => 'dark blue',
],
],
'Sedans Lux' => [
'Toyota' => [
'year' => 2025,
'color' => 'silver',
],
],
],
];
}

return [
'bar' => [
'first',
'second',
],
'cars' => array_replace_recursive($previous['cars'], [
'Trucks' => [
'Volvo' => [
'year' => 2019,
'color' => 'dark blue',
],
],
'Sedans Lux' => [
'Toyota' => [
'year' => 2025,
'color' => 'silver',
],
],
]),
];
}
}
71 changes: 70 additions & 1 deletion tests/system/Config/BaseConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -252,18 +252,87 @@ public function testRecognizesLooseValues(): void
$this->assertFalse($config->QFALSE);
}

public function testRegistrars(): void
public function testRegistrarsWithDisabledRegistrarHasData(): void
{
$modules = new Modules();

$modules->registrarHasData = false;
BaseConfig::setModules($modules);

$config = new RegistrarConfig();
$config::$registrars = [TestRegistrar::class];

$this->setPrivateProperty($config, 'didDiscovery', true);
$method = $this->getPrivateMethodInvoker($config, 'registerProperties');
$method();

$cars = [
'Sedans' => [
'Toyota' => [
'year' => 2018,
'color' => 'silver',
],
],
'Trucks' => [
'Volvo' => [
'year' => 2019,
'color' => 'dark blue',
],
],
'Sedans Lux' => [
'Toyota' => [
'year' => 2025,
'color' => 'silver',
],
],
];

// no change to unmodified property
$this->assertSame('bar', $config->foo);
// add to an existing array property
$this->assertSame(['baz', 'first', 'second'], $config->bar);
// replace some of the keys with another value
$this->assertSame($cars, $config->cars);
}

public function testRegistrarsWithEnabledRegistrarHasData(): void
{
$modules = new Modules();

$modules->registrarHasData = true;
BaseConfig::setModules($modules);

$config = new RegistrarConfig();
$config::$registrars = [TestRegistrar::class];

$this->setPrivateProperty($config, 'didDiscovery', true);
$method = $this->getPrivateMethodInvoker($config, 'registerProperties');
$method();

$cars = [
'Sedans' => [
'Toyota' => [
'year' => 2018,
'color' => 'silver',
],
],
'Trucks' => [
'Volvo' => [
'year' => 2019,
'color' => 'dark blue',
],
],
'Sedans Lux' => [
'Toyota' => [
'year' => 2025,
'color' => 'silver',
],
],
];

$this->assertSame('bar', $config->foo);
$this->assertSame(['first', 'second'], $config->bar);
$this->assertSame($cars, $config->cars);
}

public function testBadRegistrar(): void
Expand Down
18 changes: 18 additions & 0 deletions tests/system/Config/fixtures/RegistrarConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,22 @@ class RegistrarConfig extends CodeIgniter\Config\BaseConfig
public $bar = [
'baz',
];

/**
* @var array<string, array<string, mixed>>
*/
public $cars = [
'Sedans' => [
'Toyota' => [
'year' => 2018,
'color' => 'silver',
],
],
'Trucks' => [
'Volvo' => [
'year' => 2019,
'color' => 'blue',
],
],
];
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# total 33 errors
# total 35 errors

parameters:
ignoreErrors:
Expand Down Expand Up @@ -42,6 +42,11 @@ parameters:
count: 1
path: ../../tests/system/CommonFunctionsTest.php

-
message: '#^@readonly property Config\\Modules\:\:\$registrarHasData is assigned outside of its declaring class\.$#'
count: 2
path: ../../tests/system/Config/BaseConfigTest.php

-
message: '#^@readonly property Config\\Modules\:\:\$aliases is assigned outside of its declaring class\.$#'
count: 1
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# total 20 errors
# total 21 errors

parameters:
ignoreErrors:
Expand All @@ -19,7 +19,7 @@ parameters:

-
message: '#^@readonly property cannot have a default value\.$#'
count: 4
count: 5
path: ../../app/Config/Modules.php

-
Expand Down
Loading