Handle Promise.allSettled() resolved and each rejected Promise
This is a Javascript post! As you probably already knew, that Promise.all() is all or never. How about run all the things even there is/are a rejected promise(s)? There is Promise.allSettled()
for that.
The issue is about catching the error for specific promise(s). You CANNOT do this:
await Promise.allSettled( [ promise2, promise1, promise3 ] ) .then((results) => results.forEach((result) => { // ... })) .catch( // ... ) .finally( // ... );
As above will only works for Promise.all()
, while the resolved promises won’t executed even only there is one promise that rejected.
What you need to do is loop the results, and you will get the result like the following:
On loop, you can check if it has status == ‘rejected’, that a way you know that the promise rejected. The result will always has ‘reason’ key when it rejected, even if the reject value is undefined. So, the code can be:
await Promise.allSettled( [ promise2, promise1, promise3 ] ) .then( (results) => { for (let result of results) { if (result.status == 'rejected') { // handle rejected promise here // eg: make console.log()... console.log('error : ' + result.reason); // continue to next iteration continue; } // handle fulfilled promise here // eg: append in the div document.getElementsByClassName('load')[0].innerHTML += result.value + '<br />'; } } ) .finally( // ... );
That’s it!
References:
Connecting to non-public PostgreSQL schema with CodeIgniter 4
If you are building application with CodeIgniter 4 using PostgreSQL database, you will have to use ‘public’ default schema. How about if you want to use a different schema for specific needs? For example, you have the the product table that placed in “inventory” schema like the following:
To be able to make operation against “inventory” schema, we need to update schema property of Database Connection class. If we use Model class, eg: ProductModel class, we can override __construct()
method and update the schema value, like the following:
<?php namespace App\Models; use CodeIgniter\Database\ConnectionInterface; use CodeIgniter\Model; use CodeIgniter\Validation\ValidationInterface; class ProductModel extends Model { protected $table = 'product'; protected $returnType = 'array'; public function __construct(ConnectionInterface &$db = null, ValidationInterface $validation = null) { parent::__construct($db, $validation); $this->db->schema = 'inventory'; } }
So, whenever we call:
use App\Models\ProductModel; // ... $model = model(ProductModel::class); $model->findAll();
We will find all product table records in ‘inventory’ schema on first priority, if table not found in the ‘inventory’ schema, it will fallback to ‘public’.
That’s it.
Using preInsert event for generating UUID with laminas-db
If you want to do something before insert data into database table, for example: generate id as UUID binary, you can do with preInsert
event. For example, you have the following album
table structure:
DROP TABLE IF EXISTS `album`; CREATE TABLE `album` ( `id` binary(16) NOT NULL COMMENT 'uuid binary', `artist` varchar(255) COLLATE utf8_unicode_ci NOT NULL, `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; ALTER TABLE `album` ADD PRIMARY KEY (`id`);
Now, to generate the UUID data, you can use ramsey/uuid
, you can require:
➜ composer require ramsey/uuid
Now, time for the action!
Note, this is just a quick example to show you how it works. You can borrow your design architecture you love in real implementation.
For example, in laminas-mvc-skeleton application, we inject the IndexController with db adapter via factory:
namespace Application\Controller; use Laminas\Db\Adapter\AdapterInterface; class IndexControllerFactory { public function __invoke($c) { return new IndexController($c->get(AdapterInterface::class)); } }
Now, we can update the module/Application/config/module.config.php
controller factory:
// ... 'controllers' => [ 'factories' => [ Controller\IndexController::class => Controller\IndexControllerFactory::class, ], ], // ...
In our IndexController __construct, we can use the db adapter to create the TableGateway instance featuring EventFeature:
<?php declare(strict_types=1); namespace Application\Controller; use Laminas\Db\Adapter\AdapterInterface; use Laminas\Db\TableGateway\Feature\EventFeature; use Laminas\Db\TableGateway\Feature\EventFeature\TableGatewayEvent; use Laminas\Db\TableGateway\Feature\EventFeatureEventsInterface; use Laminas\Db\TableGateway\TableGateway; use Laminas\Mvc\Controller\AbstractActionController; use Laminas\View\Model\ViewModel; use Ramsey\Uuid\Uuid; class IndexController extends AbstractActionController { private $albumTableGateway; public function __construct(AdapterInterface $adapter) { $events = $this->getEventManager(); $events->attach(EventFeatureEventsInterface::EVENT_PRE_INSERT, function (TableGatewayEvent $event) { $insert = $event->getParam('insert'); $insert->id = Uuid::uuid4()->getBytes(); }); $this->albumTableGateway = new TableGateway('album', $adapter, new EventFeature($events)); } }
Above, with EventFeatureEventsInterface::EVENT_PRE_INSERT
, we update the insert id with the binary value generated.
Let’s check with index action for insert:
// ... public function indexAction() { $this->albumTableGateway->insert([ 'artist' => 'Sheila on 7', 'title' => 'Pejantan Tangguh', ]); return new ViewModel(); } // ..
Ok, when open the index page, we will have the album table inserted with id generated in preInsert event.
mysql> SELECT LOWER( -> CONCAT(SUBSTR(HEX(id), 1, 8) -> , '-' -> , SUBSTR(HEX(id), 9, 4) -> , '-' -> , SUBSTR(HEX(id), 13, 4) -> , '-' -> , SUBSTR(HEX(id), 17, 4) -> , '-' -> , SUBSTR(HEX(id), 21)) -> ) as id, -> artist, -> title -> FROM album; +--------------------------------------+-------------+------------------+ | id | artist | title | +--------------------------------------+-------------+------------------+ | 551a8518-cdd2-4f3a-968d-a45a4b232b5e | Sheila on 7 | Pejantan Tangguh | +--------------------------------------+-------------+------------------+ 1 row in set (0.00 sec)
For complete event list, you can read the documentation https://docs.laminas.dev/laminas-db/table-gateway/#tablegateway-lifecycle-events
Using laminas-cli to Consume Symfony Console Command in Mezzio Application
So, you want to use Symfony Console Command in Mezzio Application? You can! There is laminas-cli
for that. While it still in development, you already can give it a try. First, I assume that you already installed the mezzio application. Next, you can set minimum-stability and prefer-stable config in your composer.json:
➜ composer config minimum-stability dev ➜ composer config prefer-stable true
By above command, you can ensure that you can install the non-stable dependency, while prefer stable version if found. Next, you can require the laminas-cli via command:
➜ composer require laminas/laminas-cli
After installed, let’s create our first command: “HelloWorld command”, like the following:
namespace App\Command; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use function sprintf; final class HelloWorld extends Command { protected function configure() { $this ->addArgument('message', InputArgument::REQUIRED, 'Greeting Message'); } protected function execute(InputInterface $input, OutputInterface $output) { $message = $input->getArgument('message'); $output->writeln(sprintf('<info>Hello to world: %s<info>! ', $message)); return 0; } }
Greet! Now, time to register it to our App\ConfigProvider
class:
<?php declare(strict_types=1); namespace App; class ConfigProvider { public function __invoke(): array { return [ // ... 'laminas-cli' => $this->getCliConfig(), // ... ]; } public function getCliConfig(): array { return [ 'commands' => [ // ... 'app:hello-world' => Command\HelloWorld::class, // ... ], ]; } public function getDependencies(): array { return [ 'invokables' => [ // ... Command\HelloWorld::class => Command\HelloWorld::class, // ... ], ]; } // ... }
First, in getDependencies()
, we register the command, if the command has dependency, you need to provide factory for it. And then, in __invoke()
method, we register the commands, which you can move the commands definition in separate method. That’s it! Now, you can run the command:
➜ vendor/bin/laminas app:hello-world "Good Morning" Hello to world: Good Morning!
Create RESTful API in CodeIgniter 4
In CodeIgniter 4, there is already CodeIgniter\RESTful\ResourceController
that make building RESTful API easier. It can consume the model by provide modelName
property.
Preparation
1. Register Routes
// app/Config/Routes.php $routes->resource('products');
2. Create Model and Entity Classes
For example, we have the ProductModel
class:
<?php namespace App\Models; use App\Entities\Product; use CodeIgniter\Model; class ProductModel extends Model { protected $table = 'product'; /** * $returnType as entity class in RESTful API might not work in CodeIgniter 4.0.2. * You can define as "object" at CodeIgniter 4.0.2 for RESTful API usage. * * protected $returnType = 'object'; * */ protected $returnType = Product::class; protected $allowedFields = [ 'product_code', 'product_name', ]; protected $validationRules = [ 'product_code' => 'required|alpha_numeric|exact_length[5]|is_unique[product.product_code,id,{id}]', 'product_name' => 'required|alpha_numeric_space|min_length[3]|max_length[255]|is_unique[product.product_name,id,{id}]', ]; }
Above model require entity class, so, we can create as follow:
<?php namespace App\Entities; use CodeIgniter\Entity; class Product extends Entity { protected $attributes = [ 'product_code' => null, 'product_name' => null, ]; // filter on create/update data if necessary public function setProductCode(string $productCode): self { $this->attributes['product_code'] = strtoupper($productCode); return $this; } // filter on create/update data if necessary public function setProductName(string $productName): self { $this->attributes['product_name'] = ucwords($productName); return $this; } }
3. Ensure the pages has “csrf” filter DISABLED for the RESTful API pages
CSRF usually uses only for public web interation forms. For API, we can use authorization token (eg: for Oauth usage). We can disable csrf filter in app/Config/Filters.php
like the following:
<?php namespace Config; use App\Filters\PostRequestOnly; use CodeIgniter\Config\BaseConfig; class Filters extends BaseConfig { // ... public $globals = [ 'before' => [ 'csrf' => [ 'except' => [ '/products', '/products/*', ], ], ], ]; // ... }
The API
We can create a controller for it, that extends CodeIgniter\RESTful\ResourceController
:
<?php namespace App\Controllers; use CodeIgniter\RESTful\ResourceController; class Products extends ResourceController { protected $modelName = 'App\Models\ProductModel'; protected $format = 'json'; }
For display all products, we can create method index:
// ... public function index() { return $this->respond($this->model->findAll()); } // ...
This is the output of all products:
For display product by id, we can add method show:
// ... public function show($id = null) { $record = $this->model->find($id); if (! $record) { return $this->failNotFound(sprintf( 'product with id %d not found', $id )); } return $this->respond($record); } // ...
This is the output when product found and not found:
For create new product data, we can add method create:
// ... public function create() { $data = $this->request->getPost(); if (! $this->model->save($data)) { return $this->fail($this->model->errors()); } return $this->respondCreated($data, 'product created'); } // ...
Above, we use getPost()
from request object to get POST data. This is the output when product creation is succeed and failed:
For update product data, we can add method update:
// ... public function update($id = null) { $data = $this->request->getRawInput(); $data['id'] = $id; if (! $this->model->save($data)) { return $this->fail($this->model->errors()); } return $this->respond($data, 200, 'product updated'); } // ...
Above, we use getRawInput()
from request object to get PUT data. Currently, there is no “respondUpdated” method, I created Pull Request for it at https://github.com/codeigniter4/CodeIgniter4/pull/2816 for addition of “respondUpdated” method.
This is the output when product update is succeed and failed:
Now, the last one, the delete, we can add delete method:
// ... public function delete($id = null) { $delete = $this->model->delete($id); if ($this->model->db->affectedRows() === 0) { return $this->failNotFound(sprintf( 'product with id %d not found or already deleted', $id )); } return $this->respondDeleted(['id' => $id], 'product deleted'); } //...
This is the output when product delete is succeed and failed:
That’s it 😉
Using Swoole in Mezzio application with Sdebug
If you didn’t try Swoole
, you need to try it. It is a PECL extension for developing asynchronous applications in PHP. If you build a Mezzio Application, there is already mezzio-swoole
component that ease for its settings and usage.
First, if you didn’t have a mezzio skeleton, you can install the skeleton:
➜ composer create-project mezzio/mezzio-skeleton
Next, install the swoole
extension:
➜ sudo pecl install swoole
After it, you can install the mezzio-swoole
component:
➜ composer require mezzio/mezzio-swoole
That’s it, you can now open mezzio-skeleton
directory and run the mezzio-swoole
command, and we will get the following output:
➜ cd mezzio-skeleton ➜ ./vendor/bin/mezzio-swoole start Swoole is running at 127.0.0.1:8080, in /Users/samsonasik/www/mezzio-skeleton PHP Warning: Swoole\Server::start(): Using Xdebug in coroutines is extremely dangerous, please notice that it may lead to coredump! in /Users/samsonasik/www/mezzio-skeleton/vendor/mezzio/mezzio-swoole/src/SwooleRequestHandlerRunner.php on line 169
If you have Xdebug
installed, you will get above command output “PHP Warning” output. To fix it, we can uninstall the Xdebug
, and install Sdebug
instead. We can do the following command:
➜ sudo pecl uninstall xdebug ➜ git clone https://github.com/swoole/sdebug.git ➜ cd sdebug && sudo ./rebuild.sh
Now, you will get the Sdebug
information if we run php -v
:
➜ ~ php -v PHP 7.4.4 (cli) (built: Mar 24 2020 10:45:52) ( NTS ) Copyright (c) The PHP Group Zend Engine v3.4.0, Copyright (c) Zend Technologies with Sdebug v2.9.3-dev, Copyright (c) 2002-2020, by Derick Rethans with Zend OPcache v7.4.4, Copyright (c), by Zend Technologies
Let’s try run mezzio-swoole
command again:
➜ cd mezzio-skeleton ➜ ./vendor/bin/mezzio-swoole start Swoole is running at 127.0.0.1:8080, in /Users/samsonasik/www/mezzio-skeleton Worker started in /Users/samsonasik/www/mezzio-skeleton with ID 0
If you got Segmentation fault in the future, that may because of the `Sdebug`, if you don’t require the ‘Xdebug’/’Sdebug’ feature. You can just uninstall them all together
Succeed! Now, time to benchmark! I used wrk for it. I tested it in Macbook Pro 2011, core i5, with 16GB RAM. I access the page with HTML+JS+CSS in there.
1. Without Swoole
Let’s CUT the previous mezzio-swoole
( type CTRL + C ) command and use PHP Development server:
➜ cd mezzio-skeleton ➜ composer serve > php -S 0.0.0.0:8080 -t public/ [Sun Apr 5 12:24:15 2020] PHP 7.4.4 Development Server (http://0.0.0.0:8080) started
Now, we can run the benchmark with run wrk
command in separate terminal:
➜ wrk -c 1000 -t 10 http://localhost:8080/ Running 10s test @ http://localhost:8080/ 10 threads and 1000 connections Thread Stats Avg Stdev Max +/- Stdev Latency 1.33s 629.53ms 2.00s 50.33% Req/Sec 16.09 12.96 60.00 69.26% 544 requests in 10.09s, 4.25MB read Socket errors: connect 759, read 580, write 1, timeout 393 Requests/sec: 53.90 Transfer/sec: 430.92KB
2. With Swoole
Let’s CUT the previous PHP Development server command ( type CTRL + C ) command and use mezzio-swoole
command:
➜ cd mezzio-skeleton ➜ ./vendor/bin/mezzio-swoole start Swoole is running at 127.0.0.1:8080, in /Users/samsonasik/www/mezzio-skeleton Worker started in /Users/samsonasik/www/mezzio-skeleton with ID 0
Now, we can run the benchmark with run wrk
command in separate terminal:
➜ wrk -c 1000 -t 10 http://localhost:8080/ Running 10s test @ http://localhost:8080/ 10 threads and 1000 connections Thread Stats Avg Stdev Max +/- Stdev Latency 1.15s 590.35ms 2.00s 54.14% Req/Sec 30.13 35.31 170.00 83.03% 1373 requests in 10.09s, 25.10MB read Socket errors: connect 759, read 80, write 0, timeout 418 Requests/sec: 136.07 Transfer/sec: 2.49MB
Above, we get double total requests with swoole in same time! That’s it!
References:
leave a comment