diff --git a/.env.sample b/.env.sample index ce86754..d956ffc 100644 --- a/.env.sample +++ b/.env.sample @@ -1,8 +1,8 @@ # Debug mode disabled view cache and enabled higher verbosity. -DEBUG = true +DEBUG=true # Set to application specific value, used to encrypt/decrypt cookies and etc. -ENCRYPTER_KEY = {encrypt-key} +ENCRYPTER_KEY={encrypt-key} # Set to TRUE to disable confirmation in `migrate` commands. -SAFE_MIGRATIONS = true \ No newline at end of file +SAFE_MIGRATIONS=true \ No newline at end of file diff --git a/.rr.yaml b/.rr.yaml index 8489436..256a3a0 100644 --- a/.rr.yaml +++ b/.rr.yaml @@ -1,25 +1,29 @@ -# grpc service configuration. -grpc: - listen: tcp://0.0.0.0:50051 - proto: "proto/service.proto" - workers.command: "php app.php" - tls.key: "app.key" - tls.cert: "app.crt" +version: '2.7' -# queue and jobs -jobs: - dispatch: - app-job-*.pipeline: "local" - pipelines: - local: - broker: "ephemeral" - consume: ["local"] +rpc: + listen: tcp://127.0.0.1:6001 - workers: +server: command: "php app.php" - pool.numWorkers: 2 + relay: pipes -# control the max memory usage -limit: - services: - grpc.maxMemory: 100 \ No newline at end of file +# queue and jobs +jobs: + consume: [ "local" ] + pool: + num_workers: 2 + supervisor: + max_worker_memory: 100 + +# grpc service configuration. +grpc: + listen: "tcp://0.0.0.0:50051" + proto: + - "proto/service.proto" + tls: + key: "app.key" + cert: "app.crt" + pool: + num_workers: 2 + supervisor: + max_worker_memory: 100 diff --git a/README.md b/README.md index 2a9073b..59e7efe 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Spiral Framework is a High-Performance PHP/Go Full-Stack framework and group of Server Requirements -------- Make sure that your server is configured with following PHP version and extensions: -* PHP 7.2+, 64bit +* PHP 8.0+, 64bit * **mb-string** extension * PDO Extension with desired database drivers * [Install](https://github.com/protocolbuffers/protobuf/tree/master/php) `protobuf-ext` to gain higher performance. @@ -48,11 +48,16 @@ In order to run GRPC server you must specify location of server key and certific ```yaml grpc: - listen: tcp://0.0.0.0:50051 - proto: "proto/service.proto" - workers.command: "php app.php" - tls.key: "app.key" - tls.cert: "app.crt" + listen: "tcp://0.0.0.0:50051" + proto: + - "proto/service.proto" + tls: + key: "app.key" + cert: "app.crt" + pool: + num_workers: 2 + supervisor: + max_worker_memory: 100 ``` To issue local certificate: @@ -64,13 +69,13 @@ $ openssl req -newkey rsa:2048 -nodes -keyout app.key -x509 -days 365 -out app.c To start application server execute: ``` -$ ./spiral serve -v -d +$ ./rr serve ``` On Windows: ``` -$ spiral.exe serve -v -d +$ rr.exe serve ``` You can test your endpoints using any GRPC client. For example using [grpcui](https://github.com/fullstorydev/grpcui): @@ -90,7 +95,7 @@ In order to compile protobuf declarations into service code make sure to install To update or generate service code for your application run: ``` -$ php ./app.php grpc:generate proto/service.proto +$ php ./app.php grpc:generate ``` Generated code will be available in `app/src/Service`. Implemented service will be automatically registered in your application. diff --git a/app.php b/app.php index 9926415..d624f98 100644 --- a/app.php +++ b/app.php @@ -30,9 +30,13 @@ // // Initialize shared container, bindings, directories and etc. // -$app = App::init(['root' => __DIR__]); +$app = App::create( + directories: ['root' => __DIR__] +)->run(); -if ($app !== null) { - $code = (int)$app->serve(); - exit($code); +if ($app === null) { + exit(255); } + +$code = (int)$app->serve(); +exit($code); diff --git a/app/config/database.php b/app/config/database.php index 522addb..382c601 100644 --- a/app/config/database.php +++ b/app/config/database.php @@ -9,6 +9,8 @@ declare(strict_types=1); +use Cycle\Database\Config; + return [ /** * Default database connection @@ -36,10 +38,9 @@ * the driver class and its connection options. */ 'drivers' => [ - 'sqlite' => [ - 'driver' => \Spiral\Database\Driver\SQLite\SQLiteDriver::class, - 'connection' => 'sqlite:' . directory('root') . 'app.db', - 'profiling' => true, - ], + 'sqlite' => new Config\SQLiteDriverConfig( + connection: new Config\SQLite\MemoryConnectionConfig(), + queryCache: true + ), ], ]; diff --git a/app/config/grpc.php b/app/config/grpc.php new file mode 100644 index 0000000..f07051b --- /dev/null +++ b/app/config/grpc.php @@ -0,0 +1,13 @@ + __DIR__ . '/../../protoc-gen-php-grpc', + 'services' => [ + __DIR__ . '/../../proto/service.proto', + ], +]; diff --git a/app/src/App.php b/app/src/App.php index 8210c7a..314db18 100644 --- a/app/src/App.php +++ b/app/src/App.php @@ -13,8 +13,10 @@ use Spiral\Bootloader as Framework; use Spiral\DotEnv\Bootloader as DotEnv; +use Spiral\Cycle\Bootloader as CycleBridge; use Spiral\Framework\Kernel; use Spiral\Prototype\Bootloader as Prototype; +use Spiral\RoadRunnerBridge\Bootloader as RoadRunnerBridge; class App extends Kernel { @@ -31,20 +33,23 @@ class App extends Kernel Framework\Security\EncrypterBootloader::class, // Databases - Framework\Database\DatabaseBootloader::class, - Framework\Database\MigrationsBootloader::class, + CycleBridge\DatabaseBootloader::class, + CycleBridge\MigrationsBootloader::class, // ORM - Framework\Cycle\CycleBootloader::class, - Framework\Cycle\ProxiesBootloader::class, - Framework\Cycle\AnnotatedBootloader::class, + CycleBridge\SchemaBootloader::class, + CycleBridge\CycleOrmBootloader::class, + CycleBridge\AnnotatedBootloader::class, + CycleBridge\CommandBootloader::class, // Dispatchers - Framework\GRPC\GRPCBootloader::class, - Framework\Jobs\JobsBootloader::class, + RoadRunnerBridge\GRPCBootloader::class, + RoadRunnerBridge\QueueBootloader::class, // Framework commands Framework\CommandBootloader::class, + CycleBridge\CommandBootloader::class, + RoadRunnerBridge\CommandBootloader::class, // Debugging Framework\DebugBootloader::class, diff --git a/app/src/Command/GetBinaryCommand.php b/app/src/Command/GetBinaryCommand.php new file mode 100644 index 0000000..43c8cb1 --- /dev/null +++ b/app/src/Command/GetBinaryCommand.php @@ -0,0 +1,237 @@ +os = new OperatingSystemOption($this); + $this->arch = new ArchitectureOption($this); + $this->version = new VersionFilterOption($this); + $this->location = new InstallationLocationOption($this); + $this->stability = new StabilityOption($this); + } + + public function getDescription(): string + { + return 'Install or update protoc-gen-php-grpc binary'; + } + + public function execute(InputInterface $input, OutputInterface $output): int + { + $io = $this->io($input, $output); + + $target = $this->location->get($input, $io); + $repository = $this->getRepository(); + + $output->writeln(''); + $output->writeln(' Environment:'); + $output->writeln(\sprintf(' - Version: %s', $this->version->get($input, $io))); + $output->writeln(\sprintf(' - Stability: %s', $this->stability->get($input, $io))); + $output->writeln(\sprintf(' - Operating System: %s', $this->os->get($input, $io))); + $output->writeln(\sprintf(' - Architecture: %s', $this->arch->get($input, $io))); + $output->writeln(''); + + // List of all available releases + $releases = $this->version->find($input, $io, $repository); + + /** + * @var AssetInterface $asset + * @var ReleaseInterface $release + */ + [$asset, $release] = $this->findAsset($repository, $releases, $input, $io); + + // Installation + $output->writeln( + \sprintf(' - %s', $release->getRepositoryName()) . + \sprintf(' (%s):', $release->getVersion()) . + ' Downloading...' + ); + + if ($output->isVerbose()) { + $output->writeln(\sprintf(' -- %s', $asset->getName())); + } + + // Install rr binary + $file = $this->installBinary($target, $release, $asset, $io, $output); + + // Success + if ($file === null) { + $io->warning('protoc-gen-php-grpc has not been installed'); + + return 1; + } + + return 0; + } + + private function installBinary( + string $target, + ReleaseInterface $release, + AssetInterface $asset, + StyleInterface $io, + OutputInterface $out + ): ?\SplFileInfo { + $extractor = $this->assetToArchive($asset, $out) + ->extract([ + 'protoc-gen-php-grpc.exe' => $target . '/protoc-gen-php-grpc.exe', + 'protoc-gen-php-grpc' => $target . '/protoc-gen-php-grpc', + ]); + + $file = null; + while ($extractor->valid()) { + $file = $extractor->current(); + + if (!$this->checkExisting($file, $io)) { + $extractor->send(false); + continue; + } + + // Success + $path = $file->getRealPath() ?: $file->getPathname(); + $message = 'protoc-gen-php-grpc (%s) has been installed into %s'; + $message = \sprintf($message, $release->getVersion(), $path); + $out->writeln($message); + + $extractor->next(); + + if (!$file->isExecutable()) { + @chmod($file->getRealPath(), 0755); + } + } + + return $file; + } + + private function checkExisting(\SplFileInfo $bin, StyleInterface $io): bool + { + if (\is_file($bin->getPathname())) { + $io->warning('protoc-gen-php-grpc binary file already exists!'); + + if (!$io->confirm('Do you want overwrite it?', false)) { + $io->note('Skipping protoc-gen-php-grpc installation...'); + + return false; + } + } + + return true; + } + + private function findAsset( + RepositoryInterface $repo, + ReleasesCollection $releases, + InputInterface $in, + StyleInterface $io + ): array { + $osOption = $this->os->get($in, $io); + $archOption = $this->arch->get($in, $io); + $stabilityOption = $this->stability->get($in, $io); + + /** @var ReleaseInterface[] $filtered */ + $filtered = $releases + ->minimumStability($stabilityOption) + ->withAssets(); + + foreach ($filtered as $release) { + $asset = $release->getAssets() + ->filter( + static fn (AssetInterface $asset): bool => + \str_starts_with($asset->getName(), 'protoc-gen-php-grpc') + ) + ->whereArchitecture($archOption) + ->whereOperatingSystem($osOption) + ->first(); + + if ($asset === null) { + $io->warning( + \vsprintf('%s %s does not contain available assembly (further search in progress)', [ + $repo->getName(), + $release->getVersion(), + ]) + ); + + continue; + } + + return [$asset, $release]; + } + + $message = \vsprintf(self::ERROR_ENVIRONMENT, [ + $this->os->getName(), + $osOption, + $this->arch->getName(), + $archOption, + $this->stability->getName(), + $stabilityOption, + $this->version->choices($releases), + ]); + + throw new \UnexpectedValueException($message); + } + + private function assetToArchive(AssetInterface $asset, OutputInterface $out, string $temp = null): ArchiveInterface + { + $factory = new Factory(); + + $progress = new ProgressBar($out); + $progress->setFormat(' [%bar%] %percent:3s%% (%size%Kb/%total%Kb)'); + $progress->setMessage('0.00', 'size'); + $progress->setMessage('?.??', 'total'); + $progress->display(); + + try { + return $factory->fromAsset($asset, function (int $size, int $total) use ($progress) { + if ($progress->getMaxSteps() !== $total) { + $progress->setMaxSteps($total); + } + + if ($progress->getStartTime() === 0) { + $progress->start(); + } + + $progress->setMessage(\number_format($size / 1000, 2), 'size'); + $progress->setMessage(\number_format($total / 1000, 2), 'total'); + + $progress->setProgress($size); + }, $temp); + } finally { + $progress->clear(); + } + } +} diff --git a/app/src/Job/Ping.php b/app/src/Job/Ping.php index 06fa222..cda2939 100644 --- a/app/src/Job/Ping.php +++ b/app/src/Job/Ping.php @@ -11,18 +11,14 @@ namespace App\Job; -use Spiral\Jobs\JobHandler; +use Spiral\Queue\JobHandler; /** * (QueueInterface)->push(new PingJob(["value"=>"my value"])); */ class Ping extends JobHandler { - /** - * @param string $id - * @param string $value - */ - public function invoke(string $id, string $value) + public function invoke(string $id, string $value): void { // do something error_log("pong by {$id}, value `{$value}`"); diff --git a/app/src/Service/Service.php b/app/src/Service/Service.php index 0cbb5da..f1c2549 100644 --- a/app/src/Service/Service.php +++ b/app/src/Service/Service.php @@ -13,27 +13,16 @@ use App\Job\Ping; use Spiral\Core\Container\SingletonInterface; -use Spiral\GRPC; -use Spiral\Jobs\QueueInterface; +use Spiral\RoadRunner\GRPC; +use Spiral\Queue\QueueInterface; class Service implements ServiceInterface, SingletonInterface { - /** @var QueueInterface */ - private $queue; - - /** - * @param QueueInterface $queue - */ - public function __construct(QueueInterface $queue) - { - $this->queue = $queue; + public function __construct( + private QueueInterface $queue + ) { } - /** - * @param GRPC\ContextInterface $ctx - * @param Message\Message $in - * @return Message\Message - */ public function Welcome(GRPC\ContextInterface $ctx, Message\Message $in): Message\Message { $out = new Message\Message(); @@ -42,11 +31,6 @@ public function Welcome(GRPC\ContextInterface $ctx, Message\Message $in): Messag return $out; } - /** - * @param GRPC\ContextInterface $ctx - * @param Message\Job $in - * @return Message\JobID - */ public function Schedule(GRPC\ContextInterface $ctx, Message\Job $in): Message\JobID { $id = $this->queue->push(Ping::class, ['value' => $in->getValue()]); diff --git a/app/src/Service/ServiceInterface.php b/app/src/Service/ServiceInterface.php index 3ed9e15..1326cdb 100644 --- a/app/src/Service/ServiceInterface.php +++ b/app/src/Service/ServiceInterface.php @@ -5,7 +5,7 @@ namespace App\Service; -use Spiral\GRPC; +use Spiral\RoadRunner\GRPC; use App\Service\Message; interface ServiceInterface extends GRPC\ServiceInterface diff --git a/composer.json b/composer.json index 51bf677..f586ee6 100644 --- a/composer.json +++ b/composer.json @@ -15,24 +15,19 @@ } ], "require": { - "php": ">=7.2", - "spiral/framework": "^2.8", - "spiral/jobs": "^2.0", - "spiral/php-grpc": "^1.0", - "spiral/roadrunner": "^1.4", - "spiral/database": "^2.3", - "spiral/migrations": "^2.0", - "cycle/orm": "^1.0", - "cycle/proxy-factory": "^1.0", - "cycle/annotated": "^2.0", - "cycle/migrations": "^1.0" + "php": ">=8.0", + "spiral/framework": "^2.13", + "spiral/queue": "^2.13", + "spiral/roadrunner-grpc": "^2.0", + "spiral/roadrunner-bridge": "^1.1", + "spiral/cycle-bridge": "^1.1" }, "scripts": { "post-create-project-cmd": [ "php -r \"copy('.env.sample', '.env');\"", "php app.php encrypt:key -m .env", "php app.php configure -vv", - "spiral get-binary" + "rr get-binary" ] }, "autoload": { @@ -46,7 +41,10 @@ } }, "config": { - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "spiral/composer-publish-plugin": true + } }, "minimum-stability": "dev", "prefer-stable": true diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..7e0b377 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,11 @@ +version: '3.5' + +services: + compiler: + image: spiralscout/php81-grpc:1.0.0 + volumes: + - .:/app + command: > + bash -c "cd ./app + && [ -f ./protoc-gen-php-grpc ] || php ./app.php get-protoc-binary + && php ./app.php grpc:generate"