ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
* 关键词:类,类实例,对象,对象检测 * 类的三大特性: * 封装:把对象的属性和方法组织在一个类(逻辑单元)里 * 继承:以原有的类为基础,创建一个新类,从而代码复用的目的 * 多态:允许将子类类型的指针赋值给父类类型的指针 > 需要的数据库 ```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 | √ | | | | 创建命名空间