PHP进阶——对象的遍历与序列化,反射机制,命名空间

PHP进阶——对象的遍历与序列化,反射机制,命名空间,类的自动加载



一,对象的遍历:

1. 使用foreach语句进行遍历

​  PHP提供了一种定义对象的方法,使其可以通过单元列表来遍历,比如 foreach 语句。所有可见属性都将被遍历。

​  关于foreach,它的语法结构提供了遍历数组的简单方式。foreach仅能够应用于数组和对象,若是应用于其他数据类型的变量,或者未初始化的变量,就会报错。具体语法如下:(两种)

foreach (iterable_expression as $value)
    statement
foreach (iterable_expression as $key => $value)
    statement
    
//解析:iterable_expression 指的是可迭代的表达式。第一种格式遍历给定的 `iterable_expression` 迭代器。每次循环中,当前单元的值被赋给 `$value`。第二种格式做同样的事,只除了当前单元的键名也会在每次循环中被赋给变量 `$key`。

回归到话题“对象的遍历”,下面我们用实例来演示foreach遍历对象。

<?php
class test{
    public $value1 = '1'; //公有变量
    public $value2 = '2';

    protected $protected = 'protected value'; //受保护变量
    private   $private   = 'private value'; //私有变量

}

$test = new test();
foreach ($test as $k => $v){
    echo  $k . '===' . $v . "<br>";
}
?>
    
//result:
value1===1
value2===2

总结:不管是方法还是受保护或者私有的变量,都无法遍历出来。只有公共的属性才能被遍历出来。于是,我们可以实现Iterator接口,让对象自行决定如何遍历以及每次遍历时哪些值可用。

2. 实现Iterator 接口的对象遍历:

 Iterator是一个PHP预定义的接口类, 可以直接使用, 这是它内部的结构:

Iterator extends Traversable 
{
abstract public current(): mixed — 返回当前元素
abstract public key(): scalar — 返回当前元素的键
abstract public next(): void — 向前移动到下一个元素
abstract public rewind(): void — 返回到迭代器的第一个元素
abstract public valid(): bool — 检查当前位置是否有效
}

下面我们演示Iterator 接口的对象遍历:

<?php
class MyIterator implements Iterator
{
    private $var = array();

    public function __construct($array)
    {
        if (is_array($array)) {
            $this->var = $array;
        }
    }

    public function rewind() {
        echo "rewinding\n";
        reset($this->var);
    }

    public function current() {
        $var = current($this->var);
        echo "current: $var\n";
        return $var;
    }

    public function key() {
        $var = key($this->var);
        echo "key: $var\n";
        return $var;
    }

    public function next() {
        $var = next($this->var);
        echo "next: $var\n";
        return $var;
    }

    public function valid() {
        $var = $this->current() !== false;
        echo "valid: {$var}\n";
        return $var;
    }
}

$values = array(1,2,3);
$it = new MyIterator($values);

foreach ($it as $a => $b) {
    print "$a: $b\n";
}
?>

3. 使用ArrayAccess接口:

其实PHP早已为我们准备好了一个接口:ArrayAccess。可以让对象能够像数组一样进行操作。

IteratorAggregate(聚合式迭代器)接口:创建外部迭代器的接口。下面我们来演示一下:

<?php

class myData implements IteratorAggregate {
    private $property1 = ["Public property one",'test'];

    public function getIterator() {  //创建ArrayIterator迭代器的实例, 并传属性
        return new ArrayIterator($this->property1);
    }
}

$obj = new myData;
foreach($obj as $key => $value) {
    echo $key."=>".$value."<br>";
}

?>

//result:
//0=>Public property one
//1=>test
/*
ArrayIterator迭代器会把对象或数组封装为一个可以通过foreach来操作的类
*/

更加深入的内容可以学习SPL拓展库的迭代器。文章:SPL (点击可跳转)




二,PHP反射机制


反射在每个面向对象的编程语言中都存在,它的主要目的就是在运行时分析类或者对象的状态,导出或提取出关于类、方法、属性、参数等的详细信息,包括注释。 反射是操纵面向对象范型中元模型的 API,可用于构建复杂,可扩展的应用。反射在日常的web开发中用的不多,更多的是在偏向底层一些的代码中。

反射的用途

  • 面向对象编程中对象被赋予了自省的能力,而自省的过程就是反射
  • 反射,直观理解就是根据到达地找到出发地和来源。比方说,我给你一个光秃秃的对象,我可以仅仅通过这个对象就能知道它所属的类,拥有的哪些方法。
  • 自动记载插件,自动生成文档,扩充PHP语言。
  • 反射指在 PHP 运行状态中,拓展分享 PHP 程序,导出和提出关于类,方法,属性,参数等详细信息,包括注释。这种动态获取信息已经动态调用对象方法的功能称为反射。

如何使用反射 Api:

我们先创建一个类:

<?php
class person{
    //类属性:
    public $name;
    public $gender;
    
    //类方法:
    public function print_out(){
        echo $this->name . " " . $this->gender . "<br>";
    }
    
    public function set($name, $value)
    {
        echo "设置 $name 为: $value <br>";
        $this->name=$value;
    }
    
    public function get($name)
    {
        if (!isset($this->name)) {
            echo "未设置";
            $this->$name="正在设置默认值";
        }
        return $this->name;
    }
}

$oneMan = new person();
$oneMan->name='kanye';
$oneMan->gender='man';
?>

现在我们要获取这个person对象的属性和方法:

//获取对象属性列表
$reflect=new ReflectionObject($oneMan);
$props =$reflect->getProperties();
foreach ($props as $prop) {
    echo $prop -> getName() ."<br>";
}
//getName()函数是PHP内置的Reflection类的方法之一,用来获取类的名称。
//获取对象方法列表
$reflect=new ReflectionObject($oneMan);
$method = $reflect->getMethods();
foreach ($method as $prop) {
    echo $prop-> getName() ."<br>";
}

也可以不用反射 Api ,使用 class 函数,返回对象属性的关联数组及相关信息:

//返回对象属性的关联数组:
var_dump(get_object_vars($oneMan));
//返回由类的方法名组成的数组
var_dump(get_class_methods(get_class($oneMan)));

假如这个对象是从其他页面传过来的,获取它属于哪个类:

//获取对象属性列表所属的类:
echo get_class($oneMan)

反射 Api 的功能显然更强大,甚至能还原这个类的原型,包括方法的访问权限:

//反射获取类的原型:
$obj = new ReflectionClass('person');
$className = $obj->getName();
$methods = $Properties =array();
foreach($obj -> getProperties() as $v){
    $Properties[$v->getName()] = $v;
}
foreach($obj -> getMethods() as $v){
    $methods[$v->getName()] = $v;
}
echo "class {$className} . <br> ";

反射的作用: 反射可以用于文档生成,因此可以用它对文件里的类进行扫描,逐个生成描述文档:

//操作类 mysql:
class mysql{
    function connect($db){
        echo "连接到数据库${db[0]}<br>";
    }
}
//动态传入参数:
class sqlproxy{
    private $target;
    function __construct($tar){
        $this->target[] = new $tar();
    }
    function __call($name, $args){
        foreach ($this->target as $obj){
            $r = new ReflectionClass($obj);
            if($method = $r -> getMethod($name)){
                if($method->isPublic() && !$method -> isAbstract()){
                    echo "方法前拦截记录LOG <br>";
                    $method->invoke($obj, $args);
                    echo "方法后拦截 <br>";
                }
            }
        }
    }
}
$obj = new sqlproxy('mysql');
$obj->connect('member');

总结:这里,真正的操作类是 mysql 类,但是 sqlproxy 类实现了根据动态传入参数,代替实际的类运行,在方法运行前后进行拦截,并且动态的改变类中的方法和属性,这就是简单的动态代理。

关于php的代理模式,可以参考:php设计模式 Proxy (代理模式) - 疯狂奔跑 - 博客园 (cnblogs.com)

PHP设计模式之代理模式 - The code - SegmentFault 思否



总结:

  • 平时开发中,真正用到反射的地方并不多:一个是对对象进行调式,另一个是获取类的信息,在 MVC 和插件开发中,使用反射很常见,但是反射的消耗很大,在可以找到代替方案的情况下,就不要滥用。
  • PHP 有 Token 函数,可以通过这个机制实现一些反射功能。从简单灵活的角度讲,使用已反射 API 是可取的。
  • 很多时候,善于反射能保持代码的优雅和简洁,但反射也会破坏类的封装性,因为反射可以使原本不应该暴露的方法或属性被强制暴露出来,这既是优点也是缺点。



三,命名空间


1.简介:
PHP 命名空间(namespace)是在 PHP 5.3 中加入的,目的是解决重名问题,PHP中不允许两个函数或者类出现相同的名字,否则会产生一个致命的错误。

PHP 命名空间可以解决以下两类问题:

    1. 用户编写的代码与PHP内部的类/函数/常量或第三方类/函数/常量之间的名字冲突。
    1. 为很长的标识符名称(通常是为了缓解第一类问题而定义的)创建一个别名(或简短)的名称,提高源代码的可读性。

什么是命名空间? 从广义上来说,命名空间是一种封装事物的方法。在很多地方都可以见到这种抽象概念。例如,在操作系统中目录用来将相关文件分组,对于目录中的文件来说,它就扮演了命名空间的角色。

案例: 用来给目录下的相关角色分组,在平时一个目录下不能存在相同的文件名,也不能在目录外来访问这个目录里的文件,命名空间的引用就是为了解决此类问题。

2.使用方法:

定义命名空间命名空间通过关键字namespace 来声明。如果一个文件中包含命名空间,它必须在其它所有代码之前声明命名空间。 语法格式如下:

<?php  
// 定义代码在 'MyProject' 命名空间中  
namespace MyProject;  
 
// ... 代码 ...  

命名空间内可包含任意 PHP 代码,但是仅对类 (包括抽象类和 Trait)、接口、函数和常量这四种类型生效。

<?php
namespace myProject;
const a=1;
class test1{};
interface test2{};
function test(){};

子命名空间,与目录和文件的关系很象,PHP 命名空间也允许指定层次化的命名空间的名称。因此,命名空间的名字可以使用分层次的方式定义:

<?php
namespace myProject\Sub\Level;
const CONNECT = 1;
class Connection { /* ... */ }
function Connect() { /* ... */  }

上面的例子创建了常量 MyProject\Sub\Level\CONNECT_OK,类 MyProject\Sub\Level\Connection 和函数 MyProject\Sub\Level\connect

在同一个文件中定义多个命名空间, 1.大括号法:

<?php
namespace MyProject {
    const connect = 1;
}
namespace AnotherProject {
    const connect = 1;
}
//在实际的编程实践中,非常不提倡在同一个文件中定义多个命名空间。这种方式的主要用于将多个 PHP 脚本合并在同一个文件中。

2.将全局的非命名空间中的代码与命名空间中的代码组合在一起,只能使用大括号形式的语法。全局代码必须用一个不带名称的 namespace 语句加上大括号括起来,例如:

<?php
namespace MyProject{
    const connect = 1;
    function connect(){};

}

namespace{ //全局代码
    session_start();
    $a = MyProject\connect();
}
?>

命名空间的使用:1.别名和导入

PHP 命名空间支持 有两种使用别名或导入方式:为类名称使用别名,或为命名空间名称使用别名。在PHP中,别名是通过操作符 use 来实现的. 下面是一个使用所有可能的导入方式的例子:

(1)使用use操作符导入/使用别名:使用该方法可以节省代码量。

<?php
namespace foo;
//普通导入:使用 use 关键字
use My\Full\name;
//别名导入:使用 as 关键词
use My\Full\name as AnotherName;
//导入函数:使用 use func 关键词 (支持别名as)
use func Namespace\functionName
functionName();
//导入常量:使用use const 关键词 (支持别名as)
<?php
use constant Namespace\CONST_NAME;
echo CONST_NAME;

//实例化:
$obj = new namespace\AnotherName; //实例化foo\AnotherName对象
$obj = new AnotherName; //实例化My\Full\name 对象

(2)多重导入,一行中包含多个use语句:

<?php
use Illuminate\Http\Request;
use Illuminate\Http\Response;

(3)导入和动态名称:

<?php
use My\Full\Classname as Another, My\Full\NSname;

$obj = new Another; // 实例化一个 My\Full\Classname 对象
$a = 'Another';
$obj = new $a;      // 实际化一个 Another 对象
?>

(4)导入和完全限定名称:

<?php
use My\Full\Classname as Another, My\Full\NSname;

$obj = new Another; // class My\Full\Classname 的实例对象
$obj = new \Another; // class Another 的实例对象
$obj = new Another\thing; // class My\Full\Classname\thing 的实例对象
$obj = new \Another\thing; // class Another\thing 的实例对象
?>

命名空间的使用:2.后备全局函数/常量。

(1)在命名空间中访问全局类:

<?php 
namespace A\B\C;
class Exception extends \Exception{};

$a = new Exception('hi'); //$a是类 A\B\C\Exception 的一个对象
$b = new \Exception('hi'); //$b 是类Exc的一个对象
?>

(2)命名空间中后备的全局函数/常量:

<?php
namespace A\B\C;

const E_ERROR = 45;
function strlen($str)
{
    return \strlen($str) - 1;
}

echo E_ERROR, "\n"; // 输出 "45"
echo INI_ALL, "\n"; // 输出 "7" - 使用全局常量 INI_ALL

echo strlen('hi'), "\n"; // 输出 "1"
if (is_array('hi')) { // 输出 "is not array"
    echo "is array\n";
} else {
    echo "is not array\n";
}
?>




四,类的自动加载

1.简介

在使用面向对象模式开发程序时,通常大家习惯为每个类都创建一个单独的 PHP 源文件。这样会很容易实现对类进行复用,同时将来维护时也很便利,这也是面向对象程序设计的基本思想之一。

PHP5 之前,如果希望从外部引入一个 class ,通常会使用 includerequire ,去把定义这个 class 的文件包含进来即可。这个在小规模开发的时候,没什么大问题。但在大型的开发项目中,这么做会产生大量的 include 或者 require 方法调用,这样不仅效率低下,而且使得代码难以维护,况且 require_once 的代价很大。

2.PHP提供了以下几种函数实现类的自动加载功能。

2.1 __autoload 函数: __autoload() 是系统函数,名称是固定的,而且该函数没有方法体,需要我们自定义方法体。另外,该函数是自动调用的,当我们 new 一个类时,如果当前源文件中找不到这个类,PHP 则会自动调用 __autoload() 函数,并将类名传递给 __autoload() 函数。

但是::: __autoload () 函数自 PHP7.2 起已被弃用,可以使用 spl_autoload_register () 函数代替。

语法格式如下:

function __audoload($class)
{
    //方法体
}
//其中$class为要加载的类名。想要试用__autoload()函数自动加载类文件,类文件的名称需要与类名相同,另外一个类文件中只能定义一个类。
//实例:使用__autoload()函数自动加载类文件
function __autoload($class){
    $file = './' . $class. 'php';
    include_once($file);
}
$obj = new Demo();

2.2 spl_autoload_register() 函数:

spl_autoload_register() 函数可以指定一个函数来替代 __autoload() 函数的功能。语法格式如下:

spl_autoload_register ($autoload_function = null, $throw = true, $prepend = false)

参数说明:

  • $autoload_function:要替代 __autoload() 函数的函数名称,也可以是一个匿名函数。如果没有提供任何参数,则自动注册 autoload 的默认实现函数 spl_autoload()。
  • $throw:用来设置 $autoload_function 无法成功注册时,spl_autoload_register() 函数是否抛出异常
  • $prepend:如果是 true ,则 spl_autoload_register() 函数会添加 $autoload_function 函数到队列之首,否则添加到队列尾部
//示例:使用 spl_autoload_register()函数指定另一个和函数来替代 __autoload()函数
spl_autoload_register('loadClass');

function loadClass($class){
    $file = './' . $class .'php';
    include_once($file);
}

$obj = new Demo();

运行上述代码需要用到Demo.php文件。

谢谢阅读!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值