zoukankan      html  css  js  c++  java
  • Laravel驱动管理类Manager的分析和使用

    Laravel驱动管理类Manager的分析和使用

    第一部分 概念说明

    第二部分 IlluminateSupportManager源码

    第三部分 Manager类的使用

    第一部分:概念解释

    结合实际解释一下,啥是驱动:当我点了份外卖,那么外卖小哥无论如何都要讲外卖送到我的手中,我不会关心小哥走的是丝绸之路,还是强者之路,更不会关心他是骑着飞机、坦克还是大炮送来的。我只要我的外卖到我的手中。

    归纳一下,我点外卖要就要得到外卖,这就是契约,这就是接口规定的功能。

    小哥走什么路线,什么交通工具是他自己的实现,也就是各种驱动。

    是不是和laravel的契约和服务提供者概念很相似呢?

    只不过今天要讲解的Manager更加强调管理各个驱动,提前将所有的驱动全部注册好,在使用的时候直接解析或者切换。

    说到这道友们应该理解了,Manager能做的事情Container和Provider能够做的更好。

    那么Manager和Container、Provider的区别在哪呢?这里我只说明我的理解,Container和Provider更加的偏向框架层级,虽然也可以非侵入式的扩展和修改,但是相对Manager稍加麻烦,我将Manager看做一个小型的Container,里面包含了我要实现某个功能的各种驱动(实现),Manager更加的偏向业务逻辑层。

    当要频繁切换一个功能实现的时候(他更像一个频繁更换内容,但是说明书不换的组件,俗话说的换汤不换药),我可能会选择Manager(比如发送短信,可以使用阿里大于,京东万象,飞鸽等等),因为他更加轻量。当要实现一个系统级的服务的时候,我会选择Container和Provider,比如上一篇中的日志服务。

    第二部分:源码说明
    # 直接上代码 挺简单的一个类,基本可以见名知意。未展示属性
    <?php
    
    namespace IlluminateSupport;
    
    use Closure;
    use IlluminateContractsContainerContainer;
    use InvalidArgumentException;
    
    // 值得注意的是 Manager是一个抽象类,一定要实现了其中的抽象方法getDefaultDriver才能实例化
    // 我们观察构造方法中的参数,你会不会想到在Provider中挂载Manager是一个好方法呢?
    public function __construct(Container $container)
    {
        $this->app = $container;
        $this->container = $container;
        $this->config = $container->make('config');
    }
    
    // 此类是一个抽象类 这个方法用来返回默认的驱动名
    abstract public function getDefaultDriver();
    
    public function driver($driver = null)
    {
        $driver = $driver ?: $this->getDefaultDriver();
    
        if (is_null($driver)) {
            throw new InvalidArgumentException(sprintf(
                // 此处的static显然是实际调用该方法的类
                'Unable to resolve NULL driver for [%s].', static::class
            ));
        }
    	
        // 有点类似单例的写法
        // 如果要解析的驱动已经解析过 那么直接返回
        // 如果没有解析过 那么解析 并挂载到类中
        if (! isset($this->drivers[$driver])) {
            $this->drivers[$driver] = $this->createDriver($driver);
        }
    
        return $this->drivers[$driver];
    }
    
    // 创建指定驱动
    // 要注意此类中传递的$driver就是指定驱动的名字
    protected function createDriver($driver)
    {
        // First, we will determine if a custom driver creator exists for the given driver and
        // if it does not we will check for a creator method for the driver. Custom creator
        // callbacks allow developers to build their own "drivers" easily using Closures.
        # 官方注释已经非常清晰了
        # 如果要解析的驱动,是由我们手动通过键值对注册进来的 那么就调用对应的闭包
        # 否则触发魔术方法__call
        # 显然Manager本类中并不存在额外的方法,所以魔术方法调用的方法,也要我们在子类中实现
        # 以上就是两种从Manager中返回驱动的方式了
        if (isset($this->customCreators[$driver])) {
            return $this->callCustomCreator($driver);
        } else {
            $method = 'create'.Str::studly($driver).'Driver';
    
            if (method_exists($this, $method)) {
                return $this->$method();
            }
        }
    
        throw new InvalidArgumentException("Driver [$driver] not supported.");
    }
    
    // 上面说的通过此方法调用我们注册进来的闭包 从而返回驱动
    protected function callCustomCreator($driver)
    {
        return $this->customCreators[$driver]($this->container);
    }
    
    // 这个就是注册闭包进来
    // 你当然可以在业务逻辑中、甚至是指定的中间件中扩展你的Manager类
    // 但我更喜欢在ServiceProvider的boot方法中进行扩展
    public function extend($driver, Closure $callback)
    {
        $this->customCreators[$driver] = $callback;
    
        return $this;
    }
    
    public function getDrivers()
    {
        return $this->drivers;
    }
    
    // __call魔术方法 从Manager中解析驱动的第二种方式
    public function __call($method, $parameters)
    {
        return $this->driver()->$method(...$parameters);
    }
    
    第三部分:使用(依然通过日志这个不恰当例子进行展示)
    1 创建契约
    <?php
    
    namespace AppContracts;
    
    interface ManageLog
    {
        public function logCertains($level, $foo);
    }
    
    2 创建日志组件,使用管理器管理
    <?php
    
    namespace AppComponentsLog;
    
    use IlluminateSupportManager;
    
    class LogManager extends Manager
    {   
        // 这是个类,并且你可以通过$this->app拿到容器实例,也就意味着你可以做很多事情
        public function getDefaultDriver()
        {   
            // 你也可以将返回的字符串写在配置中 等等
            return 'elasticsearch';
        }
    
        // 展示魔术方法解析驱动
        // $logManager->driver('elasticsearch')时触发
        public function createElasticsearchDriver()
        {   
            // 上一篇有简单示例
            return '你的es日志驱动';
        }
        
        // 查看manager中的驱动
        public function getCustomCreators()
        {
            return $this->customCreators;
        }
    }
    
    3 创建不同的日志驱动
    <?php
    
    namespace AppDriversLog;
    
    use AppContractsManageLog;
    use MonologLogger;
    use MonologHandlerRotatingFileHandler;
    use MonologProcessorMemoryPeakUsageProcessor;
    use MonologProcessorMemoryUsageProcessor;
    
    class RotateDriver implements ManageLog
    {
        protected $logger;
    
        public function __construct()
        {
            $logger = new Logger('manager');
            $rotatingHandler = new RotatingFileHandler(storage_path('logs/test/manager.log'), 7);
            $logger->pushHandler($rotatingHandler);
            //  随便加点什么吧
            $procesccor1 = new MemoryPeakUsageProcessor();
            $procesccor2 = new MemoryUsageProcessor();
            $logger->pushProcessor($procesccor1);
            $logger->pushProcessor($procesccor2);
            $this->logger = $logger;
        }
    
        public function logCertains($level, $foo)
        {
            $this->logger->{$level}($foo);
        }
    }
    
    <?php
    
    namespace AppDriversLog;
    
    use AppContractsManageLog;
    use MonologLogger;
    use MonologHandlerStreamHandler;
    
    class StreamDriver implements ManageLog
    {
        protected $logger;
    
        public function __construct()
        {
            $logger = new Logger('manager');
            $streamHandler = new StreamHandler(storage_path('logs/test/manager.log'));
            $logger->pushHandler($streamHandler);
            $this->logger = $logger;
        }
    
        public function logCertains($level, $foo)
        {
            $this->logger->{$level}($foo);
        }
    
        public function getLogger()
        {
            return $this->logger;
        }
    }
    
    <?php
    
    namespace AppDriversLog;
    
    use AppContractsManageLog;
    use IlluminateFoundationApplication;
    use MonologLogger;
    use MonologHandlerRedisHandler;
    use PredisClient;
    
    class RedisDriver implements ManageLog
    {   
        protected $logger;
    
        public function __construct(Application $app)
        {
            // $logger = new Logger('manager');
            // $redisClient = new Client('tcp://localhost:6379');
            // $redisHandler = new RedisHandler($redisClient, 'manager');
            // $logger->pushHandler($redisHandler);
            // $this->logger = $logger;
    
            //  利用monolog的重用机制
            $customLoggers = $app->make('logManager')->getCustomCreators();
            $streamLogger = call_user_func(array_shift($customLoggers));
            $redisLogger = $streamLogger->getLogger()->withName('redis');
            $redisClient = new Client('tcp://localhost:6379');
            $redisHandler = new RedisHandler($redisClient, 'manager');
            $redisLogger->pushHandler($redisHandler);
            $this->logger = $redisLogger;
        }
    
        public function logCertains($level, $foo)
        {
            $this->logger->{$level}($foo);
        }
    }
    
    4 创建服务提供者 
    # 此处说明一下 为什么使用singleton进行绑定,因为我在boot方法中两次解析manager为了将其扩展,保证每次解析都是同一个manager,
    # 也就修改了绑定到容器的manager,一旦在register方法中使用bind绑定的话,每次从容器中解析出来的都会是一个全新的manager,
    # 也就是说我们的boot方法白白浪费了,也就自然不能够进行任何的操作了。其实laravel为了解决这个问题还有其他方法,
    # 请各位仔细查看服务提供者部分的文档,我这里选择在boot方法中对manager进行扩展,其实你可以在任何你喜欢的地方扩展。
        
    php artisan make:provider LogManagerServiceProvider
    <?php
    
    namespace AppProviders;
    
    use IlluminateSupportServiceProvider;
    use AppComponentsLogLogManager;
    use AppDriversLogStreamDriver;
    use AppDriversLogRotateDriver;
    use AppDriversLogRedisDriver;
    
    class LogManagerServiceProvider extends ServiceProvider
    {
        /**
         * Register services.
         *
         * @return void
         */
        public function register()
        {   
            $this->app->singleton('logManager', function ($app) {
                # return new LogManager($app);
                // 利用容器帮助我们解决类的依赖
                return $app->make(LogManager::class);
            });
            $this->app->singleton(LogManager::class, function ($app) {
                return new LogManager($app);
            });
        }
    
        /**
         * Bootstrap services.
         *
         * @return void
         */
        public function boot()
        {
            // 扩展我们的logmanager
            $this->app['logManager']->extend('stream', function () {
                return new StreamDriver();
            });
            $this->app['logManager']->extend('rotate', function () {
                return new RotateDriver();
            });
            // dd($this->app['logManager']->getCustomCreators());
            $this->app[LogManager::class]->extend('redis', function () {
                // return new RedisDriver($this->app);
                return $this->app->make(RedisDriver::class);
            });
        }
    }
    
    5 注册服务
    config/app.php
    ...   
    AppProvidersRouteServiceProvider::class,
    // 注册自定义日志服务
    AppProvidersLogServiceProvider::class,
    // 注册日志管理服务
    AppProvidersLogManagerServiceProvider::class,
    
    6 使用测试 
    Route::get('logmanager', function (LogManager $logManager) { 
        resolve('logManager')->driver('stream')->logCertains('emergency', 'something emergency');
        resolve('logManager')->driver('rotate')->logCertains('debug', 'debug something');
        $logManager->driver('redis')->logCertains('info', 'aaa');
    });
    

    以上代码比较简单,各位领会精神就好,大家可以结合前面说过的facade,仿照laravel原生Log服务实现一个功能一致的log manager。

    今天没有下集预告,发现错误欢迎指正,感谢!!!

  • 相关阅读:
    实现自己的类加载时,重写方法loadClass与findClass的区别
    MQ中将消息发送至远程队列的配置
    IOS开发之控件篇UITabBarControllor第一章
    IOS开发-图片尺寸
    IOS开发之进阶篇第一章
    AStar算法(转载)
    GEF
    WizardDialog 进度条使用记录
    Struts2学习笔记-jsp中引用struts2框架
    Struts2学习笔记-基本结构
  • 原文地址:https://www.cnblogs.com/alwayslinger/p/13724361.html
Copyright © 2011-2022 走看看