Skip to content

Instantly share code, notes, and snippets.

@cangelis
Last active December 16, 2015 18:29
Show Gist options
  • Save cangelis/5477977 to your computer and use it in GitHub Desktop.
Save cangelis/5477977 to your computer and use it in GitHub Desktop.

Controllers

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.

Controller routing

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

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

Eloquent class is an ORM class provided by Laravel.

Mapping a DB Table to a Class

<?php
class Menu extends Eloquent {

     public static $table = 'menu';

}
?>

Menu class is responsible for all the DB operations.

Retrieving Models

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.

Fluent Query Builder

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.

Some examples

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(); ?>

Relationships in Eloquent

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

One-To-One

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.

One-To-Many

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;
    }
?>

Many-To-Many

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 ?>

Inserting models

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();
?>

Converting Models To Arrays

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...
)

Deleting Models

Because Eloquent inherits all the features and methods of Fluent queries, deleting models is a snap:

<?php
Menu::find(1)->delete();
?>

Views

In the application there is no view, JSON inputs and outputs are handled by Laravel Libraries.

Input Class

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

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

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();
?>

Switching the language

Language switching is provided by Config class:

<?php
    Config::set('application.language','en');
?>

Validations

Validations are provided by SimpleValidator class (https://github.com/cangelis/simple-validator)

Defining Rules

<?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.

Custom Rules

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.

Generating Error Messages

getErrors() method returns an array of error messages

Custom 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.

Naming inputs

: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)

Example from Menu_Controller

<?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

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.

Installing migrations

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

Creating Migrations

php artisan migrate:make create_menu_table

and the corresponding php file is generated under application/migrations

Running migrations

Running all outstanding migrations in application and bundles:

php artisan migrate

Rolling Back

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.

Schema Builder

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 Tests

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.

How to run test cases

To run the tests, Laravel's Artisan command-line utility is used:

Running the application's tests via the Artisan CLI:

php artisan test

Calling Controllers From Tests

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);

An example test case

<?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/

Handling Notifications

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";

}

?>

Usage with an example:

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment