Sunteți pe pagina 1din 108

Building Web Applications Using Parse REST

API
Using Laravel V4.x to build a simple blog
Mhd Zaher Ghaibeh
This book is for sale at http://leanpub.com/building-web-applications-using-parse-rest-api
This version was published on 2015-01-10

This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools
and many iterations to get reader feedback, pivot until you have the right book and build
traction once you do.

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0


Unported License

Tweet This Book!


Please help Mhd Zaher Ghaibeh by spreading the word about this book on Twitter!
The suggested tweet for this book is:
I just bought the book Building Web Applications Using Parse REST API
http://s.zah.me/1qWXTwX
The suggested hashtag for this book is #laravelandparseit_english.
Find out what other people are saying about the book by clicking on this link to search for this
hashtag on Twitter:
https://twitter.com/search?q=#laravelandparseit_english

Contents
Foreword . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Introduction . . . . .
What Is Laravel . .
What Is Parse Data?
Now What? . . . .

.
.
.
.

2
2
3
7

Creating Parse.com Data Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Data Types: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Creating Your Classes: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

9
9
9

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

User Authentication . . . . . . . . . . . .
Setting Up The Database . . . . . . . .
Creating The Migration File . . . . . .
Creating The Seed File . . . . . . . . .
Setting up the Layout template . . . . .
Creating The Authentication Controller

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

14
14
14
17
19
19

Creating Posts Controller: . . . . . . .


Configure Parse.com Library: . . . . .
Testing Our Parse.com Configuration:
Creating Admin Posts Controller: . .

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

23
23
24
24

Creating The Comments Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Creating The Comments Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Building General Actions: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

41
41
42

Putting everything together . . .


Posts Missing functionality . . .
Comments Missing functionality
Front-end Functionality . . . . .
getPost Function . . . . . . . . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

54
54
56
64
67

Refactoring the posts controller, to use Models


Create the Posts Model Class . . . . . . . . . .
Create getPosts Function . . . . . . . . . . . .
Create getPost Function . . . . . . . . . . . . .
Creating deleteItem Function . . . . . . . . . .
Create handleItem Function . . . . . . . . . .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

70
70
72
74
77
78

CONTENTS

Refactoring the comments controller, to use Models


The Comments Model Class . . . . . . . . . . . . .
getComments Function . . . . . . . . . . . . . . . .
getComment Function . . . . . . . . . . . . . . . .
getPostComments Function . . . . . . . . . . . . . .
deleteItem Function . . . . . . . . . . . . . . . . . .
handleItem Function . . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

83
83
83
85
86
87
87

Mastering Parse Query Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


How to use parse query class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

91
91

Learning Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
More about Parse Products . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
More about Laravel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

98
98
98

Preparing your production server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Creating Your Laravel & nginx Server . . . . . . . . . . . . . . . . . . . . . . . . . . .

99
99

Final Words . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103

Foreword
by Mhd Zaher Ghaibeh
First of all I have to say thanks for everyone who encourage me to start this small journey and
type this beginners book, about how to use and benefit from Parse on your next web project,
and I should also say thank for Boydlee Pollentine who encourage me to read about Parse, and
know how to use it with web applications, not only for mobiles, and also to my oldest friend
Hala Deeb who always encourage me to not limit my imagination.

Who Am I?
I am the Co-founder of Creative Web Group Syria, a web development startup that specializes
in developing modern web applications and utilizing the latest web development technologies
and methodologies. I have 8 years of web development experience and holds a Bachelor Of
Information Technology from the Syrian University, Damascus. I am currently working with
Tipsy & Tumbler Limited as Lead developer.

Thank You
I want also to thank you, for supporting this small project by purchasing the book, and I would
like to encourage you contacting me via email if you find any errors, or you have any question
dont hesitate to contact me and start a discussion about it, or to say Hi.

Contact Info
Name : Mhd Zaher Ghaibeh
Email : z@zah.me
Blog : http://www.zah.me / Arabic Blog
http://boydlee.com/
http://www.haladeeb.name/
http://creativewebgroup-sy.com/
http://www.tipsyandtumbler.co.uk/
z@zah.me
http://www.zah.me

Introduction
What Is Laravel
Quoting from Laravel Documentation:
Laravel Philosophy
Laravel is a web application framework with expressive, elegant syntax. We believe
development must be an enjoyable, creative experience to be truly fulfilling. Laravel
attempts to take the pain out of development by easing common tasks used in the
majority of web projects, such as authentication, routing, sessions, and caching.
Laravel aims to make the development process a pleasing one for the developer
without sacrificing application functionality. Happy developers make the best code.
To this end, weve attempted to combine the very best of what we have seen in other
web frameworks, including frameworks implemented in other languages, such as
Ruby on Rails, ASP.NET MVC, and Sinatra.
Laravel is accessible, yet powerful, providing powerful tools needed for large, robust
applications. A superb inversion of control container, expressive migration system,
and tightly integrated unit testing support give you the tools you need to build any
application with which you are tasked.
To simplify the idea, Laravel is a powerful web application framework, which will help you to
create your next web application in an elegant powerful way.

Laravel Features

RESTful Routing.
Powerful Template Engine (called Blade).
Proven Foundation, since it has been built on top of many Symfony2 Components.
Great ORM and Migration System, to deal with the database.
Supporting many Databases including : MySQL, SQLite, MSSQL, and Postgresql.
Composer powered, so that you can use Composer to install third party libraries which
you can search for on Packagist.
Built with testing in mind.
Great Community.
http://laravel.com/docs
http://www.symfony.com
http://packagist.org

Introduction

Please Note:
This book is not intended to teach you Laravel, this book is going to demonstrate how
you can use and interact with Parse Data from Parse.com. If your looking for a
resources to learn Laravel 4, you can check Dayle Rees book Code Bright.

Installing Laravel 4
There are so many ways to install Laravel, the one which I really like is to go to Laravel website,
download the latest version and extract it on your computer, Open up terminal if youre on a
Mac or Command Prompt if youre using Windows, navigate to the directory where you have
extracted your files and then issue the command:
composer install

to install all the required libraries, including Laravel itself.


But also you can do it like this :
Download and install composer from http://www.getcomopser.org.
From the terminal issue the command :
composer create-project laravel/laravel jasmine --prefer-dist

Configure your Apache virtual host to handle the domains. If you dont know how to do
that, click here (I will use jasmine.dev here to reference to the blog app).
Change the permission for storage directory to be 777 and ensure that you choose to
recursively give all directories within it the same permissions.

What Is Parse Data?


Quoting from Parse.com website:
Save flexible data objects to the cloud with SDKs for every platform. No servers
necessary.
So in easy words, you can use Parse Data as your database server, which you can interact with
to save and retrieve your data whenever you need it.
https://parse.com/products/data#howitworks
https://parse.com/
https://twitter.com/daylerees
https://leanpub.com/codebright
http://laravel.com
http://www.getcomopser.org
http://httpd.apache.org/docs/2.2/vhosts/examples.html
http://www.parse.com/products/data

Introduction

Why Using Parse.com


There is not just one reason to use Parse.com, there are many of them, a small introduction
cant describe the full potential use of Parse Data, but i can say that your limitation is your
imagination. So I will list some of the features which you can get when using Parse products.
We can host our static image files (up to 10MB per file) on Parse Hosting Service, this
way you will be using Parse CDN to serve the images of your site.
We can use Parse Analytics Service to track our API real-time usage, and to track our
custom events via the dashboard.
Offload User management, Quickly add user accounts to your app without having to code
a full authentication system yourself.
Easy Scalability and extendability by simply adding new feature for your application, for
example you can use Parse Social to easily integrate your application with Facebook and
Twitter, which
You can take a look at Parse Customers page to get around and see what other developers has
been using Parse Products for.

Parse.com Data Features


Full Stack Of SDK, which you can use to interact with your data.
Powerful Data Management, which you can use to manage, search, and update your
content without writing a single line of code.

Data Browser

Advanced filtering directly from within the data browser.


https://www.parse.com/products/data
https://www.parse.com/products/
https://www.parse.com/products/hosting
https://www.parse.com/products/analytics
https://www.parse.com/products/social
https://www.parse.com/customers/featured
https://www.parse.com/products

Introduction

Data Filtering

Ability to interact with other Parse.com products.

Signup With Parse.com


Since we are going to use Parse Data as our database backend, we need to have an account
with them, and create an app to have your own key which you will use for interacting with
Parse.com securely. To do this, you need to:
1. Go to Parse.com Signup page.
2. After you finish you will be redirected to your Dashboard and you will be greeting with
this nice box, which asks you to create your first app:

Create a new app box

1. Now lets create our new app and lets call jasmine.
http://parse.com/products/
http://www.parse.com
https://parse.com/#signup

Introduction

2. After you click the create button, you will have a small window, which will contain all
your keys which you will use to interact with Parse.com.

Your application API keys

as you can see you have many keys, and lets be honest, all you want to have is only :
1.
2.
3.
4.

Application ID.
Client Key.
REST API Key.
Master Key.

and now we are ready to go, just remember to save them for you only, and not publish them on
the web, otherwise people will be able to access your data.

Introduction

Now What?
Lets see, we have installed Laravel 4, we have created an account on Parse.com, what else do
we need?
Actually we are missing just one component, which is the PHP Parse.com Library. Sadly,
Parse.com does not have such library, but that hasnt stopped apotropaic from creating one,
Considering this is the only library recommended by Parse.com website https://parse.com/docs/api_libraries, this is what we are going to use, and we might modify it if we find anything we need
to change or enhance.

How to install parse.com-php-library


The easiest way to install the library is by downloading it to our app directory, and adding it to
composer auto load so lets do that:
Why not clone it via git? It would be nice to do so, but what if you have modified the
code of the library? Next time the library was updated, you will lose your modifications.

Download the library from apotropaic github repo.


Extract the library in app/libraries/parse directory.
Edit composer.json so its look like :

"autoload": {
"classmap": [
"app/commands",
"app/controllers",
"app/models",
"app/database/migrations",
"app/database/seeds",
"app/libraries/parse",
"app/tests/TestCase.php"
],
},

Last thing you have to issue is the composer command :

https://github.com/apotropaic/parse.com-php-library
https://parse.com/docs/api_libraries
https://github.com/apotropaic/parse.com-php-library

Introduction

composer dump-autoload

and thats it, we are done.


In the next chapter, we are going to create the Parse.com Classes which will be used by our blog
to store our posts & comments on it .

Creating Parse.com Data Classes


Data Types:
To create the class, we need first to know the types of data which is available in Parse.com Data.
Here is a list with all of the data types within Parse.com Data:
1. Date: The Date type contains a field iso which contains a UTC timestamp stored in ISO
8601 format with millisecond precision: YYYY-MM-DDTHH:MM:SS.MMMZ.
2. Bytes: The Bytes type contains a field base64 which contains a base64 encoding of binary
data. The specific base64 encoding is the one used by MIME, and does not contain
whitespace.
3. Pointer: The Pointer type is used when we set a ParseObject as the value of another object.
It contains the className and objectId of the referred-to value.
4. Relation: The Relation type is used for many-to-many relations when we use ParseRelation as a value. It has a className that is the class name of the target objects.
5. String.
6. Number.
7. Boolean: Simple, true/false value.
8. File: You can upload files and add use the hosting service which is offered by Parse.com
Hosting, in the free account you will have 1GB to upload your files to, but keep in mind
that the file should not exceed 10MB in size.
9. GeoPoint: The GeoPoint type is used with maps and geolocation data, so it will contain
the latitude and longitude values for the point.
10. Array.
11. Object: A hashed array as JSON format.

Creating Your Classes:


Posts Class:
Now that we have know all the types of data which we can use within our class, lets create our
Posts class, this class will hold all of our blog posts.
Please Note,
Before we start with the operation, lets try to simplify the idea of the classes for those
who never worked with NOSQL before, and to be honest the simplest way for me to
describe it, is that the Class represent the Table in any RDMS, and the row record in
RDMS is simply the equivalent for Object in Parse.com Data. So whenever we say lets
get the object id, or lets get the object, make sure that you translate it in your mind like
lets get the row id, or lets get the row record.

10

Creating Parse.com Data Classes

First of all, lets go to Parse.com and login to the dashboard, because we are going to use the
data browser to create our first class.

Parse.com Dashboard - Data Browser

To create our first class, we simply click on the top left blue button which say New Class, and
that will prompt us to enter the name of the class which you want to create.

New Class Prompt

To name our class, we should make sure to use only numbers, letters, and underscore, and to
only begin with a letter.
Now that we have created our class, we should create the tables which will be contained within
the class.
Lets have a few minutes to think, what should the class have as a columns ?
Now that we have taken few minutes to think, We have found that we will need those columns:
1.
2.
3.
4.
5.

Post title, and it will be of the type String.


Post body, and it will be of the type String.
Post date, and it will be of the type Date.
Post update, and it will be of the type Date.
Post active, and it will be of the type Boolean, and it will indicate if this post is published
or not.

http://parse.com

11

Creating Parse.com Data Classes

So after we have defined what we want, lets see how we can create each columns.
First we click on the class name, then we click on the + Col button from the buttons bar.

The buttons bar

Once we click it we will be prompt with a nice modal to type the name of the column and select
the type of the column from the data types which we have talked about earlier.

Create new column

In the column name we type title since this is the first column which we will use to hold the
title of our post, then we select the type String, because it will holds only string.
We will create the same thing for each of the other fields. But wait a minute, did I mention that
Parse.com data already have some default columns which we can use? Whenever you create a
new class Parse.com automatically adds the following column to the class:
objectId: This will hold the id of the object (the id of the record) which we will use, and it
will be generated for you automatically so dont worry about it.
createdAt: This is going to be date data type column which represent the date/time which
you create your record.
updatedAt: This is going to be date data type column which represent the date/time which
you update your record, but by default when you create a record, it will have the same
value as createdAt.

12

Creating Parse.com Data Classes

So after this small info, you know that you only need to create the body and the active field.
Name of the column
The name of the column, Must only contain alphanumeric or underscore characters,
and must begin with a letter or number.

After you finish, you should see something like the image below:

Posts Class

To make things bit interesting, why dont you try to click on the + Row button and try to add
the value of each column directly using the Data Browser, you will notice how much easy it is
to edit/add new value using the data browser.
After you finish, you will have something like the image below:

Posts class with some data

By default, the default value for the boolean when you add it is true, for now lets choose to set
it to false.
Undefined Value
By default, you can have one or all of your custom columns to have value, but this is
not a mandatory thing, I mean for example here, you can have a body for the post, but
your not required to have a title and when you retrieve the data, you will not get any
title field and thats what they mean by undefined.
Remember - you dont have control over the default fields such as objectId, createdAt
and updatedAt.

Comments Class:
Now that weve become familiar with the Data browser, I dont think we need to get more into
how to create the Comments Class, since its the same old story.
Lets define what is the fields which we are going to use:
Author name: a String which will hold the name of the comment author.
Author email: a String also and it will hold the email of the comment author.
Comment body: yes, you guess it right, its also a String, which will hold the full comment.

13

Creating Parse.com Data Classes

Approved: boolean type, which will indicate that the comment is approved by the blog
author or not.
Post: this one is a new one, we didnt use any like it in the Posts class, this is going to be a
Pointer, which will point to the the Post which the comment belong to (you can think of
it as the Foreign Key which point to the post).

Creating a pointer field

Creating a pointer field

Please dont stop here, go ahead and add some default data to the comment class, but make sure
that you choose false for the approved field for now.
Adding a pointer via Data Browser:
Just so that you know, when you add a pointer you will not be able to select it. Instead,
you can simple copy the objectId of the record which we want to use, and just paste it
in the field.

Comment class with some data

User Authentication
Setting Up The Database
To setup the database in Laravel 4, all you have to do it to edit the file database.php which
located at:
app/config/database.php

As you have read about Laravel currently it support :

SQLite.
MySQL.
Postgresql.
SQL Server.

So when you open it you will have to change the default connection to sqlite, to match the
following code:
return array(
'fetch' => PDO::FETCH_CLASS,
'default' => 'sqlite',
'connections' => array(
'sqlite' => array(
'driver'
=> 'sqlite',
'database' => __DIR__.'/../database/production.sqlite',
'prefix'
=> '',
),
),
);

I have removed all the comments from the code, so all you have to change is the default value
to become sqlite.

Creating The Migration File


Now we need to create the migration file which will be used to track our database schema.
Open your terminal and navigate to your larval directory and type the following command:

User Authentication

15

php artisan migrate:make --table=users CreateUsersTable

so the result will be something like :


Created Migration: 2013_09_14_154114_CreateUsersTable
Generating optimized class loader
Compiling common classes

Note: The name of the file 2013-09-14-154114-CreateUsersTable.php should be


different than the one you will have cause it will use the current date and time for
the execution time, and as you can see am executing the command on 14/09/2013 at
15:41:14

Now lets open that file which you will find under the migrations directory under the database
directory :
app/database/migrations/2013_09_14_154114_CreateUsersTable.php
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUsersTable extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function(Blueprint $table)
{
//
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function(Blueprint $table)
{
//

16

User Authentication

});
}
}

In simple words, the up function executed when you run the command:
php artisan migrate

And yes you guessed it right down function executed whenever you run one of the commands:
php artisan migrate:rollback
php artisan migrate:reset
php artisan migrate:refresh

Usually the down function is used to drop the table, so we are going to change it to be like this :
public function down()
{
Schema::drop('users');
}

Now the users table should have some information which can be used to authenticate the user,
so we will use only: * email. * password. Thats it, nothing more, but surely Laravel will be nice
to add the created_at and updated_at fields for us, lets see how its going to be, first we need to
modify the up function to do the creation job for us.
public function up()
{
Schema::table('users', function(Blueprint $table)
{
// adding the email field
$table->string('email')->unique()->nullable()->default(null);
// adding the password field
$table->string('password')->nullable()->default(null);
$table->timestamps();
});
}

Laravel Schema: Since we are not learning Laravel here, I will point you to the Laravel
Schema documentation to read more about it.

Now that we have created the migration file we can test it, and from the terminal lets execute
the command:
http://laravel.com/docs/schema#adding-columns

17

User Authentication

php artisan migrate

If you got no error, then everything has went as it should and now we can continue seeding the
users database with some dummy data.

Users Table Fields

Creating The Seed File


Now that we have created our table using the migrate command, we need to add some data,
and this can be done via the artisan command db:seed. First lets create a file and call it
UsersTableSeeder.php
<?php
class UsersTableSeeder extends Seeder
{
public function run()
{
User::create(array(
'email' => 'test@localhost.com',
'password' => Hash::create('123456789')
));
}
}

Second we need to edit a file called DatabaseSeeder.php which can be found under database\seeds
directory, and edit it so that it has the following code:

18

User Authentication

<?php
class DatabaseSeeder extends Seeder {
public function run()
{
Eloquent::unguard();
$this->call('UsersTableSeeder');
}
}

Final step will be to issue the command:


php artisan db:seed

User data inside the table

Now that we have created our Authentication table, we can carry on and check the file
app\filter.php which already has the required filter to authenticate our website visitor, and
those filters are:
//this will help to authenticate the admins
Route::filter('auth', function()
{
if (Auth::guest()) return Redirect::guest('login');
});
//this will help to authenticate the guests/normal visitors.
Route::filter('guest', function()
{
if (Auth::check()) return Redirect::to('/');
});

User Authentication

19

Setting up the Layout template

We are going to use the latest version of Twitter Bootstrap which you can get from http://www.getbootstrap.com
download it and put the content inside the assets folder under the public folder.
Now we need to create our layout.blade.php file, under the admin folder inside our views folder,
and we just add the following code to it:
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
{{HTML::style(asset('assets/css/bootstrap.min.css'))}}
{{HTML::style(asset('assets/css/main.css'))}}
{{HTML::script('http://code.jquery.com/jquery-1.10.1.min.js')}}
{{HTML::script('http://code.jquery.com/jquery-migrate-1.2.1.min.js')}}
{{HTML::script(asset('assets/js/bootstrap.min.js'))}}
<!-http://bootsnipp.com/snippets/featured/google-style-login
http://bootsnipp.com/snippets/featured/recent-comments-admin-panel
http://bootsnipp.com/snippets/featured/admin-panel-quick-shortcuts
-->
</head>
<body>
<div class="container">
@yield('body')
</div>
</body>
</html>

As you can see I have added a small comments which is the url for the snippets which I have used
to create the forms, elements .. etc, We always should give the credits to the original creators of
the codes.

Creating The Authentication Controller


Since this book is not going to teach you Laravel 4, we are going to create a simple
Authentication Controller to handle the login/logout actions only. First lets create the Controller
file under the app\controllers directory and add the following code:

http://www.getbootstrap.com
http://www.laravel.com

User Authentication

20

<?php
class AuthController extends BaseController{
public function getLogin()
{
return View::make('login');
}
public function postLogin()
{
if(Auth::attempt(array('email' => Input::get('email'), 'password' => \
Input::get('password')))){
return Redirect::intended('/admin/dashboard');
}else{
return Redirect::to('/login')
->with('error','You dont have access permission, sorry.');
}
}
public function getLogout()
{
Auth::logout();
return Redirect::to('/login');
}
}

The first function is going to show a simple view which only have the login form, which we will
use to authenticate our admin, the second function is used to validate the credential of the user.
If the user authenticates successfully then they will be redirected to the dashboard page, if not
they will be redirected back to the login page, and the last function is used to logout the user
from the system. Now in our routes.php we should add the following code:
Route::get('/login',array('uses' => 'AuthController@getLogin'));
Route::post('/login',array('uses'=>'AuthController@postLogin'));
Route::get('/logout', array('as'=>'logout','uses'=>'AuthController@getLogout'\
));

As we can read each Route method is connected to one of the functions in our AuthController.
Here is the code for our simple login form, I am not a good designer, so I hope you can accept
this for as simple as it is:

User Authentication

@extends('admin.layout')
@section('body')
<div class="row">
@if(Session::has('error'))
<div class="alert alert-danger">{{Session::get('error')}}</div>
@endif
<div class="col-sm-6 col-md-4 col-md-offset-4">
<h1 class="text-center login-title">Sign in to continue</h1>
<div class="account-wall">
<img class="profile-img" src="https://lh5.googleusercontent.com/-\
b0-k99FZlyE/AAAAAAAAAAI/AAAAAAAAAAA/eu7opA4byxI/photo.jpg?sz=120"
alt="">
{{Form::open(array('action'=>'AuthController@postLogin','id'=\
>'loginForm', 'role'=>'form', 'class'=>'form-signin'))}}
{{Form::text('email',Input::old('email'),
array('id'=>'email','placeholder'=>'joe@johndoe.com','class'=\
>'form-control','required'=>'required')
)}}
{{Form::password('password',
array('id'=>'password','class'=>'form-control','placeholder'=\
>'password')
)}}
{{Form::submit('Login',array('class'=>'btn btn-lg btn-primary\
btn-block'))}}
<span class="clearfix"></span>
{{Form::close()}}
</div>
</div>
</div>
@stop

21

22

User Authentication

The Login Form

In the next few chapters, we will talk about how to communicate with Parse Data, so that we
can add/edit/delete our data. The next chapter will cover the Posts Class in Parse Data, which is
the core of our main topic.

Creating Posts Controller:


Configure Parse.com Library:
Since we have downloaded the recommended Parse.com library from apotropaic repo, we need
now to configure it so that we can make it work for us.
As mentioned in the repo page we need to create the parseConfig.php file and put it in the
same directory as our library, so for our case we need to create it under app/libraries/parse
directory.
The content of this file should be the constant variables which he use to authenticate any action
with parse.com RESTApi server, and it look like:
<?php
class parseConfig {
const APPID = 'D8s479IYdHR6uSFBYXsedjPANYNk8tZvfeOkji50';
const MASTERKEY = '4sFcF2UV9126M9x1X5Jh5px6NvmZHSqdTDMVNuki';
const RESTKEY = 'zCTQp4h2wLrjahIUVERkTsEi2156hKlHtCfYOE5P';
const PARSEURL = 'https://api.parse.com/1/';
}

Use your own keys,


I just added my keys here, so that you can have something to start with, but these keys
will not be valid for your own development.

Now if you opened the file app/libraries/parse/parse.php, you will notice that the creator of
the library has some include statements in the header of the file which is :
<?php
include
include
include
include
include
include
include
include
include

'parseConfig.php';
'parseObject.php';
'parseQuery.php';
'parseUser.php';
'parseFile.php';
'parsePush.php';
'parseGeoPoint.php';
'parseACL.php';
'parseCloud.php';

https://github.com/apotropaic/parse.com-php-library

Creating Posts Controller:

24

Since we are using composer to autoload our classes we will not need to include any of these
files, so we can comment them out, or if you like you can delete them, I like to keep them just in
case I have something to check later.

Testing Our Parse.com Configuration:


Now we need to check if everything is working as it should or not, so we need to edit our
routes.php file and add this test route:
Route::get('/',function(){
$test = new parseQuery('posts');
return Response::json($test->find());
});

If everything was good, and there was no errors at all, you should have a JSON result like :
{
"results":
[{
"active":false,
"body":"this is the first post which we will have here",
"title":"first post",
"createdAt":"2013-09-06T17:49:11.938Z",
"updatedAt":"2013-09-06T17:49:27.495Z",
"objectId":"J9mLUW0heO"
}]
}

To be more familiar with the RESTApi in Parse.com, it recommended that you read
the documentation.

Creating Admin Posts Controller:


We are going to build our admin controllers, but we would like to make the urls to be like
http://jasmin.dev/admin/controllername, and its really a simple things to accomplish using
Laravel routing mechanism, so lets edit our routes.php and just add the following to it:

https://parse.com/docs/rest#general
https://parse.com/docs/rest#general

Creating Posts Controller:

25

Route::group(array('before'=>'auth','prefix'=>'admin'),function(){
//here we will add each controller which belongs to the admin area.
Route::controller('posts','AdminPostsController');
Route::controller('comments','AdminCommentsController');
Route::controller('dashboard','AdminDashboardController');
});

After that we need to create two classes in our controllers directory, and we will name them:
AdminPostsController (app\controllers\AdminPostsController.php).
AdminCommentsController (app\controllers\AdminCommentsController.php).
AdminDashboardController (app\controllers\AdminDashboardController.php).

AdminPostsController Functions:
Now, We have our controller ready to be edited, so lets see what do we need? we need :

View Action.
List Action.
Delete Action.
Store Action.
Edit Action.

Those are the general actions, lets start coding and lets see how we can do it, and am going to
start with the easiest one, the View Action.
View Action: Its really a matter of querying Parse.com for a specific record, using the objectId
value, which we can get from Parse.com Data Browser, In my case its J9mLUW0heO.
In the parse.com php library, there is a class called parseQuery, this class is the one which we
should use when querying Parse.com for anything we want, and mainly they are two types:
Querying for the records/record information from a class.
Getting the counts of the records in a class.
In our case here, we want to get a record from the Posts Class, using the objectId (the id) of the
record, so lets see how the function should look like:

Creating Posts Controller:

26

public function getRecord($objectId = null)


{
if(is_null($objectId)){
return Redirect::to('/admin/posts')->with('error','You must select a \
record to view');
}
try{
$recordInfo = new parseQuery('posts');
$recordInfo->where('objectId',$objectId);
$result = $recordInfo->find();
$data = array('item'=>$result->results[0]);
return View::make('admin.posts.record')->with($data);
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

Nice, now we have a short function which does exactly what we want, but lets have a small
review of what we have just wrote:
$recordInfo = new parseQuery('posts');

We have just created an object of the class parseQuery and we have sent the name of which
Parse.com class we want to query as a parameter.
$recordInfo->where('objectId',$objectId);

Then we told the object of the class, that we want to have only the records (since we dont know
how many records it will return) which meets the required condition where the objectId is equal
to the given value. So where is used here to create this condition before send it to Parse.com.
$result = $recordInfo->find();

The find function within our parseQuery object, is used to send the data to Parse.com RESTApi
servers and give us back the results. The results which we get is a matter of JSON array with
only one element, all the other things is just a matter of sending the data back to our view to
display it, and the code for this view is (admin/posts/record.blade.php):

Creating Posts Controller:

@extends('admin.layout')
@section('body')
<div class="row">
@if(Session::has('success'))
<div class="alert alert-success">{{Session::get('success')}}</div>
@endif
@if(Session::has('error'))
<div class="alert alert-danger">{{Session::get('error')}}</div>
@endif
<ol class="breadcrumb">
<li><a href="{{URL::action('AdminDashboardController@getIndex')}}">Ho\
me</a></li>
<li><a href="{{URL::action('AdminPostsController@getIndex')}}">ALL Po\
sts</a></li>
<li class="active">{{$item->title}}</li>
<li class="pull-right no-before">
<a href="{{URL::route('logout')}}">
Logout
</a>
</li>
</ol>
<div class="panel panel-default widget">
<div class="panel-heading">
<span class="glyphicon glyphicon-list-alt"></span>
<h3 class="panel-title">
{{$item->title}}</h3>
</div>
<div class="panel-body">
<ul class="list-group">
<li class="list-group-item">
<div class="row">
<div class="col-xs-2 col-md-1">
<img src="http://placehold.it/80" class="img-circ\
le img-responsive" alt="" /></div>
<div class="col-xs-10 col-md-11">
<div>
<div class="mic-info">
on {{date('d-M-Y',strtotime($item->create\
dAt))}}
</div>
</div>
<div class="comment-text">

27

28

Creating Posts Controller:

{{$item->body}}
</div>
<div class="action">
<a class="btn btn-primary btn-xs" title="Edit\
" href="#">
<span class="glyphicon glyphicon-pencil">\
</span>
</a>
@if(!$item->active)
<a class="btn btn-success btn-xs" title="Publ\
ished" href="#">
<span class="glyphicon glyphicon-ok"></sp\
an>
</a>
@else
<a class="btn btn-danger btn-xs" title="Hidde\
n" href="#">
<span class="glyphicon glyphicon-remove">\
</span>
</a>
@endif
<a class="btn btn-danger btn-xs" title="Delet\
e" href="#">
<span class="glyphicon glyphicon-trash"><\
/span>
</a>
<a class="btn btn-primary btn-xs" title="View\
Post Comments" href="#">
<span class="glyphicon glyphicon-comment"\
></span>
</a>
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
@stop

29

Creating Posts Controller:

The result of the query

Before we continue, there is a small bug in the parse.com library which prevent us
from adding the limit,skip,order and include parameter if there was no conditions to
our query.

Solving a bug in Parse.com library:


You should open the parseQuery.php file and edit the file to comment the lines 27 till 39 like
this:
27
28
29
30
31
32
33
34
35
36
37
38

public function find(){


//if(empty($this->_query)){
//
$request = $this->request(array(
//
'method' => 'GET',
//
'requestUrl' => $this->_requestUrl
//
));
//
return $request;
//}
//else{
// $urlParams = array(
//
'where' => json_encode( $this->_query )
// );

And add this code after it:


40
41
42

if(!empty($this->_query)){
$urlParams['where'] = json_encode( $this->_query );
}

Last thing remember to delete the close curly bracket } at line 70, since this one belong to the
else which we have commented out.
List Action: Now that we have become familiar with parseQuery, lets do the List action, which
will bring to us all the records we have in the Posts Class.
First we need to do a small calculation to know how many records we have to skip, since we
dont going to list all the records at one time, to do so we made this simple calculation, and we
add it to our constructor function:

Creating Posts Controller:

30

public function __construct()


{
$this->perPage = Config::get('application.perPage');
$pageNo = Input::get('page');
$this->skip = (is_null($pageNo['page'])) ? 0 : ( $this->perPage * ( $page\
No['page'] - 1)) ;
}

So, we get the page variable, and we check if its null, since the Input class will return null if
the variable is not defined, if so we dont have to skip anything, if not we get the value of page
variable minus one, and we multiply it by 10, since we need to show 10 records in each page. By
doing that we get the value of the skipped records.
Second, we need to query Parse.com to get the data which we need, by providing it with the
amount of records which it needs to skip, and another parameter which will help us to get the
count of all records, you will see why we need that when we create the pagination :
$records = new parseQuery('posts');
// this is to return the count of all records.
$records->setCount(true);
// this is to limit the returned number of records.
$records->setLimit($this->perPage);
// this is the skip parameter which we calculate.
$records->setSkip($this->skip);
// this is to send our request to the server, and get the returned data.
$result = $records->find();

Now that we got our data, lets create the pagination, using Laravel Paginator class we can create
a nice pagination, and in the simplest form ever, using the data we have:
$paginator = Paginator::make($result->results,$result->count,$this->perPage);

So, the full code of the function will be :


public function getIndex()
{
try{
$records = new parseQuery('posts');
$records->setCount(true);
$records->setLimit($this->perPage);
$records->setSkip($this->skip);
$result = $records->find();
$paginator = Paginator::make($result->results,$result->count,$this->p\

Creating Posts Controller:

erPage);
$data = array(
'items'=> $result->results,
'paginator' => $paginator,
'total' => $result->count
);
return View::make('admin.posts.list')->with($data);
} catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

And the view (admin/posts/list.blade.php) which will give use the data is :
@extends('admin.layout')
@section('body')
<div class="row">
@if(Session::has('success'))
<div class="alert alert-success">{{Session::get('success')}}</div>
@endif
@if(Session::has('error'))
<div class="alert alert-danger">{{Session::get('error')}}</div>
@endif
<ol class="breadcrumb">
<li><a href="{{URL::action('AdminDashboardController@getIndex')}}">Ho\
me</a></li>
<li class="active">ALL Posts</li>
<li class="pull-right no-before">
<a href="{{URL::route('logout')}}">
Logout
</a>
</li>
</ol>
<div class="add-new center-block">
<a class="btn btn-default btn-sm" title="Add new Post" href="#">
<span class="glyphicon glyphicon-plus-sign"></span> Add new P\
ost
</a>
</div>

31

Creating Posts Controller:

<div class="clearfix"></div>
<br />
<div class="panel panel-default widget">
<div class="panel-heading">
<span class="glyphicon glyphicon-list-alt"></span>
<h3 class="panel-title">
All Posts</h3>
<span class="label label-info">
{{$total}}</span>
</div>
<div class="panel-body">
<ul class="list-group">
@foreach($items as $item)
<li class="list-group-item">
<div class="row">
<div class="col-xs-2 col-md-1">
<img src="http://placehold.it/80" class="img-circ\
le img-responsive" alt="" /></div>
<div class="col-xs-10 col-md-11">
<div>
<a href="{{URL::action('AdminPostsController@\
getRecord',$item->objectId)}}">
{{$item->title}}</a>
<div class="mic-info">
on {{date('d-M-Y',strtotime($item->create\
dAt))}}
</div>
</div>
<div class="comment-text">
{{Str::limit($item->body, 50)}}
</div>
<div class="action">
<a class="btn btn-primary btn-xs" title="View\
" href="{{URL::action('AdminPostsController@getRecord',$item->objectId)}}">
<span class="glyphicon glyphicon-eye-open\
"></span>
</a>
@if(!$item->active)
<a class="btn btn-success btn-xs" title="Publ\
ished" href="#">
<span class="glyphicon glyphicon-ok"></sp\
an>
</a>
@else
<a class="btn btn-danger btn-xs" title="hidde\
n" href="#">

32

33

Creating Posts Controller:

<span class="glyphicon glyphicon-remove">\


</span>
</a>
@endif
</div>
</div>
</div>
</li>
@endforeach
</ul>
</div>
</div>
{{$paginator->links()}}
</div>
@stop

List Of All Posts

Delete Action: Deleting objects is one of those actions which you will find easy to use and easy
to understand, all you have to do is to create an object of type parseObject and just execute the
delete function after providing it with the value of the object id which we want to delete. The
full code for this function is:
public function getDelete($objectId = null){
if(is_null($objectId)){
return Redirect::to('/admin/posts')->with('error','You must select a record\
to delete');
}
try{
$recordInfo = new parseObject('posts');
$recordInfo->delete($objectId);
return Redirect::action('AdminPostsController@getIndex')
->with('success','Your Post Has been deleted');
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

Creating Posts Controller:

34

We just need to add a link to the delete action to our read record view, so it will look like :
1
2
3
4

<a class="btn btn-danger btn-xs" title="Delete" href="{{URL::action('AdminPos\


tsController@getDelete',$item->objectId)}}">
<span class="glyphicon glyphicon-trash"></span>
</a>

Store Action: Since we have learned that delete post can be done by simply creating an object
of type parseObject, so does the store function, the idea is simple:
1. We will need to create an object of type parseObject.
2. Then add the data to it as attributes
But first, we need to create the form, and the code for doing so.
public function getAdd(){
return View::make('admin.posts.add');
}

And the html code for this view (admin/posts/add.blade.php) is:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

@extends('admin.layout')
@section('body')
<div class="row">
@if(Session::has('success'))
<div class="alert alert-success">{{Session::get('success')}}</div>
@endif
@if(Session::has('error'))
<div class="alert alert-danger">{{Session::get('error')}}</div>
@endif
<ol class="breadcrumb">
<li><a href="{{URL::action('AdminDashboardController@getIndex')}}">Ho\
me</a></li>
<li><a href="{{URL::action('AdminPostsController@getIndex')}}">ALL Po\
sts</a></li>
<li class="active">Add New Post</li>
<li class="pull-right no-before">
<a href="{{URL::route('logout')}}">
Logout
</a>

35

Creating Posts Controller:

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

</li>
</ol>
<div class="panel panel-default widget">
<div class="panel-heading">
<span class="glyphicon glyphicon-list-alt"></span>
<h3 class="panel-title">
Add New Post</h3>
</div>
<div class="panel-body">
{{Form::open(array('action'=>'AdminPostsController@postAdd', 'rol\
e'=>'form'))}}
<div class="form-group">
{{Form::label('title','Title :')}}
{{Form::text('title',null,array('id'=>'title','placeholder'=>\
'Post Title', 'class'=>'form-control', 'required'=>'required'))}}
</div>
<div class="form-group">
{{Form::label('body','Body :')}}
{{Form::textarea('body',null,array('id'=>'body','placeholder'\
=>'Post Body', 'class'=>'form-control', 'required'=>'required'))}}
</div>
{{Form::submit('Create',array('class'=>'btn btn-primary'))}}
{{Form::close()}}
</div>
</div>
</div>
@stop

Add New Post Form

We also need to add a link to the Add action to our list view (admin/posts/list.blade.php)

Creating Posts Controller:

36

<div class="add-new center-block"


<a class="btn btn-default btn-sm" title="Add new Post" href="{{URL::action('A\
dminPostsController@getAdd')}}"
<span class="glyphicon glyphicon-plus-sign"></span>
Add new Post
</a>
</div>

Now come the part where the real work starts, lets read the function, and i will explain it, trust
me :
public function postAdd(){
// i will trust that you have send the data, and that you need to store it
// as we said before we are here to talk about parse.com not laravel 4
try{
// create parse object
$postData = new parseObject('posts');
// add the title to the object
$postData->title = Input::get('title');
// add the body to the object
$postData->body = Input::get('body');
// and make sure that the active is true
$postData->active = true;
// now send the object to parse and return the object id
$result = $postData->save();
return Redirect::action('AdminPostsController@getRecord', $result->ob\
jectId)->with('success','Your Post Has been added');
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

As you can see we created the class object, then we added the data to it, and then send it to
parse.com, and when everything goes as we plan, we simply redirect it to the post view page to
see the result.
Edit Action: Now the edit action is some how a combination of both, add and retrieve, because
you will need to have the data to fill the form which you will use to edit the old data, so we have
two functions, the first one to get the data and fill the form, the second one to send the updates
to parse.com.

Creating Posts Controller:

37

public function getEdit($objectId = null)


{
if(is_null($objectId)){
return Redirect::to('/admin/posts')->with('error','You must select a \
record to edit');
}
try{
$recordInfo = new parseQuery('posts');
$recordInfo->where('objectId',$objectId);
$result = $recordInfo->find();
$data = array('item'=>$result->results[0]);
return View::make('admin.posts.edit')->with($data);
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

We need to add a link to the Edit action into our read view (admin/posts/record.blade.php):
<a class="btn btn-primary btn-xs" title="Edit" href="{{URL::action('AdminPost\
sController@getEdit',$item->objectId)}}">
<span class="glyphicon glyphicon-pencil"></span>
</a>

And the html code for the Edit View (admin/posts/edit.blade.php) is :


@extends('admin.layout')
@section('body')
<div class="row">
@if(Session::has('success'))
<div class="alert alert-success">{{Session::get('success')}}</div>
@endif
@if(Session::has('error'))
<div class="alert alert-danger">{{Session::get('error')}}</div>
@endif
<ol class="breadcrumb">
<li><a href="{{URL::action('AdminDashboardController@getIndex')}}">Ho\
me</a></li>
<li><a href="{{URL::action('AdminPostsController@getIndex')}}">ALL Po\

Creating Posts Controller:

sts</a></li>
<li class="active">Edit {{$item->title}}</li>
<li class="pull-right no-before">
<a href="{{URL::route('logout')}}">
Logout
</a>
</li>
</ol>
<div class="panel panel-default widget">
<div class="panel-heading">
<span class="glyphicon glyphicon-list-alt"></span>
<h3 class="panel-title">
Edit : {{$item->title}}</h3>
</div>
<div class="panel-body">
{{Form::open(array('action'=>'AdminPostsController@postEdit', 'ro\
le'=>'form'))}}
<div class="form-group">
{{Form::label('title','Title :')}}
{{Form::text('title',$item->title,array('id'=>'title','placeh\
older'=>'Post Title', 'class'=>'form-control', 'required'=>'required'))}}
</div>
<div class="form-group">
{{Form::label('body','Body :')}}
{{Form::textarea('body',$item->body,array('id'=>'body','place\
holder'=>'Post Body', 'class'=>'form-control', 'required'=>'required'))}}
</div>
{{Form::hidden('objectId',$item->objectId)}}
{{Form::submit('Update',array('class'=>'btn btn-primary'))}}
{{Form::close()}}
</div>
</div>
</div>
@stop

38

39

Creating Posts Controller:

Edit Post Form

So when you hit the update button the data will be send to our controller action, which will send
it to Parse.com. The function is similar to the add function, lets see it:
public function postEdit()
{
try{
$postData = new parseObject('posts');
$postData->title = Input::get('title');
$postData->body = Input::get('body');
$postData->active = true;
$result = $postData->update(Input::get('objectId'));
return Redirect::action('AdminPostsController@getRecord', Input::get(\
'objectId'))
->with('success','Your Post Has been updated');
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

Our previous function was the last function of our Post Controller, and as you have see its
so easy to work with Parse.com RESTApi, its just a matter of reading the documentation and
implementing a few tricks of your own.
The Parse PHP library contains so much contain so much information, and the best
way to learn it is by reading the documentation first, then reading the code again and
again till you figure out what each code does, and how you can improve it.

Creating Posts Controller:

40

In our next chapter, we are going to build the comment controller class, which is similar to the
Posts controller, except that we need to have it connected with our posts.

Creating The Comments Controller


This chapter is going to be shorter than the old one, since most of what we are going
to talk about has been mentioned in the last chapter, so i will try my best to make it
short.

Creating The Comments Controller


As we have created our Posts controller, and we have created an empty controller and we have
called it AdminCommentsController.php so now we need to extend the BaseController, as the
following (just a note we need to add the same constructor code which we used in out posts
controller):
<?php
class AdminCommentsController extends BaseController{
public function __construct()
{
$this->perPage = Config::get('application.perPage');
$pageNo = (is_null(Input::only('page'))) ? 0 : Input::only('page');
$this->skip
= (($this->perPage * ($pageNo['page'] - 1)) < 0) ? 0 : \
($this->perPage * ($pageNo['page'] - 1));
}
}

and we add it to our route file as in the following:


Route::group(array('before'=>'auth','prefix'=>'admin'),function(){
Route::controller('posts','AdminPostsController');
Route::controller('comments','AdminCommentsController');
Route::controller('dashboard','AdminDashboardController');
});

This is something we have already done in the last chapter, but its always nice to
remember it.

Creating The Comments Controller

42

Building General Actions:


We are going to build the same functionality which we have built in our posts controller, which
is:

List action.
Store action (will be coded in the next chapter).
Edit action.
View record action.
Delete action.
You must have all the functions written in the controller, when you include them in
your view file.

List Action:
As we have done before the list action consist of querying Parse.com for our data, but we have
just one simple difference here is that we have a pointer field, and we can get the content of this
pointer field by just telling Parse.com to include it in the returned results, like :
public function getIndex()
{
try{
$fullComments = new parseQuery('comments');
$fullComments->setCount(true);
//this is the important field, which also get the post data
$fullComments->whereInclude('post');
$fullComments->setLimit($this->perPage);
$fullComments->setSkip($this->skip);
$fullComments->orderByDescending('createdAt');
$comments = $fullComments->find();
$paginator = Paginator::make($comments->results, $comments->count, $t\
his->perPage);
$data = array(
'items'=> $comments->results,
'paginator' => $paginator,
'total' => $comments->count
);

Creating The Comments Controller

return View::make('admin.comments.list')->with($data);
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

And the Html code for this view is (admin/comments/list.blade.php):


@extends('admin.layout')
@section('body')
<div class="row">
@if(Session::has('success'))
<div class="alert alert-success">{{Session::get('success')}}</div>
@endif
@if(Session::has('error'))
<div class="alert alert-danger">{{Session::get('error')}}</div>
@endif
<ol class="breadcrumb">
<li><a href="{{URL::action('AdminDashboardController@getIndex')}}\
">Home</a></li>
<li class="active">Posts Comments</li>
<li class="pull-right no-before">
<a href="{{URL::route('logout')}}">
Logout
</a>
</li>
</ol>
<div class="panel panel-default widget">
<div class="panel-heading">
<span class="glyphicon glyphicon-comment"></span>
<h3 class="panel-title">
Posts Comments</h3>
<span class="label label-info">
{{$total}}</span>
</div>
<div class="panel-body">
<ul class="list-group">
@foreach($items as $item)
<li class="list-group-item">
<div class="row">

43

Creating The Comments Controller

<div class="col-xs-2 col-md-1">


<img src="http://placehold.it/80" class="img-\
circle img-responsive" alt="" /></div>
<div class="col-xs-10 col-md-11">
<div>
<a href="#">
Comment on {{ $item->post->title }}</\
a>
<div class="mic-info">
By: <a href="#">{{$item->authorName}}\
</a>
on {{date('d-M-Y',strtotime($item->cr\
eatedAt))}}
</div>
</div>
<div class="comment-text">
{{$item->commentBody}}
</div>
<div class="action">
<a class="btn btn-primary btn-xs" title="\
View" href="#">
<span class="glyphicon glyphicon-eye-\
open"></span>
</a>
@if(!$item->approved)
<a class="btn btn-success btn-xs" title="\
Approved" href="#">
<span class="glyphicon glyphicon-ok">\
</span>
</a>
@else
<a class="btn btn-danger btn-xs" title="U\
n Approve" href="#">
<span class="glyphicon glyphicon-remo\
ve"></span>
</a>
@endif
</div>
</div>
</div>
</li>
@endforeach
</ul>
</div>
</div>
{{$paginator->links()}}

44

45

Creating The Comments Controller

</div>
@stop

Listing All Comments

Edit Action:
The edit action is what you do when you have some information you need to change, like
changing a word or deleting some information, but the most important is how you add the
value for the pointer ?
You have two choices:
Never change the value, and so it will not change.
Just handle it (and this is the case when adding or when changing the value like linking
the comment to another post).
I have choose to handle it even though its not going to change, so that you can see how its going
to be coded.
public function getEdit($objectId = null)
{
try{
if(is_null($objectId)){
return Redirect::action('AdminCommentsController@getIndex')->with\
('error','Choose a comment to edit');
}
$commentRecord = new parseQuery('comments');
$commentRecord->where('objectId', $objectId);
$commentRecord->whereInclude('post');
$commentRecord->setLimit(1);
$result = $commentRecord->find();
$result->results[0]->approved = ($result->results[0]->approved) ? 1 :\
0;
$data = array(

Creating The Comments Controller

46

'item'=> $result->results[0],
);
return View::make('admin.comments.edit')->with($data);

}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}
public function postEdit()
{
try{
$oldComment = new parseObject('comments');
$oldComment->authorName = Input::get('authorName');
$oldComment->authorEmail = Input::get('authorEmail');
$oldComment->commentBody = Input::get('commentBody');
$oldComment->approved = (Input::get('approved') == 1) ? true : false;
$oldComment->post = $oldComment->dataType('pointer',array('posts', In\
put::get('postId') ));
$result = $oldComment->update(Input::get('objectId'));
return Redirect::action('AdminPostsController@getRecord', Input::get(\
'objectId'))
->with('success','The comment has been updated');
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

Most importantly is how we add the pointer to the post, the library provide us with a simple
function dataType which accept the type of the field as its first argument, and an array with the
data, as its second argument. and the function is look like this :

Creating The Comments Controller

47

public function dataType($type,$params){


if($type != ''){
switch($type){
case 'date':
$return = array(
"__type" => "Date",
"iso" => date("c", strtotime($params))
);
break;
case 'bytes':
$return = array(
"__type" => "Bytes",
"base64" => base64_encode($params)
);
break;
case 'pointer':
$return = array(
"__type" => "Pointer",
"className" => $params[0],
"objectId" => $params[1]
);
break;
default:
$return = false;
break;
}
return $return;
}
}

I have removed some of the types to make the function short to read for now, since the only
thing which matter for us is the pointer data type which connect our comment with the posts
class, and the retuned data will look something like :
{
"__type": "Pointer",
"className": "Posts",
"objectId": "Ed1nuqPvc"
}

And the HTML view code for this page (admin/comments/edit.blade.php) is:

Creating The Comments Controller

@extends('admin.layout')
@section('body')
<div class="row">
@if(Session::has('success'))
<div class="alert alert-success">{{Session::get('success')}}</div>
@endif
@if(Session::has('error'))
<div class="alert alert-danger">{{Session::get('error')}}</div>
@endif
<ol class="breadcrumb">
<li><a href="{{URL::action('AdminDashboardController@getIndex')}}">Ho\
me</a></li>
<li><a href="{{URL::action('AdminCommentsController@getIndex')}}">ALL\
Comments</a></li>
<li class="active">Edit Comment</li>
<li class="pull-right no-before">
<a href="{{URL::route('logout')}}">
Logout
</a>
</li>
</ol>
<div class="panel panel-default widget">
<div class="panel-heading">
<span class="glyphicon glyphicon-list-alt"></span>
<h3 class="panel-title">
Edit Comment</h3>
</div>
<div class="panel-body">
{{Form::open(array('action'=>'AdminCommentsController@postEdit', \
'role'=>'form'))}}
<div class="form-group">
{{Form::label('authorName','Your Name :')}}
{{Form::text('authorName',$item->authorName,array('id'=>'auth\
orName','placeholder'=>'Your Name', 'class'=>'form-control', 'required'=>'req\
uired'))}}
</div>
<div class="form-group">
{{Form::label('authorEmail','Your Email :')}}
{{Form::text('authorEmail',$item->authorEmail,array('id'=>'au\
thorEmail','placeholder'=>'Your Email', 'class'=>'form-control', 'required'=>\
'required'))}}
</div>

48

49

Creating The Comments Controller

<div class="form-group">
{{Form::label('commentBody','Comment :')}}
{{Form::textarea('commentBody',$item->commentBody,array('id'=\
>'commentBody','placeholder'=>'Comment Body', 'class'=>'form-control', 'requi\
red'=>'required'))}}
</div>
<div class="form-group">
{{Form::label('approved','Approved :')}}
{{Form::select('approved',array('1'=>'Approved','0'=>'Waiting\
Approval'),$item->approved,array('id'=>'approved','class'=>'form-control'))}\
}
</div>
{{Form::hidden('postsId',$item->post->objectId)}}
{{Form::hidden('objectId',$item->objectId)}}
{{Form::submit('Reply',array('class'=>'btn btn-primary'))}}
{{Form::close()}}
</div>
</div>
</div>
@stop

Edit Comment Form

View Record Action:


Viewing the record consists of just querying Parse.com to get the data, but we are going to include
the posts record with it like:

Creating The Comments Controller

public function getRecord($objectId = null)


{
if(is_null($objectId)){
return Redirect::action('AdminCommentsController@getIndex')->with('er\
ror','Choose a comment to view');
}
try{
$commentRecord = new parseQuery('comments');
$commentRecord->where('objectId', $objectId);
$commentRecord->whereInclude('post');
$commentRecord->setLimit(1);
$result = $commentRecord->find();
$data = array(
'item'=> $result->results[0],
);
return View::make('admin.comments.record')->with($data);

}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

Below you can read our html template code (admin/comments/record.blade.php):


@extends('admin.layout')
@section('body')
<div class="row">
@if(Session::has('success'))
<div class="alert alert-success">{{Session::get('success')}}</div>
@endif
@if(Session::has('error'))
<div class="alert alert-danger">{{Session::get('error')}}</div>
@endif
<ol class="breadcrumb">
<li><a href="{{URL::action('AdminDashboardController@getIndex')}}">Ho\
me</a></li>
<li><a href="{{URL::action('AdminCommentsController@getIndex')}}">Pos\
ts Comments</a></li>

50

Creating The Comments Controller

<li>Comment on Post {{$item->post->title}}</li>


</ol>
<div class="panel panel-default widget">
<div class="panel-heading">
<span class="glyphicon glyphicon-comment"></span>
<h3 class="panel-title">
Comment on Post {{$item->post->title}}
</h3>
</div>
<div class="panel-body">
<ul class="list-group">
<li class="list-group-item">
<div class="row">
<div class="col-xs-2 col-md-1">
<img src="http://placehold.it/80" class="img-circ\
le img-responsive" alt="" /></div>
<div class="col-xs-10 col-md-11">
<div>
<a href="{{URL::action('AdminPostsController@\
getRecord',$item->post->objectId)}}">
{{ $item->post->title }}</a>
<div class="mic-info">
By: <a href="{{URL::action('AdminComments\
Controller@getRecord',$item->objectId)}}">{{$item->authorName}}</a>
on {{date('d-M-Y',strtotime($item->create\
dAt))}}
</div>
</div>
<div class="comment-text">
{{$item->commentBody}}
</div>
<div class="action">
<a class="btn btn-primary btn-xs" title="Edit\
" href="{{URL::action('AdminCommentsController@getEdit',$item->objectId)}}">
<span class="glyphicon glyphicon-pencil">\
</span>
</a>
@if(!$item->approved)
<a class="btn btn-success btn-xs" title="Appr\
oved" href="#">
<span class="glyphicon glyphicon-ok"></sp\
an>
</a>
@else
<a class="btn btn-danger btn-xs" title="Un Ap\

51

52

Creating The Comments Controller

prove" href="#">
<span class="glyphicon glyphicon-remove">\
</span>
</a>
@endif
<a class="btn btn-danger btn-xs" title="Delet\
e" href="{{URL::action('AdminCommentsController@getDelete',$item->objectId)}}\
">
<span class="glyphicon glyphicon-trash"><\
/span>
</a>
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
@stop

View Comment

Delete Action:
Delete is some how the easiest function every, besides the query one, since we going just to delete
the comment record based on the record id, and then redirect it back to the list action, like:
public function getDelete($objectId = null)
{
if(is_null($objectId)){
return Redirect::action('AdminCommentsController@getIndex')->with('er\
ror','Choose a comment to delete');
}
try{
$recordInfo = new parseObject('comments');
$recordInfo->delete($objectId);

Creating The Comments Controller

53

return Redirect::action('AdminCommentsController@getIndex')
->with('success','The comment Has been deleted');
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

Now that we have finished most of the functions related to both posts and comments, in the
next chapter we are going to add some of the missing functionality for both the posts and the
comments, like:

Function to View comments related to a specific post.


Function to add a reply to comments on a specific post.
Function for our public index page.
Function for our public post page.
Function for adding comments on a specific post.
and more maybe ..

Putting everything together


Now that we have built our administration section, we need to have add some functionality
before we move on to create our front-end.

Posts Missing functionality


We have only one missing functionality which we must add to our Posts controller which is to
Publish/Un-Publish a specific post depending on the post object id.

Publish/Un-Publish a Specific Post


Since we have a field in our posts class which indicate if the posts must be publish or not (the
active field), updating the field value will reflect on our posts status, and as we have seen before
in the edit post we can do it directly by interacting with parseObject, so changing the value from
true to false and vise versa is what we need, lets see the code:
public function getHide($objectId = null){
if(is_null($objectId)){
return Redirect::action('AdminPostsController@getIndex')
->with('error','You must select a record to un-publish');
}
try{
$recordInfo = new parseObject('posts');
$recordInfo->active = false;
$recordInfo->update($objectId);
return Redirect::action('AdminPostsController@getIndex')
->with('success','Your Post Has been un-publish');
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}
//Changing the status of the Post to Published
public function getPublish($objectId = null){
if(is_null($objectId)){
return Redirect::action('AdminPostsController@getIndex')
->with('error','You must select a record to publish');
}

55

Putting everything together

try{
$recordInfo = new parseObject('posts');
$recordInfo->active = true;
$recordInfo->update($objectId);
return Redirect::action('AdminPostsController@getIndex')
->with('success','Your Post Has been published');
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

We now add the links to our Views. I will not put the full view code here, since we will have the
full code published on Github under the MIT license.
@if($item->active)
<a class="btn btn-success btn-xs" title="Published" href="{{URL::action('Admi\
nPostsController@getHide',$item->objectId)}}">
<span class="glyphicon glyphicon-ok"></span>
</a>
@else
<a class="btn btn-danger btn-xs" title="Un-Publish" href="{{URL::action('Admi\
nPostsController@getPublish',$item->objectId)}}">
<span class="glyphicon glyphicon-remove"></span>
</a>
@endif

If we clicked on the green button to un-publish a post, the result will be

Un-Publish a post
https://github.com/linuxjuggler/laravel-and-parse-book

56

Putting everything together

And if we clicked on the red button to publish/republish a post the result will be

Publish a post

Comments Missing functionality


Our Comments controller is missing a few pieces of functionality:
1. Getting comments for a specific post.
2. Reply to a comment for a specific post.
3. Approve/Un-Approve a comment.

Getting Comments for a specific post


Our Comments controller is missing the ability to get the comments for a specific post, right
now it only display all the comments which we have got, but this is not a functionality specific
to the comments itself, it is related to the posts controller too, but since we are going to have our
controller to handle only one class, we will add the code in here, and linked it in the post record
view. In the comments controller, we are going to add two function, one public, and this is the
one which will display the comments, and one private function to get the name of the post if
there is no comments related to this post.
public function getPostComments($postObjectId = null)
{
try{
if(is_null($postObjectId)){
return Redirect::action('AdminPostsController@getIndex')
->with('error','You must select a post to view');
}
$fullComments = new parseQuery('comments');
$fullComments->setCount(true);

Putting everything together

//this is the important field, which also get the post data
$fullComments->whereInclude('post');
$fullComments->where('post',$fullComments->dataType('pointer',arr\
ay('posts',$postObjectId)));
$fullComments->setLimit($this->perPage);
$fullComments->setSkip($this->skip);
$fullComments->orderByDescending('createdAt');
$comments = $fullComments->find();
if($comments->count == 0){
$postName = $this->_getPostName($postObjectId);
}else{
$postName = $comments->results[0]->post->title;
}

$paginator = Paginator::make($comments->results, $comments->count\


, $this->perPage);
$data = array(
'items'=> $comments->results,
'paginator' => $paginator,
'total' => $comments->count,
'postName' => $postName
);
return View::make('admin.comments.post-comment')->with($data);
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}
private function _getPostName($objectId = null)
{
if(is_null($objectId)){
return Redirect::action('AdminPostsController@getIndex')
->with('error','You must select a record to view');
}
try{
$recordInfo = new parseQuery('posts');
$recordInfo->where('objectId',$objectId);
$result = $recordInfo->find();
if(!empty($result->results)){

57

Putting everything together

return $result->results[0]->title;
}
throw new Exception('No Records found');

}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

And the html view code is


@extends('admin.layout')
@section('body')
<div class="row">
@if(Session::has('success'))
<div class="alert alert-success">{{Session::get('success')}}</div>
@endif
@if(Session::has('error'))
<div class="alert alert-danger">{{Session::get('error')}}</div>
@endif
<ol class="breadcrumb">
<li><a href="{{URL::action('AdminDashboardController@getIndex')}}">Ho\
me</a></li>
<li><a href="{{URL::action('AdminPostsController@getIndex')}}">All Po\
sts</a></li>
<li class="active">{{ $postName }} Comments</li>
<li class="pull-right no-before">
<a href="{{URL::route('logout')}}">
Logout
</a>
</li>
</ol>
<div class="panel panel-default widget">
<div class="panel-heading">
<span class="glyphicon glyphicon-comment"></span>
<h3 class="panel-title">
`{{ $postName }}` Comments</h3>
<span class="label label-info">
{{$total}}</span>

58

Putting everything together

</div>
<div class="panel-body">
<ul class="list-group">
@foreach($items as $item)
<li class="list-group-item">
<div class="row">
<div class="col-xs-2 col-md-1">
<img src="http://placehold.it/80" class="img-circ\
le img-responsive" alt="" /></div>
<div class="col-xs-10 col-md-11">
<div>
<a href="{{URL::action('AdminPostsController@\
getRecord',$item->post->objectId)}}">
{{ $item->post->title }}</a>
<div class="mic-info">
By: <a href="{{URL::action('AdminComments\
Controller@getRecord',$item->objectId)}}">{{$item->authorName}}</a>
on {{date('d-M-Y',strtotime($item->create\
dAt))}}
</div>
</div>
<div class="comment-text">
{{$item->commentBody}}
</div>
<div class="action">
<a class="btn btn-primary btn-xs" title="View\
" href="{{URL::action('AdminCommentsController@getRecord',$item->objectId)}}"\
>
<span class="glyphicon glyphicon-eye-open\
"></span>
</a>
@if(!$item->approved)
<a class="btn btn-success btn-xs" title="Appr\
oved" href="#">
<span class="glyphicon glyphicon-ok"></sp\
an>
</a>
@else
<a class="btn btn-danger btn-xs" title="Un Ap\
prove" href="#">
<span class="glyphicon glyphicon-remove">\
</span>
</a>
@endif
</div>
</div>

59

Putting everything together

60

</div>
</li>
@endforeach
</ul>
</div>
</div>
{{$paginator->links()}}
</div>
@stop

And we need to make sure that we have the code below in our admin/posts/record.blade.php
file
<a class="btn btn-primary btn-xs" title="View Post Comments" href="{{URL::act\
ion('AdminCommentsController@getPostComments',$item->objectId)}}">
<span class="glyphicon glyphicon-comment"></span>
</a>

Reply to a comment for a specific post


Last chapter, we mentioned the store functionality but we said that we will talk about it in this
chapter, so adding a reply to a comment is as easy as adding a post to Parse, and its the same for
any object you create in any class, except that we need to tell Parse that the post id is a pointer
to an object in the Posts class, and so the code will be like :
$comments->post = $comments->dataType('pointer',array('posts','J9mLUW0heO'));

And the full code for it is like


public function getAdd($postObjectId = null)
{
if(is_null($postObjectId)){
return Redirect::action('AdminPostsController@getIndex')
->with('error','Choose a post first');
}
return View::make('admin.comments.add');
}
public function postAdd(){
try{
$comment = new parseObject('comments');
$comment->authorName = Input::get('authorName');
$comment->authorEmail = Input::get('authorEmail');
http://Parse.com

Putting everything together

$comment->commentBody = Input::get('commentBody');
// as you can see we specify that the postId is a pointer.
$comment->post = $comment->dataType('pointer',array('posts',Input\
::get('postId')));
$comment->approved = true;
$result = $comment->save();
return Redirect::action('AdminPostsController@getRecord', $result\
->results[0]->objectId)
->with('success','Your comment has been added');
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

And the html code for the view is


@extends('admin.layout')
@section('body')
<div class="row">
@if(Session::has('success'))
<div class="alert alert-success">{{Session::get('success')}}</div>
@endif
@if(Session::has('error'))
<div class="alert alert-danger">{{Session::get('error')}}</div>
@endif
<ol class="breadcrumb">
<li><a href="{{URL::action('AdminDashboardController@getIndex')}}">Ho\
me</a></li>
<li><a href="{{URL::action('AdminCommentsController@getIndex')}}">ALL\
Comments</a></li>
<li class="active">Add New Reply</li>
<li class="pull-right no-before">
<a href="{{URL::route('logout')}}">
Logout
</a>
</li>
</ol>
<div class="panel panel-default widget">
<div class="panel-heading">

61

Putting everything together

62

<span class="glyphicon glyphicon-list-alt"></span>


<h3 class="panel-title">
Add New Post</h3>
</div>
<div class="panel-body">
{{Form::open(array('action'=>'AdminCommentsController@postAdd', '\
role'=>'form'))}}
<div class="form-group">
{{Form::label('authorName','Your Name :')}}
{{Form::text('authorName',null,array('id'=>'authorName','plac\
eholder'=>'Your Name', 'class'=>'form-control', 'required'=>'required'))}}
</div>
<div class="form-group">
{{Form::label('authorEmail','Your Email :')}}
{{Form::text('authorEmail',Auth::user()->email,array('id'=>'a\
uthorEmail','placeholder'=>'Your Email', 'class'=>'form-control', 'required'=\
>'required'))}}
</div>
<div class="form-group">
{{Form::label('commentBody','Comment :')}}
{{Form::textarea('commentBody',null,array('id'=>'commentBody'\
,'placeholder'=>'Comment Body', 'class'=>'form-control', 'required'=>'require\
d'))}}
</div>
{{Form::hidden('postId',$postObjectId)}}
{{Form::submit('Reply',array('class'=>'btn btn-primary'))}}
{{Form::close()}}
</div>
</div>
</div>
@stop

Approve/Un-Approve a comment
The default functionality is that when a guest submits their comment, it will be added but not
published until the admin approve it. Sometimes after you approve a comment you realize that
you need to hide it or un-publish it, so our functionality here is similar to the one we have in the
posts controller, so without any further explanation let us read the code

Putting everything together

63

public function getHide($objectId = null)


{
if(is_null($objectId)){
return Redirect::action('AdminCommentsController@getIndex')
->with('error','Choose a comment to un-publish');
}
try{
$recordInfo = new parseObject('comments');
$recordInfo->approved = false;
$recordInfo->update($objectId);
return Redirect::action('AdminCommentsController@getIndex')
->with('success','The comment Has been un-published.');
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}
public function getPublish($objectId = null)
{
if(is_null($objectId)){
return Redirect::action('AdminCommentsController@getIndex')
->with('error','Choose a comment to publish');
}
try{
$recordInfo = new parseObject('comments');
$recordInfo->approved = true;
$recordInfo->update($objectId);
return Redirect::action('AdminCommentsController@getIndex')
->with('success','The comment Has been approved.');
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

And we need to add the links to our comments view, for simplicity i will only include the html
code for the links

Putting everything together

64

@if($item->approved)
<a class="btn btn-success btn-xs" title="Approved" href="{{URL::action('Admin\
CommentsController@getHide',$item->objectId)}}">
<span class="glyphicon glyphicon-ok"></span>
</a>
@else
<a class="btn btn-danger btn-xs" title="Un Approve" href="{{URL::action('Admi\
nCommentsController@getPublish',$item->objectId)}}">
<span class="glyphicon glyphicon-remove"></span>
</a>
@endif

Front-end Functionality
Our Home Controller will have only three main functionality
1. Index function (getIndex), to get all of our posts to the front page.
2. Get Post function (getPost), to get the information about a specific post.
3. Posting a comment function (postAddComment), to handle the adding guest comments
on a specific post.
Now lets see how the home page is going to look like:

65

Putting everything together

Jasmine Blog - Home Page

Front-end Html Code


since the code is going to be located on Github, am not going to put the html code
and make reading the code boring for us, i will put a small amount of it, and once your
done reading this chapter you can go over Github and read the code there.

https://github.com/linuxjuggler/laravel-and-parse-book
https://github.com/linuxjuggler/laravel-and-parse-book

Putting everything together

66

getIndex Function
The code here is simple, all we need to do is to query Parse to get the posts that are published
and order them by creation date in descending order. We will also create the paginator, but lets
first prepare some of the general code within our constructor function.
<?php
class HomeController extends BaseController {
protected $perPage;
protected $skip;
public function __construct()
{
$this->perPage = Config::get('application.homePerPage');
$pageNo = Input::get('page');
$this->skip = (is_null($pageNo)) ? 0 : ( $this->perPage * ( $pageNo -\
1)) ;
}
}

Now the code to get the Posts is something we already done before within our AdminPostController
class, and its the same except that we need to get only the published posts.
public function getIndex()
{
try{
$posts = new parseQuery('posts');
$posts->setCount(true);
$posts->setLimit($this->perPage);
$posts->where('active', true);
$posts->setSkip($this->skip);
$posts->orderByDescending('createdAt');
$result = $posts->find();
$paginator = Paginator::make($result->results,$result->count,$this->per\
Page);
$data = array(
'posts' => $result->results,
'paginator' => $paginator,
'total' => $result->count);
return View::make('site.index')->with($data);
} catch(ParseLibraryException $e){

Putting everything together

67

throw new Exception($e->getMessage());


}
}

getPost Function
Our get post functionality will consist of two functions, the main one is inside the public function
and this will get the post from Parse, and the second one is private and it will get all the comments
which related to this post and which is also approved
public function getPost($postId = null)
{
if(is_null($postId)){
return Redirect::back()->with('error','You cant access this file dire\
ctly.');
}
try{
$post = new parseQuery('posts');
$post->where('objectId', $postId);
$post->where('active', true);
$result = $post->find();
$comments = $this->_getComment($result->results[0]->objectId);
$data = array(
'post' => $result->results[0],
'comments' => $comments->results,
'commentsCount' => $comments->count);
return View::make('site.post')->with($data);
} catch(ParseLibraryException $e){
return Redirect::back()->with('error',$e->getMessage());
}
}
private function _getComment($postId = null)
{
try {
$comments = new parseQuery('comments');
$comments->setCount(true);
$comments->where('post', $comments->dataType('pointer', array('posts'\
, $postId)));
$comments->where('approved', true);
$comments->orderByDescending('createdAt');
$result = $comments->find();
return $result;

Putting everything together

68

} catch (ParseLibraryException $e) {


throw new Exception($e->getMessage());
}
}

Adding A Comment
We have done that before, yes the same functionality for replying on a post comment apply here,
except that we need to make sure that the comment will not be automatically approved until we
read it, the code is
public function postAddComment()
{
try {
$allData
= Input::all();
$comment
= new parseObject('comments');
$comment->authorName = Input::get('authorName');
$comment->authorEmail = Input::get('authorEmail');
$comment->commentBody = Input::get('commentBody');
$comment->post
= $comment->dataType('pointer', array('posts', \
Input::get('postId')));
$comment->approved
= false;
$result = $comment->save();
return Redirect::action('HomeController@getPost', Input::get('postId'\
))
->with('success', 'Your comment has been added, and waiting f\
or approval.');
} catch (ParseLibraryException $e) {
return Redirect::back()->with('error', $e->getMessage());
}
}

And our comment form look like

69

Putting everything together

Post Comments Form

Conclusion
So far we have created a simple blog system, which depends on Parse Data as the database
for our posts/comments, in the next few chapters we will see how to refactor our Controllers to
create our Posts/Comments class which will interact with Parse instead of interacting with Parse
directly via the controllers. There is nothing wrong with that but also there is nothing wrong
with having your Parse Logic away from your controllers.
Also we will try to check for what the missing functionality inside the PHP Parse Library and
how we can add it to our benefit.
http://parse.com

Refactoring the posts controller, to


use Models
As we have found in the past few chapters, we have had to create some of the functionality over
and over again, for example getting the posts in the admin section and getting the posts in the
front-page.
As a good OOP practice, we will have to create a unified class which has only one functionality,
which is to communicate with our posts class in Parse.com.
In Laravel, there are many ways to do that, for example we can create our own package, we can
create a model class, or we can create our own classes and use them inside our controllers, so
there is no limitation to what we can do, we just need to do what we find suitable to our project.
Here, we are going to create our own model class, since at the end we are using Parse.com as our
database.

Create the Posts Model Class


If we open our Model directory we will not see anything special except our User class which
was created by Laravel for us as an example of how to handle the users authentications, and our
structure will be something like the image:

Refactoring the posts controller, to use Models

Our Application Structure

71

Refactoring the posts controller, to use Models

72

Now all we need to do is to create a new Model class and we are going to follow the convenient
naming method which Laravel use for its model classes, so the name of the model class will
be singular Post meanwhile the name of our Parse.com class is plural posts. So our file will be
app/models/Post.php and it will look like :
1
2
3
4

<?php
class Post {
protected $tablename = 'posts';
}

As you can see, we have just define a normal class called Post and a protected variable
$tablename which define our Parse.com class name.

Create getPosts Function


Now the first functionality we need to create is to get all the posts from Parse.com, since this
functionality is used in both admin area, and guests area we can combine them in one function
like this :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

/**
* @param null $active
* @param int $limit
* @param int $skip
* @param string $orderBy
* @return bool|mixed
* @throws Exception
*/
public function getPosts($active = null, $limit = 5, $skip = 0, $orderBy ='cr\
eatedAt' )
{
try{
$records = new parseQuery($this->tablename);
if(!is_null($active)){
$records->whereEqualTo('active', $active);
}
$records->orderByDescending('createdAt');
$records->setCount(true);
$records->setLimit($limit);
$records->setSkip($skip);
$result = $records->find();
http://laravel.com/docs/eloquent#basic-usage

Refactoring the posts controller, to use Models

25
26
27
28
29
30

73

return $result;
} catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

This function will have a default parameters which we need to use for retrieving our records,
for example the default values here indicate that we need to get the first 5 records starting from
record one ordered descending by the field createdAt. Since we set the active to null we will
not filter the posts based on the status of the post and this something important for the admin,
to see all the records no matter if its published or not.
Now lets see the impact of this function on our getIndex function in AdminPostsController
class.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

public function getIndex()


{
try{
//$records = new parseQuery('posts');
//$records->setCount(true);
//$records->setLimit($this->perPage);
//$records->setSkip($this->skip);
//$result = $records->find();
$posts = new Post();
$result = $posts->getPosts(null,$this->perPage,$this->skip);
$paginator = Paginator::make($result->results,$result->count,$this->p\
erPage);
$data = array(
'items'=> $result->results,
'paginator' => $paginator,
'total' => $result->count
);
return View::make('admin.posts.list')->with($data);
} catch(Exception $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

As we can see, we have replaced five lines of code with only two lines. And the reflection on
getIndex function in HomeController class is :

Refactoring the posts controller, to use Models

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

74

public function getIndex()


{
try {
//$posts = new parseQuery('posts');
//$posts->setCount(true);
//$posts->setLimit($this->perPage);
//$posts->where('active', true);
//$posts->setSkip($this->skip);
//$posts->orderByDescending('createdAt');
//$result
= $posts->find();
$posts = new Post();
$result = $posts->getPosts(true, $this->perPage, $this->skip);
$paginator = Paginator::make($result->results, $result->count, $this-\
>perPage);
$data = array(
'posts'
=> $result->results,
'paginator' => $paginator,
'total'
=> $result->count
);
return View::make('site.index')->with($data);
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
}

And as you can see, we have replaced seven lines of code with two lines, and we reused the same
function which we created in our Post class.

Create getPost Function


We have here two options:
1. We can edit the getPosts function to handle also the functionality of getting one post by
just sending the objectId as parameter and check for it.
2. Or we can create separate function to do so, I tend to like this option, so I will do that, but
feel free to try the first option if you like.
To retrieve the post from Parse.com we need to provide the objectId and since we dont need the
guests visitors from seeing or accessing non-published posts, we can provide a second parameter
which define the status of the post, so the final code for this function will look like :

Refactoring the posts controller, to use Models

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

/**
* @param $objectId the post object Id
* @param null $active the post status
* @return null
* @throws Exception
*/
public function getItem($objectId, $active = null)
{
try{
$recordInfo = new parseQuery($this->tablename);
$recordInfo->where('objectId',$objectId);
if(!is_null($active))
$recordInfo->whereEqualTo('active',$active);
$result = $recordInfo->find();
if(!empty($result->results)){
return $result->results[0];
}
return null;
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

And the impact on our getRecord function from our AdminPostsController is :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public function getRecord($objectId = null)


{
if(is_null($objectId)){
return Redirect::action('AdminPostsController@getIndex')
->with('error','You must select a record to view');
}
try{
//$recordInfo = new parseQuery('posts');
//$recordInfo->where('objectId',$objectId);
//$result = $recordInfo->find();
$recordInfo = new Post();
$result = $recordInfo->getItem($objectId);
//$data = array('item'=>$result->results[0]);

75

Refactoring the posts controller, to use Models

17
18
19
20
21
22
23
24

76

$data = array('item'=>$result);
return View::make('admin.posts.record')->with($data);
}catch(Exception $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

And the impact on our getEdit function from our AdminPostsController is :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

public function getEdit($objectId = null)


{
if(is_null($objectId)){
return Redirect::to('/admin/posts')->with('error','You must select a \
record to edit');
}
try{
//$recordInfo = new parseQuery('posts');
//$recordInfo->where('objectId',$objectId);
//$result = $recordInfo->find();
$recordInfo = new Post();
$result = $recordInfo->getItem($objectId);
//$data = array('item'=>$result->results[0]);
$data = array('item'=>$result);
return View::make('admin.posts.edit')->with($data);
}catch(Exception $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

As you can see not that much, but we gain the ability of using it in many other function like
getPost function from our HomeController class like this:

Refactoring the posts controller, to use Models

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

77

public function getPost($postId = null)


{
if (is_null($postId)) {
return Redirect::back()->with('error', 'You cant access this file dir\
ectly.');
}
try {
//$post = new parseQuery('posts');
//$post->where('objectId', $postId);
//$post->where('active', true);
//$result
= $post->find();
$post = new Post();
$result
= $post->getItem($postId, true);
//$comments = $this->_getComment($result->results[0]->objectId);
$comments = $this->_getComment($result->objectId);
$data
= array(
//'post'
=> $result->results[0],
'post'
=> $result,
'comments'
=> $comments->results,
'commentsCount' => $comments->count);
return View::make('site.post')->with($data);
} catch (Exception $e) {
return Redirect::back()->with('error', $e->getMessage());
}
}

As you can see, we have sent the active parameter to our function which will make sure that
we will get the item if its published.
Can you spot what we have changed too??
We have changed the Exception which we catch in the AdminPostsController and
HomeController from ParseLibraryException to the regular Exception since we are
catch it in in our Post class and throwing a normal Exception.

Creating deleteItem Function


From the name of the item, it will only have one which is to delete the record from Parse.com,
but also we need to have a small trick to delete all the comments which belong to the post, so
for now you can ignore the section where delete the comments executed, and we will talk about
it later in Comment Model section.

Refactoring the posts controller, to use Models

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

78

/**
* @param $itemId post id
* @return bool
* @throws Exception
*/
public function deleteItem($itemId)
{
try{
$comments = new Comment();
$commentsResult = $comments->getPostComments($itemId);
if($commetsResult->count > 0){
foreach($commetsResult->results as $item){
$comments->deleteComment($item->objectId);
}
}
$recordInfo = new parseObject($this->tablename);
$recordInfo->delete($objectId);
return true;
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
} catch(Exception $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

And we need only to change a small code in the delete function within our AdminPostsController
like this:
1
2
3
4

//$recordInfo = new parseObject('posts');


//$recordInfo->delete($objectId);
$recordInfo = new Post('posts');
$recordInfo->deleteItem($objectId);

Create handleItem Function


This function is going to be responsible of handling the following actions:
1. Create a new Post.
2. Edit a Post.

79

Refactoring the posts controller, to use Models

3. Change the status of a Post.


To make sure that the function will do exactly what we want it to do, we will send the input and
a parameter to tell the function if we are editing or creating a record.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

/**
* @param $input input data
* @param bool $isEdit to check if its update or create
* @return bool|mixed
* @throws Exception
*/
public function handleItem($input, $isEdit = false)
{
try{
$postData = new parseObject($this->tablename);
if(isset($input['title']))
$postData->title = $input['title'];
if(isset($input['body']))
$postData->body = $input['body'];
$postData->active = (isset($input['active'])) ? $input['active'] :

t\

rue;
if($isEdit){
$result = $postData->update($input['objectId']);
} else {
$result = $postData->save();
}
return $result;
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

So our first parameter is the input data which we want to use for creating or editing a record, if
we are creating a new record we need only to send the input data, meanwhile if we are editing
a record we must set the second parameter $isEdit to true which will call the update function
within the parseObject class.
And here is how to use this function within our AdminParseController class.

Create a Record
Lets first recall how our postAdd function was written :

Refactoring the posts controller, to use Models

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

80

public function postAdd(){


try{
$postData = new parseObject('posts');
$postData->title = Input::get('title');
$postData->body = Input::get('body');
$postData->active = true;
$result = $postData->save();
return Redirect::action('AdminPostsController@getRecord', $result->ob\
jectId)->with('success','Your Post Has been added');
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

As you can remember, we create an instance of parseObject class, we provided it with the data
and we send it to Parse.com, and lets see how it will be after using our Post class :
1
2
3
4
5
6
7
8
9
10
11
12

public function postAdd(){


try{
$post = new Post();
$result = $post->handleItem(Input::all());
return Redirect::action('AdminPostsController@getRecord', $result->ob\
jectId)->with('success','Your Post Has been added');
}catch(Exception $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

As you can see, we have replaced the old five lines of code with only two lines of code, and it is
much nicer and easier to understand dont you agree.

Edit a Record
Editing a record looks exactly the same as creating new one, except that we need to call the
update function instead of the save function, so the old code for this function was:

Refactoring the posts controller, to use Models

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

81

public function postEdit()


{
try{
$postData = new parseObject('posts');
$postData->title = Input::get('title');
$postData->body = Input::get('body');
$postData->active = true;
$result = $postData->update(Input::get('objectId'));
return Redirect::action('AdminPostsController@getRecord', Input::get(\
'objectId'))->with('success','Your Post Has been updated');
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

And the new code has become:


1
2
3
4
5
6
7
8
9
10
11
12
13

public function postEdit()


{
try{
$post = new Post();
$result = $post->handleItem(Input::all(), true);
return Redirect::action('AdminPostsController@getRecord', Input::get(\
'objectId'))->with('success','Your Post Has been updated');
}catch(Exception $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

And the result is the same as before, we have replaced five lines of code, with only two simple
lines.

Publish/Un-Publish a Record
Our new functions has become more simpler and is now using the new function from Post class
and the result is :

Refactoring the posts controller, to use Models

1
2
3
4
5
6
7
8
9
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

82

public function getHide($objectId = null){


if(is_null($objectId)){
return Redirect::action('AdminPostsController@getIndex')
->with('error','You must select a record to un-publish');
}
try{
$input = array('active'=>false, 'objectId'=>$objectId);
$post = new Post();
$result = $post->handleItem($input, true);
return Redirect::action('AdminPostsController@getIndex')
->with('success','Your Post Has been un-publish');
}catch(Exception $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}
public function getPublish($objectId = null){
if(is_null($objectId)){
return Redirect::action('AdminPostsController@getIndex')
->with('error','You must select a record to publish');
}
try{
$input = array('active'=>true, 'objectId'=>$objectId);
$post = new Post();
$result = $post->handleItem($input, true);
return Redirect::action('AdminPostsController@getIndex')
->with('success','Your Post Has been published');
}catch(Exception $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

As you can see, by creating our Post class we have make our code easy to understand and reusable
in many places, all we need to do is to send a small parameter and it will change how the function
will react. In the next chapter we will do the same but with our Comments by creating our
Comment class.

Refactoring the comments


controller, to use Models
As we have created our Post Model, we are going to create our Comment Model, nothing is going
to change except that now we are going to deal with the comments class.

The Comments Model Class


First thing we need to do is to create a new Model class and we are going to follow the convenient
naming method which Laravel use for its model classes, so the name of the model class will be
singular Comment meanwhile the name of our Parse.com class is plural comments. As a result our
file will be app/models/Comment.php and it will look like :
1
2
3
4

<?php
class Comment {
protected $tablename = 'comments';
}

As you can see, we have just define a normal class called Comment and a protected variable
$tablename which define our Parse.com class name.

getComments Function
Now the first functionality we need to create is to get all the comments from Parse.com, and this
is used in the admin area to see all of the comments despite of the comment status:
1
2
3
4
5
6
7
8
9
10
11

public function getComments($active = null, $limit = 5, $skip = 0, $orderBy =\


'createdAt' )
{
try{
$fullComments = new parseQuery('comments');
$fullComments->setCount(true);
$fullComments->whereInclude('post');
$fullComments->setLimit($limit);
$fullComments->setSkip($skip);
$fullComments->orderByDescending($orderBy);
http://laravel.com/docs/eloquent#basic-usage

Refactoring the comments controller, to use Models

12
13
14
15
16
17
18
19
20
21

84

if(!is_null($active))
$fullComments->where('approved',$active);
$comments = $fullComments->find();
return $comments;
} catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), 406);
}
}

This function will have some default parameters which will be used to retrieve the records, for
example the default values here indicate that we need to get the first 5 records starting from
record one ordered descending by the field createdAt. Since we set the active to null we will
not filter the comments based on the status of the comment and this something important for the
admin, to see all the records no matter if its published or not.
Now lets see the impact of this function on our getIndex function in AdminPostsController
class.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

public function getIndex()


{
try{
//$fullComments = new parseQuery('comments');
//$fullComments->setCount(true);
//this is the important field, which also get the post data
//$fullComments->whereInclude('post');
//$fullComments->setLimit($this->perPage);
//$fullComments->setSkip($this->skip);
//$fullComments->orderByDescending('createdAt');
//$comments = $fullComments->find();
$commentsModel = new Comment;
$comments = $commentsModel->getComments();
$paginator = Paginator::make($comments->results, $comments->count, $t\
his->perPage);
$data = array(
'items'=> $comments->results,
'paginator' => $paginator,
'total' => $comments->count
);
return View::make('admin.comments.list')->with($data);

Refactoring the comments controller, to use Models

27
28
29
30
31

85

}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

As we can see, we have replaced seven lines of code with only two lines.

getComment Function
To get the comment from Parse.com like any other record, all we have to do is to create a function
and pass the id of the comment which we want, then we query Parse.com and retrieve the actual
data, as simple as one two there:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

public function getComment($commentId)


{
try{
$fullComment = new parseQuery('comments');
$fullComment->whereInclude('post');
$fullComment->setLimit(1);
$fullComment->where('objectId',$commentId);
$comment = $fullComment->find();
return $comment;
} catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), 406);
}
}

So the getRecord function has become like:


1
2
3
4
5
6
7
8
9
10
11
12

public function getRecord($objectId = null)


{
if(is_null($objectId)){
return Redirect::action('AdminCommentsController@getIndex')->with('er\
ror','Choose a comment to view');
}
try{
//$commentRecord = new parseQuery('comments');
//$commentRecord->where('objectId', $objectId);
//$commentRecord->whereInclude('post');
//$commentRecord->setLimit(1);

Refactoring the comments controller, to use Models

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

86

//$result = $commentRecord->find();
$comment = new Comment;
$result = $comment->getComment($objectId);
$data = array(
'item'=> $result->results[0],
);
return View::make('admin.comments.record')->with($data);

}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

As we can see, we have replaced the 5 lines of code with only two lines of code, I guess its not
that bad.

getPostComments Function
We have used this function within our Post Model to retrieve the post comments, and it will has
some default arguments which we can use

1
2
3
4
5
6
7
8
9
10
11
12
13

The post object id.


The status of the comments.
How many records to skip.
How many records to retrieve.

public function getPostComments($postId, $status = null, $skip = null, $limit\


= null)
{
try{
$postComments = new parseQuery($this->tablename);
$postComments->setCount(true);
$postComments->whereInclude('post');
$postComments->where('post',$fullComments->dataType('pointer',array('\
posts',$postId)));
if(!is_null($limit))
$postComments->setLimit($limit);

Refactoring the comments controller, to use Models

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

87

if(!is_null($skip))
$postComments->setSkip($skip);
if(!is_null($status))
$postComments->where('approved',$status);
$postComments->orderByDescending('createdAt');
$comments = $postComments->find();
return $comments;
} catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), 406);
}
}

With those simple arguments, we can use the same function to get the comments for a specific
post within the admin dashboard and the blog post which is accessible to the guests.

deleteItem Function
Deleting a record is something simple, all we need to do is to create a function and pass the id
of the comment which we want to delete like :
1
2
3
4
5
6
7
8
9
10
11
12
13

public function deleteComment($commentId)


{
try{
$comment = new parseQuery($this->tablename);
$comment->delete($commentId);
return true;
} catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), 406);
}
}

handleItem Function
This function is going to be some how a general function between the Create, Edit and Publish
& Un-Publish a comment.
We are going to provide it with only two parameters:

Refactoring the comments controller, to use Models

88

The Input array.


Is it an edit action or not, the default is not.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

public function handelComment($input, $isEdit = false)


{
try{
$commentData = new parseObject($this->tablename);
if(isset($input['authorEmail']))
$commentData->authorEmail = $input['authorEmail'];
if(isset($input['postId']))
$commentData->post = $commentData->data(array('pointer',array('po\
sts',$input['postId'])));
if(isset($input['authorName']))
$commentData->authorName = $input['authorName'];
if(isset($input['commentBody']))
$commentData->commentBody = $input['commentBody'];

ed'] :

$commentData->approved = (isset($input['approved'])) ? $input['approv\


false;
if($isEdit){
$result = $commentData->update($input['objectId']);
} else {
$result = $commentData->save();
}
return $result;

}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), 406);
}
}

To make it short, am going to cutoff the data, and just show you the updated functions on our
CommentsController after using the handelComment function.

Refactoring the comments controller, to use Models

1
2
3
4
5
6
7
8
9
10
11
12
13

public function postAdd(){


try{
$comment = new Comment;
$result = $comment->handelComment(Input::all());

1
2
3
4
5
6
7
8
9
10
11
12
13
14

public function postEdit()


{
try{
$comment = new Comment;
$result = $comment->handelComment(Input::all(), true);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

public function getHide($objectId = null)


{
if(is_null($objectId)){
return Redirect::action('AdminCommentsController@getIndex')
->with('error','Choose a comment to un-publish');
}

return Redirect::action('AdminPostsController@getRecord', $result->re\


sults[0]->objectId)
->with('success','Your comment has been added');
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

return Redirect::action(array('PostsController@getRecord', Input::get\


('objectId')))
->with('success','The comment has been updated');
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

try{
$input = array('approved' => false, 'objectId' => $objectId);
$recordInfo = new Comment;
$recordInfo->handelComment($input, true);

return Redirect::action('AdminCommentsController@getIndex')
->with('success','The comment Has been un-published');

89

Refactoring the comments controller, to use Models

16
17
18
19
20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

90

}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}
public function getPublish($objectId = null)
{
if(is_null($objectId)){
return Redirect::action('AdminCommentsController@getIndex')
->with('error','Choose a comment to approve');
}
try{
$input = array('approved' => true, 'objectId' => $objectId);
$recordInfo = new Comment;
$recordInfo->handelComment($input, true);
return Redirect::action('AdminCommentsController@getIndex')
->with('success','The comment Has been approved');
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}

And the changes on our postAddComment function under the HomeController is :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

public function postAddComment()


{
try {
$allData = Input::all();
$comment = new Comment;
$result = $comment->handelComment($allData);
return Redirect::action('HomeController@getPost', Input::get('postId'\
))
->with('success', 'Your comment has been added, and waiting f\
or approval.');
} catch (ParseLibraryException $e) {
return Redirect::back()->with('error', $e->getMessage());
}
}

In the next few chapters, we will have some more information about the parseQuery class which
is the most important class here, and see how to use it in more details, but first we will have a
small tips on how to install Laravel 4.1 with nginx (latest version) & PHP 5.5 on Ubuntu 12.04.

Mastering Parse Query Class


In this chapter i will try my best to explain the parseQuery class which is the most class you will
ever use when working with Parse.com beside the parseObject class.

How to use parse query class


If we open the parseQuery.php file which located at libraries/parse we will notice that this
class is extended from parse class.

Parse class (parse.php file)


If we have a look at the parse.php file, we will find that it is responsible for making the request
to Parse.com and to manage our data types. The most important function is dataType which we
have used to create the pointer to our post object id.
For now, the function cover only five data types and two operations (used with counter-type
data), which are: * Date data type. * Bytes data type. * Pointer data type. * Geographical Pointer
data type. * File data type. * Increment operation. * Decrement operation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

public function dataType($type,$params){


if($type != ''){
switch($type){
case 'date':
$return = array(
"__type" => "Date",
"iso" => date("c", strtotime($params))
);
break;
case 'bytes':
$return = array(
"__type" => "Bytes",
"base64" => base64_encode($params)
);
break;
case 'pointer':
$return = array(
"__type" => "Pointer",
"className" => $params[0],
"objectId" => $params[1]
);

92

Mastering Parse Query Class

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

break;
case 'geopoint':
$return = array(
"__type" => "GeoPoint",
"latitude" => floatval($params[0]),
"longitude" => floatval($params[1])
);
break;
case 'file':
$return = array(
"__type" => "File",
"name" => $params[0],
);
break;
case 'increment':
$return = array(
"__op" => "Increment",
"amount" => $params[0]
);
break;
case 'decrement':
$return = array(
"__op" => "Decrement",
"amount" => $params[0]
);
break;
default:
$return = false;
break;
}
return $return;
}
}

Sadly, thats all what it covers, but the good news is that we can always add whatever we want,
so for example if i want to add an array data type, we simply add a new case to the switch like
this :

Mastering Parse Query Class

1
2
3
4
5
6

93

case 'addArray':
$return = array(
"__op" => "Add",
"objects" => $params
);
break;

And this will be converted to :


1

{"__op":"Add","objects":["flying","kungfu"]}

Which will be add it to an array field in our class. But also we can always check github
repository for any new pull requests which can save us a small time.
To fully understands the objects in Parse.com Data, I highly recommend that you read
the full documentation which describe everything in details.

Parse Query class


If we take a good look at the parseQuery.php file which located under app/libraries/parse,
we will notice that the class contains of twenty five functions:
find function: Which prepare our parameters and return the result of executing the
request function which inherited from parse class.
setCount function: We have used this function within our codes, and it will just add the
count parameter to our query, and this will return the total numbers of records we have
in Parse Data, and we can use it to minimize the numbers of requests we do to get the data
from Parse.com.
getCount function: This function will return the total numbers of records we have.
setLimit function: This function will be used to tell Parse.com how many records we want
to retrieve, but be careful since Parse.com does not allow more than 1000 records to return
via each query.
setSkip function: This function will be used to tell Parse.com how many records we want
to skip before we get our records.
orderBy function: This function will tell Parse.com which field we want to use to order
ascending our retrieved records.
orderByAscending function: Is identical to the orderBy function.
orderByDescending function: Is the opposite to the orderBy and orderByAscending
functions, but they all work the same, by just send the field name as a parameter to the
function.
https://github.com/apotropaic/parse.com-php-library/pulls
https://parse.com/docs/rest#objects
https://parse.com/docs/rest#queries-counting

94

Mastering Parse Query Class

whereInclude function: In parse, we saw that we can have fields as Pointer which we
can think of as Foreign key to the primary class, and in general if we query a class which
has a pointer field to another class, we will get only the objectId, type and the class name
of that field, but if we tell Parse.com that we need to include this field, we will get all the
information, except it will work for only one level so you cant get the full information of
a pointers pointer fields.

1
2
3
4
5
6

$comments = new parseQuery('comments');


$comments->setSkip(0);
$comments->setLimit(1);
$comments->setCount(true);
$result = $comments->find();
return Response::json($result);

and the result will be :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

{
"results": [
{
"post": {
"__type": "Pointer",
"className": "posts",
"objectId": "J9mLUW0heO"
},
"approved": false,
"authorEmail": "test@gmail.com",
"authorName": "jone doe",
"commentBody": "this is the comment of the year",
"createdAt": "2013-09-06T17:50:46.124Z",
"updatedAt": "2013-10-12T16:31:44.834Z",
"objectId": "lfZlfnfmTY"
}
],
"count": 3
}

As we can see, since we didnt tell the Parse.com that we need to include the post pointers, we
didnt get much information, but if we just add the whereInclude the results will be different:

Mastering Parse Query Class

1
2
3
4
5
6
7

$comments = new parseQuery('comments');


$comments->setSkip(0);
$comments->whereInclude('post');
$comments->setLimit(1);
$comments->setCount(true);
$result = $comments->find();
return Response::json($result);

So lets see the result:


1
2
3
4
5
6
7
8
9
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

{
"results": [
{
"post": {
"active": true,
"body": "Contrary to popular belief, Lorem Ipsum is not simpl\
y random text. It has roots in a piece of classical Latin literature from 45 \
BC, making it over 2000 years old. Richard McClintock, a Latin professor at H\
ampden-Sydney College in Virginia, looked up one of the more obscure Latin wo\
rds, consectetur, from a Lorem Ipsum passage, and going through the cites of \
the word in classical literature, discovered the undoubtable source. Lorem Ip\
sum comes from sections 1.10.32 and 1.10.33 of \"de Finibus Bonorum et Maloru\
m\" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is\
a treatise on the theory of ethics, very popular during the Renaissance. The\
first line of Lorem Ipsum, \"Lorem ipsum dolor sit amet..\", comes from a li\
ne in section 1.10.32.\r\n\r\nThe standard chunk of Lorem Ipsum used since th\
e 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.3\
3 from \"de Finibus Bonorum et Malorum\" by Cicero are also reproduced in the\
ir exact original form, accompanied by English versions from the 1914 transla\
tion by H. Rackham.",
"title": "Where does Lorem Ipsum come from?",
"createdAt": "2013-09-06T17:49:11.938Z",
"updatedAt": "2013-11-24T05:41:39.590Z",
"objectId": "J9mLUW0heO",
"__type": "Object",
"className": "posts"
},
"approved": false,
"authorEmail": "test@gmail.com",
"authorName": "jone doe",
"commentBody": "this is the comment of the year",
"createdAt": "2013-09-06T17:50:46.124Z",
"updatedAt": "2013-10-12T16:31:44.834Z",
"objectId": "lfZlfnfmTY"
}
],

95

Mastering Parse Query Class

37
38

96

"count": 3
}

Note:
nice thing about the whereInclude function is that you can pass a string as parameter,
which will contain the names of the fields you want to include, separated by comma
like: posts,users,someotherfield, and it will get all the information you asked for.

where and whereEqualTo functions: both functions do the same thing, they get the key
and the value as parameters, so we tell Parse.com that we need the record/records which
a field (key) has the matching value.
whereNotEqualTo function: Is the opposite of the where function, so it will help us to
exclude the records which has the field we specify, with the value we provide.
whereGreaterThan function: We can use this function to get all the records which has
the field we specify, with value greater than the value we provide.
whereLessThan function: Is the opposite of the whereGreaterThan function.
whereGreaterThanOrEqualTo function: This function work the same as whereGreaterThan
except that it will also include the records which has the field we specify, with value also
equal to the one we provide.
whereLessThanOrEqualTo function: This will work the same as whereLessThan but it
will include also the records which has the field we specify, with value also equal to the
one we provide.
whereContainedIn function: This will help us to get all the records which has the value
of the field we specify as one of the value we provide as array.
1
2
3

$games = new parseQuery('games');


$games->whereContainedIn('score',array(1,2,4));
$result = $games->find();

whereNotContainedIn function: Work as the opposite of the whereContainedIn function.


whereExists function: Some times we may have records with fields contains no data, for
Parse.com it will be fields of undefined value, so this function will make sure that we can
get all the records we want which has value.
1
2
3

$games = new parseQuery('games');


$games->whereExists('score');
$result = $games->find();

whereDoesNotExist function: Is the opposite of whereExists function.


whereRegex function: We can use this function to query the data based on regular
expression pattern, but you should note that Parse.com start deprecating general regex
queries that dont match an exact prefix.

Mastering Parse Query Class

1
2
3
4
5

97

$games = new parseQuery('games');


// this will get all the games which has the name field
// start with `J`
$games->whereRegex('gameName',"^\QJ\E");
$result = $games->find();

wherePointer function: This is so useful when dealing with pointers, we didnt use it
but for example we can use it when querying the comments class for records belong to a
specific post.
whereInQuery function: If you want to retrieve objects where a field contains an object
that matches another query, you can use the whereInQuery function. Note that the default
limit of 100 and maximum limit of 1000 apply to the inner query as well, so with large data
sets you may need to construct queries carefully to get the desired behavior. For example,
imagine you have Post class and a Comment class, where each Comment has a relation to
its parent Post. You can find comments on posts with images by doing:

1
2
3
4
5
6
7
8

$comments = new parseQuery('comments');


$comments->whereInQuery('post','posts',array(
'where'=> array(
'image' => array('$exists' => true)
)
));
$result = $comments->find();
return Response::json($result);

I know its not that pretty but this is how to get it, and the parameters which will be sent to
Parse.com will be like:
1
2

where={"post":{"$inQuery":{"where":{"image":{"$exists":true}}},"className":"p\
osts"}}

whereNotInQuery function: I think by the name we knows what it will do, its the
opposite of the whereInQuery function.
By now we have had fast overview of what doe the parseQuery class has for us, and how to use
it when querying data from Parse.com

Learning Resources
More about Parse Products
If you like to read more about Parse.com and how to use it or how other developers are using it
check :

Parse.com Documentation.
Parse.com Tutorials.
Parse.com Case Study and Featured.
Parse.com Quick start.

More about Laravel


But if your looking to read more about Laravel you can check:

Laravel Documentation.
Laravel: From Apprentice To Artisan.
Laravel: Code Bright.
Laravel 4 Cookbook.
Learning Laravel: The Easiest Way.
Laravel Testing Decoded.
Build APIs You Wont Hate.
Laracasts Video Tutorials.

https://parse.com/docs/rest
https://parse.com/tutorials
https://parse.com/customers/case_study
https://parse.com/customers/featured
https://parse.com/apps/quickstart
http://laravel.com/docs
https://leanpub.com/laravel
https://leanpub.com/codebright
https://leanpub.com/laravel4cookbook
https://leanpub.com/learninglaravel
https://leanpub.com/laravel-testing-decoded
https://leanpub.com/build-apis-you-wont-hate
https://laracasts.com/

Preparing your production server


I have put this code as a gist on github gists, so that you can easily fork it or comment on it
if you find something useful to share with the rest of us.

Creating Your Laravel & nginx Server


We will install Larave 4.1 with PHP5.5 & Latest nginx on Ubuntu 12.04.3 x64.

Updating your system


1
2
3
4

apt-get
adduser
usermod
apt-get

update && apt-get upgrade


[username]
-aG sudo [username]
-y install git

Now we need to logout and login using the user which we have created
1
2

git config --global user.name "your name"


git config --global user.email youremail@serviceprovider.com

Installing latest nginx version


Ubuntu 12.04 does not include the latest stable version of nginx, thats why we need to add the
repository from the nginx website
1
2
3
4
5
6
7
8

sudo -s
nginx=stable
apt-get -y install python-software-properties
add-apt-repository ppa:nginx/$nginx
apt-get update && apt-get upgrade
apt-get -y install nginx
service nginx start
exit

Installing php5.5
When installing Ubuntu 12.04 you will get the version 5.3.x of php, and since the latest version
is 5.5.x, we need to add an external repository to make sure we get the latest version of php.
https://gist.github.com/linuxjuggler/7812986
https://gist.github.com

Preparing your production server

1
2
3
4
5

100

sudo -s
add-apt-repository ppa:ondrej/php5
apt-get update && apt-get upgrade
apt-get -y install php5-fpm php5-mcrypt php5-sqlite sqlite php5-cli php5-xcac\
he php5-curl php5-json

Just remember that if we want to install mysql we should also install php5-mysql and any other
required module.
Note :
Someone noted out there that you should edit your php.ini file and change the value of
cgi.fix_path
1
2

sudo -s
nano /etc/php5/fpm/php.ini

change the value of cgi.fix_path from :


1

;cgi.fix_path = 1

to
1

cgi.fix_path = 0

Installing composer
Installing Composer is simple and easy, we download the composer.phar file then we copy it to
the bin directory to make sure that we can use it globaly.
1
2

curl -sS https://getcomposer.org/installer | php


sudo mv composer.phar /usr/local/bin/composer

From time to time we need to make sure that we have the latest version of composer so we issue
the command:
1

sudo composer self-update

Installing laravel
Now that we have installed composer we can use it to install laravel in a dirctory of our choice,
commenly used /var/www , most severs by default does not have it if they dont have apache
installed by default .

Preparing your production server

1
2
3
4
5
6

101

sudo -s
cd /var
mkdir www
chown -R [username]:[username] www
exit
cd www && composer create-project laravel/laravel

We will just change the owner of the storage directory to be the web server, or we can just make
it writable for all users, I like changing the ownership ..
1

chown -R www-data:www-data laravel/app/storage

Configure nginx for laravel


Now that we have everything we need, we still have one last step, which is to configure nginx
so that it will serve our site.
1
2
3
4
5

sudo -s
cd /etc/nginx/sites-avaliable
# if you didnt find this directory, try to check `/etc/nginx/conf.d/`
rm default
nano default

then we have to paste this and edit it as we need


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

server {
# Port that the web server will listen on.
listen
80;
# Host that will serve this project.
server_name
.jasmine.dev;
# Useful logs for debug.
access_log
/var/www/laravel/access.log;
error_log
/var/www/laravel/error.log;
rewrite_log
on;
# The location of our projects public directory.
root
/var/www/laravel/public;
# Point index to the Laravel front controller.
index
index.php;

102

Preparing your production server

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

location / {
# URLs to attempt, including pretty ones.
try_files
$uri $uri/ /index.php?$query_string;
}
# Remove trailing slash to please routing system.
if (!-d $request_filename) {
rewrite
^/(.+)/$ /$1 permanent;
}
# PHP FPM configuration.
location ~* \.php$ {
fastcgi_pass
fastcgi_index
fastcgi_split_path_info
include
fastcgi_param
stcgi_script_name;
}

unix:/var/run/php5-fpm.sock;
index.php;
^(.+\.php)(.*)$;
/etc/nginx/fastcgi_params;
SCRIPT_FILENAME $document_root$fa\

# We don't need .ht files with nginx.


location ~ /\.ht {
deny all;
}
# Set header expirations on per-project basis
location ~* \.(?:ico|css|js|jpe?g|JPG|png|svg|woff)$ {
expires 365d;
}
}

Final steps:
I like to have a link to my Laravel installation under my home directory so :
1

$ ln -s /var/www/laravel www

Finally we restart nginx or we can just reboot your VPS.


1

sudo service nginx restart

Final Words
I hope that you have learned a good amount of information about dealing with Parse.com REST,
but as I have said before remember that Your Limitation is just Your Imagination.
And if you have something you want to talk about you can email me, and i will do my best to
answer your email as soon as i get it.
Thanks for being a good reader, and i hope that 2014 will be much more better than 2013 for all
of us.

S-ar putea să vă placă și