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 new file mode 100644 index 0000000..b9060b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +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/composer.json b/composer.json new file mode 100644 index 0000000..502a68e --- /dev/null +++ b/composer.json @@ -0,0 +1,56 @@ +{ + "name": "micro/plugin-http-router-code", + "description": "Micro Framework: Http route locator from other plugins by plugin interface", + "license": "MIT", + "type": "micro-plugin", + "authors": [ + { + "name": "Stanislau Komar", + "email": "kost@micro-php.net" + } + ], + "require": { + "micro/http-core": "dev-master" + }, + "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\\Plugin\\Http\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Micro\\Plugin\\Http\\Test\\": "test/" + } + }, + "config": { + "allow-plugins": { + "ergebnis/composer-normalize": true + } + }, + "scripts": { + "coverage": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-text", + "php-cs-fix": "./vendor/bin/php-cs-fixer fix --verbose --using-cache=no", + "php-cs-try": "./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", + "statics": [ + "@phpstan", + "@psalm" + ], + "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..a9f4a78 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,27 @@ + + + + + + + + + test/Unit + + + + + src + + + + + + \ No newline at end of file diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..ee97e34 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Business/Locator/RouteCodeLocator.php b/src/Business/Locator/RouteCodeLocator.php new file mode 100644 index 0000000..c41d464 --- /dev/null +++ b/src/Business/Locator/RouteCodeLocator.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Micro\Plugin\Http\Business\Locator; + +use Micro\Framework\Kernel\KernelInterface; +use Micro\Plugin\Http\Facade\HttpFacadeInterface; +use Micro\Plugin\Http\Plugin\RouteProviderPluginInterface; + +/** + * @author Stanislau Komar + */ +readonly class RouteCodeLocator implements RouteLocatorInterface +{ + /** + * @param KernelInterface $kernel + */ + public function __construct( + private KernelInterface $kernel + ) + { + } + + /** + * {@inheritDoc} + */ + public function locate(): iterable + { + $iterator = $this->kernel->plugins(RouteProviderPluginInterface::class); + /** @var RouteProviderPluginInterface $plugin */ + foreach ($iterator as $plugin) { + foreach ($plugin->provideRoutes() as $route) { + yield $route; + } + } + } +} \ No newline at end of file diff --git a/src/HttpRouterCodePlugin.php b/src/HttpRouterCodePlugin.php new file mode 100644 index 0000000..e26a996 --- /dev/null +++ b/src/HttpRouterCodePlugin.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Micro\Plugin\Http; + +use Micro\Component\DependencyInjection\Container; +use Micro\Framework\Kernel\KernelInterface; +use Micro\Framework\Kernel\Plugin\DependencyProviderInterface; +use Micro\Framework\Kernel\Plugin\PluginDependedInterface; +use Micro\Plugin\Http\Business\Locator\RouteCodeLocator; +use Micro\Plugin\Http\Business\Locator\RouteLocatorInterface; +use Micro\Plugin\Http\Plugin\HttpRouteLocatorPluginInterface; + +/** + * @author Stanislau Komar + */ +readonly class HttpRouterCodePlugin implements HttpRouteLocatorPluginInterface, DependencyProviderInterface, PluginDependedInterface +{ + /** + * @var Container + * @phpstan-ignore-next-line + */ + private Container $container; + + public function provideDependencies(Container $container): void + { + // @phpstan-ignore-next-line + $this->container = $container; + } + + public function getLocatorType(): string + { + return 'code'; + } + + public function createLocator(): RouteLocatorInterface + { + $kernel = $this->container->get(KernelInterface::class); + // @phpstan-ignore-next-line + return new RouteCodeLocator($kernel); + } + + /** + * {@inheritDoc} + */ + public function getDependedPlugins(): iterable + { + return [ + HttpCorePlugin::class, + ]; + } +} \ No newline at end of file diff --git a/src/Plugin/RouteProviderPluginInterface.php b/src/Plugin/RouteProviderPluginInterface.php new file mode 100644 index 0000000..70b048c --- /dev/null +++ b/src/Plugin/RouteProviderPluginInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Micro\Plugin\Http\Plugin; + +use Micro\Plugin\Http\Business\Route\RouteInterface; + +/** + * @author Stanislau Komar + */ +interface RouteProviderPluginInterface +{ + /** + * @return iterable + */ + public function provideRoutes(): iterable; +} \ No newline at end of file diff --git a/test/Unit/AppTest.php b/test/Unit/AppTest.php new file mode 100644 index 0000000..ef06c70 --- /dev/null +++ b/test/Unit/AppTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Micro\Plugin\Http\Test\Unit; + +use Micro\Framework\Kernel\Configuration\DefaultApplicationConfiguration; +use Micro\Kernel\App\AppKernel; +use Micro\Plugin\Http\Facade\HttpFacadeInterface; +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; + +/** + * @author Stanislau Komar + */ +class AppTest extends TestCase +{ + public function testApp() + { + $configuration = new DefaultApplicationConfiguration([]); + $kernel = new AppKernel( + $configuration, + [ + TestPlugin::class + ], + ); + + $kernel->run(); + $request = Request::create('/kost'); + $response = $kernel->container()->get(HttpFacadeInterface::class) + ->execute($request); + + $this->assertEquals('Hello, kost', $response->getContent()); + + } +} \ No newline at end of file diff --git a/test/Unit/Business/Locator/RouteCodeLocatorTest.php b/test/Unit/Business/Locator/RouteCodeLocatorTest.php new file mode 100644 index 0000000..0c27d1c --- /dev/null +++ b/test/Unit/Business/Locator/RouteCodeLocatorTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + + +namespace Micro\Plugin\Http\Test\Unit\Business\Locator; + +use Micro\Framework\Kernel\KernelInterface; +use Micro\Plugin\Http\Business\Locator\RouteCodeLocator; +use Micro\Plugin\Http\Business\Route\RouteInterface; +use Micro\Plugin\Http\Plugin\RouteProviderPluginInterface; +use PHPUnit\Framework\TestCase; + +class RouteCodeLocatorTest extends TestCase +{ + + public function testLocate() + { + $kernel = $this->createMock(KernelInterface::class); + $kernel + ->expects($this->once()) + ->method('plugins') + ->with(RouteProviderPluginInterface::class) + ->willReturn([ + $this->createRouteProvider(), + $this->createRouteProvider() + ]); + + $routerCodeLocator = new RouteCodeLocator($kernel); + foreach ($routerCodeLocator->locate() as $route) { + $this->assertInstanceOf(RouteInterface::class, $route); + } + } + + protected function createRouteProvider() + { + $routeProvider = $this->createMock(RouteProviderPluginInterface::class); + $routeProvider + ->expects($this->once()) + ->method('provideRoutes') + ->willReturn([ + $this->createMock(RouteInterface::class), + ]); + + return $routeProvider; + } +} diff --git a/test/Unit/HttpRouterCodePluginTest.php b/test/Unit/HttpRouterCodePluginTest.php new file mode 100644 index 0000000..d8cd7e9 --- /dev/null +++ b/test/Unit/HttpRouterCodePluginTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Micro\Plugin\Http\Test\Unit; + +use Micro\Component\DependencyInjection\Container; +use Micro\Framework\Kernel\KernelInterface; +use Micro\Framework\Kernel\Plugin\DependencyProviderInterface; +use Micro\Plugin\Http\Business\Locator\RouteLocatorInterface; +use Micro\Plugin\Http\HttpCorePlugin; +use Micro\Plugin\Http\HttpRouterCodePlugin; +use Micro\Plugin\Http\Plugin\HttpRouteLocatorPluginInterface; +use PHPUnit\Framework\TestCase; + +class HttpRouterCodePluginTest extends TestCase +{ + protected \Micro\Plugin\Http\HttpRouterCodePlugin $plugin; + protected Container $container; + + public function setUp(): void + { + $this->container = $this->createMock(Container::class); + $this->plugin = new HttpRouterCodePlugin(); + $this->plugin->provideDependencies($this->container); + } + + public function testConstruct() { + $this->assertInstanceOf(HttpRouteLocatorPluginInterface::class, $this->plugin); + } + + public function testGetLocatorType() + { + $this->assertEquals('code', $this->plugin->getLocatorType()); + } + + public function testCreateLocator() + { + $this->container + ->expects($this->once()) + ->method('get') + ->with(KernelInterface::class) + ->willReturn( + $this->createMock(KernelInterface::class) + ); + + $this->assertInstanceOf(RouteLocatorInterface::class, $this->plugin->createLocator()); + } + + public function testProvideDependencies() + { + $this->assertInstanceOf(DependencyProviderInterface::class, $this->plugin); + } + + public function testGetDependedPlugins() + { + $this->assertEquals( + [ HttpCorePlugin::class ], + $this->plugin->getDependedPlugins() + ); + } +} diff --git a/test/Unit/TestPlugin.php b/test/Unit/TestPlugin.php new file mode 100644 index 0000000..534edca --- /dev/null +++ b/test/Unit/TestPlugin.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Micro\Plugin\Http\Test\Unit; + +use Micro\Component\DependencyInjection\Container; +use Micro\Framework\Kernel\Plugin\DependencyProviderInterface; +use Micro\Framework\Kernel\Plugin\PluginDependedInterface; +use Micro\Plugin\Http\Facade\HttpFacadeInterface; +use Micro\Plugin\Http\HttpRouterCodePlugin; +use Micro\Plugin\Http\Plugin\RouteProviderPluginInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * @author Stanislau Komar + */ +class TestPlugin implements RouteProviderPluginInterface, DependencyProviderInterface, PluginDependedInterface +{ + private Container $container; + + public function provideDependencies(Container $container): void + { + $this->container = $container; + } + + public function provideRoutes(): iterable + { + yield $this->container + ->get(HttpFacadeInterface::class) + ->createRouteBuilder() + ->setUri('/{parameter}') + ->setController(function(Request $request) { return new Response('Hello, ' . $request->get('parameter'));}) + ->build() + ; + } + + public function getDependedPlugins(): iterable + { + return [ + HttpRouterCodePlugin::class + ]; + } +} \ No newline at end of file