laravel框架,容器的简单实现


  在laravel框架中,根据laravel开发文档,文档介绍了框架中的4中核心,一个是请求周期,一个是服务提容器,一个是服务提供者,还有则是facade,另外还有契约,这几天跟随laravel社区大佬的脚本,读那位大佬所实现的容器代码,收获颇丰,今天主要总结的就是有关服务容器所实现的东西。

  根据开发文档,为什么需要服务容器,开到开发文档中的示例,在一个控制器被实例化的时候,控制器可能依赖于某个实例,比如控制器对应的数据模型实例,但是控制器的方法不是我们调用的,是框架自己在请求的过程中自己调用的,由于控制器依赖于某个对象,所以最好的实例化时机就是在类被初始化即调用构造方法的时候,我们可以在够造方法的参数中添加一个(带类型提示)参数,并在控制器中设置值,通过这种方式实现依赖。构造方法中传入的参数必须是已经注册过的类,否则会报错,注册类的文件即为服务提供者,而服务容器则用来存储待实例化的类或已经实例化的实例。

  引用官方的例子,以下是控制器实例依赖于另一个实例:

<?php

namespace App\Http\Controllers;

use App\User;
use App\Repositories\UserRepository;
use App\Http\Controllers\Controller;

class UserController extends Controller
{
    /**
     * 用户存储库的实现。
     *
     * @var UserRepository
     */
    protected $users;

    /**
     * 创建新的控制器实例。
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
     *显示指定用户的 profile。
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        $user = $this->users->find($id);

        return view('user.profile', ['user' => $user]);
    }
}
View Code

  创建一个服务提供者,可以使用php artisan  命令:

php artisan make:provider RiakServiceProvider

  生成的文件内容如下:

<?php

namespace App\Providers;

use Riak\Connection;
use Illuminate\Support\ServiceProvider;

class RiakServiceProvider extends ServiceProvider
{
    /**
     * 在服务容器里注册
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(Connection::class, function ($app) {
            return new Connection(config('riak'));
        });
    }
}

  在这个文件中,我们通过在register中编写后面需要使用的实例,框架则会在实例化控制器的时候同时将这个服务提供者生成的对象传入,在学java过程中,spring篇也提到了这一项:控制反转。(稍微提一下)

  重点来了,一下是以为大佬写的简化版的服务容器:

<?php
/**
 * Created by PhpStorm.
 * User: @DennisRitche
 * Date: 2019/11/25 0025
 * Time: 9:00
 */

class  Container
{
    /**
     * @var  $instance array
     * **/
    private $instance = [];
    /**
     * @var  $binds array
     * **/
    private $binds = [];

    public function __construct()//构造方法
    {
        $this->instance[Container::class] = $this;
    }

    /**
     * add new instance
     * @param $name string
     * @param $value mixed
     */
    public function addInstance($name, $value)
    {
        $this->instance[$name] = $value;
    }


    /**
     * @param $name string
     * @param $value  mixed
     * @param bool $shared
     */
    public function newBind($name, $value, $shared = false)
    {
        if ($value instanceof Closure) {
            $this->binds[$name] = ['concrete' => $value, "shared" => $shared];
        } else {
            if (!is_string($value) || !class_exists($value, true)) {
                throw new InvalidArgumentException("value must be callback or class name");
            }
        }
        $this->binds[$name] = ['concrete' => $value, 'shared' => $shared];
    }


    /**
     * @param $name string
     * @param array $real_args
     * @return  mixed
     */
    public function make($name, $real_args = [])
    {
        //check firstly
        if (!isset($this->instance[$name]) && !isset($this->binds[$name])) {
            if (!class_exists($name, true)) {
                throw new  InvalidArgumentException("class not exist");
            }
        }

        if (isset($this->instance[$name])) {
            return $this->instance[$name];
        }

        try {
            if (isset($this->binds[$name])) {
                $concrete = $this->binds[$name]['concrete'];
                if (is_callable($concrete)) {
                    print_r($real_args);
                    $instance = $this->parseCallable($concrete, $real_args);
                } else {
                    $instance = $this->parseClass($concrete, $real_args);
                }
            } else {
                $instance = $this->parseClass($name, $real_args);
            }
            if (isset($this->binds[$name]) && $this->binds[$name]['shared']) {
                $this->instance[$name] = $instance;
            }
            return $instance;
        } catch (Exception $exception) {
            echo $exception->getMessage();
        }
    }

    /**
     * @param callable $callback
     * @param $real_args
     * @return mixed
     * @throws ReflectionException
     */
    private function parseCallable(callable $callback, $real_args)
    {
        $refl_function = new ReflectionFunction($callback);
        $parameters = $refl_function->getParameters();
        $parsed_args = [];
        if (count($parameters) > 0) {
            $this->resolveDependencies($parameters, $parsed_args, $real_args);
        }
        return $refl_function->invokeArgs($parsed_args);
    }

    /**
     * @param $class_name
     * @param array $real_args
     * @return object
     * @throws ReflectionException
     */
    private function parseClass($class_name, $real_args = [])
    {
        $refl_class = new ReflectionClass($class_name);
        if (!$refl_class->isInstantiable()) {
            throw  new RuntimeException("{$class_name} can not be initialized");
        }
        if (!($constructor = $refl_class->getConstructor())) {
            //no default constructor,so create it directly
            return new $class_name;
        } else {
            $args = [];
            return $refl_class->newInstanceArgs($this->resolveDependencies($constructor->getParameters(), $args, $real_args));
        }
    }

    /**
     * @param $parameters
     * @param $parsed_args
     * @param $real_args
     * @return array|mixed
     * @throws ReflectionException
     */
    private function resolveDependencies($parameters, &$parsed_args, $real_args = [])
    {
        /**
         * @var $parameter ReflectionParameter
         * **/
        foreach ($parameters as $parameter) {
            if ($parameter->getClass() != null) {
                if (!class_exists($parameter->getClass()->getName(), true)) {
                    throw new RuntimeException($parameter->getClass()->getName() . " not exist");
                } else {
                    $parsed_args[] = $this->make($parameter->getClass()->getName());
                }
            } else {
                if (!$parameter->isDefaultValueAvailable()) {
                    if (!isset($real_args[$parameter->getName()])) {
                        throw  new RuntimeException($parameter->getName() . " has no  value");
                    } else {
                        $parsed_args[] = $real_args[$parameter->getName()];
                    }
                } else {
                    if (isset($real_args[$parameter->getName()])) {
                        $parsed_args[] = $real_args[$parameter->getName()];
                    } else {
                        $parsed_args[] = $parameter->getDefaultValue();
                    }
                }
            }
        }
        return $parsed_args;
    }
    
}

  这个类中定义的一系列方法,作用是什么呢,让我们看看大佬发的一个例子:

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2019/11/25 0025
 * Time: 11:32
 */

require_once "./Container.php";

class  Family
{
    private $level;
    private $children_num;

    /**
     * @var  $container Container
     * **/
    private $container;

    /**
     * Family constructor.
     * @param $level int
     * @param $children_num string
     * @param $container
     */
    public function __construct($level, $children_num, $container)
    {
        $this->level = $level;
        $this->children_num = $children_num;
        $this->container = $container;
    }
}

class Student
{
    /**
     * @var  $name string
     * **/
    private $name;
    /**
     * @var  $family Family
     * **/
    private $family;

    /**
     * Student constructor.
     * @param $name
     * @param Family $family
     */
    public function __construct($name, Family $family)
    {
        $this->name = $name;
        $this->family = $family;
    }
}

/*$family=new Family();*/
$container = new Container();
$container->newBind(Family::class, function (Container $container) {
    $family = new Family(10, 3, $container);
    return $family;
});
$inst = $container->make(Student::class, ["name" => "obama"]);
print_r($inst);

  首先创建了一个容器对象,创建时,会把当前对象存储至$instance数组中key是类名,value是对应的实例,然后调用了newBind方法,第一个是Family类名,第二个是个闭包函数,进入newBind方法中,经过一系列操作,它将该闭包存入了$bind数组, $bind数组的内容为:

$bind = [
    'Family'=>[
        'concrete'=>闭包对象,
        ‘share’=>false,
    ]       
]

  然后在dubug.php中调用make方法创建Student的实例,进入make方法,一系列检测之后发现bind和instance属性都没有有关该类的东西,直接调用了parseClass()方法,这个方法通过反射机制获取构造方法的参数名数组,并调用resolveDependencies()方法传入参数名数组,和传入的实际参数,之后检测发现少了Family类型参数,于是再一次进行make,此次make在bind属性中找到了闭包方法,公共闭包方法返回了Family的一个实例,期间还去找了Container实例传入闭包方法中,最终成功实例化Student。

  之前一直在纳闷,newbind方法有什么用,之后发现是给bind传入待实例化的东西,这个东西可以是闭包可以是一个类的名称,其实感觉还可以优化一下如果第二个参数是字符串,则存储类名,第一个也是类名,第一个参数和第二个参数的关系可以是同一个类,也可以有继承或者实现的关系这种模式与Laravel中的契约不谋而合。后面感觉很奇怪,为什么resolveDependencies()需要的第二个参数需要引用,之后发现在parseCallable这个方法中有用到,其实可以稍微改下parseCallable这个方法结构就不需要引用传值了。

  读完这个容器代码之后,感觉很奇妙,知道了这个类以及这个类中的方法的具体职责是什么,这个类究竟干了些什么,哪些场景可以用到。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM