IMPORTANT: Per accedir als fitxer de subversion: http://acacha.org/svn (sense password). Poc a poc s'aniran migrant els enllaços. Encara però funciona el subversion de la farga però no se sap fins quan... (usuari: prova i la paraula de pas 123456)

Requirements

Step by step configuration

$ cd 
$ mkdir github && cd github 
$ laravel new solid_laravel
$ cd solid_laravel
$ git init

Now create a new repo at github. In this example:

https://github.com/acacha/solid_laravel

Configure github repo with SSH keys

$ git remote add origin [email protected]:acacha/solid_laravel.git
$ git remote -v
$ git pull origin master
$ git status
$ git add .
$ git commit -a -m "First commit" 
$ git push origin master

Now test app. Two possibilites. First using php artisan serve

$ php artisan serve

Use then following URL:

http://localhost:8000/

Second using Homestead (I suppose you have already installed it)

$ homestead edit

Add new site:

    - map: solid_laravel.app
     to: /home/vagrant/Code/solid_laravel/public

And database:

- solidlaravel

Example file:


---
ip: "192.168.10.10"
memory: 2048
cpus: 1

authorize: ~/.ssh/id_dsa.pub

keys:
    - ~/.ssh/id_dsa

folders:
    - map: ~/github
      to: /home/vagrant/Code

sites:
    - map: laravel.app
      to: /home/vagrant/Code/laravel_base_app/public
    - map: laravelnotificationsapp.app
      to: /home/vagrant/Code/laravelnotificationsapp/public
    - map: laravelpamimadre.app
      to: /home/vagrant/Code/laravelpamimadre/public
    - map: solid_laravel.app
      to: /home/vagrant/Code/solid_laravel/public
databases:
    - homestead
    - laravel_base_app
    - laravelnotificationsapp
    - laravelpamimadre
    - solidlaravel

variables:
    - key: APP_ENV
      value: local

# blackfire:
#     - id: foo
#       token: bar

Reprovision homestead:

$ vagrant global-status
id       name    provider   state    directory                                      
------------------------------------------------------------------------------------ 
5e3258a  default virtualbox poweroff /home/sergi/Homestead                          
b916503  default virtualbox running  /home/sergi/.composer/vendor/laravel/homestead 
...

In my case then:

$ vagrant provision b916503

Check all is ok:

$ homestead ssh
$ ls /etc/nginx/sites-available/ | grep solid
solid_laravel.app
$ sudo mysql -psecret
> SHOW DATABASES;
> exit
$ exit

Now again on main machine (not homestead) edit /etc/hosts:

$ editor /etc/hosts

And add line:

192.168.10.10 solid_laravel.app

Then use following URL for testing:

http://solid_laravel.app

Now configure app. First change app name:

$ php artisan app:name SolidLaravel

Configure database, edit environment:

$ cp .env.example .env

Edit .env file and change:

DB_DATABASE=solidlaravel

Create database. With homestead:

$ homestead ssh
$ cd Code/solid_laravel/
$ php artisan migrate

Test you can Login to app, first register user then login at:

http://solid_laravel.app/auth/register

Now is time to commit and push

$ git status
$ git add .
$ git commit -a -m "Laravel configured"
$ git push origin master

SOLID

Single Responsability Principle

Let's see and example of how to implement S of SOLID. First we will create an STUPID example (not using SOLID principles) and then we propose a solution following SOLID principles.

NOTA: What means STUPID? See STUPID article.

STUPID case

First we need to create a new Model and database table for Invoices.
$ php artisan make:model Invoices

Edit file databases/migrations/XXXX_create_invoices_table.php and add line:

 $table->integer('totalAmmount'); 

Example file:

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateInvoicesTable extends Migration {

	/**
	 * Run the migrations.
	 *
	 * @return void
	 */
	public function up()
	{
        Schema::create('invoices', function(Blueprint $table)
        {
            $table->increments('id');
            $table->integer('totalAmmount');
            $table->timestamps();
        });
	}

	/**
	 * Reverse the migrations.
	 *
	 * @return void
	 */
	public function down()
	{
		//
        Schema::drop('invoices');
	}

}

now check Invoices Model file at app/Invoices.php and set fillable fields:

<?php namespace SolidLaravel;

use Illuminate\Database\Eloquent\Model;

class Invoices extends Model {

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['totalAmmount'];

}

Now on Homestead:

$ homestead ssh
$ cd Code/solid_laravel
$ php artisan migrate
Migrated: 2015_04_08_104435_create_invoices_table

An create a first invoice in table with php artisan tinker:

$ php artisan tinker
Psy Shell v0.4.4 (PHP 5.6.6-1+deb.sury.org~utopic+1 — cli) by Justin Hileman
>>> $invoice = new \SolidLaravel\Invoices();
=> <SolidLaravel\Invoices #00000000055478ab0000000045824b3d> {}
>>> $invoice->totalAmmount=4532;
=> 4532
>>> $invoice->save();
=> true
>>>  exit

Check that a new row is added to table invoices.

Now we create a new controller:

$ php artisan make:controller --plain InvoiceController

With following code:

<?php namespace SolidLaravel\Http\Controllers;

use SolidLaravel\Http\Requests;
use SolidLaravel\Http\Controllers\Controller;

use Illuminate\Http\Request;
use SolidLaravel\InvoiceReport;

class InvoiceController extends Controller {

    /**
     * Show invoice
     *
     * @return Response
     */
    public function index()
    {
        $invoice = new InvoiceReport(1);
        return view('invoice')->with('totalAmmount',$invoice->show());
    }

}

Where InvoiceReport class at app/InvoiceReport.php:

<?php namespace SolidLaravel;

class InvoiceReport {


    private $id;

    private $invoice;

    function __construct($id)
    {

        if( ! \Auth::user()) {
            throw new \Exception("Authentication needed to obtain data");
        }

        $this->id = $id;
        $this->invoice = $this->getFromDatabase($id);
    }

    private function getFromDatabase($id)
    {
        return $this->invoice = Invoices::find($id);
    }

    public function show(){
        return "<strong>" . $this->invoice->totalAmmount . " </strong>";
    }
}

Create invoice view on resources/views folder. File invoice.blade.php:

@extends('app')

@section('content')
<div class="container">
	<div class="row">
		<div class="col-md-10 col-md-offset-1">
			<div class="panel panel-default">
				<div class="panel-heading">Invoice</div>

				<div class="panel-body">
					Invoice ammount: {!! $totalAmmount or 'no data available!'  !!}
				</div>
			</div>
		</div>
	</div>
</div>
@endsection

And add route to routes.php:

 <?php
/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the controller to call when that URI is requested.
|
*/

Route::get('/', '[email protected]');

Route::get('home', '[email protected]');

Route::get('invoice', '[email protected]');

Route::controllers([
	'auth' => 'Auth\AuthController',
	'password' => 'Auth\PasswordController',
]);

Edit app.blade.php to add a new menu to Bootstrap navbar:

 ...
 <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
				<ul class="nav navbar-nav">
					<li><a href="{{ url('/') }}">Home</a></li>
                                        <li><a href="{{ url('/invoice') }}">Invoice</a></li>
				</ul>
 ...

Check URL:

http://solid_laravel.app/invoice

Now we have an STUPID case that works what not follows SOLID principles. Time to create a git "milestone"/tag:

$ git status
$ git add .
$ git commit -a -m "STUPID CASE. S of SOLID"
$ git tag -a stupid_case_s
$ git push origin master
$ git push origin stupid_case_s

On following section we will create a SOLID solution

SOLID case

First we propose you to find a solution. I will help you remarking the bad designs at InvoiceReport class:

class InvoiceReport {


    private $id;

    private $invoice;

    function __construct($id)
    {

        if(!\Auth::user()) {
            throw new \Exception("Authentication needed to obtain data");
        }

        $this->id = $id;
        $this->invoice = $this->getFromDatabase($id);
    }

    private function getFromDatabase($id)
    {
        return $this->invoice = Invoices::find($id);
    }

    public function show(){
        return "<strong>" . $this->invoice->totalAmmount . " </strong>";
    }
}

This class has too many responsabilites. You can simplify it with:

  • Proposing a solution (using Laravel) for checking authentication outside InvoiceReport class.
  • Using Dependency injection and Repository Pattern move away method getFromDatabase.
  • Create a specific class in charge of showing invoice as an HTML

A possible solution could be find here:

Single_Responsibility_Principle_example_with_Laravel_Solution1

Open/Closed principle

O STUPID case

STUPID case for Open/Closed principle start at solution of S case with tag:

https://github.com/acacha/solid_laravel/tree/solution_case_s

or:

https://github.com/acacha/solid_laravel/tree/stupid_case_o

So tags stupid_case_o and solution_case_s points to same files.

Now the problem is at InvoiceShow class:

<?php
/**
 * Created by Sergi Tur Badenas @2015
 * http://acacha.org/sergitur
 * http://acacha.org
 * Date: 15/04/15
 * Time: 11:54
 */

namespace SolidLaravel\Output;


class InvoiceShow {

    public function show($invoice)
    {
        return "<strong>" . $invoice->totalAmmount . " </strong>";
    }

}

This class not implements Open/Closed principle:

Software entities should be OPEN for extension but CLOSED for modification

because is not ready for extension behaviour (for example for showing Invoices as TEXT, XML, JSON or other formats) and is also not close for modification for the same reason (whe will change class when we will want to change presentation of invoices). Following:

Separate extensible behaviour behind a interface and flip de dependencies

Create and interface, flip the dependencies and implement a text versión and a HTML version.

O SOLID case

You could find the solution at:

Open Closed Principle_example_with_Laravel_Solution

Please add a tag (solution_case_o) to your github repo when your code is ready with the proposed solution:

$ git status
$ git add --all
$ git commit -a -m "Solution for stupid case O"
$ git push origin master
$ git tag -a solution_case_o
$ git push origin solution_case_o


Liskov Substitution principle

Liskov substitution principle:

objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program

See also design by contract. Or WTF version:

Let q(x) a property provable about objects x of type T then q(y) should be provable for object y of subtype x (transparency 21 and 22 at [1])

Let me show you an example...

L STUPID case

stupid_case_l tag starts at the same point as tag solution_case_o:

https://github.com/acacha/solid_laravel/tree/stupid_case_s

The problem here is with InvoiceRepository:

namespace SolidLaravel\Repositories;

use SolidLaravel\Invoices;

class InvoiceRepository {

    /**
     * Return invoice from the database
     * @param $id - The ID of the requested invoice
     * @return Eloquent object
     */
    public function get($id) {
        return Invoices::find($id);
    }

}

This repository is only ready for an specific persistence layer: Database. What if we want to save our invoices in files or other persistence systems? then apply Liskov Substitution principle.

How?

Using interfaces!

What a surprise!

Try to propose a solution or see next section.

L SOLID case

You could find the solution at:

Liskov Principle_example_with_Laravel_Solution

Please add a tag (solution_case_o) to your github repo when your code is ready with the proposed solution:

$ git status
$ git add --all
$ git commit -a -m "Solution for stupid case L"
$ git push origin master
$ git tag -a solution_case_l
$ git push origin solution_case_l

Interface Segregation principle

The Interface Segregation principle ([2]) states that a client should never be forced to implement an interface that it doesn’t use.

I STUPID case

First for this case we need to convert our code to STUPID code:
TODO

I SOLID case

Dependency Inversion principle

D STUPID case

D SOLID case

You could find the solution at:

Dependency Inversion Principle_example_with_Laravel_Solution

Please add a tag (solution_case_d) to your github repo when your code is ready with the proposed solution:

$ git status
$ git add --all
$ git commit -a -m "Solution for stupid case D"
$ git push origin master
$ git tag -a solution_case_d
$ git push origin solution_case_d

Source Code

https://github.com/acacha/solid_laravel

Students Github and wikis

Alexandre Martínez --> https://github.com/AlexMartCap/solid_laravel -- Exemple Single Responsibility amb Laravel
Paolo Davila --> https://github.com/pdavila13/solid_laravel -- Single Responsibility Principle example with Laravel
Nicolae, Turcan --> SOLID principles example with Laravel: GitHub, Wiki.
Dorian Ungureanu --> https://github.com/cinek19/solid_laravel --> http://acacha.org/mediawiki/Usuari:Dorian_Ungureanu/solid
Liviu Coronciuc --> https://github.com/Kraissus/solid_laravel -- SOLID

See Also

External links