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);