搭建Laravel
工欲善其事必先利其器,我们先搭建laravel,看看它的结构。
Laravel的核心设计模式
控制反转(IoC):由内部依赖转化为外部依赖,调用者不在自身内部创建被调用者的实例,改为在外部创建。一般通过“接口(interface)”去约束外部依赖,让它满足我们的依赖条件。这个过程我们成为依赖注入(DI)。
//普通内部依赖
class robot {
private $abilities = [];
public function __construct($a){
$abilities = $a ;
}
public function doSomething() {
foreach($abilities as $ability){
echo $ability;
}
}
}
//=======分割线===========
//外部依赖实现的接口
interface abilityInterface {
public function work(){}
}
//接口的具体实现
class swimRobot implements abilityInterface {
public funtion work(){
echo 'swim';
}
}
class answerRobot implements abilityInterface {
public funtion work(){
echo 'answer';
}
}
//外部依赖(构造函数)
class robot {
private $ability;
public function __construct(abilityInterface $ability){
$this->ability = $ability;
}
public function doSomething(){
$this->ability->work();
}
}
$robot_swim = new robot(new swimRobot());
$robot_answer = new robot(new answerRobot());
通过控制反转,我们可以根据业务需求去制造不同功能的类,只需要在类外部修改代码就可以,这个类实现我们约定的接口就可以。
laravel的核心是一个IoC容器。所谓的容器就是一个可以动态添加依赖的类。
//简单的“IoC容器“”
class IocContainer {
private $classes_dependencies = [];
public function bind($class, $dependency){
$this->classes_dependencies[$class] = $dependency;
}
public function initialize($class, $parameters = []){
if(isset($this->classes_dependencies[$class])){
return call_user_func_array($this->classes_dependencies[$class], $parameters);
}
}
}
服务提供者(Service Provider): 我们了解了IoC容器的概念之后,知道要声明服务类(一个提供服务的类,我们称为服务类),那必须在IoC容器中注册,在控制器中就可以直接找到该类并使用。我们不能把注册行为分散到每个类中,所以使用服务提供者来帮我们实现这个注册的动作。同时也便于管理。Laravel中使用配置文件去配置服务提供者,然后使用服务提供者去向IoC容器去注册服务类。
<?php
//为了示例声明了服务类的契约。
namespace App\Contracts;
interface MessageContract
{
public function send($m);
}
<?php
//定义一个提供服务的类
namespace App\Services;
use App\Contracts\MessageContract;
class MessageService implements MessageContract {
public function send($m){
echo "send message: ".$m;
}
}
定义完这个消息类之后,我们需要将它注册。需要一个对应的服务提供者。可以使用下面的命令:
php artisan make:provider MessageServiceProvider
<?php
//这是生成的服务提供者。
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Services\MessageService ; //我们刚刚定义的服务类
class MessageServiceProvider extends ServiceProvider{
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* @return void
* @author LaravelAcademy.org
*/
public function register()
{
//使用singleton绑定单例
$this->app->singleton('MyMessage',function(){
return new MessageService();
});
//或者使用bind绑定实例到接口以便依赖注入
/*
$this->app->bind('App\Contracts\MessageContract',function(){
return new MessageService();
});
*/
}
}
可以看到一个服务提供者有两个方法,boot跟register。当我们确定没有使用其他尚未加载的服务类时,我们写在register方法中,并且只绑定事物到服务容器,不做其他事情。当我们使用到了尚未加载的服务类时,我们写在boot中,该方法是在所有服务提供者被注册以后才会被调用。例如:
public function boot()
{
//这里使用了其他未被加载的服务类
view()->composer('view', function () {
//
});
}
如果我们确定当前服务只需要一个实例,我们可以使用 singleton 去注册,我们使用到该服务的时候都是取这一个实例,如果我们需要动态修改(依赖注入)这个服务类,我们使用bind方法。两者取一。底层的框架singleton的实现代码:
...
public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}
...
我们需要去向容器注册我们的服务提供者,追加该类到配置文件config/app.php的providers数组中即可:
'providers' => [
//其他服务提供者
App\Providers\MessageServiceProvider::class,
],
创建一个test控制器:
php artisan make:controller Test/TestController
简单设置路由(app\Http\routes.php):
Route::resource('test','Test\TestController');
<?php
namespace App\Http\Controllers\Test;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
class TestController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//直接使用容器调用
app('MyMessage')->send('testing');
}
...//其他控制器动作
}
访问你本地的路由就可以看到结果(public作为根目录)。我们回顾一下声明服务的过程,首先声明一个提供服务的类(可以选择使用契约),声明一个对应的服务提供者,将它注册到IoC容器,接着注册一下该服务提供者。在IoC容器看来,就是先获取了服务提供者数组,然后注册他们提供的服务类。如果我们需要修改这个服务类,只需要修改它本身就可以,删除就取消注册对应的服务提供者,控制器中的代码不用去关心这个服务类。
门面模式 (Facade):外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。门面模式又称为外观模式,它是一种对象结构型模式。
门面模式的概念看起来挺复杂,我们结合生活中的例子来阐述。
场景:下班回家,你首先要开门,开灯,然后开煮水器,开热水器,开空调。我们抽象你的家为一个系统,那么你(用户)跟这个系统的交互是很繁杂的。门面模式为你提供一个家用机器人,只需要告诉这个机器人我要回家了,它会帮你把上面的动作都做好,你(用户)根本感觉不到家里的电器是怎么开的。在这个例子中,机器人就相当于系统的“门面”或者“外观”。
在 Laravel 应用中,该机制原理由 Facade 类实现。Laravel 自带的门面,以及我们创建的自定义门面,都会继承自 Illuminate\Support\Facades\Facade 基类。门面类只需要实现一个方法:getFacadeAccessor。正是 getFacadeAccessor 方法定义了从容器中解析什么,然后Facade 基类使用魔术方法 __callStatic() 从你的门面中调用解析对象。下面是实例:
创建一个出租屋类:
<?php
namespace App\Facades;
class RentHouse
{
public function comeHome()
{
echo "turn on the lights <br>";
echo "turn on the TV <br>";
echo "shower <br>";
}
}
创建一个出租屋的机器人(门面):
<?php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class RentHouseRobot extends Facade
{
protected static function getFacadeAccessor()
{
//相当于告诉容器 该类是哪个类的门面
return 'RentHouse';
}
}
接下来我们要在服务提供者中绑定Test类到服务容器,新建一个provider:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Facades\RentHouse;
class RentHouseProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
}
/**
* Register the application services.
*
* @return void
*/
public function register()
{
$this->app->singleton('RentHouse',function(){
return new RentHouse();
});
}
}
再然后需要到配置文件config/app.php中注册门面类别名以及RentHouseProvider:
'providers' => [
//其他服务提供者
App\Providers\RentHouseProvider::class,
],
'aliases' => [
...//其他门面类别名映射
'RentHouseRobot' => App\Facades\RentHouseRobot::class,
],
最后控制器再调用:
<?php
namespace App\Http\Controllers\Test;
use Illuminate\Http\Request;
use App\Http\Requests;
//这里需要引入这个门面
use RentHouseRobot;
use App\Http\Controllers\Controller;
class TestController extends Controller
{
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
//app('MyMessage')->send('testing');
RentHouseRobot::comeHome();
}
...//其他方法
}
同上访问你的路由就可以看到结果了。
仓储模式(repository):Repository 是一个独立的层,介于领域层与数据映射层(数据访问层)之间。它的存在让领域层感觉不到数据访问层的存在,它提供一个类似集合的接口提供给领域层进行领域对象的访问。Repository 是仓库管理员,领域层需要什么东西只需告诉仓库管理员,由仓库管理员把东西拿给它,并不需要知道东西实际放在哪。
又是文绉绉的定义。让我们举这样一个例子,有一个有钱人,他有农场,牧场,渔场。这几个仓库的类型都是不一样的,我们(领域层)去操作不同的仓库是痛苦的,所以需要一个仓库管理员,他负责帮我们存储,取出,扔掉,只要他会这些技能他就能做我们的仓库管理员。同时我们定义一个类别的货物成对象,比如渔场的产品,有名字,鱼类,生产日期等,我们跟仓库管理员打交道只需要获取这个对象去处理就可以了,其他的我们不用管。实例如下:
我们先定义我们货物的类,假设货物具有产地,ID,生成日期,过期时间,名称等属性。
<?php
namespace App\Repositories\Farm;
//在仓储模式中,我们定义货物的类只需要属性跟getter,setter。
class Product
{
private $id;
private $name;
private $created;
private $source;
private $expired;
public function setId($id)
{
$this->id = $id;
}
public function getId()
{
return $this->id;
}
public function setName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
public function setCreated($created)
{
$this->created = $created;
}
public function getCreated()
{
return $this->created;
}
public function setSource($source)
{
$this->source = $source;
}
public function getSource()
{
return $this->source;
}
public function setExpired($expired)
{
$this->expired = $expired;
}
public function getExpired()
{
return $this->expired;
}
}
接下来我们定义一个仓库应该具有的方法(数据访问层):
<?php
namespace App\Repositories\Farm;
interface Storage
{
//制定一些规则
public function persist($data);
public function retrieve($id);
public function delete($id);
}
通过实现上面的接口,我们可以定义一个或几个仓库管理员。在实际应用中,我们或许会对货物划分不同的规则,但是货物类本身是不会改变的,规则就交给仓库管理员接口去管理。
<?php
namespace App\Repositories\Farm;
use App\Repositories\Farm\Storage;
//一个仓库管理员的实现
class FarmStorage implements Storage {
private $data;
private $lastId;
public function __construct()
{
$this->data = array();
$this->lastId = 0;
}
public function persist($data)
{
$this->data[++$this->lastId] = $data;
return $this->lastId;
}
public function retrieve($id)
{
return isset($this->data[$id]) ? $this->data[$id] : null;
}
public function delete($id)
{
if (!isset($this->data[$id])) {
return false;
}
$this->data[$id] = null;
unset($this->data[$id]);
return true;
}
}