annotations是通过反射实现的。代码示例是annotations 1.7版本

github地址为、https://github.com/doctrine/annotations

php-di的文档地址:http://php-di.org/doc/annotations.html

声明自己的自定义注解

namespace App\annotations;

/**
 * @Annotation
 */
class Value
{
    public $name;
}

新定义一个类文件使用value注解(自定义)、注意必须放入命名空间

namespace App\test;

use App\annotations\Value;

class MyRedis
{
    /**
     * @Value(name="my_url")
     */
    public $conn_url;
}

index.php测试使用

require __DIR__.'/vendor/autoload.php';
use App\annotations\Value;
use App\test\MyRedis;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;



//注册 自定义注解的namespace

AnnotationRegistry::registerAutoloadNamespace("App\annotations");

$rc=new ReflectionClass(MyRedis::class);
$p=$rc->getProperty("conn_url");//获取反射对象的属性对象

$reader = new AnnotationReader();
$anno=$reader->getPropertyAnnotation($p,Value::class);
//加载属性对象和value注解
echo $anno->name;

注解的类型

  • CLASS:Allowed in the class docblock
  • PROPERTY:Allowed in the property docblock
  • METHOD:Allowed in the method docblock
  • ALL:Allowed in the class,property and method docblock
  • ANNOTATION:Allowed inside other annotations

修改自定注解、标注类型app/annotations

namespace App\annotations;

use Doctrine\Common\Annotations\Annotation\Target;

/**
 * @Annotation
 * @Target({"PROPERTY"})
 * 代表这能用于属性注解
 */
class Value
{
    public $name;
}

然后添加env 文件、在自定义的Value的注解类添加do方法

my_url=127.0.0.1

图

自定义简单的加载类

namespace App\core;


use Doctrine\Common\Annotations\AnnotationReader;

class ClassFactory
{

    /**
     * 只处理属性注解
     * @param $classname
     * @return object
     * @throws \Doctrine\Common\Annotations\AnnotationException
     * @throws \ReflectionException
     */
    public static function loadClass($classname)
    {
        $ref_class=new \ReflectionClass($classname);
        //获取类的所有属性
        $properties=$ref_class->getProperties();
        $reader=new AnnotationReader();
        foreach($properties as $property){
            /**
             * 获取属性的所有注解
             * 拿MyRedis类中的@value举例name是Value注解的属性
             */
            $annos=$reader->getPropertyAnnotations($property);
            foreach ($annos as $anno){
                //获取的调用类属性对象
                var_dump($anno);
                //获取配置文件的值
                var_dump($anno->do());
                $getValue=$anno->do();
                $retObj=$ref_class->newInstance();
                //给反射对象设置值
                $property->setValue($retObj,$getValue);
                return $retObj;
            }
        }
    }
}

index.php代码更改为

require __DIR__.'/vendor/autoload.php';
use App\core\ClassFactory;
use App\test\MyRedis;
use Doctrine\Common\Annotations\AnnotationRegistry;

//注册 自定义注解的namespace
AnnotationRegistry::registerAutoloadNamespace("App\annotations");

$redis=ClassFactory::loadClass(MyRedis::class);
var_dump($redis);

结果成功的MyRedis类中的属性赋值了

图

简易模拟Bean装载功能

定义一个Bean类打上注解,完整的实现Bean装载功能,需要对IOC容器/依赖注入需要完整的理解。

namespace App\annotations;

use Doctrine\Common\Annotations\Annotation\Target;

/**
 * @Annotation
 * @Target({"CLASS"})
 */
class Bean
{

}
//注意 这个自定义的Bean注解、需要打在需要装载的类上面

修改MyRedis类、添加上类Bean注解

namespace App\test;

use App\annotations\Bean;
use App\annotations\Value;

/**
 * Class MyRedis
 * @Bean()
 */
class MyRedis
{
    /**
     * @Value(name="my_url")
     */
    public $conn_url;
}

修改ClassFactory类实现简易装载

代码中的get_declared_classes位置上来说 、并不是唯一方式来引入文件,也可以根据文件名称截取获取这个类名来实例化

namespace App\core;


use App\annotations\Bean;
use Doctrine\Common\Annotations\AnnotationReader;
use http\Env\Request;

class ClassFactory
{
    private static $beans=[];//key--value

    /**
     * @param string $path
     * @param $namespace
     */
    public static function ScanBeans(string $path,$namespace)
    {
        //glob加载文件,可以支持匹配模式
        $phpFiles=glob($path."/*.php");
        foreach ($phpFiles as $php){
            require ($php);
        }
        $reader=new AnnotationReader();
        //获取当前运行后加载的所有类
        $classes=get_declared_classes();
        foreach ($classes as $class){
            if(strstr($class,$namespace)){
                $ref_class=new \ReflectionClass($class);
                $annos=$reader->getClassAnnotations($ref_class);
                foreach ($annos as $anno){
                    if($anno instanceof Bean){
                        self::$beans[$ref_class->getName()]=$ref_class->newInstance();
                    }
                }
            }
        }
    }

    public static function getBean(string $BeanName)
    {
        if(isset(self::$beans[$BeanName])){
            return self::$beans[$BeanName];
        }
        return false;
    }

    /**
     * 只处理属性注解
     * @param $classname
     * @return object
     * @throws \Doctrine\Common\Annotations\AnnotationException
     * @throws \ReflectionException
     */
    public static function loadClass($classname)
    {
        $ref_class=new \ReflectionClass($classname);
        //获取类的所有属性
        $properties=$ref_class->getProperties();
        $reader=new AnnotationReader();
        foreach($properties as $property){
            /**
             * 获取属性的所有注解
             * 拿MyRedis类中的@value举例name是Value注解的属性
             */
            $annos=$reader->getPropertyAnnotations($property);
            foreach ($annos as $anno){
                //获取的调用类属性对象
                var_dump($anno);
                //获取配置文件的值
                var_dump($anno->do());
                $getValue=$anno->do();
                $retObj=$ref_class->newInstance();
                $property->setValue($retObj,$getValue);
                return $retObj;
            }
        }
    }
}

修改index.php文件

require __DIR__.'/vendor/autoload.php';
use App\core\ClassFactory;
use App\test\MyRedis;
use Doctrine\Common\Annotations\AnnotationRegistry;

//注册 自定义注解的namespace
AnnotationRegistry::registerAutoloadNamespace("App\annotations");

ClassFactory::ScanBeans(__DIR__."/app/test",'App\\test');
$myRedis=ClassFactory::getBean(MyRedis::class);
var_dump($myRedis);

PHP-di中的@Inject注解

通过Inject的注解实现依赖注入,方法注入

namespace App\test;
use DI\Annotation\Inject;

class MyUser{
    private $mydb;

    /**
     * @Inject()
     * @param MyDB $DB
     */
    public function __construct(MyDB $DB)
    {
        $this->mydb=$DB;
    }
    public function getAllUsers():array{//业务方法
        return $this->mydb->queryForRows("select * from users");
    }
}

MyDB.php 因为在同一个命名空间、所以不用use

namespace App\test;
class MyDB{
    private $db;//这里可能是pdo 可能是别的,仅仅为了演示
    //因为暂时没有$connInfo信息、所以设置为空防止运行报错
    public function __construct($connInfo="")
    {
        //略
    }
    public function queryForRows($sql){
        return ['user_id'=>101,"user_name"=>"qidong"];
    }
}

test.php

require_once __DIR__."/vendor/autoload.php";

$builder=new \DI\ContainerBuilder();
/**
 * addDefinitions是以配置数组文件的方式加载依赖关系
 */
//$builder->addDefinitions(__DIR__."/app/test/beans.php");
//$container=$builder->build();
//$myuser=$container->get(\App\test\MyUser::class);
//var_dump($myuser->getAllUsers());
//echo \App\test\MyDB::class;
$builder->useAnnotations(true);
$container=$builder->build();
$myUser=$container->get(\App\test\MyUser::class);

var_dump($myUser->getAllUsers());

图

通过Inject的注解实现属性值的注入

新建MyRedis.php

namespace App\test;


class MyRedis
{
    public function getValue()
    {
        return "redis";
    }
}

修改MyUser.php。给MyRedis属性打上注解、因为是同个文件夹的命名空间所以

namespace App\test;
use DI\Annotation\Inject;

class MyUser{
    private $mydb;

    /**
     * @Inject()
     * @param MyDB $DB
     */
    public function __construct(MyDB $DB)
    {
        $this->mydb=$DB;
    }
    public function getAllUsers():array{//业务方法
        return $this->mydb->queryForRows("select * from users");
    }

    /**
     * @Inject()
     * @var MyRedis
     */
    public $MyRedis;
}

调用处改成

$builder->useAnnotations(true);
$container=$builder->build();
$myUser=$container->get(\App\test\MyUser::class);

var_dump($myUser->MyRedis->getValue());

图

Bean注解之支持自定义Bean名称

namespace Core;


use DI\ContainerBuilder;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;

class BeanFactory
{
    private static $env=[]; //env 配置文件
    private static $cotainer; //ioc 容器

    public static function init() //初始化函数
    {
        self::$env=parse_ini_file(ROOT_PATH."/env");

        $builder=new ContainerBuilder(); //初始化容器Builder
        $builder->useAnnotations(true); //启用注解,主要是用它的Inject注解
        self::$cotainer=$builder->build(); //容器初始化

        self::ScanBeans(); //扫描
    }

    private static function getEnv(string $key,string $default=""){ //获取env文件中的配置内容
        if(isset(self::$env[$key])) return self::$env[$key];
        return $default;
    }

    public static function getBean($name){
        return self::$cotainer->get($name);
    }
    public static function ScanBeans(){
        //读取注解 对应的handler
        $anno_handlers=require_once(ROOT_PATH."/core/annotations/AnnotationHandler.php");

        $scan_dir=self::getEnv("scan_dir",ROOT_PATH."/app");//扫描路径
        $scan_root_namespace=self::getEnv("scan_root_namespace", "App\\");//扫描的 namespace
        $files=glob($scan_dir."/*.php");
        foreach ($files as $file){
            require_once $file;
        }
        $reader=new  AnnotationReader();
        AnnotationRegistry::registerAutoloadNamespace("Core\annotations");

        foreach (get_declared_classes() as $class){
            if(strstr($class,$scan_root_namespace)) {
                $ref_class=new \ReflectionClass($class);//目标类的反射对象
                $class_annos=$reader->getClassAnnotations($ref_class);//获取所有类注解

                /////下方是处理 类注解
                foreach ($class_annos as $class_anno){
                    $handler=$anno_handlers[get_class($class_anno)]; //获取handler处理过程
                    //$class_anno是类本身
                    $handler(self::$cotainer->get($ref_class->getName()),self::$cotainer,$class_anno); //执行处理
                }
            }
        }


    }

}

定义AnnotationHandler.php文件

namespace Core\annotations;


return [
    //类注解
  Bean::class=>function($instance,$container,$self){
    var_dump($self);
    $vars=get_object_vars($self);
    if(isset($vars["name"]) && $vars["name"]!=""){
        $beanName=$vars["name"];
    }else{
        $arr=explode("\\",get_class($instance));
        $beanName=end($arr);
    }

    $container->set($beanName,$instance);
  } ,
  //属性注解
  Value::class=>function(){

  }
];

Bean类添加name属性

namespace Core\annotations;

use Doctrine\Common\Annotations\Annotation\Target;

/**
 * @Annotation
 * @Target({"CLASS"})
 */
class Bean
{
    public $name;
}

类注解那里是关键代码、自定义了加载类的名称

require_once __DIR__."/vendor/autoload.php";
require_once __DIR__."/app/config/define.php";

\Core\BeanFactory::init();

$user=\Core\BeanFactory::getBean('User');

var_dump($user);
➜  lu php index.php
object(Core\annotations\Bean)#23 (1) {
  ["name"]=>
  NULL
}
object(App\controllers\User)#29 (1) {
  ["version"]=>
  string(3) "1.0"
}
➜  lu

修改controller下的user、给user命名aaa

namespace App\controllers;

use Core\annotations\Bean;

/**
 * @Bean(name="aaa")
 */
class User
{
    public $version="1.0";
}

调用index.php

require_once __DIR__."/vendor/autoload.php";
require_once __DIR__."/app/config/define.php";

\Core\BeanFactory::init();

$user=\Core\BeanFactory::getBean('aaa');

var_dump($user);
Last modification:February 4, 2020
如果觉得我的文章对你有用,请随意赞赏