Building Web Applications Using Parse Rest API
Building Web Applications Using Parse Rest API
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.
Contents
Foreword . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Introduction . . . . .
What Is Laravel . .
What Is Parse Data?
Now What? . . . .
.
.
.
.
2
2
3
7
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
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
23
23
24
24
41
41
42
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
54
54
56
64
67
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
70
70
72
74
77
78
CONTENTS
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
83
83
83
85
86
87
87
91
91
Learning Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
More about Parse Products . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
More about Laravel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
98
98
98
99
99
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
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.
Introduction
Data Browser
Introduction
Data Filtering
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.
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.
"autoload": {
"classmap": [
"app/commands",
"app/controllers",
"app/models",
"app/database/migrations",
"app/database/seeds",
"app/libraries/parse",
"app/tests/TestCase.php"
],
},
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
10
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.
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.
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.
http://parse.com
11
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.
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.
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
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:
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
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).
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.
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
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.
User Authentication
15
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
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.
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');
}
}
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
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.
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
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.
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
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.
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.
https://parse.com/docs/rest#general
https://parse.com/docs/rest#general
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:
26
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):
@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
{{$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
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.
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:
30
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);
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
<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
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());
}
}
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
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');
}
@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
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
We also need to add a link to the Add action to our list view (admin/posts/list.blade.php)
36
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.
37
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>
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
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.
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.
This is something we have already done in the last chapter, but its always nice to
remember it.
42
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
);
return View::make('admin.comments.list')->with($data);
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}
43
44
45
</div>
@stop
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(
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 :
47
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:
@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
<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
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}
50
51
52
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);
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:
55
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
Un-Publish a post
https://github.com/linuxjuggler/laravel-and-parse-book
56
And if we clicked on the red button to publish/republish a post the result will be
Publish a post
//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;
}
57
return $result->results[0]->title;
}
throw new Exception('No Records found');
}catch(ParseLibraryException $e){
throw new Exception($e->getMessage(), $e->getCode());
}
}
58
</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
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>
$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());
}
}
61
62
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
63
And we need to add the links to our comments view, for simplicity i will only include the html
code for the links
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
https://github.com/linuxjuggler/laravel-and-parse-book
https://github.com/linuxjuggler/laravel-and-parse-book
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){
67
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;
68
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());
}
}
69
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
71
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.
/**
* @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
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
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 :
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
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.
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());
}
}
75
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());
}
}
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:
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
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.
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
79
/**
* @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 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
80
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
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
81
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 :
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
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.
<?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
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
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
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
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
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:
88
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
ed'] :
}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.
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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());
}
}
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.
92
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 :
1
2
3
4
5
6
93
case 'addArray':
$return = array(
"__op" => "Add",
"objects" => $params
);
break;
{"__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.
94
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
{
"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:
1
2
3
4
5
6
7
{
"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
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
1
2
3
4
5
97
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
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.
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/
apt-get
adduser
usermod
apt-get
Now we need to logout and login using the user which we have created
1
2
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
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
;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
From time to time we need to make sure that we have the latest version of composer so we issue
the command:
1
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 .
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
sudo -s
cd /etc/nginx/sites-avaliable
# if you didnt find this directory, try to check `/etc/nginx/conf.d/`
rm default
nano default
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
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\
Final steps:
I like to have a link to my Laravel installation under my home directory so :
1
$ ln -s /var/www/laravel www
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.