Laravel - 服务设计模式

服务设计模式-start

  • 服务模式
    • 在 app 目录下建立一个 Repositories 目录,在 Repositories目录下,创建三个子目录:Contracts、Eloquent 和Exceptions。
      Contracts: 接口目录
      Eloquent: 用于存放实现Repository接口的抽象类和具体类。
      Exceptions:目录用于存放异常处理类。
      file
      这种模式是参考Laravel的服务容器和服务提供者,Laravel 服务容器是一个用于管理类依赖和执行依赖注入的强大工具。依赖注入听上去很花哨,其实质是通过构造函数或者某些情况下通过 set 方法将类依赖注入到类中。服务提供者是所有Laravel应用启动的中心,你自己的应用以及所有Laravel的核心服务都是通过服务提供者启动。通过服务容器创建服务(service),然后在服务提供者中(provider)注册。
    • 实现步骤:
      1. 定义接口
        在app\Repositories\Contracts 文件夹下创建 UserInterface.php
        <?php
        namespace App\Repositories\Contracts;
        interface UserInterface{
                public function findBy($id);
        }
      2. 实现接口类
        在app\Repositories\Eloquent 文件夹下创建UserServiceRepository.php
        <?php
            namespace App\Repositories\Eloquent;
            use App\Repositories\Contracts\UserInterface;
            use App\User;
            //服务模式
            class UserServiceRepository implements UserInterface{
                    public function findBy($id)
                    {
                            return User::find($id);
                    }
            }
      3. 注册服务
        创建provider或在系统的provider中注册(接口与实现类的绑定):
        php artisan make:provider RepositoryServiceProvider
        打开 app\Providers\RepositoryServiceProvider.php
        public function register()
        {    
        //接口与实现类的两种绑定模式
                //单例模式(个人推荐)
             $this->app->singleton('App\Repositories\Contracts\UserInterface',function ($app){
                        return new \App\Repositories\Eloquent\UserServiceRepository();
                });
        //绑定
        //        $this->app->bind('App\Repositories\Contracts\UserInterface','App\Repositories\Eloquent\UserServiceRepository');
        }
      4. 添加到配置文件中:
        注:如果不是自己创建provider,而是直接在AppServiceProvider.php中绑定的话,则不需要操作本步骤,因为系统已经引入了那4个Provider 。
        具体步骤:
        打开 config/app.php, providers 数组中添加下面一行:
        app\Providers\RepositoryServiceProvider::class,
      5. 在控制器中调用
        首先要在控制器中引入:
        use App\Repositories\Contracts\UserInterface;
        然后再进行依赖注入:
        private $user;   //定义一个私有的变量
        public function __construct(UserInterface $user){
            $this->user = $user;
        }
        public function index(){
            dd($this->user->findBy(1)->toArray());
        }
  • 门面模式

    • 门面为应用的服务容器中的绑定类提供了一个“静态”接口。Laravel 内置了很多门面,你可能在不知道的情况下正在使用它们。Laravel 的门面作为服务容器中的底层类的“静态代理”,相比于传统静态方法,在维护时能够提供更加易于测试、更加灵活的、简明且富有表现力的语法。门面就是一个提供访问容器中对象的类。该机制原理由 Facade 类实现,Laravel 自带的门面,以及创建的自定义门面,都会继承自 Illuminate\Support\Facades\Facade 基类。门面类只需要实现一个方法:getFacadeAccessor。正是 getFacadeAccessor 方法定义了从容器中解析什么,然后 Facade 基类使用魔术方法 __callStatic() 从你的门面中调用解析对象。
    • 实现步骤:

      1. 定义接口(建议):
        虽然门面模式中可以不用定义接口,个人建议还是定义接口。在以后的项目功能升级并兼容老功能的时候非常有用,增加了开发时间,却减少了后期维护或升级功能的难度。
        具体步骤:
        在 app\Repositories\Contracts 文件夹下新建UserInterface.php
        <?php
        namespace App\Repositories\Contracts;
        interface UserInterface{
        public function findBy($id);
        }
      2. 接口实现类
        具体步骤:
        在 app\Repositories\Eloquent 文件夹下新建 UserFacadeRepository.php
        <?php
        namespace App\Repositories\Eloquent;
        use App\Repositories\Contracts\UserInterface;
        use App\User;
        class UserFacadeRepository implements UserInterface{
                //根据id查找用户信息
                public function findBy($id){
                        return User::find($id);
                }
        }
      3. 注册服务
        执行如下命令,新建一个RepositoryServiceProvider:
        php artisan make:provider RepositoryServiceProvider
        在register 方法中添加如下代码来注册服务:
        $this->app->singleton('UserFacadeRepository',function ($app){
                return new \App\Repositories\Eloquent\UserFacadeRepository();
        });
      4. 定义Facade(门面)
        在 app目录下创建一个 Facades 目录, Facades目录下新建一个 UserFacade.php

        <?php
        namespace App\Facades;
        use Illuminate\Support\Facades\Facade;
        
        class UserFacade extends Facade{
                protected static function getFacadeAccessor(){
                        return 'UserFacadeRepository';
                }
        }
      5. 在app\config\app.php中的aliases 数组中添加下面代码:
        //自己新建的门面
        'UserRepository' => App\Facades\UserFacade::class,
      6. 使用门面
        首先在方法中引入门面:
        use UserRepository;
        使用门面:
        public function index(){
                dd(UserRepository::findBy(1)->toArray());
        }
  • 仓库(Repository)模式

    • Repository 是衔接数据映射层和领域层之间的一个纽带,作用相当于一个在内存中的域对象集合。客户端对象把查询的一些实体进行组合,并把它们提交给 Repository。对象能够从 Repository 中移除或者添加。Repository 是MVC中衔接Controller和Model之间的一个纽带。从概念上讲,Repository 是把将数据给封装后的集合并提供给Controller的操作。
    • 实现步骤:

      1. 定义接口
        在app\Repositories\Contracts 目录下新建一个UserInterface.php
            <?php
            namespace App\Repositories\Contracts;
            interface UserInterface{
                    public function findBy($id);
            }
      2. 定义一个基本的抽象类:
        在 app\Repositories\Eloquent目录下新建一个Repository.php

            <?php
            namespace App\Repositories\Eloquent;
        
            use App\Repositories\Contracts\UserInterface;
            use Illuminate\Database\Eloquent\Model;
            use Illuminate\Container\Container as App;
            abstract class Repository implements UserInterface{
                    protected $app;     //App容器
                    protected $model;   //操作model
        
                    public function __construct(App $app){
                            $this->app = $app;
                            $this->makeModel();
                    }
        
                    abstract function model();
        
                    public function findBy($id){
                            return $this->model->find($id);
                    }
        
                    public function makeModel(){
                            $model = $this->app->make($this->model());
                            /*是否是Model实例*/
                            if (!$model instanceof Model){
                                    throw new RepositoryException("Class {$this->model()} must be an instance of Illuminate\\Database\\Eloquent\\Model");
                            }
                            $this->model = $model;
                    }
            }
      3. 创建UserRepository.php 并继承抽象类
        在 app\Repositories\Eloquent 目录下新建一个UserRepository.php
            <?php
            namespace App\Repositories\Eloquent;
            use App\Repositories\Eloquent\Repository;
            use App\User;
            class UserRepository extends Repository{
                    public function model(){
                        return User::class;
                    }
                    public function findBy($id){
                        return $this->model->where('id',$id)->first()->toArray();
                    }
            }
      4. 控制器依赖注入
        首先需要引入文件,然后在HomeController类里书写代码:
                    <?php
                    namespace App\Http\Controllers;
                    use App\Http\Requests;
                    use Illuminate\Http\Request;
                    use App\Repositories\Contracts\UserInterface;
                    use UserRepository;
                    use App\Repositories\Eloquent\UserRepository as UserRepo;
                    class HomeController extends Controller{
                            private $user;   //定义一个私有的变量
                            private $userRepo;
                            public function __construct(UserInterface $user,UserRepo $userRepo){
                                    $this->user = $user;
                                    $this->userRepo = $userRepo;
                            }
                            public function index(){
                                    dd($this->userRepo->findBy(2));
                                    return view('home');
                            }
                    }
  • 总结:
  • 这种将数据访问从业务逻辑中分离出来的模式有很多好处:
    1. 集中数据访问逻辑使代码易于维护
    2. 业务和数据访问逻辑完全分离
    3. 减少重复代码
    4. 使程序出错的几率降低
  • 一个控制器调用多个Repository数据的时候,要么都在相应的Repository中进行注入或引用,要么在控制其中添加。避免交叉引用乱象!

服务设计模式-end

本作品采用《CC 协议》,转载必须注明作者和本文链接
老郭博客:laughing: 个人博客地址:www.phpsix.com
本帖由系统于 5年前 自动加精
PHPSIX
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 10

并不推荐刚开始学习就使用 Repository 模式,很容易把自己的代码搞得一团糟

6年前 评论
PHPSIX

@Seaony 我感觉只要一开始注意一点,多去了解一下代码复用性,然后做注释,就可以避免代码乱得问题。

6年前 评论
lol173

代码写得不好我觉得是看个人,开发项目约定好规范,大家遵守就很好了

6年前 评论
PHPSIX

@lol173 对,这话说的没毛病。个人责任心和重视度上去了,团队凝聚力上去了,项目进度会很快,并且代码质量也会提高!

6年前 评论

这个文章对于老手来说没太多意义来,他们都有了自己的风格,或者说一个团队的风格,对于新手来说可能不容易理解很多的名词,反而适合像我这样马上要走出新手的人,步入规范书写路的重大帮助。让我找了很久啊。非常感谢,看了很多别人写的风格和命名,反而这个理解起来和容易,不论命名还是逻辑,都蛮清晰的。非常感谢?!

5年前 评论

还有一个问题,我看到很多老手都加了Service层,命名是这样的“UserService”,里面调用了一些接口定义的函数,还使用分页功能,还有格式化信息等等功能,所以我想问一下楼主怎么看这个问题,这个做法有什么优点吗,有时候我觉得Controller层其实也能做这些工作。还有些人加了Entity这一层,里面是一些公共的参数,如:public $user_id; public $id; public $mobile;等等,然后接口和Repository层都引入了Entity层的参数作为形参,这个是否有必要这么做。

5年前 评论

这样做很容易误导初学者,我刚才看了,吓我一大跳!现在看了几集别人的教程才发现,其实很简单! 门脸,注册,工厂模式,代码复用!好吧,就这些了

4年前 评论
Allen_Chan 2年前

最后的接口如何实例的很惊讶!

3年前 评论

想问下 基于服务要如何进行测试呢?测试人员如何参与进来呢? 总不能程序员进行交叉测试吧?

3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!