# :-: 一、接口中的常量
* 接口常量的作用: 当作配置常量来使用
```php
namespace admin;
if (!interface_exists(__NAMESPACE__.'\iDbParam')) {
interface iDbParam{
const TYPE = 'mysql';
const HOST = 'localhost';
const USER_NAME = 'root';
const PASSWORD = 'root';
const DBNAME = 'php';
public static function connection ();
}
}
class Connection implements namespace\iDbParam{
// 初始化连接参数
private static $type = iDbParam::TYPE;
private static $host = iDbParam::HOST;
private static $userName = iDbParam::USER_NAME;
private static $password = iDbParam::PASSWORD;
private static $dbname = iDbParam::DBNAME;
// 实现接口中的抽象方法: connection
public static function connection(){
$dsn = self::$type.':host='.self::$host.';dbname='.self::$dbname;
$user = self::$userName;
$password = self::$password;
$pdo = new \PDO($dsn,$user,$password);
return $pdo;
}
}
// 以后连接数据库只需要这个静态方法即可, 注意命名空间
$link = Connection::connection();
// 执行一个查询进行测试
$stmt = $link->prepare('SELECT * FROM `user` LIMIT :limit');
$stmt->bindValue('limit', 5, \PDO::PARAM_INT);
$stmt->execute();
//die($stmt->debugDumpParams());
$users = $stmt->fetchAll(\PDO::FETCH_ASSOC);
// 遍历结果集
foreach ($users as $user) {
// date(时间格式,时间戳): 将时间戳转为指定格式的日期时间字符串
$last_time = date('Y/m/d',$user['last_time']);
echo "<li>{$user['uid']}-{$user['name']}-{$user['phone']}-{$last_time}</li>";
}
```
>[info] 以后只要数据库发生了变化, 我们只需要改一下连接接口参数就可以, 项目代码不必做任何改动
*****
# :-: 二、后期静态绑定
* 后期静态绑定,也叫"延迟静态绑定"
* 这个技术应用在静态继承的上下文环境中,用于动态调用被重写的方法
* 调用被重写的静态方法使用关键字: `static` 加上"范围解析符"`::`
* `::` 范围解析符的使用场景
* 1. 访问类方法与类常量
* 2. 访问被重写的对象或类方法
```php
# 为了简化代码,直接引用php官网上的例子:
# http://cn2.php.net/manual/zh/language.oop5.late-static-bindings.php
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
// self::who();
// 那么如何在这种静态继承的上下文环境中, 静态调用类中方法的时候,正确识别调用者呢?
// 可以将self 关键字改为: static ,
// 注意: static 除了可以用在静态方法中, 也可以用在普通对象方法中
static::who();
}
}
class B extends A {
// 在子类中重写了父类A中的静态方法who()
public static function who() {
echo __CLASS__;
}
}
B::test();
echo '<hr>';
```
>[info] 总结: static关键字用来调用重写方法的时候,可以动态的绑定当前调用的类
* 这种绑定是在运行阶段发生, 而不是代码编写的词法分析阶段(静态的代码文本),所以要后期延迟静态绑定
* 这种延迟绑定,有什么卵用呢? 用处非常大,特别是在现代的PHP框架开中, 随处可见
* 下面举一个简单的小案例,来看一下它的使用场景
```php
// 以最简单的数据库连接,来演示延迟静态绑定的应用
class Connect{
public static function connect(){
// self::调用是当前Connec类中的config方法,而并非在子类被重写的config方法
// 因为用户在子类中重新定义了连接参数, 所以查询会失败
// return self::config();
// 使用static:: 根据调用者,动态的调用被重写的方法,这样就可以调用到被重写的方法了
return static::config();
}
public static function config(){
return new \PDO('mysql:dbname=ouyangke','root', '123456');
}
}
class Link extends Connect{
public static function config(){
return new \PDO('mysql:dbname=php','root', 'root');
}
}
$pdo = Link::connect();
//var_dump($pdo instanceof PDO);
$users = $pdo->query('select * from user limit 5');
foreach ($users as $user) {
print_r($user); echo '<br>';
}
```
>[info] 总结: user::, 可以在静态继承的上下文环境中, 调用被子类重写的静态方法(大家可以试试能否调用被重写的普通方法)
*****
# :-: 三、命名空间的层级关系
* 命名空间是可以分层管理的,也叫子命名空间
* __NAMESPACE__: 双下划线开头的魔术常量, 所谓魔术是指,尽管是常量,但它的值可以随作用域发生变化
```php
namespace admin;
echo '当前命名空间是: ' . __NAMESPACE__ . '<br>';
class Dog {}
echo Dog::class . '<hr>';
namespace admin\one;
echo '当前命名空间是: ' . __NAMESPACE__ . '<br>';
class Dog {}
echo Dog::class . '<br>';
// 关键字: namespace: 可以显示的访问当前空间或子空间中的元素
echo namespace\Dog::class, '<br>';
// 如果我想访问空间:admin\one\two类
// 可以将当前空间看成当前目录,用关键字namespace来引用当前空间
// 当前命名空间是: admin\one\, 所以从two开始就可以找到指定的类
echo namespace\two\Dog::class . '<hr>';
namespace admin\one\two;
echo '当前命名空间是: ' . __NAMESPACE__ . '<br>';
class Dog {}
echo Dog::class . '<hr>';
```
* "\\"是命名空间分隔符, 将空间分层有什么卵用呢?
* 作用非常大, 现代PHP编程中的类的自动加载技术就靠它撑着呢,框架没有它, 难以想像
* 多层级的命名空间,非常像多层级的目录结构,如果类名称中的空间部分与类文件的绝对路径一致,就可以实现
* 类文件的全自动加载,并且不会千万命名冲突,因为类名本身仍是带有命名空间的
*****
# :-: 四、使用空间别名简化命名空间
```php
namespace admin;
# 允许通过别名引用或导入外部的完全限定名称
# 当类带有空间或子空间时, 类名称有可能会变得很长,可以使用空间别名导入来简化类名
// 例如, 当前脚本需要加载'demo4.php'中的: admin\one\two\Dog类
include __DIR__ . '/Test.php';
# 检测 Dog类是否导入成功
$className = namespace\one\two\three\Test::class;
# 如果类存在,则调用类中的静态方法demo()
echo class_exists($className) ? $className::demo().' 类存在' : '类不存在';
// 大家应该注意到了, 这个类名非常的长, 不仅书写起来非常的不方便, 而且引用时候, 也容易出错,代码也臃肿
// 特别是当前脚本,如果需要在多处引用这个类的时候, 不得不每次都写一遍这么长的类名, 太麻烦了
// 解决方案: 给当前类起一个简短的别名, 起别名使用的关键字是: use
// 下面使用别名: "T" 代替之前冗长的类名称
// use 默认是从全局空间开始查找类,不得使用当前空间的引用关键字namespace
// 可以在use 的第一个空间名称前加上全局空间标识符: \, 尽管你根本不需要这样去做
use admin\one\two\three\Test as T; // 允许用 Test 代替 完整的Test1类名
// 现在起, 在当前脚本中, 我就可以直接用Test1 来取代之前的: admin\one\two\three\Test
echo class_exists(T::class) ? T::demo().' 类存在' : '类不存在';
// 别名允许与原始类名相同
//use admin\one\two\three\Test as Test;
// 如果类的别名, 与原始类名相同, 例如本例, 都是 Test, 允许省略后面的 as 部分
use admin\one\two\three\Test;
echo class_exists(Test::class) ? Test::demo().' 类存在' : '类不存在';
// 那么什么时候使用 as 来定义 别名呢?
// 当前脚本中导入的空间别名冲突的时候使用
use o\p\q\Demo;
use x\y\z\Demo as Hell;
```
> Tast.php文件
```php
namespace admin\one\two\three;
class Test{
public static function demo(){
return __METHOD__;
}
}
class Test2{
public static function demo(){
return __METHOD__;
}
}
```
*****
# :-: 五、命名空间的实战小案例
```php
// 命名空间实战
namespace admin;
// 导入全局空间中的PDO类
use PDO;
// 连接数据库: 目前在空间中写代码, PDO类中全局中, 建议加上"\",或者在前面用use导入类的别名
// 将连接参数放在接口常量中
interface iDbParams{
const DSN = 'mysql:host=localhost;dbname=ouyangke';
const URSE = 'root';
const PASS = 'root';
}
$pdo = new PDO(iDbParams::DSN,iDbParams::URSE,iDbParams::PASS);
//查询点数据展示出来,以测试数据库操作正确
// :num, :offset, 这里必须是整数类型, 而SQL语句默认都是字符串, 需要进行类型限定
$sql = 'SELECT `uid`,`name`,`phone` FROM `user` LIMIT :num OFFSET :offset';
$stmt = $pdo->prepare($sql);
$stmt->bindValue('num',5, PDO::PARAM_INT);
$stmt->bindValue('offset',0, PDO::PARAM_INT);
$stmt->execute();
// 遍历结果集
foreach ($stmt->fetchAll() as $user) {
echo "<p>{$user['uid']} -- {$user['name']} -- {$user['phone']}</p>";
}
exit;
```
*****
# :-: 六、Trait 技术
* 为什么要用 Trait?
* php是单继承的语言, 即一个类只允许从一个父类中继承成员
* trait是一个与"类"类似的数据结构,内部可以声明一些方法或属性,供调用者使用
* Trait 解析了什么问题?
* 解决php只能从一个类中继承成员的问题
* 最大程度的实现了代码复用
* 对一些无法用类进行封装的功能,使用Trait封装更加的方便,实用
* trait 的创建语句与class类是完全一样的
* trait 使用 trait 关键字来声明, 同样, 也不允许实例化,只能是调用类调用
```php
namespace admin;
use PDO;
trait Db{
// 连接数据库
public function connect($dsn, $username, $password){
return new \PDO($dsn, $username, $password);
}
}
trait Query{
// 查询满足条件的第一条记录
public function get($pdo, $where = ''){
// 处理查询条件
$where = empty($where) ? '' : ' WHERE '.$where;
// 拼装SQL语句, 创建预处理对象PDOStatment
$stmt = $pdo->prepare('SELECT * FROM `user` ' . $where . ' LIMIT 1');
$stmt->execute();
// 生成的SQL语句: SELECT * FROM `user` WHERE age < 30 LIMIT 1
// die($stmt->debugDumpParams());
// 将获取的结果集以一维数组的形式返回给调用者
return $stmt->fetch(PDO::FETCH_ASSOC);
}
// 其它方法,例如 insert, update, delete, select , 大家可自行扩展
}
// 客户端调用类
class Client{
// 在宿主类中引入上面声明的二个Trait方法库
use Db;
use Query;
// 也可以写到一行引入, 推荐分行引入, 便于写注释和代码调试
// use Db, Query;
// 调用类会有多个方法要用到数据库连接对象,所以应该将它声明一个独立于方法的共享属性
public $pdo = null;
// 调用类实例化, 应该实现自动连接数据库的功能, 这里调用的Trait类中的connect()
// 当前Trait类Db已经导入了, 所以它里面声明的connect()方法, 就像是Client类自己的方法一样,直接用$this调用
public function __construct($dsn, $username, $password){
$this->pdo = $this->connect($dsn, $username, $password);
}
// 调用类的find()方法, 实现查询满足条件的第一条记录的功能
// find()方法是调用 Trait类Query中的get()方法实现
public function find($where){
return $this->get($this->pdo, $where);
}
}
// 客户端通过客户类的实例化,来调用Trait中的方法
// 设置数据库连接参数
$dsn = 'mysql:host=localhost;dbname=ouyangke';
$username = 'root';
$password = 'root';
// 实例化并连接数据库, 底层是调用 Trait类Db::connect()
$client = new Client($dsn, $username, $password);
// 获取满足条件的第一条记录, 底层是调用 Trait类Query::get()
echo '<pre>'.print_r($client->find('age < 30'), true);
```
- 序言
- PHP基础
- 认识PHP
- 环境安装
- PHP语法
- 流程控制
- PHP数组
- PHP函数
- PHP类与对象
- PHP命名空间
- PHP7新特性
- PHP方法库
- PHP交互
- 前后端交互
- 项目常规开发流程
- MySQL数据库
- 会话控制
- Ajax分页技术
- 细说函数
- 类与对象
- 对象进阶
- 类与对象进阶
- OOP面向对象
- 设计模式
- 路由与模板引擎
- 异常类
- PHP爬虫
- PHP抓取函数
- PHP匹配函数
- 正则表达式
- PHP字符串函数
- 抓取实战
- PHP接口
- 了解接口
- PHP插件
- PHPSpreadsheet
- ThinkPHP6
- 安装
- 架构
- 数据库
- 数据库操作
- 视图
- 模版
- 模型
- 杂项
- 命令行
- 交互
- 微信小程序
- 介绍
- 配置
- 组件
- 交互
- API
- 其他知识
- 百度小程序
- 介绍
- 配置
- 组件
- 交互
- API
- 其他知识
- Linux
- 服务器上线流程
- 安装svn
- MySQL
- 认识MySQL
- MySQL函数
- 杂项
- composer依赖管理工具