current position:Home>Swoft 2. X Foundation (HTTP, database, redis)

Swoft 2. X Foundation (HTTP, database, redis)

2022-04-29 08:15:59hualaoshuan

0. Frame installation ;

0.1 official Docker The image starts Swoft;

#  Pull the mirror image 
docker pull swoft/swoft:latest

#  see  Dockerfile  Understand mirror construction 
# https://hub.docker.com/r/swoft/swoft/dockerfile

#  start-up 
docker run -p 18306:18306 --name swoft --rm -d swoft/swoft:latest
#  Verify startup 
curl localhost:18306

#  Enter the local working directory 
cd /data/php
#  Copy the working directory code in the container to the host 
#  The working directory in the container is  /var/www/swoft
#  final “.” Copy to the current directory 
docker cp swoft:/var/www/swoft .

#  After copying successfully , The current machine will show a  swoft  Catalog 
cd swoft && ls

# Swoft  Built in a reload mechanism , After modifying a file, it will  md5  comparison 
#  Stop container , Do folder mapping after restart 
docker stop swoft
docker run -p 18306:18306 --name swoft --rm -d \
-v /data/php/swoft/:/var/www/swoft \
swoft/swoft:latest

1. Http Server;

1.1 controller ;

  • contain :RequestMapping notes 、 Request and response 、 return JSON Format 、 route path Parameters 、GET \ POST Parameters to obtain
  • PhpStorm Installing a plug-in
     Insert picture description here
  • newly build App/Http/Controller/ProductController.php
<?php

namespace App\Http\Controller;

use App\lib\MyRequest;
use function foo\func;
use Swoft\Context\Context;
use Swoft\Http\Message\ContentType;
use Swoft\Http\Message\Response;
use Swoft\Http\Server\Annotation\Mapping\Controller;
use Swoft\Http\Server\Annotation\Mapping\RequestMapping;
use Swoft\Http\Server\Annotation\Mapping\RequestMethod;
use Swoft\Http\Server\Annotation\Mapping\Middleware;
use App\Http\Middleware\ControllerMiddleware;

/** *  Commodity module  * // @Controller() * @Controller(prefix="/product") *  Use a middleware  * @Middleware(ControllerMiddleware::class) */
class ProductController{
    

    /** * 1.1.1 RequestMapping  notes  * * @RequestMapping(route="/product", method={RequestMethod::GET}) */
    public function prod_list(){
    
        // 1.1.2  Request and response 
        //  Reference resources :https://www.swoft.org/documents/v2/core-components/http-server/#http-1
        //  Context object :Contexts
        //  Get request object 
        $req = Context::get()->getRequest();
        //  Get the response object 
        $res = Context::get()->getResponse();
        //  Return content 
        // return $res->withContent("abc");
        // 1.1.3  return  JSON  Format 
        // return $res->withContentType("application/json")->withContent("abc");

        // 1.2  Call global functions 
        // return $res->withContentType("application/json")
        // ->withContent(json_encode([NewProduct(101, " Test product "), NewProduct(102, " Test product 2")]));
        return $res->withContentType("application/json")
            ->withData([NewProduct(101, " Test product "), NewProduct(102, " Test product 2")]);
    }


    /** * //@RequestMapping(route="/product/{pid}", method={RequestMethod::GET}) *  route  path  Parameter prefixes , Regular control routing parameters  *  Injection parameters  Response, May or cause too many parameters  * 1.1.4  route  path  Parameters  * @RequestMapping(route="{pid}", params={"pid"="\d+"}, method={RequestMethod::GET, RequestMethod::POST}) */
    public function prod_detail(int $pid, Response $response){
    
        $p = NewProduct($pid, " Test product ");
        //return \response()->withContentType("application/json")->withData([$p]);
        // return $response->withContentType(ContentType::JSON)->withData([$p]);
        // 1.3  The returned value here will call the middleware 
        // return [$p];

        // 1.1.5 GET \ POST  Parameters to obtain 
        // echo request()->get("type", "default type");
        // if(request()->getMethod() == RequestMethod::GET){
    

        // *1.5 JSON  Parameter to entity object 
        /** @var $product ProductEntity */
        // $product = jsonForObject(ProductEntity::class);
        // var_dump($product);

        if(isGet()){
    
            return $p;
        }else if(isPost()) {
    
            $p->pname = " Product modification " . request()->post('title', 'none');
            return $p;

        }

        // *1.4  call chaining 
// $my = new MyRequest();
// $my->if(isGet())
// ->then(function() use ($p){
    
// return $p;
// })
// ->if(isPost())
// ->then(function() use ($p){
    
// $p->pname = " Product modification " . request()->post('title', 'none');
// return $p;
// })
// ->getResult();
    }

}
  • Example operation
#  The terminal enters the project directory 
cd /data/test/php/swoft/
#  start-up  Swoft
php ./bin/swoft http:start
#  return 
 SERVER INFORMATION(v2.0.8)
  ********************************************************************************
  * HTTP     | Listen: 0.0.0.0:18306, Mode: Process, Worker: 6, Task worker: 12
  ********************************************************************************

HTTP Server Start Success!

#  Browser access :http://localhost:18306/product

1.2 Global function ;

  • modify App/Helper/Functions.php
<?php

use Swoft\Http\Server\Annotation\Mapping\RequestMethod;

require_once (__DIR__ . "/WebFunctions.php");
require_once (__DIR__ . "/OrderFunctions.php");

/** * This file is part of Swoft. * * @link https://swoft.org * @document https://swoft.org/docs * @contact [email protected] * @license https://github.com/swoft-cloud/swoft/blob/master/LICENSE */

function user_func(): string
{
    
    return 'hello';
}


function NewProduct(int $pid, string $pname){
    
    $p = new stdClass();
    $p->pid = $pid;
    $p->pname = $pname . $p->pid;

    return $p;
}


function response($contentType=false){
    
    if($contentType){
    
        return Swoft\Context\Context::get()->getResponse()->withContentType($contentType);
    }
    return Swoft\Context\Context::get()->getResponse();
}


function request(){
    
    return Swoft\Context\Context::get()->getRequest();
}


function isGet(){
    
    return request()->getMethod() == RequestMethod::GET;
}


function isPost(){
    
    return request()->getMethod() == RequestMethod::POST;
}


function ip(){
    
    $req = request();
    if($req->server('http_x_forwarded_for')){
    
        return $req->server('http_x_forwarded_for');
    }else if($req->server('http_client_ip')){
    
        return $req->server('http_client_ip');
    }else{
    
        return $req->server('remote_addr');
    }
}


//  Transaction encapsulation ( It can also be used.  AOP)
function tx(callable $func, &$result=null){
    
    \Swoft\Db\DB::beginTransaction();
    try{
    
        $result = $func();
        \Swoft\Db\DB::commit();
    }catch(Exception $exception){
    
        $result = OrderCreateFail($exception->getMessage());
        \Swoft\Db\DB::rollBack();
    }
}

1.3 middleware ;

<?php

namespace App\Http\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Swoft\Bean\Annotation\Mapping\Bean;
use Swoft\Http\Message\Request;
use Swoft\Http\Message\Response;
use Swoft\Http\Server\Contract\MiddlewareInterface;
use Swoft\Http\Message\ContentType;

/** * @Bean() */
class ControllerMiddleware implements MiddlewareInterface
{
    

    /** * Process an incoming server request. * * @param ServerRequestInterface|Request $request * @param RequestHandlerInterface|Response $handler *  Put it into the controller for corresponding , You can also set global middleware  * * @return ResponseInterface */
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
    
        /** @var $ret \Swoft\Http\Message\Response */
        // response  object 
        $ret = $handler->handle($request);
        $data = $ret->getData();
        //$p2 = NewProduct(10000, " Middleware test products ");
        //$data[] = $p2;

        if(is_object($data)){
    
            return \response(ContentType::JSON)->withContent(json_encode($data));
        }

        return \response(ContentType::JSON)->withData($data);
    }
}

*1.4 call chaining ;

  • newly build App\lib\MyRequest.php
<?php

namespace App\lib;

class MyRequest{
    

    private $result;

    private $do = false;

    function if($bool){
    
        $this->do = $bool;
        return clone $this;
    }

    function then(callable $func){
    
        if($this->do){
    
            $this->result = $func();
            $this->do = !$this->do;
        }
        return clone $this;
    }

    function getResult(){
    
        return $this->result;
    }

}

*1.5 JSON Parameter to entity object ;

  • newly build App\lib\ProductEntity.php
<?php

namespace App\lib;

class ProductEntity{
    

    private $prod_id;

    private $prod_name;

    private $prod_price;

    /** * @return mixed */
    public function getProdId(){
    
        return $this->prod_id;
    }

    /** * @param mixed $prod_id */
    public function setProdId($prod_id): void{
    
        $this->prod_id = $prod_id;
    }

    /** * @return mixed */
    public function getProdName(){
    
        return $this->prod_name;
    }

    /** * @param mixed $prod_name */
    public function setProdName($prod_name): void{
    
        $this->prod_name = $prod_name;
    }

    /** * @return mixed */
    public function getProdPrice(){
    
        return $this->prod_price;
    }

    /** * @param mixed $prod_price */
    public function setProdPrice($prod_price): void{
    
        $this->prod_price = $prod_price;
    }
}
  • newly build App/Helper/WebFunction.php
<?php

/** * @param $class * $class  Not instantiated objects , It is  function  name  *  Reflection is required  */
function jsonForObject($class=""){
    
    $req = request();
    try {
    
        $contentType = $req->getHeader('content-type');
        if (!$contentType || false === stripos($contentType[0], \Swoft\Http\Message\ContentType::JSON)) {
    
            return false;
        }

        //  Get the original  body  Content 
        $raw = $req->getBody()->getContents();
        //  Compare the obtained object with  $class  Make a comparison 
        $map = json_decode($raw, true); // kv  Array 
        if($class == "") return $map;   //  Realization  2.0.2  Before version  request()->json()  Method 

        $class_obj = new ReflectionClass($class);   //  Reflection object 
        $class_instance = $class_obj->newInstance(); //  Create instances from reflective objects 
        //  Get all methods ( Public methods )
        $methods = $class_obj->getMethods(ReflectionMethod::IS_PUBLIC);
        foreach ($methods as $method){
    
            //  Regular get  set  Method 
            if(preg_match("/set(\w+)/", $method->getName(), $matches)){
    
                // echo $matches[1] . PHP_EOL; //  Get the method name  ProdId,ProdName,ProdPrice
                invokeSetterMethod($matches[1], $class_obj, $map,$class_instance);
            }
        }

        return $class_instance;
    }catch (Exception $exception){
    
        return false;

    }
}

//  Map an array to an entity ( One dimensional array )
function mapToModel(array $map, $class){
    
    try {
    
        $class_obj = new ReflectionClass($class);
        $class_instance = $class_obj->newInstance(); //  Create instances from reflective objects 
        //  Get all methods ( Public methods )
        $methods = $class_obj->getMethods(ReflectionMethod::IS_PUBLIC);
        foreach ($methods as $method){
    
            //  Regular get  set  Method 
            if(preg_match("/set(\w+)/", $method->getName(), $matches)){
    
                // echo $matches[1] . PHP_EOL; //  Get the method name  ProdId,ProdName,ProdPrice
                invokeSetterMethod($matches[1], $class_obj, $map,$class_instance);
            }
        }
        return $class_instance;

    } catch (Exception $exception){
    
        return null;
    }
}


//  Two dimensional array 
// $fill  Fill in new fields 
// $toarray=false  Returns an array of entities 
function mapToModelsArray(array $maps, $class, $fill=[], $toarray=false){
    
    $ret = [];
    foreach ($maps as $map){
    
        $getObject = mapToModel($map, $class);
        if($getObject){
    
           if($fill && count($fill) > 0){
    
               $getObject->fill($fill);
           }
           if($toarray){
    
               //  Array 
               $ret[] = $getObject->getModelAttributes();
           }else{
    
               //  Entity object 
               $ret[] = $getObject;
           }
        }
    }
    return $ret;
}


function invokeSetterMethod($name, ReflectionClass $class_obj, $jsonMap, &$class_instance){
    
    //  hold  ProdId  Turn it into  Prod_Id
    $filter_name = strtolower(preg_replace("/(?<=[a-z])([A-Z])/", "_$1", $name));
    //  compatible  swoft
    //  hold  ProdId  Turn it into  prodId
    $filter_name_ForSwoft = lcfirst($name);
    $props = $class_obj->getProperties(ReflectionProperty::IS_PRIVATE);
    foreach ($props as $prop) {
    
        //  There are corresponding private properties 
        if(strtolower($prop->getName()) == $filter_name || $prop->getName() == $filter_name_ForSwoft){
    
            $method = $class_obj->getMethod("set" . $name);
            $args = $method->getParameters(); //  Take out the parameters 
            if(count($args) == 1 && isset($jsonMap[$filter_name])){
    
                //  Assign values to instance objects ( By quoting )
                $method->invoke($class_instance, $jsonMap[$filter_name]);
            }
        }
    }
}
  • About Reflexive class and Instantiate the class The difference between :new class() It is the presentation of a class object after encapsulation , You don't need to know the private members and methods of the class , And the internal mechanism , You can use it directly through the open member methods and properties of the class
  • and new ReflectionClass() The reflection class is the presentation of a class object after it is opened , It will the internal properties of the class 、 Including public or private properties and methods , Is it static , Interface 、 Inherit 、 Namespace information , Even all comments are made public , Can be reflected API Visit
  • This shows the power of reflection class . But reflection is usually used to write more complex underlying logic of the business . The external function development still uses instantiation class encapsulation , It's also safer and more convenient
  • The reflection mechanism itself is the means to release the closure , Its purpose mostly lies in the bottom or not external business development , For example, interface documents are automatically generated 、 Implement hook . Since the post reflection class is so open , Nature can be modified at will

2. database ;

2.1 Basic configuration ;

<?php
//  The setting configuration file is as follows 
db' => [ 'class' => Database::class, 'dsn' => 'mysql:dbname=test;host=127.0.0.1', 'username' => 'root', 'password' => 'asdf', 'charset' => 'utf8mb4', 'config' => [ 'collation' => 'utf8mb4_unicode_ci', 'strict' => true, 'timezone' => '+8:00', 'modes' => 'NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES', 'fetchMode' => PDO::FETCH_ASSOC
    ]
],
#  The main function of connection pool : Protect the database 
#  The built-in control pool has its own connection 
 'db.pool' => [
    'class'    => Pool::class,
    'database' => bean('db'),
    'minActive' => 10,
    'maxActive' => 20,
    'minWait' => 0,
    'maxWaitTime' => 0
],
#  The actual development should use middleware , Not the method given by the framework 
#  To switch data sources, you need to create a new  db  and  db.pool

2.2 Native operation 、 Query builder ;

<?php

namespace App\Http\Controller;

use App\lib\ProductEntity;
use App\Model\Product;
use Swoft\Db\DB;
use Swoft\Http\Message\ContentType;
use Swoft\Http\Server\Annotation\Mapping\Controller;
use Swoft\Http\Server\Annotation\Mapping\RequestMapping;
use Swoft\Http\Server\Annotation\Mapping\RequestMethod;
use Swoft\Validator\Annotation\Mapping\Validate;

/** * @Controller(prefix="/product2") */
class Product2Controller{
    

    /** * 2.3  Validator :https://www.swoft.org/documents/v2/core-components/validator/ *  Annotations use  * // @Validate(validator="product") // type="get" * @RequestMapping(route="{pid}", params={"pid"="\d+"}, method={RequestMethod::GET, RequestMethod::POST}) */
    public function prod_detail(int $pid){
    
        // 2.2.1  Native query 
        // $product = DB::selectOne("SELECT * FROM product WHERE id = ?", [$pid]);
        //  Switch database 
        // $product = DB::db('test')->selectOne("SELECT * FROM product WHERE id = :pid", ["pid" => $pid]);
        //  Switch data source 
        // $product = DB::query("db.pool")->getConnection()->selectOne("SELECT * FROM product WHERE id = :pid", ["pid" => $pid]);

        // 2.2.2  The query constructor uses 、 Association table :https://www.swoft.org/documents/v2/mysql/query/
        //  Get all 
        // $product = DB::table("product")->get();
        //  Get one 
        // $product = DB::table("product")->where("id", "=", $pid)->select("id")->first();
        //  Association table 
// $product = DB::table("product")
// ->join("product_class", "product.id", "product_class.pid")
// ->select("product.*", "product_class.pid")
// ->where("product.id", $pid)
// ->first();

        // 2.4  Use of models 
        $product = Product::find($pid);
        //  Does not affect the interface display , Join the code , Speed up the value taking process 
        //  Reference resources :https://www.swoft.org/documents/v2/basic-components/public-function/#heading1
        if($product){
    
            //  Code block   Scope 
            {
    
                sgo(function () use ($product){
    
                    \Swoole\Coroutine::sleep(3);
                    //  Specifies that the field is incremented 1
                    $product->increment("prod_click");
                });
            }
            {
    
                sgo(function () use ($pid){
    
                    \Swoole\Coroutine::sleep(5);
                    //  Every time a product is visited , Product access logs will be added 

                    //  Method  1 :  Insert in model mode 
                    $product_view = ProductView::new();
                    {
    
                        $product_view->setProdId($pid);
                        $product_view->setViewIp(ip());
                        $product_view->setViewNum(1);
                        $product_view->save();
                    }

                    //  Method  2 :  Insert in array mode 
                    $pviewData = [
                        "prod_id" => $pid,
                        "view_ip" => ip(),
                        "view_num" => 1
                    ];
                    $product_view = ProductView::new($pviewData)->save();

                    //  If one  ip  Visited every product on the same day , It only needs  view_num +1  Just go 
                    // ProductView::updateOrCreate() //  Data needs to be retrieved during update and creation , Using this method 
                    //  Just add or insert   Use the following methods , Every time  view_num +1
                    ProductView::updateOrInsert(
                        [
                            "prod_id" => $pid,
                            "view_ip" => ip()
                        ],
                        [
                            "view_num" => DB::raw("view_num+1")
                        ]
                    );
                });
            }
        }

        /** //@var $product ProductEntity */
        //$product = jsonForObject(ProductEntity::class);
        //var_dump($product);
        if(isGet()){
    
            return response(ContentType::JSON)->withData($product);
        }else if(isPost()) {
    
            //  Non annotation use :https://www.swoft.org/documents/v2/core-components/validator/#heading15
            \validate(jsonForObject(),"product");
            $product['prod_name'] = " Product modification " . request()->post('prod_name', 'none');
            $product['prod_price'] = " Revise the price " . request()->post('prod_price', 0);
           return response(ContentType::JSON)->withData($product);

        }

    }

}

2.3 Validator ;

  • Annotations use
  • newly build App\Http\MyValidator\ProductValidator.php
<?php

namespace App\Http\MyValidator;

use Swoft\Validator\Annotation\Mapping\IsFloat;
use Swoft\Validator\Annotation\Mapping\IsString;
use Swoft\Validator\Annotation\Mapping\Length;
use Swoft\Validator\Annotation\Mapping\Min;
use Swoft\Validator\Annotation\Mapping\Max;
use Swoft\Validator\Annotation\Mapping\Validator;

/** *  verification :https://www.swoft.org/documents/v2/core-components/validator/#heading4 * ***  remaining problems : A null value judgment  * @Validator(name="product") */
class ProductValidator{
    

    /** * @IsString(message=" Commodity name cannot be empty ") * @Length(min=5,max=20,message=" The length of the product name is 5-20") * @var string */
    protected $prod_name;

    /** * @IsString(message=" Commodity short name cannot be empty ") * @Length(min=2,max=20,message=" The length of the product name is 2-20") * @var string */
    protected $prod_sname;

    /** * @IsFloat(message=" Commodity price cannot be empty ") * @Min(value=20,message=" Lowest price 20") * @Max(value=1000,message=" Highest price 1000") * @var float */
    protected $prod_price;

}

2.4 Use of models ;

php ./bin/swoft entity:create --table=product,product_class --pool=db.pool --path=@app/Model

#  Add new fields later : After defining private variables , PhpStorm  use  Alt + Ins  New generation  set  and  get  Method 

2.5 Scene practice ( data validation , Primary sub order receipt , Transaction control );

 Insert picture description here
 Insert picture description here

  • Generating entities :
#  Master order table 
php ./bin/swoft entity:create --table=orders_main --pool=db.pool --path=@app/Model

#  Sub order table 
php ./bin/swoft entity:create --table=orders_detail --pool=db.pool --path=@app/Model
  • modify App\Model\OrdersMain.php
	// Alt + ins  Generate  set  and  get  Method 
    //  Don't annotate the attributes you define 
    //  This attribute is not available in the database , Just mapping to objects 
    private $orderItems;

    /** * @return mixed */
    public function getOrderItems()
    {
    
        return $this->orderItems;
    }

    /** * @param mixed $orderItems */
    public function setOrderItems($orderItems): void
    {
    
        $this->orderItems = $orderItems;
    }
  • Main table verifier : newly build App\Http\MyValidator\OrderValidator.php
<?php

namespace App\Http\MyValidator;

use App\Http\MyValidator\MyRules\OrderDetail;
use Swoft\Validator\Annotation\Mapping\IsArray;
use Swoft\Validator\Annotation\Mapping\IsFloat;
use Swoft\Validator\Annotation\Mapping\IsInt;
use Swoft\Validator\Annotation\Mapping\Min;
use Swoft\Validator\Annotation\Mapping\Max;
use Swoft\Validator\Annotation\Mapping\Validator;

/** * @Validator(name="order") */
class OrderValidator{
    

// /**
// * @IsString(message=" Order No. cannot be empty ")
// * @var string
// */
// protected $order_no;

    /** * @IsInt(message=" user ID Can't be empty ") * @Min(value=1,message=" user id Incorrect ") * @var int */
    protected $user_id;

    /** * @IsInt(message=" Order status cannot be empty ") * @Min(value=0,message=" The state is not correct min") * @Max(value=5,message=" The state is not correct max") * @var int */
    protected $order_status;

    /** * @IsFloat(message=" Order amount cannot be blank ") * @Min(value=1,message=" The order amount is incorrect ") * @var int */
    protected $order_money;

    //  Order details , Is an array , Several entities change orders 
    //  Custom validator rules :https://www.swoft.org/documents/v2/core-components/validator/#heading7
    /** * @IsArray(message=" Order details cannot be blank ") * @OrderDetail(message=" Incorrect order details ") * @var array */
    protected $order_items;

}
  • Secondary table verifier : newly build 1 App\Http\MyValidator\OrderDetailValidator.php
<?php

namespace App\Http\MyValidator;

use Swoft\Validator\Annotation\Mapping\IsFloat;
use Swoft\Validator\Annotation\Mapping\IsInt;
use Swoft\Validator\Annotation\Mapping\IsString;
use Swoft\Validator\Annotation\Mapping\Min;
use Swoft\Validator\Annotation\Mapping\Max;
use Swoft\Validator\Annotation\Mapping\Validator;

/** * @Validator(name="order_detail") */
class OrderDetailValidator{
    

    /** * @IsInt(message=" goods ID Can't be empty ") * @Min(value=1,message=" goods id Incorrect ") * @var int */
    protected $prod_id;

    /** * @IsString(message=" Commodity name cannot be empty ") * @var string */
    protected $prod_name;

    /** * @IsFloat(message=" Commodity price cannot be empty ") * @Min(value=0,message=" The commodity price is incorrect ") * @var int */
    protected $prod_price;

    /** * @IsInt(message=" Discount cannot be blank ") * @Min(value=1,message=" The discount is incorrect min") * @Max(value=10,message=" The discount is incorrect max") * @var int */
    protected $discount;

    /** * @IsInt(message=" Commodity quantity cannot be blank ") * @Min(value=1,message=" The quantity of goods is incorrect ") * @var int */
    protected $prod_num;

}
  • Custom validator , newly build 2 App\Http\MyValidator\MyRules\OrderDetail.php
<?php

namespace App\Http\MyValidator\MyRules;

/** * @Annotation * @Attributes({ * @Attribute("message",type="string") * }) */
class OrderDetail{
    

    /** * @var string */
    private $message = '';

    /** * @var string */
    private $name = '';

    /** * StringType constructor. * * @param array $values */
    public function __construct(array $values)
    {
    
        if (isset($values['value'])) {
    
            $this->message = $values['value'];
        }
        if (isset($values['message'])) {
    
            $this->message = $values['message'];
        }
        if (isset($values['name'])) {
    
            $this->name = $values['name'];
        }
    }

    /** * @return string */
    public function getMessage(): string
    {
    
        return $this->message;
    }

    /** * @return string */
    public function getName(): string
    {
    
        return $this->name;
    }

}
  • Custom validator , newly build 3 App\Http\MyValidator\MyRules\OrderDetailParser.php
<?php

namespace App\Http\MyValidator\MyRules;

use Swoft\Annotation\Annotation\Mapping\AnnotationParser;
use Swoft\Annotation\Annotation\Parser\Parser;
use App\Http\MyValidator\MyRules\OrderDetail;
use Swoft\Validator\ValidatorRegister;

/** * Class OrderDetailParser * @AnnotationParser(annotation=OrderDetail::class) */
class OrderDetailParser extends Parser{
    

    /** * Parse object * * @param int $type Class or Method or Property * @param object $annotationObject Annotation object * * @return array * Return empty array is nothing to do! * When class type return [$beanName, $className, $scope, $alias] is to inject bean * When property type return [$propertyValue, $isRef] is to reference value */
    public function parse(int $type, $annotationObject): array
    {
    
        if ($type != self::TYPE_PROPERTY) {
    
            return [];
        }
        // Register an authentication rule with the verifier 
        ValidatorRegister::registerValidatorItem($this->className, $this->propertyName, $annotationObject);
        return [];
    }
}

  • Custom validator , newly build 4 App\Http\MyValidator\MyRules\OrderDetailRule.php
<?php

namespace App\Http\MyValidator\MyRules;

use App\Http\MyValidator\MyRules\UserPass;
use Swoft\Bean\Annotation\Mapping\Bean;
use Swoft\Validator\Contract\RuleInterface;
use Swoft\Validator\Exception\ValidatorException;

/** * Class OrderDetailRule * @Bean(OrderDetail::class) */
class OrderDetailRule implements RuleInterface {
    

    /** * @param array $data * @param string $propertyName * @param object $item * @param mixed $default * * @return array */
    public function validate(array $data, string $propertyName, $item, $default = null, $strict = false): array
    {
    
        $getData = $data[$propertyName];
        if(!$getData || !is_array($getData) || count($getData) == 0){
    
            throw new ValidatorException($item->getMessage());
        }

        foreach ($getData as $data) {
    
           validate($data, "order_detail");
        }

        return $data;
    }
}
  • newly build App/Http/Controller/OrderController.php
<?php

namespace App\Http\Controller;

use App\Exception\ApiException;
use App\Model\OrdersDetail;
use App\Model\OrdersMain;
use Swoft\Db\DB;
use Swoft\Http\Server\Annotation\Mapping\Controller;
use Swoft\Http\Server\Annotation\Mapping\RequestMapping;
use Swoft\Http\Server\Annotation\Mapping\RequestMethod;
use Swoft\Task\Task;
use Swoft\Validator\Annotation\Mapping\Validate;

/** * @Controller(prefix="/order") */
class OrderController{
    

    /** *  Create order  * @Validate(validator="order") * @RequestMapping(route="new",method={RequestMethod::POST}) */
    public function createOrder(){
    

        //  obtain  POST  Basic verification of data 
        /** @var OrdersMain $orderPost */
        //  Contains the data of main order and sub order 
        $orderPost = jsonForObject(OrdersMain::class);
        //  Chrysanthemum algorithm can also be used 
        $orderNo = date("YmdHis") . substr(implode(NULL, array_map('ord',str_split(substr(uniqid(), 7, 13), 1))),0,8);

        // ApiExceptionHandler  Intercept 
        // throw new ApiException("api exception");

        //  Get sub order data , Is an array 
        //  Need to pass through ORM Way to insert , Arrays don't work 
        //  You need a function to map it to  ORM  Entity 
        //  Sub order data ([],[]), Array of model entities 
        //  the last one  true  Parameter represents the return of an array , The fields are consistent with the database 
        $orderDetail_array = mapToModelsArray($orderPost->getOrderItems(), OrdersDetail::class, ["order_no" => $orderNo], true);
        // var_dump($orderDetail_array);
        // $orderPost->getModelAttributes() //  Entity object to array 

        //  Both order data are received 
        if($orderPost){
    
            // $orderPost->setOrderNo($orderNo);
            // $orderPost->setCreateTime(date("Y-m-d H:i:s"));
            $orderPost->fill(["order_no" => $orderNo, "create_time" => date("Y-m-d H:i:s")]);
            //  Join the business : https://www.swoft.org/documents/v2/mysql/transaction/
// DB::beginTransaction();
// if($orderPost->save() && OrdersDetail::insert($orderDetail_array) ){
    
// DB::commit();
// return OrderCreateSuccess($orderNo);
// }else{
    
// DB::rollBack();
// }

            tx(function () use ($orderPost, $orderDetail_array, $orderNo){
    
                if($orderPost->save() && OrdersDetail::insert($orderDetail_array) ){
    
                    return OrderCreateSuccess($orderNo);
                }
                throw new \Exception(" Failed to create order ");
            }, $result);

            // 3.2.1  Asynchronous task delivery 
            // Task::async("orders", "putorderno", [$orderNo]);

            // 3.2.2  Put in the delay queue 
            Task::async("orders", "putorderno_zset", [$orderNo, 5]);


            return $result;
        }

        return OrderCreateFail();
    }

}
  • Throw exception related modification :
#  file  App\Exception\Handler\HttpExceptionHandler.php
#  Revise the following sections , A generic exception is thrown here 
if (!APP_DEBUG) {
    
   //return $response->withStatus(500)->withContent($e->getMessage());
    return $response->withStatus(500)->withData(["message" => $e->getMessage()]);
}

#  file  App\Exception\Handler\ApiExceptionHandler.php
#  Code  throw new ApiException("api exception");  Make the following output 
public function handle(Throwable $except, Response $response): Response
    {
    
// $data = [
// 'code' => $except->getCode(),
// 'error' => sprintf('(%s) %s', get_class($except), $except->getMessage()),
// 'file' => sprintf('At %s line %d', $except->getFile(), $except->getLine()),
// 'trace' => $except->getTraceAsString(),
// ];
//
// return $response->withData($data);

        return $response->withStatus(500)->withData([
            "apimessage" => $except->getMessage()
        ]);
    }
  • add to : App/Helper/OrderFunctions.php
<?php

function OrderCreateSuccess($orderNo){
    
    return ["status" => "success", "orderNo" => $orderNo];
}


function OrderCreateFail($msg="error"){
    
    return ["status" => $msg, "orderNo" => ""];
}

function getInWhere($array) {
    
    $where = "";
    foreach ($array as $item){
    
        if($where == ""){
    
            $where .= "'" . $item . "'";
        }else{
    
            $where .= ",'" . $item . "'";
        }
    }
    
    return $where;
}

3. Redis relevant ;

3.1 Redis Configuration and use ;

 'redis'             => [
        'class'    => RedisDb::class,
        'host'     => '127.0.0.1',
        'port'     => 6379,
        'password' => 'asdf',
        'database' => 0,
        'option'   => [
                //'serializer' => Redis::SERIALIZER_PHP
            'serializer' => 0
        ]
    ],
    'redis.pool' => [
        'class'       => Swoft\Redis\Pool::class,
        'redisDb'     => bean('redis'),
        'minActive'   => 2,
        'maxActive'   => 5,
        'maxWait'     => 0,
        'maxWaitTime' => 0,
        'maxIdleTime' => 60,
    ],
  • newly build App/Http/Controller/testController.php
<?php

namespace App\Http\Controller;

use Swoft\Http\Server\Annotation\Mapping\Controller;
use Swoft\Http\Server\Annotation\Mapping\RequestMapping;
use Swoft\Http\Server\Annotation\Mapping\RequestMethod;
use Swoft\Redis\Redis;

/** * Class testController * @Controller(prefix="/test") */
class TestController{
    

    /** * @RequestMapping(route="/test",method={RequestMethod::GET}) *  in addition : Connection pool injection :https://www.swoft.org/documents/v2/redis/usage/#heading1 */
    public function test(){
    
        $redis = Redis::connection("redis.pool");
        $v = $redis->get("name");
        return $v;
    }

}

3.2 Scene practice ( The order is overdue );

3.2.1 Asynchronous task ;

  • Connect , The receipt of primary and sub orders must be successful . If there are other steps , For example, insert the order number redis, There is no need for customers to wait for this part . As long as the primary sub order is inserted successfully , Return a response to the client immediately , Insert redis You can use concurrent or asynchronous tasks to complete
  • Asynchronous tasks are used here :https://www.swoft.org/documents/v2/core-components/task/
  • newly build App/Task/OrderTask.php
<?php

namespace App\Tasks;

use Swoft\Redis\Redis;
use Swoft\Task\Annotation\Mapping\Task;
use Swoft\Task\Annotation\Mapping\TaskMapping;

/** * Class OrderTask * @Task(name="orders") */
class OrderTask{
    

    /** * @TaskMapping(name="putorderno") */
    public function putOrderNoToRedis($orderNo){
    
        //  Expiration time  5  second 
        Redis::setex("order" . $orderNo, 5, 1);
    }

}
  • key Expiration trigger event listening : This method can be used when the pressure is not particularly high , Use queues to complete... Under high pressure
  • Redis Of Keyspace notice :https://redis.io/topics/notifications , After triggering some events, you can send a notification to the specified channel
  • Relevant events need to be set , once key Be overdue , Will send one to the specified channel key name , Get this key after , We can do the corresponding treatment
#  operation  1:
#  modify  Redis  The configuration file  redis.conf, hold  notify-keyspace-events Ex  Ahead  “#”  Get rid of , Save and exit , restart  redis

#  operation  2: Use  Redis  Subscription publishing function 
subscribe [email protected]__:expired
#  return :
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "[email protected]__:expired"
3) (integer) 1
#  Generate order  10  Seconds later  redis  Be overdue , Send an event to the channel to trigger , The console returns 
1) "message"
2) "[email protected]__:expired"
3) "order2020022614311652575653"

3.2.2 User process ;

  • Code implementation , Need to use user processes :https://www.swoft.org/documents/v2/core-components/process/#heading2
  • If the process brings something particularly complex , Especially consumes machine performance , This process needs to be done alone , Do not embed directly in Swoft Inside . If the process is simple ( Do order monitoring ), That can be embedded in HTTP Server Inside ( stay Swoft When it starts , Start a process at the same time , You can do some monitoring and other related matters in the process )
  • newly build :App/MyProcess/OrderProcess.php
<?php

namespace App\MyProcess;

use App\Model\OrdersMain;
use Swoft\Bean\Annotation\Mapping\Bean;
use Swoft\Process\Process;
use Swoft\Process\UserProcess;
use Swoft\Redis\Redis;

/** * Class OrderProcess * @Bean() */
class OrderProcess extends UserProcess {
    


    /** * Run * * @param Process $process */
    public function run(Process $process): void
    {
    
        //  Officials say we must add , In fact, it's OK not to add 
        //  If not , Output  abc  after , End of process , Then restart the process output  abc
// while(true){
    
// echo "abc\n";
// \Swoole\Coroutine::sleep(5);
// }

        //  The following code is similar to blocking , Unwanted  while
        //  Order  10  Seconds expired , Set the order status to expired 
         Redis::subscribe(["[email protected]__:expired"], function ($redis, $chan, $msg){
    
            // echo "Channel: $chan\n";
            // echo "Payload: $msg\n";
             $orderNo = str_replace("order", "", $msg);
             OrdersMain::where("order_no", $orderNo)
                 ->update(["order_status" => -1]);

             echo " Order No " . $orderNo . " Your order is overdue " . PHP_EOL;
        });
    }
}

  • modify :App\bean.php
// httpServer  Add the following to it 
'process' => [
	// 3.2.2
    // 'orderp' => bean(\App\MyProcess\OrderProcess::class)
    // 3.2.3
    'orderp' => bean(\App\MyProcess\OrderProcessBySet::class)
 ]

3.2.3 Redis Delay queue ;

  • Before using Redis The subscription and publishing function of . In actual development , In the case of large orders , If Auto trigger is used , Will affect performance , Therefore, the delay queue method will be used to complete , And use the client process to poll
  • Use Redis Polling in an orderly collection of . Order placed successfully , be zadd key score value, among key It's a fixed value ( such as orderset),score Is the timestamp value (time+5),value It's the order number
  • In the process, you can read circularly , Once it exceeds the time, delete it
  • modify App/Task/OrderTask.php
//  Insert the following code 

/** * @TaskMapping(name="putorderno_zset") */
public function putOrderNoToRedisZset($orderNo, $delay){
    
   Redis::zAdd("orderset", [$orderNo => time() + $delay] );
    echo " Insert  redis_zset  success " . PHP_EOL;
}

  • Redis The relevant operation
#  View ordered queues 
zrange orderset 0 -1 withscores
zrangebyscore orderset -inf +inf withscores
  • newly build :App/MyProcess/OrderProcessBySet.php
<?php

namespace App\MyProcess;

use Swoft\Bean\Annotation\Mapping\Bean;
use Swoft\Db\DB;
use Swoft\Process\Process;
use Swoft\Process\UserProcess;
use Swoft\Redis\Redis;

/** * Class OrderProcessBySet * @Bean() */
class OrderProcessBySet extends UserProcess {
    


    /** * Run * * @param Process $process */
    public function run(Process $process): void
    {
    
        while (true){
    
            $orders = Redis::zRangeByScore("orderset", '-inf', strval(time()), ["limit" => [0, 3]]);

            if($orders && count($orders) > 0) {
    
                DB::beginTransaction();
                try {
    
                    //  Batch execution 
                    $rows = DB::update("UPDATE orders_main SET order_status = -1 WHERE order_no in (" . getInWhere($orders) . ")");
					//  Extender :"..."
					//  There is a problem with the logic here , Once triggered  MySQL  Roll back ,Redis  No rollback , Data inconsistency 
                    if(Redis::zRem("orderset", ...$orders) === $rows){
    
                        throw new \Exception("orderset remove error");
                    }
                    DB::commit();

                }catch (\Exception $exception){
    
                    echo $exception->getMessage();
                    DB::rollBack();
                }

                usleep(1000*500);   // 500  millisecond 
            }
        }
    }
}

copyright notice
author[hualaoshuan],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2022/119/202204290600599257.html

Random recommended