$ 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
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.
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
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:
A possible solution could be find here:
Single_Responsibility_Principle_example_with_Laravel_Solution1
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.
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:
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...
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.
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
The Interface Segregation principle ([2]) states that a client should never be forced to implement an interface that it doesn’t use.
First for this case we need to convert our code to STUPID code:
TODO
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
https://github.com/acacha/solid_laravel