From dbd8b26ac13df7088882ae31c6fb584292fc2cd0 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 27 Oct 2026 14:39:20 -0500 Subject: [PATCH 1/2] [CP-17696] - optimizing factory construction Assisted-by: Claude Code --- phalcon/Factory/AbstractFactory.zep | 9 +- .../unit/Factory/AbstractFactory/InitTest.php | 126 ++++++++++++++++++ 2 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 tests/unit/Factory/AbstractFactory/InitTest.php diff --git a/phalcon/Factory/AbstractFactory.zep b/phalcon/Factory/AbstractFactory.zep index 51db59d9a7..8fcdc4bc6a 100644 --- a/phalcon/Factory/AbstractFactory.zep +++ b/phalcon/Factory/AbstractFactory.zep @@ -56,14 +56,15 @@ abstract class AbstractFactory extends AbstractConfigFactory */ protected function init(array services = []) -> void { - var adapters, name, service; + var adapters; let adapters = this->getServices(), adapters = array_merge(adapters, services); - for name, service in adapters { - let this->mapper[name] = service; - unset(this->services[name]); + let this->mapper = array_replace(this->mapper, adapters); + + if !empty this->services { + let this->services = array_diff_key(this->services, adapters); } } } diff --git a/tests/unit/Factory/AbstractFactory/InitTest.php b/tests/unit/Factory/AbstractFactory/InitTest.php new file mode 100644 index 0000000000..1cc39481ff --- /dev/null +++ b/tests/unit/Factory/AbstractFactory/InitTest.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Tests\Unit\Factory\AbstractFactory; + +use Phalcon\Factory\AbstractFactory; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; +use PHPUnit\Framework\Attributes\DataProvider; +use stdClass; + +final class InitTest extends AbstractUnitTestCase +{ + /** + * @return array, 1: array}> + */ + public static function getInitCases(): array + { + return [ + 'no services' => [ + [], + ['one' => 'ClassOne', 'two' => 'ClassTwo'], + ], + 'replace and add' => [ + ['two' => 'ClassNew', 'three' => 'ClassThree'], + ['one' => 'ClassOne', 'two' => 'ClassNew', 'three' => 'ClassThree'], + ], + 'integer key' => [ + [5 => 'ClassFive'], + ['one' => 'ClassOne', 'two' => 'ClassTwo', 0 => 'ClassFive'], + ], + ]; + } + + /** + * init() sets the default services and the custom services in the + * mapper. A custom service replaces a default service with the same + * name. + * + * @param array $services + * @param array $expected + * + * @author Phalcon Team + * @since 2026-10-27 + */ + #[DataProvider('getInitCases')] + public function testFactoryAbstractFactoryInit(array $services, array $expected): void + { + $factory = $this->getFactory(); + + $this->callProtectedMethod($factory, 'init', $services); + + $actual = $this->getProtectedProperty($factory, 'mapper'); + $this->assertSame($expected, $actual); + } + + /** + * A second init() keeps the order of the mapper: a name that exists + * keeps its place, a new name goes at the end. + * + * @author Phalcon Team + * @since 2026-10-27 + */ + public function testFactoryAbstractFactoryInitAgain(): void + { + $factory = $this->getFactory(); + + $this->callProtectedMethod($factory, 'init', ['three' => 'ClassThree']); + $this->callProtectedMethod($factory, 'init', ['four' => 'ClassFour', 'one' => 'ClassNew']); + + $expected = [ + 'one' => 'ClassNew', + 'two' => 'ClassTwo', + 'three' => 'ClassThree', + 'four' => 'ClassFour', + ]; + $actual = $this->getProtectedProperty($factory, 'mapper'); + $this->assertSame($expected, $actual); + } + + /** + * init() removes the stored instances of the names that it sets. The + * other instances stay, in their order. + * + * @author Phalcon Team + * @since 2026-10-27 + */ + public function testFactoryAbstractFactoryInitServices(): void + { + $five = new stdClass(); + $one = new stdClass(); + $seven = new stdClass(); + $six = new stdClass(); + $factory = $this->getFactory(); + + $this->setProtectedProperty( + $factory, + 'services', + ['five' => $five, 'one' => $one, 'six' => $six, 'seven' => $seven] + ); + $this->callProtectedMethod($factory, 'init', ['seven' => 'ClassSeven']); + + $expected = ['five' => $five, 'six' => $six]; + $actual = $this->getProtectedProperty($factory, 'services'); + $this->assertSame($expected, $actual); + } + + private function getFactory(): AbstractFactory + { + return new class () extends AbstractFactory { + protected function getServices(): array + { + return ['one' => 'ClassOne', 'two' => 'ClassTwo']; + } + }; + } +} From 582734a7c21ae1ae05e8670b0b38b6ce5fdd4e80 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 27 Oct 2026 15:02:34 -0500 Subject: [PATCH 2/2] [CP-17696] - updating changelog Assisted-by: Claude Code --- CHANGELOG-5.0.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG-5.0.md b/CHANGELOG-5.0.md index e4a7ea861d..48447347bb 100644 --- a/CHANGELOG-5.0.md +++ b/CHANGELOG-5.0.md @@ -16,6 +16,7 @@ All notable changes are documented here. The format is based on [Keep a Changelo - Faster item storage, lookup, removal and JSON serialization in `Phalcon\Support\Collection`. [#17683](https://github.com/phalcon/cphalcon/issues/17683) - Faster route index build in `Phalcon\Mvc\Router` for routes with no host name. [#17693](https://github.com/phalcon/cphalcon/issues/17693) +- Faster service registration in `Phalcon\Factory\AbstractFactory` and the factories that extend it. [#17696](https://github.com/phalcon/cphalcon/issues/17696) ### Added