restful 使用对象_使用ProxyManager的RESTful远程对象代理

restful 使用对象

This article was peer reviewed by Deji Akala and Marco Pivetta. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

本文由Deji AkalaMarco Pivetta进行同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!

The proxy pattern is another cool design pattern in software development. A proxy is a class working as an interface to another class or web service. For the sake of simplicity, we’ll refer to proxied classes as subjects throughout the rest of the article. A proxy usually implements the same interface as the subject, so it looks like we’re calling the methods directly on the subject.

代理模式是软件开发中的另一个很酷的设计模式。 代理是一个类,用作与另一个类或Web服务的接口 。 为了简单起见,在本文的其余部分中,我们将代理类称为主题 。 代理通常实现与主题相同的接口,因此看起来我们正在直接在主题上调用方法。

Although this article is not about the proxy pattern concept, we’ll discuss some basics, just to get started.

尽管本文不是关于代理模式概念的,但我们将讨论一些基础知识,只是作为入门。

There are a variety of proxy types used for different purposes:

有多种用于不同目的的代理类型:

  1. Virtual Proxy – for lazily loading the resource-hungry objects, preventing them from occupying memory until they are needed.

    虚拟代理 –用于延迟加载资源匮乏的对象,防止它们在需要它们之前占用内存。

  2. Protection Proxy – for limiting access to properties or methods of an object through a set of rules.

    保护代理 –用于通过一组规则限制对对象的属性或方法的访问。

  3. Smart Reference – for adding additional behaviors when a method is called on the subject – suitable for aspect-oriented programming.

    智能参考 -用于在主题上调用方法时添加其他行为-适用于面向方面的编程。

  4. Remote Objects – for accessing remote objects, hiding the fact that they are actually in a separate address space. This article will be mainly covering remote object proxies.

    远程对象 –用于访问远程对象,隐藏它们实际上位于单独的地址空间中的事实。 本文将主要介绍远程对象代理。

Let’s get started with a basic example of a virtual proxy:

让我们从虚拟代理的基本示例开始:

class MyObjectProxy
{
    protected $subject;

    public function someMethod()
    {
        $this->init();

        return $this->subject->someMethod();
    }

    private function init()
    {
        if (null === $this->subject) {
            $this->subject = new Subject();
        }
    }
}

In the above code, we’re keeping a reference to the subject; each time a method is called against the proxy, init() is invoked, checking whether the subject has been instantiated yet, and if not, it will instantiate it.

在上面的代码中,我们保留了对该主题的引用; 每次针对代理调用方法时,都会调用init() ,以检查主题是否已实例化,如果尚未实例化,它将对其进行实例化。

Finally, the respective method on the subject is called:

最后,关于该主题的相应方法称为:

// ...
return $this->subject->someMethod();
// ...

This is a very basic example of a value holder virtual proxy, which keeps a reference to the subject all the times. Although the previous example works just fine, this is not how we create proxies in this world. Instead, we’ll do what any other good developer would do: use a well-known, well-tested third-party library.

这是价值持有者虚拟代理的非常基本的示例,该代理始终保持对主题的引用。 尽管前面的示例很好用,但这不是我们在此世界上创建代理的方式。 相反,我们将做任何其他优秀的开发人员都将做的事情:使用一个知名的,经过测试的第三方库。

ProxyManager is a PHP library for creating various kinds of proxies through a set of factory classes.

ProxyManager是一个PHP库,用于通过一组工厂类创建各种代理。

Marco Pivetta, the author of ProxyManager, has a comprehensive presentation, which is an easy-to-understand introduction to the Proxy pattern. I also recommend taking a quick look at the official documentation of ProxyManager before we start.

ProxyManager的作者Marco Pivetta进行了全面的介绍 ,这是对代理模式的易于理解的介绍。 我还建议在开始之前快速浏览ProxyManager的正式文档

切入正题 (Cutting to the Chase)

This post is dedicated to one of the less-discussed proxy types known as remote object proxies. We will learn what they are and how they work. Then, we’ll move on to creating a remote object proxy, which is capable of interacting with a RESTful API.

这篇文章专门讨论一种较少讨论的代理类型,称为远程对象代理 。 我们将学习它们是什么以及它们如何工作。 然后,我们将继续创建一个能够与RESTful API进行交互的远程对象代理。

Simply put, remote object proxies are used to interact with remote subjects (using HTTP as the transport mechanism) and disguise them as local objects!

简而言之,远程对象代理用于与远程主题进行交互(使用HTTP作为传输机制)并将其伪装成本地对象!

Whenever a method is called against the proxy object, it tries to map the called method name to its remote counterpart. Then, it encodes and forwards the request to the remote method. All this happens in a matter of milliseconds.

每当针对代理对象调用方法时 ,它都会尝试将被调用的方法名称映射到其远程对应对象 。 然后,它将请求编码并转发到远程方法。 所有这一切仅需几毫秒即可完成。

We create our remote object proxies by using the RemoteObjectFactory class of ProxyManager. This factory requires an adapter to create a remote object proxy, capable of interacting with a certain remote subject. On the other hand, the adapter is responsible for transforming a request in a way that the remote subject can handle.

我们使用ProxyManager的RemoteObjectFactory类创建远程对象代理。 该工厂需要适配器来创建能够与某个远程主题进行交互的远程对象代理。 另一方面,适配器负责以远程主体可以处理的方式转换请求。

The adapters vary based on the implemented protocol by the remote subject; whether the subject is XML-RPC based, SOAP based or even a RESTful JSON API, the appropriate adapter should be used.

适配器根据远程主体实现的协议而有所不同。 无论主题是基于XML-RPC,基于SOAP还是RESTful JSON API,都应使用适当的适配器。

The code which follows will be using ProxyManager’s RemoteObjectFactory.

接下来的代码将使用ProxyManager的RemoteObjectFactory

Currently, ProxyManager provides three adapters out of the box: XmlRpc, JsonRpc, and Soap. These adapters use an implementation of the Zend\Server\Client interface as a transporting mechanism.

目前,ProxyManager提供了三个适配器开箱: XmlRpcJsonRpcSoap 。 这些适配器将Zend\Server\Client接口的实现用作传输机制。

To see examples of how it’s done, the official documentation is the best place.

要查看示例,请参阅官方文档 ,这是最好的地方。

In the next section, we will create what is lacking at the moment: a custom adapter suitable for RESTful APIs!

在下一节中,我们将创建当前缺少的东西:适用于RESTful API的自定义适配器!

制备 (Preparation)

First, we’ll create a dummy JSON API as our remote subject – using Silex as our framework. Then, we’ll install ProxyManager to create a proxy for it.

首先,我们将使用Silex作为框架,将虚拟JSON API创建为我们的远程主题。 然后,我们将安装ProxyManager为其创建代理。

Since ProxyManager doesn’t provide an adapter for RESTful APIs, we need to create our own. Our custom adapter will use Guzzle as its HTTP client.

由于ProxyManager不为RESTful API提供适配器,因此我们需要创建自己的适配器。 我们的自定义适配器将使用Guzzle作为其HTTP客户端。

Let’s start off by creating a directory to keep our files in:

让我们从创建目录开始,以将文件保留在其中:

mkdir rest-proxy && cd rest-proxy

Now, we install Silex, ProxyManager, and Guzzle (for now):

现在,我们安装Silex,ProxyManager和Guzzle(目前):

composer require silex/silex ocramius/proxy-manager guzzlehttp/guzzle

目录结构 (Directory Structure)

We will implement the test API and the proxy-related code in the same project, pretending that one is local and the other one is remote. For the sake of readability, we put these two components under different namespaces.

我们将在一个项目中实现测试API和与代理相关的代码,并假装一个是本地的,另一个是远程的。 为了便于阅读,我们将这两个组件放在不同的名称空间下。

That said, we need to change the autoload key of our composer.json file:

也就是说,我们需要更改composer.json文件的autoload键:

Filename: composer.json

文件名: composer.json

{
    ...

    "autoload": {
        "psr-4": {
            "": "src/"
        }
    }
}

Then we run composer dump-autoload.

然后我们运行composer dump-autoload

As a result, we can have two different namespaces, mapped to two different directories within the src/ directory. Let’s name them RemoteProxy and API respectively. In addition, we create another directory named web in the project’s root directory to keep our index.php file. The directory structure should look like this now:

结果,我们可以有两个不同的名称空间,它们映射到src/目录中的两个不同的目录。 我们分别命名为RemoteProxyAPI 。 另外,我们在项目的根目录中创建另一个名为web的目录,以保留index.php文件。 现在,目录结构应如下所示:

remote-proxy
├── src
│   ├── Api
│   └── RemoteProxy
└── web

创建API (Creating the API)

The API is going to have three endpoints for returning a list of books, details of a book, and authors of a book.

该API将具有三个端点,用于返回书籍列表,书籍的详细信息以及书籍的作者。

To do this, we create a Silex controller provider under src/Api/Controller as BookControllerProvider.php:

为此,我们在src/Api/Controller下创建一个Silex控制器提供程序,作为BookControllerProvider.php

To see how Silex controller providers work, you might want to take a look at this page.

要了解Silex控制器提供程序的工作方式,您可能需要看看此页面

Filename: src/Api/Controller/ApiControllerProvider.php

文件名: src/Api/Controller/ApiControllerProvider.php

<?php

namespace Api\Controller;

use Silex\Application;
use Silex\Api\ControllerProviderInterface;
use Symfony\Component\HttpFoundation\JsonResponse as Json;

class ApiControllerProvider implements ControllerProviderInterface
{

    public function connect(Application $app)
    {
        $controllers = $app['controllers_factory'];

        /**
         * Return a list of the books
         */
        $controllers->get('books', function (Application $app) {

            return new Json([
                'books' => 'List of books...'
            ]);
        });

        /**
         * Return book details by Id
         */
        $controllers->get('books/{id}', function (Application $app, $id) {

            return new Json([
                'details' => 'Details of book with id ' . $id,
            ]);
        });

        /**
         * Return the author(s) of the book
         */
        $controllers->get('books/{id}/authors', function (Application $app, $id) {

            return new Json([
                'authors' => 'Authors of book with id ' . $id,
            ]);
        });

        return $controllers;
    }
}

We have three routes with controllers, each of which returns an instance of JsonResponse with a dummy message as content.

我们有三个带有控制器的路由,每个路由都返回一个JsonResponse实例,其中包含一条虚拟消息。

创建应用程序 (Creating the Application)

Next, we create our src/app.php file, where we create the application and mount our controller provider:

接下来,我们创建src/app.php文件,在其中创建应用程序并挂载控制器提供程序:

Filename: src/app.php

文件名: src/app.php

<?php

use Silex\Application;
use Api\Controller\ApiControllerProvider;

$app = new Application();
$app->mount('/api/v1', new ApiControllerProvider());

return $app;

前控制器 (Front Controller)

Finally, we create the front controller, which will be the entry point to our API. We save this file as web/index.php with the following content:

最后,我们创建前端控制器,它将成为我们API的入口点。 我们将此文件另存为web/index.php ,内容如下:

Filename: web/index.php

档案名称: web/index.php

<?php
require_once __DIR__.'/../vendor/autoload.php'; 
$app = require __DIR__ . '/../src/app.php';
$app->run();

In the above code, we include the composer autoloader and our app.php file (which returns a Silex application object). Finally we run the application by calling the run() method.

在上面的代码中,我们包括了作曲家自动加载器和我们的app.php文件(该文件返回一个Silex应用程序对象)。 最后,我们通过调用run()方法来运行应用程序。

To test the code, refer to your environment’s server setup (automatic if using something like Homestead Improved) or for brevity, use PHP’s built-in web server:

要测试代码,请参考您环境的服务器设置(如果使用Homestead Improvement ,则为自动),或者为了简洁起见,请使用PHP的内置Web服务器:

php -S localhost:9000 -t web

As a result of the latter option, the API will be accessible under http://localhost:9000/api/v1 from the browser.

由于采用了后者,因此可以从浏览器的http://localhost:9000/api/v1下访问该API。

创建代理 (Creating the Proxy)

Now that we have everything set up on the “remote” side, it is time to create some proxies!

既然我们已经在“远程”方面进行了所有设置,那么现在该创建一些代理了!

代理适配器 (The Proxy Adapter)

The adapters in ProxyManager extend the abstract class BaseAdapter, which implements AdapterInterface.

在ProxyManager适配器扩展抽象类BaseAdapter ,它实现AdapterInterface

All adapters accept an HTTP client object (via their constructor) for interacting with the remote subject, along with an array, which contains method-to-endpoint mapping information. By using this array, the adapter will know to which remote endpoint it should forward the request.

所有适配器(通过其构造函数)都接受用于与远程主题进行交互的HTTP客户端对象,以及一个包含方法到端点映射信息的array 。 通过使用此数组,适配器将知道它将请求转发到哪个远程端点。

In our case, the array looks like this:

在我们的例子中,数组如下所示:

<?php
// ...

$array = [
    'getBooks'   => ['path' => 'books',             'method' => 'get'],
    'getBook'    => ['path' => 'books/:id',         'method' => 'get'],
    'getAuthors' => ['path' => 'books/:id/authors', 'method' => 'get'],
];

// ...

Each key is a method name (on the proxy object) and the value (which is an array) has the remote path with the method to access it. We’ll see how this array is used later.

每个键都是方法名称(在代理对象上),值(是数组)具有用于访问它的方法的远程路径 。 稍后我们将了解如何使用此数组。

For creating our adapter, unlike other adapters, we won’t extend the BaseAdapter abstract class. Instead, we’ll create it from scratch. Why do this? Because the abstract class forces us to use an implementation of Zend\Server\Client as our HTTP client, whereas we want to use Guzzle.

与其他适配器不同,在创建适配器时,我们不会扩展BaseAdapter抽象类。 相反,我们将从头开始创建它。 为什么这样 因为抽象类迫使我们使用Zend\Server\Client作为HTTP客户端,而我们想使用Guzzle。

The adapters (implementing AdapterInterface) must implement call(), which is invoked by the proxy whenever a method is called. This method has three arguments: the proxied class itself, the called method’s name, and the called method’s arguments.

适配器(实现AdapterInterface )必须实现call() ,只要方法被调用,代理就会调用该call() 。 此方法具有三个参数:代理类本身,被调用方法的名称和被调用方法的参数。

Here’s the code of our RESTful adapter:

这是我们的RESTful适配器的代码:

Filename: src/RemoteProxy/Adapter/RestAdapter.php

文件名: src/RemoteProxy/Adapter/RestAdapter.php

<?php

namespace RemoteProxy\Adapter;

use GuzzleHttp\ClientInterface;
use ProxyManager\Factory\RemoteObject\AdapterInterface;

class RestAdapter implements AdapterInterface
{

    /**
     * Adapter client
     *
     * @var GuzzleHttp\ClientInterface
     */
    protected $client;

    /**
     * Mapping information
     *
     * @var array
     */
    protected $map;

    /**
     * Constructor
     *
     * @param Client $client
     * @param array  $map    Map of service names to their aliases
     */
    public function __construct(ClientInterface $client, $map = [])
    {
        $this->client  = $client;
        $this->map     = $map;
    }

    /**
     * {@inheritDoc}
     */
    public function call($wrappedClass, $method, array $parameters = [])
    {
        if (!isset($this->map[$method])) {
            throw new \RuntimeException('No endpoint has been mapped to this method.');
        }

        $endpoint =  $this->map[$method];
        $path     = $this->compilePath($endpoint['path'], $parameters);

        $response = $this->client->request($endpoint['method'], $path);

        return (string) $response->getBody();
    }

    /**
     * Compile URL with its values
     *
     * @param string $path
     * @param array $parameters
     *
     * @return string
     */
    protected function compilePath($path, $parameters)
    {
        return preg_replace_callback('|:\w+|', function ($matches) use (&$parameters) {
            return array_shift($parameters);
        }, $path);
    }
}

In the preceding the code, first, we set the properties $map and $client in the class constructor. Inside the call() method, we check if the called method name exists in the $map property; if it does, we call compilePath(), putting the called method’s arguments into their respective placeholders in the URL.

在前面的代码中,首先,我们在类构造函数中设置$map$client属性。 在call()方法内部,我们检查被调用方法的名称是否存在于$map属性中; 如果是这样,我们调用compilePath() ,将被调用方法的参数放入URL中它们各自的占位符。

For example, if we call getBook(12), the URL could be something like http://localhost:9000/api/v1/book/12.

例如,如果我们调用getBook(12) ,则URL可能类似于http://localhost:9000/api/v1/book/12

Finally, we send a request according to the specified HTTP verb – using the request() method of our Guzzle client:

最后,我们使用Guzzle客户端的request()方法根据指定的HTTP动词发送请求:

// ...
$response = $this->client->request($endpoint['method'], $path);
// ...

创建代理对象 (Creating the Proxy Object)

Now, any time we need to create a remote object proxy, we create an instance of ProxyManager\Factory\RemoteObjectFactory, passing it an instance of our custom adapter and the mapping array.

现在,每当需要创建远程对象代理时,我们都将创建ProxyManager\Factory\RemoteObjectFactory的实例,并将其传递给自定义适配器映射数组的实例。

This procedure should be repeated anywhere we need to use the proxy.

在我们需要使用代理的任何地方都应重复此过程。

Usage example:

用法示例:

<?php

// ...

use ProxyManager\Factory\RemoteObjectFactory;
use RemoteProxy\Adapter\RestAdapter;

$base_uri = 'http://localhost';
// Creating the factory
$factory = new RemoteObjectFactory(
            // Passing our custom adapter in
            new RestAdapter(
                // Passing our HTTP client to the adapter
                new \GuzzleHttp\Client([
                    'base_uri' => $base_uri,
                ]),
                // Along with the arraym which contain the method-to-endpoint mapping
                $array = [
                  'getBooks'   => ['path' => 'books',             'method' => 'get'],
                  'getBook'    => ['path' => 'books/:id',         'method' => 'get'],
                  'getAuthors' => ['path' => 'books/:id/authors', 'method' => 'get'],
                ];
            )
        );

$factory->createProxy('interfaceName');

// ...

Please note that the base URI that we pass to Guzzle at the instantiation time is the base URI of our API.

请注意,我们在实例化时传递给Guzzle的基本URI是我们API的基本URI。

Now, we create our proxy object by calling createProxy() on the factory. This method requires an interface the methods of which are implemented by the remote subject. That said, we need to have the interface as well. Let’s put this file under the RemoteProxy namespace:

现在,我们通过在工厂上调用createProxy()创建代理对象。 该方法需要一个接口,该接口的方法由远程主体实现。 也就是说,我们还需要具有接口。 让我们将此文件放在RemoteProxy命名空间下:

File: src/RemoteProxy/LibraryInterface.php

文件: src/RemoteProxy/LibraryInterface.php

<?php

namespace RemoteProxy;

interface LibraryInterface
{    
    /**
     * Return the books
     */
    public function getBooks();

    /**
     * Return book's details
     *
     * @param  int $id
     * @return  mixed
     */
    public function getBook($id);

    /**
     * Return author of a book
     *
     * @param  int $id
     * @return mixed
     */
    public function getAuthors($id);
}

Now that we have the interface, we can create the proxy:

现在我们有了接口,我们可以创建代理了:

<?php

// ...

$library = $factory->createProxy(RemoteProxy\LibraryInterface::class);

// calling the methods:

var_dump($library->getBooks());
var_dump($library->getAuthors(1));

// ...

通过接口保留映射信息 (Keeping the Mapping Information with the Interface)

Although we can prepare the mapping array at the time of instantiating the adapter, there’s a cleaner way to do it. We define the endpoints with each method in the interface, by using a custom annotation.

尽管我们可以在实例化适配器时准备映射数组,但是有一种更简洁的方法。 我们通过使用自定义注释在接口中使用每种方法定义端点。

Something like:

就像是:

<?php

interface LibraryInterface
{
    /**
     * Return all books
     * @Endpoint(path="/books", method="get")
     */
    public function getBooks();

    // ...
}

A custom annotation named Endpoint has been used, specifying a path and method to access it.

使用了名为Endpoint的自定义注释,该注释指定了访问它的路径和方法。

Creating a custom annotation is a very simple task. Each annotation is a class of the same name, with annotation parameters as its public properties. We parse this information with an annotation parser into an array, which can be sent to our adapter.

创建自定义注释是一个非常简单的任务。 每个注释都是同名的类,其注释参数为其公共属性。 我们使用注释解析器将此信息解析为一个数组,该数组可以发送到我们的适配器。

To create and parse the annotation we use the Doctrine Annotations library.

为了创建和解析注释,我们使用了Doctrine Annotations库。

composer require doctrine/annotations

Each annotation class should be annotated with @Annotation, so Doctrine’s annotation parser will know it’s a valid annotation class.

每个注释类都应使用@Annotation进行注释,因此Doctrine的注释解析器将知道它是有效的注释类。

Let’s put the annotation class inside src/RemoteProxy/Annotation, so we can have it under the Annotation namespace:

让我们将注释类放在src/RemoteProxy/Annotation ,这样我们就可以将其放在Annotation命名空间下:

File: src/RemoteProxy/Annotation/Endpoint.php

文件: src/RemoteProxy/Annotation/Endpoint.php

<?php

namespace RemoteProxy\Annotation;

/**
 * @Annotation
 */
class Endpoint
{
    public $path;
    public $method;

    public function __construct($parameters)
    {
        $this->path = $parameters['path'];
        $this->method = isset($parameters['method']) ? $parameters['method'] : 'get';
    }
}

In the above code, we create a property for each parameter of the annotation.

在上面的代码中,我们为注释的每个参数创建一个属性。

In our case, these are $path and $method. In the constructor, we receive the annotation parameters as an associative array ($parameters). Finally, we assign the elements of $parameters to their respective properties in the class.

在我们的例子中,它们是$path$method 。 在构造函数中,我们将注释参数作为关联数组( $parameters )接收。 最后,我们将$parameters的元素分配给类中它们各自的属性。

Now, we update the interface:

现在,我们更新接口:

Filename: src/RemoteProxy/LibraryInterface.php

文件名: src/RemoteProxy/LibraryInterface.php

<?php

namespace RemoteProxy;

use RemoteProxy\Annotation\Endpoint;

interface LibraryInterface
{    
    /**
     * Return all books
     *
     * @Endpoint(path="books")
     */
    public function getBooks();

    /**
     * Return book's details
     *
     *
     * @Endpoint(path="books/:id")
     * @param  int $id
     * @return mixed
     */
    public function getBook($id);

    /**
     * Return author of a book
     *
     * @Endpoint(path="books/:id/authors")
     * @param  int $id
     * @return mixed
     */
    public function getAuthors($id);
}

So far, we have the annotation and the interface, but we should parse and pass them to our adapter, which expects an array of mapping information.

到目前为止,我们已经有了注释和接口,但是我们应该解析它们并将其传递给我们的适配器,该适配器需要一个映射信息数组。

To do this, we create a helper class which takes the interface, extracts the information with the help of PHP’s Reflection API and Doctrine’s annotation parser, and returns the result as an array. We can then pass this array to our adapter – just like the array we hard coded in the previous example.

为此,我们创建一个使用接口的帮助程序类,在PHP的Reflection API和Doctrine的注释解析器的帮助下提取信息,并将结果作为数组返回。 然后,我们可以将该数组传递给适配器-就像在上一个示例中经过硬编码的数组一样。

Let’s name the class UriResolver:

让我们将类命名为UriResolver

Filename: src/RemoteProxy/UriResolver.php

文件名: src/RemoteProxy/UriResolver.php

<?php

namespace RemoteProxy;

use Doctrine\Common\Annotations\AnnotationReader;
use RemoteProxy\Annotation\Endpoint;

class UriResolver
{

    /**
     * Annotation reader instance
     *
     * @var Doctrine\Common\Annotation\AnnotationReader
     */
    protected $annotationReader;

    /**
     * Instantiate the URI resolver
     */
    public function __construct()
    {
        $this->annotationReader = new AnnotationReader();
    }

    /**
     * Return an array of mapping information
     *
     * @param string $interface
     *
     * @return array
     */
    public function getMappings($interface)
    {

        $mappings   = [];
        $methods    = (new \ReflectionClass($interface))->getMethods();

        foreach ($methods as $method) {
            $annotations = $this->annotationReader->getMethodAnnotations($method);
            foreach ($annotations as $annotation) {
                if ($annotation instanceof Endpoint) {
                    $mappings[$method->name] = ['path' => $annotation->path, 'method' => $annotation->method];
                }
            }
        }
        return $mappings;
    }
}

In the above code, first, we instantiate Doctrine\Common\Annotation\AnnotationReader and store it in the $annotationReader property.

在上面的代码中,首先,我们实例化Doctrine\Common\Annotation\AnnotationReader并将其存储在$annotationReader属性中。

In getMappings(), we get all the interface’s methods using the reflection API. Then, we iterate over them to parse the DocBlocks. On each iteration, we check if the method has the Endpoint annotation. If it does, we add a new element to the $mappings array, with the method name as the key, and path and method as values.

getMappings() ,我们使用反射API获取所有接口的方法。 然后,我们遍历它们以解析DocBlock。 在每次迭代中,我们检查该方法是否具有Endpoint注释。 如果是这样,我们将一个新元素添加到$mappings数组中,方法名称为键, pathmethod为值。

This is how we use this new class:

这就是我们使用这个新类的方式:

Usage example:

用法示例:

<?php

// ...

use ProxyManager\Factory\RemoteObjectFactory;
use RemoteProxy\Adapter\RestAdapter;

$base_uri = 'http://localhost';
AnnotationRegistry::registerLoader('class_exists');
$factory = new RemoteObjectFactory(
            new RestAdapter(
                new \GuzzleHttp\Client([
                    'base_uri' => $base_uri,
                ]),
                // Array
                (new UriResolver())->getMappings(RemoteProxy\LibraryInterface::class)
            )
        );
// ...

You might be wondering about the call to AnnotationRegistry::registerLoader('class_exists'). Since Doctrine annotations are not loaded by the defined PHP autoloaders, we need to use Doctrine’s built-in silent autoloading mechanism:

您可能想知道对AnnotationRegistry::registerLoader('class_exists')的调用。 由于已定义PHP自动加载器未加载Doctrine批注,因此我们需要使用Doctrine的内置静默自动加载机制

<?php
AnnotationRegistry::registerLoader('class_exists');

建立特殊工厂 (Creating a Special Factory)

Every time we want to create a remote object proxy in our code, we need to repeat the above steps which, of course, is inconvenient. In addition, it results in plenty of instantiations across our controllers. As a solution, we abstract the above procedure into a convenient factory class.

每次我们想在代码中创建一个远程对象代理时,我们都需要重复上述步骤,这当然很不方便。 此外,它会在我们的控制器上产生大量实例化。 作为解决方案,我们将上述过程抽象为方便的工厂类。

We name it RestProxyFactory under the src/RemoteProxy directory:

我们在src/RemoteProxy目录下将其RestProxyFactorysrc/RemoteProxy

Filename: src/RemoteProxy/RestProxyFactory.php

文件名src/RemoteProxy/RestProxyFactory.php

<?php

namespace RemoteProxy;

use Doctrine\Common\Annotations\AnnotationRegistry;
use RemoteProxy\Adapter\RestAdapter;

class RestProxyFactory
{
    public static function create($interface, $base_uri)
    {
        // Registering a silent autoloader for the annotation
        AnnotationRegistry::registerLoader('class_exists');

        $factory = new \ProxyManager\Factory\RemoteObjectFactory(
            new RestAdapter(
                new \GuzzleHttp\Client([
                    'base_uri' => rtrim($base_uri, '/') . '/',
                ]),
                (new UriResolver())->getMappings($interface)
            )
        );

        return $factory->createProxy($interface);
    }
}

In the above class, we accept an interface and the API’s base URL (we need this to instantiate Guzzle). Then, we put all the previous instantiation steps in there.

在上面的类中,我们接受一个接口和API的基本URL (我们需要用它来实例化Guzzle)。 然后,我们将所有先前的实例化步骤放入其中。

用法 (Usage)

Now, we can easily create a REST-friendly remote object proxy at any time by using our special factory class.

现在,我们可以随时使用我们的特殊工厂类轻松创建一个REST友好的远程对象代理。

To test the proxy, let’s define a route in our src/app.php file:

为了测试代理,让我们在src/app.php文件中定义一条路由:

<?php
// src/app.php
// ...

$app->get('test-proxy', function (Application $app) {            

    $proxy = new RestProxyFactory(LibraryInterface::class, 'localhost:9000/api/v1');
    return new JsonResponse([
        'books' => $proxy->getBooks(),
    ]);
});

// ...

In the above code, invoking getBooks() against $proxy, will make an HTTP request to http://localhost:9000/api/v1/books. As a result, we should get the following response:

在上面的代码中,对$proxy调用getBooks()会向http://localhost:9000/api/v1/books发出HTTP请求。 结果,我们应该得到以下响应:

{
resp: "{"books":"List of books..."}"
}

That’s all there is to it. I hope you enjoyed this article. The complete code is available on Github if you want to try it yourself.

这里的所有都是它的。 希望您喜欢这篇文章。 如果您想自己尝试,可以在Github上找到完整的代码。

If you just want to use it in your project, you can install it with composer:

如果只想在项目中使用它,则可以使用composer安装它:

composer require lavary/rest-remote-proxy

结语 (Wrapping Up)

We learned how remote proxies work and how they interact with different remote subjects with different protocols. We also created an adapter suitable for RESTful APIs.

我们了解了远程代理如何工作,以及它们如何与具有不同协议的不同远程主体交互。 我们还创建了适用于RESTful API的适配器。

By proxying the remote subjects, we can use them without even knowing they are located in a different address space. We just request, and the proxy makes the calls. It is as simple as that.

通过代理远程主题,我们甚至可以在不知道它们位于不同地址空间的情况下使用它们。 我们只要求,代理进行呼叫。 它是如此简单。

How are you using proxies? Do you think this approach might come in handy in some of your own projects?

您如何使用代理? 您认为这种方法在您自己的某些项目中可能会派上用场吗?

翻译自: https://www.sitepoint.com/restful-remote-object-proxies-with-proxymanager/

restful 使用对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值