From 3637e82f0d75205c0bd36267583666b377833c96 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 11 Oct 2020 18:40:12 +0900 Subject: [PATCH 1/6] Extracet Compiler class --- src/Compiler.php | 103 ++++++------------------------ src/Compiler/CompileDiScripts.php | 64 +++++++++++++++++++ src/Compiler/ScanClass.php | 82 ++++++++++++++++++++++++ 3 files changed, 165 insertions(+), 84 deletions(-) create mode 100644 src/Compiler/CompileDiScripts.php create mode 100644 src/Compiler/ScanClass.php diff --git a/src/Compiler.php b/src/Compiler.php index 8ad6e277..30e6e8e3 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -6,13 +6,12 @@ use BEAR\AppMeta\AbstractAppMeta; use BEAR\AppMeta\Meta; +use BEAR\Package\Compiler\CompileDiScripts; +use BEAR\Package\Compiler\ScanClass; use BEAR\Package\Provide\Error\NullPage; -use BEAR\Resource\Exception\ParameterException; -use BEAR\Resource\NamedParameterInterface; use BEAR\Resource\Uri; use BEAR\Sunday\Extension\Application\AppInterface; use Composer\Autoload\ClassLoader; -use Doctrine\Common\Annotations\Reader; use Ray\Di\AbstractModule; use Ray\Di\Exception\Unbound; use Ray\Di\InjectorInterface; @@ -30,7 +29,6 @@ use function get_class; use function in_array; use function interface_exists; -use function is_callable; use function is_float; use function is_int; use function memory_get_peak_usage; @@ -76,6 +74,12 @@ final class Compiler /** @var list */ private $overwritten = []; + /** @var ScanClass */ + private $compilerScanClass; + + /** @var CompileDiScripts */ + private $compilerDiScripts; + /** * @param string $appName application name "MyVendor|MyProject" * @param string $context application context "prod-app" @@ -90,6 +94,8 @@ public function __construct(string $appName, string $context, string $appDir) $this->appMeta = new Meta($appName, $context, $appDir); /** @psalm-suppress MixedAssignment */ $this->injector = Injector::getInstance($appName, $context, $appDir); + $this->compilerScanClass = new ScanClass(); + $this->compilerDiScripts = new CompileDiScripts($this, $this->compilerScanClass); } /** @@ -138,7 +144,7 @@ public function dumpAutoload(): int return 0; } - public function registerLoader(string $appDir): void + private function registerLoader(string $appDir): void { $loaderFile = $appDir . '/vendor/autoload.php'; if (! file_exists($loaderFile)) { @@ -158,27 +164,12 @@ function (string $class) use ($loader): void { ); } - public function compileDiScripts(AbstractAppMeta $appMeta): void + private function compileDiScripts(AbstractAppMeta $appMeta): void { - $reader = $this->injector->getInstance(Reader::class); - assert($reader instanceof Reader); - $namedParams = $this->injector->getInstance(NamedParameterInterface::class); - assert($namedParams instanceof NamedParameterInterface); - // create DI factory class and AOP compiled class for all resources and save $app cache. - $app = $this->injector->getInstance(AppInterface::class); - assert($app instanceof AppInterface); - - // check resource injection and create annotation cache - $metas = $appMeta->getResourceListGenerator(); - /** @var array{0: string, 1:string} $meta */ - foreach ($metas as $meta) { - [$className] = $meta; - assert(class_exists($className)); - $this->scanClass($reader, $namedParams, $className); - } + ($this->compilerDiScripts)($appMeta); } - public function compileSrc(AbstractModule $module): AbstractModule + private function compileSrc(AbstractModule $module): AbstractModule { $container = $module->getContainer()->getContainer(); $dependencies = array_keys($container); @@ -279,67 +270,6 @@ private function invokeTypicalRequest(): void $app->resource->get->object($ro)(); } - /** - * Save annotation and method meta information - * - * @param class-string $className - * - * @template T - */ - private function scanClass(Reader $reader, NamedParameterInterface $namedParams, string $className): void - { - $class = new ReflectionClass($className); - $instance = $class->newInstanceWithoutConstructor(); - if (! $instance instanceof $className) { - return; // @codeCoverageIgnore - } - - $reader->getClassAnnotations($class); - $methods = $class->getMethods(); - $log = sprintf('M %s:', $className); - foreach ($methods as $method) { - $methodName = $method->getName(); - if ($this->isMagicMethod($methodName)) { - continue; - } - - if (substr($methodName, 0, 2) === 'on') { - $log .= sprintf(' %s', $methodName); - $this->saveNamedParam($namedParams, $instance, $methodName); - } - - // method annotation - $reader->getMethodAnnotations($method); - $log .= sprintf('@ %s', $methodName); - } - -// echo $log . PHP_EOL; - } - - private function isMagicMethod(string $method): bool - { - return in_array($method, ['__sleep', '__wakeup', 'offsetGet', 'offsetSet', 'offsetExists', 'offsetUnset', 'count', 'ksort', 'asort', 'jsonSerialize'], true); - } - - private function saveNamedParam(NamedParameterInterface $namedParameter, object $instance, string $method): void - { - // named parameter - if (! in_array($method, ['onGet', 'onPost', 'onPut', 'onPatch', 'onDelete', 'onHead'], true)) { - return; // @codeCoverageIgnore - } - - $callable = [$instance, $method]; - if (! is_callable($callable)) { - return; // @codeCoverageIgnore - } - - try { - $namedParameter->getParameters($callable, []); - } catch (ParameterException $e) { - return; - } - } - /** * @param array $classes * @@ -456,4 +386,9 @@ private function compileObjectGraphDotFile(AbstractModule $module): string return $dotFile; } + + public function getInjector(): InjectorInterface + { + return $this->injector; + } } diff --git a/src/Compiler/CompileDiScripts.php b/src/Compiler/CompileDiScripts.php new file mode 100644 index 00000000..c52474fc --- /dev/null +++ b/src/Compiler/CompileDiScripts.php @@ -0,0 +1,64 @@ +compiler = $compiler; + $this->compilerScanClass = $compilerScanClass; + } + + public function __invoke(AbstractAppMeta $appMeta): void + { + $reader = $this->compiler->getInjector()->getInstance(Reader::class); + assert($reader instanceof Reader); + $namedParams = $this->compiler->getInjector()->getInstance(NamedParameterInterface::class); + assert($namedParams instanceof NamedParameterInterface); + // create DI factory class and AOP compiled class for all resources and save $app cache. + $app = $this->compiler->getInjector()->getInstance(AppInterface::class); + assert($app instanceof AppInterface); + + // check resource injection and create annotation cache + $metas = $appMeta->getResourceListGenerator(); + /** @var array{0: string, 1:string} $meta */ + foreach ($metas as $meta) { + [$className] = $meta; + assert(class_exists($className)); + $this->scanClass($reader, $namedParams, $className); + } + } + + /** + * Save annotation and method meta information + * + * @param class-string $className + * + * @template T + */ + private function scanClass(Reader $reader, NamedParameterInterface $namedParams, string $className): void + { + ($this->compilerScanClass)($reader, $namedParams, $className); + } +} diff --git a/src/Compiler/ScanClass.php b/src/Compiler/ScanClass.php new file mode 100644 index 00000000..3074061a --- /dev/null +++ b/src/Compiler/ScanClass.php @@ -0,0 +1,82 @@ + $className + * + * @template T + */ + public function __invoke(Reader $reader, NamedParameterInterface $namedParams, string $className): void + { + $class = new ReflectionClass($className); + $instance = $class->newInstanceWithoutConstructor(); + if (! $instance instanceof $className) { + return; // @codeCoverageIgnore + } + + $reader->getClassAnnotations($class); + $methods = $class->getMethods(); + $log = sprintf('M %s:', $className); + foreach ($methods as $method) { + $methodName = $method->getName(); + if ($this->isMagicMethod($methodName)) { + continue; + } + + if (substr($methodName, 0, 2) === 'on') { + $log .= sprintf(' %s', $methodName); + $this->saveNamedParam($namedParams, $instance, $methodName); + } + + // method annotation + $reader->getMethodAnnotations($method); + $log .= sprintf('@ %s', $methodName); + } + +// echo $log . PHP_EOL; + } + + private function isMagicMethod(string $method): bool + { + return in_array($method, ['__sleep', '__wakeup', 'offsetGet', 'offsetSet', 'offsetExists', 'offsetUnset', 'count', 'ksort', 'asort', 'jsonSerialize'], true); + } + + private function saveNamedParam(NamedParameterInterface $namedParameter, object $instance, string $method): void + { + // named parameter + if (! in_array($method, ['onGet', 'onPost', 'onPut', 'onPatch', 'onDelete', 'onHead'], true)) { + return; // @codeCoverageIgnore + } + + $callable = [$instance, $method]; + if (! is_callable($callable)) { + return; // @codeCoverageIgnore + } + + try { + $namedParameter->getParameters($callable, []); + } catch (ParameterException $e) { + return; + } + } +} From 7c80817da115a20b5a83a8b09886ee697e28065f Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 11 Oct 2020 22:13:21 +0900 Subject: [PATCH 2/6] Extract Compiler sub right classes --- src/Compiler.php | 329 +++--------------- src/Compiler/CompileAutoload.php | 195 +++++++++++ ...ScanClass.php => CompileClassMetaInfo.php} | 7 +- src/Compiler/CompileDependencies.php | 41 +++ src/Compiler/CompileDiScripts.php | 23 +- src/Compiler/CompileObjectGraph.php | 33 ++ src/Compiler/CompilePreload.php | 92 +++++ src/Compiler/FilePutContents.php | 23 ++ src/Compiler/NewInstance.php | 90 +++++ 9 files changed, 530 insertions(+), 303 deletions(-) create mode 100644 src/Compiler/CompileAutoload.php rename src/Compiler/{ScanClass.php => CompileClassMetaInfo.php} (96%) create mode 100644 src/Compiler/CompileDependencies.php create mode 100644 src/Compiler/CompileObjectGraph.php create mode 100644 src/Compiler/CompilePreload.php create mode 100644 src/Compiler/FilePutContents.php create mode 100644 src/Compiler/NewInstance.php diff --git a/src/Compiler.php b/src/Compiler.php index 30e6e8e3..856e2cf6 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -4,53 +4,37 @@ namespace BEAR\Package; -use BEAR\AppMeta\AbstractAppMeta; +use ArrayObject; use BEAR\AppMeta\Meta; +use BEAR\Package\Compiler\CompileAutoload; +use BEAR\Package\Compiler\CompileClassMetaInfo; +use BEAR\Package\Compiler\CompileDependencies; use BEAR\Package\Compiler\CompileDiScripts; -use BEAR\Package\Compiler\ScanClass; +use BEAR\Package\Compiler\CompileObjectGraph; +use BEAR\Package\Compiler\CompilePreload; +use BEAR\Package\Compiler\FilePutContents; +use BEAR\Package\Compiler\NewInstance; use BEAR\Package\Provide\Error\NullPage; -use BEAR\Resource\Uri; -use BEAR\Sunday\Extension\Application\AppInterface; use Composer\Autoload\ClassLoader; -use Ray\Di\AbstractModule; -use Ray\Di\Exception\Unbound; use Ray\Di\InjectorInterface; -use Ray\ObjectGrapher\ObjectGrapher; -use ReflectionClass; use RuntimeException; -use Throwable; -use function array_keys; use function assert; -use function class_exists; use function count; use function file_exists; -use function file_put_contents; -use function get_class; -use function in_array; -use function interface_exists; use function is_float; -use function is_int; use function memory_get_peak_usage; use function microtime; use function number_format; -use function preg_quote; -use function preg_replace; use function printf; -use function property_exists; use function realpath; -use function sort; use function spl_autoload_register; -use function sprintf; -use function strpos; -use function substr; -use function trait_exists; use const PHP_EOL; final class Compiler { - /** @var string[] */ + /** @var list */ private $classes = []; /** @var InjectorInterface */ @@ -59,26 +43,26 @@ final class Compiler /** @var string */ private $context; - /** @var string */ - private $appDir; - /** @var Meta */ private $appMeta; - /** @var array */ - private $compiled = []; + /** @var CompileDiScripts */ + private $compilerDiScripts; - /** @var array */ - private $failed = []; + /** @var NewInstance */ + private $newInstance; - /** @var list */ - private $overwritten = []; + /** @var CompileAutoload */ + private $dumpAutoload; - /** @var ScanClass */ - private $compilerScanClass; + /** @var CompilePreload */ + private $compilePreload; - /** @var CompileDiScripts */ - private $compilerDiScripts; + /** @var CompileObjectGraph */ + private $compilerObjectGraph; + + /** @var CompileDependencies */ + private $compileDependencies; /** * @param string $appName application name "MyVendor|MyProject" @@ -90,12 +74,20 @@ public function __construct(string $appName, string $context, string $appDir) $this->registerLoader($appDir); $this->hookNullObjectClass($appDir); $this->context = $context; - $this->appDir = $appDir; $this->appMeta = new Meta($appName, $context, $appDir); - /** @psalm-suppress MixedAssignment */ + /** @psalm-suppress MixedAssignment (?) */ $this->injector = Injector::getInstance($appName, $context, $appDir); - $this->compilerScanClass = new ScanClass(); - $this->compilerDiScripts = new CompileDiScripts($this, $this->compilerScanClass); + $this->compilerDiScripts = new CompileDiScripts(new CompileClassMetaInfo(), $this->injector); + $this->newInstance = new NewInstance($this->injector); + /** @var ArrayObject $overWritten */ + $overWritten = new ArrayObject(); + /** @var ArrayObject $classes */ + $classes = new ArrayObject(); + $filePutContents = new FilePutContents(); + $this->dumpAutoload = new CompileAutoload($this->injector, $filePutContents, $this->appMeta, $overWritten, $classes, $appDir, $context); + $this->compilePreload = new CompilePreload($this->newInstance, $this->dumpAutoload, $filePutContents, $classes, $context); + $this->compilerObjectGraph = new CompileObjectGraph($filePutContents, $appDir); + $this->compileDependencies = new CompileDependencies($this->newInstance); } /** @@ -105,43 +97,32 @@ public function __construct(string $appName, string $context, string $appDir) */ public function compile(): int { - $preload = $this->compilePreload($this->appMeta, $this->context); + $preload = ($this->compilePreload)($this->appMeta, $this->context); $module = (new Module())($this->appMeta, $this->context); - $this->compileSrc($module); + ($this->compileDependencies)($module); echo PHP_EOL; - $this->compileDiScripts($this->appMeta); - $dot = $this->failed ? '' : $this->compileObjectGraphDotFile($module); + ($this->compilerDiScripts)($this->appMeta); + $failed = $this->newInstance->getFailed(); + $dot = $failed ? '' : ($this->compilerObjectGraph)($module); $start = $_SERVER['REQUEST_TIME_FLOAT']; assert(is_float($start)); $time = number_format(microtime(true) - $start, 2); $memory = number_format(memory_get_peak_usage() / (1024 * 1024), 3); echo PHP_EOL; printf("Compilation (1/2) took %f seconds and used %fMB of memory\n", $time, $memory); - printf("Success: %d Failed: %d\n", count($this->compiled), count($this->failed)); - printf("preload.php: %s\n", $this->getFileInfo($preload)); - printf("module.dot: %s\n", $dot ? $this->getFileInfo($dot) : 'n/a'); - - foreach ($this->failed as $depedencyIndex => $error) { + printf("Success: %d Failed: %d\n", $this->newInstance->getCompiled(), count($this->newInstance->getFailed())); + printf("preload.php: %s\n", $this->dumpAutoload->getFileInfo($preload)); + printf("module.dot: %s\n", $dot ? $this->dumpAutoload->getFileInfo($dot) : 'n/a'); + foreach ($this->newInstance->getFailed() as $depedencyIndex => $error) { printf("UNBOUND: %s for %s \n", $error, $depedencyIndex); } - return $this->failed ? 1 : 0; + return $failed ? 1 : 0; } public function dumpAutoload(): int { - echo PHP_EOL; - $this->invokeTypicalRequest(); - $paths = $this->getPaths($this->classes); - $autolaod = $this->saveAutoloadFile($this->appMeta->appDir, $paths); - $start = $_SERVER['REQUEST_TIME_FLOAT']; - assert(is_float($start)); - $time = number_format(microtime(true) - $start, 2); - $memory = number_format(memory_get_peak_usage() / (1024 * 1024), 3); - printf("Compilation (2/2) took %f seconds and used %fMB of memory\n", $time, $memory); - printf("autoload.php: %s\n", $this->getFileInfo($autolaod)); - - return 0; + return ($this->dumpAutoload)(); } private function registerLoader(string $appDir): void @@ -164,204 +145,6 @@ function (string $class) use ($loader): void { ); } - private function compileDiScripts(AbstractAppMeta $appMeta): void - { - ($this->compilerDiScripts)($appMeta); - } - - private function compileSrc(AbstractModule $module): AbstractModule - { - $container = $module->getContainer()->getContainer(); - $dependencies = array_keys($container); - sort($dependencies); - foreach ($dependencies as $dependencyIndex) { - $pos = strpos((string) $dependencyIndex, '-'); - assert(is_int($pos)); - $interface = substr((string) $dependencyIndex, 0, $pos); - $name = substr((string) $dependencyIndex, $pos + 1); - $this->getInstance($interface, $name); - } - - return $module; - } - - private function getFileInfo(string $filename): string - { - if (in_array($filename, $this->overwritten, true)) { - return $filename . ' (overwritten)'; - } - - return $filename; - } - - /** - * @param array $paths - */ - private function saveAutoloadFile(string $appDir, array $paths): string - { - $requiredFile = ''; - foreach ($paths as $path) { - $requiredFile .= sprintf( - "require %s';\n", - $this->getRelativePath($appDir, $path) - ); - } - - $autoloadFile = sprintf("context, $requiredFile); - $fileName = realpath($appDir) . '/autoload.php'; - $this->putFileContents($fileName, $autoloadFile); - - return $fileName; - } - - private function compilePreload(AbstractAppMeta $appMeta, string $context): string - { - $this->loadResources($appMeta->name, $context, $appMeta->appDir); - $paths = $this->getPaths($this->classes); - $requiredOnceFile = ''; - foreach ($paths as $path) { - $requiredOnceFile .= sprintf( - "require_once %s';\n", - $this->getRelativePath($appMeta->appDir, $path) - ); - } - - $preloadFile = sprintf("context, $requiredOnceFile); - $fileName = realpath($appMeta->appDir) . '/preload.php'; - $this->putFileContents($fileName, $preloadFile); - - return $fileName; - } - - private function getRelativePath(string $rootDir, string $file): string - { - $dir = (string) realpath($rootDir); - if (strpos($file, $dir) !== false) { - return (string) preg_replace('#^' . preg_quote($dir, '#') . '#', "__DIR__ . '", $file); - } - - return $file; - } - - /** - * @psalm-suppress MixedFunctionCall - * @psalm-suppress NoInterfaceProperties - */ - private function invokeTypicalRequest(): void - { - $app = $this->injector->getInstance(AppInterface::class); - assert($app instanceof AppInterface); - assert(property_exists($app, 'resource')); - $ro = new NullPage(); - $ro->uri = new Uri('app://self/'); - /** @psalm-suppress MixedMethodCall */ - $app->resource->get->object($ro)(); - } - - /** - * @param array $classes - * - * @return array - */ - private function getPaths(array $classes): array - { - $paths = []; - foreach ($classes as $class) { - // could be phpdoc tag by annotation loader - if ($this->isNotAutoloadble($class)) { - continue; - } - - /** @var class-string $class */ - $filePath = (string) (new ReflectionClass($class))->getFileName(); // @phpstan-ignore-line - if (! $this->isNotCompileFile($filePath)) { - continue; // @codeCoverageIgnore - } - - $paths[] = $this->getRelativePath($this->appDir, $filePath); - } - - return $paths; - } - - private function isNotCompileFile(string $filePath): bool - { - return file_exists($filePath) || is_int(strpos($filePath, 'phar')); - } - - private function isNotAutoloadble(string $class): bool - { - return ! class_exists($class, false) && ! interface_exists($class, false) && ! trait_exists($class, false); - } - - private function loadResources(string $appName, string $context, string $appDir): void - { - $meta = new Meta($appName, $context, $appDir); - - $resMetas = $meta->getGenerator('*'); - foreach ($resMetas as $resMeta) { - $this->getInstance($resMeta->class); - } - } - - private function getInstance(string $interface, string $name = ''): void - { - $dependencyIndex = $interface . '-' . $name; - if (in_array($dependencyIndex, $this->compiled, true)) { - // @codeCoverageIgnoreStart - printf("S %s:%s\n", $interface, $name); - - return; - - // @codeCoverageIgnoreEnd - } - - try { - $this->injector->getInstance($interface, $name); - $this->compiled[] = $dependencyIndex; - $this->progress('.'); - } catch (Unbound $e) { - if ($dependencyIndex === 'Ray\Aop\MethodInvocation-') { - return; - } - - $this->failed[$dependencyIndex] = $e->getMessage(); - $this->progress('F'); - // @codeCoverageIgnoreStart - } catch (Throwable $e) { - $this->failed[$dependencyIndex] = sprintf('%s: %s', get_class($e), $e->getMessage()); - $this->progress('F'); - // @codeCoverageIgnoreEnd - } - } - - private function progress(string $char): void - { - /** - * @var int - */ - static $cnt = 0; - - echo $char; - $cnt++; - if ($cnt === 60) { - $cnt = 0; - echo PHP_EOL; - } - } - private function hookNullObjectClass(string $appDir): void { $compileScript = realpath($appDir) . '/.compile.php'; @@ -369,26 +152,4 @@ private function hookNullObjectClass(string $appDir): void require $compileScript; } } - - private function putFileContents(string $fileName, string $content): void - { - if (file_exists($fileName)) { - $this->overwritten[] = $fileName; - } - - file_put_contents($fileName, $content); - } - - private function compileObjectGraphDotFile(AbstractModule $module): string - { - $dotFile = sprintf('%s/module.dot', $this->appDir); - $this->putFileContents($dotFile, (new ObjectGrapher())($module)); - - return $dotFile; - } - - public function getInjector(): InjectorInterface - { - return $this->injector; - } } diff --git a/src/Compiler/CompileAutoload.php b/src/Compiler/CompileAutoload.php new file mode 100644 index 00000000..b02e4d90 --- /dev/null +++ b/src/Compiler/CompileAutoload.php @@ -0,0 +1,195 @@ + */ + private $overwritten; + + /** @var ArrayObject */ + private $classes; + + /** @var InjectorInterface */ + private $injector; + + /** @var FilePutContents */ + private $filePutContents; + + /** + * @param ArrayObject $overwritten + * @param ArrayObject $classes + */ + public function __construct( + InjectorInterface $injector, + FilePutContents $filePutContents, + Meta $appMeta, + ArrayObject $overwritten, + ArrayObject $classes, + string $appDir, + string $context + ) { + $this->appDir = $appDir; + $this->context = $context; + $this->appMeta = $appMeta; + $this->overwritten = $overwritten; + $this->classes = $classes; + $this->injector = $injector; + $this->filePutContents = $filePutContents; + } + + public function getFileInfo(string $filename): string + { + if (in_array($filename, (array) $this->overwritten, true)) { + return $filename . ' (overwritten)'; + } + + return $filename; + } + + public function __invoke(): int + { + echo PHP_EOL; + $this->invokeTypicalRequest(); + /** @var list $classes */ + $classes = (array) $this->classes; + $paths = $this->getPaths($classes); + $autolaod = $this->saveAutoloadFile($this->appMeta->appDir, $paths); + $start = $_SERVER['REQUEST_TIME_FLOAT']; + assert(is_float($start)); + $time = number_format(microtime(true) - $start, 2); + $memory = number_format(memory_get_peak_usage() / (1024 * 1024), 3); + printf("Compilation (2/2) took %f seconds and used %fMB of memory\n", $time, $memory); + printf("autoload.php: %s\n", $this->getFileInfo($autolaod)); + + return 0; + } + + /** + * @param array $classes + * + * @return array + */ + public function getPaths(array $classes): array + { + $paths = []; + foreach ($classes as $class) { + // could be phpdoc tag by annotation loader + if ($this->isNotAutoloadble($class)) { + continue; + } + + /** @var class-string $class */ + $filePath = (string) (new ReflectionClass($class))->getFileName(); // @phpstan-ignore-line + if (! $this->isNotCompileFile($filePath)) { + continue; // @codeCoverageIgnore + } + + $paths[] = $this->getRelativePath($this->appDir, $filePath); + $paths[] = $this->getRelativePath($this->appDir, $filePath); + } + + return $paths; + } + + /** + * @param array $paths + */ + public function saveAutoloadFile(string $appDir, array $paths): string + { + $requiredFile = ''; + foreach ($paths as $path) { + $requiredFile .= sprintf( + "require %s';\n", + $this->getRelativePath($appDir, $path) + ); + } + + $autoloadFile = sprintf("context, $requiredFile); + $fileName = realpath($appDir) . '/autoload.php'; + ($this->filePutContents)($fileName, $autoloadFile); + + return $fileName; + } + + /** + * @psalm-suppress MixedFunctionCall + * @psalm-suppress NoInterfaceProperties + */ + public function invokeTypicalRequest(): void + { + $app = $this->injector->getInstance(AppInterface::class); + assert($app instanceof AppInterface); + assert(property_exists($app, 'resource')); + $ro = new NullPage(); + $ro->uri = new Uri('app://self/'); + /** @psalm-suppress MixedMethodCall */ + $app->resource->get->object($ro)(); + } + + private function isNotAutoloadble(string $class): bool + { + return ! class_exists($class, false) && ! interface_exists($class, false) && ! trait_exists($class, false); + } + + private function isNotCompileFile(string $filePath): bool + { + return file_exists($filePath) || is_int(strpos($filePath, 'phar')); + } + + private function getRelativePath(string $rootDir, string $file): string + { + $dir = (string) realpath($rootDir); + if (strpos($file, $dir) !== false) { + return (string) preg_replace('#^' . preg_quote($dir, '#') . '#', "__DIR__ . '", $file); + } + + return $file; + } +} diff --git a/src/Compiler/ScanClass.php b/src/Compiler/CompileClassMetaInfo.php similarity index 96% rename from src/Compiler/ScanClass.php rename to src/Compiler/CompileClassMetaInfo.php index 3074061a..446e4d60 100644 --- a/src/Compiler/ScanClass.php +++ b/src/Compiler/CompileClassMetaInfo.php @@ -14,10 +14,7 @@ use function sprintf; use function substr; -/** - * Compiler Component - */ -final class ScanClass +final class CompileClassMetaInfo { /** * Save annotation and method meta information @@ -52,8 +49,6 @@ public function __invoke(Reader $reader, NamedParameterInterface $namedParams, s $reader->getMethodAnnotations($method); $log .= sprintf('@ %s', $methodName); } - -// echo $log . PHP_EOL; } private function isMagicMethod(string $method): bool diff --git a/src/Compiler/CompileDependencies.php b/src/Compiler/CompileDependencies.php new file mode 100644 index 00000000..c131d304 --- /dev/null +++ b/src/Compiler/CompileDependencies.php @@ -0,0 +1,41 @@ +newInstance = $newInstance; + } + + public function __invoke(AbstractModule $module): AbstractModule + { + $container = $module->getContainer()->getContainer(); + $dependencies = array_keys($container); + sort($dependencies); + foreach ($dependencies as $dependencyIndex) { + $pos = strpos((string) $dependencyIndex, '-'); + assert(is_int($pos)); + $interface = substr((string) $dependencyIndex, 0, $pos); + $name = substr((string) $dependencyIndex, $pos + 1); + ($this->newInstance)($interface, $name); + } + + return $module; + } +} diff --git a/src/Compiler/CompileDiScripts.php b/src/Compiler/CompileDiScripts.php index c52474fc..8ea0962b 100644 --- a/src/Compiler/CompileDiScripts.php +++ b/src/Compiler/CompileDiScripts.php @@ -5,39 +5,36 @@ namespace BEAR\Package\Compiler; use BEAR\AppMeta\AbstractAppMeta; -use BEAR\Package\Compiler; use BEAR\Resource\NamedParameterInterface; use BEAR\Sunday\Extension\Application\AppInterface; use Doctrine\Common\Annotations\Reader; +use Ray\Di\InjectorInterface; use function assert; use function class_exists; -/** - * Compiler Component - */ final class CompileDiScripts { - /** @var Compiler */ - private $compiler; - - /** @var ScanClass */ + /** @var CompileClassMetaInfo */ private $compilerScanClass; - public function __construct(Compiler $compiler, ScanClass $compilerScanClass) + /** @var InjectorInterface */ + private $injector; + + public function __construct(CompileClassMetaInfo $compilerScanClass, InjectorInterface $injector) { - $this->compiler = $compiler; $this->compilerScanClass = $compilerScanClass; + $this->injector = $injector; } public function __invoke(AbstractAppMeta $appMeta): void { - $reader = $this->compiler->getInjector()->getInstance(Reader::class); + $reader = $this->injector->getInstance(Reader::class); assert($reader instanceof Reader); - $namedParams = $this->compiler->getInjector()->getInstance(NamedParameterInterface::class); + $namedParams = $this->injector->getInstance(NamedParameterInterface::class); assert($namedParams instanceof NamedParameterInterface); // create DI factory class and AOP compiled class for all resources and save $app cache. - $app = $this->compiler->getInjector()->getInstance(AppInterface::class); + $app = $this->injector->getInstance(AppInterface::class); assert($app instanceof AppInterface); // check resource injection and create annotation cache diff --git a/src/Compiler/CompileObjectGraph.php b/src/Compiler/CompileObjectGraph.php new file mode 100644 index 00000000..7deb3f10 --- /dev/null +++ b/src/Compiler/CompileObjectGraph.php @@ -0,0 +1,33 @@ +filePutContents = $filePutContents; + $this->appDir = $appDir; + } + + public function __invoke(AbstractModule $module): string + { + $dotFile = sprintf('%s/module.dot', $this->appDir); + ($this->filePutContents)($dotFile, (new ObjectGrapher())($module)); + + return $dotFile; + } +} diff --git a/src/Compiler/CompilePreload.php b/src/Compiler/CompilePreload.php new file mode 100644 index 00000000..fba5cdca --- /dev/null +++ b/src/Compiler/CompilePreload.php @@ -0,0 +1,92 @@ + */ + private $classes; + + /** @var FilePutContents */ + private $filePutContents; + + /** @var string */ + private $context; + + /** + * @param ArrayObject $classes + */ + public function __construct(NewInstance $newInstance, CompileAutoload $dumpAutoload, FilePutContents $filePutContents, ArrayObject $classes, string $context) + { + $this->newInstance = $newInstance; + $this->dumpAutoload = $dumpAutoload; + $this->classes = $classes; + $this->filePutContents = $filePutContents; + $this->context = $context; + } + + public function __invoke(AbstractAppMeta $appMeta, string $context): string + { + $this->loadResources($appMeta->name, $context, $appMeta->appDir); + /** @var list $classes */ + $classes = (array) $this->classes; + $paths = $this->dumpAutoload->getPaths($classes); + $requiredOnceFile = ''; + foreach ($paths as $path) { + $requiredOnceFile .= sprintf( + "require_once %s';\n", + $this->getRelativePath($appMeta->appDir, $path) + ); + } + + $preloadFile = sprintf("context, $requiredOnceFile); + $fileName = realpath($appMeta->appDir) . '/preload.php'; + ($this->filePutContents)($fileName, $preloadFile); + + return $fileName; + } + + public function loadResources(string $appName, string $context, string $appDir): void + { + $meta = new Meta($appName, $context, $appDir); + + $resMetas = $meta->getGenerator('*'); + foreach ($resMetas as $resMeta) { + ($this->newInstance)($resMeta->class); + } + } + + private function getRelativePath(string $rootDir, string $file): string + { + $dir = (string) realpath($rootDir); + if (strpos($file, $dir) !== false) { + return (string) preg_replace('#^' . preg_quote($dir, '#') . '#', "__DIR__ . '", $file); + } + + return $file; + } +} diff --git a/src/Compiler/FilePutContents.php b/src/Compiler/FilePutContents.php new file mode 100644 index 00000000..7af5fe95 --- /dev/null +++ b/src/Compiler/FilePutContents.php @@ -0,0 +1,23 @@ + */ + private $overwritten = []; + + public function __invoke(string $fileName, string $content): void + { + if (file_exists($fileName)) { + $this->overwritten[] = $fileName; + } + + file_put_contents($fileName, $content); + } +} diff --git a/src/Compiler/NewInstance.php b/src/Compiler/NewInstance.php new file mode 100644 index 00000000..eb2c24f0 --- /dev/null +++ b/src/Compiler/NewInstance.php @@ -0,0 +1,90 @@ + */ + private $compiled = []; + + /** @var array */ + private $failed = []; + + /** @var InjectorInterface */ + private $injector; + + public function __construct(InjectorInterface $injector) + { + $this->injector = $injector; + } + + public function __invoke(string $interface, string $name = ''): void + { + $dependencyIndex = $interface . '-' . $name; + if (in_array($dependencyIndex, $this->compiled, true)) { + // @codeCoverageIgnoreStart + printf("S %s:%s\n", $interface, $name); + // @codeCoverageIgnoreEnd + } + + try { + $this->injector->getInstance($interface, $name); + $this->compiled[] = $dependencyIndex; + $this->progress('.'); + } catch (Unbound $e) { + if ($dependencyIndex === 'Ray\Aop\MethodInvocation-') { + return; + } + + $this->failed[$dependencyIndex] = $e->getMessage(); + $this->progress('F'); + // @codeCoverageIgnoreStart + } catch (Throwable $e) { + $this->failed[$dependencyIndex] = sprintf('%s: %s', get_class($e), $e->getMessage()); + $this->progress('F'); + // @codeCoverageIgnoreEnd + } + } + + private function progress(string $char): void + { + /** + * @var int + */ + static $cnt = 0; + + echo $char; + $cnt++; + if ($cnt === 60) { + $cnt = 0; + echo PHP_EOL; + } + } + + public function getCompiled(): int + { + return count($this->compiled); + } + + /** + * @return array + */ + public function getFailed(): array + { + return $this->failed; + } +} From 1f2e116e64f5cd9f2582015d5c4e9767946ba74e Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 11 Oct 2020 22:13:57 +0900 Subject: [PATCH 3/6] Add key type --- src/Injector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Injector.php b/src/Injector.php index 07b458e7..8b0298c2 100644 --- a/src/Injector.php +++ b/src/Injector.php @@ -24,7 +24,7 @@ final class Injector /** * Serialized injector instances * - * @var array + * @var array */ private static $instances; From 0f550a412b025f4dccdcb2de1d454652fe8c0af6 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 11 Oct 2020 22:14:08 +0900 Subject: [PATCH 4/6] Log echo message --- tests/CompilerTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/CompilerTest.php b/tests/CompilerTest.php index 55aebd0b..810466bd 100644 --- a/tests/CompilerTest.php +++ b/tests/CompilerTest.php @@ -8,13 +8,15 @@ use Ray\Compiler\ScriptInjector; use RuntimeException; +use function error_log; use function unlink; class CompilerTest extends TestCase { public function setUp(): void { - $this->setOutputCallback(static function () { + $this->setOutputCallback(static function (string $msg) { + error_log($msg); }); } From 23ff68b1c4e6e884054cd1628f20826205e54b02 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Mon, 12 Oct 2020 00:59:14 +0900 Subject: [PATCH 5/6] Improve coverage --- src/Compiler.php | 6 ++++-- src/Compiler/CompileAutoload.php | 1 - src/Compiler/CompilePreload.php | 8 ++++---- tests/CompilerTest.php | 5 ----- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Compiler.php b/src/Compiler.php index 856e2cf6..fc799546 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -34,8 +34,8 @@ final class Compiler { - /** @var list */ - private $classes = []; + /** @var ArrayObject */ + private $classes; /** @var InjectorInterface */ private $injector; @@ -83,6 +83,7 @@ public function __construct(string $appName, string $context, string $appDir) $overWritten = new ArrayObject(); /** @var ArrayObject $classes */ $classes = new ArrayObject(); + $this->classes = $classes; $filePutContents = new FilePutContents(); $this->dumpAutoload = new CompileAutoload($this->injector, $filePutContents, $this->appMeta, $overWritten, $classes, $appDir, $context); $this->compilePreload = new CompilePreload($this->newInstance, $this->dumpAutoload, $filePutContents, $classes, $context); @@ -139,6 +140,7 @@ private function registerLoader(string $appDir): void function (string $class) use ($loader): void { $loader->loadClass($class); if ($class !== NullPage::class) { + /** @psalm-suppress NullArgument */ $this->classes[] = $class; } } diff --git a/src/Compiler/CompileAutoload.php b/src/Compiler/CompileAutoload.php index b02e4d90..2d048472 100644 --- a/src/Compiler/CompileAutoload.php +++ b/src/Compiler/CompileAutoload.php @@ -126,7 +126,6 @@ public function getPaths(array $classes): array } $paths[] = $this->getRelativePath($this->appDir, $filePath); - $paths[] = $this->getRelativePath($this->appDir, $filePath); } return $paths; diff --git a/src/Compiler/CompilePreload.php b/src/Compiler/CompilePreload.php index fba5cdca..6a4ed222 100644 --- a/src/Compiler/CompilePreload.php +++ b/src/Compiler/CompilePreload.php @@ -80,13 +80,13 @@ public function loadResources(string $appName, string $context, string $appDir): } } - private function getRelativePath(string $rootDir, string $file): string + private function getRelativePath(string $rootDir, string $path): string { $dir = (string) realpath($rootDir); - if (strpos($file, $dir) !== false) { - return (string) preg_replace('#^' . preg_quote($dir, '#') . '#', "__DIR__ . '", $file); + if (strpos($path, $dir) !== false) { + return (string) preg_replace('#^' . preg_quote($dir, '#') . '#', "__DIR__ . '", $path); } - return $file; + return $path; } } diff --git a/tests/CompilerTest.php b/tests/CompilerTest.php index 810466bd..d0fc5518 100644 --- a/tests/CompilerTest.php +++ b/tests/CompilerTest.php @@ -58,11 +58,6 @@ public function testWrongAppDir(): void (new Compiler('FakeVendor\HelloWorld', 'app', '__invalid__'))->compile(); } - /** - * @covers \BEAR\Package\Compiler::compile() - * @covers \BEAR\Package\Compiler::getInstance() - * @covers \BEAR\Package\Compiler::getFileInfo() - */ public function testUnbound(): void { $compiler = new Compiler('FakeVendor\HelloWorld', 'unbound-app', __DIR__ . '/Fake/fake-app'); From 5140a0c64776db2600486ece16398274d9370c5d Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Mon, 12 Oct 2020 02:00:20 +0900 Subject: [PATCH 6/6] Test coverage 100% --- src/Compiler.php | 2 +- src/Compiler/CompilePreload.php | 15 +-------------- src/Compiler/FilePutContents.php | 15 +++++++++++++-- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/Compiler.php b/src/Compiler.php index fc799546..d22c4283 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -84,7 +84,7 @@ public function __construct(string $appName, string $context, string $appDir) /** @var ArrayObject $classes */ $classes = new ArrayObject(); $this->classes = $classes; - $filePutContents = new FilePutContents(); + $filePutContents = new FilePutContents($overWritten); $this->dumpAutoload = new CompileAutoload($this->injector, $filePutContents, $this->appMeta, $overWritten, $classes, $appDir, $context); $this->compilePreload = new CompilePreload($this->newInstance, $this->dumpAutoload, $filePutContents, $classes, $context); $this->compilerObjectGraph = new CompileObjectGraph($filePutContents, $appDir); diff --git a/src/Compiler/CompilePreload.php b/src/Compiler/CompilePreload.php index 6a4ed222..af842906 100644 --- a/src/Compiler/CompilePreload.php +++ b/src/Compiler/CompilePreload.php @@ -8,11 +8,8 @@ use BEAR\AppMeta\AbstractAppMeta; use BEAR\AppMeta\Meta; -use function preg_quote; -use function preg_replace; use function realpath; use function sprintf; -use function strpos; final class CompilePreload { @@ -53,7 +50,7 @@ public function __invoke(AbstractAppMeta $appMeta, string $context): string foreach ($paths as $path) { $requiredOnceFile .= sprintf( "require_once %s';\n", - $this->getRelativePath($appMeta->appDir, $path) + $path ); } @@ -79,14 +76,4 @@ public function loadResources(string $appName, string $context, string $appDir): ($this->newInstance)($resMeta->class); } } - - private function getRelativePath(string $rootDir, string $path): string - { - $dir = (string) realpath($rootDir); - if (strpos($path, $dir) !== false) { - return (string) preg_replace('#^' . preg_quote($dir, '#') . '#', "__DIR__ . '", $path); - } - - return $path; - } } diff --git a/src/Compiler/FilePutContents.php b/src/Compiler/FilePutContents.php index 7af5fe95..f26027a5 100644 --- a/src/Compiler/FilePutContents.php +++ b/src/Compiler/FilePutContents.php @@ -4,17 +4,28 @@ namespace BEAR\Package\Compiler; +use ArrayObject; + use function file_exists; use function file_put_contents; class FilePutContents { - /** @var list */ - private $overwritten = []; + /** @var ArrayObject */ + private $overwritten; + + /** + * @param ArrayObject $overwritten + */ + public function __construct(ArrayObject $overwritten) + { + $this->overwritten = $overwritten; + } public function __invoke(string $fileName, string $content): void { if (file_exists($fileName)) { + /** @psalm-suppress NullArgument */ $this->overwritten[] = $fileName; }