Hello everyone!

We have been investing plenty of personal time and energy for many years to share our knowledge with you all. However, we now need your help to keep this blog running. All you have to do is just click one of the adverts on the site, otherwise it will sadly be taken down due to hosting etc. costs. Thank you.

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.


Composer update


Install "abraham/twitteroauth" : "0.5.3" package with composer. Visit homepage and GitHub pages for more info.


Twitter OAuth details


You need to obtain your OAuth details with steps below.



Parameters.yml


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

Controllers.yml


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.yml


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

Factories.yml


services:
application_backend.factory.twitter:
class: Application\BackendBundle\Factory\TwitterFactory

AbstractController.php


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()];
}
}

TwitterController.php


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);
}
}

TwitterServiceInterface.php


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);
}

TwitterService.php


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;
}
}

TwitterApi.php


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);
}
}

TwitterFactoryInterface.php


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);
}

TwitterFactory.php


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;
}
}

Error.php


namespace Application\BackendBundle\Model\Twitter;

class Error
{
/**
* @var array
*/
public $errors = [];
}

NewTweet.php


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 = [];
}

Tweet.php


namespace Application\BackendBundle\Model\Twitter;

use DateTime;

class Tweet
{
/**
* @var int
*/
public $id;

/**
* @var datetime
*/
public $createdAt;

/**
* @var string
*/
public $text;
}

TweetList.php


namespace Application\BackendBundle\Model\Twitter;

class TweetList
{
/**
* @var Tweet[]
*/
public $tweets = [];
}

TwitterException.php


namespace Application\BackendBundle\Exception;

use RuntimeException;

/**
* Triggered when twitter related error occurs.
*/
class TwitterException extends RuntimeException
{
}

Examples


Chrome postman extension has been used for the tests. You can add more parameters to the URI so read the Twitter API documentation.


Listing tweets


# 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"
}
]
}

Create a new tweet with images attached to it


# 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."
}
]
}