Подход к архитектуре парсера почтовых отправлений.

Обсуждаем, как правильно строить приложения
Ответить
Stasgar
Сообщения: 77
Зарегистрирован: 2016.07.15, 14:08

Подход к архитектуре парсера почтовых отправлений.

Сообщение Stasgar »

Написал модуль для парсинга почтовых отправлений трёх почтовых служб - Почты Финляндии, почты Сингапура и почты Нидерландов.
Модуль представляет из себя набор классов:
- AbstractPostService.php - абстрактный класс, который наследуется парсером каждой службы (ServiceFI.php, ServiceNL.php и ServiceSG.php)
- ParseHelper.php - класс, в котором реализовано 2 метода для отправления curl запроса, и возвращения ответа - parse_post() и parse_get()
- SearchTrack.php - непосредственно класс, с которым работает модель трекинга
- ServiceFI.php, ServiceNL.php и ServiceSG.php - классы, характеризующие алгоритмы парсинга каждого сервиса, наследуются от AbstractPostService.php

Дальше приведу код:

AbstractPostService.php:
(метод isAvailable() публичный, чтобы была возможность реализовать простое правило валидации в модели)

Код: Выделить всё

<?php
namespace app\modules\tracking\classes;
use Yii;

abstract class AbstractPostService
{
    /*
        Абстрактный метод, возвращаемое значение - массив с данными об отправлении.
    */
    abstract public function getResult($track);

    /*
        Метод для перевода статуса, используется google api.
    */
    public static function translateText($text)
    {

        if(Yii::$app->params['googleTranslate']['active'] == false)
        {
            return '-';
        }

        $text = rawurlencode($text);
        $apiKey = Yii::$app->params['googleTranslate']['key'];
        $apiLink = 'https://www.googleapis.com/language/translate/v2?key='.$apiKey.'&q='.$text.'&source=en&target=ru';

        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $apiLink);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER,true);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        $rawResponse = curl_exec($curl);
        curl_close($curl);

        $response = json_decode($rawResponse);

        if(isset($response->data->translations[0]->translatedText))
        {
            return $response->data->translations[0]->translatedText;
        }
        else
        {
            return '-';
        }

    }

}
ParseHelper.php:

Код: Выделить всё

<?php
namespace app\modules\tracking\classes;

/*
    Класс-помощник, предоставляет упрощенный интерфейс парсинга страницы.
*/
class ParseHelper
{
    //Метод для парсинга пост-запросом
    public static function parse_post($url, $params){
        if( $curl = curl_init() ) {
            curl_setopt($curl, CURLOPT_URL, $url);
            curl_setopt($curl, CURLOPT_RETURNTRANSFER,true);
            curl_setopt($curl, CURLOPT_POST, true);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $params);
            $out = curl_exec($curl);
            curl_close($curl);
            return $out;
        }
    }

    //Метод для парсинга гет-запросом
    public static function parse_get($url){
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER,true);
        $out = curl_exec($curl);
        curl_close($curl);
        return $out;
    }
}
SearchTrack.php:

Код: Выделить всё

<?php
namespace app\modules\tracking\classes;

use Yii;

/*
    Используется для получения информации о почтовом управлении.
*/
class SearchTrack
{
    // непосредственно текстовое значение "трек-номера"
    private $track_number;

    public function __construct($track_number)
    {
        $this->track_number = $track_number;
    }

    /*
        Метод проверяет существует ли стратегия для данного типа трек-номера.
    */
    public function isAvailable()
    {
        $type = $this->getType(); // get type

        if(!class_exists($this->getServiceName())) //check if service exists
        {
            return false;
        }

        return true;
    }

    /**
    * Метод, используется для поиска информации по предоставленному трек-номеру.
    * Используется идентификатор отпарвления (track_number), переданный в конструкторе.
    * Результат возвращается в виде массива PHP 
    */
    public function getParcelInfo()
    {

        if($this->isAvailable())
        {
            $serviceName = $this->getServiceName();
            $service = new $serviceName();
            $result = $this->getArrayInfo($service);
            return $result;
        }

        return false;
        
    }

    /*
        Метод для непосредственного поиска информации об отправлении по предоставленному алгоритму
    */
    private function getArrayInfo(AbstractPostService $service)
    {
        return $service->getResult($this->track_number);
    }

    /*
        Метод для определения типа отправления.
        Может быть расширен при добавлении нового типа трека.
        На данный момент тип получается путем получения двух последних символов:
        ***FI - тип "FI" и т.д.
    */
    private function getType()
    {
        //return 2 last characters of the track number
        return substr($this->track_number, strlen($this->track_number)-2, 2);
    }

    /*
        Метод для получения полного имени класса сервиса (алгоритма).
    */
    private function getServiceName()
    {
        $type = $this->getType();
        $serviceName = '\Service'.$type;
        $serviceModel = __NAMESPACE__.$serviceName;
        return $serviceModel;
    }

}
Классы ServiceXX.php в таком виде:

Код: Выделить всё

<?php
namespace app\modules\tracking\classes;

use app\modules\tracking\classes\AbstractPostService;
use app\modules\tracking\classes\ParseHelper;

/*
    Класс-алгоритм, используемый для поиска информации для треков с типом "FI"(финляндия)
*/
class ServiceXX extends AbstractPostService
{
    public function getResult($track)
    {
        //код непосредственно парсинга и формирование массива
        return $result;
    }
}
Используется данная система в модели таким образом:

Код: Выделить всё

...
$searchTrack = new SearchTrack($this->trackNumber);
$searchResults = $searchTrack->getParcelInfo();
...
Самое явное преимущество - для добавления нового типа трек-номера нам нужно создать лишь новый сервис.
Мне на данный момент не очень нравится переводчик статусов хранить в сервиске, но с другой стороны это лишь одна функция, и создавать отдельный класс для нее было-бы излишним.

Что скажите насчет такого подхода? И можно ли данный подход назвать фабрикой?
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Подход к архитектуре парсера почтовых отправлений.

Сообщение ElisDN »

Вы делаете все запросы через Curl. Инкапсулируем весь Curl в объект для запросов:

Код: Выделить всё

class HttpClient
{
    function get($url)
    function post($url, array $params)
}
Или вместо него подключим yii2-httpclient.

Через него будет работать переводчик:

Код: Выделить всё

class Translator
{
    function __construct(HttpClient $client, $apiKey)
    function translate($text)
}
Далее сделаем компонент трекера. Ему понадобится некий определитель сервиса по типу:

Код: Выделить всё

class ServiceResolver
{
    function __construct(\yii\di\Container $container)
    function getByNumber(Number $number) {
        return $this->container->get(__NAMESPACE__ . '\services\Service' .  $number->getType());
    }
}
на основе которого трекер будет выбирать сервис и запрашивать данные:

Код: Выделить всё

class Tracker
{
    function __construct(ServiceResolver $services)
    function track(Number $number) {
        return $this->services->getByNumber($number)->getInfo($number);
    }
}
От сервисов понадобится всего один метод:

Код: Выделить всё

interface ServiceInterface
{
    function getInfo($number);
}

class ServiceNL
{
    function __construct(HttpClient $client, Translator $translator)
    function getInfo($number) {
        $data = $this->client->post(...);
        return new TrackInfo(...);
    }
}
После регистрации классов в DI-контейнере используем:

Код: Выделить всё

$info = $this->tracker->track(new Number('xxx'));
Stasgar
Сообщения: 77
Зарегистрирован: 2016.07.15, 14:08

Re: Подход к архитектуре парсера почтовых отправлений.

Сообщение Stasgar »

Благодарю. Сделал не совсем как вы сказали, но посыл понял.
Ответить