26/06/2015 - SYMFONY
This example follows API style symfony conventions so you better check other posts to see how it is done otherwise you won't be able run example below unless you handle request and response model mapping differently.
Install "abraham/twitteroauth" : "0.5.3"
package with composer. Visit homepage and GitHub pages for more info.
You need to obtain your OAuth details with steps below.
We're going to post images when creating tweets so lets define the path to the dummy images.
parameters:
twitter.image.directory: '%kernel.root_dir%/../build/dummy/image/'
#football/build/dummy/image/png.png
#football/build/dummy/image/jpg.jpg
services:
application_backend.controller.abstract:
class: Application\BackendBundle\Controller\AbstractController
abstract: true # Abstract enabled
arguments:
- @serializer # Enabled by JMS Serializer Bundle
- @validator # Enabled by the application by default
- @doctrine_common_inflector # Enabled by user
application_backend.controller.twitter:
class: Application\BackendBundle\Controller\TwitterController
parent: application_backend.controller.abstract # Parent abstract class
arguments:
- @application_backend.service.twitter # Twitter service
# Application level services
doctrine_common_inflector:
class: Doctrine\Common\Inflector\Inflector
services:
application_backend.service.twitter:
class: Application\BackendBundle\Service\TwitterService
arguments:
- @application_backend.util.twitter # Twitter utility class
- @application_backend.factory.twitter # Twitter factory class
application_backend.util.twitter:
class: Application\BackendBundle\Util\TwitterApi
arguments:
- %twitter.image.directory% # Where tweet images stored
services:
application_backend.factory.twitter:
class: Application\BackendBundle\Factory\TwitterFactory
namespace Application\BackendBundle\Controller;
use Doctrine\Common\Inflector\Inflector;
use JMS\Serializer\SerializationContext;
use JMS\Serializer\SerializerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
abstract class AbstractController
{
protected $serializer;
protected $validator;
protected $inflector;
public function __construct(
SerializerInterface $serializer,
ValidatorInterface $validator,
Inflector $inflector
) {
$this->serializer = $serializer;
$this->validator = $validator;
$this->inflector = $inflector;
}
/**
* @param string $responseData
* @param int $status
*
* @return Response
*/
protected function createJsonResponse($responseData = '', $status = 200)
{
$context = new SerializationContext();
$context->setSerializeNull(false);
$jsonResponse = $this->serializer->serialize($responseData, 'json', $context);
return (new Response(
$jsonResponse,
$status,
['Content-Type' => 'application/json']
));
}
/**
* @param $errors
* @param int $status
*
* @return Response
*/
protected function createJsonErrorResponse($errors, $status = 400)
{
$errorData = ['errors' => []];
foreach ($errors as $error) {
if ($error instanceof ConstraintViolationInterface) {
$preparedError = $this->getErrorFromValidation($error, $errorData);
} else {
$preparedError = ['key' => count($errorData['errors']), 'value' => $error];
}
$errorData['errors'][$preparedError['key']] = $preparedError['value'];
}
return $this->createJsonResponse($errorData, $status);
}
/**
* @param string $content
* @param string $class
*
* @return mixed|Response
*/
protected function validate($content, $class)
{
$content = $this->serializer->deserialize(
$content,
$class,
'json'
);
$errors = $this->validator->validate($content);
if (count($errors)) {
return $this->createJsonErrorResponse($errors);
}
return $content;
}
/**
* @param ConstraintViolationInterface $error
*
* @return mixed
*/
private function getErrorFromValidation($error)
{
$properties = $this->inflector->tableize($error->getPropertyPath());
return ['key' => $properties, 'value' => $error->getMessage()];
}
}
namespace Application\BackendBundle\Controller;
use Application\BackendBundle\Exception\TwitterException;
use Application\BackendBundle\Service\TwitterServiceInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use JMS\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Doctrine\Common\Inflector\Inflector;
use Exception;
/**
* @Route("twitter", service="application_backend.controller.twitter")
*/
class TwitterController extends AbstractController
{
private $twitterService;
public function __construct(
SerializerInterface $serializer,
ValidatorInterface $validator,
Inflector $inflector,
TwitterServiceInterface $twitterService
) {
parent::__construct($serializer, $validator, $inflector);
$this->twitterService = $twitterService;
}
/**
* @param Request $request
*
* @Route("")
* @Method({"GET"})
*
* @return Response
* @throws TwitterException
*/
public function listAction(Request $request)
{
try {
$tweets = $this->twitterService->getTweets(
$request->query->get('screen_name'),
$request->query->get('count', 25),
$request->query->get('exclude_replies', true),
$request->query->get('since_id', null),
$request->query->get('max_id', null)
);
} catch (Exception $e) {
throw new TwitterException($e->getMessage());
}
return $this->createJsonResponse($tweets);
}
/**
* @param Request $request
*
* @Method({"POST"})
* @Route("")
*
* @return Response
*/
public function createTweetAction(Request $request)
{
$tweet = $this->validate(
$request->getContent(),
'Application\BackendBundle\Model\Twitter\NewTweet'
);
if ($tweet instanceof Response) {
return $tweet;
}
try {
$response = $this->twitterService->createTweet($tweet);
} catch (Exception $e) {
throw new TwitterException($e->getMessage());
}
return $this->createJsonResponse($response);
}
}
namespace Application\BackendBundle\Service;
use Application\BackendBundle\Model\Twitter\Error;
use Application\BackendBundle\Model\Twitter\NewTweet;
interface TwitterServiceInterface
{
/**
* @param string $screenName
* @param int $count
* @param bool $excludeReplies
* @param int|null $sinceId
* @param int|null $maxId
*
* @return mixed
*/
public function getTweets(
$screenName,
$count = 25,
$excludeReplies = true,
$sinceId = null,
$maxId = null
);
/**
* @param NewTweet $newTweet
*
* @return bool|Error
*/
public function createTweet(NewTweet $newTweet);
}
namespace Application\BackendBundle\Service;
use Application\BackendBundle\Factory\TwitterFactoryInterface;
use Application\BackendBundle\Model\Twitter\NewTweet;
use Application\BackendBundle\Util\TwitterApi;
class TwitterService implements TwitterServiceInterface
{
private $twitterApi;
private $twitterFactory;
public function __construct(
TwitterApi $twitterApi,
TwitterFactoryInterface $twitterFactory
) {
$this->twitterApi = $twitterApi;
$this->twitterFactory = $twitterFactory;
}
public function getTweets(
$screenName,
$count = 25,
$excludeReplies = true,
$sinceId = null,
$maxId = null
) {
$payload['screen_name'] = $screenName;
$payload['count'] = $count;
$payload['exclude_replies'] = $excludeReplies;
if (!is_null($sinceId)) {
$payload['since_id'] = $sinceId;
}
if (!is_null($maxId)) {
$payload['max_id'] = $maxId;
}
$tweets = $this->twitterApi->getTweets($payload);
return $this->twitterFactory->createTweetList($tweets);
}
public function createTweet(NewTweet $newTweet)
{
$result = $this->twitterApi->createTweet($newTweet);
if ($result->errors) {
return $this->twitterFactory->createError($result->errors);
}
return;
}
}
namespace Application\BackendBundle\Util;
use Abraham\TwitterOAuth\TwitterOAuth;
use Application\BackendBundle\Model\Twitter\NewTweet;
/**
* Example below uses our own Twitter account.
* To post a tweet to someone elses or multiple accounts, we need associated oAuth Tokens to those accounts.
*/
class TwitterApi
{
const API_DOMAIN = 'https://api.twitter.com/1.1/';
const CONSUMER_KEY = '8TcNEf477DHV***************';
const CONSUMER_SECRET = 'sw0QwysUZRBFPVOdHIhzWNsvL**************';
const ACCESS_TOKEN = '3159342661-1oNWsZmekO36mJKWKP**************';
const ACCESS_SECRET_TOKEN = 'aJcV4JgxPRzGVKL7PLZcrtsF**************';
private $twitterImageDirectory;
private $connection;
public function __construct($twitterImageDirectory)
{
$this->twitterImageDirectory = $twitterImageDirectory;
$this->connection = new TwitterOAuth(
self::CONSUMER_KEY,
self::CONSUMER_SECRET,
self::ACCESS_TOKEN,
self::ACCESS_SECRET_TOKEN
);
}
public function getTweets(array $payload)
{
return $this->connection->get('statuses/user_timeline', $payload);
}
public function createTweet(NewTweet $newTweet)
{
$parameters['status'] = $newTweet->text;
if ((bool) $newTweet->images) {
$parameters['media_ids'] = $this->uploadImages($newTweet->images);
}
return $this->connection->post("statuses/update", $parameters);
}
/**
* @param array $images
*
* @return string
*/
private function uploadImages(array $images)
{
$mediaIds = null;
foreach ($images as $image) {
if (file_exists($this->twitterImageDirectory.$image)) {
$upload = $this->connection->upload(
'media/upload',
['media' => $this->twitterImageDirectory.$image]
);
$mediaIds .= $upload->media_id_string.',';
}
}
return substr($mediaIds, 0, -1);
}
}
namespace Application\BackendBundle\Factory;
use Application\BackendBundle\Model\Twitter\Error;
use Application\BackendBundle\Model\Twitter\TweetList;
interface TwitterFactoryInterface
{
/**
* @param array $tweets
*
* @return TweetList
*/
public function createTweetList(array $tweets);
/**
* @param array $errors
*
* @return Error
*/
public function createError(array $errors);
}
namespace Application\BackendBundle\Factory;
use Application\BackendBundle\Model\Twitter\Error;
use Application\BackendBundle\Model\Twitter\Tweet;
use Application\BackendBundle\Model\Twitter\TweetList;
use stdClass;
class TwitterFactory implements TwitterFactoryInterface
{
public function createTweetList(array $tweets)
{
$tweetList = new TweetList();
foreach ($tweets as $tweet) {
$tweetList->tweets[] = $this->getTweet($tweet);
}
return $tweetList;
}
public function createError(array $errors)
{
$errorList = new Error();
foreach ($errors as $error) {
$errorList->errors[] = [
'code' => $error->code,
'message' => $error->message,
];
}
return $errorList;
}
/**
* @param stdClass $tweetData
*
* @return Tweet
*/
private function getTweet(stdClass $tweetData)
{
$tweet = new Tweet();
$tweet->createdAt = date('Y-m-d H:i:s', strtotime(($tweetData->created_at)));
$tweet->id = $tweetData->id;
$tweet->text = $tweetData->text;
return $tweet;
}
}
namespace Application\BackendBundle\Model\Twitter;
class Error
{
/**
* @var array
*/
public $errors = [];
}
namespace Application\BackendBundle\Model\Twitter;
use Symfony\Component\Validator\Constraints as Assert;
use JMS\Serializer\Annotation as Serializer;
class NewTweet
{
/**
* @var string
*
* @Assert\NotBlank(message="Tweet message is required.")
* @Assert\Length(max="140", maxMessage="Tweet cannot be longer than 140 chars in length.")
*
* @Serializer\Type("string")
*/
public $text;
/**
* @var array
*
* @Serializer\Type("array")
*/
public $images = [];
}
namespace Application\BackendBundle\Model\Twitter;
use DateTime;
class Tweet
{
/**
* @var int
*/
public $id;
/**
* @var datetime
*/
public $createdAt;
/**
* @var string
*/
public $text;
}
namespace Application\BackendBundle\Model\Twitter;
class TweetList
{
/**
* @var Tweet[]
*/
public $tweets = [];
}
namespace Application\BackendBundle\Exception;
use RuntimeException;
/**
* Triggered when twitter related error occurs.
*/
class TwitterException extends RuntimeException
{
}
Chrome postman extension has been used for the tests. You can add more parameters to the URI so read the Twitter API documentation.
# GET http://football.local/app_test.php/backend/twitter?screen_name=YourScreenName&count=10&exclude_replies=true
# Response
{
"tweets": [
{
"id": 614525596304056300,
"created_at": "2015-06-26 21:08:14",
"text": "Test tweet with two images http://t.co/xxxxxxxx"
},
{
"id": 589094824239898600,
"created_at": "2015-04-17 16:55:25",
"text": "Just launched! 10 available now! http://t.co/bEixxxxxxxx"
},
{
"id": 589094662381707300,
"created_at": "2015-04-17 16:54:47",
"text": "Just launched! 20 available now! http://t.co/7ZUDxxxxxx"
}
]
}
# POST http://football.local/app_test.php/backend/twitter
# Request payload
{
"text": "Test tweet with two images",
"images": [
"png.png",
"jpg.jpg"
]
}
# Successful response
{}
# Error response
{
"errors": [
{
"code": 44,
"message": "media_ids parameter is invalid."
}
]
}