Создаём собственный API – 3

Кирилл Евсеев, February 21, 2011

Как я и обещал, сегодня мы допилим классы api_executor и api_functions. Для начала посмотрим на класс api_functions и интерфейс api_functions_itrfc. Интерфейс api_functions_itrfc теперь выглядит так:

interface api_functions_itrfc
{
  public function delete_user($id);
  public function add_user($fname, $lname);
  public function add_users($udata);
  public function get_user($id);
  public function get_users();
  public function get_users_and($cases);
  public function get_users_or($cases);
}

Видно, что api_functions_itrfc объявляет все методы-колбеки, которые мы предварительно сохранили в базу. Это именно те функции, которые будут вызывать удалённые пользователи. Соответственно, класс api_functions эти методы определяет:

class api_functions extends api implements api_functions_itrfc
{
  public function __construct($db_object)
  {
    parent::__construct($db_object);
  }

  public function delete_user($id)
  {
    return $this->db_handler->query('DELETE FROM `'.
      $this->data_table.'` WHERE id = "'.$id.'"');
  }

  public function add_user($fname, $lname)
  {
    return $this->db_handler->query('INSERT LOW_PRIORITY INTO  `'.
      $this->data_table.'` values (NULL, "'.$fname.'", "'.$lname.'")');
  }

  public function add_users($udata)
  {
    $query = 'INSERT LOW_PRIORITY INTO `'.
      $this->data_table.'` (id, f_name, l_name) values ';

    if(is_array($udata) && !empty($udata))
    {
      for($i=0; $i<sizeof($udata); $i++)
      {
        if($i!=0)
        {
          $query .= ', ';
        }

        $query .= '(NULL, "'.$udata[$i]['fname'].'", "'.$udata[$i]['lname'].'" )';
      }

      return $this->db_handler->query($query);
    }
    else
    {
      return false;
    }
  }

  public function get_user($id)
  {
    return $this->db_handler->res_query('SELECT * FROM `'.
      $this->data_table.'` WHERE id = '.$id);
  }

  public function get_users()
  {
    return $this->db_handler->res_query('SELECT * FROM `'.
      $this->data_table.'`');
  }

  public function get_users_and($cases)
  {
    $query = 'SELECT * FROM `'.$this->data_table.'` WHERE ';

    if(is_array($cases) && !empty($cases))
    {
      $j = 0;

      for($i=0; $i<sizeof($cases); $i++)
      {
        foreach($cases[$i] as $key => $val)
        {
          if($j!=0)
          {
            $query .= ' AND ';
          }

          $query .= '('.$key.' = "'. $val.'")';

          $j++;
        }
      }

      return $this->db_handler->res_query($query);
    }
    else
    {
      return false;
    }
  }

  public function get_users_or($cases)
  {
    $query = 'SELECT * FROM `'.$this->data_table.'` WHERE ';

    if(is_array($cases) && !empty($cases))
    {
      $j = 0;

      for($i=0; $i<sizeof($cases); $i++)
      {
        foreach($cases[$i] as $key => $val)
        {
          if($j!=0)
          {
            $query .= ' OR ';
          }

          $query .= '('.$key.' = "'. $val.'")';

          $j++;
        }
      }

      return $this->db_handler->res_query($query);
    }
    else
    {
      return false;
    }
  }

}

Другими словами, методы api_functions непосредственно выполняют необходимые действия. Надо заметить, как я говорил во второй части статьи, класс этот уязвим для SQL-инъекций. Эту проблему мы обсудим, когда доберёмся до вопросов защиты.

Гораздо интереснее теперь выглядит класс api_executor. Конструктор остался без изменений, но метод api_executor::execute изменился принципиально. Если в прошлой версии мы просто проверяли метод на существование и, в случае успеха, вызывали колбэк, то сейчас мы проверяем параметры, которые этот колбэк получает. Во-первых, это необходимо для дополнительной защищённости. Во-вторых, это логично. И в-третьих, такой подход уменьшает количество ошибок и в клиентском и в серверном коде. Что же изменилось? Смотрим:

public function execute($func_name, $args=array())
{
  $res = null;

  //дополнительная проверка передаваемых аргументов.
  if($this->api_list->api_func_exists($func_name)
    &&$this->api_list->chk_args($func_name, $args))
  {
    $callback_array = array($this->api_functions, $func_name);

    if(is_callable($callback_array, true, $func_name))
    {
      //вызываем колбек и передаём ему массив параметров.
      $res = call_user_func_array($callback_array, $args);

      return $res;
    }
    else
    {
      return false;
    }
  }
  else
  {
    return false;
  }
}

Вызовы стандартных функций call_user_func_array и is_callable не совсем очевидны. Детально посмотреть информацию о них можно в справке PHP.

Рассмотрим метод chk_args класса api_list и интерфейс api_list_itrfc.

interface api_list_itrfc
{
  public function get_api($func='');
  public function api_func_exists($func_name='');

  //bool chk_args(string, array) - добавили объявление
  //этой функции в интерфейс
  public function chk_args($fname, $args=array());
}

метод api_list::chk_args определяется как показано ниже:

public function chk_args($fname, $args=array())
//получаем имя функции и массив параметров
{
  $fdata = array();
  $adata = array();
  $fdata = $this->get_api_func($fname);
  //получаем данные по необходимой функции

  // проверяем в том числе совпадение количества элементов
  // в оригинальном и переданном массивах параметров
  if(is_array($args) && is_array($fdata) &&
    isset($fdata[0]['args']) && (sizeof($args) ==
    sizeof($fdata[0]['args'])))
  {
    $adata = $fdata[0]['args'];
    $flag = false;

    //цикл по всем параметрам из базы.
    for($i=0; $i<sizeof($adata); $i++)
    {
      //цикл по всем переданным параметрам.
      foreach($args as $key=>$val)
      {
        if($adata[$i]['arg_name']==$key)
       {
         $flag = true;

          //сравниваем тип переданного параметра и данных из базы
          if($this->chk_types($adata[$i]['arg_type'], $val))
          {
            break;
          }
          else
          {
            return false;
          }
        }
      }

      if(!$flag)
      {
        return false;
      }
      else
      {
        $flag = false;
      }
    }

    //если типы всех параметров правильные, возвращаем true
    return true;
  }
  else
  {
    return false;
  }
}

Ещё один метод, которого не было в прошлой версии класса – bool chk_types(string, mixed) Метод очень прост и выглядит так:

protected function chk_types($type, $val)
{
  switch ($type)
  {
    case 'string':
      return is_string($val);

    case 'array':
      return is_array($val);

    case 'int':
      return is_int($val);

    case 'bool':
      return is_bool($val);

    case 'void':
      return (!isset($val) || empty($val)) ?  true : false;

    default:
      return false;
  }
}

Нужно заметить, что case ‘void’: никогда не будет выполнен, т.к. тип void в нашем случае проверяется как произвольная строка. Даже если пользователь передаст в этой строке нежелательные данные, методы с типом void во входящих параметрах никак на это не отреагируют, т.к. не принимают никаких параметров. Однако, сравнение для типа void добавленно для совместимости. Возможно, однажды нам это пригодится. Передо мной встала дилемма, как этот тип проверять – как пустую строку или как NULL и я остановился на пустой строке. Дело в том, что клиент, использующий API может быть написан на любом языке и я не могу угадать, существует ли в клиентском языке возможность присвоить переменной значения типа NULL. Вероятность того, что в клиентском языке программист способен создать пустую строку мне кажется гораздо более высокой.

Ну что ж, подведём итоги. Мы привели классы api_functions и api_executor в рабочее состояние и дополнили класс api_list весьма важным методом, который проверяет соответствие ожидаемых и передаваемых типов параметров.

Класс api_list может быть дополнен ещё одним методом, который проверяет возвращаемый тип. Сейчас api_executor::execute возвращает клиенту данные без всяких проверок, as is. В принципе, спасение утопающих, как известно, дело рук самих утопающих и о правильности ответа может заботиться сам клиент. Но в рамках серьёзного проекта было бы неплохо такой метод иметь. Я предлагаю вам разработать его самостоятельно, чтобы у вас появилась возможность немного поиграться с архитектурой иерархии api.

В аттачменте находится полный код проекта и драйвер index.php, чтобы протестировать то, что получилось.

В следующей статье мы в очередной раз проигнорируем вопросы безопасности. Но зато, мы разработаем класс Gateway, который будет принимать запрос от удалённого хоста и запускать необходимый метод API.

Ну а пока жду ваших вопросов, критики и идей.

В тему:

2комментария

Превосходно! С нетерпением жду продолжение.

Gena, February 27, 2011 11:31 am Reply

Благодарю вас. Продолжение будет в ближайшее время.

кирилл евсеев, February 27, 2011 6:16 pm Reply
Ваше имя
Ваш email*
Ваш сайт
Текст вашего комментария:

Поиск по блогу:
Подписаться:
Популярные:
Облако тегов:
Разное:
Счетчик: