From 82c93e2d6e429702ee52af812783a5368a8d970d Mon Sep 17 00:00:00 2001 From: Mateusz Charytoniuk <mateusz.charytoniuk@protonmail.com> Date: Tue, 13 Feb 2024 04:32:54 +0100 Subject: [PATCH] feat: RequiresPhpExtension attribute --- src/Attribute/RequiresPhpExtension.php | 17 +++++++++++ src/Command/GrpcGenerate.php | 2 ++ src/Command/PostfixBounce.php | 7 ++--- src/Command/Watch.php | 9 ++---- src/DependencyInjectionContainer.php | 13 ++++++++ .../MissingPhpExtension.php | 30 +++++++++++++++++++ src/DoctrineMySQLDriver.php | 2 ++ src/DoctrinePostgreSQLDriver.php | 2 ++ src/DoctrineSQLiteDriver.php | 2 ++ src/IntlFormatter.php | 2 ++ src/LlamaCppClient.php | 2 ++ src/SQLiteVSSConnectionBuilder.php | 2 ++ ...tabaseConnectionPoolRepositoryProvider.php | 2 ++ ...octrineEntityManagerRepositoryProvider.php | 2 ++ ...oolConnectionBuilderCollectionProvider.php | 2 ++ 15 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 src/Attribute/RequiresPhpExtension.php create mode 100644 src/DependencyInjectionContainerException/MissingPhpExtension.php diff --git a/src/Attribute/RequiresPhpExtension.php b/src/Attribute/RequiresPhpExtension.php new file mode 100644 index 00000000..8cff848e --- /dev/null +++ b/src/Attribute/RequiresPhpExtension.php @@ -0,0 +1,17 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\Attribute; + +use Attribute; +use Distantmagic\Resonance\Attribute as BaseAttribute; + +#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)] +final readonly class RequiresPhpExtension extends BaseAttribute +{ + /** + * @param non-empty-string $name + */ + public function __construct(public string $name) {} +} diff --git a/src/Command/GrpcGenerate.php b/src/Command/GrpcGenerate.php index 6078bd36..3d200a94 100644 --- a/src/Command/GrpcGenerate.php +++ b/src/Command/GrpcGenerate.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Distantmagic\Resonance\Command; use Distantmagic\Resonance\Attribute\ConsoleCommand; +use Distantmagic\Resonance\Attribute\RequiresPhpExtension; use Distantmagic\Resonance\Attribute\WantsFeature; use Distantmagic\Resonance\Command; use Distantmagic\Resonance\Feature; @@ -16,6 +17,7 @@ use Symfony\Component\Console\Output\OutputInterface; name: 'grpc:generate', description: 'Generate GRPC stubs' )] +#[RequiresPhpExtension('grpc')] #[WantsFeature(Feature::Grpc)] final class GrpcGenerate extends Command { diff --git a/src/Command/PostfixBounce.php b/src/Command/PostfixBounce.php index 6db07429..19c79966 100644 --- a/src/Command/PostfixBounce.php +++ b/src/Command/PostfixBounce.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Distantmagic\Resonance\Command; use Distantmagic\Resonance\Attribute\ConsoleCommand; +use Distantmagic\Resonance\Attribute\RequiresPhpExtension; use Distantmagic\Resonance\Attribute\WantsFeature; use Distantmagic\Resonance\Command; use Distantmagic\Resonance\CoroutineCommand; @@ -22,6 +23,8 @@ use Symfony\Component\Console\Output\OutputInterface; name: 'postfix:bounce', description: 'Handles email bounces (requires mailparse)' )] +#[RequiresPhpExtension('http')] +#[RequiresPhpExtension('mailparse')] #[WantsFeature(Feature::Postfix)] final class PostfixBounce extends CoroutineCommand { @@ -36,10 +39,6 @@ final class PostfixBounce extends CoroutineCommand protected function executeInCoroutine(InputInterface $input, OutputInterface $output): int { - if (!extension_loaded('mailparse') || !extension_loaded('http')) { - throw new RuntimeException('You need to install "http" and "mailparse" extensions'); - } - $content = stream_get_contents(STDIN); if (false === $content || empty($content)) { diff --git a/src/Command/Watch.php b/src/Command/Watch.php index 19bf802c..ab17e8c9 100644 --- a/src/Command/Watch.php +++ b/src/Command/Watch.php @@ -6,10 +6,10 @@ namespace Distantmagic\Resonance\Command; use Distantmagic\Resonance\ApplicationConfiguration; use Distantmagic\Resonance\Attribute\ConsoleCommand; +use Distantmagic\Resonance\Attribute\RequiresPhpExtension; use Distantmagic\Resonance\Command; use Distantmagic\Resonance\InotifyIterator; use Psr\Log\LoggerInterface; -use RuntimeException; use Swoole\Process; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -17,8 +17,9 @@ use Symfony\Component\Console\Output\OutputInterface; #[ConsoleCommand( name: 'watch:command', - description: 'Watch project files for changes (requires inotify)' + description: 'Watch project files for changes' )] +#[RequiresPhpExtension('inotify')] final class Watch extends Command { private ?Process $process = null; @@ -37,10 +38,6 @@ final class Watch extends Command protected function execute(InputInterface $input, OutputInterface $output): int { - if (!extension_loaded('inotify')) { - throw new RuntimeException('You need to install "inotify" extension'); - } - /** * @var string $childCommandName */ diff --git a/src/DependencyInjectionContainer.php b/src/DependencyInjectionContainer.php index 61b47071..eaaa224f 100644 --- a/src/DependencyInjectionContainer.php +++ b/src/DependencyInjectionContainer.php @@ -5,8 +5,10 @@ declare(strict_types=1); namespace Distantmagic\Resonance; use Closure; +use Distantmagic\Resonance\Attribute\RequiresPhpExtension; use Distantmagic\Resonance\DependencyInjectionContainerException\AmbiguousProvider; use Distantmagic\Resonance\DependencyInjectionContainerException\DisabledFeatureProvider; +use Distantmagic\Resonance\DependencyInjectionContainerException\MissingPhpExtension; use Distantmagic\Resonance\DependencyInjectionContainerException\MissingProvider; use Ds\Map; use Ds\Set; @@ -224,6 +226,17 @@ readonly class DependencyInjectionContainer */ private function makeClassFromReflection(ReflectionClass $reflectionClass, DependencyStack $stack): object { + $reflectionClassAttributeManager = new ReflectionClassAttributeManager($reflectionClass); + $requiredPhpExtensions = $reflectionClassAttributeManager->findAttributes(RequiresPhpExtension::class); + + if (!$requiredPhpExtensions->isEmpty()) { + foreach ($requiredPhpExtensions as $requiredPhpExtension) { + if (!extension_loaded($requiredPhpExtension->name)) { + throw new MissingPhpExtension($reflectionClass->name, $requiredPhpExtension->name); + } + } + } + $constructorReflection = $reflectionClass->getConstructor(); if ($constructorReflection) { diff --git a/src/DependencyInjectionContainerException/MissingPhpExtension.php b/src/DependencyInjectionContainerException/MissingPhpExtension.php new file mode 100644 index 00000000..35a34078 --- /dev/null +++ b/src/DependencyInjectionContainerException/MissingPhpExtension.php @@ -0,0 +1,30 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\DependencyInjectionContainerException; + +use Distantmagic\Resonance\DependencyInjectionContainerException; +use Throwable; + +class MissingPhpExtension extends DependencyInjectionContainerException +{ + /** + * @param class-string $className + * @param non-empty-string $extensionName + */ + public function __construct( + string $className, + string $extensionName, + ?Throwable $previous = null, + ) { + parent::__construct( + sprintf( + 'To use "%s" you need to install "%s" PHP extension.', + $className, + $extensionName, + ), + $previous + ); + } +} diff --git a/src/DoctrineMySQLDriver.php b/src/DoctrineMySQLDriver.php index a74d99db..ac2636f3 100644 --- a/src/DoctrineMySQLDriver.php +++ b/src/DoctrineMySQLDriver.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Distantmagic\Resonance; +use Distantmagic\Resonance\Attribute\RequiresPhpExtension; use Distantmagic\Resonance\Attribute\Singleton; use Doctrine\DBAL\Driver\AbstractMySQLDriver; @@ -13,6 +14,7 @@ use Doctrine\DBAL\Driver\AbstractMySQLDriver; * * @psalm-suppress DeprecatedInterface */ +#[RequiresPhpExtension('pdo')] #[Singleton] class DoctrineMySQLDriver extends AbstractMySQLDriver { diff --git a/src/DoctrinePostgreSQLDriver.php b/src/DoctrinePostgreSQLDriver.php index b0147adf..8c406390 100644 --- a/src/DoctrinePostgreSQLDriver.php +++ b/src/DoctrinePostgreSQLDriver.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Distantmagic\Resonance; +use Distantmagic\Resonance\Attribute\RequiresPhpExtension; use Distantmagic\Resonance\Attribute\Singleton; use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver; @@ -13,6 +14,7 @@ use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver; * * @psalm-suppress DeprecatedInterface */ +#[RequiresPhpExtension('pdo')] #[Singleton] class DoctrinePostgreSQLDriver extends AbstractPostgreSQLDriver { diff --git a/src/DoctrineSQLiteDriver.php b/src/DoctrineSQLiteDriver.php index fa6f0f10..014e6ec2 100644 --- a/src/DoctrineSQLiteDriver.php +++ b/src/DoctrineSQLiteDriver.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Distantmagic\Resonance; +use Distantmagic\Resonance\Attribute\RequiresPhpExtension; use Distantmagic\Resonance\Attribute\Singleton; use Doctrine\DBAL\Driver\AbstractSQLiteDriver; @@ -13,6 +14,7 @@ use Doctrine\DBAL\Driver\AbstractSQLiteDriver; * * @psalm-suppress DeprecatedInterface */ +#[RequiresPhpExtension('pdo')] #[Singleton] class DoctrineSQLiteDriver extends AbstractSQLiteDriver { diff --git a/src/IntlFormatter.php b/src/IntlFormatter.php index 7fbca43c..baa66291 100644 --- a/src/IntlFormatter.php +++ b/src/IntlFormatter.php @@ -6,11 +6,13 @@ namespace Distantmagic\Resonance; use DateTimeImmutable; use DateTimeInterface; +use Distantmagic\Resonance\Attribute\RequiresPhpExtension; use Distantmagic\Resonance\Attribute\Singleton; use IntlDateFormatter; use Swoole\Http\Request; #[Singleton] +#[RequiresPhpExtension('intl')] final readonly class IntlFormatter { private IntlDateFormatterRepository $formatters; diff --git a/src/LlamaCppClient.php b/src/LlamaCppClient.php index 48b298e9..09b7a3ef 100644 --- a/src/LlamaCppClient.php +++ b/src/LlamaCppClient.php @@ -5,12 +5,14 @@ declare(strict_types=1); namespace Distantmagic\Resonance; use CurlHandle; +use Distantmagic\Resonance\Attribute\RequiresPhpExtension; use Distantmagic\Resonance\Attribute\Singleton; use Generator; use JsonSerializable; use RuntimeException; use Swoole\Coroutine\Channel; +#[RequiresPhpExtension('curl')] #[Singleton] readonly class LlamaCppClient { diff --git a/src/SQLiteVSSConnectionBuilder.php b/src/SQLiteVSSConnectionBuilder.php index be27327a..0523cb5d 100644 --- a/src/SQLiteVSSConnectionBuilder.php +++ b/src/SQLiteVSSConnectionBuilder.php @@ -4,9 +4,11 @@ declare(strict_types=1); namespace Distantmagic\Resonance; +use Distantmagic\Resonance\Attribute\RequiresPhpExtension; use Distantmagic\Resonance\Attribute\Singleton; use SQLite3; +#[RequiresPhpExtension('sqlite3')] #[Singleton] readonly class SQLiteVSSConnectionBuilder { diff --git a/src/SingletonProvider/DatabaseConnectionPoolRepositoryProvider.php b/src/SingletonProvider/DatabaseConnectionPoolRepositoryProvider.php index a79340aa..dcfa7966 100644 --- a/src/SingletonProvider/DatabaseConnectionPoolRepositoryProvider.php +++ b/src/SingletonProvider/DatabaseConnectionPoolRepositoryProvider.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Distantmagic\Resonance\SingletonProvider; +use Distantmagic\Resonance\Attribute\RequiresPhpExtension; use Distantmagic\Resonance\Attribute\Singleton; use Distantmagic\Resonance\DatabaseConfiguration; use Distantmagic\Resonance\DatabaseConnectionPoolRepository; @@ -17,6 +18,7 @@ use Swoole\Database\PDOConfig; /** * @template-extends SingletonProvider<DatabaseConnectionPoolRepository> */ +#[RequiresPhpExtension('pdo')] #[Singleton(provides: DatabaseConnectionPoolRepository::class)] final readonly class DatabaseConnectionPoolRepositoryProvider extends SingletonProvider { diff --git a/src/SingletonProvider/DoctrineEntityManagerRepositoryProvider.php b/src/SingletonProvider/DoctrineEntityManagerRepositoryProvider.php index 587ab4f6..41a8fb93 100644 --- a/src/SingletonProvider/DoctrineEntityManagerRepositoryProvider.php +++ b/src/SingletonProvider/DoctrineEntityManagerRepositoryProvider.php @@ -6,6 +6,7 @@ namespace Distantmagic\Resonance\SingletonProvider; use Distantmagic\Resonance\ApplicationConfiguration; use Distantmagic\Resonance\Attribute\GrantsFeature; +use Distantmagic\Resonance\Attribute\RequiresPhpExtension; use Distantmagic\Resonance\Attribute\Singleton; use Distantmagic\Resonance\DoctrineConnectionRepository; use Distantmagic\Resonance\DoctrineEntityManagerRepository; @@ -21,6 +22,7 @@ use Symfony\Component\Filesystem\Filesystem; /** * @template-extends SingletonProvider<DoctrineEntityManagerRepository> */ +#[RequiresPhpExtension('pdo')] #[GrantsFeature(Feature::Doctrine)] #[Singleton(provides: DoctrineEntityManagerRepository::class)] final readonly class DoctrineEntityManagerRepositoryProvider extends SingletonProvider diff --git a/src/SingletonProvider/PDOPoolConnectionBuilderCollectionProvider.php b/src/SingletonProvider/PDOPoolConnectionBuilderCollectionProvider.php index 74021897..1820af1f 100644 --- a/src/SingletonProvider/PDOPoolConnectionBuilderCollectionProvider.php +++ b/src/SingletonProvider/PDOPoolConnectionBuilderCollectionProvider.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Distantmagic\Resonance\SingletonProvider; use Distantmagic\Resonance\Attribute\BuildsPDOPoolConnection; +use Distantmagic\Resonance\Attribute\RequiresPhpExtension; use Distantmagic\Resonance\Attribute\RequiresSingletonCollection; use Distantmagic\Resonance\Attribute\Singleton; use Distantmagic\Resonance\HttpResponderCollection; @@ -19,6 +20,7 @@ use Distantmagic\Resonance\SingletonProvider; /** * @template-extends SingletonProvider<HttpResponderCollection> */ +#[RequiresPhpExtension('pdo')] #[RequiresSingletonCollection(SingletonCollection::PDOPoolConnectionBuilder)] #[Singleton(provides: PDOPoolConnectionBuilderCollection::class)] final readonly class PDOPoolConnectionBuilderCollectionProvider extends SingletonProvider -- GitLab