В сегодняшней статье мы будем писать свой пакет для Laravel.

Пакет для запросов адреса и получения информации по адресу через API сервиса Dadata.

Что будет уметь наш пакет:

- Отправлять адрес на удаленное API и возвращать результат ( Будем использовать  PSR-7  и Guzzle), формат ответа будет выглядеть следующем образом при успехе:

{

 "data": { 
  
"suggestions": [ { 
       
"region": "Москва",
"value": "г Москва, ул Лубянка Б., д 12", "coordinates": { 
 
           "geo_lat": "55.7618518", 
"geo_lon": "37.6284306" } 
}, 
... 
   
] 
  }, 
   
"success": true 
 } 

Или так в случае ошибки:

{

  "data": [ 
{
"code": 1020,
"message": "Access forbidden" 
}, 
... 
], 

  "success": false 
} 

Давайте начинать!

Что будем использовать?

В качеству примера мы будем использовать https://dadata.ru/api/clean/address/ первые 100 записей бесплатно, далее следуя тарифу. Для тестов нам хватит бесплатных записей.

Шаг 1 - Создаем composer.json

 Первым делом нам нужно создать   файл с зависимостями и подключить некоторые из них (например  Guzzle).   Создадим каталог  client  и выполним в нем команду:

 composer init

В итоге в терминале вы увидите что-то типо такого:

Терминал 1

Далее на всех шагах просто нажимайте  Enter   и в итоге мы получим созданный файл   composer.json  примерно со следующим содержимым:

{
    "name": "stanislavboyko/client",
    "authors": [
        {
            "name": "stanislavboyko",
            "email": "myemail@gmail.com"
        }
    ],
    "require": {}
}

Наименование пакета и автора соответственно будут различаться. Давайте теперь добавим несколько директив. Сразу привету итоговую версию данного файла, а потом мы его немного разберём:

{
    "name": "stanislavboyko/client",
    "description": "Laravel Dadadta example client",
    "homepage": "https://stanislavboyko.ru/",
    "type": "library",
    "license": "MIT",
    "authors": [
        {
            "name": "stanislavboyko",
            "email": "mzcoding@gmail.com"
        }
    ],
    "minimum-stability": "dev",
    "autoload": {
        "psr-4": {
            "Stanislavboyko\\Client\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Stanislavboyko\\Client\\Tests\\": "tests/"
        }
    },
    "require": {
    	"php": ">=7.1",
        "ext-curl": "*",
        "ext-json": "*"
    },
    "require-dev": {
        "phpunit/phpunit": "^7.0",
        "vlucas/phpdotenv": "^3.6"
    },
    "extra": {
        "laravel": {
            "providers": [
                "Stanislavboyko\\Client\\Laravel\\ClientServiceProvider"
            ],
            "aliases": {
                "DDataClient": "Stanislavboyko\\Client\\Laravel\\ClientFacade"
            }
        }
    }
}

 

Теперь рассмотрим самые важные директивы данного файла!

 minimum-stability -  Данная директива указывает уровень стабильности проекта, по умолчанию stable.  Другими словами, если в вашей версии пакета будет указан stable, а в вашей версии фреймворка  dev  он не установит этот пакет.

autoload и autoload-dev -  Данные директивы содержат относительный путь от корня пакета к каталогам автозагрузки классов.

 require -  Здесь мы указываем зависимости которые обязательно для работы нашей библиотеки, так-же указываются необходимая версия  PHP  и необходимые расширения.

Так-же мы установим  Guzzle  для запросов.

require-dev -  Здесь указаны зависимости доступные только для разработки, то есть они устанавливаются только в режиме разработки.

Здесь мы подключаем   PhpUnit для тестов и библиотеку vlucas/phpdotenv для работы с  .env  файлами.

extra -  Данная директива служит инструкцией для фреймворка по подключению Провайдера и Фасада пакета.

Теперь создадим каталоги  src  и  tests, а так-же каталог  config. После этого в терминале введем, чтобы установить необходимые зависимости:

 composer install

 

Шаг 2 - создаем базовый клиент

После этого у вас так-же появится каталог  vendor  со всеми зависимостями и так-же добавится файл composer.lock. Давайте сразу в корне нашего пакета создадим файл  .gitignore   и добавим в него несколько строк:

/vendor/
/composer.lock

 

Теперь давайте опишем файл конфигурации, в каталоге  config  создадим  файл  dclient.php и добавим туда следующий код:

<?php
  return [
     'api_key'    => '', //your api key for dadata
     'secret_key' => '',
     'base_url'   => '', 
     'timeout'    => 20,
  ];

 Вы можете зарегистрироваться и добавить свои ключи, при регистрации Дадата зачисляет 10 рублей на счет, этого хватит на 100 запросов.

 

Теперь в каталоге  src  создадим 4 каталога, это  Laravel, Dadata,Contract и каталог Exception.  Теперь создадим наши  СервисПровайдер и Фасад ( файлы  ClientServiceProvider.php  и ClientFacade.php соответственно).

 Сперва опишем наш провайдер  ClientServiceProvider.php:

<?php
namespace Stanislavboyko\Client\Laravel;

use Illuminate\Support\ServiceProvider;
use Stanislavboyko\Client\Dadata\Client;

class ClientServiceProvider extends ServiceProvider
{
	public function __construct($dispatcher = null)
	{
		parent::__construct($dispatcher);
	}

	/**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        $this->publishes([
            __DIR__ . '/../../config/dclient.php' => config_path('mzcoding-client.php'),
        ]);
    }
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
          $this->mergeConfigFrom(
            __DIR__ . '/../../config/dclient.php', 'dclient'
          );
          $this->app->bind(Client::class);
    }
}

 Здесь мы регистрирует конфигурационный файл и наш основной класс клиента ( для  dadadta) его мы опишем немного позже, а пока посмотрим на фасад  ClientFacade.php:

<?php
namespace Stanislavboyko\Client\Laravel;

use Illuminate\Support\Facades\Facade;
use Stanislavboyko\Client\Dadata\Client;

class ClientFacade extends Facade
{
    /**
     * @return string
	 * @see \Stanislavboyko\Client\Client
     */
    protected static function getFacadeAccessor(): string
    {
        return Client::class;
    }
}

Сперва создадим и опишем интерфейс  ClientInterface.php:

<?php
namespace Stanislavboyko\Client\Contract;

use Stanislavboyko\Client\Client;
use Stanislavboyko\Client\Response;

interface ClientInterface
{
    /**
     * @return \GuzzleHttp\Client|null
     */
    function getClient(): ?\GuzzleHttp\Client;
    function clientInit(array $config = []): \GuzzleHttp\Client;

    /**
     * @param array $headers
     * @return Client
     */
    function setHeaders(array $headers = []): Client;

    /**
     * @param $url
     * @param string $method
     * @param array $params
     * @return Response
     */
    function request($url, $method = 'GET', array $params = []): Response;

}

Теперь в каталоге  src  создадим файл  Client.php  это будет наш основной клиент.

<?php
namespace Stanislavboyko\Client;

use Stanislavboyko\Client\Config;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Client as GuzzleClient;

/**
 * @method getReasonPhrase()
 */
class Client
{
	protected $client, $config;
	protected $params, $headers, $auth = [];

    public function __construct(Config $config, bool $init = true)
    {
       $this->config  = $config->get();
       $this->headers = $config->guzzle()['headers'];

       if($init) {
          return $this->clientInit($config->guzzle());
       }
    }
   /**
     * Get current client instance
     *
     * @return null|\GuzzleHttp\Client
     */
    public function getClient(): ?\GuzzleHttp\Client
    {
        return $this->client;
    }

    public function clientInit(array $config = []): self
    {
       $this->client =  new \GuzzleHttp\Client($config);
       return $this;
    }

    /**
     * @param array $headers
     * @return Client
     */
   public function setHeaders(array $headers = []): Client
   {
       $this->headers = array_merge($this->headers, $headers);
       return $this;
   }
    /**
     * @return array
     */
   public function getHeaders(): array
   {
      return $this->headers;
   }
    /**
     * @param array $credentials
     * @return Client
     */
    public function setAuth(array $credentials = []): Client
    {
        $this->auth = $credentials;
        return $this;
    }

    /**
     * @return array
     */
    public function getAuth(): array
    {
        return (array)$this->auth;
    }

	/**
	 * @param string $url
	 * @param string $method
	 * @param array $params
	 * @return Response
	 */
	public function request(string $url, string $method = 'GET', array $params = []): Response
    {
       $options = [];
       $method = strtoupper($method);
       if(isset($params[0])) {
       	  $options['json'] = [$params[0]];
       	  unset($params[0]);
	   }
       if( $this->auth ) {
           $options['auth'] = $this->auth;
       }
       if((bool)$this->config['debug']) {
           $options['debug'] = (bool)$this->config['debug'];
       }

       $response = $this->client->request($method, $this->config['base_url'] . "/" . $url, $options);
       return new Response($response);
    }

}

Теперь опишем файлы Config и Response также находящиеся в каталоге src.

<?php
   namespace Stanislavboyko\Client;

  use Stanislavboyko\Client\Exception;
  use Stanislavboyko\Client\Exception\ClientException;

  class Config
  {
      protected $params = [];

      public function __construct(array $parameters = [])
	  {
      	 $this->params = $parameters;
	  }

      public function set(array $parameters): void
	  {
	  	 $this->params = array_merge($this->params, $parameters);
	  }

	  public function get(): array
	  {
	  	return $this->params;
	  }

	  public function first(string $name)
	  {
	  	if(!isset($this->params[$name])) {
	  		if($name === 'track_redirects') {
	  			return false;
			}
			if($name === 'user_agent') {
				return 'testing/1.0';
			}
	  		throw new ClientException('Parameter ' . $name . ' not found');
		}

	  	return $this->params[$name];
	  }

	  public function guzzle(): array
	  {
		  $options = [
			  'base_uri'        => $this->first('base_url'),
			  'timeout'         => $this->first('timeout'),
			  'track_redirects' => $this->first('track_redirects'),
			  'debug'           => $this->first('debug'),
			  'headers'         => [
				  'User-Agent' => $this->first('user_agent'),
				  'Content-Type' => "application/json",
				  "Accept" => "application/json",
				  "Authorization" => "Token " . $this->first('api_key'),
				  "X-Secret"  => $this->first('secret_key')

			  ]
		  ];


		  return $options;
	  }
  }

И так-же файл Response.php

 

<?php

namespace Stanislavboyko\Client;

use GuzzleHttp\Psr7\Response as GuzzleResponse;
use Stanislavboyko\Client\Contract\ResponseInterface;


class Response implements ResponseInterface
{
    private $response;

    /**
     * Response constructor.
     * @param Response $response
     */
    public function __construct(GuzzleResponse $response)
    {
        $this->response = $response;
    }


    /**
     * @return \App\Classes\Client\Guzzle\Response
     */
    public function get(): GuzzleResponse
    {
        return $this->response;
    }

	/**
	 * @return mixed
	 */
	public function getBody()
	{
		 return json_decode($this->get()->getBody(), true);
	}
}

В заключении нам осталось описать файл который непосредственно взаимодействует с Dadata в каталоге src который содержит файл Dadata/Client.php со следующим содержимым:

<?php
namespace Stanislavboyko\Client\Dadata;

use GuzzleHttp\Exception\ClientException;
use Stanislavboyko\Client\Config;
use Stanislavboyko\Client\Client as BaseClient;

class Client 
{
	/**
	 * Return Dadata API client object
	 *
	 * @param string $url
	 * @param string $method
	 * @param array $params
	 *
	 * @return BaseClient
	 */
    public function getClient(string $url, string $method, array $params = []): BaseClient
    {
        $parameters = config('dclient');
        if(!$parameters) {
        	 $parameters = include __DIR__ . '/../../config/dclient.php';
		}

        $config     = new Config($parameters);
        $response   = new BaseClient($config);
        $res = $response->request($url, $method, $params);
        dd($res->getBody());
        $code = $response->getStatusCode();
        if($code !== 200 || $code !== 201) {
            throw new ClientException("Error exception: " . $code);
        }

        if ($code !== 429) {
            throw new ClientException($code . ' ' . $response->getReasonPhrase());
        }

        return $response;
    }
	 
}

На этом все, теперь мы можем вызывать наш клиент через фасад:

Route::get('/', function () {
     $client = DDataClient::getClient('clean/address', 'POST', ["мск суханка 24"]);
     dd($client);
});

Весь код пакеты вы можете посмотреть и скачать по адресу: https://gitlab.com/mzcoding/client




Поддержать автора