PHP实现一个轻量级容器的方法
什么是容器
在开发过程中,经常会用到的一个概率就是依赖注入。我们借助依懒注入来解耦代码,选择性的按需加载服务,而这些通常都是借助容器来实现。
容器实现对类的统一管理,并且确保对象实例的唯一性
常用的容器网上有很多,如PHP-DI、YII-DI等各种实现,通常他们要么大而全,要么高度适配特定业务,与实际需要存在冲突。
出于需要,我们自己造一个轻量级的轮子,为了保持规范,我们基于PSR-11来实现。
PSR-11
PSR是php-fig提供的标准建议,虽然不是官方组织,但是得到广泛认可。PSR-11提供了容器接口。他包含ContainerInterface和两个异常接口,提供使用建议。
/**
*Describestheinterfaceofacontainerthatexposesmethodstoreaditsentries.
*/
interfaceContainerInterface
{
/**
*Findsanentryofthecontainerbyitsidentifierandreturnsit.
*
*@paramstring$idIdentifieroftheentrytolookfor.
*
*@throwsNotFoundExceptionInterfaceNoentrywasfoundfor**this**identifier.
*@throwsContainerExceptionInterfaceErrorwhileretrievingtheentry.
*
*@returnmixedEntry.
*/
publicfunctionget($id);
/**
*Returnstrueifthecontainercanreturnanentryforthegivenidentifier.
*Returnsfalseotherwise.
*
*`has($id)`returningtruedoesnotmeanthat`get($id)`willnotthrowanexception.
*Itdoeshowevermeanthat`get($id)`willnotthrowa`NotFoundExceptionInterface`.
*
*@paramstring$idIdentifieroftheentrytolookfor.
*
*@returnbool
*/
publicfunctionhas($id);
}
实现示例
我们先来实现接口中要求的两个方法
abstractclassAbstractContainerimplementsContainerInterface
{
protected$resolvedEntries=[];
/**
*@vararray
*/
protected$definitions=[];
publicfunction__construct($definitions=[])
{
foreach($definitionsas$id=>$definition){
$this->injection($id,$definition);
}
}
publicfunctionget($id)
{
if(!$this->has($id)){
thrownewNotFoundException("Noentryorclassfoundfor{$id}");
}
$instance=$this->make($id);
return$instance;
}
publicfunctionhas($id)
{
returnisset($this->definitions[$id]);
}
实际我们容器中注入的对象是多种多样的,所以我们单独抽出实例化方法。
publicfunctionmake($name)
{
if(!is_string($name)){
thrownew\InvalidArgumentException(sprintf(
'Thenameparametermustbeoftypestring,%sgiven',
is_object($name)?get_class($name):gettype($name)
));
}
if(isset($this->resolvedEntries[$name])){
return$this->resolvedEntries[$name];
}
if(!$this->has($name)){
thrownewNotFoundException("Noentryorclassfoundfor{$name}");
}
$definition=$this->definitions[$name];
$params=[];
if(is_array($definition)&&isset($definition['class'])){
$params=$definition;
$definition=$definition['class'];
unset($params['class']);
}
$object=$this->reflector($definition,$params);
return$this->resolvedEntries[$name]=$object;
}
publicfunctionreflector($concrete,array$params=[])
{
if($concreteinstanceof\Closure){
return$concrete($params);
}elseif(is_string($concrete)){
$reflection=new\ReflectionClass($concrete);
$dependencies=$this->getDependencies($reflection);
foreach($paramsas$index=>$value){
$dependencies[$index]=$value;
}
return$reflection->newInstanceArgs($dependencies);
}elseif(is_object($concrete)){
return$concrete;
}
}
/**
*@param\ReflectionClass$reflection
*@returnarray
*/
privatefunctiongetDependencies($reflection)
{
$dependencies=[];
$constructor=$reflection->getConstructor();
if($constructor!==null){
$parameters=$constructor->getParameters();
$dependencies=$this->getParametersByDependencies($parameters);
}
return$dependencies;
}
/**
*
*获取构造类相关参数的依赖
*@paramarray$dependencies
*@returnarray$parameters
**/
privatefunctiongetParametersByDependencies(array$dependencies)
{
$parameters=[];
foreach($dependenciesas$param){
if($param->getClass()){
$paramName=$param->getClass()->name;
$paramObject=$this->reflector($paramName);
$parameters[]=$paramObject;
}elseif($param->isArray()){
if($param->isDefaultValueAvailable()){
$parameters[]=$param->getDefaultValue();
}else{
$parameters[]=[];
}
}elseif($param->isCallable()){
if($param->isDefaultValueAvailable()){
$parameters[]=$param->getDefaultValue();
}else{
$parameters[]=function($arg){
};
}
}else{
if($param->isDefaultValueAvailable()){
$parameters[]=$param->getDefaultValue();
}else{
if($param->allowsNull()){
$parameters[]=null;
}else{
$parameters[]=false;
}
}
}
}
return$parameters;
}
如你所见,到目前为止我们只实现了从容器中取出实例,从哪里去提供实例定义呢,所以我们还需要提供一个方水法
/**
*@paramstring$id
*@paramstring|array|callable$concrete
*@throwsContainerException
*/
publicfunctioninjection($id,$concrete)
{
if(is_array($concrete)&&!isset($concrete['class'])){
thrownewContainerException('数组必须包含类定义');
}
$this->definitions[$id]=$concrete;
}
只有这样吗?对的,有了这些操作我们已经有一个完整的容器了,插箱即用。
不过为了使用方便,我们可以再提供一些便捷的方法,比如数组式访问。
classContainerextendsAbstractContainerimplements\ArrayAccess
{
publicfunctionoffsetExists($offset)
{
return$this->has($offset);
}
publicfunctionoffsetGet($offset)
{
return$this->get($offset);
}
publicfunctionoffsetSet($offset,$value)
{
return$this->injection($offset,$value);
}
publicfunctionoffsetUnset($offset)
{
unset($this->resolvedEntries[$offset]);
unset($this->definitions[$offset]);
}
}
这样我们就拥有了一个功能丰富,使用方便的轻量级容器了,赶快整合到你的项目中去吧。
点击这里查看完整代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。