包和命名空间

1. 什么是包?

  • 包是一组相关类的集合,这些相关的类以某种方式组合在一起
  • 包于包之间可以将系统的部分隔离
  • PHP现在还没有包的概念

2. 命名空间

2.1 namespace 的常规使用

  • 虽然PHP中本身不支持包的概念,但是从PHP5.3开始就支持了命名空间
  • 在本类中我们可以直接 类名称::方法名称,来调用方法
  1. <?php
  2. namespace com\getinstance\util;
  3. class Debug
  4. {
  5. public static function helloWord()
  6. {
  7. print "hello from debug";
  8. }
  9. }
  10. // 本类中调用
  11. Debug::helloWord(); // hello from debug
  • 现在我们去另外一个命名空间去使用
  • 这里有好几个问题需要注意
      1. namespace 一般声明在最前面,在声明命名空间之前唯一合法的代码是用于定义源文件编码方式的 declare 语句
      1. 如果向下面代码第七行com前面没有加上反斜杠,他会认为这是一个相对路径,会从 main 下面找,所以呢一定是找不到啊。不加就是相对命名空间,加上了就是从根命名空间下搜索
  1. <?php
  2. declare(encoding='UTF-8');
  3. namespace main;
  4. require_once 'demo-1.php';
  5. \com\getinstance\util\Debug::helloWord(); // hello from debug
  • 为了使后续的代码简化,我们使用use,来告知我们将使用命名空间中的哪一部分.
  • 这里我们明确的指出了我们想要使用的是该命名空间下的 Debug 类
  1. <?php
  2. namespace main;
  3. use com\getinstance\util\Debug;
  4. require_once 'demo-1.php';
  5. Debug::helloWord(); // hello from debug
  • 如果main这个命名空间下,也包括了 Debug类呢,为了避免冲突我们可以使用as来起一个别名use com\getinstance\util\Debug as uDebug;
  1. <?php
  2. namespace main;
  3. use com\getinstance\util\Debug as uDebug;
  4. require_once 'demo-1.php';
  5. class Debug
  6. {
  7. public static function helloWord()
  8. {
  9. print "hello from local <br>";
  10. }
  11. }
  12. uDebug::helloWord(); // hello from debug
  13. Debug::helloWord(); // hello from local
  • 如果想要在命名空间下访问一个没有使用命名空间文件中的方法(也就是说这个想要访问的类保存在全局空间之中),我们仅仅需要在访问时,在类名钱添加一个反斜杠.
  • 下面的代码,列出demo3.php 和 demo.4php中的代码,如果我在demo4中不适用命名空间的话,因为demo3中和demo4中声明的类一样,会造成冲突。\加上就是访问全局空间下的类方法
  1. <?php
  2. class Listener
  3. {
  4. public static function helloWorld()
  5. {
  6. print '嘿,我来自demo-3.php <br>';
  7. }
  8. }
  1. <?php
  2. namespace com\getinstance\util;
  3. require_once 'demo-3.php';
  4. class Listener
  5. {
  6. public static function helloWorld()
  7. {
  8. print '嘿,我来自demo-4.php <br>';
  9. }
  10. }
  11. Listener::helloWorld(); // 嘿,我来自demo-4.php
  12. \Listener::helloWorld(); // 嘿,我来自demo-3.php
  • 我们可以通过 NAMESPACE 这个常量来查看此文件中的命名空间。

2.2 naspace的其他用法

  • 我们其实也可以在一个文件中使用多个命名空间,只是我们并不推荐这种做法。
    • 风格一:无特别间隔
    • 风格二:花括号间隔{}
    • 风格三:全局和含有命名空间的混用,更加恶心
    • 其实如果非要这么写,我更加喜欢风格二的代码。
  • 其实这么一看,将单个文件使用一个命名空间还是会显得更优雅,不是吗
  1. <?php
  2. // 风格1
  3. namespace MyProject;
  4. const CONNECT_OK = 1;
  5. class Connection { /* ... */ }
  6. function connect() { /* ... */ }
  7. namespace AnotherProject;
  8. const CONNECT_OK = 1;
  9. class Connection { /* ... */ }
  10. function connect() { /* ... */ }
  1. <?php
  2. // 风格2
  3. namespace MyProject {
  4. const CONNECT_OK = 1;
  5. class Connection { /* ... */ }
  6. function connect() { /* ... */ }
  7. }
  8. namespace AnotherProject {
  9. const CONNECT_OK = 1;
  10. class Connection { /* ... */ }
  11. function connect() { /* ... */ }
  12. }
  1. <?php
  2. // 风格三,混用。看起来就不爽。
  3. namespace MyProject {
  4. const CONNECT_OK = 1;
  5. class Connection { /* ... */ }
  6. function connect() { /* ... */ }
  7. }
  8. namespace { // 全局代码
  9. session_start();
  10. $a = MyProject\connect();
  11. echo MyProject\Connection::start();
  12. }

2.4 全局空间和命名空间

  • 上面说了,这么多。其实没有定义命名空间的就可以称之为全局空间

2.5 use 关键字在命名空间中的真正的含义

都写到这了,感觉还是很有必要说明一下:很多人(包括我自己)在一开始使用命名空间中的use导入其他命名空间时,发现不论怎样,最后的执行都会报错 “找不到那个类” 这时候,可能很多人就会很懵逼,我的命名空间是对的,为啥找不到,编译器也没有报错,点击方法也可以跳转(vscode),到底是哪里出错了呢?其实是我们必须还要引入这个文件 require_once ‘’; 这时候你可能和我一样心里开始骂娘了,use 这个关键字在帮你确定确定你要使用的类或者函数常量时,它只是打了一个标记,并没有自动导入文件或者类。 use 和自动导入有什么关系吗?有个毛线的的关系,use仅仅是对系统做了个标记,就像你让别人买可乐,小刚,你去帮我买瓶可乐,然后小刚去商店,“老板,一会xxx要下来买瓶快乐肥宅水”。其实你看这个事情根本就没去干。呵呵,这时候回想起,我们在php框架中貌似在导入命名空间之后,也没有去引入文件啊。之所以会有这种假象,其实是人家框架在一开始加载的时候就标记了,在你使用use后引入了,延迟自动加载。 我们只需要了解 use 仅仅是告诉系统我们将要使用命名空间中的哪一部分,使用use后并不会自动加载,这两tm根本就不是一件事情。然后会觉得这样好垃圾哦。

3. 使用文件系统模拟包

  • 最大的好处,我们无论在哪一个版本的PHP都可以达到相同的效果。
  • 我们使用 requie include requice_once include_once 来达到使用效果,几乎每个人都会
  • 区别:
    • include 和require 的不同在于它们处理错误的方式
      • require 调用文件发生错误时会停止整个程序,更为安全
      • include 则会生成警告,跳出代码然后继续执行

4. PEAR风格的命名方式

  • 这种风格的代码一般用的很少,这里只作为简单的了解。

在没有命名空间的支持下我们下我们如何解决命名冲突的问题呢?一个办法就是使用PERA包的命名规则。
image.png
其他问题不过多纠缠


自动加载

1. __autoload()

  • 注意:该特性在PHP 7.2.0中已经被弃用。非常不鼓励依赖这个特性。
  • 说明:
    • void __autoload ( string $class )
    • 作用:我们可以定义这个函数来启用类的自动加载。这是PHP5引入的__autoload()拦截器方法来自动包含文件。当PHP引擎遇到试图实例化未知类的操作时,会调用这个方法,并将类名称当做字符串参数来传递给它,这间接的也就要求我们想要引入的类文件,必须是类名称.php.
    • 参数:类名
    • 返回值:无返回值

使用例子

  1. <?php
  2. function __autoload ($className) {
  3. require_once "$className.php";
  4. }
  5. $product = new ShopProduct('The Darkening', 'Harry', 'Hunter', 12.99);

2. spl_autoload_register()

bool spl_autoload_register ([ callable $autoload_function [, bool $throw = true [, bool $prepend = false ]]] )

  • 作用说明:注册给定的函数作为autoload的实现,将函数注册到SPLautoload函数队列中,如果该队列的函数尚未激活,则激活它们。与__autoload()只可以定义一次,如果需要多条autoload函数,spl_autoload_register函数恰好就满足了我们的需求。它实际创建了autoload函数的队列,按照定义的顺序逐个执行。
  • 参数:
    • 可选参数,预注册自动装载的函数
    • 可选参数,设置无法注册成功时,是否抛出异常,默认是TRUE,抛出异常
    • 可选参数,设置添加的函数是头添加还是尾添加。默认尾添加,设置为true则会是,头添加。
  • 返回值:成功返回true,失败返回false

我先建立一个Person类如下所示,Person.php

  1. <?php
  2. // namespace March_23;
  3. class Person
  4. {
  5. private $name;
  6. private $age;
  7. function __construct($name, $age)
  8. {
  9. $this->name = $name;
  10. $this->age = $age;
  11. }
  12. public function getInfo()
  13. {
  14. print '姓名:' . $this->name . '----' . '年龄:' . $this->age;
  15. }
  16. }

同级目录下创建测试文件,testPerson.php

  1. <?php
  2. spl_autoload_register(function ($className) {
  3. require_once ("$className.php") ;
  4. });
  5. $p = new Person('向上', 24);
  6. $p->getInfo();
  • 我现在对于,匿名函数中的参数表示怀疑,我加上命名空间看看这个参数到底指向的是一个什么路径
  • 发现这个参数会先找你的命名空间,再去找你调用的未定义的类
  1. <?php
  2. namespace March_23;
  3. spl_autoload_register(function ($className) {
  4. // require_once ("$className.php") ;
  5. print($className); // March_23\Person
  6. });
  7. $p = new Person('向上', 24);
  8. $p->getInfo();

类函数和对象函数

1. 字符串动态引用类

  • PHP 允许使用字符串来动态的引用类,可能直接这么说直接就懵了。看下面的例子。

先创建 Task.php

  1. <?php
  2. class Task
  3. {
  4. function doSpeak()
  5. {
  6. print "hello <br>";
  7. }
  8. }

再去创建 TaskRunner.php, 运行下面文件中的代码.这两个文件是同级的文件。

  1. <?php
  2. $className = "Task";
  3. require_once "$className.php" ;
  4. $obj1 = new Task();
  5. $obj1->doSpeak();
  6. echo '<hr>';
  7. $obj2 = new $className();
  8. $obj2->doSpeak();

运行结果:
image.png

分析:第一个hello就表示 require_once “$className.php” ; 已经将Task.php 这个文件引入了过来,然后我们通过最最常规的方式去创建了一个Task类的对象,然后对象调用方法,打印出了 hello。然后我们后面使用的是一个字符串的变量来创建的对象 $obj2 = new $className(); 这样竟然都是可以的。这就是字符串动态引用类,不得不说的是PHP具有很大的灵活性。

  • 我们修改一下上面的两个文件,看看对命名空间的支持是否也是如出一辙的。

Task.php

  1. <?php
  2. namespace tasks;
  3. class Task
  4. {
  5. function doSpeak()
  6. {
  7. print "hello <br>";
  8. }
  9. }

TaskRunner.php

  1. <?php
  2. // [1] 不使用use关键字只是最基本的使用
  3. require_once "Task.php" ;
  4. $obj1 = new tasks\Task();
  5. $obj1->doSpeak();
  1. <?php
  2. // [2] 使用use后
  3. use tasks\Task;
  4. require_once "Task.php" ;
  5. $obj1 = new Task();
  6. $obj1->doSpeak();
  1. <?php
  2. // 第[1]种情况的变形
  3. require_once "Task.php" ;
  4. $str = "tasks\Task";
  5. $obj1 = new $str();
  6. $obj1->doSpeak();
  1. <?php
  2. // 第[2]种情况的变形
  3. use tasks\Task;
  4. require_once "Task.php" ;
  5. $str = "Task";
  6. $obj1 = new $str();
  7. $obj1->doSpeak();
  8. /*
  9. 上面的前三种情况都没有问题,只有这种情况会报错,找不到Task类,除非 $str = "tasks\Task";
  10. 呵呵,很多时候事情想得并不如我们预想的一样。说实话PHP的这个特性并还是让我感到失望的。
  11. */

2. 检查类

简单说明

  • class_exists()一用于检查类是否存在。
  • bool class_exists ( string $class_name [, bool $autoload = true ] )
    • 第一个参数是传入的类名称,第二个可选参数是是否启用自动加载

PHP中类似的方法还有很多,都可以保证我们代码的健壮性,如:

  1. 检查文件是否存在:bool file_exists ( string $filename )
  2. 检查类是否存在:bool class_exists ( string $class_name [, bool $autoload = true ] )
  3. 检查方法是否存在:bool function_exists ( string $function_name )
  4. 检查变量是否已经设置,并且不为空:bool isset ( mixed $var [, mixed $... ] )

文件路径补充

  • 获取当前文件所在的绝对路径
    • FILE
    • 如:D:\wamp64\www\numb\2020-3-24\test.php


  • 获取当前文件的名称
    • basename(FILE)
    • 如:test.php


  • 获取当前文件的目录





  • 返回当前的工作目录

我们可以利用上面的知识点,获取PHP中项目的名称

  1. <?php
  2. // 获取项目名称
  3. function getProgectName ()
  4. {
  5. $str = $_SERVER['PHP_SELF']; // '/numb/2020-3-25/demo-01.php'
  6. $staratIndex = strpos($str, '/') + 1;
  7. $length = strpos($str, '/', $staratIndex) - $staratIndex;
  8. $tarStr = substr($str, $staratIndex, $length);
  9. return $tarStr;
  10. }
  11. echo getProgectName() . '<br>'; // numb

我们很多时候可以用 get_class(),来判断这个对象的类名称,但更多的时候用到的是 instanceof 运算符