diff --git a/.gitignore b/.gitignore index 595f3463..87a9374c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,7 @@ /.idea/ /.vscode/ /vendor/ +/data/ +/.phpbench/ .phpunit.* .php_cs.cache diff --git a/composer.json b/composer.json index aec43f1c..0f950357 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,8 @@ "vimeo/psalm": "^4.6", "friendsofphp/php-cs-fixer": "^2.19", "symfony/var-dumper": "^5.3", - "phpstan/phpstan": "^0.12.90" + "phpstan/phpstan": "^0.12.90", + "phpbench/phpbench": "^1.0" }, "autoload": { "psr-4": { @@ -56,6 +57,7 @@ "psalm": "./vendor/bin/psalm", "phpstan": "./vendor/bin/phpstan analyze -c phpstan.neon src", "csfix": "./vendor/bin/php-cs-fixer fix --allow-risky=yes", - "csrun": "./vendor/bin/php-cs-fixer fix --allow-risky=yes --dry-run" + "csrun": "./vendor/bin/php-cs-fixer fix --allow-risky=yes --dry-run", + "phpbench": "vendor/bin/phpbench run --report=aggregate --ansi" } } diff --git a/composer.lock b/composer.lock index efee064d..bf0ea53d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3efc0a79b3ebb95e76239d890ec22c24", + "content-hash": "139bc38efc35ddeb24e1cc12fb4729d9", "packages": [ { "name": "psr/container", @@ -2105,6 +2105,194 @@ }, "time": "2020-10-14T08:39:05+00:00" }, + { + "name": "phpbench/container", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpbench/container.git", + "reference": "def10824b6009d31028fa8dc9f73f4b26b234a67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/container/zipball/def10824b6009d31028fa8dc9f73f4b26b234a67", + "reference": "def10824b6009d31028fa8dc9f73f4b26b234a67", + "shasum": "" + }, + "require": { + "psr/container": "^1.0", + "symfony/options-resolver": "^4.2 || ^5.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "phpstan/phpstan": "^0.12.52", + "phpunit/phpunit": "^8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpBench\\DependencyInjection\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "Simple, configurable, service container.", + "support": { + "issues": "https://github.com/phpbench/container/issues", + "source": "https://github.com/phpbench/container/tree/2.1.0" + }, + "time": "2021-04-04T07:23:17+00:00" + }, + { + "name": "phpbench/dom", + "version": "0.3.1", + "source": { + "type": "git", + "url": "https://github.com/phpbench/dom.git", + "reference": "d26e615c4d64da41d168ab1096e4f55d97f2344f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/dom/zipball/d26e615c4d64da41d168ab1096e4f55d97f2344f", + "reference": "d26e615c4d64da41d168ab1096e4f55d97f2344f", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": "^7.2||^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.18", + "phpstan/phpstan": "^0.12.83", + "phpunit/phpunit": "^8.0||^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpBench\\Dom\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "DOM wrapper to simplify working with the PHP DOM implementation", + "support": { + "issues": "https://github.com/phpbench/dom/issues", + "source": "https://github.com/phpbench/dom/tree/0.3.1" + }, + "time": "2021-04-21T20:44:19+00:00" + }, + { + "name": "phpbench/phpbench", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/phpbench/phpbench.git", + "reference": "9827767f62f6f84974b1317f53536d68ae8db5e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/9827767f62f6f84974b1317f53536d68ae8db5e1", + "reference": "9827767f62f6f84974b1317f53536d68ae8db5e1", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.2.7", + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "ext-tokenizer": "*", + "php": "^7.2 || ^8.0", + "phpbench/container": "^2.1", + "phpbench/dom": "~0.3.1", + "psr/log": "^1.1", + "seld/jsonlint": "^1.1", + "symfony/console": "^4.2 || ^5.0", + "symfony/filesystem": "^4.2 || ^5.0", + "symfony/finder": "^4.2 || ^5.0", + "symfony/options-resolver": "^4.2 || ^5.0", + "symfony/process": "^4.2 || ^5.0", + "webmozart/path-util": "^2.3" + }, + "require-dev": { + "dantleech/invoke": "^2.0", + "friendsofphp/php-cs-fixer": "^2.13.1", + "jangregor/phpstan-prophecy": "^0.8.1", + "phpspec/prophecy": "^1.12", + "phpstan/phpstan": "^0.12.7", + "phpunit/phpunit": "^8.5.8 || ^9.0", + "symfony/error-handler": "^5.2", + "symfony/var-dumper": "^4.0 || ^5.0" + }, + "suggest": { + "ext-xdebug": "For Xdebug profiling extension." + }, + "bin": [ + "bin/phpbench" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpBench\\": "lib/", + "PhpBench\\Extensions\\XDebug\\": "extensions/xdebug/lib/", + "PhpBench\\Extensions\\Reports\\": "extensions/reports/lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "PHP Benchmarking Framework", + "support": { + "issues": "https://github.com/phpbench/phpbench/issues", + "source": "https://github.com/phpbench/phpbench/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/dantleech", + "type": "github" + } + ], + "time": "2021-07-03T09:36:14+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", @@ -3928,6 +4116,69 @@ ], "time": "2020-09-28T06:39:44+00:00" }, + { + "name": "seld/jsonlint", + "version": "1.8.3", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "9ad6ce79c342fbd44df10ea95511a1b24dee5b57" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9ad6ce79c342fbd44df10ea95511a1b24dee5b57", + "reference": "9ad6ce79c342fbd44df10ea95511a1b24dee5b57", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.8.3" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2020-11-11T09:19:24+00:00" + }, { "name": "symfony/event-dispatcher", "version": "v5.3.0", diff --git a/phpbench.json b/phpbench.json new file mode 100644 index 00000000..7227b670 --- /dev/null +++ b/phpbench.json @@ -0,0 +1,21 @@ +{ + "runner.bootstrap": "vendor/autoload.php", + "runner.path": "tests/Benchmark/", + "runner.retry_threshold": 5, + "runner.iterations": 3, + "runner.revs": 10, + "runner.time_unit": "time", + "runner.assert": [ + "mode(variant.time.avg) <= mode(baseline.time.avg) +/- 15%" + ], + "report.generators": { + "all": { + "generator": "composite", + "reports": [ + "env", + "default", + "aggregate" + ] + } + } +} diff --git a/src/Framework/ClassResolver/AbstractClassResolver.php b/src/Framework/ClassResolver/AbstractClassResolver.php index fc8efabb..204019b3 100644 --- a/src/Framework/ClassResolver/AbstractClassResolver.php +++ b/src/Framework/ClassResolver/AbstractClassResolver.php @@ -12,8 +12,16 @@ abstract class AbstractClassResolver protected static array $cachedInstances = []; protected static ?ClassNameFinderInterface $classNameFinder = null; + /** @var array */ + private static array $globalResolvedClasses = []; + abstract public function resolve(object $callerClass): ?object; + public static function addGlobal(string $key, object $resolvedClass): void + { + self::$globalResolvedClasses[$key] = $resolvedClass; + } + /** * @return null|mixed */ @@ -29,7 +37,7 @@ public function doResolve(object $callerClass) $resolvedClassName = $this->findClassName($classInfo); if (null === $resolvedClassName) { - return null; + return $this->resolveGlobal($cacheKey); } self::$cachedInstances[$cacheKey] = $this->createInstance($resolvedClassName); @@ -39,6 +47,22 @@ public function doResolve(object $callerClass) abstract protected function getResolvableType(): string; + /** + * @return null|mixed + */ + private function resolveGlobal(string $cacheKey) + { + $resolvedClass = self::$globalResolvedClasses[$cacheKey] ?? null; + + if (null === $resolvedClass) { + return null; + } + + self::$cachedInstances[$cacheKey] = $resolvedClass; + + return self::$cachedInstances[$cacheKey]; + } + private function getCacheKey(ClassInfo $classInfo): string { return $classInfo->getCacheKey($this->getResolvableType()); diff --git a/src/Framework/ClassResolver/ClassInfo.php b/src/Framework/ClassResolver/ClassInfo.php index 6bcea3e9..5a3a8065 100644 --- a/src/Framework/ClassResolver/ClassInfo.php +++ b/src/Framework/ClassResolver/ClassInfo.php @@ -4,6 +4,10 @@ namespace Gacela\Framework\ClassResolver; +use function array_slice; +use function count; +use function get_class; + final class ClassInfo { private ?string $cacheKey = null; @@ -13,16 +17,28 @@ final class ClassInfo public function __construct(object $callerObject) { $callerClass = get_class($callerObject); + + /** @var string[] $callerClassParts */ $callerClassParts = explode('\\', ltrim($callerClass, '\\')); + if (count($callerClassParts) <= 1) { + $callerClassParts = [ + 'module-name@anonymous', + 'class-name@anonymous', + ]; + } $this->callerNamespace = implode('\\', array_slice($callerClassParts, 0, count($callerClassParts) - 1)); - $this->callerModuleName = $callerClassParts[count($callerClassParts) - 2]; + $this->callerModuleName = $callerClassParts[count($callerClassParts) - 2] ?? ''; } public function getCacheKey(string $resolvableType): string { if (!$this->cacheKey) { - $this->cacheKey = $this->getFullNamespace() . $this->getModule() . $resolvableType; + $this->cacheKey = sprintf( + '\\%s\\%s', + $this->getFullNamespace(), + $resolvableType + ); } return $this->cacheKey; diff --git a/tests/Benchmark/Framework/ClassResolver/AbstractFacadeBench.php b/tests/Benchmark/Framework/ClassResolver/AbstractFacadeBench.php new file mode 100644 index 00000000..038dd9cf --- /dev/null +++ b/tests/Benchmark/Framework/ClassResolver/AbstractFacadeBench.php @@ -0,0 +1,69 @@ +getConfig()) { + private AbstractConfig $config; + + public function __construct(AbstractConfig $config) + { + $this->config = $config; + } + + public function getConfigValues(): array + { + return $this->config->getValues(); + } + }; + } + } + ); + + $this->facade = new class() extends AbstractFacade { + public function getSomething(): array + { + return $this->getFactory() + ->createDomainClass() + ->getConfigValues(); + } + }; + } + + public function bench_class_resolving(): void + { + $this->facade->getSomething(); + } +} diff --git a/tests/Integration/Framework/GlobalServices/GlobalServicesTest.php b/tests/Integration/Framework/GlobalServices/GlobalServicesTest.php new file mode 100644 index 00000000..bd8ae7f2 --- /dev/null +++ b/tests/Integration/Framework/GlobalServices/GlobalServicesTest.php @@ -0,0 +1,68 @@ +getConfig()) { + private AbstractConfig $config; + + public function __construct(AbstractConfig $config) + { + $this->config = $config; + } + + public function getConfigValues(): array + { + return $this->config->getValues(); + } + }; + } + } + ); + + $this->facade = new class() extends AbstractFacade { + public function getSomething(): array + { + return $this->getFactory() + ->createDomainClass() + ->getConfigValues(); + } + }; + } + + public function test_factory(): void + { + self::assertSame( + ['1', 2, [3]], + $this->facade->getSomething() + ); + } +}