Laravel 学习心得

本文详细介绍了Laravel框架的学习心得,包括如何搭建Laravel,重点讲解了Laravel的核心设计模式:控制反转(IoC)容器、服务提供者和服务门面。文中通过实例解释了IoC容器的工作原理,服务提供者的注册与管理,以及门面模式在简化系统交互中的作用。最后,提到了仓储模式的概念,强调了其在领域层与数据访问层之间的解耦作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

搭建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;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值