🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# :-: 一、接口中的常量 * 接口常量的作用: 当作配置常量来使用 ```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); ```