PHP 面向对象进阶

接口用在什么地方?

站在更高一层级定义了很多接口,那么这些接口或者这些是个框架需要底层代码去实现。你是做了高层的一个抽象,比如说增、删、改、查这些操作,然后通过特定的方法把它实现了。这就是的接口的作用,接口就是个规范,大家去遵守这个规范。

一、接口常量

应用场景:通常可以用作配置常量来使用

<?php
namespace admin;
//interface_exists() 规范的写法是先检查该接口存不存在,不存在则创建一个接口
echo __NAMESPACE__;
echo '<hr>';
//接口常量,通常用作配置常量来使用
if (!interface_exists(__NAMESPACE__ . '\iDbParam')) {
	interface iDbParam {
		//定义一个接口,接口中保存的是数据库的连接参数,连接参数设置为常量
		const TYPE = 'mysql';
		const HOST = 'localhost';
		const USER_NAME = 'root';
		const PASSWORD = 'root';
		const DBNAME = 'sordidez';
		//抽象方法
		public static function connection();
	}
}
// Connection 工作类,其实就是一个普通类,是真正干活的类
// namespace\iDbParam 当前的接口定义在当前命名空间下,因此要引用一下当前命名空间
class Connection implements namespace\iDbParam {
	// 该类要实现数据库连接,则初始化连接参数:直接调用接口的常量  接口名::常量名
	private static $type = iDbParam::TYPE; //参数只在当前类用,声明为protected或private都可以
	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;
		// 在含有命名空间的文件下 new PDO()时必须加上反斜杠\
		// 因为PDO类是全局的并不在当前空间下,所以用\引用一下PDO
		$pdo = new \PDO($dsn, $user, $password);
		return $pdo;
	}
}

// 以后连接数据库只需要这个静态方法即可, 注意命名空间
$link = Connection::connection(); //link是一个PDO对象

//  执行一个查询进行测试
$stmt = $link->prepare('SELECT * FROM `user` LIMIT :limit');
//bindValue()值绑定,第一个参数是sql预处理语句中的命名占位变量,可加/可不加冒号(:),第二个参数是:字面量
//bindParam()变量绑定,第一个参数是sql预处理语句中的命名占位变量,第二个参数是:变量,即存放值的变量
$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>";
// }
echo '<pre>', print_r($users, true);

以后只要数据库发生了变化, 我们只需要改一下连接接口参数就可以, 项目代码不必做任何改动

二、延迟(后期)静态绑定

应用场景:用在静态继承的上下文环境中,让继承下来 重写的静态方法继承类 绑定在一起。只要类中有静态方法,静态方法又被子类重写,都可以用到 ,如果想看懂框架的源码,如果想要让代码看起来更加优雅更加可扩展,父类中尽可能不出现self,而全部用static替代掉。
静态继承的上下文在子类中重写父类的静态方法,父类和子类就是静态继承的上下文。
静态绑定子类间接调用重写过的父类的静态方法,方法应该跟着子类走,是哪个类就把这个类绑定到这个方法上,这就叫静态绑定。动态的跟踪/绑定当前类而与父类无关,换句话说这个类是变量而不是常量。
具体实现:通过static调用静态方法:static::静态方法()
:: 范围解析符的使用场景: 1. 访问类方法与类常量 2. 访问被重写的对象或类方法

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>';

总结: 静态继承的上下文环境中,调用被子类重写静态方法,使用关键字static代替掉self,根据调用者,动态的调用被重写的方法,这样就可以调用到被重写的方法了

  • 这种绑定是在运行阶段发生, 而不是代码编写的词法分析阶段(静态的代码文本),所以要后期延迟静态绑定
  • 这种延迟绑定,有什么卵用呢? 用处非常大,特别是在现代的PHP框架开中, 随处可见
  • 下面举一个简单的小案例,来看一下它的使用场景:
<?php
<?php
namespace _0123;
// 以最简单的数据库连接,来演示延迟静态绑定的应用
class Connect {
	public static function connect() {
		// self::调用是当前Connec类中的config方法,而并非在子类被重写的config方法
		// 因为用户在子类中重新定义了连接参数, 所以查询会失败
		// return self::config();

		// 使用static:: 根据调用者,动态的调用被重写的方法,这样就可以调用到被重写的方法了
		return static::config();
		// ②return $obj->config();
	}
	// ②public function config()
	public static function config() {
		return new \PDO('mysql:dbname=sordidez', 'root', '123456');
	}
}

class Link extends Connect {
	// ②public  function config()
	public static function config() {
		return new \PDO('mysql:dbname=sordidez', 'root', 'root');
	}
}
// ②$obj= new Link();
// ②$pdo = Link::connect($obj);
$pdo = Link::connect();
//var_dump($pdo instanceof PDO);

//query() PDO里面的一个查询方法,不是预处理,但不推荐使用
$users = $pdo->query('select * from movies limit 5');
foreach ($users as $user) {
	echo '<pre>', print_r($user, true);
	echo '<br>';
}
// echo '<pre>', print_r($users, true);
?>

总结: user::, 可以在静态继承的上下文环境中, 调用被子类重写的静态方法 (大家可以试试能否调用被重写的普通方法) 答:可以

三、命名空间的分级管理

'\' :命名空间的分割符。
作用:现代的php编程的自动加载技术都是靠他,框架没有自动加载技术就废了。以后创建的类它的命名空间,无论多少层,一定要和当前空间所在的目录一致。当命名空间和当前类所在的路径一致时,我们就可以通过自动加载函数实现类的自动加载技术,并且不会因为类的命名冲突,因为类名本身仍是带有命名空间的其实就是在某个脚本中拿到某个类的命名空间,将这个类所在命名空间映射为这个类所在文件的路径,并把这个文件(通过路径)加载进来。

  • 命名空间是可以分层管理的,也叫子命名空间
  • NAMESPACE: 双下划线开头的魔术常量, 所谓魔术是指,尽管是常量,但它的值可以随作用域发生变化
<?php

namespace admin;
echo '当前命名空间是: ' . __NAMESPACE__ . '<br>';  //  admin
echo __DIR__ .'\\'.__NAMESPACE__.'.php';         //E:\phpstudy\phpstudy_pro\WWW\面向对象的进阶内容\admin.php
echo '<br>';  
class Dog {}
echo Dog::class . '<hr>';                        //  admin\Dog

namespace admin\one;
echo '当前命名空间是: ' . __NAMESPACE__ . '<br>';  //  admin\one
class Dog {}
echo Dog::class . '<br>';                        //  admin\one\Dog

// 关键字: namespace: 可以显示的访问当前空间或子空间中的元素
echo namespace\Dog::class, '<br>';               //  admin\one\Dog

// 如果我想访问空间:admin\one\two 类名
// 可以将当前空间看成当前目录,用关键字namespace来引用当前空间
// 当前命名空间是: admin\one\, 所以从two开始就可以找到指定的类
echo namespace\two\Dog::class . '<hr>';          //  admin\one\two\Dog

namespace admin\one\two;
echo '当前命名空间是: ' . __NAMESPACE__ . '<br>';  //  admin\one\two
class Dog {}
echo Dog::class . '<hr>';                        //  admin\one\two\Dog

?>

四、使用空间别名简化命名空间

名称引用的问题:什么时候需要写一个完整的命名空间,什么时候时候不需要写完整的命名空间,就像目录那样,什么时候写绝对路径,什么时候写相对路径。
use 关键字:为一个带有较长的命名空间的类名设置一个别名进行简化。
如: use (\)admin\one\two\three\Test1 as T;
1、use 默认是从全局空间开始查找类,不得使用当前空间的引用关键字namespace
2、可以在use 的第一个空间名称前加上全局空间标识符: \ , 尽管你根本不需要这样去做,因为默认从全局开始,可以省略:\
3、如果类的别名, 与原始类名相同, 例如下例, 都是 Test1, 允许省略后面的 as 部分
4、当前脚本中导入的空间别名冲突的时候使用

<?php
namespace admin;
# 允许通过别名引用或导入外部的完全限定名称
# 当类带有空间或子空间时, 类名称有可能会变得很长,可以使用空间别名导入来简化类名

// 例如, 当前脚本需要加载'test.php'中的: admin\one\two\three\test1类
include __DIR__ . '/test.php';

# 检测 test1类 是否导入成功
// $className = namespace\one\two\three\Test1::class;
$className = one\two\three\Test1::class;
# 如果类存在,则调用类中的静态方法demo()
echo class_exists($className) ? $className::demo() . ' 类存在' : '类不存在';
echo '<hr>';

// 这个类名非常的长, 不仅书写起来非常的不方便, 而且引用时候, 也容易出错,代码也臃肿
// 特别是当前脚本,如果需要在多处引用这个类的时候, 不得不每次都写一遍这么长的类名, 太麻烦了
// 解决方案: 给当前类起一个简短的别名, 起别名使用的关键字是: use
// 下面使用别名: "T" 代替之前冗长的类名称
// 1、use 默认是从全局空间开始查找类,不得使用当前空间的引用关键字namespace
// 2、可以在use 的第一个空间名称前加上全局空间标识符: \,  尽管你根本不需要这样去做
use admin\one\two\three\Test1 as T; // 允许用 Test 1代替 完整的Test1类名

// 现在起, 在当前脚本中, 我就可以直接用T 来取代之前的: admin\one\two\three\Test1
echo class_exists(T::class) ? T::demo() . ' 类存在' : '类不存在';
echo '<hr>';
// 别名允许与原始类名相同
//use admin\one\two\three\Test as Test;
// 如果类的别名, 与原始类名相同, 例如本例, 别名是Test1, 允许省略后面的 as 部分
use admin\one\two\three\Test1;
echo class_exists(Test1::class) ? Test1::demo() . ' 类存在' : '类不存在';

// 那么什么时候使用 as 来定义 别名呢?
// 当前脚本中导入的空间别名冲突的时候使用
// use o\p\q\Demo;
// use x\y\z\Demo as Hell;
?>

test.php文件

<?php
namespace admin\one\two\three;
class Test{
	public static function demo(){
		return __METHOD__;
	}
}

class Test2{
	public static function demo(){
		return __METHOD__;
	}
}

五、命名空间实战案例

PDO类是全局的,所以他的带命名空间的类名是:\PDO,给 PDO类 起个别名: use \PDO as PDO; 又因为use关键字默认是从全局开始的所以可以省略: \ ,为:use PDO as PDO; 又别名和类名一样所以可以省略 as,为:use PDO;

<?php
// 命名空间实战
namespace admin;
// 导入全局空间中的PDO类
use PDO;

// 连接数据库: 目前在空间中写代码, PDO类中全局中, 建议加上"\",或者在前面用use导入类的别名
// 将连接参数放在接口常量中
interface iDbParams {
	// const DSN = 'mysql:host=localhost;dbname=sordidez';
	const DSN = 'mysql:dbname=sordidez'; //host有默认值
	const URSE = 'root';
	const PASS = 'root';
}
$pdo = new PDO(iDbParams::DSN, iDbParams::URSE, iDbParams::PASS);

//查询点数据展示出来,以测试数据库操作正确
// :num, :offset, 这里必须是整数类型, 而SQL语句默认都是字符串, 需要进行类型限定
$sql = 'SELECT `mov_id`,`name`,`image` FROM `movies` LIMIT :num OFFSET :offset';
$stmt = $pdo->prepare($sql);
$stmt->bindValue('num', 5, PDO::PARAM_INT);
$stmt->bindValue('offset', 0, PDO::PARAM_INT);
$stmt->execute();
// $stmt->execute([
// 	':num' => 5,
// 	':offset' => 0,
// ]);

// 遍历结果集
foreach ($stmt->fetchAll() as $user) {
	echo "<p>{$user['mov_id']} -- {$user['name']} -- {$user['image']}</p>";
}
exit;

?>

六、Trait 技术(php5.4)

代码复用的方法:函数、面向对象对象继承上下文、trait
trait实际上是一个类,但不能实例化,用在子类和父类之间,允许有多个trait类,trait里面既有属性也有方法,但通常使用方法,它里面主要是一些方法集,这些方法集通过copy进入到子类中。
子类继承了父类:
①假设子类重写了demo()方法,则子类对象调用demo()方法,调用的是子类的方法。
②如果子类没有重写demo()方法,trait类有demo()方法则,父类中也有demo()方法,则调用的是trait的demo()方法。
③如果trait类没有demo()方法,则调用父类的demo()方法。

<?php

namespace _0124;
use PDO;

trait Db {
	// 连接数据库
	public function connect($dsn, $username, $password) {
		echo '数据库连接成功';
		echo '<hr>';
		return new PDO($dsn, $username, $password);
	}
}
trait Query {
	// 查询满足条件的第一条记录
	public function get($pdo, $where = '') {
		// 处理查询条件
		$where = empty($where) ? $where : ' WHERE ' . $where;
		// $sql = 'SELECT *FROM ' . 'movies' . $where . ' LIMIT 1';
		// 拼装SQL语句, 创建预处理对象PDOStatment
		$sql = 'SELECT *FROM ' . 'movies' . $where . ' LIMIT 1';
		$stmt = $pdo->prepare($sql);
		$stmt->execute();
		// 生成的SQL语句: SELECT * FROM `user` WHERE age < 30 LIMIT 1
		// die($stmt->debugDumpParams());
		//SQL: [42] SELECT *FROM movies WHERE mov_id<3 LIMIT 1 Params: 0
		// 将获取的结果集以一维数组的形式返回给调用者
		return $stmt->fetchAll(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:dbname=sordidez';
$username = 'root';
$password = 'root';
// 实例化并连接数据库, 底层是调用 Trait类Db::connect()
$client = new Client($dsn, $username, $password);
// 获取满足条件的第一条记录, 底层是调用 Trait类Query::get()
echo '<pre>' . print_r($client->find('mov_id<3'), true);

?>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值