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 命名空间可以解决以下两类问题:
-
- 用户编写的代码与PHP内部的类/函数/常量或第三方类/函数/常量之间的名字冲突。
-
- 为很长的标识符名称(通常是为了缓解第一类问题而定义的)创建一个别名(或简短)的名称,提高源代码的可读性。
什么是命名空间? 从广义上来说,命名空间是一种封装事物的方法。在很多地方都可以见到这种抽象概念。例如,在操作系统中目录用来将相关文件分组,对于目录中的文件来说,它就扮演了命名空间的角色。
案例: 用来给目录下的相关角色分组,在平时一个目录下不能存在相同的文件名,也不能在目录外来访问这个目录里的文件,命名空间的引用就是为了解决此类问题。
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
,通常会使用 include
和 require
,去把定义这个 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文件。
谢谢阅读!!!