Controllers are classes that are responsible for accepting user input and managing interactions between models, libraries, and views. Typically, they will ask a model for data, and then return a view that presents that data to the user. The usage of controllers is the most common method of implementing application logic in modern web-development.
All routes (links, urls) are explicitly defined in Laravel. This means that controller methods that have not been exposed through route registration cannot be accessed. It's possible to automatically expose all methods within a controller using controller route registration. In this application, automatic controller routing is used. Usage:
file: application/routes.php
<?php
Route::controller(Controller::detect());
?>
this peace of code registers all the methods of the controllers in the application.
An example Controller (Order Controller) from the application:
<?php
class Order_Controller extends Base_Controller {
public $restful = true;
public function post_add() {}
public function post_delete() {}
public function post_listByTable() {}
}
The url of the application: http://(domain)/(controller_name)/(controller_method_name)/(controller_method_param)
Accessing add page:
http://restaurant.dev/order/add
Accessing delete page:
http://restaurant.dev/order/delete
Note: Every controller method are prefixed with post_
that indicates that method is only called when the POST
request comes to the server. In order to apply this operation, the class
should be defined as RESTful Controller. In the RESTful Controllers, Controller methods are prefixed with the HTTP verb they should respond to. In order to mark a Controller as a RESTful Controller $restful
property should be defined as true
.
public $restful = true
Models are the heart of the application. The application logic (controllers / routes) and views (html) are just the mediums with which users interact with the models. The most typical type of logic contained within a model is Business Logic.
Some examples of functionality that would exist within a model are:
- Database Interactions
- File I/O
- Interactions with Web Services
Note: Model files live in application/models directory.
Eloquent
class is an ORM class provided by Laravel.
<?php
class Menu extends Eloquent {
public static $table = 'menu';
}
?>
Menu
class is responsible for all the DB operations.
The most basic way to retrieve an Eloquent
model is the static find
method. This method will return a single model by primary key with properties corresponding to each column on the table:
<?php
// 1 is id of the menu
$menu = Menu::find(1);
echo $menu->name;
echo $menu->description;
?>
The find
method will execute a query that looks something like this:
SELECT * FROM menu WHERE id = 1
Every method that is available through the fluent query builder is available in Eloquent
.
The Fluent Query Builder is Laravel's powerful fluent interface for building SQL queries and working with the database. All queries use prepared statements and are protected against SQL injection.
Retrieving the value of a single column from the database:
<?php $menu_name = DB::table('menu')->where('id', '=', 1)->only('name'); ?>
Only selecting certain columns from the database:
<?php $menu = DB::table('menu')->get(array('id', 'description as desc','name')); ?>
Selecting distinct results from the database:
<?php $menu = DB::table('menu')->distinct()->get(); ?>
Eloquent
makes defining relationships and retrieving related models simple and intuitive. Laravel supports three types of relationships:
- One-To-One
- One-To-Many
- Many-To-Many
A one-to-one relationship is the most basic form of relationship. For example, let's pretend a user has one phone. Simply describe this relationship to Eloquent:
<?php
class User extends Eloquent {
public function phone()
{
return $this->has_one('Phone');
}
}
?>
Notice that the name of the related model is passed to the has_one
method. Now phone of a user can be retrieved through the phone
method:
<?php $phone = User::find(1)->phone()->first(); ?>
Let's examine the SQL performed by this statement. Two queries will be performed: one to retrieve the user and one to retrieve the user's phone:
SELECT * FROM users WHERE id = 1
SELECT * FROM phones WHERE user_id = 1
Eloquent assumes the foreign key of the relationship will be user_id. Most foreign keys will follow this model_id convention; Defining a foreign key explicitly:
<?php
return $this->has_one('Phone', 'my_foreign_key');
?>
Retriving phone's user
<?php
class Phone extends Eloquent {
public function user()
{
return $this->belongs_to('User');
}
}
?>
Phones belong to users. When using the belongs_to
method, the name of the relationship method should correspond to the foreign key (sans the _id). Since the foreign key is user_id
, the relationship method should be named user.
In the application a menucategory has many menus
<?php
class MenuCategory extends Eloquent {
public static $table = "menu_category";
public static $timestamps = false;
public function menus() {
return $this->has_many('menu','menu_category_id');
}
}
?>
Note: When $timestamps
is true
created_at and updated_at columns are automatically generated and filled in the database.
Accessing the menus of a menu category:
<?php
public function post_listByCategory() {
// $inputs variable is predefined
$menus = MenuCategory::find($inputs['category_id'])->menus;
$result_menu = null;
foreach ($menus as $menu) {
$result_menu[] = $menu->to_array();
}
return $result_menu;
}
?>
In the application a menu has many option categories (eg: Drink Selection, Extra size selection etc.). This option categories also has many menus. An option category can be assigned to many menus.
Database tables:
option_category:
- id
- name
menu:
- id
- menu_category_id
- kitchen_id
- description
- price
- name
- image
optionlist:
- id
- menu_id
- optioncategory_id
Tables contain many records and are consequently plural. Pivot tables used in has_many_and_belongs_to
relationships are named by combining the singular names of the two related models arranged alphabetically and concatenating them with an underscore.
<?php
class Menu extends Eloquent {
public static $table = 'menu';
public static $timestamps = false;
public function option_categories() {
return $this->has_many_and_belongs_to('OptionCategory', 'optionlist');
}
}
?>
Retriving the option categories of a menu:
<?php $option_categories = Menu::find(1)->option_categories ?>
Retriving the menus of an option category:
<?php $menus = OptionCategory::find(1)->menus ?>
Eloquent
's save()
method provides easy way to insert models to the database. An example:
<?php
$menu = new Menu();
$menu->name = "Pizza";
$menu->description = "A great pizza!!";
$menu->category_id = 4;
$menu->kitchen_id = 2;
$menu->price = 0.99;
$menu->save();
?>
to_array()
method simply converts the models into arrays. An example:
<?php
$menu = Menu::find(1);
var_dump($menu->to_array());
?>
This code generates this output:
array(
'id' => 1,
'name' => 'Pizza',
'description' => 'A great pizza!!!',
// and so on...
)
Because Eloquent inherits all the features and methods of Fluent queries, deleting models is a snap:
<?php
Menu::find(1)->delete();
?>
In the application there is no view, JSON inputs and outputs are handled by Laravel Libraries.
The Input
class handles input that comes into the application via GET, POST, PUT, or DELETE requests. Here is the example of how it handles JSON inputs:
$data = Input::json();
json()
method returns an array that is the converted version of JSON input. an Example json input:
{"name": "Can","surname": "Gelis"}
this JSON data string converted to this php array;
array(
'name' => 'Can',
'surname' => 'Gelis'
);
Response
class is responsible of creating HTTP responses. json()
method simply generates a json output. An example:
// data that will be generated to json string
$data = array(
'name' => 'Can',
'surname' => 'Gelis'
);
// $data variable is converted to json string
return Response::json($data);
The output:
{"name": "Can", "surname": "Gelis"}
An example from Menu_Controller:
<?php
class Menu_Controller extends Base_Controller {
public $restful = true;
/**
* ListByCategory method
* list the menus which belong to a category
* Inputs required:
* category_id
* Result:
* array(
* 'success' => true|false
* 'errors' => Array('error1','error2'...) (in case of unsuccessful response),
* 'menus' => Array(MenuInfo1, MenuInfo2...) (in case of successful response)
* );
*/
public function post_listByCategory() {
$inputs = Input::json();
// validations here
if ($validation->isSuccess()) {
$menus = MenuCategory::find($inputs['category_id'])->menus;
$result_menu = null;
foreach ($menus as $menu) {
$result_menu[] = $menu->to_array();
}
$response = array(
'success' => true,
'menus' => $result_menu
);
} else {
$response = array(
'success' => false,
'errors' => $validation->getErrors()
);
}
return Response::json($response);
}
}
?>
Localization is the process of translating the application into different languages. The Lang
class provides a simple mechanism to help the application organize and retrieve the text of the multilingual application.
All of the language files for the application live under the application/language directory. Within the application/language directory, a directory for each language the application should be created. So, for example, if the application speaks English and Spanish, en and sp directories under the language directory should be created.
Retrieving a language line:
<?php
// Retrieves the welcome text in general.php
echo Lang::line('general.welcome')->get();
?>
Language switching is provided by Config
class:
<?php
Config::set('application.language','en');
?>
Validations are provided by SimpleValidator
class (https://github.com/cangelis/simple-validator)
<?php
$rules = array(
'table_id' => array(
'required',
'integer'
)
);
?>
From now on, table_id
field is required, and expected to be an integer. When the table_id field is non-integer or not given the validation will not be success.
Running the validation:
listByTable method from Menu_Controller
<?php
public function post_listByTable() {
$inputs = Input::json();
$rules = array(
'table_id' => array(
'required',
'integer'
)
);
$validator = SimpleValidator::validate($inputs, $rules);
if ($validator->isSuccess()) {
// successful validation
} else {
// failed validation
}
return Response::json($response);
}
?>
Inputs should be given as an array
to the validate()
method.
SimpleValidator
provides both custom and default rules (eg: required, alpha, integer). Custom rules are implemented using lambda functions.
<?php
$rules = array(
'table_id' => array(
'required',
'integer',
'table_exists' => function($id) {
$table = ResTable::find($id);
if ($table == null)
return false;
return true;
}
)
);
?>
Lambda function should return true
if the validation is successful, otherwise it should return false
.
getErrors()
method returns an array
of error messages
By default error messages are generated in the language of the application. Custom error messages provide localization for the error messages. Custom error messages are defined in a seperate file that is in the specific language directory and then getErrors()
method is called with the language name wanted.
application/language/en/simple-validator.php
return array(
"required" => ":attribute field is required",
'table_exists' => 'Table does not exist'
);
application/language/tr/simple-validator.php
return array(
"required" => ":attribute alanı zorunludur",
'table_exists' => 'Böyle bir masa yok'
);
getErrors:
<?php
$validation_result->getErrors('tr');
?>
Note: All rules have to have at least one error message.
:attribute
string in an error message represents the input name (eg: name, table_id) in an error message. To override this inputs should be named:
$naming = array(
'table_id' => Lang::line("general.table_id")
);
$validation_result = SimpleValidator::validate($inputs, $rules, $naming);
Output Sample:
- Table Id field is required (instead of table_id is required)
- Table Id must be an integer (instead of table_id must be an integer)
<?php
/**
* Add method
* Inputs required:
* name, category_id, kitchen_id, price, description, image(as a base64 string)
* Result:
* array(
* 'success' => true|false
* 'errors' => Array('error1','error2'...) (in case of unsuccessful creation),
* 'id' => id number of added menu (in case of successful creation)
* );
*/
public function post_add() {
$input = Input::json();
$rules = array(
'name' => array('required'),
'category_id' => array(
'required',
'integer',
'category_exists' => function ($id) {
$category = MenuCategory::find($id);
if ($category == null)
return false;
return true;
}
),
'kitchen_id' => array(
'required',
'integer',
'kitchen_exists' => function ($id) {
$kitchen = Kitchen::find($id);
if ($kitchen == null)
return false;
return true;
}
),
'price' => array(
'required',
'float'
),
'description' => array('required'),
'image' => array(
'required',
'is_valid_base64' => function($data) {
if (base64_encode(base64_decode($data)) == $data)
return true;
return false;
}
)
);
$naming = array(
'name' => Lang::line("general.name"),
'price' => Lang::line("general.price"),
'kitchen_id' => Lang::line("general.kitchen_id"),
'category_id' => Lang::line("general.cat_id"),
'description' => Lang::line("general.description"),
'image' => Lang::line('general.image')
);
$validation = SimpleValidator::validate($input, $rules, $naming);
if ($validation->isSuccess()) {
$response = array(
'success' => true
);
$menu = new Menu;
$menu->name = $input['name'];
$menu->price = $input['price'];
$menu->kitchen_id = $input['kitchen_id'];
$menu->menu_category_id = $input['category_id'];
$menu->description = $input['description'];
$menu->image = $input['image'];
$result = $menu->save();
if ($result) {
$response = array(
'success' => true,
'id' => $menu->id
);
} else {
$response = array(
'success' => false,
'errors' => array(Lang::line("general.unexpected"))
);
}
} else {
$response = array(
'success' => false,
'errors' => $validation->getErrors()
);
}
return Response::json($response);
}
?>
Migrations can be assumed as a type of version control for the database. Let's say you are working on a team, and you all have local databases for development. Ibrahim makes a change to the database and checks in his code that uses the new column. You pull in the code, and your application breaks because you don't have the new column. What do you do? Migrations are the answer.
Before running migrations, database should be configured. Laravel uses a special table to keep track of which migrations have already run. To create this table, this command is used on the CLI:
php artisan migrate:install
php artisan migrate:make create_menu_table
and the corresponding php file is generated under application/migrations
Running all outstanding migrations in application and bundles:
php artisan migrate
When roll back is called, Laravel rolls back the entire migration "operation". So, if the last migration command ran 122 migrations, all 122 migrations would be rolled back.
Rolling back the last migration operation:
php artisan migrate:rollback
Roll back all migrations that have ever run:
php artisan migrate:reset
Roll back everything and run all migrations again:
php artisan migrate:rebuild
An example migration file from the application:
<?php
class Create_Orders_Table {
/**
* Make changes to the database.
*
* @return void
*/
public function up()
{
Schema::create('order', function($table){
$table->increments('id');
$table->integer('table_id');
$table->integer('menu_id');
$table->integer('waiter_id');
$table->string('payment_method');
$table->string('status');
$table->timestamps();
});
}
/**
* Revert the changes to the database.
*
* @return void
*/
public function down()
{
Schema::drop('order');
}
}
?>
In this example database tables are created through Schema
Class.
The Schema Builder provides methods for creating and modifying your database tables. Using a fluent syntax, tables can be created without using any vendor specific SQL.
The Schema class is used to create and modify tables.
Creating a simple database table:
<?php
Schema::create('users', function($table)
{
$table->increments('id');
});
?>
Dropping a table:
<?php
Schema::drop('users');
?>
for more information (http://laravel.com/docs/database/schema)
Unit Testing allows to test the code and verify that it is working correctly. Laravel provides beautiful integration with the popular PHPUnit testing library, making it easy to get started writing the tests.
All of the application's tests live in the application/tests directory. Take special note of the .test.php file suffix. This tells Laravel that it should include this class as a test case when running the test. Any files in the test directory that are not named with this suffix will not be considered a test case.
To run the tests, Laravel's Artisan command-line utility is used:
Running the application's tests via the Artisan CLI:
php artisan test
Here's an example of how to call the controllers from the tests:
Calling a controller from a test:
$response = Controller::call('home@index', $parameters);
<?php
class CategoryAddTest extends PHPUnit_Framework_TestCase {
public function setUp() {
Request::setMethod('POST');
}
public function tearDown() {
}
public function testEmptyName() {
Input::$json = array();
$response_json = Controller::call('menucategory@add');
$response = json_decode($response_json->content);
$this->assertEquals($response->success, false);
}
public function testNullInput() {
Input::$json = null;
$response_json = Controller::call('menucategory@add');
$response = json_decode($response_json->content);
$this->assertEquals($response->success, false);
}
public function testValidName() {
Input::$json = array(
'name' => 'Burgerler'
);
$response_json = Controller::call('menucategory@add');
$response = json_decode($response_json->content);
$test_category = MenuCategory::find($response->id);
$this->assertEquals($response->success, true);
$this->assertNotNull($test_category);
$test_category->delete();
}
}
?>
for more information: http://www.phpunit.de/manual/3.7/en/
In HTTP, the connection between the server and the client is closed after the request is handled by the server. The term Comet Programming is used for handling this issue. Comet Programming is a programming technic that allows client to wait until a change happens in the server.
In the application, Notifier
class handles the changes between the client and the server. The status of a change is stored in the memory. When a change occurs, the data in the memory is changed by the notifier. Memcached
is used in order to store the data in the memory.
notify()
method in application/models/Notifier.php:
<?php
public function notify($message) {
$this->memcache->set('hasnew-' . $this->type . '-' . $this->id, array(
'hasnew' => true,
'message' => $message
));
}
?>
The client that waits for a new notification by checking for whether there is a new notification or not. hasnew
index in the array
in the cache represents whether there is a change or not.
hasNew()
method:
<?php
private function hasNew() {
$data = $this->memcache->get('hasnew-' . $this->type . '-' . $this->id);
if ($data['hasnew'] == true) {
return true;
}
return false;
}
?>
wait()
method blocks the code until a new change occurs in the memory.
<?php
public function wait() {
while (!$this->hasNew()) {
// sleep 1 sec
usleep(1000000);
}
return $this->memcache->get('hasnew-' . $this->type . '-' . $this->id);
}
?>
Notifiers need to extend the Notifier
class. An example:
<?php
class KitchenNotifier extends Notifier {
public $type = "kitchen";
const MESSAGE_NEW_ORDER = "neworder";
}
?>
When a new order is placed, the kitchen should be notified. In the order controller when the add()
method is called, the notifier is invoked.
<?php
$menu = Menu::find($order->menu_id);
$notifier = new KitchenNotifier($menu->kitchen_id);
$notifier->notify(KitchenNotifier::MESSAGE_NEW_ORDER);
?>
When a new notification is received, the wait()
method stops and the execution continues. Example from waitForNotification()
in Kitchen Controller:
<?php
public function post_waitForNotification() {
// validations and other stuff
$notifier = new KitchenNotifier($inputs['kitchen_id']);
// block the code
$data = $notifier->wait();
$response = array(
'success' => true,
'message' => $data['message']
);
return Response::json($response);
}
?>
After the notification is received by the client, the connection between the server and the client is closed. The client should create a new request to wait for a new notification after received one.