* 关键词:类,类实例,对象,对象检测
* 类的三大特性:
* 封装:把对象的属性和方法组织在一个类(逻辑单元)里
* 继承:以原有的类为基础,创建一个新类,从而代码复用的目的
* 多态:允许将子类类型的指针赋值给父类类型的指针
> 需要的数据库
```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依赖管理工具