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:
[](https://documenter.getpostman.com/view/6224358/UV5agGTG)
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"
}
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/*
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;
}
}
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;
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;
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 @@
😊 = substr(UserInfo::info($post['user_id'])['email'], 0, strpos(UserInfo::info($post['user_id'])['email'], '@')); ?>
✍️
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 @@
😊 = substr(UserInfo::info($data['post']['user_id'])['email'], 0, strpos(UserInfo::info($data['post']['user_id'])['email'], '@')); ?>
✍️
Read
😊 = substr(UserInfo::info($post['user_id'])['email'], 0, strpos(UserInfo::info($post['user_id'])['email'], '@')); ?>
✍️
Read
😊 = substr(UserInfo::info($post['user_id'])['email'], 0, strpos(UserInfo::info($post['user_id'])['email'], '@')); ?>
✍️
Read
😊 = substr(UserInfo::info($post['user_id'])['email'], 0, strpos(UserInfo::info($post['user_id'])['email'], '@')); ?>
✍️
Read
😊 = substr(UserInfo::info($post['user_id'])['email'], 0, strpos(UserInfo::info($post['user_id'])['email'], '@')); ?>
✍️
addHttp2Port('localhost:8585');
-//$server->handle(new \Controllers\API\BlogGrpcController());
-//$server->run();