From cdce49b539bdf3a92f03e50364342691a312b271 Mon Sep 17 00:00:00 2001 From: iazaran Date: Mon, 14 Feb 2022 02:08:47 +0400 Subject: [PATCH 1/8] Dockerfile updated because of protobuf --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 652fd62..c8bf38f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,7 +6,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libpng-dev libjpeg62-turbo-dev libfreetype6-dev \ jpegoptim optipng pngquant gifsicle \ cron \ - protobuf-compiler-grpc \ + protobuf-compiler-grpc libprotobuf-dev \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* From 3137db0f3f071142df4f60f3c26131c6d0a6bdb9 Mon Sep 17 00:00:00 2001 From: iazaran Date: Mon, 14 Feb 2022 02:09:09 +0400 Subject: [PATCH 2/8] Nginx updated for gRPC server --- docker/nginx_http2/default.conf | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/docker/nginx_http2/default.conf b/docker/nginx_http2/default.conf index 823e383..e4e964d 100644 --- a/docker/nginx_http2/default.conf +++ b/docker/nginx_http2/default.conf @@ -3,9 +3,21 @@ server { client_max_body_size 108M; access_log /var/log/nginx/grpc.access.log; error_log /var/log/nginx/grpc.error.log; + index index.php; + root /var/www/grpc; - # Listening for unencrypted gRPC traffic on port 80 and forwarding requests to the server on port 50051 - location / { - grpc_pass grpc://localhost:50051; + add_trailer grpc-status $sent_http_grpc_status; + add_trailer grpc-message $sent_http_grpc_message; + + rewrite /blog /index.php; + + location ~ \.php$ { + include fastcgi_params; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass php-mvc-app:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PHP_VALUE "error_log=/var/log/nginx/application_php_errors.log"; + fastcgi_param PATH_INFO $fastcgi_path_info; } } From a450d2b09cd837976fc310c8d637529d83ee697e Mon Sep 17 00:00:00 2001 From: iazaran Date: Mon, 14 Feb 2022 02:09:44 +0400 Subject: [PATCH 3/8] Sample router for gRPC --- grpc/index.php | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 grpc/index.php diff --git a/grpc/index.php b/grpc/index.php new file mode 100644 index 0000000..2ecd4db --- /dev/null +++ b/grpc/index.php @@ -0,0 +1,48 @@ +mergeFromString($message); +} catch (Exception $e) { + echo $e->getMessage(); +} +$response = $blogGrpcController->show($request); + +$out = $response->serializeToString(); + +// Add grpc-status as a trailer in the nginx configuration +echo pack('CN', 0, strlen($out)); +echo $out; From ae4342d3b0570401bfee4d10a279a2ae72e77e5c Mon Sep 17 00:00:00 2001 From: iazaran Date: Mon, 14 Feb 2022 02:10:20 +0400 Subject: [PATCH 4/8] gRPC proto file/controller updated --- src/Controllers/API/BlogGrpcController.php | 137 +++++++----------- src/Controllers/API/blog.proto | 2 + src/Controllers/API/gRPC/GPBMetadata/Blog.php | 2 +- 3 files changed, 59 insertions(+), 82 deletions(-) diff --git a/src/Controllers/API/BlogGrpcController.php b/src/Controllers/API/BlogGrpcController.php index 8f79822..70a2d89 100644 --- a/src/Controllers/API/BlogGrpcController.php +++ b/src/Controllers/API/BlogGrpcController.php @@ -6,7 +6,6 @@ use App\Database; use App\HandleForm; use App\Helper; -use App\Middleware; use App\XmlGenerator; use Blog\BlogClient; use Blog\BlogInterface; @@ -18,7 +17,6 @@ use Blog\SuccessResponse; use Grpc\ChannelCredentials; use Google\Protobuf\GPBEmpty; -use Grpc\MethodDescriptor; use Models\Blog; /** @@ -27,7 +25,7 @@ */ class BlogGrpcController implements BlogInterface { - // TODO: Update authentication + // TODO: This is a temporary solution to get the blog posts. It is not working yet private BlogClient $client; @@ -36,26 +34,30 @@ class BlogGrpcController implements BlogInterface * * @param string $host */ - public function __construct(string $host = 'localhost:50051') + public function __construct(string $host = 'localhost:8585') { - $this->client = new BlogClient($host, ['credentials' => ChannelCredentials::createInsecure()]); + $this->client = new BlogClient($host, [ + 'credentials' => ChannelCredentials::createInsecure(), + 'grpc.max_send_message_length' => 512 * 1024 * 1024, + 'grpc.max_receive_message_length' => 512 * 1024 * 1024, + ]); } /** * READ all * - * @param GPBEmpty $gpbEmpty + * @param GPBEmpty $request * @return ListPostsResponse */ - public function index(GPBEmpty $gpbEmpty): ListPostsResponse + public function index(GPBEmpty $request): ListPostsResponse { // Checking cache if (!list($response, $status) = Cache::checkCache('api.index')) { - $request = $this->client->Index($gpbEmpty); + $client = $this->client->Index($request); list($response, $status) = Cache::cache( 'api.index', - $request->wait() + $client->setPosts(Blog::index()) ); } @@ -66,18 +68,28 @@ public function index(GPBEmpty $gpbEmpty): ListPostsResponse /** * READ one * - * @param PostRequest $postRequest + * @param PostRequest $request * @return PostResponse */ - public function show(PostRequest $postRequest): PostResponse + public function show(PostRequest $request): PostResponse { // Checking cache - if (!list($response, $status) = Cache::checkCache('api.show.' . $postRequest->getSlug())) { - $request = $this->client->Show($postRequest); + if (!list($response, $status) = Cache::checkCache('api.show.' . $request->getSlug())) { + $client = $this->client->Show($request); + $post = Blog::show($request->getSlug()); + $client->setId($post['id']); + $client->setCategory($post['category']); + $client->setTitle($post['title']); + $client->setSlug($post['slug']); + $client->setSubtitle($post['subtitle']); + $client->setBody($post['body']); + $client->setPosition($post['position']); + $client->setCreatedAt($post['created_at']); + $client->setUpdatedAt($post['updated_at']); list($response, $status) = Cache::cache( - 'api.show.' . $postRequest->getSlug(), - $request->wait() + 'api.show.' . $request->getSlug(), + $client ); } @@ -88,38 +100,32 @@ public function show(PostRequest $postRequest): PostResponse /** * STORE * - * @param PostStoreRequest $postStoreRequest + * @param PostStoreRequest $request * @return SuccessResponse */ - public function store(PostStoreRequest $postStoreRequest): SuccessResponse + public function store(PostStoreRequest $request): SuccessResponse { - $request = $this->client->Store($postStoreRequest); - - if (is_null(Middleware::init(__METHOD__))) { - list($response, $status) = $request->wait()->setMessage('Authorization failed!'); - $this->checkStatus($status); - return $response; - } + $client = $this->client->Store($request); $output = HandleForm::validations([ - [$postStoreRequest->getTitle(), 'required', 'Please enter a title for the post!'], - [$postStoreRequest->getSubtitle(), 'required', 'Please enter a subtitle for the post!'], - [$postStoreRequest->getBody(), 'required', 'Please enter a body for the post!'], + [$request->getTitle(), 'required', 'Please enter a title for the post!'], + [$request->getSubtitle(), 'required', 'Please enter a subtitle for the post!'], + [$request->getBody(), 'required', 'Please enter a body for the post!'], ]); if ($output['status'] != 'OK') { - list($response, $status) = $request->setMessage($output['status']); - } elseif (Blog::store($postStoreRequest)) { + list($response, $status) = $client->setMessage($output['status']); + } elseif (Blog::store($request)) { if (isset($_FILES['image']['type'])) { - HandleForm::upload($_FILES['image'], ['jpeg', 'jpg','png'], 5000000, '../public/assets/images/', 85, Helper::slug($postStoreRequest->getTitle(), '-', false)); + HandleForm::upload($_FILES['image'], ['jpeg', 'jpg','png'], 5000000, '../public/assets/images/', 85, Helper::slug($request->getTitle(), '-', false)); } XmlGenerator::feed(); Cache::clearCache(['index', 'blog.index', 'api.index']); - list($response, $status) = $request->wait()->setMessage('Data saved successfully!'); + list($response, $status) = $client->setMessage('Data saved successfully!'); } else { - list($response, $status) = $request->wait()->setMessage('Failed during saving data!'); + list($response, $status) = $client->setMessage('Failed during saving data!'); } $this->checkStatus($status); @@ -129,30 +135,24 @@ public function store(PostStoreRequest $postStoreRequest): SuccessResponse /** * UPDATE * - * @param PostUpdateRequest $postUpdateRequest + * @param PostUpdateRequest $request * @return SuccessResponse */ - public function update(PostUpdateRequest $postUpdateRequest): SuccessResponse + public function update(PostUpdateRequest $request): SuccessResponse { - $request = $this->client->Update($postUpdateRequest); - - if (is_null(Middleware::init(__METHOD__))) { - list($response, $status) = $request->wait()->setMessage('Authorization failed!'); - $this->checkStatus($status); - return $response; - } + $client = $this->client->Update($request); $output = HandleForm::validations([ - [$postUpdateRequest->getTitle(), 'required', 'Please enter a title for the post!'], - [$postUpdateRequest->getSubtitle(), 'required', 'Please enter a subtitle for the post!'], - [$postUpdateRequest->getBody(), 'required', 'Please enter a body for the post!'], + [$request->getTitle(), 'required', 'Please enter a title for the post!'], + [$request->getSubtitle(), 'required', 'Please enter a subtitle for the post!'], + [$request->getBody(), 'required', 'Please enter a body for the post!'], ]); if ($output['status'] != 'OK') { - list($response, $status) = $request->setMessage($output['status']); - } elseif (Blog::update($postUpdateRequest)) { + list($response, $status) = $client->setMessage($output['status']); + } elseif (Blog::update($request)) { Database::query("SELECT * FROM posts WHERE id = :id"); - Database::bind(':id', $postUpdateRequest->getId()); + Database::bind(':id', $request->getId()); $currentPost = Database::fetch(); @@ -164,9 +164,9 @@ public function update(PostUpdateRequest $postUpdateRequest): SuccessResponse Cache::clearCache('blog.show.' . $currentPost['slug']); Cache::clearCache(['index', 'blog.index', 'api.index']); - list($response, $status) = $request->wait()->setMessage('Data updated successfully!'); + list($response, $status) = $client->setMessage('Data updated successfully!'); } else { - list($response, $status) = $request->wait()->setMessage('Failed during updating data!'); + list($response, $status) = $client->setMessage('Failed during updating data!'); } $this->checkStatus($status); @@ -176,50 +176,25 @@ public function update(PostUpdateRequest $postUpdateRequest): SuccessResponse /** * DELETE * - * @param PostRequest $postRequest + * @param PostRequest $request * @return SuccessResponse */ - public function delete(PostRequest $postRequest): SuccessResponse + public function delete(PostRequest $request): SuccessResponse { - $request = $this->client->Delete($postRequest); - - if (is_null(Middleware::init(__METHOD__))) { - list($response, $status) = $request->wait()->setMessage('Authorization failed!'); - $this->checkStatus($status); - return $response; - } + $client = $this->client->Delete($request); - if (Blog::delete($postRequest->getSlug())) { - Cache::clearCache('blog.show.' . $postRequest->getSlug()); - Cache::clearCache(['index', 'blog.index', 'api.index']); + if (Blog::delete($request->getSlug())) { + Cache::clearCache(['index', 'blog.index', 'api.index', 'blog.show.' . $request->getSlug()]); - list($response, $status) = $request->wait()->setMessage('Data deleted successfully!'); + list($response, $status) = $client->setMessage('Data deleted successfully!'); } else { - list($response, $status) = $request->wait()->setMessage('Failed during deleting data!'); + list($response, $status) = $client->setMessage('Failed during deleting data!'); } $this->checkStatus($status); return $response; } - // TODO: Complete this for others - /** - * Get the method descriptors of the service for server registration - * - * @return MethodDescriptor[] - */ - public final function getMethodDescriptors(): array - { - return [ - '/blog.Blog/Index' => new MethodDescriptor( - $this, - 'Index', - \Google\Protobuf\GPBEmpty::class, - MethodDescriptor::UNARY_CALL - ), - ]; - } - /** * Check if status is OK * diff --git a/src/Controllers/API/blog.proto b/src/Controllers/API/blog.proto index f847e68..054b9aa 100644 --- a/src/Controllers/API/blog.proto +++ b/src/Controllers/API/blog.proto @@ -19,6 +19,8 @@ syntax = "proto3"; package blog; option php_generic_services = true; +option php_namespace="Blog"; +option php_metadata_namespace="GPBMetadata"; import "google/protobuf/empty.proto"; diff --git a/src/Controllers/API/gRPC/GPBMetadata/Blog.php b/src/Controllers/API/gRPC/GPBMetadata/Blog.php index d76f86b..8ce8d0b 100644 --- a/src/Controllers/API/gRPC/GPBMetadata/Blog.php +++ b/src/Controllers/API/gRPC/GPBMetadata/Blog.php @@ -16,7 +16,7 @@ public static function initOnce() { } \GPBMetadata\Google\Protobuf\GPBEmpty::initOnce(); $pool->internalAddGeneratedFile(hex2bin( - "0abb060a0a626c6f672e70726f746f1204626c6f6722a3010a0c506f7374526573706f6e7365120a0a02696418012001280312100a0863617465676f7279180220012809120d0a057469746c65180320012809120c0a04736c756718042001280912100a087375627469746c65180520012809120c0a04626f647918062001280912100a08706f736974696f6e18072001280512120a0a637265617465645f617418082001280912120a0a757064617465645f617418092001280922360a114c697374506f737473526573706f6e736512210a05706f73747318012003280b32122e626c6f672e506f7374526573706f6e736522220a0f53756363657373526573706f6e7365120f0a076d657373616765180120012809221b0a0b506f737452657175657374120c0a04736c756718012001280922650a10506f737453746f72655265717565737412100a0863617465676f7279180120012809120d0a057469746c6518022001280912100a087375627469746c65180320012809120c0a04626f647918042001280912100a08706f736974696f6e18052001280522720a11506f737455706461746552657175657374120a0a02696418012001280312100a0863617465676f7279180220012809120d0a057469746c6518032001280912100a087375627469746c65180420012809120c0a04626f647918052001280912100a08706f736974696f6e180620012805329f020a04426c6f67123a0a05496e64657812162e676f6f676c652e70726f746f6275662e456d7074791a172e626c6f672e4c697374506f737473526573706f6e73652200122f0a0453686f7712112e626c6f672e506f7374526571756573741a122e626c6f672e506f7374526573706f6e7365220012380a0553746f726512162e626c6f672e506f737453746f7265526571756573741a152e626c6f672e53756363657373526573706f6e73652200123a0a0655706461746512172e626c6f672e506f7374557064617465526571756573741a152e626c6f672e53756363657373526573706f6e7365220012340a0644656c65746512112e626c6f672e506f7374526571756573741a152e626c6f672e53756363657373526573706f6e736522004203d00201620670726f746f33" + "0ad0060a0a626c6f672e70726f746f1204626c6f6722a3010a0c506f7374526573706f6e7365120a0a02696418012001280312100a0863617465676f7279180220012809120d0a057469746c65180320012809120c0a04736c756718042001280912100a087375627469746c65180520012809120c0a04626f647918062001280912100a08706f736974696f6e18072001280512120a0a637265617465645f617418082001280912120a0a757064617465645f617418092001280922360a114c697374506f737473526573706f6e736512210a05706f73747318012003280b32122e626c6f672e506f7374526573706f6e736522220a0f53756363657373526573706f6e7365120f0a076d657373616765180120012809221b0a0b506f737452657175657374120c0a04736c756718012001280922650a10506f737453746f72655265717565737412100a0863617465676f7279180120012809120d0a057469746c6518022001280912100a087375627469746c65180320012809120c0a04626f647918042001280912100a08706f736974696f6e18052001280522720a11506f737455706461746552657175657374120a0a02696418012001280312100a0863617465676f7279180220012809120d0a057469746c6518032001280912100a087375627469746c65180420012809120c0a04626f647918052001280912100a08706f736974696f6e180620012805329f020a04426c6f67123a0a05496e64657812162e676f6f676c652e70726f746f6275662e456d7074791a172e626c6f672e4c697374506f737473526573706f6e73652200122f0a0453686f7712112e626c6f672e506f7374526571756573741a122e626c6f672e506f7374526573706f6e7365220012380a0553746f726512162e626c6f672e506f737453746f7265526571756573741a152e626c6f672e53756363657373526573706f6e73652200123a0a0655706461746512172e626c6f672e506f7374557064617465526571756573741a152e626c6f672e53756363657373526573706f6e7365220012340a0644656c65746512112e626c6f672e506f7374526571756573741a152e626c6f672e53756363657373526573706f6e736522004218ca0204426c6f67d00201e2020b4750424d65746164617461620670726f746f33" ), true); static::$is_initialized = true; From ab3c9d0e3144bc743550f191d2aced85c0a4968a Mon Sep 17 00:00:00 2001 From: iazaran Date: Mon, 14 Feb 2022 02:11:38 +0400 Subject: [PATCH 5/8] Views updated to check UserInfo::current() before getting ID --- src/Views/Blog/index.php | 2 +- src/Views/Blog/show.php | 2 +- src/Views/Home/home.php | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Views/Blog/index.php b/src/Views/Blog/index.php index 537f184..3b2738c 100644 --- a/src/Views/Blog/index.php +++ b/src/Views/Blog/index.php @@ -39,7 +39,7 @@ 😊 ✍️ diff --git a/src/Views/Blog/show.php b/src/Views/Blog/show.php index fcebb79..81552d6 100644 --- a/src/Views/Blog/show.php +++ b/src/Views/Blog/show.php @@ -24,7 +24,7 @@ 😊 ✍️ Read 😊 ✍️ Read 😊 ✍️ Read 😊 ✍️ Read 😊 ✍️ Date: Mon, 14 Feb 2022 02:12:03 +0400 Subject: [PATCH 6/8] gRPC old route removed --- src/grpc_route.php | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 src/grpc_route.php diff --git a/src/grpc_route.php b/src/grpc_route.php deleted file mode 100644 index 281567f..0000000 --- a/src/grpc_route.php +++ /dev/null @@ -1,6 +0,0 @@ -addHttp2Port('localhost:8585'); -//$server->handle(new \Controllers\API\BlogGrpcController()); -//$server->run(); From 8960ac7122b4077f8dcec5f6f6b964c60ed6c858 Mon Sep 17 00:00:00 2001 From: iazaran Date: Mon, 14 Feb 2022 02:12:30 +0400 Subject: [PATCH 7/8] Composer updated for PHP 8.1 --- composer.json | 4 ++-- composer.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 7ba9c83..3770cb3 100644 --- a/composer.json +++ b/composer.json @@ -21,11 +21,11 @@ }, "config": { "platform": { - "php": "8.0" + "php": "8.1" } }, "require": { - "php": ">=8.0", + "php": ">=8.1", "ext-pdo": "*", "ext-json": "*", "ext-gd": "*", diff --git a/composer.lock b/composer.lock index 1727d75..d2e3929 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7a20cfa79672c3491e51eae2690ab1b2", + "content-hash": "cb0b4d56eddd3b7d6a3412a0e96a4a0d", "packages": [ { "name": "google/protobuf", @@ -181,7 +181,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=8.0", + "php": ">=8.1", "ext-pdo": "*", "ext-json": "*", "ext-gd": "*", @@ -191,7 +191,7 @@ }, "platform-dev": [], "platform-overrides": { - "php": "8.0" + "php": "8.1" }, - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.0.0" } From e31977547e7552248b1caca1988c33bff1bca9d3 Mon Sep 17 00:00:00 2001 From: iazaran Date: Mon, 14 Feb 2022 02:12:45 +0400 Subject: [PATCH 8/8] README.md updated --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 925d9e8..d90d5ca 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,7 @@ > This project tries to cover some PHP features in a simple MVC structure with minimum installed composer packages. Then developers can use packages for specific requirements. Please add your ideas in Discussions, ask features or report bugs in issues. -🚧 WIP: gRPC server _(gRPC client not completed yet, and gRPC server is under working, so be careful about the bugs in gRPC ⚠)_ - -💡 TODO: WebSocket +💡 TODO: SEO & WebSocket #### Features: **List of features related with structure** @@ -25,7 +23,7 @@ Contains all classes that used in codes like PDO, Middleware, Router & ... - **src/Console** Contains all scripts to run multiple times via Cron Jobs _(Scripts should be registered in /commands.php with custom timing, they will run by independent service in docker-compose)_ - **src/Controllers** -Controllers related with your routes separated for web and API. API folder includes both RESTful API and gRPC API. If you want use gRPC _(Under working now and server isn't ready to handle gRPC requests)_, you can find .proto file in API folder. Updating it will need to generate PHP codes again by +Controllers related with your routes separated for web and API. API folder includes both RESTful API and gRPC API. If you want use gRPC _(gRPC client & server are not completed, and I ignored them for now. So be careful about the bugs in gRPC ⚠ and if you have an idea or a solution, only by PHP, please make a new discussion/issue/PR)_, you can find .proto file in API folder. Updating it will need to generate PHP codes again by ``` docker-compose exec php-mvc-app protoc -I=src/Controllers/API \ src/Controllers/API/blog.proto \ @@ -71,7 +69,7 @@ Register an event listener and trigger it when needed #### Run Web App: - Install docker and docker-compose if needed - Uncomment `// createTables();` in `src/routes` -- Run `docker-compose up --build -d` _(A compatibility issue for PHP8.1 and protobuf will fix soon in here: [#9359](https://github.com/protocolbuffers/protobuf/pull/9359))_ +- Run `docker-compose up --build -d` - Open your browser and open web app in `localhost:8080` _(It will create tables related with migrations.php and then will comment `createTables();` automatically.)_ - You can run `docker-compose down` to stop and remove containers - Next time you can use `docker-compose up -d` @@ -80,7 +78,7 @@ Register an event listener and trigger it when needed Consider a route for your form like `/blog/create`; now use `blog-create` as an ID for form, and `blog-create-submit` for submit button ID. All form's buttons need to have constant `form-button` class. #### RESTful API samples -Ready to use PostMan collection for RESTful API side: _(gRPC API side will be added later)_ +Ready to use PostMan collection for RESTful API side: [![Run in Postman](https://run.pstmn.io/button.svg)](https://documenter.getpostman.com/view/6224358/UV5agGTG)