* 關鍵詞:類,類實例,對象,對象檢測
* 類的三大特性:
* 封裝:把對象的屬性和方法組織在一個類(邏輯單元)里
* 繼承:以原有的類為基礎,創建一個新類,從而代碼復用的目的
* 多態:允許將子類類型的指針賦值給父類類型的指針
> 需要的數據庫
```php
CREATE TABLE `user` (
`uid` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '用戶ID',
`name` varchar(50) NOT NULL COMMENT '姓名',
`age` smallint(3) unsigned NOT NULL COMMENT '年齡',
PRIMARY KEY (`uid`)
) ENGINE=MyISAM AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
INSERT INTO `user` VALUES ('1', '歐陽克', '18');
INSERT INTO `user` VALUES ('2', '老頑童', '50');
INSERT INTO `user` VALUES ('3', '黃蓉', '33');
INSERT INTO `user` VALUES ('4', '郭靖', '18');
INSERT INTO `user` VALUES ('5', '一燈大師', '80');
INSERT INTO `user` VALUES ('6', '洪七公', '79');
```
*****
### :-: 一、創建類
* 類不好理解,因為概念太抽象。
* 先說一個比較抽象的,比如動物是一個類,而兔子、豬、貓狗,都是動物類的實例化對象。
* 類就是一個分類,一個清單。這個類有什么,通過這個類,創建出對象。
* 對象不是憑空出來的,都是通過類創建出來的。所以咱們是先寫類,在創建對象。
```php
# 創建類
class Animal{
}
# 調用類(實例化)
$monkey = new Animal(); // 猴子
$rabbit = new Animal(); // 兔子
```
>[danger] 備:類如果只能使用一次,那我們沒必須用類,每次直接寫代碼就可以了。所以類可以實例化多次(N次),次數無限制。
* 同一個類實例化出來,它們是不一樣的
```php
var_dump($monkey == $rabbit);
echo '<br>';
var_dump($monkey === $rabbit);
echo '<br>';
```
* 檢測對象是否是類的實例
```php
var_dump($monkey instanceof Animal);
echo '<br>';
```
*****
### :-: 二、類屬性
* 關鍵詞: 類屬性, 訪問限制符, 動態屬性
>[info] 在類里直接寫代碼,是錯誤的
```php
# 錯誤示例
class Animal{
echo 111;
}
```
* 類 是一個類型的集合,它里面有成員的。
* 類里成員有兩種:屬性(變量)和行為(方法)。任何類,都有屬性和行為。
#### 1、**屬性(變量)**
```php
class People{
$name = '楊冪'; //會報錯,必須有修飾符
$age = 31; //會報錯,必須有修飾符
// 屬性 設置了初始值
public $name = '楊冪';
public $age = 31;
}
# 外部訪問:需要通過訪問限定符、或修飾符
$yangmi = new People;
echo $yangmi->name;
```
* 在類中聲明屬性變量時,設置它的作用域
* 作用域(修飾符):目前我們先學習public
* public 公有的,類內,類外,子類都是可訪問的
* protected 受保護的,類內,子類都可以訪問
* private 私有的,只能類內訪問
>[info] 屬性重新賦值
```php
$yangmi->name = '歐陽克';
$yangmi->age = 18;
echo $yangmi->name.$yangmi->age;
```
#### 2、**行為(方法)**
* 關鍵詞: self, $this
* 每個方法里,都帶有$this,用作調用自己類的屬性和方法
```php
class People{
// 屬性
public $name = '楊冪';
public $age = 31;
// 方法,默認就是public ,不加也是
public function getInfo(){
echo '姓名:楊冪,年齡:31';
return '姓名:楊冪,年齡:31';
}
// 方法
public function getInfo1(){
// self : 當前類
$obj = new self();
// 輸出對象屬性
return '姓名: ' .$obj->name .', 年齡: ' . $obj->age . '<br>';
}
// 方法
public function getInfo2(){
// 因為該方法必須通過對象調用,所有沒必要在類中實例化
// 直接引用該類的實例化對象即可
// 在類中使用偽變量: "$this" 引用當前類的實例
// $this = new self(); 相當于先執行了這條語句,盡管你不需要這樣做
return '姓名: ' .$this->name .', 年齡: ' . $this->age . '<br>';
}
// 方法
public function getInfo3(){
// 當前類
$obj = new People();
$obj->name = '歐陽克';
$obj->age = 18;
// 輸出對象屬性
return '姓名: ' .$obj->name .', 年齡: ' . $obj->age . '<br>';
}
}
// 類實例化
$yangmi = new People();
echo $yangmi->getInfo();
echo $yangmi->getInfo1();
echo $yangmi->getInfo2();
echo $yangmi->getInfo3();
// 查看類中定義的對象方法: public 才會顯示出來
$methods = get_class_methods('People');
echo '<pre>'.print_r($methods,true);
echo '<hr>';
```
*****
### :-: 三、構造方法(魔術方法)
* 構造方法是類中的特殊方法,在類實例化時會被自動調用,可用來初始化對象成員
* 調用類的時候,立即執行構造方法,第一個執行的方法。方法的沒有位置的先后順序
* 構造方法: `public function __construct(){...}` ,也可以跟類名一樣的方法
```php
class People{
// 屬性
public $name;
public $age;
// 構造方法
public function __construct($name, $age){
echo '開始執行';
$this->name = $name;
$this->age = $age;
}
// 方法
public function getInfo(){
return '姓名: ' .$this->name .', 年齡: ' . $this->age . '<br>';
}
}
// 實例化
$obj = new People('楊冪',31);
echo $obj->getInfo();
```
*****
### :-: 四、析構方法(魔術方法)
* 析構方法是類中的特殊方法,在類執行完前,自動調用。可以釋放,關閉一些資源
* 構造方法: `public function __destruct(){...}` ,也可以跟類名一樣的方法
```php
class People{
// 屬性
public $name;
public $age;
// 構造方法
public function __construct($name, $age){
echo '開始執行';
$this->name = $name;
$this->age = $age;
}
// 方法
public function getInfo(){
return '姓名: ' .$this->name .', 年齡: ' . $this->age . '<br>';
}
// 析構方法
public function __destruct(){
echo '類執行完畢,要關閉了';
}
}
// 實例化
$obj = new People('楊冪',31);
echo $obj->getInfo();
$obj = null; // 如果沒有手動釋放,就會在最后執行析構方法
```
>[info] 實戰:自動連接數據庫
```php
class Db{
// 連接參數
public $dsn;
public $user;
public $password;
// 連接屬性
public $pdo;
// 連接方法
public function connect(){
// 使用PDO方式管理數據庫, 連接成功則返回PDO對象,賦值給對象屬性pdo
$this->pdo = new PDO($this->dsn, $this->user, $this->password);
}
// 希望在實例化時, 自動連接數據庫, 這個需求很常見
public function __construct($dsn, $user, $password){
// 初始化對象屬性
$this->dsn = $dsn;
$this->user = $user;
$this->password = $password;
// 自動調用對象方法,連接數據庫
$this->connect();
}
// 析構方法
public function __destruct(){
$this->pdo = null;
}
}
// 實例化
$db = new Db('mysql:host=localhost;dbname=ouyangke', 'root', 'root');
if ($db->pdo) {
echo '<h2>連接成功</h2>';
}
// 讀取數據庫測試
$stmt = $db->pdo->prepare('select * from user');
$stmt->execute();
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $user) {
print_r($user); echo '<br>';
}
```
*****
### :-: 五、類的繼承:類的第一特點
* 功能擴展, 方法重寫
* 繼承只能單集成,不能繼承多個父類
* 繼承方便擴展,維護
```php
class People{
// 對象屬性
public $name;
public $age;
// 構造方法
public function __construct($name, $age){
$this->name = $name;
$this->age = $age;
}
// 方法
public function getInfo(){
return '姓名: ' .$this->name .', 年齡: ' . $this->age;
}
}
// 子類Sub1, 代碼復用
class Woman extends People{
// ...
}
// 實例化子類Woman, 盡管子類中無任何成員,但是它可以直接調用父類People中的全部成員
$sub1 = new Woman('楊冪', 31);
echo $sub1->getInfo() . '<br>';
```
>[info] 實現子類里的方法
```php
// 子類Woman, 增加屬性和方法,擴展父類功能
class Woman extends People{
public $wages; // 工資
// 子類的構造方法
public function __construct($name, $age, $wages){
// 調用父類的構造方法,否則還要手工把父類中的屬性初始化語句全部寫一遍
// parent:: 調用被覆寫的父類方法內容
parent::__construct($name, $age);
// 只需要添加子類中的成員初始化代碼
$this->wages = $wages;
}
// 計算一年工資
public function total(){
return $this->wages*12;
}
}
// 實例化子類
$sub2 = new Woman('楊冪',31,500000);
echo $sub2->name . '的年薪: ' . $sub2->total() . '<br>';
```
>[info] 子類重寫父類方法
```php
// 如果父類有這個方法,子類也用了這個方法
// 第三個子類, 繼承自Woman, 而Star又繼承自People,這就形成了多層給的繼承關系
class Star extends Woman{
// 重寫父類total()方法
public function total(){
$total = parent::total();
// 判斷工資單位
switch ($total) {
case $total>100000000:
$total = ($total/10000).'億';
break;
case $total>10000:
$total = ($total/10000).'萬';
break;
default:
$total = $total;
break;
}
return $total;
}
}
// 實例化子類
$sub3 = new Star('楊冪',31,500000);
echo $sub3->name . '的年薪: ' . $sub3->total() . '<br>';
echo '<hr>';
```
* 備注:this,parent關鍵字
* this是指向當前對象,也包括繼承的。但是會有相同的方法名,this是先找本類,在上父類
* parent是指向父類,重寫時,使用到。或者有父類和子類,有相同方法時,才使用。
*****
### :-: 六、類的封裝:類的第二特點
* 訪問控制符, public, protected, private
* public: 默認, 類內,類外,子類都可見
* protected: 類內, 子類可見, 類外不可見
* private: 類內可見, 子類, 類外不可見
>[info] 成員(變量)的封裝
```php
class Woman{
// 屬性
public $name; // 姓名
protected $age; // 年齡
private $wages; // 工資
// 構造方法
public function __construct($name, $age, $wages){
$this->name = $name;
$this->age = $age;
$this->wages = $wages;
}
}
// 類實例化
$obj = new Woman('楊冪',31,500000);
echo $obj->name, '<br>';
// echo $obj->age, '<br>'; // 會報錯
// echo $obj->wages, '<br>'; // 會報錯
// 繼承后訪問
class Star extends Woman{
public function info(){
echo $this->name, '<br>';
echo $this->age, '<br>';
// echo $this->wages, '<br>'; // 會報錯
}
}
// 類實例化
$obj1 = new Star('baby',28,400000);
echo $obj1->name, '<br>';
// echo $obj->age, '<br>'; // 會報錯
// echo $obj->wages, '<br>'; // 會報錯
echo $obj1->info();
echo '<hr>';
```
>[info] 行為(方法)的封裝
```php
class Woman{
// 屬性
public $name; // 姓名
protected $age; // 年齡
private $wages; // 工資
// 構造方法
public function __construct($name, $age, $wages){
$this->name = $name;
$this->age = $age;
$this->wages = $wages;
}
public function name(){
return '我的名字叫:'.$this->name.'<br>';
}
protected function age(){
return '我的年齡是:'.$this->age.'<br>';
}
private function wages(){
return '我的工資是:'.$this->wages.'<br>';
}
public function all(){
echo $this->name();
echo $this->age();
echo $this->wages();
}
}
// 類實例化
$obj = new Woman('楊冪',31,500000);
echo $obj->name();
//echo $obj->age(); // 會報錯
//echo $obj->wages(); // 會報錯
echo $obj->all();
class Star extends Woman{
public function info(){
echo $this->name();
echo $this->age();
// echo $this->wages(); //私有的會報錯
}
public function a(){
echo $this->all();
}
}
// 類實例化
$obj1 = new Star('baby',28,400000);
echo $obj1->a();
echo '<hr>';
```
*****
* 類的文件起名:
* 有描述意義,比如:女人woman
* 用二級命名,比如:女人woman.class.php, 二級命名是告訴程序員,不直接運行的文件。
* 一般一個文件,只保存一個類,所以不會在類的文件里,調用本類。
* 都是在外部調用。新建個文件,調用這個類。include '類文件'
* include 'woman.class.php'
*****
### :-: 七、類屬性與類方法(靜態成員)
* 類屬性: 靜態屬性
* 類方法: 靜態方法
* 靜態成員屬于類,而不屬于對象(重點)
* 靜態成員不需要通過對象訪問,所以不必實例化
* 使用`static`關鍵字定義
* 類外使用類名訪問,類內使用`self`訪問
* 類外部, 類屬性不能用實例訪問,但類方法可以
```php
class People{
// 屬性
public $name;
// 屬性
public $age;
// 屬性: 靜態屬性
public static $country = '中國';
// 構造方法
public function __construct($name, $age){
$this->name = $name;
$this->age = $age;
// $this->country = $country; // 會報錯
// 盡管可以在構造方法中初始化靜態屬性,但不建議這樣做,否則靜態屬性,無法在對象之間共享
}
// 對象方法
public function getInfo1(){
// 這個方法可以用對象訪問,方法中訪問了靜態屬性,實現了類屬性在對象中的共享
// return $this->name . '年齡是: ' . $this->age. '國家是:' . $this->country; // 這樣會報錯
return $this->name . '年齡是: ' . $this->age. '國家是:' . self::$country;
}
// 類方法: 靜態方法
public static function getInfo2(){
// 靜態方法是類方法, 不能用對象調用,所以內部也不允許使用對象引用$this
// 如果靜態方法中,一定要用到對象屬性或方法,可以用參數傳入
return $this->name . '年齡是: ' . $this->age . '國家是:' . self::$country;
}
// 靜態方法: 以方法傳參方式調用對象屬性/方法
public static function getInfo3($name,$age){
// return $this->name; // 會報錯,在靜態方法里,不能訪問非靜態成員
// 可以用self調用,也可以用本類名調用。 最好在本類用self,在外部用類名
return $name . '年齡是: ' . $age . '國家是:' . Demo1::$country;
}
}
$obj = new People('范冰冰',33);
echo $obj->name, '<br>';
echo $obj->age, '<br>';
// echo $obj->country, '<br>'; //會報錯
echo People::$country; // 應該以這種方式訪問靜態屬性
echo '<br>';
echo $obj->getInfo1(), '<br>';
// echo $obj->getInfo2(), '<br>'; // 會報錯
// echo People::getInfo2(), '<br>'; // 會報錯
echo People::getInfo3($obj->name,$obj->age);
echo '<br>';
// 對象不能訪問靜態屬性,但是可以訪問靜態方法
echo $obj->getInfo3($obj->name,$obj->age);
// 靜態成員可以重新賦值。在創建很多對象,值不會因為創建的對象改變。
People::$country = 'china';
$obj1 = new People('楊冪',31);
echo People::$country;
echo '<hr>';
```
* 之前,將類做為對象的模板, 訪問類中的成員,必須先將類實例化,通過對象訪問
* 面向對象編程,實際上操作都是對象,而類,僅做為生成對象的代碼模板而存在
* 而實際情況卻并非總是如此,試想一下,以下的二種場景:
* 如果一個對象,我們僅僅只用一次,還有必要創建對象嗎, 直接將類變為對象,豈不是更方便?
* 如果多個對象之間, 需要共享一些屬性和方法, 而他們必須通過一個個獨立對象調用的,無法共享,怎么辦?
* 靜態可以公用,只會在內存中,生成一份。
* 靜態只有在程序執行完,才會被釋放。
* 能用靜態,就多用靜態,靜態效率高
* 以上情況,將屬性和方法直接定義在類中, 直接用類調用, 就可以解決
*****
### :-: 八、類常量
* 類常量也類屬性一樣,也是屬于類的, 必須用類訪問,不能用對象訪問
* 類常量與類屬性的區別是: 類常量不允許修改,而類屬性可以修改
* 類常量與普通常量的命名規則是一致的, 推薦使用大寫字母或大寫字母+下劃線
* 類常量不需要設置訪問限制符,默認行為與`public`是一樣的
```php
define('COUNTRY','中國');
class People{
// 類常量也類屬性一樣,也是屬于類的, 必須用類訪問,不能用對象訪問
const COUNTRY = '中國';
// 類常量與類屬性的區別是: 類常量不允許修改,而類屬性可以修改
public static $sex = '女';
private $name;
public function __construct($name){
$this->name = $name;
}
public function getInfo(){
// 類常量在類的內部,訪問方式與類屬性是一樣的
return $this->name.'的性別是:' . self::$sex.',國籍是: ' . self::COUNTRY;
}
}
$obj = new People('劉詩詩');
// 訪問類屬性
echo People::$sex, '<br>';
// 訪問類常量
echo People::COUNTRY, '<br>';
// 訪問對象方法: 該方法又訪問了類屬性與類常量
echo $obj->getInfo();
echo '<hr>';
// 修改類屬性
People::$sex = '保密';
// 修改類常量: 報錯
//People::COUNTRY = '美國';
// 可以看到類屬性:$sex發生了變化
echo $obj->getInfo();
echo '<hr>';
```
*****
### :-: 九、屬性重載
* 重載: 動態的創建屬性和方法
* 當訪問未定義或不可見的屬性/方法時, 重載方法會自動調用
* "當訪問未定義或不可見", 統稱為: "不可訪問"
* PHP中的重載,是通過"魔術方法"實現
* "魔術方法"是特指客戶端不能訪問,而只能是系統根據一定條件自動調用
* 所有重載方法必須聲明為: `public`
* 重載方法
* `__get($name)`: 當獲取未定義可不見屬性時觸發,需要一個參數
* `__set($name, $value)` :當給未定義可不見屬性賦值時觸發,需要兩個參數
* `__isset($name)`: 當檢測未定義可不見屬性時觸發
* `__unset($name)`: 當注銷未定義可不見屬性時觸發
* 魔術方法,可以給變量賦值。它的作用是可以進行賦值判斷,如果是共有的,可以隨便賦值。直接復制不能判斷,用\_\_get,\_\_set可以判斷一些不太可能的值。比如年齡,不可能會超過200歲。比如女朋友,不可能多個。
```php
class People{
private $name;
private $age;
protected $country = '中國';
// public $country = '中國';
// 構造方法
public function __construct($name, $age){
$this->name = $name;
$this->age = $age;
}
// __get($name):當獲取未定義可不見屬性時觸發
// $name 是屬性名
public function __get($name){
if ($name === 'country') {
// 僅允許name=='admin'的用戶可以查看country字段內容
return ($this->name === 'admin') ? $this->$name : '無權查看';;
}
return $this->$name;
}
// __set($name, $value):當給未定義可不見屬性賦值時觸發
public function __set($name, $value){
// 直接返回, 極少這樣做,這樣做相當于把類屬性直接設置為:public
// $this->$name = $value;
// 添加過濾機制
if ($name === 'age') {
return $this->name === 'admin' ? $this->$name = $value : '無權修改';
}
return $this->$name = $value;
}
// __isset($name): 當檢測未定義可不見屬性時
public function __isset($name){
if ($this->name === 'admin') {
if (isset($this->$name)){
echo '存在該屬性';
} else {
echo '沒有該屬性';
}
} else {
echo '無權檢測';
}
}
//__unset($name): 當注銷未定義可不見屬性時觸發
public function __unset($name){
if ($this->name === 'admin') {
unset($this->$name);
} else {
echo '無法刪除';
}
}
}
$obj = new People('迪麗熱巴', 26);
echo $obj->name, '<br>';
echo $obj->country, '<br>';
// 怎么才能查看 country, 只能用'admin'來實例化
$obj = new People('admin', 50);
echo $obj->country, '<br>';
// 直接修改 age, 類中沒有__set()會報錯
$obj->age = 80;
// 查看age字段值
echo $obj->age, '<br>';
// 檢測是否存在age字段
isset($obj->age);
echo '<br>';
// 刪除salary屬性
unset($obj->age);
echo '<br>';
isset($obj->age);
echo '<hr>';
```
*****
### :-: 十、方法重載
* `__call()`: 訪問未定義的對象方法時會自動調用它
* `__callStatic()`: 訪問未定義的靜態類方法時會自動調用它
```php
class People{
// __call(): 訪問不存在/不可見對象方法時觸發,有兩個參數,第一個是方法名,第二個方法的參數
public function __call($name, $arguments){
return '方法名: '.$name.'<br>方法參數列表: ' . '<pre>'.print_r($arguments, true).'不存在';
}
// __callStatic(): 訪問不存在/不可見的類方法(靜態)方法時觸發
public static function __callStatic($name, $arguments){
return '方法名: '.$name.'<br>方法參數列表: ' . '<pre>'.print_r($arguments, true).'不存在';
}
}
$obj = new People();
// 訪問不存在或無權訪問的對象方法
echo $obj->getInfo1(10,20,30);
echo '<hr>';
// 訪問不存在或無權訪問的靜態類方法
echo Demo4::getInfo2('html','css', 'javascript');
echo '<hr>';
```
*****
### :-: 十一、小案例
* call_user_func($callback[,$parameter...]): 以函數參數的方式,執行一個函數,其實就是以回調的方式執行函數
* call_user_func_array($callback,$array): 功能與call\_user\_func()相同, 僅僅是參數以數組形式提供
```php
function sum($a, $b) {
return $a . ' + ' . $b . ' = ' . ($a+$b);
}
// 正常函數調用
echo sum(20, 40);
echo '<br>';
// 以回調的方式執行該函數
echo call_user_func('sum', 50, 20);
echo '<br>';
// call_user_func_array(), 第二個參數是數組格式,不能省略
echo call_user_func_array('sum', [30, 80]);
echo '<hr>';
```
```php
// 現在換個思路, 將函數放在一個類中, 再來調用這個方法/函數
class Test1{
// 對象方法
public function sum($a, $b){
return $a . ' + ' . $b . ' = ' . ($a+$b);
}
}
// 如果以回調方式執行對象方法呢?
$obj = new Test1();
echo call_user_func([$obj,'sum'], 50, 20);
echo '<br>';
// 僅以call_user_func_array()舉例, call_user_func()原理一樣
echo call_user_func_array([$obj,'sum'], [10,30]);
echo '<br>';
// 如果僅調用一次,可以簡化一下對象創建方式
echo call_user_func_array([new Test1(),'sum'], [15,35]);
echo '<hr>';
```
```php
// 如果是一個靜態方法,如果調用呢?
class Test2{
// 對象方法 (乘法運算)
public static function mul($a, $b){
return $a . ' * ' . $b . ' = ' . ($a*$b);
}
}
// 直接將類名與方法寫在一個字符串即可
echo call_user_func_array('Test2::mul', [10,30]);
echo '<br>';
// 將類名與類方法分開,放在一個數組中
echo call_user_func_array(['Test2','mul'], [10,30]);
echo '<br>';
echo '類名是: '. Test2::class; // 返回一個類名字符串
echo '<br>';
// 所以這樣寫,也是正確的
echo call_user_func_array([Test2::class,'mul'], [10,30]);
```
> 下面是一個sql語句類的案例
* 類方法的跨類調用的實現:方法重載的應用,數據庫查詢的鏈式操作
* 鏈式調用的原理分析:以模擬ThinkPHP5.1框架中的數據庫查詢構造器為案例,來演示方法重載的精妙之處
```php
require 'query.php';
class Db
{
// 數據庫連接對象
protected static $pdo = null;
// 數據庫連接方法, 每次查詢時再連接, 實現真正的惰性連接,節省系統開銷
public static function connection(){
// 為簡化,這里直接使用字面量參數連接數據庫,真實項目中應該將參數放在配置文件中
self::$pdo = new PDO('mysql:host=localhost;dbname=ouyangke','root','root');
}
// 這是查詢類操作的入口, 通過靜態魔術方法進行跳轉,實現對象方法的跨類調用
public static function __callStatic($name, $arguments){
// 創建pdo對象,并連接數據庫
self::connection();
// 實例化查詢類,將連接對象做為參數
$query = new query(self::$pdo);
// 執行查詢類Query中的對象方法, 注意參數是數組,我只需要第一個參數:表名, 所以加了索引鍵名
return call_user_func_array([$query,$name],[$arguments[0]]);
}
}
// 客戶端的鏈式調用
// 以Db類做入整數數據庫操作的入口, SQL語句的各個部分用對象方法提供
// 鏈式操作是現代PHP框架的基礎,非常有用
$users = Db::table('user')
->field('uid,name,age')
->where('uid > 1')
->limit(5)
->select();
// 遍歷查詢結果
foreach ($users as $user) {
print_r($user); echo '<br>';
}
```
> query.php
```php
<?php
// 數據庫查詢類
class query
{
// 連接對象
public $pdo = null;
// 數據表名
public $table = '';
// 字段列表
public $field = '';
// 查詢條件
public $where = '';
// 顯示數量
public $limit = 0;
// 構造方法,初始化連接對象
public function __construct($pdo)
{
// 連接對象是對象方法的共享屬性
$this->pdo = $pdo;
}
// 調用表名
public function table($tablName)
{
$this->table = $tablName;
// 返回當前對象,便于鏈式調用該對象的其它方法
return $this;
}
// 設置查詢字段
public function field($fields)
{
$this->field = $fields;
return $this;
}
// 設置查詢條件
public function where($where)
{
$this->where = $where;
return $this;
}
// 設置顯示數量
public function limit($limit)
{
$this->limit = $limit;
return $this;
}
// 創建SQL查詢語句對象,并返回查詢結果
public function select()
{
// 查詢條件分開設置, 可以確保鏈式方法獨立
$fields = empty($this->field) ? '*' : $this->field;
$where = empty($this->where) ? '' : ' WHERE '.$this->where;
$limit = empty($this->limit) ? '' : ' LIMIT '.$this->limit;
// 接裝SQL語句
$sql = 'SELECT '.$fields.' FROM '.$this->table. $where . $limit;
// 預處理查詢
$stmt = $this->pdo->prepare($sql);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
```
*****
### :-: 十二、自動加載
* `spl_autoload_register(callback)`: 通過回調自動加載外部文件
* `__DIR__`魔術常量
```php
// 查看當前腳本所在的目錄
echo __DIR__, '<br>';
include __DIR__ . '/inc/Test1.php';
include __DIR__ . '/inc/Test2.php';
include __DIR__ . '/inc/Test3.php';
# 如果當前腳本使用了幾十上百這樣的類, 上面的方式就很不人性
# 使用下面的自動加載機制, 會根據客戶端調用的類, 自動進行加載,效率高, 不出錯
```
```php
// php標準函數庫中提供了一個自動加載文件的注冊函數,可以實現這個功能
// 這個函數,在當前腳本引用一個未加載的文件時, 會自動調用它的回調方法來加載這個文件
spl_autoload_register(function ($class){
// include __DIR__ . '/inc/Test1.php';
// 將include中的類名Test1用變量替換掉,這樣就實現了最簡單的自動加載
// 后面我們會使用命名空間來完善這個函數,目前大家先理解到這里即可
include __DIR__ . '/inc/'.$class.'.php';
});
$test1 = new Test1();
echo $test1->get(), '<br>';
$test1 = new Test2();
echo $test1->get(), '<br>';
$test1 = new Test3();
echo $test1->get(), '<br>';
echo '<hr>';
```
>[info] Test1.php、Test2.php、Test3.php 文件
```php
<?php
namespace inc;
class Test1{
public static function get(){
return __CLASS__ . ' 類, 加載成功~~';
}
}
```
*****
### :-: 十三、抽象類
* `abstract`: 定義抽象方法/抽象類
* 類中只要有一個抽象方法, 該類就應該聲明為抽象類
* 抽象類只能被繼承,不能實例化,并且抽象方法必須在子類實現
* 實現抽象方法的子類方法可見性不能低于抽象方法原定義
* 抽象方法是public, 子類方法只能是public
* 抽象方法是protected, 子類方法只能是protected/public
> 一個抽象類必須被擴展為一個特定的類,我們才能創建類實例,使用類中功能
```php
abstract class a{
public $name;
public function __construct($name){
$this->name = $name;
}
// 不管有多少個普通方法,只要有一個抽象方法,就是抽象類
public function af(){
echo $this->name;
}
// 抽象方法不能有內容,里面不能有代碼,{}:不能有
abstract public function aff();
}
// 抽象類不能實例化,不能new,只能繼承
// 我們就用b類,繼承 a抽象類
class b extends a{
// b類 繼承 a抽象類后:必須把a抽象類 ,里面的抽象方法,重新寫一遍(實現)
public function aff(){
echo $this->name;
}
}
// 實現后,我們可以調用子類,進行實例化,然后調用成員方法和成員變量。
$a = new b('歐陽克');
// 為什么抽象類里的af方法能調用呢,因為它是普通方法。
$a->af();
echo '<br/>';
// 這里的方法為什么能調用呢? 因為b類,繼承了a抽象類的方法后:實現成為普通類。
$a->aff();
```
* 在實際開發過程中, 通常并不會直接使用一個父類/超類,而是在父類中定義一些方法聲明
* 并且確信這個方法肯定是會被子類重寫的, 父類中沒必要實現,只要給一個方法編寫規范即可
* 這個規范包括方法的名稱, 參數列表等,具體實現就完全交給子類去完成了
* 相當于公司的部門主管, 接受到老板的任務, 只把屬于自己的部分做了, 其它部分, 設置一個標準交給下屬去完成
```php
abstract class Person{
protected $name;
protected function __construct($name='peter zhu'){
$this->name = $name;
}
// 該方法不需要重寫, 可以通過子類對象訪問,應該設置為public
public function getName(){
return $this->name;
}
// 修改屬性方法,設置為抽象方法,交給子類實現
abstract protected function setName($value);
}
// 當子類繼承 抽象父類,普通的方法,可以直接使用,抽象方法,必須重新實現。
class Stu extends Person{
// 注意: 構造方法不會自動繼承, 必須手動重寫
public function __construct($name='peter zhu'){
parent::__construct($name);
}
// 1,它的父類,有這個抽象方法,這里必須重新寫,帶著具體的代碼。
// 2,類實例化后,調用這個方法,就是直接調用這個方法,跟抽象方法沒關系。
public function setName($value){
$this->name = $value;
}
}
$stu = new Stu('豬哥');
echo 'php中文網創始人: ' . $stu->getName() . '<br>';
// 調用子類的重寫的抽象方法setName(),來設置屬性
$stu->setName('滅絕師太');
// 3,用setName傳值后,值給到父抽象類里的$name,用父抽象類的getName方法可以輸出傳值
echo 'php中文網前端講師: ' . $stu->getName() . '<br>';
echo '<hr>';
```
* 類中只要有一個成員設置為抽象,該類就必須設置抽象類
* 一個類一旦被設置為抽象類,就具備了二個特點:
* 不能實例化
* 類中抽象方法,在子類中必須實現(全部),就是子類必須有父類的抽象方法
* 注意:
* 屬性設置為抽象無意義, 抽象僅針對方法,類
* 子類的成員可見性必須等于或高于所繼承的抽象類成員可見性,例如抽象類是proteced,子類可以是protected/pulic
* 子類重寫的抽象方法可見性,究竟用protected/pulic, 要看這個子類是最終被客戶端訪問,如果是就public,如果不是protected
* 級別:
* public 級別最高: A
* protected 級別中等: B
* private 級別最低: C
*****
### :-: 十三、接口
* `interface`: 指定某個類必須實現的方法,但不需要定義方法的具體實現過程
* 接口中僅允許出現: 方法與常量
* 接口的方法可見性必須是: public
* 接口的方法體必須是空的
* 接口是類的代碼模板, 可以像類一樣有父子繼承關系,例如父接口, 子接口
* `implements`: 類實現接口的關鍵字
* 如果僅是部分實現接口中的方法, 請用一個抽象類來實現它
* 接口中的方法,必須全是抽象方法
* 抽象和接口區別:
* 抽象可以有普通方法,成員變量。
* 接口不可以有普通方法,不可以有成員變量。
```php
interface iVehicle{
const COUNTRY = '中國';
// 驅動方式: 汽車, 新能源
public function setFuel($fuel);
// 用途
public function setPurpose($purpose);
}
// Car 類 實現了接口: iVehicle,關鍵詞:implements
// 抽象類 實現接口: iVehicle,關鍵詞:implements
// 接口 可以 繼承接口:extends
// 類 可以 同時 繼承 和實現(先繼承,在實現)
// 可以實現多個接口,用逗號隔開
class Car implements iVehicle{
public $fuel;
public $purpose;
// 構造方法
public function __construct($fuel='汽油', $purpose='家用'){
$this->fuel = $fuel;
$this->purpose = $purpose;
}
// 必須實現的接口方法
public function setFuel($fuel){
$this->fuel = $fuel;
}
// 必須實現的接口方法
public function setPurpose($purpose){
$this->purpose = $purpose;
}
// 類中自定義的對象方法
public function getInfo(){
return $this->fuel . $this->purpose . '車 <br>';
}
}
// 客戶端代碼
$car = new Car();
echo $car->getInfo();
$car->setFuel('新能源');
$car->setPurpose('公交');
echo $car->getInfo();
echo '<hr>';
```
> 如果暫時只能實現接口中的部分方法, 可以用一個抽象來實現這個接口
```php
interface iVehicle{
const COUNTRY = '中國';
// 驅動方式: 汽車, 新能源
public function setFuel($fuel);
// 用途
public function setPurpose($purpose);
}
abstract class Auto implements iVehicle{
public $fuel;
// 只實現接口中的setFuel()方法, 另一個方法并未實現
public function setFuel($fuel){
$this->fuel = $fuel;
}
}
// 再創建一個類,來繼承擴展這個抽象類 Auto
class Car1 extends Auto{
public $purpose;
// 構造方法
public function __construct($fuel='汽油', $purpose='家用'){
$this->fuel = $fuel;
$this->purpose = $purpose;
}
// 這個方法原來在接口中聲明的,在它繼承的抽象類中并沒有聲明
public function setPurpose($purpose){
$this->purpose = $purpose;
}
// 自定義的方法
public function getInfo(){
return $this->fuel . $this->purpose . '車 <br>';
}
}
// 客戶端代碼
$car1 = new Car1();
echo $car1->getInfo();
$car1->setFuel('天然氣');
$car1->setPurpose('家用');
echo $car1->getInfo();
```
* 總結:
* 如果不能將接口中方法全部實現就用抽象類來實現它
* 否則,就必須全部把接口中方法全部實現
*****
### :-: 十四、接口按實戰案例
```php
// 定義一個接口,實現數據庫常用操作:增刪改查
interface iCurd
{
// 增加數據
public function create($data);
// 讀取數據
public function read();
// 更新數據
public function update($data, $where);
// 刪除數據
public function delete($where);
}
// 創建Db類, 實現iCurd接口,完成基本的數據庫操作
class Db implements iCurd{
//數據庫的連接對象
protected $pdo = null;
// 數據表名
protected $table;
// 構造方法: 連接數據庫,并設置默認數據表名稱
public function __construct($dsn, $user, $password, $table='staff'){
$this->pdo = new PDO($dsn, $user, $password);
$this->table = $table;
}
// 讀取
public function read($fields='*', $where='', $limit='0, 5'){
// 設置查詢條件
$where = empty($where) ? '' : ' WHERE ' . $where;
// 設置顯示數量
$limit = ' LIMIT ' . $limit;
// 預處理查詢操作
$sql = 'SELECT '.$fields.' FROM '.$this->table.$where.$limit;
$stmt = $this->pdo->prepare($sql);
$stmt->execute();
// 返回二維數組表示的查詢結果集
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// 新增, 參數是數組: 新記錄的鍵值對
public function create($data){
// 字段列表
$fields = ' (name,age,sex,position,mobile,hiredate)';
// 值列表
$values = '(:name,:age,:sex,:position,:mobile,:hiredate)';
// 創建SQL語句
$sql = 'INSERT INTO '.$this->table.$fields.' VALUES '.$values;
// 預處理執行新增操作
$stmt = $this->pdo->prepare($sql);
$stmt->execute($data);
// 返回新增數量, 新增記錄的ID組成的數組
return [
'count'=>$stmt->rowCount(),
'id'=>$this->pdo->lastInsertId()
];
}
// 更新, 為了數據安全, 不允許無條件更新
public function update($data, $where){
// 難點在于SET 參數的處理上,利用傳入的$data數組,進行拆裝
// 獲取數組的鍵名組成的數組
$keyArr = array_keys($data);
$set = '';
// 遍歷鍵名表示的字段列表,拼裝預處理需要的sql語句,注意占符符的表示
foreach ($keyArr as $value) {
$set .= $value . ' = :' .$value. ', ';
}
// 去掉最后一個逗號, 注意每個逗號后有一個空格,去除時也要帶上這個空格
$set = rtrim($set,', ');
// 預處理執行更新操作
$sql = 'UPDATE '.$this->table.' SET '.$set .' WHERE ' .$where;
$stmt = $this->pdo->prepare($sql);
$stmt->execute($data);
// 返回被更新的記錄數量
return $stmt->rowCount();
}
// 刪除: 與更新一樣, 這也是危險的寫操作, 不允許無條件刪除
public function delete($where){
// 預處理執行刪除操作
$sql = 'DELETE FROM '.$this->table.' WHERE '.$where;
$stmt = $this->pdo->prepare($sql);
$stmt->execute();
return $stmt->rowCount();
}
}
// 客戶端的測試代碼
// 實例化Db類
$dsn = 'mysql:host=localhost;dbname=ouyangke';
$user = 'root';
$password = 'root';
$db = new Db($dsn, $user, $password);
// 遍歷讀取
foreach ($db->read() as $item) {
print_r($item); echo '<br>';
}
echo '<hr>';
// 新增數據
$data = [
'name'=>'郭靖',
'age'=>30,
'sex'=>1,
'position'=>'金刀駙馬',
'mobile'=>'13666668888',
'hiredate'=>time()
];
$res = $db->create($data);
echo '成功新增'.$res['count'].'條記錄,最新記錄的主鍵ID是: '.$res['id'];
echo '<hr>';
// 更新記錄
$data = [
'age' => 5,
'position'=>'抗金英雄'
];
$where = 'id = 5';
echo '成功更新了: ' .$db->update($data, $where). ' 條記錄';
echo '<hr>';
// 刪除記錄
$where = 'id = 5';
echo '成功更新了: ' .$db->delete($where). ' 條記錄';
```
*****
### :-: 十五、后期靜態綁定
* 后期靜態綁定,也叫"延遲靜態綁定"
* 這個技術應用在靜態繼承的上下文環境中,用于動態調用被重寫的方法
* 調用被重寫的靜態方法使用關鍵字: `static` 加上"范圍解析符"`::`
* `::` 范圍解析符的使用場景
* 訪問類方法與類常量
* 訪問被重寫的對象或類方法
* static關鍵字用來調用重寫方法的時候,可以動態的綁定當前調用的類
```php
class A{
public static function who(){
echo 111;
}
public function test(){
// self::who(); // 猜一下,是調用它自己的who,還是子類的who呢?
// 那么如何在這種靜態繼承的上下文環境中, 靜態調用類中方法的時候,正確識別調用者呢?
// 可以將self 關鍵字改為: static ,
// 注意: static 除了可以用在靜態方法中, 也可以用在普通對象方法中
static::who();
}
}
// B繼承了A,重寫A類里面的who方法。
class B extends A{
public static function who(){
echo 222;
}
}
$a = new B();
echo $a->test();
```
>[info] 表格表達類相關的關鍵詞
**關鍵詞** | **類外聲明** | **聲明類** | **聲明屬性** | **聲明方法** | **解釋**
--- | --- | --- | --- | --- | ---
const | √ | | √ | | 定義類常量
extends | | √ | | | 擴展類,用一個類去擴展它的父類
public | | | √ | √ | 公用屬性或方法
protected | | | √ | √ | 私有屬性或方法
private | | | √ | √ | 受保護的屬性或方法
abstract | | √ | √ | | 抽象類或方法
final | | √ | | √ | 類不能被繼承
interface | | √ | | | 創建接口
implements | | √ | | | 實現接口
parent:: | | | | | 訪問父類
$this-> | | | | | 訪問本類
self:: | | | | | 訪問靜態
static:: | | | | | 后期靜態綁定
namespace | √ | | | | 創建命名空間
- 序言
- 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依賴管理工具