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

撸代码的乡下人
252 声望46 粉丝

我只是一个单纯爱写代码的人,不限于语言,不限于平台,不限于工具