In recent years, I wandered in the javascript and golang ecology and found many advantages of npm and go mod. went back to develop V3, and found that composer has always been a very good tool, but many phpers have not used composer very deeply. Today I will use composer to create a native project to help everyone understand the modernization. Native PHP development process.
PHP developers may be the most spoiled of all languages, because almost every framework provides scaffolding, like this:
composer create-project
This function does not have this function in npm and go mod. You need to create your own program skeleton. Of course, npm and go ecology have produced their own solutions, which are scaffolding tools such as mixcli
Create a project
Like npm init and go mod init, we use composer init to create a project
mkdir hello
cd hello
composer init
After filling in some content interactively, the composer.json file is generated
{
"name": "liujian/hello",
"type": "project",
"autoload": {
"psr-4": {
"Liujian\\Hello\\": "src/"
}
},
"require": {}
}
This file is created according to the standards of the composer library, and it must have a two-level name, which makes me very painful, so I modify it
{
"name": "project/app",
"type": "project",
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"require": {}
}
Choose the library I need to use
Like node.js and go ecology, the second step is to find the libraries we need. Usually our requirement is to write an API service, we need an http server library, and a db library can start working.
Because it is a modern PHP development, I chose the resident high-performance library of PHP CLI mode. Here I choose:
- mix/vega Vega is a CLI mode HTTP network framework written in PHP, supporting Swoole, WorkerMan
- mix/database A lightweight database that can be used in various environments, supports FPM, Swoole, WorkerMan, and optional connection pool (coroutine)
Both of these are the core components of MixPHP
Mix Vega & Mix Database installation
Vega supports Swoole and WorkerMan at the same time, and will also support Swow in the future. The simplest principle is that WorkerMan can be executed without installing extensions. The development first uses WorkerMan to drive Vega, and it can be switched online according to your needs.
Install Workerman
composer require workerman/workerman
Install Mix Vega
composer require mix/vega
Install Mix Database
composer require mix/database
Create an entry file
The entrance of vue is usually src/main.js
because js is usually a single entry project, we still follow the binary convention to create a bin/start.php
entry file
<?php
require __DIR__ . '/../vendor/autoload.php';
$vega = new Mix\Vega\Engine();
$vega->handleF('/hello', function (Mix\Vega\Context $ctx) {
$ctx->string(200, 'hello, world!');
})->methods('GET');
$http_worker = new Workerman\Worker("http://0.0.0.0:2345");
$http_worker->onMessage = $vega->handler();
$http_worker->count = 4;
Workerman\Worker::runAll();
Then we imitate npm's approach and add in composer.json:
"scripts": {
"server": "php bin/start.php start"
},
Here I am very confused about the way of composer. Npm entry file does not need require __DIR__ . '/../vendor/autoload.php';
. The script executed directly by npm run server can find the corresponding dependencies. However, even if composer uses composer run server to execute the corresponding script, it still needs to be processed in the code. autoload, give a bad review.
Now we composer run server
start the service at 060e7211753053:
% composer run server
> php8 bin/start.php start
Workerman[bin/start.php] start in DEBUG mode
----------------------------------------- WORKERMAN -----------------------------------------
Workerman version:4.0.19 PHP version:8.0.7
------------------------------------------ WORKERS ------------------------------------------
proto user worker listen processes status
tcp liujian none http://0.0.0.0:2345 4 [OK]
---------------------------------------------------------------------------------------------
Press Ctrl+C to stop. Start success.
Write an API interface
Let's modify the above entry file and write a user query interface. The use of Vega is very simple.
<?php
require __DIR__ . '/../vendor/autoload.php';
const DSN = 'mysql:host=127.0.0.1;port=3306;charset=utf8;dbname=test';
const USERNAME = 'root';
const PASSWORD = '123456';
$db = new \Mix\Database\Database(DSN, USERNAME, PASSWORD);
$vega = new Mix\Vega\Engine();
$vega->handleF('/users/{id}', function (Mix\Vega\Context $ctx) use ($db) {
$row = $db->table('users')->where('id = ?', $ctx->param('id'))->first();
if (!$row) {
throw new \Exception('User not found');
}
$ctx->JSON(200, [
'code' => 0,
'message' => 'ok',
'data' => $row
]);
})->methods('GET');
$http_worker = new Workerman\Worker("http://0.0.0.0:2345");
$http_worker->onMessage = $vega->handler();
$http_worker->count = 4;
Workerman\Worker::runAll();
Test it with curl:
% curl http://127.0.0.1:2345/users/1
{"code":0,"message":"ok","data":{"id":"1","name":"foo2","balance":"102","add_time":"2021-07-06 08:40:20"}}
Use PSR to adjust the directory structure
Earlier we defined PSR
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
Next, we use automatic loading to reasonably split the code of the above entry file. The directory structure after splitting is as follows:
├── bin
│ └── start.php
├── composer.json
├── composer.lock
├── src
│ ├── Controller
│ │ └── Users.php
│ ├── Database
│ │ └── DB.php
│ └── Router
│ └── Vega.php
└── vendor
bin/start.php
<?php
require __DIR__ . '/../vendor/autoload.php';
$vega = \App\Router\Vega::new();
$http_worker = new Workerman\Worker("http://0.0.0.0:2345");
$http_worker->onMessage = $vega->handler();
$http_worker->count = 8;
Workerman\Worker::runAll();
src/Router/Vega.php
<?php
namespace App\Router;
use App\Controller\Users;
use Mix\Vega\Engine;
class Vega
{
/**
* @return Engine
*/
public static function new()
{
$vega = new Engine();
$vega->handleC('/users/{id}', [new Users(), 'index'])->methods('GET');
return $vega;
}
}
src/Controller/Users.php
<?php
namespace App\Controller;
use App\Database\DB;
use Mix\Vega\Context;
class Users
{
public function index(Context $ctx)
{
$row = DB::instance()->table('users')->where('id = ?', $ctx->param('id'))->first();
if (!$row) {
throw new \Exception('User not found');
}
$ctx->JSON(200, [
'code' => 0,
'message' => 'ok',
'data' => $row
]);
}
}
src/Database/DB.php
<?php
namespace App\Database;
use Mix\Database\Database;
const DSN = 'mysql:host=127.0.0.1;port=3306;charset=utf8;dbname=test';
const USERNAME = 'root';
const PASSWORD = '123456';
class DB extends Database
{
static private $instance;
public static function instance()
{
if (!isset(self::$instance)) {
self::$instance = new self(DSN, USERNAME, PASSWORD);
}
return self::$instance;
}
}
After the adjustment, the prototype of a formal project is basically completed, and then everyone can play freely.
Stress test
mysql: docker mysql8 native
cpu: macOS M1 8-core
mem: 16G
wokerman (libevent is not installed): 8 processes, equivalent to 8 mysql connections
% wrk -c 1000 -d 1m http://127.0.0.1:2345/users/1
Running 1m test @ http://127.0.0.1:2345/users/1
2 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 36.08ms 8.11ms 428.09ms 95.38%
Req/Sec 3.49k 211.80 4.00k 71.00%
416817 requests in 1.00m, 109.31MB read
Socket errors: connect 749, read 295, write 1, timeout 0
Requests/sec: 6943.38
Transfer/sec: 1.82MB
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。