IronMQ and Laravel: Implementation

Share this article

Welcome back to the IronMQ and Laravel series – this is the second and final part in which we finalize our background-job enabled web app.

There are several tutorials out there related to queues already – for example: https://vimeo.com/64703617 where Taylor teaches you how to write to a file using queues. In this tutorial, we will try something different.

We will make a jobs table which has job_id, and the status of the job. When you put a job on the queue, the job status will be queued and when we receive the job, we set the status to running. Accordingly, after finishing it we will mark it finished.

Later, we will resize images.

Jobs Table

Step 1

Let’s create a jobs table:

php artisan migrate:make create_jobs_table

Go to app/database/migrations/xxxxx_create_jobs_table.php and add the following (This code is extracted from app/database/migrations/xxxxx_create_jobs_table.php, so edit your file accordingly):

public function up()
{
    Schema::create('jobs', function($table)
{
    $table->increments('id');
    $table->string('job_id');
    $table->enum('status', array('queued', 'running','finished'))->default('queued');
    $table->timestamps();
    });
}

public function down()
{
    Schema::drop('jobs');
}

Here we created a table with the columns job_id, status and timestamps. Run php artisan migrate to create the table.

Step 2

Create a file job.php in app/models with the following content:

<?php

class Job extends Eloquent {

    protected $table = 'jobs';

    protected $fillable = array('job_id', 'status');

}

Make a JobController using the following command:

php artisan controller:make JobController

and add the following in the app/routes.php file:

Route::resource('job','JobController');

Go to JobController populate the index method.

public function index()
{
    $jobs = Job::all();

    return View::make('job.index')->with('jobs',$jobs);
}

Before we go ahead we need to set up our views. First, let’s create a views/master.blade.php which will serve as a template.

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Laravel PHP Framework</title>
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
</head>
<body>

    <div class="container">
        <div class="row">
            <div class="col-md-12 well">
                Let's Learn Queues
            </div>

            @yield('content')
        </div>
    </div>

</body>
</html>

Let’s make a view for our jobs. Put the following content in app/views/job/index.blade.php

@extends('master')

@section('content')
<div class="col-md-12">
    <table class="table">
        <thead>
            <tr>
                <th>#</th>
                <th>Job ID</th>
                <th>Status</th>
            </tr>
        </thead>

        <tbody>
            @foreach($jobs as $job)
                <tr>
                    <td>{{ $job->id }}</td>
                <td>{{ $job->job_id }}</td>
                <td>{{ $job->status }}</td>
                </tr>
            @endforeach
        </tbody>

    </table>
</div>
@stop

As we have no jobs now, we won’t see anything if we go to http://localhost:8000/job.

Now let’s create a new job (where we write a string to a file) and then update the status of the job to ‘finished’.

public function create()
{
    $job_id = Queue::push('JobController@run',array('string' => 'Hello World '));

    Job::create(array('job_id' => $job_id));

    return Redirect::route('job.index');
}

Note that above we pushed a method ‘run’ onto the queue with some data. So we need to create a method ‘run’ in JobController.

public function run($job,$data)
{

    $job_id = $job->getJobId(); // Get job id

    $ejob = Job::where('job_id',$job_id)->first(); // Find the job in database

    $ejob->status = 'running'; //Set job status to running

    $ejob->save();

    File::append('public/queue.txt',$data['string'].$job_id.PHP_EOL); //Add content to file

    $ejob->status = 'finished'; //Set job status to finished

    $ejob->save();

    return true;
}

At this point you can test this. Let’s give it a go with the url generated by ngrok. Please note that your url will be different from mine.

  1. Go to http://953ffbb.ngrok.com (It should be working)
  2. In Iron MQ Dashboard -> Project -> Queues -> Your Queue -> Push Queues : You should see http://953ffbb.ngrok.com/queue/receive under subscribers.
  3. Go to http://953ffbb.ngrok.com/job : It should show a list of jobs (It’s possible there are no jobs as we didn’t create any).
  4. Create a job: http://953ffbb.ngrok.com/job/create. This will create a job and come back to http://953ffbb.ngrok.com/job where you see your job with job_id and status “queued”.
  5. Refresh the page: http://953ffbb.ngrok.com/job and you can see that the status is changed to ‘finished’ and find the file public/queue.txt with some text.

That’s it! We pushed a job into the queue, Iron sent the job to the subscriber and we completed our job (wrote data to file and changed the status).


Resize Photos

Let’s upload photos and resize them using Intervention.

Make a PhotoController to handle operations on photos:

php artisan controller:make PhotoController

Add a route for photos in app/routes.php

Route::resource('photo','PhotoController');

Let’s make a table for photos:

php artisan migrate:make create_photos_table

Open the file app/database/migrations/xxxxxx_create_photos_table.php and the following content:

public function up()
{
    Schema::create('photos', function($table)
{
    $table->increments('id');

        $table->string('path');

        $table->timestamps();
    });
}

public function down()
{
    Schema::drop('photos');
}

And then run php artisan migrate to create the table.

Let’s create a model for Photos at app/models/photo.php

<?php

class Photo extends Eloquent {

    protected $table = 'photos';

    protected $fillable = array('path');

}

Add the index method in PhotoController

public function index()
{
    $photos = Photo::all();

    return View::make('photo.index')->with('photos',$photos);
}

Add the view for photos index in app/views/photo/index.blade.php

@extends('master')

@section('content')
<div class="col-md-12">
    <h2>Photos</h2>
    <a href="{{ route('photo.create') }}" class="btn btn-primary">Upload New Photo</a> <br><br>
    <table class="table">
        <thead>
            <tr>
                <th>#</th>
                <th>Path</th>
            </tr>
        </thead>

        <tbody>
            @foreach($photos as $photo)
                <tr>
                    <td>{{ $photo->id }}</td>
                <td>{{ $photo->path }}</td>

                    <td>
                        <a href="{{ route('photo.edit', $photo->id) }}" class="btn btn-primary btn-xs"> Resize </a>
                    </td>
                </tr>
            @endforeach
        </tbody>

    </table>
</div>
@stop

You can now visit: http://localhost:8000/photo and check the page (but there won’t be any photos)

We need a page to upload photos, so let’s make a view and hook in a few methods to achieve that.

Put this content in app/views/photo/create.blade.php

@extends('master')

@section('content')
<div class="col-md-12">
    <h2>Upload New Photo</h2>

    {{ Form::open(array('route' => array('photo.store'),'files' => true)) }}

    <div class="form-group">
        {{ Form::file('photo') }}
    </div>

    {{ Form::submit('Upload',array('class' => 'btn btn-blue btn-primary')) }}

    {{ Form::close() }}

</div>
@stop

Make changes in app/controllers/PhotoController.php

public function create()
{
    return View::make('photo.create');
}

public function store()
{
    $file = Input::file('photo'); // Get the file

    $extension = $file->getClientOriginalExtension(); //Get the extension

    $filename = time().'.'.$extension; //Prepare filename

    $upload_success = $file->move('public/uploads', $filename); //Move file

    Photo::create(array('path' => 'uploads/'.$filename )); //Store info in DB

    return Redirect::route('photo.index');
}

Go to http://localhost:8000/photo and click on ‘Upload New Photo’ and upload the photo. You can go public/uploads and find your photo. At the same time you can find the photos list along with a resize button. Now, we need a form where we can mention width and height to resize.

Put this content in app/views/photo/edit.blade.php

@extends('master')

@section('content')
<div class="col-md-12">
    <h2>Resize Photo</h2>

    {{ Form::open(array('route' => array('photo.update',$photo->id), 'method' => 'PUT')) }}

    <div class="form-group">
        {{ Form::label('width', 'Width') }}

        {{ Form::text('width') }}
    </div>

    <div class="form-group">
        {{ Form::label('height', 'Height') }}

        {{ Form::text('height') }}
    </div>

    {{ Form::submit('Submit',array('class' => 'btn btn-blue btn-primary')) }}

    {{ Form::close() }}

</div>
@stop

The following three methods will be doing our work to resize images:

    public function edit($id)
    {
        $photo = Photo::find($id);

        return View::make('photo.edit')->with('photo', $photo);
    }

    public function update($id)
    {
        $data['photo_id'] = $id;
        $data['width'] = Input::get('width');
        $data['height'] = Input::get('height');

        $job_id = Queue::push('PhotoController@resize', $data); //Put a job in queue to resize

        Job::create(array('job_id' => $job_id));

        return Redirect::route('photo.index');
    }

    public function resize($job, $data)
    {

        $job_id = $job->getJobId(); // Get job id

        $ejob = Job::where('job_id', $job_id)->first(); // Find the job in database

        $ejob->status = 'running'; //Set job status to running

        $ejob->save();

        $photo = Photo::find($data['photo_id']);

        $filename = $data['width'] . '_' . $data['height'] . '_' . basename($photo->path);

        Image::open('public/' . $photo->path)
            ->resize($data['width'], $data['height'], true)
            ->save('public/uploads/' . $filename);

        Photo::create(array('path' => 'uploads/' . $filename));

        $ejob->status = 'finished'; //Set job status to finished

        $ejob->save();

        return true;
    }

Now if you click on resize and submit the form, a job will be queued. If you refresh the photos page, you will find the new photo.

Conclusion

In this article series we implemented IronMQ with Laravel to add background image processing capabilities to our web app. See how simple it was? Did you have any problems? Want to see more? Let us know!

Rajiv SeelamRajiv Seelam
View Author

Rajiv is an enthusiastic web developer and product developer. He is a Computer Science Engineer from India. He loves PHP and has a deep affection for Laravel. He loves exploring new technologies. In his free time he reads technical articles written for PHP and tries to contribute to the community.

ironmqlaravelmessage queuePHPqueue
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week