





In this tutorial we will build a fun little tool for internal/personal usage that posts random tweets at pre-defined times with Laravel and the Twitter API. We will use some of Laravel 4’s powerful features such as Artisan commands, Eloquent ORM and some external packages to make creating an application to post tweets with Laravel 4 quick and easy!

Our application will:

Allow us to insert a tweet into the database

Allow us to insert tags into the database

Link tags to a tweet

Have a command that grabs a random tweet and appends tags in a random order

To post random tweets with Laravel we will need the following:

Our Twitter API keys

The thujohn/twitter-l4 package. (Twitter API wrapper for Laravel 4)

Laravel 4 generators package by Jeffrey Way

Create a database to contain tweets and tags

Calculate the length of a tweet with a URL in it.

Attach tags to a tweet.

Create an Artisan command to post tweets

Set a cron-job to call the Artisan command.

Setting up the project

First things first, you will need to create an application in your Twitter developer console here. Next we need to get the Laravel 4 Twitter API wrapper. We also want to make our life a bit easier and get the Laravel 4 generators by Jeffrey Way which will allow us to automate the creation of our basic code and rapidly scaffold our Laravel 4 tweet poster. To do this, edit your composer.json file and add the packages.

composer.json

"require": { "laravel/framework": "4.1.*", "way/generators": "dev-master", "thujohn/twitter": "dev-master", },

Next, run composer update to install these packages. Open your app.php config file and edit the file to be able to use the Twitter API wrapper and the Laravel 4 generators.

app/config/app.php

'providers' => array( ... 'Way\Generators\GeneratorsServiceProvider', 'Thujohn\Twitter\TwitterServiceProvider', ),

'aliases' => array( ... 'Twitter' => 'Thujohn\Twitter\TwitterFacade', ),

Now in order to be able to use the Twitter API we must fill out the config file of the Twitter API wrapper. In your terminal, run the command php Artisan config:publish thujohn/twitter in order to publish the configuration files to the application level (your app folder).

You can now fill out the configuration file with your own Twitter API information:

app/config/packages/thujohn/twitter/config.php

<?php return array( 'API_URL' => 'api.twitter.com', 'API_VERSION' => '1.1', 'USE_SSL' => true, 'CONSUMER_KEY' => 'YOUR CONSUMER/CLIENT KEY', 'CONSUMER_SECRET' => 'YOUR CONSUMER/CLIENT SECRET', 'ACCESS_TOKEN' => 'YOUR ACCESS TOKEN', 'ACCESS_TOKEN_SECRET' => 'YOUR TOKEN SECRET', );

Now that all of that is done there is one thing left to do to complete our configuration. Edit the app/config/database.php file with the information of your preferred database. Once this is done, we can move on to the actual code!

Defining the database and generating scaffolds

Now that everything is set up we can start thinking about our database and generate scaffolds. Scaffolds are “templates” that quickly prototype your application. Thanks to Jeffrey Way’s generators we only have to define our table columns and the generators do the rest for us!

We will need two tables, and only one field in each table (aside from the ID of course)! Aside from that we will need one extra pivot table which links the tweets and tags together, giving us three tables in total.

Let’s create our tweets scaffold. In your command line, enter the following Artisan commands:

php artisan generate:scaffold tweet --fields="body:text"

php artisan generate:scaffold tag --fields="tag:text"

php artisan generate:pivot posts tags

Now we have some basic files and routes generated for us. In fact, we only have to make some small modifications now to be able to link tags to our tweets. In your tweets model, add a belongsToMany relationship.

The generator also added the following routes for us:

Route::resource('tweets', 'TweetsController'); Route::resource('tags', 'TagsController');

app/models/Tweet.php

<?php class Tweet extends Eloquent { protected $guarded = array(); public static $rules = array( 'body' => 'required' ); public function tags() { return $this->belongsToMany('Tag'); } }

You can now use the tags() function to get all the tags that are attached to a tweet. Of course we still need to be able to attach tags to our tweets. First things first, go to the tags view and create some tags there! Remember the generator already made the route for us, so we can simply go to http://[YOURAPP]/tags to create them. Same thing applies for the tweets.

Once you added some tags, we need to be able to select those tags in our tweets create view.

Modifying the controllers and views

In the TweetsController we need to select all tweets so we can display them in our view in a multiselect select element. To do this, we need to modify the create() function.

app/controllers/TweetsController.php

/** * Show the form for creating a new resource. * * @return Response */ public function create() { $tags = Tag::lists('tag', 'id'); return View::make('tweets.create', compact('tags')); }

What we did here was get all the tags in a simple array form with the tag field as value, and the id as key.

And while we are here, we need to make sure our tags get stored alongside our tweets!

app/controllers/TweetsController.php

/** * Store a newly created resource in storage. * * @return Response */ public function store() { $input = array_except(Input::all(), array('tags')); $validation = Validator::make($input, Tweet::$rules); if ($validation->passes()) { $tweet = $this->tweet->create($input); $tags = Input::get('tags'); $tweet->tags()->sync($tags); return Redirect::route('tweets.index'); } return Redirect::route('tweets.create') ->withInput() ->withErrors($validation) ->with('message', 'There were validation errors.'); }

What we do here, is first save the tweet. If the tweet was saved, we use the sync() function to synchronize the links between our tweet and the tags. This function will add or remove rows as needed from the pivot table.







Now, we also need to do the exact same thing for edit() and update().

app/controllers/TweetsController.php

/** * Show the form for editing the specified resource. * * @param int $id * @return Response */ public function edit($id) { $tweet = $this->tweet->find($id); $tags = Tag::lists('tag', 'id'); $tweetTags = $tweet->tags()->get(); $selectedTags = array(); foreach ($tweetTags as $tag) { $selectedTags[] = $tag->id; } if (is_null($tweet)) { return Redirect::route('tweets.index'); } return View::make('tweets.edit', compact('tweet', 'tags', 'selectedTags')); } /** * Update the specified resource in storage. * * @param int $id * @return Response */ public function update($id) { $input = array_except(Input::all(), array('_method', 'tags')); $validation = Validator::make($input, Tweet::$rules); if ($validation->passes()) { $tweet = $this->tweet->find($id); $tweet->update($input); $tags = Input::get('tags'); $tweet->tags()->sync($tags); return Redirect::route('tweets.show', $id); } return Redirect::route('tweets.edit', $id) ->withInput() ->withErrors($validation) ->with('message', 'There were validation errors.'); }

For the views, we simply add an extra select box with the multiselect property. We do this for both the files create.blade.php and edit.blade.php files.

app/views/tweets/create.blade.php

@extends('layouts.scaffold') @section('main') <h1>Create Tweet</h1> {{ Form::open(array('route' => 'tweets.store')) }} <ul> <li> {{ Form::label('body', 'Body:') }} {{ Form::textarea('body', null, array('style' => 'width: 850px;')) }} </li> <li> {{ Form::select('tags[]', $tags, null, array('multiple', 'size' => 10)) }} </li> <li> {{ Form::submit('Submit', array('class' => 'btn btn-info')) }} </li> </ul> {{ Form::close() }} @if ($errors->any()) <ul> {{ implode('', $errors->all('<li class="error">:message</li>')) }} </ul> @endif @stop

app/views/tweets/create.blade.php

@extends('layouts.scaffold') @section('main') <h1>Edit Tweet</h1> {{ Form::model($tweet, array('method' => 'PATCH', 'route' => array('tweets.update', $tweet->id))) }} <ul> <li> {{ Form::label('body', 'Body:') }} {{ Form::textarea('body', $tweet->body, array('style' => 'width: 850px;')) }} </li> <li> {{ Form::select('tags[]', $tags, $selectedTags, array('multiple', 'size' => 10)) }} </li> <li> {{ Form::submit('Update', array('class' => 'btn btn-info')) }} {{ link_to_route('tweets.show', 'Cancel', $tweet->id, array('class' => 'btn')) }} </li> </ul> {{ Form::close() }} @if ($errors->any()) <ul> {{ implode('', $errors->all('<li class="error">:message</li>')) }} </ul> @endif @stop

As you can see this was really short and simple! You can now create or edit a tweet, and select some tags to be appended in a random order to the tweet. That was pretty easy huh? Now let’s move on to the final, and best part: Posting random tweets with an Artisan command.

Creating an Artisan command

To create an Artisan command you must go to your command line and execute the following command:

php artisan command:make TweetCommand --command=tweet:post

This will generate a new Artisan command file located in the commands folder. We need to fill out a name and description for our command now.



app/commands/TweetCommand.php

... ** * The console command name. * * @var string */ protected $name = 'tweet:post'; /** * The console command description. * * @var string */ protected $description = 'Post a random tweet.'; ...

Now let’s think about how we can get a random tweet from our database the best possible way. ORDER BY RAND() is a bit slow so it is better to do a count of the records first, and then get a single row and use LIMIT.

// Count the numbe of tweets in the database $numTweets = Tweet::count(); // Get a random row number $rnd = rand(0,$numTweets-1); // Fetch a random tweet. $randomTweet = Tweet::take(1)->skip($rnd)->with('tags')->first();

We now have a tweet and its tags. We will now prepare our data. I chose to add a new line and a separator inbetween my tweet and tags. The tags also have to be shuffled in order to keep our tweets more unique.

$tags = shuffle($randomTweet['tags']->toArray()); $tweetText = $randomTweet['body'] . "

---

";

Now, we need to detect if there are any URLs in the tweet. This affects our tweet length limit since Twitter shortens URLs to 21 characters for HTTP and 22 for HTTPS. We will assume in this code that they are HTTP URLs.

// Get the length of the tweet without a URL preg_match('(http[s]?:[^\s]*)', $tweetText, $match); $match = $match[0]; // Remove the URL from the tweet and put it in a temporary variable // in order to calculate the length of the tweet with shortened URL $tmpString = str_replace($match, '', $tweetText); $length = strlen($tmpString) + 21;

Finally all that is left is to append the tags to the tweet body until you reach the length limit, and post that tweet! Everything put together:

app/commands/TweetCommand.php

... /** * Execute the console command. * * @return mixed */ public function fire() { $numTweets = Tweet::count(); $rnd = rand(0,$numTweets-1); $randomTweet = Tweet::take(1)->skip($rnd)->with('tags')->first(); $tags = $randomTweet['tags']->toArray(); // Put the tweet and a separator in a variable. $tweetText = $randomTweet['body'] . "

---

"; // Get the length of the tweet without a URL preg_match('(http[s]?:[^\s]*)', $tweetText, $match); $match = $match[0]; $tmpString = str_replace($match, '', $tweetText); $length = strlen($tmpString) + 21; // Randomize the order of the tags shuffle($tags); // Append tags to the tweet (without making the tweet too long) foreach ($tags as $key => $tag) { $pre = ''; if ($key) { $pre = ' '; } $tagLength = strlen(' ' . $tag['tag']); if (($length + $tagLength) < 140) { $tweetText .= $pre . $tag['tag']; $length += $tagLength; } else { break; } } // Post the tweet using the Twitter API wrapper. Twitter::postTweet( array( 'status' => $tweetText, 'format' => 'json' ) ); exit; } ...

Now if you use the command php artisan tweet:post you will post a random tweet from the database!

All that you have to do now is use crontab to execute the command every X minutes/hours/…