面向对象编程(Object-Oriented Programming,OOP),是现代编程语言的核心特性,也是写出可维护、可扩展、可复用代码的关键。
PHP,从PHP 5开始,引入了完整的面向对象模型,包括类、对象、继承、接口、抽象类、命名空间、性状(Trait)等特性。PHP 7进一步完善了面向对象模型,增加了返回类型声明、标量类型声明、匿名类等特性。
在之前的文章中,我们学习了PHP的各种功能开发,但对面向对象涉及不多。今天,我们来详细学习PHP面向对象编程,从基础概念到高级特性,从设计原则到实战案例,帮你掌握面向对象编程,写出更优雅、更易维护的PHP代码。
为什么需要面向对象
面向过程 vs 面向对象
面向过程编程:以函数为中心,把程序分解为一系列函数,数据和函数是分离的。适合简单、小规模的程序。
// 面向过程:定义函数操作数据
function getUser($id) {
global $db;
return $db->query("SELECT * FROM users WHERE id = $id")->fetch();
}
function updateUser($id, $data) {
global $db;
// ...
}
$user = getUser(1);
updateUser(1, ['name' => '张三']);面向对象编程:以对象为中心,把数据和操作数据的方法封装在一起(类)。适合复杂、中大规模的程序。
// 面向对象:定义类,封装数据和方法
class User {
private $id;
private $name;
private $db;
public function __construct($id, PDO $db) {
$this->id = $id;
$this->db = $db;
$this->load();
}
private function load() {
$stmt = $this->db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$this->id]);
$data = $stmt->fetch();
$this->name = $data['name'];
}
public function updateName($name) {
$this->name = $name;
$stmt = $this->db->prepare("UPDATE users SET name = ? WHERE id = ?");
$stmt->execute([$this->name, $this->id]);
}
public function getName() {
return $this->name;
}
}
$user = new User(1, $db);
$user->updateName('张三');
echo $user->getName();面向对象的优势
- 封装:把数据和操作数据的方法封装在一起,隐藏内部实现细节,只暴露必要的接口。降低了代码的复杂度,提高了安全性。
- 继承:子类可以继承父类的属性和方法,实现代码复用,减少重复代码。
- 多态:不同的对象,对同一个方法,可以有不同的实现。提高了代码的灵活性和可扩展性。
- 抽象:抽象出事物的本质特征,忽略非本质细节。降低了复杂度,提高了代码的可理解性。
- 可维护性:面向对象的代码,结构清晰,模块化,易于维护和修改。
- 可扩展性:通过继承、接口、多态,可以方便地扩展功能,而不需要修改已有代码。
- 可复用性:类可以在多个项目中复用,提高开发效率。
类与对象
定义类
class Person {
// 属性(成员变量)
public $name;
public $age;
// 方法(成员函数)
public function sayHello() {
echo "你好,我是 {$this->name},今年 {$this->age} 岁。";
}
}创建对象
$person = new Person();
$person->name = '张三';
$person->age = 25;
$person->sayHello(); // 输出:你好,我是 张三,今年 25 岁。构造函数和析构函数
class Person {
public $name;
public $age;
// 构造函数:创建对象时自动调用
public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
echo "对象被创建了\n";
}
// 析构函数:对象销毁时自动调用
public function __destruct() {
echo "对象被销毁了\n";
}
}
$person = new Person('张三', 25); // 输出:对象被创建了
// 脚本结束时,输出:对象被销毁了$this关键字
$this指向当前对象,用于在类的方法中访问当前对象的属性和方法。
class Person {
public $name;
public function setName($name) {
$this->name = $name; // $this->name 是对象的属性,$name 是方法参数
}
public function getName() {
return $this->name;
}
}访问控制(封装)
PHP提供了三种访问控制修饰符:
- public(公共):可以在任何地方访问,类内部、子类、外部都可以。
- protected(受保护):只能在类内部和子类中访问,外部不能访问。
- private(私有):只能在类内部访问,子类和外部都不能访问。
class User {
public $username; // 公共,任何地方可访问
protected $email; // 受保护,类内部和子类可访问
private $password; // 私有,只有类内部可访问
public function __construct($username, $email, $password) {
$this->username = $username;
$this->email = $email;
$this->password = $password;
}
// 公共方法:外部可以调用
public function getEmail() {
return $this->email;
}
// 受保护方法:只有类内部和子类可以调用
protected function validatePassword($password) {
return password_verify($password, $this->password);
}
// 私有方法:只有类内部可以调用
private function hashPassword($password) {
return password_hash($password, PASSWORD_DEFAULT);
}
}
$user = new User('zhangsan', 'zhangsan@example.com', '123456');
echo $user->username; // 可以,public
echo $user->getEmail(); // 可以,public方法
// echo $user->email; // 错误,protected
// echo $user->password; // 错误,privateGetter和Setter
对于私有属性,通常提供公共的getter和setter方法来访问和修改,这样可以在方法中加入验证逻辑。
class User {
private $name;
private $age;
public function getName() {
return $this->name;
}
public function setName($name) {
if (empty($name)) {
throw new InvalidArgumentException('名字不能为空');
}
$this->name = $name;
}
public function getAge() {
return $this->age;
}
public function setAge($age) {
if (!is_numeric($age) || $age < 0 || $age > 150) {
throw new InvalidArgumentException('无效的年龄');
}
$this->age = $age;
}
}继承
继承,是子类继承父类的属性和方法,实现代码复用。
基本继承
// 父类(基类)
class Animal {
protected $name;
public function __construct($name) {
$this->name = $name;
}
public function eat() {
echo "{$this->name} 在吃东西。\n";
}
public function sleep() {
echo "{$this->name} 在睡觉。\n";
}
}
// 子类(派生类)
class Dog extends Animal {
public function bark() {
echo "{$this->name} 在汪汪叫。\n";
}
}
class Cat extends Animal {
public function meow() {
echo "{$this->name} 在喵喵叫。\n";
}
}
$dog = new Dog('旺财');
$dog->eat(); // 继承自Animal
$dog->sleep(); // 继承自Animal
$dog->bark(); // Dog自己的方法
$cat = new Cat('咪咪');
$cat->eat();
$cat->meow();方法重写(Override)
子类可以重写父类的方法,提供不同的实现。
class Animal {
protected $name;
public function __construct($name) {
$this->name = $name;
}
public function makeSound() {
echo "{$this->name} 发出了声音。\n";
}
}
class Dog extends Animal {
// 重写父类的makeSound方法
public function makeSound() {
echo "{$this->name} 汪汪叫。\n";
}
}
class Cat extends Animal {
// 重写父类的makeSound方法
public function makeSound() {
echo "{$this->name} 喵喵叫。\n";
}
}
$animals = [new Dog('旺财'), new Cat('咪咪')];
foreach ($animals as $animal) {
$animal->makeSound(); // 多态:同一个方法,不同的实现
}parent关键字
在子类中,可以用parent::调用父类的方法。
class Dog extends Animal {
public function __construct($name, $breed) {
parent::__construct($name); // 调用父类的构造函数
$this->breed = $breed;
}
public function makeSound() {
parent::makeSound(); // 调用父类的方法
echo "{$this->name} 汪汪叫。\n";
}
}final关键字
final class:不能被继承final method:不能被子类重写
final class Util {
// 这个类不能被继承
}
class Base {
final public function cannotOverride() {
// 这个方法不能被子类重写
}
}抽象类
抽象类,是不能被实例化的类,只能被继承。抽象类可以包含抽象方法(没有实现的方法),子类必须实现这些抽象方法。
abstract class Shape {
protected $name;
public function __construct($name) {
$this->name = $name;
}
// 抽象方法:没有实现,子类必须实现
abstract public function area();
// 普通方法:有实现,子类可以继承或重写
public function getName() {
return $this->name;
}
}
class Circle extends Shape {
private $radius;
public function __construct($radius) {
parent::__construct('圆形');
$this->radius = $radius;
}
// 必须实现抽象方法
public function area() {
return pi() * $this->radius * $this->radius;
}
}
class Rectangle extends Shape {
private $width;
private $height;
public function __construct($width, $height) {
parent::__construct('矩形');
$this->width = $width;
$this->height = $height;
}
public function area() {
return $this->width * $this->height;
}
}
$shapes = [new Circle(5), new Rectangle(4, 6)];
foreach ($shapes as $shape) {
echo $shape->getName() . "的面积是:" . $shape->area() . "\n";
}抽象类的作用:
- 定义规范:抽象方法定义了子类必须实现的方法,形成了规范。
- 代码复用:抽象类可以包含普通方法,子类可以继承复用。
- 强制实现:子类必须实现所有抽象方法,否则子类也必须是抽象类。
接口
接口,是一种特殊的抽象类,只包含方法签名,不包含实现。类可以实现(implements)一个或多个接口,必须实现接口中定义的所有方法。
定义接口
interface Logger {
public function log($message, $level = 'info');
public function info($message);
public function error($message);
}
interface Cache {
public function get($key);
public function set($key, $value, $ttl = 3600);
public function delete($key);
public function clear();
}实现接口
class FileLogger implements Logger {
private $filePath;
public function __construct($filePath) {
$this->filePath = $filePath;
}
// 必须实现接口的所有方法
public function log($message, $level = 'info') {
$log = sprintf("[%s] [%s] %s\n", date('Y-m-d H:i:s'), $level, $message);
file_put_contents($this->filePath, $log, FILE_APPEND);
}
public function info($message) {
$this->log($message, 'info');
}
public function error($message) {
$this->log($message, 'error');
}
}
class DatabaseLogger implements Logger {
private $db;
public function __construct(PDO $db) {
$this->db = $db;
}
public function log($message, $level = 'info') {
$stmt = $this->db->prepare("INSERT INTO logs (message, level, created_at) VALUES (?, ?, NOW())");
$stmt->execute([$message, $level]);
}
public function info($message) {
$this->log($message, 'info');
}
public function error($message) {
$this->log($message, 'error');
}
}实现多个接口
PHP支持实现多个接口(接口的多继承):
class RedisCache implements Cache, Logger {
private $redis;
public function __construct(Redis $redis) {
$this->redis = $redis;
}
// Cache接口的方法
public function get($key) {
return $this->redis->get($key);
}
public function set($key, $value, $ttl = 3600) {
return $this->redis->setex($key, $ttl, $value);
}
public function delete($key) {
return $this->redis->del($key);
}
public function clear() {
return $this->redis->flushDB();
}
// Logger接口的方法
public function log($message, $level = 'info') {
$this->redis->lPush('logs', sprintf("[%s] [%s] %s", date('Y-m-d H:i:s'), $level, $message));
}
public function info($message) {
$this->log($message, 'info');
}
public function error($message) {
$this->log($message, 'error');
}
}接口继承
接口可以继承其他接口:
interface AdvancedCache extends Cache {
public function increment($key, $step = 1);
public function decrement($key, $step = 1);
public function exists($key);
}抽象类 vs 接口
| 特性 | 抽象类 | 接口 |
|---|---|---|
| 实例化 | 不能 | 不能 |
| 继承 | 单继承(extends) | 多实现(implements) |
| 方法 | 可以有抽象方法和普通方法 | 只能有抽象方法(PHP 8之前) |
| 属性 | 可以有属性 | 不能有属性(只能有常量) |
| 构造函数 | 可以有 | 不能有 |
| 访问控制 | 可以有public/protected/private | 只能是public |
选择建议:
- 表示"是什么"(is-a)的关系,用抽象类(如Dog是Animal)
- 表示"能做什么"(can-do)的关系,用接口(如Logger能记录日志)
- 需要代码复用,用抽象类
- 需要多继承,用接口
性状(Trait)
Trait(性状),是PHP 5.4引入的特性,用于解决PHP单继承的限制。Trait可以被多个类复用,类似代码片段的复用。
定义和使用Trait
trait Logger {
public function log($message) {
echo "[LOG] " . date('Y-m-d H:i:s') . " - " . $message . "\n";
}
public function error($message) {
echo "[ERROR] " . date('Y-m-d H:i:s') . " - " . $message . "\n";
}
}
trait Cacheable {
protected $cache = [];
public function setCache($key, $value) {
$this->cache[$key] = $value;
}
public function getCache($key) {
return $this->cache[$key] ?? null;
}
}
class User {
use Logger, Cacheable; // 使用多个Trait
public function register($name) {
$this->log("用户注册:$name");
$this->setCache('current_user', $name);
}
}
$user = new User();
$user->register('张三');
echo $user->getCache('current_user');Trait的冲突解决
如果多个Trait有同名方法,会产生冲突,需要用insteadof和as解决。
trait A {
public function hello() {
echo "Hello from A\n";
}
}
trait B {
public function hello() {
echo "Hello from B\n";
}
}
class MyClass {
use A, B {
B::hello insteadof A; // 用B的hello方法,代替A的
A::hello as helloFromA; // 把A的hello方法改名为helloFromA
}
}
$obj = new MyClass();
$obj->hello(); // 输出:Hello from B
$obj->helloFromA(); // 输出:Hello from ATrait的使用场景:
- 多个类需要复用相同的方法,但这些类不能继承同一个父类
- 需要横向复用代码,而不是纵向继承
- 组合多个功能模块
注意:Trait虽然强大,但不要滥用。过度使用Trait会导致代码结构混乱,难以维护。如果能用继承或组合解决,就不要用Trait。
命名空间
命名空间(Namespace),是PHP 5.3引入的特性,用于解决类名、函数名、常量名的冲突问题,也用于组织代码结构。
定义命名空间
<?php
namespace App\Models;
class User {
// ...
}
// 另一个文件
namespace App\Controllers;
use App\Models\User;
class UserController {
public function show($id) {
$user = new User();
// ...
}
}命名空间的层次
namespace App\Models\Blog; // 子命名空间
class Post {
// ...
}
class Category {
// ...
}使用命名空间
// 完全限定名称
$user = new \App\Models\User();
// 导入(use)
use App\Models\User;
use App\Models\Post as BlogPost; // 别名
$user = new User();
$post = new BlogPost();
// 导入多个
use App\Models\{User, Post, Category};命名空间的作用
- 避免命名冲突:不同的包可以有同名的类,通过命名空间区分。
- 组织代码结构:按照目录结构组织命名空间,代码结构清晰。
- 自动加载:配合PSR-4自动加载规范,根据命名空间自动加载类文件。
现代PHP开发,几乎所有项目都使用命名空间。PSR-4是PHP-FIG制定的自动加载规范,规定了命名空间和目录结构的对应关系。
自动加载
自动加载(Autoloading),是指在使用类时,自动加载对应的类文件,不需要手动require/include。
splautoloadregister
spl_autoload_register(function ($className) {
// 把命名空间转换为文件路径
$file = __DIR__ . '/' . str_replace('\\', '/', $className) . '.php';
if (file_exists($file)) {
require_once $file;
}
});
// 现在可以直接使用类,不需要手动require
$user = new App\Models\User();Composer自动加载
现代PHP项目,通常使用Composer的自动加载。在composer.json中配置:
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}然后在入口文件中:
require_once __DIR__ . '/vendor/autoload.php';
// 自动加载App命名空间下的类
$user = new App\Models\User();静态成员
静态成员(Static),属于类本身,不属于某个对象。可以通过类名直接访问,不需要创建对象。
静态属性和方法
class Counter {
// 静态属性:所有对象共享
public static $count = 0;
// 静态方法:属于类,不属于对象
public static function increment() {
self::$count++;
}
public static function getCount() {
return self::$count;
}
}
// 访问静态成员,不需要创建对象
echo Counter::$count; // 0
Counter::increment();
echo Counter::getCount(); // 1self vs static
self:指向当前类,静态绑定static:指向调用类,延迟静态绑定(PHP 5.3+)
class Base {
public static function whoAmI() {
echo "Base\n";
}
public static function testSelf() {
self::whoAmI(); // 静态绑定,总是调用Base的whoAmI
}
public static function testStatic() {
static::whoAmI(); // 延迟静态绑定,调用实际类的whoAmI
}
}
class Child extends Base {
public static function whoAmI() {
echo "Child\n";
}
}
Child::testSelf(); // 输出:Base
Child::testStatic(); // 输出:Child静态成员的使用场景:
- 计数器:统计类被实例化的次数
- 工具类:不需要保存状态的工具方法(如字符串处理、数学计算)
- 单例模式:控制类只能有一个实例
- 工厂方法:创建对象的静态方法
注意:静态成员虽然方便,但不要过度使用。静态成员会增加耦合,降低可测试性。
类型声明
PHP 7引入了标量类型声明和返回类型声明,PHP 7.4引入了属性类型声明,让PHP的类型系统更完善。
参数类型声明
// 标量类型声明(PHP 7+)
function add(int $a, int $b): int {
return $a + $b;
}
function greet(string $name): string {
return "Hello, $name!";
}
// 类类型声明
function saveUser(User $user): bool {
// ...
}
// 数组类型声明
function sum(array $numbers): int {
return array_sum($numbers);
}
// 可空类型(PHP 7.1+)
function findUser(?int $id): ?User {
if ($id === null) {
return null;
}
// ...
}返回类型声明
function getUser(int $id): User {
// 必须返回User类型
}
function findUser(int $id): ?User {
// 可以返回User或null
}
// void返回类型(PHP 7.1+)
function log(string $message): void {
// 不返回任何值
}属性类型声明(PHP 7.4+)
class User {
public int $id;
public string $name;
public ?string $email;
protected array $roles = [];
private bool $active = true;
}类型声明的好处:
- 代码更清晰:一看就知道参数和返回值的类型
- 提前发现错误:类型不匹配时,会抛出TypeError,提前发现bug
- IDE支持更好:IDE可以根据类型声明提供更好的代码提示和补全
- 代码更可靠:减少了类型相关的bug
魔术方法
魔术方法(Magic Methods),是PHP中以__开头的特殊方法,在特定的时机自动调用。
常用魔术方法
class MyClass {
// 构造函数:创建对象时调用
public function __construct() {
echo "构造函数\n";
}
// 析构函数:对象销毁时调用
public function __destruct() {
echo "析构函数\n";
}
// __get:访问不存在的属性时调用
public function __get($name) {
echo "获取不存在的属性:$name\n";
return null;
}
// __set:设置不存在的属性时调用
public function __set($name, $value) {
echo "设置不存在的属性:$name = $value\n";
}
// __isset:对不存在的属性调用isset()时调用
public function __isset($name) {
echo "检查不存在的属性:$name\n";
return false;
}
// __unset:对不存在的属性调用unset()时调用
public function __unset($name) {
echo "销毁不存在的属性:$name\n";
}
// __call:调用不存在的方法时调用
public function __call($name, $arguments) {
echo "调用不存在的方法:$name,参数:" . implode(', ', $arguments) . "\n";
}
// __callStatic:调用不存在的静态方法时调用
public static function __callStatic($name, $arguments) {
echo "调用不存在的静态方法:$name\n";
}
// __toString:对象被当作字符串时调用
public function __toString() {
return "这是MyClass的对象";
}
// __invoke:对象被当作函数调用时调用
public function __invoke($arg) {
echo "对象被当作函数调用,参数:$arg\n";
}
// __clone:对象被克隆时调用
public function __clone() {
echo "对象被克隆\n";
}
}魔术方法虽然强大,但不要滥用。过度使用魔术方法会导致代码难以理解和调试。大多数情况下,应该用明确的方法代替魔术方法。
设计原则
面向对象编程,有一些经典的设计原则,指导我们写出更好的面向对象代码。
单一职责原则(SRP)
一个类,应该只有一个引起它变化的原因,即一个类只负责一项职责。
// 不好:User类负责用户数据、验证、数据库操作、邮件发送
class User {
public function validate() { /* ... */ }
public function save() { /* ... */ }
public function sendWelcomeEmail() { /* ... */ }
}
// 好:每个类只负责一项职责
class User { /* 用户数据 */ }
class UserValidator { /* 用户验证 */ }
class UserRepository { /* 数据库操作 */ }
class UserMailer { /* 邮件发送 */ }开放封闭原则(OCP)
软件实体(类、模块、函数),应该对扩展开放,对修改封闭。即添加新功能时,应该通过扩展而不是修改已有代码。
// 不好:每增加一种形状,都要修改AreaCalculator类
class AreaCalculator {
public function calculate($shape) {
if ($shape instanceof Circle) {
return pi() * $shape->radius * $shape->radius;
} elseif ($shape instanceof Rectangle) {
return $shape->width * $shape->height;
}
// 每增加一种形状,都要加一个elseif
}
}
// 好:通过接口扩展,不需要修改已有代码
interface Shape {
public function area();
}
class Circle implements Shape {
public function area() { /* ... */ }
}
class Rectangle implements Shape {
public function area() { /* ... */ }
}
// 增加新形状,只需要新增一个类,不需要修改AreaCalculator
class AreaCalculator {
public function calculate(Shape $shape) {
return $shape->area();
}
}里氏替换原则(LSP)
子类必须能够替换父类,而不影响程序的正确性。即子类应该遵守父类的行为约定。
// 不好:Square继承Rectangle,但重写了setWidth和setHeight,违反了里氏替换原则
class Rectangle {
protected $width;
protected $height;
public function setWidth($width) { $this->width = $width; }
public function setHeight($height) { $this->height = $height; }
public function area() { return $this->width * $this->height; }
}
class Square extends Rectangle {
public function setWidth($width) {
$this->width = $width;
$this->height = $width; // 同时设置高
}
public function setHeight($height) {
$this->width = $height; // 同时设置宽
$this->height = $height;
}
}
// 测试函数,期望Rectangle的行为
function testRectangle(Rectangle $r) {
$r->setWidth(5);
$r->setHeight(4);
return $r->area(); // 期望20,但传入Square时返回16
}
// 好:Square不继承Rectangle,而是独立实现,或继承自更通用的Shape
interface Shape {
public function area();
}接口隔离原则(ISP)
客户端不应该依赖它不需要的接口。即接口应该小而专一,不要把很多方法放在一个大接口里。
// 不好:大接口,实现类必须实现不需要的方法
interface Worker {
public function work();
public function eat();
public function sleep();
}
class HumanWorker implements Worker {
public function work() { /* ... */ }
public function eat() { /* ... */ }
public function sleep() { /* ... */ }
}
class RobotWorker implements Worker {
public function work() { /* ... */ }
public function eat() { /* 机器人不需要吃饭,但必须实现 */ }
public function sleep() { /* 机器人不需要睡觉,但必须实现 */ }
}
// 好:小接口,实现类只实现需要的接口
interface Workable {
public function work();
}
interface Eatable {
public function eat();
}
interface Sleepable {
public function sleep();
}
class HumanWorker implements Workable, Eatable, Sleepable {
// 实现所有需要的方法
}
class RobotWorker implements Workable {
// 只需要实现work方法
}依赖倒置原则(DIP)
高层模块不应该依赖低层模块,两者都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象。
// 不好:高层模块UserService依赖低层模块MySQLDatabase
class MySQLDatabase {
public function query($sql) { /* ... */ }
}
class UserService {
private $db;
public function __construct() {
$this->db = new MySQLDatabase(); // 直接依赖具体实现
}
}
// 好:依赖抽象(接口)
interface Database {
public function query($sql);
}
class MySQLDatabase implements Database {
public function query($sql) { /* ... */ }
}
class PostgreSQLDatabase implements Database {
public function query($sql) { /* ... */ }
}
class UserService {
private $db;
public function __construct(Database $db) { // 依赖接口,不依赖具体实现
$this->db = $db;
}
}
// 使用时,可以注入不同的实现
$service = new UserService(new MySQLDatabase());
// 或者
$service = new UserService(new PostgreSQLDatabase());这五个原则,合称为SOLID原则,是面向对象设计的基本原则。遵循这些原则,能让我们的代码更易维护、更易扩展、更易测试。
实战:一个简单的博客系统类设计
下面,用面向对象的方式,设计一个简单的博客系统的核心类。
<?php
namespace App\Models;
// 文章类
class Post {
private $id;
private $title;
private $slug;
private $content;
private $excerpt;
private $categoryId;
private $cover;
private $tags;
private $views;
private $status;
private $createdAt;
private $updatedAt;
public function __construct(array $data = []) {
foreach ($data as $key => $value) {
if (property_exists($this, $key)) {
$this->$key = $value;
}
}
}
// Getter和Setter
public function getId() { return $this->id; }
public function getTitle() { return $this->title; }
public function setTitle($title) {
if (empty($title)) throw new \InvalidArgumentException('标题不能为空');
$this->title = $title;
$this->slug = $this->generateSlug($title);
}
// ... 其他Getter和Setter
public function getWordCount() {
return mb_strlen(strip_tags($this->content));
}
public function getReadTime() {
$wordsPerMinute = 300;
return max(1, ceil($this->getWordCount() / $wordsPerMinute));
}
public function isPublished() {
return $this->status === 'published';
}
private function generateSlug($title) {
// 生成URL友好的slug
return strtolower(trim(preg_replace('/[^a-zA-Z0-9]+/', '-', $title), '-'));
}
}
// 文章仓储接口
interface PostRepository {
public function findById($id): ?Post;
public function findBySlug($slug): ?Post;
public function findAll($page = 1, $perPage = 10): array;
public function findByCategory($categoryId, $page = 1, $perPage = 10): array;
public function search($keyword, $page = 1, $perPage = 10): array;
public function save(Post $post): bool;
public function delete($id): bool;
public function count(): int;
}
// MySQL文章仓储实现
class MySQLPostRepository implements PostRepository {
private $db;
public function __construct(\PDO $db) {
$this->db = $db;
}
public function findById($id): ?Post {
$stmt = $this->db->prepare("SELECT * FROM blog_posts WHERE id = ?");
$stmt->execute([$id]);
$data = $stmt->fetch(\PDO::FETCH_ASSOC);
return $data ? new Post($data) : null;
}
public function findBySlug($slug): ?Post {
$stmt = $this->db->prepare("SELECT * FROM blog_posts WHERE slug = ? AND status = 'published'");
$stmt->execute([$slug]);
$data = $stmt->fetch(\PDO::FETCH_ASSOC);
return $data ? new Post($data) : null;
}
public function findAll($page = 1, $perPage = 10): array {
$offset = ($page - 1) * $perPage;
$stmt = $this->db->prepare("SELECT * FROM blog_posts WHERE status = 'published' ORDER BY created_at DESC LIMIT ? OFFSET ?");
$stmt->bindValue(1, $perPage, \PDO::PARAM_INT);
$stmt->bindValue(2, $offset, \PDO::PARAM_INT);
$stmt->execute();
$posts = [];
while ($data = $stmt->fetch(\PDO::FETCH_ASSOC)) {
$posts[] = new Post($data);
}
return $posts;
}
// ... 其他方法的实现
public function save(Post $post): bool {
// 保存文章到数据库
}
public function delete($id): bool {
$stmt = $this->db->prepare("DELETE FROM blog_posts WHERE id = ?");
return $stmt->execute([$id]);
}
public function count(): int {
return (int)$this->db->query("SELECT COUNT(*) FROM blog_posts WHERE status = 'published'")->fetchColumn();
}
}
// 文章服务类
class PostService {
private $repository;
private $cache;
public function __construct(PostRepository $repository, ?Cache $cache = null) {
$this->repository = $repository;
$this->cache = $cache;
}
public function getPost($id): ?Post {
$cacheKey = "post:$id";
if ($this->cache && $this->cache->exists($cacheKey)) {
return $this->cache->get($cacheKey);
}
$post = $this->repository->findById($id);
if ($post && $this->cache) {
$this->cache->set($cacheKey, $post, 3600);
}
return $post;
}
public function getPosts($page = 1, $perPage = 10): array {
return $this->repository->findAll($page, $perPage);
}
public function createPost(array $data): Post {
$post = new Post();
$post->setTitle($data['title']);
// ... 设置其他属性
$this->repository->save($post);
if ($this->cache) {
$this->cache->clear('posts:*'); // 清除列表缓存
}
return $post;
}
public function deletePost($id): bool {
$result = $this->repository->delete($id);
if ($result && $this->cache) {
$this->cache->delete("post:$id");
$this->cache->clear('posts:*');
}
return $result;
}
}这个设计,展示了面向对象的核心思想:
- 封装:Post类封装了文章的数据和行为
- 抽象:PostRepository接口抽象了数据访问层
- 多态:MySQLPostRepository实现了PostRepository,可以替换为其他实现
- 依赖倒置:PostService依赖PostRepository接口,不依赖具体实现
- 单一职责:每个类只负责一项职责
总结
面向对象编程,是现代PHP开发的基础,也是写出可维护、可扩展、可复用代码的关键。
核心要点:
- 类与对象:类是模板,对象是实例;属性和方法;构造函数和析构函数;$this关键字
- 访问控制:public/protected/private;封装;Getter和Setter
- 继承:extends;方法重写;parent关键字;final关键字
- 抽象类:abstract;抽象方法;定义规范和代码复用
- 接口:interface;implements;多实现;定义能力规范
- 性状(Trait):use;代码横向复用;冲突解决
- 命名空间:namespace;use;避免命名冲突;组织代码
- 自动加载:splautoloadregister;Composer PSR-4
- 静态成员:static;self vs static;工具类、单例、计数器
- 类型声明:参数类型、返回类型、属性类型、可空类型;代码更清晰更可靠
- 魔术方法:construct/destruct/get/set/call/toString等;特定时机自动调用
- SOLID原则:单一职责、开放封闭、里氏替换、接口隔离、依赖倒置
- 实战:Post类、PostRepository接口、MySQLPostRepository实现、PostService服务类
面向对象编程,不仅仅是语法,更是一种思维方式。它让我们从"怎么做"转向"谁来做",从"过程"转向"对象",从"细节"转向"抽象"。
掌握面向对象编程,能让你写出更优雅、更易维护、更易扩展的PHP代码,也能让你更好地理解和使用现代PHP框架(如Laravel、Symfony)。
"面向对象编程,是用代码来模拟现实世界。"希望这篇文章,能帮你更好地理解和使用PHP面向对象编程,写出更优雅的代码。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录