# :-: 一、接口中的常量
* 接口常量的作用: 當作配置常量來使用
```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依賴管理工具