定制 kilylabs/phprest 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

kilylabs/phprest

Composer 安装命令:

composer require kilylabs/phprest

包简介

PHP Rest Framework.

README 文档

README

Added PHP 7.0 support!

Phprest

Build Status Code Coverage Quality Score Software License

Description

REST-like PHP micro-framework.

It's based on the Proton (StackPhp compatible) micro-framework.

Phprest gives you only the very basics to build your own architecture within your own framework and assemble any folder structure you like. It is just a thin layer around your application with the help of some great libraries.

Components

Skills

  • Dependency injection
  • Routing
  • Error handling
  • Serialization
  • Deserialization
  • HATEOAS
  • API versioning
  • Pagination
  • Logging

ToC

Installation

Install it through composer.

{
    "require": {
        "phprest/phprest": "@stable"
    }
}

tip: you should browse the phprest/phprest page to choose a stable version to use, avoid the @stable meta constraint.

Usage

Services

There are a couple of services which can help you to solve some general problems:

These are separate repositories.

Setup

<?php
require __DIR__ . '/../vendor/autoload.php';

use Phprest\Config;
use Phprest\Response;
use Phprest\Application;
use Symfony\Component\HttpFoundation\Request;

# vendor name, current API version, debug
$config = new Config('vendor.name', '0.1', true);

$app = new Application($config);

$app->get('/{version:\d\.\d}/', function (Request $request) {
    return new Response\Ok('Hello World!');
});

$app->run();

Configuration

You should check the Config class.

Logging

<?php
use Phprest\Service\Logger\Config as LoggerConfig;
use Phprest\Service\Logger\Service as LoggerService;
use Monolog\Handler\StreamHandler;

$config = new Config('vendor.name', '0.1');

$loggerHandlers[] = new StreamHandler('path_to_the_log_file', \Monolog\Logger::DEBUG);

$config->setLoggerConfig(new LoggerConfig('phprest', $loggerHandlers));
$config->setLoggerService(new LoggerService());

Usage with Stack

You can register middlewares trough the registerMiddlewarefunction.

$app->registerMiddleware('Jsor\Stack\JWT', [
    [
        'firewall' => [
	    ['path' => '/',         'anonymous' => false],
	    ['path' => '/tokens',   'anonymous' => true]
	],
	'key_provider' => function() {
	    return 'secret-key';
	},
	'realm' => 'The Glowing Territories'
    ]
]);

API Versioning

Phprest works with API versions by default. This means that the ApiVersion Middleware manipulates the incoming request. The version (based on the current Accept header) is added to the path.

What does it mean?

Accept header Route Result route*
application/vnd.phprest-v1+json /temperatures /1.0/temperatures
application/vnd.phprest-v3.5+json /temperatures /3.5/temperatures
*/* /temperatures /the version which you set in your Config/temperatures

* It is not a redirect or a forward method, it is just an inner application routing through a middleware.

Accept/Content-Type header can be Transfers to
application/vnd.Vendor-vVersion+json itself
application/vnd.Vendor+json; version=Version itself
application/vnd.Vendor-vVersion+xml itself
application/vnd.Vendor+xml; version=Version itself
application/json application/vnd.Vendor-vVersion+json
application/xml application/vnd.Vendor-vVersion+xml
*/* application/vnd.Vendor-vVersion+json

API Version only can be one of the following ranges:

  • 0 - 9
  • 0.0 - 9.9
  • If Accept header is not parsable

  • then Phprest throws a Not Acceptable exception

  • If you do a deserialization and Content-Type header is not parsable

  • then Phprest throws an Unsupported Media Type exception

Routing

For more information please visit League/Route.

Simple routing

<?php
$app->get('/{version:\d\.\d}/hello', function (Request $request, $version) {
	# You can leave the $request and the $version variable
    return new Response\Ok('Hello World!');
});
  • The ApiVersion Middleware manipulates the inner routing every time, so you have to care about the first part of your route as a version number.
  • This route is available in all API versions (see the \d\.\d regular expression)
  • You can set a fix API version number too e.g. '/3.6/hello'

Routing with arguments

<?php
$app->get('/2.4/hello/{name:word}', function (Request $request, $name) {
    return new Response\Ok('Hello ' . $name);
});
  • This route is available only in API version 2.4

Routing through a controller

<?php
# index.php

# calls index method on HomeController class
$app->get('/{version:\d\.\d}/', '\Foo\Bar\HomeController::index');
<?php namespace Foo\Bar;
# HomeController.php

use Symfony\Component\HttpFoundation\Request;
use Phprest\Response;

class HomeController
{
    public function index(Request $request, $version)
    {
        return new Response\Ok('Hello World!');
    }
}

Routing through a service controller

<?php
$app['HomeController'] = function () {
    return new \Foo\Bar\HomeController();
};

$app->get('/{version:\d\.\d}/', 'HomeController::index');

Routing with annotations

You have to register your controller.

<?php

$app->registerController('\Foo\Bar\Controller\Home');
<?php namespace Foo\Bar\Controller;
# Home.php

use Phprest\Util\Controller;
use Symfony\Component\HttpFoundation\Request;
use Phprest\Response;
use Phprest\Annotation as Phprest;

class Home extends Controller
{
    /**
     * @Phprest\Route(method="GET", path="/foobars/{id}", since=1.2, until=2.8)
     */
    public function get(Request $request, $version, $id)
    {
        return new Response\Ok('Hello World!');
    }
}
  • since tag is optional
  • until tag is optional

Controller

To create a Phprest Controller simply extends your class from \Phprest\Util\Controller.

<?php namespace App\Module\Controller;

class Index extends \Phprest\Util\Controller
{
   public function index(Request $request)
   {
      # ...
   }
}

Serialization, Deserialization, Hateoas

  • Phprest will automatically serialize* your response based on the Accept header.
  • Phprest can deserialize your content based on the Content-Type header.

Except*:

  • If your response is not a Response instance (e.g. it a simple string)
  • If your response is empty

Serialization example

Let's see a Temperature entity:

You do not have to use annotations! You can use configuration files! Browse in Jms\Serializer and Willdurand\Hateoas

<?php namespace Foo\Entity;

use JMS\Serializer\Annotation as Serializer;
use Hateoas\Configuration\Annotation as Hateoas;

/**
 * @Serializer\XmlRoot("result")
 *
 * @Hateoas\Relation(
 *      "self",
 *      href = @Hateoas\Route("/temperatures", parameters = {"id" = "expr(object.id)"}, absolute = false)
 * )
 */
class Temperature
{
    /**
     * @var integer
     * @Serializer\Type("integer")
     */
    public $id;

    /**
     * @var integer
     * @Serializer\Type("integer")
     */
    public $value;

    /**
     * @var \DateTime
     * @Serializer\Type("DateTime")
     * @Serializer\Since("2")
     * @Serializer\Exclude
     */
    public $created;

    /**
     * @param integer $id
     * @param integer $value
     * @param \DateTime $created
     */
    public function __construct($id = null, $value = null, \DateTime $created = null)
    {
        $this->id = $id;
        $this->value = $value;
        $this->created = $created;
    }
}

The router:

<?php
$app->post('/{version:\d\.\d}/temperatures', function () use ($app, $version) {
    $temperature = new \Foo\Entity\Temperature(1, 32, new \DateTime());
    
    return new Response\Created('/temperatures/1', $temperature);
});

Json response (Accept: application/vnd.vendor+json; version=1):

{
    "id": 1,
    "value": 32,
    "_links": {
        "self": {
            "href": "\/temperatures\/1"
        }
    }
}

Xml response (Accept: application/vnd.vendor+xml; version=1):

<result>
  <id>1</id>
  <value>32</value>
  <link rel="self" href="/temperatures/1"/>
</result>

Properties will be translated from camel-case to a lower-cased underscored name, e.g. camelCase -> camel_case by default. If you want to use a custom serialized name you have to use the @SerializedName option on your attribute.

Deserialization example

You have to use the HATEOAS Util trait in your controller to do deserialization.

# ...
use JMS\Serializer\Exception\RuntimeException;
# ...
    public function post(Request $request)
    {
        try {
            /** @var \Foo\Entity\Temperature $temperature */
            $temperature = $this->deserialize('\Foo\Entity\Temperature', $request);
        } catch (RuntimeException $e) {
            throw new Exception\UnprocessableEntity(0, [new Service\Validator\Entity\Error('', $e->getMessage())]);
        }
    }
# ...

Pagination

<?php
# ...
use Hateoas\Representation\PaginatedRepresentation;
use Hateoas\Representation\CollectionRepresentation;
# ...
$paginatedCollection = new PaginatedRepresentation(
    new CollectionRepresentation([$user1, $user2, ...]),
    '/users', # route
    [],       # route parameters, should be $request->query->all()
    1,        # page, should be (int)$request->query->get('page')
    10,       # limit, should be (int)$request->query->get('limit')
    5,        # total pages
    'page',   # page route parameter name, optional, defaults to 'page'
    'limit',  # limit route parameter name, optional, defaults to 'limit'
    true,     # absolute URIs
    47        # total number of rows
);
# ...
return new Response\Ok($paginatedCollection);

For more informations please visit the HATEOAS docs

Responses

There are several responses you can use by default, one of them is the Ok response.

1xx, 2xx, 3xx status codes

These are simple Response objects.

Example

<?php
# ...
$app->get('/', function (Request $request) {
    return new Response\Ok('Hello World!');
});
# ...

Types

Responses
Accepted
Created
NoContent
NotModified
Ok

4xx, 5xx status codes

These are Exceptions.

Example

<?php
# ...
$app->get('/', function (Request $request) {
    # ...
    
    throw new \Phprest\Exception\BadRequest();
    
    # ...
});
# ...

Types

Exceptions
BadRequest
Conflict
Forbidden
Gone
InternalServerError
MethodNotAllowed
NotAcceptable
NotFound
TooManyRequests
PreconditionFailed
TooManyRequests
Unauthorized
UnprocessableEntity
UnsupportedMediaType

Dependency Injection Container

See Proton's doc and for more information please visit League/Container.

CLI

You can use a helper script if you want after a composer install (vendor/bin/phprest).

You have to provide the (bootstrapped) app instance for the script. You have two options for this:

  • Put your app instance to a specific file: app/app.php
  • You have to return with the bootstrapped app instance in the proper file
  • Put the path of the app instance in the paths.php file
  • You have to return with an array from the paths.php file with the app file path under the app array key

Error handler

Phprest handles error with League\BooBoo. The default formatter is Json and Xml Formatter.

On a single exception

<?php
# ...
$app->get('/{version:\d\.\d}/', function (Request $request, $version) {
    throw new \Phprest\Exception\Exception('Code Red!', 9, 503);
});
# ...

The response is content negotiationed (xml/json), the status code is 503.

{
    "code": 9,
    "message": "Code Red!",
    "details": []
}
<result>
    <code>9</code>
    <message>
        <![CDATA[Code Red!]]>
    </message>
</result>

Authentication

Basic Authentication

You'll need this package:

$app->registerMiddleware('Dflydev\Stack\BasicAuthentication', [
    [
        'firewall' => [
	    ['path' => '/', 'anonymous' => false],
            ['path' => '/temperatures', 'method' => 'GET', 'anonymous' => true]
	],
	'authenticator' => function ($username, $password) {
            if ('admin' === $username && 'admin' === $password) {
                # Basic YWRtaW46YWRtaW4=
                return 'success';
            }
        },
	'realm' => 'The Glowing Territories'
    ]
]);

JWT Authentication

You'll need this package:

$app->registerMiddleware('Jsor\Stack\JWT', [
    [
        'firewall' => [
	    ['path' => '/',         'anonymous' => false],
	    ['path' => '/tokens',   'anonymous' => true]
	],
	'key_provider' => function() {
	    return 'secret-key';
	},
	'realm' => 'The Glowing Territories'
    ]
]);

API testing

There are a couple of great tools out there for testing your API.

  • Postman and Newman
  • Tip: Create collections in Postman and then run these in Newman
  • Frisby
  • Frisby is a REST API testing framework built on node.js and Jasmine that makes testing API endpoints easy, fast, and fun.
  • Runscope
  • For Api Monitoring and Testing

API documentation

Just a few recommendations:

  • API Blueprint
    • API Blueprint is a documentation-oriented API description language. A couple of semantic assumptions over the plain Markdown.
  • Swagger
    • With a Swagger-enabled API, you get interactive documentation, client SDK generation and discoverability.

kilylabs/phprest 适用场景与选型建议

kilylabs/phprest 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 605 次下载、GitHub Stars 达 1, 最近一次更新时间为 2018 年 11 月 08 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「framework」 「rest」 「api」 「restful」 「micro」 「phprest」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 kilylabs/phprest 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 kilylabs/phprest 我们能提供哪些服务?
定制开发 / 二次开发

基于 kilylabs/phprest 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 605
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 1
  • 点击次数: 2
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 1
  • Watchers: 1
  • Forks: 17
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2018-11-08