Статьи

Создание приложения OAuth для Twitter

Поначалу OAuth может оказаться сложной задачей, чтобы обернуть голову, но теперь, когда API-интерфейс Twitter требует его использования, его необходимо понять, прежде чем создавать приложение Twitter. Это руководство познакомит вас с OAuth и проведет вас через процесс создания базового приложения.


В этом уроке мы будем создавать простое приложение, которое позволит пользователям применять различные эффекты к своему аватару в Твиттере. Чтобы работать с API Twitter, мы должны использовать OAuth, чтобы авторизовать наше приложение для отправки запросов от имени пользователя.

Наш поток приложений будет примерно таким:

  1. Пользователю предлагается связаться с Twitter.
  2. Пользователю предоставляется список аватаров предварительного просмотра на выбор.
  3. После выбора пользователю предоставляется экран подтверждения, показывающий исходный и новый аватар для сравнения. Пользователю также предлагается отправить твит.
  4. После подтверждения пользователем приложение создает и загружает измененный аватар в Twitter, и отображается страница успеха.

Для начала нам нужно настроить наш исходный каталог. Нам нужен каталог lib для наших файлов библиотеки (классов) PHP, каталог tmp для хранения временных файлов (это необходимо для записи на сервере), каталог css для нашей таблицы стилей и каталог img для любых изображений.

Вот как должно выглядеть ваше дерево каталогов:

  • руководство
    • CSS
    • IMG
    • Lib
    • TMP (доступный для записи)

Чтобы использовать OAuth, вам потребуется так называемый ключ и секретный ключ пользователя для идентификации вашего приложения. Чтобы получить их, вы должны зарегистрировать приложение в Twitter, выполнив следующие действия.

Перейдите на страницу регистрации , войдите в систему, если это необходимо. Вас приветствует форма, изображенная ниже:

Заполните форму с подробной информацией о вашем приложении. В нашем случае Тип приложенияБраузер, и нам нужно установить URL обратного вызова по умолчанию. Этот URL может быть любым, если он является допустимым форматом. Мы будем переопределять обратный вызов в нашем коде, поэтому не критично, что это реальный URL. Тип доступа по умолчанию должен быть для чтения и записи .

Как только вы зарегистрируетесь и примете условия, вам будет представлена ​​информация о вашем новом заявлении. Важные детали, которые нам нужны, это ключ Consumer и Consumer secret , который должен выглядеть примерно так:


Мы будем использовать библиотеку для обработки всех деталей, стоящих за выполнением запросов OAuth. В этом руководстве мы будем использовать библиотеку @themattharris ‘ tmhOAuth , которая поддерживает загрузку файлов.

  1. Скачать tmhOAuth с GitHub
  2. Извлеките tmhOAuth.php в каталог lib, который мы создали ранее

Аутентификация с помощью OAuth в основном состоит из трех этапов. Более подробное объяснение см. На этой странице по аутентификации в Twitter, но вот краткое изложение:

  1. Приложение получает токен запроса. На первом этапе наше приложение идентифицирует себя в Twitter (используя ключ потребителя) и получает токен запроса . Нам нужно сохранить этот токен запроса на потом.
  2. Пользователь авторизует приложение в Twitter. Теперь пользователю необходимо отправить его в Twitter, чтобы предоставить нашему приложению доступ к его учетной записи. После этого пользователь отправляется обратно на URL обратного вызова, указанный приложением.
  3. Приложение обменивает токен запроса на токен доступа. Теперь, когда наше приложение было одобрено, оно может обменять токен запроса с шага 1 на токен доступа . Когда у нас есть токен доступа, наше приложение может взаимодействовать с API Twitter от имени пользователя.

Итак, начнем с некоторого кода. Мы TwitterApp все задачи аутентификации в классе под названием TwitterApp . Начните со следующего кода в новом файле с именем lib/TwitterApp.php :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
class TwitterApp {
    /**
     * This variable holds the tmhOAuth object used throughout the class
     *
     * @var tmhOAuth An object of the tmhOAuth class
     */
    public $tmhOAuth;
 
    /**
     * User’s Twitter account data
     *
     * @var array Information on the current authenticated user
     */
    public $userdata;
 
    /**
     * Authentication state
     *
     * Values:
     * — 0: not authed
     * — 1: Request token obtained
     * — 2: Access token obtained (authed)
     *
     * @var int The current state of authentication
     */
    protected $state;
 
    /**
     * Initialize a new TwitterApp object
     *
     * @param tmhOAuth $tmhOAuth A tmhOAuth object with consumer key and secret
     */
    public function __construct(tmhOAuth $tmhOAuth) {
         
        // save the tmhOAuth object
        $this->tmhOAuth = $tmhOAuth;
    }
}

Здесь мы создали три свойства и простой конструктор. Свойство $tmhOAuth будет объектом tmhOAuth, который будет использоваться во всем классе. Свойство $userdata будет содержать объект, содержащий информацию о пользователе, такую ​​как его имя пользователя и статус в Twitter. Свойство $state отслеживает текущее состояние аутентификации.

Конструктор просто принимает объект tmhOAuth и назначает его свойству $tmhOAuth .


Вот метод для получения токена запроса:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
 * Obtain a request token from Twitter
 *
 * @return bool False if request failed
 */
private function getRequestToken() {
     
    // send request for a request token
    $this->tmhOAuth->request(«POST», $this->tmhOAuth->url(«oauth/request_token», «»), array(
        // pass a variable to set the callback
        ‘oauth_callback’ => $this->tmhOAuth->php_self()
    ));
 
    if($this->tmhOAuth->response[«code»] == 200) {
         
        // get and store the request token
        $response = $this->tmhOAuth->extract_params($this->tmhOAuth->response[«response»]);
        $_SESSION[«authtoken»] = $response[«oauth_token»];
        $_SESSION[«authsecret»] = $response[«oauth_token_secret»];
 
        // state is now 1
        $_SESSION[«authstate»] = 1;
 
        // redirect the user to Twitter to authorize
        $url = $this->tmhOAuth->url(«oauth/authorize», «») .
        header(«Location: ‘ . $url);
        exit;
    }
    return false;
}

Чтобы понять первую часть, вам нужно знать о tmhOAuth::request() . Этот метод позволяет нам сделать HTTP-запрос с поддержкой OAuth, и он имеет следующее использование:

tmhOAuth::request($method, $url[, $params[, $useauth[, $multipart]]])

  • string $methodstring $method запроса (GET, POST и т. д.)
  • string $url — URL для доступа
  • array $params (необязательно) — ассоциативный массив параметров для включения в запрос
  • bool $useauth (необязательно, по умолчанию true) — Требуется ли аутентификация?
  • bool $multipart (необязательно, по умолчанию false) — установите значение true для загрузки файлов

Для параметра $url мы используем метод tmhOAuth::url() для создания URL-адреса на основе метода API, который мы вызываем:

tmhOAuth::url($request[, $format])

  • string $request — метод API (без расширения)
  • string $format (необязательно, по умолчанию ‘json «) — желаемый формат ответа (JSON, XML и т. д.)

Теперь, когда вы знакомы с этими методами, мы должны сделать запрос POST к методу API oauth / request_token . Это вернет данные OAuth в специальном формате, поэтому нам нужно установить пустой формат при использовании tmhOAuth::url() . Нам также нужно передать переменную с именем oauth_callback , куда пользователь вернется после авторизации в Twitter. Мы будем использовать метод tmhOAuth::php_self() для ссылки на текущую страницу. Вот этот код снова:

1
2
3
4
5
// send request for a request token
$this->tmhOAuth->request(«POST», $this->tmhOAuth->url(«oauth/request_token», «»), array(
    // pass a variable to set the callback
    ‘oauth_callback’ => $this->tmhOAuth->php_self()
));

Как только мы сделаем запрос, ответ сохраняется в виде массива в tmhOAuth::response со следующими ключевыми частями данных:

  • code — код ответа HTTP
  • response — возвращены фактические данные
  • headers — заголовки ответа

Поэтому следующая часть нашего кода проверяет код ответа (200 означает успех), а затем помещает полученные oauth_token и oauth_token_secret в переменные сеанса, так как они понадобятся нам позже. Они извлекаются из данных ответа с помощью tmhOAuth::extract_params() , который возвращает массив данных, содержащихся в ответе. Мы также устанавливаем authstate сеанса authstate чтобы authstate что мы находимся на следующем этапе аутентификации. Вот код:

01
02
03
04
05
06
07
08
09
10
if($this->tmhOAuth->response[«code»] == 200) {
 
    // get and store the request token
    $response = $this->tmhOAuth->extract_params($this->tmhOAuth->response[«response»]);
    $_SESSION[«authtoken»] = $response[«oauth_token»];
    $_SESSION[«authsecret»] = $response[«oauth_token_secret»];
 
    // state is now 1
    $_SESSION[«authstate»] = 1;
}

После этого мы должны перенаправить пользователя на URL-адрес oauth / authorize , включая oauth_token в параметре GET. Вот этот код снова:

1
2
3
4
// redirect the user to Twitter to authorize
$url = $this->tmhOAuth->url(«oauth/authorize», «») .
header(«Location: ‘ . $url);
exit;

Вот способ обмена нашего токена запроса на токен доступа:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
 * Obtain an access token from Twitter
 *
 * @return bool False if request failed
 */
private function getAccessToken() {
 
    // set the request token and secret we have stored
    $this->tmhOAuth->config[«user_token»] = $_SESSION[«authtoken»];
    $this->tmhOAuth->config[«user_secret»] = $_SESSION[«authsecret»];
 
    // send request for an access token
    $this->tmhOAuth->request(«POST», $this->tmhOAuth->url(«oauth/access_token», «»), array(
        // pass the oauth_verifier received from Twitter
        ‘oauth_verifier’ => $_GET[«oauth_verifier»]
    ));
 
    if($this->tmhOAuth->response[«code»] == 200) {
 
        // get the access token and store it in a cookie
        $response = $this->tmhOAuth->extract_params($this->tmhOAuth->response[«response»]);
        setcookie(«access_token», $response[«oauth_token»], time()+3600*24*30);
        setcookie(«access_token_secret», $response[«oauth_token_secret»], time()+3600*24*30);
 
        // state is now 2
        $_SESSION[«authstate»] = 2;
 
        // redirect user to clear leftover GET variables
        header(«Location: ‘ . $this->tmhOAuth->php_self());
        exit;
    }
    return false;
}

Сначала мы устанавливаем user_token и user_secret в tmhOAuth::config на токен запроса, который мы получили ранее.

1
2
3
// set the request token and secret we have stored
$this->tmhOAuth->config[«user_token»] = $_SESSION[«authtoken»];
$this->tmhOAuth->config[«user_secret»] = $_SESSION[«authsecret»];

Следующая часть — это то, где мы делаем запрос POST к oauth / access_token . Мы передаем oauth_verifier мы получили в переменной GET, в качестве параметра в этом запросе.

1
2
3
4
5
// send request for an access token
$this->tmhOAuth->request(«POST», $this->tmhOAuth->url(«oauth/access_token», «»), array(
    // pass the oauth_verifier received from Twitter
    ‘oauth_verifier’ => $_GET[«oauth_verifier»]
));

Twitter ответит токеном доступа и секретом, который нам нужно сохранить для любых будущих запросов. Таким образом, следующий фрагмент кода берет их и сохраняет каждый в куки, а затем устанавливает состояние в 2.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
if($this->tmhOAuth->response[«code»] == 200) {
 
    // get the access token and store it in a cookie
    $response = $this->tmhOAuth->extract_params($this->tmhOAuth->response[«response»]);
    setcookie(«access_token», $response[«oauth_token»], time()+3600*24*30);
    setcookie(«access_token_secret», $response[«oauth_token_secret»], time()+3600*24*30);
 
    // state is now 2
    $_SESSION[«authstate»] = 2;
 
    // redirect user to clear leftover GET variables
    header(«Location: ‘ . $this->tmhOAuth->php_self());
    exit;
}

Переадресация в конце предназначена для очистки параметров URL, оставленных Twitter, и позволяет cookie-файлам вступать в силу.


Получив наш токен доступа, мы должны убедиться, что он действителен. Вот метод, чтобы сделать это:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
/**
 * Verify the validity of our access token
 *
 * @return bool Access token verified
 */
private function verifyAccessToken() {
    $this->tmhOAuth->config[«user_token»] = $_COOKIE[«access_token»];
    $this->tmhOAuth->config[«user_secret»] = $_COOKIE[«access_token_secret»];
 
    // send verification request to test access key
    $this->tmhOAuth->request(«GET», $this->tmhOAuth->url(«1/account/verify_credentials»));
 
    // store the user data returned from the API
    $this->userdata = json_decode($this->tmhOAuth->response[«response»]);
 
    // HTTP 200 means we were successful
    return ($this->tmhOAuth->response[«code»] == 200);
}

Этот код должен выглядеть довольно знакомым к настоящему времени. Все, что мы здесь делаем, это устанавливаем user_token и user_secret и делаем запрос GET равным 1 / account / verify_credentials . Если Twitter отвечает кодом 200, то токен доступа действителен.

Еще одна деталь, на которую стоит обратить внимание, — это то, где мы заполняем свойство $userdata данными, возвращаемыми этим запросом Twitter. Данные представлены в формате JSON, поэтому мы используем json_decode() для преобразования их в объект PHP. Вот эта строка снова:

1
2
// store the user data returned from the API
$this->userdata = json_decode($this->tmhOAuth->response[«response»]);

С нашими компонентами OAuth пришло время связать все вместе. Нам нужен общедоступный метод, позволяющий нашему клиентскому коду запустить процесс аутентификации, и вот он:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * Authenticate user with Twitter
 *
 * @return bool Authentication successful
 */
public function auth() {
 
    // state 1 requires a GET variable to exist
    if($this->state == 1 && !isset($_GET[«oauth_verifier»])) {
        $this->state = 0;
    }
 
    // Step 1: Get a request token
    if($this->state == 0) {
        return $this->getRequestToken();
    }
    // Step 2: Get an access token
    elseif($this->state == 1) {
        return $this->getAccessToken();
    }
 
    // Step 3: Verify the access token
    return $this->verifyAccessToken();
}

Большая часть метода auth() не требует пояснений. Основываясь на состоянии, он выполняет соответствующий метод для этой стадии аутентификации. Если состояние равно 1, должна существовать переменная GET oauth_verifier , поэтому метод также проверяет это.

Теперь мы должны создать публичный метод, чтобы узнать, аутентифицированы ли мы. Этот isAuthed() возвращает true, если состояние равно 2:

1
2
3
4
5
6
7
8
/**
 * Check the current state of authentication
 *
 * @return bool True if state is 2 (authenticated)
 */
public function isAuthed() {
    return $this->state == 2;
}

Мы также можем использовать метод для удаления аутентификации пользователя. Этот endSession() устанавливает состояние в 0 и удаляет куки, содержащие токен доступа:

1
2
3
4
5
6
7
8
9
/**
 * Remove user’s access token cookies
 */
public function endSession() {
    $this->state = 0;
    $_SESSION[«authstate»] = 0;
    setcookie(«access_token», «», 0);
    setcookie(«access_token_secret», «», 0);
}

Теперь нам нужно добавить некоторые вещи в наш __construct() чтобы выяснить, в каком состоянии аутентификации находится приложение при инициализации. Кроме того, поскольку наш код использует переменные сеанса, мы должны убедиться, что сеанс запущен с этим кодом:

1
2
3
4
// start a session if one does not exist
if(!session_id()) {
    session_start();
}

Эта следующая часть, где мы определяем состояние. Состояние начинается с 0; если существуют файлы cookie, содержащие токен доступа, состояние считается равным 2; в противном случае состояние устанавливается authstate переменной сеанса authstate если она существует. Вот код:

01
02
03
04
05
06
07
08
09
10
11
// determine the authentication status
// default to 0
$this->state = 0;
// 2 (authenticated) if the cookies are set
if(isset($_COOKIE[«access_token»], $_COOKIE[«access_token_secret»])) {
    $this->state = 2;
}
// otherwise use value stored in session
elseif(isset($_SESSION[«authstate»])) {
    $this->state = (int)$_SESSION[«authstate»];
}

Если состояние 1, это означает, что мы находимся в процессе аутентификации. Таким образом, мы можем продолжить и продолжить процесс на этом этапе:

1
2
3
4
// if we are in the process of authentication we continue
if($this->state == 1) {
    $this->auth();
}

Если состояние 2, мы должны проверить токен доступа. Если аутентификация не удалась, этот код очищает куки и сбрасывает состояние:

1
2
3
4
// verify authentication, clearing cookies if it fails
elseif($this->state == 2 && !$this->auth()) {
    $this->endSession();
}

Вот новый конструктор с этими изменениями:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
 * Initialize a new TwitterApp object
 *
 * @param tmhOAuth $tmhOAuth A tmhOAuth object with consumer key and secret
 */
public function __construct(tmhOAuth $tmhOAuth) {
 
    // save the tmhOAuth object
    $this->tmhOAuth = $tmhOAuth;
 
    // start a session if one does not exist
    if(!session_id()) {
        session_start();
    }
 
    // determine the authentication status
    // default to 0
    $this->state = 0;
    // 2 (authenticated) if the cookies are set
    if(isset($_COOKIE[«access_token»], $_COOKIE[«access_token_secret»])) {
        $this->state = 2;
    }
    // otherwise use value stored in session
    elseif(isset($_SESSION[«authstate»])) {
    $this->state = (int)$_SESSION[«authstate»];
    }
 
    // if we are in the process of authentication we continue
    if($this->state == 1) {
        $this->auth();
    }
    // verify authentication, clearing cookies if it fails
    elseif($this->state == 2 && !$this->auth()) {
        $this->endSession();
    }
}

Теперь, когда весь код авторизации завершен, мы можем добавить некоторые общие функции в наш класс. Вот метод отправки твита через Twitter API:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
/**
 * Send a tweet on the user’s behalf
 *
 * @param string $text Text to tweet
 * @return bool Tweet successfully sent
 */
public function sendTweet($text) {
 
    // limit the string to 140 characters
    $text = substr($text, 0, 140);
 
    // POST the text to the statuses/update method
    $this->tmhOAuth->request(«POST», $this->tmhOAuth->url(«1/statuses/update»), array(
        ‘status’ => $text
    ));
 
    return ($this->tmhOAuth->response[«code»] == 200);
}

Метод sendTweet() принимает строку, ограничивает ее 140 символами, а затем отправляет в запросе POST значение 1 / statuses / update . Этот шаблон должен быть довольно знакомым к настоящему времени.



Теперь, когда у нас есть класс, который обрабатывает все задачи OAuth, мы можем теперь расширить его с помощью функций, специфичных для нашего приложения. Это включает в себя возможность получить, изменить и установить аватар пользователя.

Мы будем расширять класс TwitterApp классом TwitterAvatars. Начните со следующего кода в новом файле с именем lib/TwitterAvatars.php :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
class TwitterAvatars extends TwitterApp {
     
    /**
     * The path to our temporary files directory
     *
     * @var string Path to store image files
     */
    public $path;
     
    /**
     * These are all the GD image filters available in this class
     *
     * @var array Associative array of image filters
     */
    protected $filters = array(
        ‘grayscale’ => IMG_FILTER_GRAYSCALE,
        ‘negative’ => IMG_FILTER_NEGATE,
        ‘edgedetect’ => IMG_FILTER_EDGEDETECT,
        ’embossed’ => IMG_FILTER_EMBOSS,
        ‘blurry’ => IMG_FILTER_GAUSSIAN_BLUR,
        ‘sketchy’ => IMG_FILTER_MEAN_REMOVAL
    );
     
    /**
     * Initialize a new TwitterAvatars object
     *
     * @param tmhOAuth $tmhOAuth A tmhOAuth object with consumer key and secret
     * @param string $path Path to store image files (default ‘tmp»)
     */
    public function __construct(tmhOAuth $tmhOAuth, $path = ‘tmp») {
         
        // call the parent class’ constructor
        parent::__construct($tmhOAuth);
 
        // save the path variable
        $this->path = $path;
    }
}

Как вы можете видеть, расширенный класс включает в себя свойство $path указывающее, куда будут перемещаться временные файлы изображений, свойство $filters содержащее массив фильтров изображений, и расширенный конструктор с параметром для установки пути. Поскольку мы переопределяем исходный конструктор, мы должны явно вызвать конструктор parent::__construct() с помощью parent::__construct() .

Теперь мы можем начать добавлять наши методы.


Очевидно, нам понадобится возможность загружать изображения, чтобы манипулировать ими. Вот общий метод download() который принимает URL и возвращает данные в этом месте. Метод делает основной запрос cURL.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
/**
 * Download data from specified URL
 *
 * @param string $url URL to download
 * @return string Downloaded data
 */
protected function download($url) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 
    $ret = curl_exec($ch);
    curl_close($ch);
 
    return $ret;
}

Теперь, когда мы можем загружать файлы, нам нужно найти расположение файлов, которые нам нужны. Нас интересуют два разных изображения: миниатюра стандартного размера и оригинальное полноразмерное изображение. Итак, мы создадим метод для получения каждого URL.

Чтобы получить миниатюру стандартного размера, мы вызовем метод API users / profile_image /: screen_name, который отвечает перенаправлением 302 на изображение аватара указанного пользователя. Это означает, что URL будет найден в заголовке Location. Вот этот метод:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
/**
 * Get the URL to the standard sized avatar
 *
 * @return string The URL to the image file
 */
protected function getImageURL() {
 
    // request user’s ‘bigger’ profile image
    $this->tmhOAuth->request(«GET», $this->tmhOAuth->url(«1/users/profile_image/» . $this->userdata->screen_name), array(
        ‘screen_name’ => $this->userdata->screen_name,
        ‘size’ => ‘bigger’
    ));
 
    if($this->tmhOAuth->response[«code»] == 302) {
 
        // the direct URL is in the Location header
        return $this->tmhOAuth->response[«headers»][«location»];
    }
    throw new Exception(«Error locating image»);
}

Обратите внимание, что мы делаем запрос GET с помощью tmhOAuth, передавая параметры screen_name и size , а затем возвращая содержимое заголовка Location.

Нет метода API для получения полноразмерного изображения, поэтому для нашего следующего метода мы немного обманем и отредактируем URL. Пользовательские данные содержат поле profile_image_url , которое указывает на что-то вроде avatar_normal.jpg , а оригинальное изображение можно найти на avatar.jpg без суффикса. Таким образом, этот метод получает URL, удаляет суффикс размера и возвращает измененный URL:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
/**
 * Get the URL to the full sized avatar
 *
 * @return string The URL to the image file
 */
protected function getOriginalImageURL() {
 
    // get the regular sized avatar
    $url = $this->userdata->profile_image_url;
 
    // save the extension for later
    $ext = strrchr($url, ‘.»);
 
    // strip the «_normal’ suffix and add back the extension
    return substr($url, 0, strrpos($url, «_»)) .
}

Теперь, когда мы можем найти и загрузить изображения, нам нужен способ их чтения. Мы будем использовать библиотеку GD для управления изображениями, поэтому этот метод преобразует необработанные данные изображения в ресурс изображения GD.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
 * Convert raw image data to a GD resource
 *
 * @param string $data Binary image data to parse
 * @return resource A GD image resource identifier
 */
protected function readImage($data) {
 
    // read in the original image
    $src = imagecreatefromstring($data);
 
    if(!$src) {
        throw new Exception(«Error reading image»);
    }
 
    // get the dimensions
    $width = imagesx($src);
    $height = imagesy($src);
 
    // create a blank true color image of the same size
    $img = imagecreatetruecolor($width, $height);
 
    // copy the original image to this new canvas
    imagecopy($img, $src, 0, 0, 0, 0, $width, $height);
 
    // discard the source image
    imagedestroy($src);
 
    return $img;
}

Чтобы описать, что происходит выше:

  1. Данные изображения преобразуются в ресурс GD с помощью функции imagecreatefromstring () .
  2. Размеры изображения записываются с использованием imagesx () и imagesy () .
  3. Новое пустое изображение истинного цвета с imagecreatetruecolor () же размерами создается с помощью imagecreatetruecolor () .
  4. Исходное изображение копируется в новое изображение с помощью функции imagecopy () . В результате получается оригинальная цветная версия исходного изображения независимо от исходного цветового режима.
  5. Исходный ресурс изображения уничтожается с помощью imagedestroy () и возвращается дескриптор нового изображения.

Теперь, когда мы можем загружать изображения и создавать ресурс GD, нам нужен метод для сохранения изображений в файловой системе. Вот метод, который сохраняет предоставленное изображение как файл PNG с указанным именем, используя imagepng () :

01
02
03
04
05
06
07
08
09
10
11
12
13
/**
 * Save a GD image resource to a PNG file
 *
 * @param resource $img GD image resource identifier
 * @param string $name Name of the image
 * @return string Path to the saved image
 */
protected function saveImage($img, $name) {
    $path = $this->path .
    imagepng($img, $path);
    imagedestroy($img);
    return $path;
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
 * Generate previews for each image filter
 *
 * @return array Associative array of image previews
 */
public function generatePreviews() {
 
    // we need valid user info to know whose avatar to handle
    if(!$this->isAuthed()) {
        throw new Exception(«Requires oauth authorization»);
    }
    $username = $this->userdata->screen_name;
 
    // cache the raw data to use
    $data = $this->download($this->getImageURL());
 
    // copy the original image
    $img = $this->readImage($data);
    $this->saveImage($img, $username . «_orig»);
 
    // array to hold the list of previews
    $images = array();
 
    // loop through each filter to generate previews
    foreach($this->filters as $filter_name => $filter) {
        $img = $this->readImage($data);
        imagefilter($img, $filter);
        $images[$filter_name] = $this->saveImage($img, $username . «_’ . $filter_name);
    }
 
    return $images;
}

Первое, что мы делаем, это проверяем, что пользователь прошел проверку подлинности, а затем подбираем имя пользователя для использования в именах файлов.

1
2
3
4
5
// we need valid user info to know whose avatar to handle
if(!$this->isAuthed()) {
    throw new Exception(«Requires oauth authorization»);
}
$username = $this->userdata->screen_name;

Затем мы загружаем изображение пользователя, используя методы getImageURL() и download() которые мы создали. Эти данные будут неоднократно использоваться для каждого предварительного просмотра, поэтому мы сохраняем их в переменной $data .

1
2
// cache the raw data to use
$data = $this->download($this->getImageURL());

Далее мы сохраняем неизмененную копию с суффиксом _orig. Это для визуального сравнения позже.

1
2
3
// copy the original image
$img = $this->readImage($data);
$this->saveImage($img, $username . «_orig»);

В последней части метода мы перебираем фильтры изображений, перечисленные в свойстве $filters , создавая изображение для каждого фильтра. На каждой итерации мы создаем изображение и применяем imagefilter () , которая принимает одну из констант, перечисленных в массиве $filters . Затем для каждого сохраняемого изображения мы добавляем его путь к ассоциативному массиву (используя имя фильтра в качестве ключа), который этот метод возвращает в конце.

01
02
03
04
05
06
07
08
09
10
11
// array to hold the list of previews
$images = array();
 
// loop through each filter to generate previews
foreach($this->filters as $filter_name => $filter) {
    $img = $this->readImage($data);
    imagefilter($img, $filter);
    $images[$filter_name] = $this->saveImage($img, $username . «_’ . $filter_name);
}
 
return $images;

Следующая часть нашего потока приложений просит пользователя подтвердить свой выбор, поэтому нам нужен способ найти конкретный предварительный просмотр. Вот простой способ получить путь к изображению на основе параметра, переданного в качестве параметра, по умолчанию к исходному изображению:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
/**
 * Get the path to a previously generated preview
 *
 * @param string $filter The image filter to get the preview for
 * @return string The path to the preview file or null if not found
 */
public function getPreview($filter = ‘orig») {
    if(!$this->isAuthed()) {
        throw new Exception(«Requires oauth authorization»);
    }
    $path = $this->path .
    if(file_exists($path)) {
        return $path;
    }
    return null;
}

Завершающим этапом нашего потока приложений является фактическое изменение аватара пользователя. Сначала нам нужен метод, чтобы получить полноразмерное изображение и применить к нему определенный фильтр. Вот:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
 * Process the user’s full avatar using one of the filters
 *
 * @param string $filter The filter to apply to the image
 * @return string Path to the output file
 */
protected function processImage($filter = «grayscale») {
 
    // make sure the filter exists
    $filter = strtolower($filter);
    if(!array_key_exists($filter, $this->filters)) {
        throw new Exception(«Unsupported image filter»);
    }
 
    $username = $this->userdata->screen_name;
 
    // get the full sized avatar
    $data = $this->download($this->getOriginalImageURL());
    $img = $this->readImage($data);
 
    // apply the filter to the image
    imagefilter($img, $this->filters[$filter]);
 
    // save the image and return the path
    return $this->saveImage($img, $username . «_’ . $filter . «_full»);
}

Это должно быть легко понять, поскольку оно очень похоже на метод generatePreviews() . Он принимает параметр для указания фильтра изображения и проверяет его наличие. Затем он загружает исходное изображение и применяет к нему фильтр, возвращая путь к сгенерированному изображению в качестве возвращаемого значения.

Теперь нам нужен метод, который фактически отправляет сгенерированное изображение в Twitter, обновляя аватар пользователя. Этот метод вызывает метод processImage() для создания изображения и загружает его в Twitter с помощью метода API 1 / account / update_profile_image :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
 * Update user’s avatar with a filtered version
 *
 * @param string $filter The filter to use
 * @return bool Operation successful
 */
public function commitAvatar($filter) {
    if(!$this->isAuthed()) {
        throw new Exception("Requires oauth authorization");
    }
 
    // generate the image and get the path
    $path = $this->processImage($filter);
    if(file_exists($path)) {
 
        // send a multipart POST request with the image file data
        $this->tmhOAuth->request("POST", $this->tmhOAuth->url("1/account/update_profile_image"), array(
            // format: @local/path.png;type=mime/type;filename=file_name.png
            'image' => '@' . $path . ';type=image/png;filename=' . basename($path)
        ), true, true);
 
        return ($this->tmhOAuth->response["code"] == 200);
    }
 
    return false;
}

Сложной частью здесь является фактический POST-запрос tmhOAuth, который представляет собой многоэлементный запрос, содержащий необработанные данные изображения. Для этого мы должны установить последний параметр tmhOAuth::request()метода trueи передать imageпеременную в специальном формате:

@ [ path_to_image ]; type = [ mime_type ]; filename = [ file_name ]

Например, если мы хотим загрузить tmp / username_grayscale_full.png, значение будет равно @tmp/username_grayscale_full.png;type=image/png;filename=username_grayscale_full.png.

Вот снова эта часть кода:

1
2
3
4
5
// send a multipart POST request with the image file data
$this->tmhOAuth->request("POST", $this->tmhOAuth->url("1/account/update_profile_image"), array(
    // format: @local/path.png;type=mime/type;filename=file_name.png
    'image' => '@' . $path . ';type=image/png;filename=' . basename($path)
), true, true);

Побочным эффектом всех этих манипуляций с файлами является то, что многие временные файлы остаются позади. Вот метод для очистки временного каталога:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
 * Delete leftover image files
 */
public function cleanupFiles() {
 
    // file to track when we last checked
    $flag = $this->path . "/.last_check';
 
    $time = time();
 
    // have we checked within the last hour?
    if(!file_exists($flag) || $time - filemtime($flag) > 3600) {
 
        // get an array of PNG files in the directory
        $files = glob($this->path . "/*.png");
 
        // loop through files, deleting old files (12+ hours)
        foreach($files as $file) {
            if($time - filemtime($file) > 60*60*12) {
                unlink($file);
            }
        }
 
        // update the timestamp of our flag file
        touch($flag);
    }
}

Это просто перебирает файлы PNG, удаляя те, которым больше 12 часов. Он также проверяет, сколько времени прошло с тех пор, как мы проверили, используя отметку времени в файле .last_check, что позволяет нам ограничить проверку до одного в час. Таким образом, мы можем вызывать этот метод при каждом запросе, не тратя ресурсы.

Примечание: мы используем glob()функцию в PHP, которая является простым способом получить массив файлов, соответствующих шаблону.



У нас есть все компоненты приложения вместе, так что теперь все, что нам нужно, это пользовательский интерфейс. Весь код будет помещен в файл index.php в корневом каталоге. Начнем с включения библиотек и установки конфигурации:

01
02
03
04
05
06
07
08
09
10
11
<?php
 
// include our libraries
include 'lib/tmhOAuth.php';
include 'lib/TwitterApp.php';
include 'lib/TwitterAvatars.php';
 
// set the consumer key and secret
define("CONSUMER_KEY", 'qSkJum23MqlG6greF8Z76A");
define("CONSUMER_SECRET", 'Bs738r5UY2R7e5mwp1ilU0voe8OtXAtifgtZe9EhXw");
?>

Примечание: обязательно замените CONSUMER_KEYи CONSUMER_SECRETна свой.

Мы собираемся поместить наш код в блок try-catch, чтобы мы могли корректно обрабатывать любые ошибки, присваивая их сообщения $errorпеременной.

1
2
3
4
5
6
7
try {
     
} catch(Exception $e) {
 
    // catch any errors that may occur
    $error = $e;
}

Внутри блока try мы можем начать писать наш код, начиная с инициализации объекта TwitterAvatars, вызываемого $taс помощью настроенного объекта tmhOAuth:

1
2
3
4
5
6
7
8
// our tmhOAuth settings
   $config = array(
       'consumer_key' => CONSUMER_KEY,
       'consumer_secret' => CONSUMER_SECRET
   );
 
   // create a new TwitterAvatars object
   $ta = new TwitterAvatars(new tmhOAuth($config));

Мы можем удалить все старые временные файлы на этом этапе:

1
2
// check for stale files
   $ta->cleanupFiles();

Затем мы проверяем, прошел ли пользователь аутентификацию или, если это не удалось, запросил ли пользователь аутентификацию, и в этом случае мы запускаем процесс, вызывая auth()метод:

01
02
03
04
05
06
07
08
09
10
// check our authentication status
   if($ta->isAuthed()) {
        
   }
   // did the user request authorization?
   elseif(isset($_POST["auth"])) {
 
       // start authentication process
       $ta->auth();
   }

Если пользователь аутентифицирован, нам нужно проверить, была ли выбрана опция, в противном случае мы будем генерировать превью:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
// check our authentication status
   if($ta->isAuthed()) {
 
       // has the user selected an option?
       if(isset($_POST["filter"])) {
            
       }
       // generate previews if the user has not chosen
       else {
 
           // $previews will be a list of images
           $previews = $ta->generatePreviews();
       }
   }

Если была выбрана опция, нам нужно получить пути к старым и новым изображениям для отображения:

1
2
3
4
5
6
7
// has the user selected an option?
       if(isset($_POST["filter"])) {
 
           // get the image paths for display
           $original = $ta->getPreview();
           $newimage = $ta->getPreview($_POST["filter"]);
       }

Наконец, мы проверяем, подтвердил ли пользователь свой выбор, и применяем изменения. Мы также отправляем твит по запросу и устанавливаем $successпеременную в true:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
// has the user selected an option?
       if(isset($_POST["filter"])) {
 
           // is the user sure?
           if(isset($_POST["confirm"])) {
 
               // change the user's avatar
               $ta->commitAvatar($_POST["filter"]);
 
               // tweet if the user chose to
               if(isset($_POST["tweet"])) {
                   $ta->sendTweet("I just updated my avatar using Avatar Effects...");
               }
 
               $success = true;
           }
 
           // get the image paths for display
           $original = $ta->getPreview();
           $newimage = $ta->getPreview($_POST["filter"]);
       }

Вот что мы имеем до сих пор:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?php
 
// include our libraries
include 'lib/tmhOAuth.php';
include 'lib/TwitterApp.php';
include 'lib/TwitterAvatars.php';
 
// set the consumer key and secret
define("CONSUMER_KEY", 'qSkJum23MqlG6greF8Z76A");
define("CONSUMER_SECRET", 'Bs738r5UY2R7e5mwp1ilU0voe8OtXAtifgtZe9EhXw");
 
try {
 
    // our tmhOAuth settings
    $config = array(
        'consumer_key' => CONSUMER_KEY,
        'consumer_secret' => CONSUMER_SECRET
    );
 
    // create a new TwitterAvatars object
    $ta = new TwitterAvatars(new tmhOAuth($config));
 
    // check for stale files
    $ta->cleanupFiles();
 
    // check our authentication status
    if($ta->isAuthed()) {
 
        // has the user selected an option?
        if(isset($_POST["filter"])) {
 
            // is the user sure?
            if(isset($_POST["confirm"])) {
 
                // change the user's avatar
                $ta->commitAvatar($_POST["filter"]);
 
                // tweet if the user chose to
                if(isset($_POST["tweet"])) {
                    $ta->sendTweet("I just updated my avatar using Avatar Effects...");
                }
 
                $success = true;
            }
 
            // get the image paths for display
            $original = $ta->getPreview();
            $newimage = $ta->getPreview($_POST["filter"]);
        }
        // generate previews if the user has not chosen
        else {
 
            // $previews will be a list of images
            $previews = $ta->generatePreviews();
        }
    }
    // did the user request authorization?
    elseif(isset($_POST["auth"])) {
 
        // start authentication process
        $ta->auth();
    }
} catch(Exception $e) {
 
    // catch any errors that may occur
    $error = $e;
}
?>

После кода PHP мы выведем соответствующий HTML, начиная с этого шаблона, который устанавливает заголовок и основной заголовок:

01
02
03
04
05
06
07
08
09
10
11
<!DOCTYPE html>
<html>
  <head>
    <meta charset=»UTF-8″>
    <title>Twitter Avatar Effects</title>
    <link rel=»stylesheet» href=»css/style.css»>
  </head>
  <body>
      <h1>Twitter Avatar Effects</h1>
  </body>
</html>

Вот где мы отображаем форму с входными данными для каждого предварительного просмотра:

01
02
03
04
05
06
07
08
09
10
11
<?php if(isset($previews)): ?>
     <h2>Choose your weapon...</h2>
     <form action=»index.php» method=»post»>
     <?php foreach($previews as $filter => $path): ?>
         <input type="image" src="<?php echo $path; ?>"
                alt="<?php echo ucfirst($filter); ?>"
                 width="73" height="73"
                name="filter" value="<?php echo $filter; ?>">
     <?php endforeach;
     </form>
     <p>Select one of the images above to change your Twitter avatar.</p>

Вот страница успеха:

1
2
3
4
<?php elseif(isset($success)): ?>
     <h2>Success! Your Twitter avatar is now:</h2>
     <img src="<?php echo $newimage; ?>" alt="Your Avatar" width="73" height="73">
     <p><a href="http://twitter.com/<?php echo $ta->userdata->screen_name; ?>">Go see it</a></p>

Вот страница подтверждения, где мы показываем сравнение и предлагаем возможность отменить:

01
02
03
04
05
06
07
08
09
10
11
12
<?php elseif(isset($newimage)): ?>
     <h2>Are you sure?</h2>
     <img src="<?php echo $original; ?>" alt="Original" width="73" height="73">
     <span class="arrow">&rArr;</span>
     <img src="<?php echo $newimage; ?>" alt="<?php echo ucfirst($_POST["filter"]); ?>">
     <form action=»index.php» method=»post»>
         <input type="hidden" name="filter" value="<?php echo $_POST["filter"]; ?>">
         <input type="submit" name="confirm" value="Confirm">
         <a href="index.php">Cancel</a>
         <p><label>Tweet about your new avatar?
                 <input type="checkbox" name="tweet" value="true"></label></p>
     </form>

Обратите внимание, что форма подтверждения содержит выбранный фильтр в скрытом поле.

Если есть ошибка, мы показываем это:

1
2
<?php elseif(isset($error)): ?>
     <p>Error. <a href="index.php">Try again?</a></p>

По умолчанию отображается кнопка «Подключиться к Twitter» в качестве входного изображения (загрузите одно из изображений внизу этой страницы в каталог img):

1
2
3
4
5
6
7
<?php else: ?>
     <form action=»index.php» method=»post»>
         <input type="image" src="img/sign-in-with-twitter-l.png"
                alt="Connect to Twitter" name="auth" value="1">
     </form>
     <p>Connect to Twitter to use this app.</p>
 <?php endif;

Вот полный раздел HTML:


Вот некоторые основные CSS, чтобы интерфейс выглядел красиво, сохраненные в css / style.css :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
html {
    background-color: #eee;
    text-align: center;
    font-family: "Lucida Grande",Verdana, sans-serif;
    font-size: 16px;
    color: #224;
}
body {
    width: 700px;
    margin: 30px auto;
    background-color: #acf;
    padding: 10px;
    border-radius: 10px;
    -moz-border-radius: 10px;
    -webkit-border-radius: 10px;
}
p {
    font-size: 1em;
}
h1 {
    font-size: 2em;
}
h2 {
    font-size: 1.6em;
}
.arrow {
    font-size: 4em;
    font-weight: bold;
}

Вот видео, которое детализирует, как должно выглядеть наше заполненное приложение:


Если вы полностью следовали этому руководству, у вас должно быть довольно хорошее понимание OAuth и того, что нужно для создания простого веб-приложения Twitter. Использовать Twitter API легко, если вы понимаете основные понятия, особенно если вы используете библиотеку, такую ​​как tmhOAuth, для обработки мелких деталей.

Простой пример, который мы создали в этом уроке, может быть легко изменен или расширен для любых действий. Так что, если у вас есть отличная идея для нового крутого приложения Twitter, не стесняйтесь использовать его в качестве основы.

Спасибо за прочтение. Если у вас есть какие-либо вопросы или комментарии по поводу этого урока, пожалуйста, пишите!