silktide/semrush-api

View on GitHub
src/Model/Request.php

Summary

Maintainability
A
3 hrs
Test Coverage
<?php

namespace Silktide\SemRushApi\Model;

use Silktide\SemRushApi\Data\Database;
use Silktide\SemRushApi\Data\TypeAPIVersionMap;
use Silktide\SemRushApi\Model\Exception\InvalidOptionException;

class Request
{
    const ENDPOINT = "http://api.semrush.com/";
    const ENDPOINTv2 = "http://api.semrush.com/analytics/da/v2/";

    /**
     * @var string
     */
    protected $type;

    /**
     * @var array
     */
    protected $options = [];

    /**
     * @var Definition
     */
    protected $definition;

    /**
     * @var int
     */
    protected $APIVersion;

    /**
     * @param string $type
     * @param array $options
     */
    public function __construct($type, $options = [])
    {
        $this->type = $type;
        $this->APIVersion = TypeAPIVersionMap::getAPIVersion($type);
        $this->options = $this->buildOptionsArray($options);
        $this->loadRequestDefinition();
        $this->mergePresets();
        $this->options['export_columns'] = $this->getExpectedResultColumns();
        $this->validate();
    }


    /**
     * @param array $options
     * @return array
     */
    public function buildOptionsArray($options = [])
    {
        if ($this->APIVersion == 2) {
            return ['action' => 'report'] + $options + ['type' => $this->type];
        }
        return ['type' => $this->type] + $options;
    }
    /**
     * Merge in presets from the definition
     */
    protected function mergePresets()
    {
        $presets = $this->definition->getPresetFields();
        $this->options = $this->options + $presets;
    }

    /**
     * Validate this request
     */
    protected function validate()
    {
        $this->validateOptions();
    }

    /**
     * Validate the options passed
     * @throws InvalidOptionException
     */
    protected function validateOptions()
    {
        $optionsPassed = array_keys($this->options);

        //check for invalid options
        $validOptions = array_keys($this->definition->getAvailableFields());
        $unknownOptions = array_diff($optionsPassed, $validOptions);
        if (count($unknownOptions) > 0) {
            throw new InvalidOptionException("Invalid option(s) [" . implode(", ", $unknownOptions) . "] passed for request [{$this->type}]");
        }

        //check for missing options
        $requiredOptions = array_keys($this->definition->getRequiredFields());
        $missingOptions = array_diff($requiredOptions, $optionsPassed);
        if (count($missingOptions) > 0) {
            throw new InvalidOptionException("Missing option(s) [" . implode(", ", $missingOptions) . "] which are required for request [{$this->type}]");
        }

        //validate each field
        foreach ($this->options as $option => $value) {
            $this->validateOption($option, $value);
        }
    }

    /**
     * Validate the option passed
     *
     * @param string $option
     * @param mixed $value
     */
    protected function validateOption($option, $value)
    {
        $fieldDefinitions = $this->definition->getAvailableFields();
        $fieldType = $fieldDefinitions[$option];
        switch ($fieldType) {
            case "type":
                break;

            case "string":
                $this->validateString($option, $value);
                break;

            case "domain":
                $this->validateDomain($option, $value);
                break;

            case "domains":
                $this->validateDomains($option, $value);
                break;

            case "database":
                $this->validateDatabase($option, $value);
                break;

            case "date":
                $this->validateDate($option, $value);
                break;

            case "columns":
                $this->validateColumns($option, $value);
                break;

            case "boolean":
                $this->validateBoolean($option, $value);
                break;

            case "integer":
                $this->validateInteger($option, $value);
                break;
        }
    }

    /**
     * Validate boolean option (1 or 0)
     *
     * @param string $key
     * @param mixed $value
     * @throws InvalidOptionException
     */
    protected function validateBoolean($key, $value)
    {
        $value = strval($value);
        if (!($value == "1" || $value == "0")) {
            throw new InvalidOptionException("[{$key}] was not 1 or 0 [{$value}]");
        }
    }

    /**
     * Validate database
     *
     * @param string $key
     * @param string $database
     * @throws InvalidOptionException
     */
    protected function validateDatabase($key, $database)
    {
        if (!in_array($database, Database::getDatabases())) {
            throw new InvalidOptionException("[{$key}] was not a database [{$database}]");
        }
    }

    /**
     * Validate integer
     *
     * @param string $key
     * @param string $string
     * @throws InvalidOptionException
     */
    protected function validateInteger($key, $string)
    {
        if (!is_int($string)) {
            throw new InvalidOptionException("[{$key}] was not an integer [{$string}]");
        }
    }

    /**
     * Validate string
     *
     * @param string $key
     * @param string $string
     * @throws InvalidOptionException
     */
    protected function validateString($key, $string)
    {
        if (!is_string($string)) {
            throw new InvalidOptionException("[{$key}] was not a string [{$string}]");
        }
    }

    /**
     * Validate domain
     *
     * @param string $key
     * @param string $domain
     * @throws InvalidOptionException
     */
    protected function validateDomain($key, $domain)
    {
        if (!preg_match('/^[a-z0-9-.]+$/i', $domain)) {
            throw new InvalidOptionException("[{$key}] was not a valid domain [{$domain}]");
        }
    }

    /**
     * Validate domain
     *
     * @param string $key
     * @param string $domain
     * @throws InvalidOptionException
     *
     * Validates the format <sign>|<type>|<domain>|<sign>|<type>|<domain>|...
     */
    protected function validateDomains($key, $domains)
    {
        $parts = explode('|', $domains);

        if (count($parts) > 5 * 3) { // 5 domains max, each with 3 parts
            throw new InvalidOptionException("[{$key}] contains too many domains");
        }

        for ($i = 0; $i < count($parts); $i++) {
           switch ($i % 3) {
               case 0:
                   if (!in_array($parts[$i], ['+', '-', '/', '*'], true)) {
                       throw new InvalidOptionException("[{$key}] contains an invalid sign [{ $parts[$i]}]");
                   }
                   break;
               case 1:
                   if (!in_array($parts[$i], ['or', 'ad'], true)) {
                       throw new InvalidOptionException("[{$key}] contains an invalid type - must be or or ad [{ $parts[$i]}]");
                   }
                    break;
               case 2:
                   if (!preg_match('/^[a-z0-9-.]+$/i', $parts[$i])) {
                       throw new InvalidOptionException("[{$key}] contains an invalid domain [{ $parts[$i]}]");
                   }
                   break;
           }
        }
    }

    /**
     * Validate date
     *
     * @param string $key
     * @param string $date
     * @throws InvalidOptionException
     */
    protected function validateDate($key, $date)
    {
        if (!preg_match('/^20[0-2][0-9][0-1][0-9]15$/', $date)) {
            throw new InvalidOptionException("[{$key}] was not a valid date [{$date}]");
        }
    }

    /**
     * Validate columns
     *
     * @param string $key
     * @param string[] $columns
     * @throws InvalidOptionException
     */
    protected function validateColumns($key, $columns)
    {
        if (!is_array($columns)) {
            throw new InvalidOptionException("[{$key}] is expected to be array for [{$this->type}]");
        }

        //check for invalid columns
        $validColumns = $this->definition->getValidColumns();
        $unknownColumns = array_diff($columns, $validColumns);
        if (count($unknownColumns) > 0) {
            throw new InvalidOptionException("Invalid [{$key}](s) [" . implode(", ", $unknownColumns) . "] passed for request [{$this->type}]");
        }
    }

    /**
     * Load request definition
     *
     * @return Definition
     */
    protected function loadRequestDefinition()
    {
        $this->definition = new Definition($this->type);
    }

    /**
     * Get the columns for this request
     *
     * @return string[]
     */
    public function getExpectedResultColumns()
    {
        if (isset($this->options['export_columns'])) {
            return $this->options['export_columns'];
        }
        return $this->definition->getDefaultColumns();
    }

    /**
     * Get all the options of this request
     *
     * @return array
     */
    public function getUrlParameters()
    {
        return $this->options;
    }

    /**
     * Get the request endpoint
     *
     * @return string
     */
    public function getEndpoint()
    {
        if ($this->APIVersion == 2) {
            return self::ENDPOINTv2;
        }
        return self::ENDPOINT;
    }


}