GinoPane/PHPolyglot

View on GitHub
src/API/Implementation/Dictionary/Yandex/YandexDictionaryApi.php

Summary

Maintainability
B
4 hrs
Test Coverage
<?php

namespace GinoPane\PHPolyglot\API\Implementation\Dictionary\Yandex;

use GinoPane\NanoRest\Request\RequestContext;
use GinoPane\NanoRest\Response\ResponseContextAbstract;
use GinoPane\PHPolyglot\Supplemental\Language\Language;
use GinoPane\PHPolyglot\Exception\InvalidResponseContent;
use GinoPane\NanoRest\Exceptions\RequestContextException;
use GinoPane\PHPolyglot\Exception\BadResponseContextException;
use GinoPane\PHPolyglot\API\Supplemental\Yandex\YandexApiTrait;
use GinoPane\PHPolyglot\API\Response\Dictionary\DictionaryResponse;
use GinoPane\PHPolyglot\API\Response\Dictionary\Entry\DictionaryEntry;
use GinoPane\PHPolyglot\API\Response\Dictionary\Entry\POS\DictionaryEntryPos;
use GinoPane\PHPolyglot\API\Implementation\Dictionary\DictionaryApiAbstract;

/**
 * Class YandexDictionaryApi
 *
 * Yandex Dictionary API implementation
 *
 * API version 1
 *
 * @link   https://tech.yandex.com/dictionary/doc/dg/concepts/api-overview-docpage/
 * @link   https://tech.yandex.com/keys/?service=dict
 *
 * @author Sergey <Gino Pane> Karavay
 */
class YandexDictionaryApi extends DictionaryApiAbstract
{
    /**
     * Family search filter (child-safe)
     */
    const LOOKUP_FAMILY_FLAG = 0x1;

    /**
     * Search by word form
     */
    const LOOKUP_MORPHO_FLAG = 0x4;

    /**
     * Enable a filter that requires matching parts of speech for the search word and translation
     */
    const LOOKUP_POS_FILTER_FLAG = 0x8;

    /**
     * URL path for lookup action
     */
    protected const LOOKUP_API_PATH = 'lookup';

    /**
     * Main API endpoint
     *
     * @var string
     */
    protected $apiEndpoint = 'https://dictionary.yandex.net/api/v1/dicservice.json';

    /**
     * Mapping of properties to environment variables which must supply these properties
     *
     * @var array
     */
    protected $envProperties = [
        'apiKey' => 'YANDEX_DICTIONARY_API_KEY'
    ];

    use YandexApiTrait;

    /**
     * Create request context for get-text-alternatives request
     *
     * @param string   $text
     * @param Language $language
     *
     * @throws RequestContextException
     *
     * @return RequestContext
     */
    protected function createGetTextAlternativesContext(
        string $text,
        Language $language
    ): RequestContext {
        $requestContext = $this->getLookupRequest($text, $language, $language);

        return $this->fillGeneralRequestSettings($requestContext);
    }

    /**
     * Process response of get-text-alternatives request and prepare valid response
     *
     * @param ResponseContextAbstract $context
     *
     * @return DictionaryResponse
     */
    protected function prepareGetTextAlternativesResponse(ResponseContextAbstract $context): DictionaryResponse
    {
        return $this->processLookupResponse($context);
    }

    /**
     * Create request context for get-translate-alternatives request
     *
     * @param string   $text
     * @param Language $languageTo
     * @param Language $languageFrom
     *
     * @throws RequestContextException
     *
     * @return RequestContext
     */
    protected function createGetTranslateAlternativesContext(
        string $text,
        Language $languageTo,
        Language $languageFrom
    ): RequestContext {
        $requestContext = $this->getLookupRequest($text, $languageTo, $languageFrom);

        return $this->fillGeneralRequestSettings($requestContext);
    }

    /**
     * Process response of get-translate-alternatives request and prepare valid response
     *
     * @param ResponseContextAbstract $context
     *
     * @return DictionaryResponse
     */
    protected function prepareGetTranslateAlternativesResponse(ResponseContextAbstract $context): DictionaryResponse
    {
        return $this->processLookupResponse($context);
    }

    /**
     * Filters ResponseContext from common HTTP errors
     *
     * @param ResponseContextAbstract $responseContext
     *
     * @throws InvalidResponseContent
     * @throws BadResponseContextException
     */
    protected function processApiResponseContextErrors(ResponseContextAbstract $responseContext): void
    {
        $responseArray = $responseContext->getArray();

        $this->filterYandexSpecificErrors($responseArray);

        parent::processApiResponseContextErrors($responseContext);

        if (!isset($responseArray['def'])) {
            throw new InvalidResponseContent(sprintf('There is no required field "%s" in response', 'def'));
        }
    }

    /**
     * @param string   $text
     * @param Language $languageTo
     * @param Language $languageFrom
     *
     * @return RequestContext
     * @throws RequestContextException
     */
    private function getLookupRequest(string $text, Language $languageTo, Language $languageFrom): RequestContext
    {
        $requestContext = (new RequestContext(sprintf("%s/%s", $this->apiEndpoint, self::LOOKUP_API_PATH)))
            ->setRequestParameters(
                [
                    'lang' => $this->getLanguageString($languageTo, $languageFrom),
                    'flags' => self::LOOKUP_MORPHO_FLAG,
                    'ui' => Language::CODE_ENGLISH
                ] + $this->getAuthData()
            )
            ->setData(['text' => $text])
            ->setMethod(RequestContext::METHOD_POST);

        return $requestContext;
    }

    /**
     * @param ResponseContextAbstract $context
     *
     * @return DictionaryResponse
     */
    private function processLookupResponse(ResponseContextAbstract $context): DictionaryResponse
    {
        $responseArray = $context->getArray()['def'];

        $response = new DictionaryResponse();

        foreach ($responseArray as $sourceTextGroup) {
            if (empty($sourceTextGroup['text'])) {
                continue;
            }

            if (empty($sourceTextGroup['tr']) || !is_array($sourceTextGroup['tr'])) {
                continue;
            }

            $this->processTextGroup($sourceTextGroup, $response);
        }

        return $response;
    }

    /**
     * @param $sourceTextGroup
     * @param $response
     *
     * @return mixed
     */
    private function processTextGroup(array $sourceTextGroup, DictionaryResponse $response)
    {
        foreach ($sourceTextGroup['tr'] as $targetTextGroup) {
            if (empty($targetTextGroup['text'])) {
                continue;
            }

            $entry = new DictionaryEntry();

            $entry->setTextFrom($sourceTextGroup['text']);
            $entry->setTextTo($targetTextGroup['text']);

            $this->processTranscription($sourceTextGroup, $entry);
            $this->processPosFrom($sourceTextGroup, $entry);
            $this->processPosTo($targetTextGroup, $entry);
            $this->processSynonyms($targetTextGroup, $entry);
            $this->processMeanings($targetTextGroup, $entry);
            $this->processExamples($targetTextGroup, $entry);

            $response->addEntry($entry);
        }

        return $sourceTextGroup;
    }

    /**
     * @param array           $sourceTextGroup
     * @param DictionaryEntry $entry
     *
     * @return void
     */
    private function processTranscription(array $sourceTextGroup, DictionaryEntry $entry): void
    {
        if (!empty($sourceTextGroup['ts'])) {
            $entry->setTranscription($sourceTextGroup['ts']);
        }
    }

    /**
     * @param array           $sourceTextGroup
     * @param DictionaryEntry $entry
     *
     * @return void
     */
    private function processPosFrom(array $sourceTextGroup, DictionaryEntry $entry): void
    {
        if (!empty($sourceTextGroup['pos'])) {
            $entry->setPosFrom(new DictionaryEntryPos($sourceTextGroup['pos']));
        }
    }

    /**
     * @param array           $targetTextGroup
     * @param DictionaryEntry $entry
     *
     * @return void
     */
    private function processPosTo(array $targetTextGroup, DictionaryEntry $entry): void
    {
        if (!empty($targetTextGroup['pos'])) {
            $entry->setPosTo(new DictionaryEntryPos($targetTextGroup['pos']));
        }
    }

    /**
     * @param array           $targetTextGroup
     * @param DictionaryEntry $entry
     *
     * @return void
     */
    private function processSynonyms(array $targetTextGroup, DictionaryEntry $entry): void
    {
        if (!empty($targetTextGroup['syn']) && is_array($targetTextGroup['syn'])) {
            $synonyms = [];

            foreach ($targetTextGroup['syn'] as $synonym) {
                if (empty($synonym['text'])) {
                    continue;
                }

                $synonyms[] = $synonym['text'];
            }

            $entry->setSynonyms($synonyms);
        }
    }

    /**
     * @param array           $targetTextGroup
     * @param DictionaryEntry $entry
     *
     * @return void
     */
    private function processMeanings(array $targetTextGroup, DictionaryEntry $entry): void
    {
        if (!empty($targetTextGroup['mean']) && is_array($targetTextGroup['mean'])) {
            $meanings = [];

            foreach ($targetTextGroup['mean'] as $meaning) {
                if (empty($meaning['text'])) {
                    continue;
                }

                $meanings[] = $meaning['text'];
            }

            $entry->setMeanings($meanings);
        }
    }

    /**
     * @param array           $targetTextGroup
     * @param DictionaryEntry $entry
     *
     * @return void
     */
    private function processExamples(array $targetTextGroup, DictionaryEntry $entry): void
    {
        if (!empty($targetTextGroup['ex']) && is_array($targetTextGroup['ex'])) {
            $examples = [];

            foreach ($targetTextGroup['ex'] as $example) {
                if (empty($example['text']) || empty($example['tr'][0]['text'])) {
                    continue;
                }

                $examples[$example['text']] = $example['tr'][0]['text'];
            }

            $entry->setExamples($examples);
        }
    }
}