🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# :-: 一、类属性与类方法(静态成员) * 类属性: 静态属性 * 类方法: 静态方法 * 静态成员属于类,而不属于对象 * 静态成员不需要通过对象访问,所以不必实例化 * 使用`static`关键字定义 * 类外使用类名访问,类内使用`self`访问 * 类外部, 类属性不能用实例访问,但类方法可以 ```php namespace admin; class Demo{ // 实例属性 public $product; // 静态属性, 使用关键字 static 定义静态成员 public static $price; // 构造方法 public function __construct($product, $price){ $this->product = $product; // 静态属性的初始化 self::$price = $price; // 尽管可以在构造方法中初始化静态属性,但不建议这样做,否则静态属性,无法在对象之间共享 } // 实例方法 public function getInfo1(){ // 实例方法, 即可以访问实例属性,也可以访问静态属性 return $this->product . '价格是: ' . self::$price; } // 类方法: 静态方法 public static function getInfo2(){ // 静态方法是类方法, 不能用对象调用,所以内部也不允许使用对象引用$this // 如果静态方法中,一定要用到对象属性或方法,可以用参数传入 // return $this->product . '价格是: ' . self::$price; } // 在静态方法中, 使用实例属性的实现手段 // 将实例属性以访问参数的形式, 注入到录前的静态方法中即可 public static function getInfo3($product){ return $product . '价格是: ' . self::$price; } } $obj = new Demo('衣服', 300); // 实例属性: 用类的实例访问 echo $obj->product, '<br>'; // 静态属性: 用类直接访问,例如双冒号[范围解析符] echo Demo::$price; echo '<hr>'; echo $obj->getInfo1(), '<br>'; //echo $obj->getInfo2(), '<br>'; echo '<hr>'; // 在静态方法中访问实例属性 echo Demo::getInfo3($obj->product); echo '<br>'; // 对象不能访问静态属性,但是可以访问静态方法,应该避免这样去做 echo $obj->getInfo3($obj->product); ``` -------------------------------------------- # :-: 二、类常量 * 类常量也类属性一样,也是属于类的, 必须用类访问,不能用对象访问 * 类常量与类属性的区别是: 类常量不允许修改,而类属性可以修改 * 类常量与普通常量的命名规则是一致的, 推荐使用大写字母或大写字母+下划线 * 类常量不需要设置访问限制符,默认行为与`public`是一样的 ```php namespace admin; class Demo { // 类常量也类属性一样,也是属于类的, 必须用类访问,不能用对象访问 const NATION = '中国'; // 类常量与类属性的区别是: 类常量不允许修改,而类属性可以修改 // 类常量必须初始化, 而且必须是字面量, 不允许使用表达式或变量对象等 public static $sex = '男'; private $name; public function __construct($name){ $this->name = $name; } public function getInfo(){ // 类常量在类的内部,访问方式与类属性是一样的 return $this->name.'的性别是:' . self::$sex.',国籍是: ' . self::NATION; } } $obj = new Demo('欧阳克'); // 访问类属性 echo Demo::$sex, '<br>'; // 访问类常量 echo Demo::NATION, '<br>'; // 访问对象方法: 该方法又访问了类属性与类常量 echo $obj->getInfo(); echo '<hr>'; // 修改类属性 Demo::$sex = '保密'; // 修改类常量: 报错 //Demo::NATION = '中国'; // 可以看到类属性:$sex发生了变化 echo $obj->getInfo(); ``` -------------------------------------------- # :-: 三、属性重载 * 重载: 动态的创建属性和方法 * 当访问未定义或不可见的属性/方法时, 重载方法会自动调用 * "当访问未定义或不可见", 统称为: "不可访问" * PHP中的重载,是通过"魔术方法"实现 * "魔术方法"是特指客户端不能访问,而只能是系统根据一定条件自动调用 * 所有重载方法必须声明为: `public` * `__get($name)`: 当获取未定义可不见属性时触发 * `__set($name, $value)` :当给未定义可不见属性赋值时触发 * `__isset($name)`: 当检测未定义可不见属性时触发 * `__unset($name)`: 当注销未定义可不见属性时触发 ```php namespace admin; class Demo{ private $name; private $salary; protected $secret = '其实猪哥与朱老师不是同一个人'; // 构造方法 public function __construct($name, $salary){ $this->name = $name; $this->salary = $salary; } // __get($name):当获取未定义可不见属性时触发 public function __get($name){ if ($name === 'secret') { // 仅允许name=='admin'的用户可以查看secret字段内容 return ($this->name === 'admin') ? $this->$name : '无权查看'; } return $this->$name; } // __set($name, $value):当给未定义可不见属性赋值时触发 public function __set($name, $value){ // 直接返回, 极少这样做 // $this->$name = $value; // 添加过滤机制 if ($name === 'salary') { 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 Demo('欧阳克', 6666); echo $obj->name, '<br>'; echo $obj->secret, '<br>'; // 怎么才能查看 secret, 只能用'admin'来实例化 $obj = new Demo('admin', 8888); echo $obj->secret, '<br>'; // 直接修改 salary, 类中没有__set()会报错 $obj->salary = 9999; // 查看salary字段值 echo $obj->salary, '<br>'; echo '<hr>'; // 检测是否存在salary字段 isset($obj->salary); echo '<br>'; // 删除salary属性 unset($obj->salary); echo '<br>'; isset($obj->salary); ``` -------------------------------------------- # :-: 四、方法重载 * call_user_func($callback[,$parameter...]): 以函数参数的方式,执行一个函数,其实就是以回调的方式执行函数 * call_user_func_array($callback,$array): 功能与call_user_func()相同, 仅仅是参数以数组形式提供 >[info] 函数回掉 ```php function sum($a, $b) { return $a . ' + ' . $b . ' = ' . ($a+$b); } // 正常函数调用 echo sum(20, 40); echo '<br>'; // 以回调的方式执行该函数 // __NAMESPACE__: 命名空间魔术常量,它的值始终是表示当前命名空间的字符串 // 因为当前脚本使用了命名空间, 所以函数使用使用命名空间做为前缀,否则会报错 echo call_user_func(__NAMESPACE__.'\sum', 50, 20); echo '<br>'; // call_user_func_array(), 第二个参数是数组格式,如果没参数就传空数据,不能省略 echo call_user_func_array(__NAMESPACE__.'\sum', [30, 80]); echo '<hr>'; ``` >[info] 方法回掉 ```php class Test1 { // 对象方法 public function sum($a, $b){ return $a . ' + ' . $b . ' = ' . ($a+$b); } } // 如果以回调方式执行对象方法呢? //$obj = new namespace\Test1(); $obj = new Test1(); // 等价 // 仅以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'], [10,30]); echo '<hr>'; ``` >[info] 静态方法回掉 ```php class Test2{ // 对象方法 (乘法运算) public static function mul($a, $b){ return $a . ' * ' . $b . ' = ' . ($a*$b); } } // 直接将类名与方法写在一个字符串即可 echo call_user_func_array(__NAMESPACE__.'\Test2::mul', [10,30]); echo '<br>'; // 将类名与类方法分开,放在一个数组中 echo call_user_func_array([__NAMESPACE__.'\Test2','mul'], [10,30]); echo '<br>'; // ::class的功能: 用于类名解析, 来获取一个带有命名空间的完整类名 echo '类名是: '. Test2::class; // 返回一个类名字符串 echo '<br>'; // 所以这样写,也是正确的 echo call_user_func_array([Test2::class,'mul'], [10,30]); echo '<hr>'; ``` * `__call()`: 访问未定义的对象方法时会自动调用它 * `__callStatic()`: 访问未定义的静态类方法时会自动调用它 ```php class Demo{ // __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 Demo(); // 访问不存在或无权访问的对象方法 echo $obj->getInfo1(10,20,30); echo '<hr>'; // 访问不存在或无权访问的静态类方法 echo Demo4::getInfo2('html','css', 'javascript'); echo '<hr>'; ``` -------------------------------------------- # :-: 五、方法重载实例演示 * 类方法的跨类调用的实现 * 链式调用的原理分析 ```php namespace admin; require 'Query.php'; class Db{ // 数据库连接对象 protected static $pdo = null; // 数据库连接方法, 每次查询时再连接, 实现真正的惰性连接,节省系统开销 public static function connection(){ // 为简化,这里直接使用字面量参数连接数据库,真实项目中应该将参数放在配置文件中 self::$pdo = new \PDO('mysql:host=localhost;dbname=php','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')->select(); // 遍历查询结果 foreach ($users as $user) { print_r($user); echo '<br>'; } echo '<hr>'; // 传入用户自定义参数 $staffs = Db::table('user') ->field('uid,name,phone,sex') ->where('uid > 2') // = 2,只会输出一条 ->limit(5) ->select(); // 遍历查询结果 foreach ($users as $user) { print_r($user); echo '<br>'; } ``` >[info] Query.php 文件 ```php <?php namespace _0801; // 数据库查询类 class Query{ // 连接对象 public $pdo = null; // 数据表名 public $table; // 字段列表 public $field = '*'; // 查询条件 public $where; // 显示数量 public $limit; // 构造方法,初始化连接对象 public function __construct($pdo){ // 连接对象是对象方法的共享属性 $this->pdo = $pdo; } // 调用表名 public function table($tablName){ $this->table = $tablName; // 返回当前对象,便于链式调用该对象的其它方法 return $this; } // 设置查询字段 public function field($fields = '*'){ $this->field = empty($fields) ? '*' : $fields; return $this; } // 设置查询条件 public function where($where = ''){ $this->where = empty($where) ? $where : ' WHERE '. $where; return $this; } // 设置显示数量 public function limit($limit){ $this->limit = empty($limit) ? $limit : ' LIMIT '.$limit; return $this; } // 创建SQL查询语句对象,并返回查询结果 public function select(){ // 拼装SQL语句 $sql = 'SELECT ' . $this->field //字段列表 . ' FROM ' . $this->table // 数据表 . $this->where // 查询条件 . $this->limit; // 显示数量 // 测试预处理查询 $stmt = $this->pdo->prepare($sql); $stmt->execute(); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } } ```