【深入PHP 面向对象】读书笔记(三) - 对象工具

【简介】
包(package):将代码按逻辑分类打包。
命名空间:从 PHP5.3 开始,可以将代码元素封装在独立的单元中。
包含路径:为你的类库代码设置访问路径。
类函数和对象函数:测试对象、类、属性和方法的函数。
反射API(Reflection API):一组强大的内置类,可以在代码运行时访问类信息。

5. 对象工具

5.1 包和命名空间

包是一组相关类的集合,这些类以某种方式组合在一起。

包可以把系统的一部分和其他部分分隔开来。

PHP 本身不支持包的概念,但是有一种使用文件系统和命名模式将代码组织成类似于包的结构(命名空间)。

在命名空间中,可以访问其中的类、函数和变量;在命名空间之外,必须导入或引用命名空间,才可以访问其中的类、函数和变量。

namespace 关键字用于创建命名空间。

namespace my;

class Outputter {
    // 用于输出的代码
}

在创建层次更深的命名空间时,通过反斜杠字符将每一层分开就可以。

namespace com\getinstance\util;

class Debug {
    static function helloWorld() {
        echo "hello from Debug.";
    }
}

调用时取决于从何处进行调用,在不同位置需要不同的调用方法。

在命名空间内部调用类中的方法,可以直接调用:

Debug::helloWorld();

这种称为非限定名。因为已经在 com\getinstance\util 的命名空间中了,所以不必在类名前加任何种类的路径。

如果在命名空间环境外访问类,则需要使用限定名,使用相对命名空间或者绝对命名空间,这有点类似文件路径中的相对路径和绝对路径。

在 com 的命名空间中,可以使用相对命名空间进行调用:

namespace com;
getinstance\util\Debug::helloWorld();

但是在一个不相干的命名空间中,则需要使用绝对命名空间:

namespace main;
com\getinstance\util\Debug::helloWorld();

使用绝对命名空间可以保证调用的正确性,但是会使得代码变得冗长,假如需要调用好几个Debug类中的方法,则需要反复写好多遍com\getinstance\util。

namespace main;
com\getinstance\util\Debug::a();
com\getinstance\util\Debug::b();
com\getinstance\util\Debug::c();
...

这种情况下,可以使用 use 关键字,用于导入命名空间。

namespace main;
use com\getinstance\util
util\Debug::a();
util\Debug::b();
util\Debug::c();
...

导入 com\getinstance\util 命名空间,在调用的时候需要使用最后一级空间名util,这实际上是一个隐式的别名util。

有的时候,需要使用显式的别名,在下面的例子中,main中也用Debug类,在引用util中的Debug类时,就会报错,此时需要使用别名来做区分。

namespace main;
use com\getinstance\util\Debug as uDebug;

class Debug {
    static function helloWorld() {
        echo "hello from main\Debug.";
    }
}

uDebug::helloWorld();

__NAMESPACE__ 常量可以获取到当前的命名空间,在调试的时候很有用。

// global.php 无命名空间
class Lister {
    public static function helloWorld() {
        echo "hello from global.";
    }
}


namespace com\getinstance\util;
require_once 'global.php';
class Lister {
    static function helloWorld() {
        echo "hello from ".__NAMESPACE__.;
    }
}

Lister::helloWorld();
\Lister::helloWorld();

这里的 \Lister,表示从全局命名空间中调用helloWorld()方法。输出的内容为:

hello from com\getinstance\util
hello from global

5.2 PEAR 风格的命名方式和自动加载

PEAR(PHP Extension and Application Repository,PHP 扩展和应用程序库)是 PHP 官方维护的软件包集合,也是增强 PHP功能的工具。

PEAR 使用文件系统来定义包,每个类根据包路径来命名,每个路径名以下划线来分隔。

例如,PEAR 有一个 XML 包,XML 包中有一个 RPC 子包,RPC 包中有一个 Server.php 文件。但这个 Server.php 定义的类并不是直接叫做 Server,而是被命名为 XML_RPC_Server。

PHP5 引入了 __autoload()拦截器方法来自动包含类文件。

当 PHP 引擎遇到试图实例化未知类的操作时,会调用 __autoload()方法(如果已经定义),并将类名当做字符串参数传递给它。

__autoload() 的编写者应该自己定义一种策略来定位和包含缺少的类文件。

function __autoload($classname){
    include_once("$classname.class.php");
}

在实例化一个类时,如果这个类没有被引入,就会去执行__autoload()方法。

对于可能使用 PEAR 命名风格的类名,可以使用以下的代码进行处理。测试反斜杠字符,如果该字符存在的话,就添加转换:

function __autoload($classname){
    if (preg_match('/\\\\/', $classname)) {
        $path = str_replace('\\', DIRECTORY_SEPARATOR, $classname);
    } else {
        $path = str_replace('_', DIRECTORY_SEPARATOR, $classname);
    }
    include_once("$path.class.php");
}

5.3 类函数和对象函数

5.3.1 查找类

class_exists()函数接受表示类的字符串,检查并返回布尔值。如果类存在,则返回 true,否则返回 false。

if (!class_exists($classname)) {
    throw new Exception("No such class as $classname");    
}

$myObj = new $classname;

在使用类之前,对类进行安全性检查,会使代码更加严谨。

get_declared_classes() 函数来获得脚本进程中定义的所有类的数组。print_r(get_declared_classes()) 会列出用户定义的类和 PHP 内置的类。

5.3.2 了解对象或类

get_class() 函数用于检查对象所属的类,参数为对象,返回值为类名。

$product = new CDProduct();
if(get_class($product) == 'CDProduct') {
    echo "\$product is a CDProduct object.";
}

5.3.3 了解类中的方法

get_class_methods()函数可以获取类中所有的方法的列表。该函数接受一个类名作为参数,返回包含类中所有方法名的数组。

class A {

    public function fun1() {}   
    public function fun2() {}   
    public function fun3() {}   
}

print_r(get_class_methods('A'));

//获取到的方法列表
Array ( 
    [0] => fun1 
    [1] => fun2 
    [2] => fun3 
)

is_callable() 和 method_exists() 用于检测函数是否在类中。

is_callable() 传入一个数组参数,数组中第一个元素为对象或类,第二个参数为方法名,如果该方法存在,返回true。

class A {

    public function fun1() {}   
    public function fun2() {}   
    public function fun3() {}   
}

$obj = new A();
$method = "fun1";
if (is_callable(array($obj, $method))) {
    echo "\$obj exists $method.";
}

//输出
$obj exists fun1.

method_exists()接收两个参数, 第一个为对象,第二个为方法名,如果给定方法在对象的类中存在,则返回true。

class A {

    public function fun1() {}   
    public function fun2() {}   
    public function fun3() {}   
}

$obj = new A();
$method = "fun1";
if (method_exists($obj, $method)) {
    echo "\$obj exists $method.";
}

//输出
$obj exists fun1.

5.3.4 了解类属性

get_class_vars() 函数检查类中的属性(只能获取到public的属性,private和protected的属性无法获取)。

该函数接收类名作为参数,返回关联数组,属性名为键名,属性值为键值。

class A {

    private $a = 'private';
    protected $b = 'protected';
    public $c = 'public';

}

print_r(get_class_vars('A'));

// 输出关联数组
Array ( [c] => public )

5.3.5 了解继承

get_parents_class() 可以获取一个类的父类,参数为一个对象或类名。如果父类存在的话,返回父类的名字;如果父类不存在的话,即没有检测到父类,返回false。

class Fu {}

class Zi extends Fu {}


print_r(get_parent_class('Zi'));

//输出
Fu

is_subclass_of() 函数检测类是否是另一个类的派生类,接受一个子类对象和父类的字符串名字。如果第二个参数是第一个参数的父类的话,该函数返回 true。

<?php

class Fu {}

class Zi extends Fu {}

$obj = new Zi();
if (is_subclass_of($obj, 'Fu')) {
    echo "\$product is a subclass of Fu.";
}

// 输出
$product is a subclass of Fu.

class_implements()使用一个类名或一个对象引用作为参数,并且返回一个由接口名构成的数组。

5.3.6 方法调用

call_user_func()函数用于调用方法或函数。要调用一个函数,需要将字符串作为它的第一个参数:

// 一个fun()函数
function fun() {}

// 调用fun()函数
call_user_func('fun');

要调用一个类中的方法,需要传入一个数组。数组的第一个元素是对象,第二个元素是要调用的方法名。

class A {

    public function fun() {}
}

$obj = new A();
call_user_func(array($obj, 'fun'));

call_user_func_array() 方法的基本使用方法和 call_user_func() 一样,但在传入参数上更加方便,第二个参数为一个数组,所调用函数的传参,可在这个数组中传入任意数量个的参数。

5.4 反射 API

PHP 的反射 API 由一系列可以分析属性、方法和类的内置类组成。

PHP 的反射 API 是一个类测试工具,可以在运行时访问对象、函数和脚本中的扩展信息。可用于生成类结构的图表或文档,或者用于保存对象信息到数据库,或者拥有检查对象的访问方法(getter或setter)来提取字段名。
这里写图片描述

5.4.1 汇总类 ReflectionClass

ReflectionClass 提供揭示给定类所有信息的方法,无论这个类是用户给定的还是PHP自带的内置类。ReflectionClass 的构造方法接受类名作为它的唯一参数:

class A {}

$reflectionExample = new ReflectionClass('A');

Reflection::export($reflectionExample);

创建 ReflectionClass 对象后,就可以使用 Reflection 工具类输出 A 类的相关信息。Reflection 类有一个静态方法 export(),用于格式化和输出 Reflection 对象管理的数据(即任何实现 Reflector 接口的类的实例)。下面是调用 Reflection::export() 所生成的输出摘要:

Class [ <user> class A ] {
  @@ D:\phpStudy\WWW\demo\demo.php 3-3

  - Constants [0] {
  }

  - Static properties [0] {
  }

  - Static methods [0] {
  }

  - Properties [0] {
  }

  - Methods [0] {
  }
}

可以看到 Reflection::export() 提供了包括静态的和非静态的属性和方法,以及脚本文档的位置等信息。

将该函数与调试函数 var_dump() 相比较,var_dump() 函数是汇总数据的通用工具,但使用 var_d() 在提取摘要前必须实例化一个对象,而且它也无法提供像 Reflection::export() 提供的那么多的细节。

使用 var_dump() 函数打印信息:

var_dump($reflectionExample);

object(ReflectionClass)#1 (1) {
  ["name"]=>
  string(1) "A"
}

5.4.1 检查类

Reflection 类的相关方法可以检查类的相关信息,我们自己封装一个检查信息的类函数如下:

function classData(ReflectionClass $class) {
    $details = "";
    $name = $class->getName();
    if ($class->isUserDefined()) {
        $details .= "$name is user defined.<br>";
    }
    if ($class->isInternal()) {
        $details .= "$name is built-in.<br>";
    }
    if ($class->isInterface()) {
        $details .= "$name is user interface.<br>";
    }
    if ($class->isAbstract()) {
        $details .= "$name is an abstract class.<br>";
    }
    if ($class->isFinal()) {
        $details .= "$name is a final class.<br>";
    }
    if ($class->isInstantiable()) {
        $details .= "$name can be instantiated.<br>";
    } else {
        $details .= "$name can not be instantiated.<br>";
    }
    return $details;
}

final class A {}

$reflectionExample = new ReflectionClass('A');


print_r(classData($reflectionExample));
  • getName() 方法返回要检查的类名;
  • isUserDefined() 方法用于检查是否是用户自定义类,如果是用户自定义类,返回 true;与 isInternal() 相反。
  • isInternal() 方法用于检查是否是内置类,如果是内置类,返回 true;与 isInternal() 相反。
  • isInterface() 方法判断是否是接口,如果是接口,返回 true。
  • isAbstract() 方法用于测试一个类是否是抽象的,如果是抽象类,返回 true;
  • isFinal() 方法用于检测是否是 final 类,如果是 final 类,返回 true。
  • isInstantiable() 方法用于检测该类是否可以实例化,如果可以实例化,返回 true。

我们检查 A 这个类的相关信息,可以获取以下内容:

A is user defined.
A is a final class.
A can be instantiated.

此外我们可以检测用户自定义类的相关源代码。


class ReflectionUtil {
    static function getClassSource(ReflectionClass $class) {
        $path = $class->getFileName();
        $lines = @file($path);
        $from = $class->getStartLine();
        $to = $class->getEndLine();
        $len = $to-$from;
        return implode(array_slice($lines, $from-1, $len));
    }
}

class A {
    private $a;
    private $b;
    public function fun1() {}
    public function fun2() {}
}

print ReflectionUtil::getClassSource(new ReflectionClass('A'));

自定义一个 ReflectionUtil 的类和一个自定义的静态方法 ReflectioUtil::getClassSource()。用于接收 ReflectionClass 对象并返回相应类的源代码。

  • ReflectionClass::getFileName() 提供到类文件的绝对路径;
  • file() 函数获得由文件中所有行组成的数组;
  • ReflectionClass::getStartLine() 提供类的起始行;
  • ReflectionClass::getEndLine() 提供类的结束行;

返回的内容如下:

class A { private $a; private $b; public function fun1() {} public function fun2() {}

5.4.2 检查方法

ReflectionClass 用于检查类,ReflectionMethod 用于检查类中的方法。

ReflectionMethod 对象的获取有两种方法:

  1. 从 ReflectionClass::getMethods() 获取含有所有 ReflectionMethod 对象的数组。
  2. 从 ReflectionClass::getMethod(arg) 获取特定的方法,arg为参数,接受一个方法名字符串。

同样自己封装一个methodData() 方法:

function methodData(ReflectionMethod $method) {
    $details = "";
    $name = $method->getName();
    if ($method->isUserDefined()) {
        $details .= "$name is user defined.<br>";
    }
    if ($method->isAbstract()) {
        $details .= "$name is abstract.<br>";
    }
    if ($method->isPublic()) {
        $details .= "$name is public.<br>";
    }
    if ($method->isProtected()) {
        $details .= "$name is protected.<br>";
    }
    if ($method->isPrivate()) {
        $details .= "$name is private.<br>";
    }
    if ($method->isStatic()) {
        $details .= "$name is static.<br>";
    }
    if ($method->isFinal()) {
        $details .= "$name is final.<br>";
    }
    if ($method->isConstructor()) {
        $details .= "$name is the constructor.<br>";
    } 
    return $details;
}

这个方法将 ReflectionMethod 对象作为参数,用于检查类方法是否为用户定义的、抽象的、public、protected、private、static、final,以及是否是构造方法。

写一段测试程序:

class A {

    public function fun1() {}
    public function fun2() {}
}

$reflectionExample = new ReflectionClass('A');
$methods = $reflectionExample->getMethods();

foreach ($methods as $method) {
    print_r(methodData($method));
    echo "---------<br>";
}

这段测试程序使用 ReflectionClass 的 getMethods() 方法获取到所有的 ReflectionMethod 对象,然后循环遍历数组,传递每个对象给 methodData()。

浏览器输出:

fun1 is user defined.
fun1 is public.
---------
fun2 is user defined.
fun2 is public.
---------

同样可以获取到类方法的源代码:

class ReflectionUtil {
    static function getMethodSource(ReflectionMethod $method) {
        $path = $method->getFileName();
        $lines = @file($path);
        $from = $method->getStartLine();
        $to = $method->getEndLine();
        $len = $to-$from+1;
        return implode(array_slice($lines, $from-1, $len));
    }
}

测试程序:

class A {

    public function fun1() {
        echo "fun1";
    }
    public function fun2() {
        echo "fun2";
    }
}

$reflectionExample = new ReflectionClass('A');
$methods = $reflectionExample->getMethods();

foreach ($methods as $method) {
    print_r(ReflectionUtil::getMethodSource($method));
    echo "<br>";
}

输出:

public function fun1() { echo "fun1"; } 
public function fun2() { echo "fun2"; } 

5.4.2 检查方法参数

反射 API 提供 ReflectionParameter 类用于检查方法的参数,限制参数中对象的类型。

ReflectionMethod::getParameters() 方法返回ReflectionParameter 对象数组,ReflectionParameter 对象可以告诉你参数的名称、所属类等信息。

同样封装一个 argData() 函数,用于获取参数及其位置:

function argData(ReflectionParameter $arg) {
    $name = $arg->getName();
    $position = $arg->getPosition();
    $details = "\$$name in position $position.<br>";

    return $details;
}

测试程序:


class A {

    public function fun1($a, $b, $c) {
        echo "fun1";
    }
    public function fun2() {
        echo "fun2";
    }
}

$reflection = new ReflectionClass('A');
$method = $reflection->getMethod('fun1');
$params = $method->getParameters();
foreach ($params as $param) {
    print_r(argData($param));
}

输出结果:

$a in position 0.
$b in position 1.
$c in position 2.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值