Linux 解决 Laravel 命令行与 Web 进程用户不一致的权限问题

在 Ubuntu 环境中,日常部署代码、执行 Composer 与 Artisan 命令,一般使用 ubuntu 普通用户;
而 Nginx、PHP-FPM 等 Web 服务默认以低权限用户 www-data 运行。

CLI 命令行用户与 Web 运行用户不一致,会直接造成 storagebootstrap/cache 目录读写拒绝、缓存生成失败、日志写入异常等权限问题。

一、统一用户方案

通过让 Web 与命令行共用同一运行用户,从根源规避权限冲突,分为两种实现方式:

1. Web 进程统一使用 ubuntu 用户

修改 PHP-FPM 配置,将进程执行用户改为 ubuntu
操作简单、彻底杜绝权限报错,日常维护最省心。

缺点是会扩大 Web 进程权限范围,存在一定安全隐患。
适合场景:单机单站、个人项目、自用服务、开发/自建生产环境。

2. 命令行统一使用 www-data 用户

保持 PHP-FPM 默认 www-data 隔离权限,执行 Laravel 相关命令时,手动切换为该用户运行。

示例:

1
sudo -u www-data php artisan view:cache

优势是保留系统原生安全隔离,适配企业生产、多站点服务器;
劣势为每次执行 Artisan、Composer 都需要追加用户切换命令,操作繁琐且容易遗漏。

二、附属组 + SGID 标准隔离方案(生产推荐)

该方案是 Laravel 生态公认最佳实践,兼顾安全性与运维便捷性,也是线上生产环境的主流配置。

核心思路: 不改动 Web 服务默认运行用户,通过附属组授权 + SGID 目录继承权限,让两个不同用户互相读写项目临时目录。

  1. ubuntu 追加加入 www-data 附属组,保留原有用户组权限不变;

务必使用 usermod -aG 追加模式,**不可省略 -a**,避免覆盖原有附属组导致权限丢失。

  1. 对读写高频目录绑定归属组,并配置 SGID 权限,使新文件自动继承目录属组,永久解决文件属主错乱问题。

执行命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 追加用户至 www-data 组
sudo usermod -aG www-data ubuntu

# 关键读写目录统一归属 www-data
sudo chown -R www-data:www-data storage bootstrap/cache

# 给目录 775 权限
sudo chmod -R 775 storage bootstrap/cache

# 给文件 664 权限
sudo chmod -R 664 storage/* bootstrap/cache/*

# 配置 SGID,新建文件/目录自动继承所属组
sudo chmod -R g+s storage bootstrap/cache

# 退出 ubuntu 用户后重新登录,即可生效

这样,名ubuntu用户执行 Artisan 命令时,在 storage 或 bootstrap/cache 目录生成的文件用户为 ubuntu:www-data,php-fpm 生成的文件用户为 www-data:www-data,两者都可以对项目临时目录进行读写操作。

拓展:若业务安全要求极高,可搭配 Ubuntu ACL 精细化授权,进一步收紧权限范围,本文不做展开。

PHP 扩展管理工具 PIE

PHP PIE(PHP Installer for Extensions)是 PHP 官方推出的一款现代化扩展管理工具,旨在简化 PHP 扩展的安装与管理流程,被视为 PECL(PHP 扩展社区库)的潜在替代方案。

首个公开版本发布于 2023 年下半年,具体而言,其在 GitHub 上的首个正式发布版本(v0.1.0)于 2023 年 9 月推出。这一工具由 PHP 基金会主导开发,旨在为 PHP 扩展管理提供更现代化、自动化的解决方案,逐步替代传统的 PECL 工具。

自首次发布以来,PHP PIE 持续迭代更新,不断扩展支持的扩展范围并优化功能,截至目前(2025 年)已推出多个版本,成为 PHP 生态中备受关注的扩展管理工具。其发展节奏与 PHP 基金会对现代化工具链的推进策略相契合,进一步巩固了在 PHP 扩展管理领域的地位。

以下是其核心特性和使用要点:

核心特性

1.类似 Composer 的使用体验

采用简洁的命令行语法,类似composer的操作逻辑,例如:

1
2
pie install xdebug/xdebug  # 安装Redis扩展
pie install xdebug/xdebug:^3.2 # 安装指定版本的Xdebug

2.自动化处理流程

  • 自动从 Packagist 获取扩展包,无需手动查找资源。
  • 自动编译扩展(Linux/macOS)或使用预编译 DLL(Windows)。
  • 自动修改 php.ini 配置文件,添加扩展加载指令(如 extension=redis)。

3.多 PHP 版本支持

可指定为不同 PHP 版本安装扩展,例如:

1
pie install mongodb/mongodb --php /usr/bin/php8.4  # 为PHP 8.4安装MongoDB扩展

4.与现代开发流程兼容

支持在 CI/CD 管道中自动化安装扩展,无缝融入项目的自动化部署流程。你可以在 shell 脚本中添加 pie install 命令,实现自动安装扩展,也可以在 Docker 配置中指定安装哪些模块。

使用前提

  • 运行环境需 PHP 8.1 及以上版本(PIE 自身依赖),但可管理其他 PHP 版本的扩展。
  • Linux/macOS 需安装编译工具链(如gcc、make);
  • Windows 依赖扩展作者提供的预编译 DLL。

安装与配置

  1. 下载pie.phar(官方发布地址:GitHub)。
    curl -O https://github.com/php/pie/releases/latest/download/pie.phar

  2. 配置为全局命令(Linux/macOS 太容易搞定,我们就以 Windows 为例):

    • pie.phar 保存到 D:\portable\pie\ 目录。
    • 创建 pie.bat 文件,内容为: php "%~dp0pie.phar" %*
    • D:\portable\pie\ 添加到系统环境变量 Path 中,这样就可以直接在命令行使用pie命令了。
  3. 验证安装:在命令行中运行 pie -V,若显示版本信息则安装成功。

扩展安装位置

pie.phar 如果与执行php.exe在同一个目录,那么扩展默认安装到 ~/ext 文件夹中,否则默认安装到 pie.phar 所在目录的 ext 文件夹中。

适用场景与局限

  • 推荐场景:
    • 基于 PHP 8.1 + 的新项目
    • 需要自动化管理扩展的 CI/CD 流程
    • 主流扩展(如 Redis、Xdebug、MongoDB)的安装管理
  • 当前局限:
    • 扩展覆盖度不及 PECL,部分冷门扩展暂不支持(可在这里查看已支持的PHP扩展
    • Windows 环境依赖预编译 DLL,部分扩展可能无法安装
    • 暂未提供扩展卸载功能,需手动操作

Laravel 新旧版本模型属性访问器、修改器的选用

Laravel 9 之前模型属性访问器、修改器

通过 setFooBarAttribute($value)getFooBarAttribute() 方法来定义和访问自定义动态 fooBar 属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
/**
* 设置用户的名字
*/
public function setFirstNameAttribute($value)
{
$this->attributes['first_name'] = strtolower($value);
}

/**
* 获取用户的全名
*/
public function getFullNameAttribute()
{
return "{$this->first_name} {$this->last_name}";
}
}

Laravel 9 及之后版本的模型属性访问器、修改器

通过返回值类型为 Attribute 类来定义和访问自定义动态属性。

阅读更多

PHPStorm 配置 WSL PHP 开发文件

WSL 跨文件操作性能比较差,WSL Debian PHP 解释器执行 Windows 上的代码速度会很慢,因此 PHP 项目代码要保存在 WSL Debian 中。

1、打开项目文件夹

点击 PHPStorm 的 文件 > 打开,选择 \wsl$\Debian 目录,找到 WSL Debian 系统上的项目文件夹,这样就可以打开 WSL Debian 上的项目文件夹了。

2、WSL Debian 安装 PHP

1
2
3
4
sudo apt update
sudo apt install php php-dev php-gd php-curl php-mysql \
php-mbstring php-redis php-xdebug php-intl php-zip \
composer
阅读更多

测试用 Octane 加速 Laravel10,并发达到4倍左右,同时对比Hyperf3压力测试

Laravel 开发爽,但性能完全无法忍受。刚完成一个项目,有点时间,试试看用 octane 加持后是否能摆脱 Laravel 的性能魔咒。
Laravel 应用基本可可无缝迁移到 Hyperf,因此同时测试 Hyperf,看看 Swoole 协程异步加持的 Hyperf 是不是比 Laravel 快很多。

测试环境

1
2
3
4
5
6
7
8
9
服务器: 阿里轻量云服务器
CPU: 2核
内存: 2G
OS: CentOS 8.5
PHP: 8.2.5
MySQL: 8.0
Swoole: 5.0.3
Laravel: 10.8
Hyperf: 3.0
阅读更多

Laravel Eloquent 日期系列化

Laravel Eloquent 日期系列化成 json,默认系列化格式为:2023-03-08T08:16:02.000000Z
原因是 Laravel 模型基类的 serializeDate() 时间系列化方法调用 Carbon\Traits\Converter::toJSON() 方法,返回的是 ISO-8601 格式的日期。

引起问题的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Illuminate\Database\Eloquent\Concerns\HasAttributes
protected function serializeDate(DateTimeInterface $date)
{
return $date instanceof DateTimeImmutable ?
CarbonImmutable::instance($date)->toJSON() :
Carbon::instance($date)->toJSON();
}

// class Carbon\Traits\Converter
public function toJSON()
{
return $this->toISOString();
}
阅读更多

Laravel Eloquent 模型属性参数详解

@TODO

$table

模型绑定表名,默认是模型类名 kebab-case 带下划线小写的复数形式。

$primaryKey = ‘id’

$keyType = ‘integer’

fillable

attributes

hidden

casts

数字、字符串类型不需要转换。

appends

$timestamps = true

$dateFormat = ‘Y-m-d H:i:s’

Mac 下用 brew 安装多版本 php

以前用 window 系统的时候,经常去 php 官网下载不同版本的 php 来使用,改一下系统 path 就可以更换默认 php 版本,其它版本用完整路径也可以使用。Mac 上就更省事一点,用 brew 命令就可以安装不同版本的 php,再用 brew-php-switcher 切换默认 php 版本,而且用 pecl 命令就可以安装 pecl 扩展。

查询 php 安装包版本

1
brew search php

执行命令后,你会看到不同版本的 php, 最新版的是不带版本号直接是 php,旧版本带版本号,如:php@8.1php@7.4

安装 php

1
2
brew install php #不带版本号是最新版
brew install php@8.1 # 带版本号

切换 php 版本

阅读更多

Laravel Eloquent 数据库关联模型的增删改操作

Laravel Eloquent ORM 提供了数据模型关联表操作的 API,熟练掌握这些API后,才体会到 Laravel 数据库操作有多高效。

一、hasMany 一对多关联

save/saveMany 创建关联记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 新建一条数据 Post 的评论,save 将自动添加 post_id 字段
$comment = new Comment(['message' => 'A new comment.']);
$post = Post::find(1);
$post->comments()->save($comment);

// 保持多条记录
$post->comments()->saveMany([
new Comment(['message' => 'A new comment.']),
new Comment(['message' => 'Another new comment.']),
]);

// 更新后需要重新加载模型及其关联,才会加到 $post->comments 中
$post->refresh();

// 所有评论,包括新保存的评论...
$post->comments;

create/createMany 创建关联记录

save/saveMany 的区别是参数时数组,而不是模型。

阅读更多