PHP中的魔术方法

魔术方法是PHP中以双下划线 __ 开头,并具有特殊用途的方法。它们会在特定情况下被自动调用,赋予开发者在不修改类外部代码的情况下,改变对象行为的能力。

常用的PHP魔术方法:

方法名调用时机用途
__construct()在创建新对象时自动调用用于初始化对象的属性,例如设置默认值等
__destruct()在对象被销毁前自动调用用于执行清理工作,例如关闭连接、释放资源等
__get($name)在访问对象不可访问的属性时自动调用用于实现属性的重载,例如动态生成属性值、实现属性的延迟加载等
__set($name, $value)在给对象不可访问的属性赋值时自动调用用于实现属性的重载,例如对属性值进行验证、记录属性修改日志等
__isset($name)使用 isset() 或 empty() 函数检查对象不可访问的属性是否存在时自动调用用于实现属性的重载,例如判断属性是否设置、是否为空等
__unset($name)使用 unset() 函数删除对象不可访问的属性时自动调用用于实现属性的重载,例如禁止删除某些属性、记录属性删除日志等
__call($name, $arguments)在调用对象不可访问的方法时自动调用用于实现方法的重载,例如处理未定义方法的调用、实现代理模式等
__callStatic($name, $arguments)在调用类不可访问的静态方法时自动调用用于实现静态方法的重载,例如处理未定义静态方法的调用、实现单例模式等
__toString()当将对象转换为字符串时自动调用用于自定义对象的字符串表示形式,例如打印对象信息时
__invoke()当将对象作为函数调用时自动调用用于将对象实例作为函数使用,例如实现回调函数、闭包等
__clone()使用 clone 关键字克隆对象时自动调用用于控制对象的克隆行为,例如实现深拷贝、防止对象被克隆等
__sleep()使用 serialize() 函数序列化对象时自动调用用于指定哪些属性需要被序列化,例如排除不需要序列化的属性、优化序列化性能等
__wakeup()使用 unserialize() 函数反序列化对象时自动调用用于在反序列化后初始化对象,例如恢复数据库连接、重新建立对象依赖关系等
__autoload($class)当试图实例化一个尚未定义的类时自动调用用于自动加载类文件,例如根据类名自动加载对应路径的类文件
__set_state($properties)使用 var_export() 函数导出对象时自动调用用于自定义对象的导出格式,例如只导出特定属性、对属性值进行加密等
__debugInfo()使用 var_dump() 函数打印对象调试信息时自动调用用于自定义对象在调试时的输出信息,例如隐藏敏感信息、显示更友好的调试信息

举例说明:

  1. __construct() 和 __destruct()
      class DatabaseConnection {
    private $connection;

    public function __construct($host, $username, $password, $database) {
        $this->connection = new mysqli($host, $username, $password, $database);
    }

    public function __destruct() {
        $this->connection->close();
    }

    // ... other methods ...
}
    
  • __construct() 在创建 DatabaseConnection 对象时建立数据库连接。
  • __destruct() 在对象销毁前关闭数据库连接,释放资源。
  1. __get() 和 __set()
      class User {
    private $data = [];

    public function __get($name) {
        if (array_key_exists($name, $this->data)) {
            return $this->data[$name];
        }
        return null;
    }

    public function __set($name, $value) {
        $this->data[$name] = $value;
    }
}

$user = new User();
$user->name = "John Doe"; // 调用 __set() 方法
echo $user->name; // 调用 __get() 方法,输出: John Doe
    
  • __get() 允许访问私有属性 $data 中的值。
  • __set() 允许设置私有属性 $data 中的值。

总结:

魔术方法为 PHP 开发者提供了强大的灵活性,允许开发者自定义对象的行为,实现更优雅的代码结构和更强大的功能。

PHP 面向对象的理解,以及 PHP 是如何实现面向对象的

一、 对 PHP 面向对象的理解

面向对象编程(OOP)是一种软件开发范式,它将数据和操作数据的方法封装在一起,形成“对象”。 这些对象是类的实例,类是对象的蓝图。PHP 从 PHP5 开始支持面向对象编程,并逐渐完善。

OOP 的核心概念包括:

  • 类(Class): 对象的蓝图,定义了对象的属性和方法。
  • 对象(Object): 类的实例,拥有类定义的属性和方法。
  • 封装(Encapsulation): 将数据和操作数据的方法封装在对象内部,隐藏内部实现细节,对外提供接口访问。
  • 继承(Inheritance): 子类可以继承父类的属性和方法,实现代码复用和扩展。
  • 多态(Polymorphism): 同一个方法在不同的类中可以有不同的实现方式。

OOP 的优势:

  • 代码重用性高: 通过继承和多态,可以减少代码冗余,提高开发效率。
  • 可维护性强: 封装性使得代码易于维护和修改,降低模块之间的耦合度。
  • 可扩展性好: 易于添加新功能,而无需修改现有代码。

二、 PHP 如何实现面向对象

PHP 使用类和对象来实现面向对象编程,并支持封装、继承、多态等特性。

1. 类和对象:

  • 使用 class 关键字定义类,类中可以包含属性和方法。
  • 使用 new 关键字实例化对象。

示例:

      class Car { // 定义一个 Car 类
    public $brand; // 公共属性
    protected $model; // 受保护的属性
    private $price; // 私有属性

    public function __construct($brand, $model, $price) { // 构造函数
        $this->brand = $brand;
        $this->model = $model;
        $this->price = $price;
    }

    public function getBrand() { // 公共方法
        return $this->brand;
    }
}

$myCar = new Car("Toyota", "Camry", 25000); // 实例化一个 Car 对象
echo $myCar->getBrand(); // 输出: Toyota
    

2. 封装:

  • PHP 使用 public、protected 和 private 三个访问修饰符来控制属性和方法的访问权限,实现封装性。

3. 继承:

  • 使用 extends 关键字实现继承。
  • 子类可以继承父类的属性和方法,并可以添加自己的属性和方法。
  • PHP 只支持单继承,一个子类只能继承一个父类。

示例:

      class SportsCar extends Car { // SportsCar 类继承 Car 类
    public $speed;

    public function __construct($brand, $model, $price, $speed) {
        parent::__construct($brand, $model, $price); // 调用父类构造函数
        $this->speed = $speed;
    }
}

$mySportsCar = new SportsCar("Ferrari", "458 Italia", 350000, 200); 
echo $mySportsCar->getBrand(); // 输出: Ferrari,继承自父类
    

4. 多态:

  • PHP 通过接口和抽象类实现多态。
  • 接口使用 interface 关键字定义,只能包含方法声明,不能包含属性和方法实现。
  • 抽象类使用 abstract 关键字定义,可以包含属性和方法,但抽象方法必须由子类实现。

示例:

      interface Drivable { // 定义一个 Drivable 接口
    public function drive();
}

class Car implements Drivable { // Car 类实现 Drivable 接口
    public function drive() {
        echo "Driving a car";
    }
}

class Bike implements Drivable { // Bike 类实现 Drivable 接口
    public function drive() {
        echo "Riding a bike";
    }
}
    

总结:

PHP 提供了丰富的语法和特性来支持面向对象编程,合理地运用 OOP 原则和技巧,可以提高代码的质量和开发效率。对于拥有 多 年 PHP 开发经验的开发者来说,深入理解 OOP 的概念和 PHP 的实现机制,并能在实际项目中灵活运用,是必备的技能。

Laravel 被誉为 “优雅的 PHP 框架”,其优雅之处体现在多个方面,我将从以下几个方面进行阐述:

1. 简洁易懂的语法:

  • 富有表现力的语法: Laravel 利用了 PHP 的最新特性,提供了简洁而富有表现力的语法,例如链式调用、集合操作、路由定义等,使得代码更易读、易写。
  • 清晰的代码结构: Laravel 遵循 MVC 架构,并提供了清晰的目录结构和代码组织方式,使得大型项目更易于管理和维护。

2. 强大的功能和工具:

  • Eloquent ORM: 提供了简洁优雅的 ActiveRecord 实现,简化了数据库操作。
  • Artisan 命令行工具: 提供了丰富的命令行工具,用于生成代码、运行任务、管理数据库等操作,提高了开发效率。
  • Blade 模板引擎: 提供了简洁易用的模板语法,支持模板继承和组件化开发。
  • 丰富的扩展包: 拥有庞大的扩展包生态系统,可以方便地集成各种第三方服务和功能。

3. 注重开发者体验:

  • 详细的文档: 提供了完整、清晰、易懂的文档,方便开发者学习和使用。
  • 活跃的社区: 拥有庞大而活跃的社区,可以方便地获取帮助和交流经验。
  • 持续的改进: Laravel 团队持续不断地改进框架,保持其与时俱进。

4. 具体例子:

  • 路由定义: Route::get('/users', [UserController::class, 'index']); PHP简洁明了地定义了路由规则,将 GET 请求 /users 映射到 UserController 的 index 方法。
  • Eloquent ORM 查询: $users = User::where('active', 1)->orderBy('name')->get();.PHP使用链式调用和易懂的方法名,轻松构建复杂的数据库查询语句。
  • Blade 模板引擎: <h1>{{ $title }}</h1> <ul> @foreach ($users as $user) <li>{{ $user->name }}</li> @endforeach </ul> Blade简洁的语法和易于理解的指令,使得模板编写更加高效。

总而言之,Laravel 的优雅体现在它简洁易懂的语法、强大的功能和工具、以及注重开发者体验的设计理念。它致力于让开发者以更愉悦、更高效的方式构建 Web 应用程序。

Laravel 中的依赖注入与反向控制

Laravel 框架的核心机制之一就是依赖注入(DI)和反向控制(IoC),它们共同实现了代码的松耦合、可测试性和可维护性。

1. 依赖注入(Dependency Injection)

依赖注入是一种设计模式,它允许将对象的依赖关系(即它所依赖的其他对象)从对象本身中分离出来,并在运行时由外部容器注入。

在 Laravel 中,通常通过以下两种方式实现依赖注入:

  • 构造器注入: 将依赖项作为参数传递给类的构造函数。
  • 方法注入: 将依赖项作为参数传递给类的方法。

例如,假设我们有一个 UserController 类,它需要使用 UserRepository 类来访问用户数据:

class UserController
{
    protected $userRepository;

    // 构造器注入
    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    public function index()
    {
        $users = $this->userRepository->all();
        // ...
    }
}

2. 反向控制(Inversion of Control)

反向控制是一种设计原则,它将控制权从应用程序代码转移到外部容器。这意味着应用程序代码不再负责创建或管理其依赖项,而是由容器负责。

Laravel 的服务容器充当了 IoC 容器的角色,它负责:

  • 注册绑定: 将接口或抽象类绑定到具体的实现类。
  • 解析依赖: 自动解析类所需的依赖项,并注入到类中。

例如,我们可以在服务提供器中将 UserRepository 接口绑定到 EloquentUserRepository 实现类:

// AppServiceProvider.php
public function register()
{
    $this->app->bind(UserRepository::class, EloquentUserRepository::class);
}

这样,当 Laravel 需要创建一个 UserController 实例时,它会自动解析 UserRepository 依赖项,并注入 EloquentUserRepository 实例。

3. 优势

  • 松耦合: 类不再依赖于具体的实现,而是依赖于接口或抽象类,从而降低了代码的耦合度,提高了代码的可重用性和可测试性。
  • 可测试性: 由于依赖项可以轻松替换,因此可以方便地进行单元测试,将依赖项替换为模拟对象。
  • 可维护性: 代码更易于理解和维护,因为对象的创建和管理逻辑集中在服务容器中。

4. 示例

在 Laravel 中,依赖注入和反向控制无处不在。例如:

  • 路由器使用依赖注入来解析控制器。
  • 控制器使用依赖注入来获取模型、服务等依赖项。
  • 事件监听器使用依赖注入来获取事件对象和其他依赖项。

总结

依赖注入和反向控制是 Laravel 框架的基石,它们共同实现了代码的松耦合、可测试性和可维护性。理解这些概念对于开发高质量的 Laravel 应用程序至关重要。

Supervisor

Supervisor 是一个用 Python 编写的进程管理工具,它被广泛用于 Linux 系统上管理和监控进程。它可以确保你的应用程序或服务在崩溃或服务器重启后自动重启,从而提高了系统的稳定性和可靠性。

快速开始

下面操作需要 root 权限,这里默认是使用 root 账号,如果当前不是 root 账号记得加 sudo

安装

yum install epel-release
yum install -y supervisor

启动

systemctl enable supervisord # 开机自启动
systemctl start supervisord # 启动supervisord服务

配置 supervisor

主配置在 /etc/supervisor/supervisord.conf 路径
子配置在 /etc/supervisord.d 路径
如果不是可以通过 cat /etc/supervisor/supervisord.conf 最后会指名子配置路径,当然也可以自己修改

[include]
files = supervisord.d/*.ini

配置模板

在编辑配置文件时一定要注意 ; 前后需要空格

;[program:HelloSupervisor] 是被管理的进程配置参数,HelloSupervisor 是进程的名称
[program:HelloSupervisor]
environment=JAVA_HOME=/opt/jdk1.8.0_241/bin  ; 这里可以创建环境变量
directory=/opt/HelloSupervisor ; 程序的启动目录
command=/opt/jdk1.8.0_241/bin/java -Xms512m -Xmx1024m -Dspring.profiles.active=prd -Dserver.port=8080 -jar /opt/HelloSupervisor/HelloSupervisor.jar ; 启动命令,可以看出与手动在命令行启动的命令是一样的
autostart=true       ; 在 supervisord 启动的时候也自动启动
startsecs=10         ; 启动 10 秒后没有异常退出,就表示进程正常启动了,默认为 1 秒
autorestart=true     ; 程序退出后自动重启 , 可选值:[unexpected,true,false],默认为 unexpected,表示进程意外杀死后才> 重启
startretries=3       ; 启动失败自动重试次数,默认是 3
user=root          ; 用哪个用户启动进程,默认是 root
priority=999         ; 进程启动优先级,默认 999,值小的优先启动
redirect_stderr=true ; 把 stderr 重定向到 stdout,默认 false
stdout_logfile_maxbytes=20MB  ; stdout 日志文件大小,默认 50MB
stdout_logfile_backups = 20   ; stdout 日志文件备份数,默认是 10
; stdout 日志文件,需要注意当指定目录不存在时无法正常启动,所以需要手动创建目录(supervisord 会自动创建日志文件)
stdout_logfile=/opt/HelloSupervisor/logs/log.log
stopasgroup=false     ;默认为 false, 进程被杀死时,是否向这个进程组发送 stop 信号,包括子进程
killasgroup=false     ;默认为 false,向进程组发送 kill 信号,包括子进程
stopsignal=TERM      ; 杀死进程的信号量:kill -9:KILL; kill -2:INT; kill -15:TERM 默认配置

其他配置

stopscript=stop_script.sh ; 当进行停止的脚本
stopwaitsecs=10 ; 定义 Supervisor 等待进程停止的时间
command_stop=echo "Stopping process..." ; 定义在停止进程之前执行的命令
command_start=echo "Starting process..." ; 定义在启动进程之前执行的命令
command_restart=echo "Restarting process..." ; 定义在重启进程之前执行的命令
command_reload=echo "Reloading process..." ; 定义在重新加载进程之前执行的命令
numprocs=1 ; 定义要启动的进程实例数量
numprocs_start=1 ; 定义启动的进程实例的起始编号

配置完成后可以使用这个命令, 配置了autostart=true将会自动启动

supervisorctl update

常用命令

刷新配置

supervisorctl update 
supervisorctl reload # 可以避免手动停止和启动进程,而是通过重新加载配置来实现更改的生效,从而提高了操作的便利性和效率

区别:

  • update 命令用于在配置文件发生更改时启动新的进程,而 reload 命令用于重新加载配置文件并重新启动受影响的进程。
  • update 命令主要用于添加新的进程配置,而 reload 命令用于应用配置文件的更改,包括进程配置的修改、删除和其他配置的更新。
  • update 命令只启动新添加的进程,不会重新启动已经在运行的进程,而 reload 命令会重新启动受到更改影响的进程,使其应用新的配置
supervisorctl # 查看所有服务列表
supervisorctl start all # 启动全部服务
supervisorctl stop all # 停止全部服务
supervisorctl restart all # 重启全部服务

使用普通用户控制 supervisor
默认情况下只有 root 用户或者 sudo 的方式才可用使用,如果需要普通用户直接可以使用,可以按下面方式配置

假设普通用户名为:testuser

  • 步骤 1:使用root用户安装supervisor
  • 步骤 2:创建普通用户组
groupadd supervisor
usermod -a testuser -G supervisor
  • 步骤 3:修改以下配置文件
[testuser@4fff02d62bba ~]# vi /path/to/supervisord.conf
;修改下面路径

[unix_http_server]

file=/etc/supervisor/supervisor.sock ; the path to the socket file chmod=0766 ; socket file mode (default 0700) chown= testuser:supervisor ; socket file uid:gid owner

下面步骤可选,如果需要普通用户完全控制 supervisord 需要下面操作

  • 步骤 4:删除默认路径下的: /etc/supervisord.conf
  • 步骤 5:使用普通用户启动 supervisord
supervisord -c  /path/to/supervisord.conf

进程分组

配置

需要在想要分组服务配置文件下新增分组配置

[group:test] ; 分组名称
programs=service1,servie2 ; 上面配置的 program 名称 即 program 后面的名称

进程分组命令

# 查看分组子服务列表
supervisorctl status group_name:
# 启动指定组名下服务
supervisorctl start group_name:
# 停止指定组名下服务
supervisorctl stop group_name:
# 重启指定组名下服务
supervisorctl restart group_name:

笔记

假设是常规windows安装,要回滚到composer版本1,只需执行以下命令:

composer self-update --1


当您想回到版本2时(在更新或删除不兼容的插件之后,您应该这样做):

composer self-update --2