diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1366148 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +/.github export-ignore +/tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.php-cs-fixer.dist.php export-ignore +/psalm.xml export-ignore + +*.php diff=php \ No newline at end of file diff --git a/.github/workflows/.editorconfig b/.github/workflows/.editorconfig new file mode 100644 index 0000000..473df25 --- /dev/null +++ b/.github/workflows/.editorconfig @@ -0,0 +1,2 @@ +[{*.yaml,*.yml}] +indent_size = 2 \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..fe23261 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,48 @@ +name: Plugin CI +on: + push: + branches: [ 'master' ] + pull_request: + +env: + PHP_CS_FIXER_IGNORE_ENV: 1 + XDEBUG_MODE: coverage + +jobs: + tests: + name: "Tests ${{ matrix.php-version }} deps ${{ matrix.dependency-versions }}" + runs-on: ubuntu-22.04 + + strategy: + fail-fast: false + matrix: + # normal, highest, non-dev installs + php-version: [ '8.2' ] + dependency-versions: [ 'highest' ] + include: + # testing lowest PHP version with the lowest dependencies + # - php-version: '8.2' + # dependency-versions: 'lowest' + + # testing dev versions with the highest PHP + - php-version: '8.2' + dependency-versions: 'highest' + + steps: + - name: "Checkout code" + uses: "actions/checkout@v2" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + php-version: "${{ matrix.php-version }}" + + - name: "Composer install" + uses: "ramsey/composer-install@v2" + with: + dependency-versions: "${{ matrix.dependency-versions }}" + composer-options: "--prefer-dist --no-progress" + + - name: Run tests + run: composer run test \ No newline at end of file diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 index 8759011..c40cd9d --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ -.phpdoc/ -docs/ -vendor/ +.idea +vendor composer.lock .phpunit.result.cache +.php-cs-fixer.cache +test-coverage-report +phpunit.xml +.php-cs-fixer.php +phpstan.neon \ No newline at end of file diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..ded2263 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,30 @@ +in(__DIR__.'/src') + ->in(__DIR__.'/tests') +; + +return (new PhpCsFixer\Config()) + ->setRules(array( + '@Symfony' => true, + '@Symfony:risky' => true, + 'protected_to_private' => false, + 'semicolon_after_instruction' => false, + 'header_comment' => [ + 'header' => << + + For the full copyright and license information, please view the LICENSE + file that was distributed with this source code. +EOF + ] + )) + ->setRiskyAllowed(true) + ->setFinder($finder); \ No newline at end of file diff --git a/README.md b/README.md index 33e97c3..cba81fa 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,3 @@ # Micro Framework - The minimum kernel for application initialization. -### Requirements - -PHP >= 8.0.0 - -### How to use the library - -Add the latest version of micro/kernel into your project by using Composer or manually: - -__Using Composer (Recommended)__ - -Or require the package inside the composer.json of your project: -``` -"require": { - "micro/kernel": "^1" -}, -``` - -### Example - -After adding the library to your project, include the file autoload.php found in root of the library. -```html -include 'vendor/autoload.php'; -``` - -#### Simple usage: - -```php - -use Micro\Framework\Kernel\Configuration\DefaultApplicationConfiguration; -use Micro\Framework\Kernel\Plugin\PluginDependedInterface; -use Micro\Framework\Kernel\Plugin\ApplicationPluginInterface; -use Micro\Component\DependencyInjection\Container; -use Micro\Framework\Kernel\Plugin\PluginBootLoaderInterface; -use Micro\Framework\Kernel\KernelBuilder; - -// Create simple plugin -class TestPlugin implements PluginDependedInterface -{ - public function provideDependencies(Container $container): void - { - print_r('Provided dependencies'); - } -} - -// Create Dependency provider boot loader -class DependencyProviderLoader implements PluginBootLoaderInterface -{ - - public function __construct(private readonly Container $container) - { - } - - public function boot(ApplicationPluginInterface $applicationPlugin): void - { - $applicationPlugin->getDependedPlugins($this->container); - } -} - -$kernelBuilder = new KernelBuilder(); -$container = new Container(); -$configuration = new DefaultApplicationConfiguration(['APP_ENV' => 'dev']); -$kernel = $kernelBuilder - ->setApplicationConfiguration($configuration) - ->setContainer($container) - ->setApplicationPlugins([ - TestPlugin::class - ]) - ->addBootLoaders([ - new DependencyProviderLoader($container) - ]) - ->build(); -; - -$kernel->run(); -``` - -## License - -[MIT](LICENSE) \ No newline at end of file +Documentation is available [here](https://micro-php.net/docs). If not, we will be grateful if you can become its author :) \ No newline at end of file diff --git a/composer.json b/composer.json index b3b589a..f4598d2 100755 --- a/composer.json +++ b/composer.json @@ -1,14 +1,8 @@ { "name": "micro/kernel", - "type": "library", "description": "", - "version": "1.3", "license": "MIT", - "autoload": { - "psr-4": { - "Micro\\Framework\\Kernel\\": "src/" - } - }, + "type": "library", "authors": [ { "name": "Stanislau.Komar", @@ -16,7 +10,51 @@ } ], "require": { - "php": "^8.1|^8.2", + "php": "^8.2", "micro/dependency-injection": "^1" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.29", + "friendsofphp/php-cs-fixer": "^3.13", + "phpstan/phpstan": "^1.9", + "phpunit/php-code-coverage": "^9.2", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^5.2" + }, + "autoload": { + "psr-4": { + "Micro\\Framework\\Kernel\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Micro\\Framework\\Kernel\\Test\\Unit\\": "tests/Unit" + } + }, + "config": { + "allow-plugins": { + "ergebnis/composer-normalize": true + }, + "sort-packages": true + }, + "scripts": { + "coverage": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-text", + "coverage-html": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-html ./test-coverage-report", + "php-cs-fix": "PHP_CS_FIXER_IGNORE_ENV=1 ./vendor/bin/php-cs-fixer fix --verbose --using-cache=no", + "php-cs-try": "PHP_CS_FIXER_IGNORE_ENV=1 ./vendor/bin/php-cs-fixer fix --verbose --dry-run --using-cache=no", + "phpstan": "./vendor/bin/phpstan analyze --no-progress", + "phpunit": "./vendor/bin/phpunit", + "psalm": "./vendor/bin/psalm --no-progress --show-info=true --no-cache", + "statics": [ + "@phpstan", + "@psalm", + "@php-cs-try" + ], + "test": [ + "@statics", + "composer validate --strict", + "composer normalize", + "@coverage" + ] } } diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..18d77f7 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,4 @@ +parameters: + level: 7 + paths: + - src \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..2ac0ce9 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,36 @@ + + + + + + + + + tests/Unit + + + + + src/ + + src/Exception + src/HttpCorePlugin.php + + + + + + src + + + + + + \ No newline at end of file diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..99ad2c8 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/Container/ApplicationContainerFactoryInterface.php b/src/Container/ApplicationContainerFactoryInterface.php deleted file mode 100755 index 38f36cf..0000000 --- a/src/Container/ApplicationContainerFactoryInterface.php +++ /dev/null @@ -1,13 +0,0 @@ - + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Micro\Framework\Kernel; use Micro\Component\DependencyInjection\Container; @@ -7,39 +16,28 @@ class Kernel implements KernelInterface { - /** - * @var bool - */ private bool $isStarted; - /** - * @var bool - */ - private bool $isTerminated; - /** * @var object[] */ private array $plugins; /** - * @var string[] + * @var class-string[] */ private array $pluginsLoaded; /** - * @param array $applicationPluginCollection - * @param Container|null $container - * @param PluginBootLoaderInterface[] $pluginBootLoaderCollection + * @param class-string[] $applicationPluginCollection + * @param PluginBootLoaderInterface[] $pluginBootLoaderCollection */ public function __construct( private readonly array $applicationPluginCollection, private readonly iterable $pluginBootLoaderCollection, - private readonly ?Container $container = null - ) - { - $this->isStarted = false; - $this->isTerminated = false; + private readonly Container $container + ) { + $this->isStarted = false; $this->pluginsLoaded = []; $this->plugins = []; @@ -50,7 +48,7 @@ public function __construct( */ public function run(): void { - if($this->isStarted) { + if ($this->isStarted) { return; } @@ -58,18 +56,6 @@ public function run(): void $this->isStarted = true; } - /** - * {@inheritDoc} - */ - public function terminate(): void - { - if(!$this->isStarted || $this->isTerminated) { - return; - } - - $this->isTerminated = true; - } - /** * {@inheritDoc} */ @@ -83,7 +69,7 @@ public function container(): Container */ public function loadPlugin(string $applicationPluginClass): void { - if (in_array($applicationPluginClass, $this->pluginsLoaded, true)) { + if (\in_array($applicationPluginClass, $this->pluginsLoaded, true)) { return; } @@ -100,18 +86,15 @@ public function loadPlugin(string $applicationPluginClass): void /** * {@inheritDoc} */ - public function plugins(string $interfaceInherited = null): iterable + public function plugins(string $interfaceInherited = null): \Traversable { foreach ($this->plugins as $plugin) { - if(!$interfaceInherited || ($plugin instanceof $interfaceInherited)) { + if (!$interfaceInherited || ($plugin instanceof $interfaceInherited)) { yield $plugin; } } } - /** - * @return void - */ protected function loadPlugins(): void { foreach ($this->applicationPluginCollection as $applicationPlugin) { diff --git a/src/KernelBuilder.php b/src/KernelBuilder.php index 06dc50b..64802be 100755 --- a/src/KernelBuilder.php +++ b/src/KernelBuilder.php @@ -1,43 +1,47 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Micro\Framework\Kernel; use Micro\Component\DependencyInjection\Container; -use Micro\Framework\Kernel\Container\ApplicationContainerFactoryInterface; -use Micro\Framework\Kernel\Container\Impl\ApplicationContainerFactory; use Micro\Framework\Kernel\Plugin\PluginBootLoaderInterface; use Psr\Container\ContainerInterface; class KernelBuilder { /** - * @var iterable + * @var class-string[] */ - private iterable $pluginCollection; + private array $pluginCollection; /** - * @var iterable + * @var PluginBootLoaderInterface[] */ - private iterable $bootLoaderPluginCollection; + private array $bootLoaderPluginCollection; - /** - * @var Container|null - */ private ?Container $container; public function __construct() { - $this->pluginCollection = []; + $this->pluginCollection = []; $this->bootLoaderPluginCollection = []; - $this->container = null; + $this->container = null; } /** - * @param array $applicationPluginCollection + * @param class-string[] $applicationPluginCollection * * @return $this */ - public function setApplicationPlugins(iterable $applicationPluginCollection): self + public function setApplicationPlugins(array $applicationPluginCollection): self { $this->pluginCollection = $applicationPluginCollection; @@ -45,7 +49,6 @@ public function setApplicationPlugins(iterable $applicationPluginCollection): se } /** - * @param PluginBootLoaderInterface $bootLoader * @return $this */ public function addBootLoader(PluginBootLoaderInterface $bootLoader): self @@ -56,7 +59,8 @@ public function addBootLoader(PluginBootLoaderInterface $bootLoader): self } /** - * @param PluginBootLoaderInterface[] $bootLoaderCollection + * @param PluginBootLoaderInterface[] $bootLoaderCollection + * * @return $this */ public function addBootLoaders(iterable $bootLoaderCollection): self @@ -69,7 +73,7 @@ public function addBootLoaders(iterable $bootLoaderCollection): self } /** - * @param Container $container + * @param Container $container * * @return $this */ @@ -80,20 +84,9 @@ public function setContainer(ContainerInterface $container): self return $this; } - /** - * @return ApplicationContainerFactoryInterface - */ - protected function createApplicationContainerFactory(): ApplicationContainerFactoryInterface - { - return new ApplicationContainerFactory(); - } - - /** - * @return Container - */ protected function container(): Container { - return $this->container ?? $this->createApplicationContainerFactory()->create(); + return $this->container ?? new Container(); } /** diff --git a/src/KernelInterface.php b/src/KernelInterface.php index fe1293b..eb1b360 100755 --- a/src/KernelInterface.php +++ b/src/KernelInterface.php @@ -1,92 +1,53 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Micro\Framework\Kernel; use Micro\Component\DependencyInjection\Container; /** * The kernel is needed for plugin management. A plugin can be any class object. - * - * Kernel implementation - * - * GitHub Docs - * - * Packagist Repo - * - * ```php - * interface SomePluginInterface - * { - * public function getName(): string; - * } - * - * $kernel = new Kernel( - * [ - * new class implements SomePluginInterface - * { - * public function getName(): string - * { - * return 'SomePluginName'; - * } - * } - * ], - * [] - * ); - * - * $kernel->run(); - * $iterator = $kernel->plugins(SomePluginInterface::class); - * foreach($iterator as $plugin) - * { - * print_r($plugin->getName() . "\r\n"); - * } - * - * ``` - * - * @api */ interface KernelInterface { /** - * Get service Dependency Injection Container + * Get service Dependency Injection Container. * * @api - * - * @return Container */ public function container(): Container; /** - * Run application + * Run application. * * @api - * - * @return void */ public function run(): void; /** - * Terminate application - * - * @api - * - * @return void - */ - public function terminate(): void; - - /** - * @param string $applicationPluginClass - * - * @return void + * @param class-string $applicationPluginClass */ public function loadPlugin(string $applicationPluginClass): void; /** * Iterate plugins with the specified type. * - * @param string|null $interfaceInherited If empty, each connected plugin will be iterated. + * @template T of object * - * @api + * @psalm-param class-string|null $interfaceInherited if empty, each connected plugin will be iterated + + * + * @return \Traversable Application plugins iterator * - * @return iterable Application plugins iterator + * @api */ - public function plugins(string $interfaceInherited = null): iterable; + public function plugins(string $interfaceInherited = null): \Traversable; } diff --git a/src/Plugin/PluginBootLoaderInterface.php b/src/Plugin/PluginBootLoaderInterface.php index 84da579..702c45b 100755 --- a/src/Plugin/PluginBootLoaderInterface.php +++ b/src/Plugin/PluginBootLoaderInterface.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Micro\Framework\Kernel\Plugin; /** @@ -19,10 +28,6 @@ interface PluginBootLoaderInterface * Immediately after creation, a pre-configuration plugin gets here. * * @api - * - * @param object $applicationPlugin - * - * @return void */ public function boot(object $applicationPlugin): void; } diff --git a/tests/Unit/KernelBuilderTest.php b/tests/Unit/KernelBuilderTest.php new file mode 100644 index 0000000..ce7fb2d --- /dev/null +++ b/tests/Unit/KernelBuilderTest.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Micro\Framework\Kernel\Test\Unit; + +use Micro\Component\DependencyInjection\Container; +use Micro\Framework\Kernel\KernelBuilder; +use Micro\Framework\Kernel\KernelInterface; +use Micro\Framework\Kernel\Plugin\PluginBootLoaderInterface; +use PHPUnit\Framework\TestCase; + +class KernelBuilderTest extends TestCase +{ + private KernelBuilder $builder; + + protected function setUp(): void + { + $this->builder = new KernelBuilder(); + } + + public function testBuildWithoutContainer() + { + $kernel = $this->builder + ->setApplicationPlugins([]) + ->build(); + + $this->assertInstanceOf(KernelInterface::class, $kernel); + } + + public function testBuildWithContainer() + { + $container = new Container(); + + $kernel = $this->builder + ->setApplicationPlugins([]) + ->setContainer($container) + ->build(); + + $this->assertInstanceOf(KernelInterface::class, $kernel); + $this->assertEquals($container, $kernel->container()); + } + + public function testBootLoaders() + { + $kernel = $this->builder + ->setApplicationPlugins([ + \stdClass::class, + ]) + ->addBootLoaders([ + $this->createBootLoader(), + $this->createBootLoader(), + ]) + ->addBootLoader($this->createBootLoader()) + ->build() + ; + + $kernel->run(); + } + + protected function createBootLoader() + { + $bl = $this->createMock(PluginBootLoaderInterface::class); + $bl + ->expects($this->once()) + ->method('boot'); + + return $bl; + } +} diff --git a/tests/Unit/KernelTest.php b/tests/Unit/KernelTest.php new file mode 100644 index 0000000..475e93a --- /dev/null +++ b/tests/Unit/KernelTest.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Micro\Framework\Kernel\Test\Unit; + +use Micro\Component\DependencyInjection\Container; +use Micro\Framework\Kernel\Kernel; +use Micro\Framework\Kernel\KernelInterface; +use Micro\Framework\Kernel\Plugin\PluginBootLoaderInterface; +use PHPUnit\Framework\TestCase; + +class KernelTest extends TestCase +{ + private KernelInterface $kernel; + + private Container $container; + + protected function setUp(): void + { + $plugins = []; + for ($i = 0; $i < 3; ++$i) { + $plugins[] = \stdClass::class; + } + + $bootLoaders = []; + for ($i = 0; $i < 3; ++$i) { + $bootLoader = $this->createMock(PluginBootLoaderInterface::class); + $bootLoader + ->expects($this->any()) + ->method('boot'); + + $bootLoaders[] = $bootLoader; + } + + $this->container = new Container(); + + $this->kernel = new Kernel( + $plugins, + $bootLoaders, + $this->container, + ); + + $this->kernel->run(); + } + + public function testKernelPlugins() + { + foreach ($this->kernel->plugins(\stdClass::class) as $plugin) { + $this->assertInstanceOf(\stdClass::class, $plugin); + } + + foreach ($this->kernel->plugins() as $plugin) { + $this->assertInstanceOf(\stdClass::class, $plugin); + } + } + + public function testRunAgain() + { + $kernel = $this->getMockBuilder(Kernel::class) + ->enableOriginalConstructor() + ->setConstructorArgs( + [ + [], + [], + new Container(), + ] + ) + ->onlyMethods([ + 'loadPlugins', + ]) + ->getMock(); + + $kernel + ->expects($this->once()) + ->method('loadPlugins'); + + $kernel->run(); + $kernel->run(); + } + + public function testContainer() + { + $this->assertEquals($this->container, $this->kernel->container()); + } +}