包和命名空间
1. 什么是包?
- 包是一组相关类的集合,这些相关的类以某种方式组合在一起
- 包于包之间可以将系统的部分隔离
- PHP现在还没有包的概念
2. 命名空间
2.1 namespace 的常规使用
- 虽然PHP中本身不支持包的概念,但是从PHP5.3开始就支持了命名空间
- 在本类中我们可以直接 类名称::方法名称,来调用方法
<?php
namespace com\getinstance\util;
class Debug
{
public static function helloWord()
{
print "hello from debug";
}
}
// 本类中调用
Debug::helloWord(); // hello from debug
- 现在我们去另外一个命名空间去使用
- 这里有好几个问题需要注意
- namespace 一般声明在最前面,在声明命名空间之前唯一合法的代码是用于定义源文件编码方式的 declare 语句
- 如果向下面代码第七行com前面没有加上反斜杠,他会认为这是一个相对路径,会从 main 下面找,所以呢一定是找不到啊。不加就是相对命名空间,加上了就是从根命名空间下搜索
<?php
declare(encoding='UTF-8');
namespace main;
require_once 'demo-1.php';
\com\getinstance\util\Debug::helloWord(); // hello from debug
- 为了使后续的代码简化,我们使用use,来告知我们将使用命名空间中的哪一部分.
- 这里我们明确的指出了我们想要使用的是该命名空间下的 Debug 类
<?php
namespace main;
use com\getinstance\util\Debug;
require_once 'demo-1.php';
Debug::helloWord(); // hello from debug
- 如果main这个命名空间下,也包括了 Debug类呢,为了避免冲突我们可以使用as来起一个别名use com\getinstance\util\Debug as uDebug;
<?php
namespace main;
use com\getinstance\util\Debug as uDebug;
require_once 'demo-1.php';
class Debug
{
public static function helloWord()
{
print "hello from local <br>";
}
}
uDebug::helloWord(); // hello from debug
Debug::helloWord(); // hello from local
- 如果想要在命名空间下访问一个没有使用命名空间文件中的方法(也就是说这个想要访问的类保存在全局空间之中),我们仅仅需要在访问时,在类名钱添加一个反斜杠.
- 下面的代码,列出demo3.php 和 demo.4php中的代码,如果我在demo4中不适用命名空间的话,因为demo3中和demo4中声明的类一样,会造成冲突。\加上就是访问全局空间下的类方法
<?php
class Listener
{
public static function helloWorld()
{
print '嘿,我来自demo-3.php <br>';
}
}
<?php
namespace com\getinstance\util;
require_once 'demo-3.php';
class Listener
{
public static function helloWorld()
{
print '嘿,我来自demo-4.php <br>';
}
}
Listener::helloWorld(); // 嘿,我来自demo-4.php
\Listener::helloWorld(); // 嘿,我来自demo-3.php
- 我们可以通过 NAMESPACE 这个常量来查看此文件中的命名空间。
2.2 naspace的其他用法
- 我们其实也可以在一个文件中使用多个命名空间,只是我们并不推荐这种做法。
- 风格一:无特别间隔
- 风格二:花括号间隔{}
- 风格三:全局和含有命名空间的混用,更加恶心
- 其实如果非要这么写,我更加喜欢风格二的代码。
- 其实这么一看,将单个文件使用一个命名空间还是会显得更优雅,不是吗
<?php
// 风格1
namespace MyProject;
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
namespace AnotherProject;
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
<?php
// 风格2
namespace MyProject {
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
}
namespace AnotherProject {
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
}
<?php
// 风格三,混用。看起来就不爽。
namespace MyProject {
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
}
namespace { // 全局代码
session_start();
$a = MyProject\connect();
echo MyProject\Connection::start();
}
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包的命名规则。
其他问题不过多纠缠
自动加载
1. __autoload()
- 注意:该特性在PHP 7.2.0中已经被弃用。非常不鼓励依赖这个特性。
- 说明:
- void __autoload ( string
$class
)- 作用:我们可以定义这个函数来启用类的自动加载。这是PHP5引入的__autoload()拦截器方法来自动包含文件。当PHP引擎遇到试图实例化未知类的操作时,会调用这个方法,并将类名称当做字符串参数来传递给它,这间接的也就要求我们想要引入的类文件,必须是类名称.php.
- 参数:类名
- 返回值:无返回值
使用例子
<?php
function __autoload ($className) {
require_once "$className.php";
}
$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
<?php
// namespace March_23;
class Person
{
private $name;
private $age;
function __construct($name, $age)
{
$this->name = $name;
$this->age = $age;
}
public function getInfo()
{
print '姓名:' . $this->name . '----' . '年龄:' . $this->age;
}
}
同级目录下创建测试文件,testPerson.php
<?php
spl_autoload_register(function ($className) {
require_once ("$className.php") ;
});
$p = new Person('向上', 24);
$p->getInfo();
- 我现在对于,匿名函数中的参数表示怀疑,我加上命名空间看看这个参数到底指向的是一个什么路径
- 发现这个参数会先找你的命名空间,再去找你调用的未定义的类
<?php
namespace March_23;
spl_autoload_register(function ($className) {
// require_once ("$className.php") ;
print($className); // March_23\Person
});
$p = new Person('向上', 24);
$p->getInfo();
类函数和对象函数
1. 字符串动态引用类
- PHP 允许使用字符串来动态的引用类,可能直接这么说直接就懵了。看下面的例子。
先创建 Task.php
<?php
class Task
{
function doSpeak()
{
print "hello <br>";
}
}
再去创建 TaskRunner.php, 运行下面文件中的代码.这两个文件是同级的文件。
<?php
$className = "Task";
require_once "$className.php" ;
$obj1 = new Task();
$obj1->doSpeak();
echo '<hr>';
$obj2 = new $className();
$obj2->doSpeak();
运行结果:
分析:第一个hello就表示 require_once “$className.php” ; 已经将Task.php 这个文件引入了过来,然后我们通过最最常规的方式去创建了一个Task类的对象,然后对象调用方法,打印出了 hello。然后我们后面使用的是一个字符串的变量来创建的对象 $obj2 = new $className(); 这样竟然都是可以的。这就是字符串动态引用类,不得不说的是PHP具有很大的灵活性。
- 我们修改一下上面的两个文件,看看对命名空间的支持是否也是如出一辙的。
Task.php
<?php
namespace tasks;
class Task
{
function doSpeak()
{
print "hello <br>";
}
}
TaskRunner.php
<?php
// [1] 不使用use关键字只是最基本的使用
require_once "Task.php" ;
$obj1 = new tasks\Task();
$obj1->doSpeak();
<?php
// [2] 使用use后
use tasks\Task;
require_once "Task.php" ;
$obj1 = new Task();
$obj1->doSpeak();
<?php
// 第[1]种情况的变形
require_once "Task.php" ;
$str = "tasks\Task";
$obj1 = new $str();
$obj1->doSpeak();
<?php
// 第[2]种情况的变形
use tasks\Task;
require_once "Task.php" ;
$str = "Task";
$obj1 = new $str();
$obj1->doSpeak();
/*
上面的前三种情况都没有问题,只有这种情况会报错,找不到Task类,除非 $str = "tasks\Task";
呵呵,很多时候事情想得并不如我们预想的一样。说实话PHP的这个特性并还是让我感到失望的。
*/
2. 检查类
简单说明
- class_exists()一用于检查类是否存在。
- bool class_exists ( string
$class_name
[, bool$autoload
= true ] )
- 第一个参数是传入的类名称,第二个可选参数是是否启用自动加载
PHP中类似的方法还有很多,都可以保证我们代码的健壮性,如:
- 检查文件是否存在:bool file_exists ( string
$filename
) - 检查类是否存在:bool class_exists ( string
$class_name
[, bool$autoload
= true ] ) - 检查方法是否存在:bool function_exists ( string
$function_name
) - 检查变量是否已经设置,并且不为空:bool isset ( mixed
$var
[, mixed$...
] )
文件路径补充
- 获取当前文件所在的绝对路径
- FILE
- 如:D:\wamp64\www\numb\2020-3-24\test.php
- 获取当前文件的名称
- basename(FILE)
- 如:test.php
- 获取当前文件的目录
- DIR
- dirname(FILE)
- 如:D:\wamp64\www\numb\2020-3-24
- 获取当前脚本的文件名称
- $_SERVER[‘PHP_SELF’]
- $_SERVER[‘SCRIPT_NAME’]
- 如:/numb/2020-3-24/test.php
- 获取当前脚本的绝对路径
- $_SERVER[‘SCRIPT_FILENAME’]
- D:/wamp64/www/numb/2020-3-24/test.php
- 获取当前运行脚本所在的文档根目录
- $_SERVER[‘DOCUMENT_ROOT’]
- D:/wamp64/www
- 返回当前的工作目录
- getcwd()
- D:\wamp64\www\numb\2020-3-24
我们可以利用上面的知识点,获取PHP中项目的名称
<?php
// 获取项目名称
function getProgectName ()
{
$str = $_SERVER['PHP_SELF']; // '/numb/2020-3-25/demo-01.php'
$staratIndex = strpos($str, '/') + 1;
$length = strpos($str, '/', $staratIndex) - $staratIndex;
$tarStr = substr($str, $staratIndex, $length);
return $tarStr;
}
echo getProgectName() . '<br>'; // numb
我们很多时候可以用 get_class(),来判断这个对象的类名称,但更多的时候用到的是 instanceof 运算符