慣性聚合 高效追讀感興趣之博客、新聞、科技資訊
閱原文 以慣性聚合開啟

推薦訂閱源

博客园 - 司徒正美
V
V2EX
T
Tailwind CSS Blog
有赞技术团队
有赞技术团队
aimingoo的专栏
aimingoo的专栏
Apple Machine Learning Research
Apple Machine Learning Research
IT之家
IT之家
Blog — PlanetScale
Blog — PlanetScale
A
About on SuperTechFans
月光博客
月光博客
T
The Blog of Author Tim Ferriss
宝玉的分享
宝玉的分享
Martin Fowler
Martin Fowler
博客园 - 聂微东
The GitHub Blog
The GitHub Blog
V
Visual Studio Blog
WordPress大学
WordPress大学
酷 壳 – CoolShell
酷 壳 – CoolShell
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python)
PHP 8.4 非对称可见性:五式代之构造与设定
Gabriel Anha · 2026-05-24 · via DEV Community

PHP 8.4于2024年11月推出非对称可见性,多数团队采用之,仅用于跳过getter。此乃显见之例。然有四优者。

此功能甚微。其语法为public public(set)public protected(set)public private(set)及对称之变体。读之修饰,处其素位。写之修饰,括之侧畔。set此乃全矣。

所取代者,其大也。乃取代十五年来PHP团队所撰之私有财产加公共获取模式。亦取代构造函数之冗余模板。与属性钩子相配,readonly,乃代内建构造模式。吾等详述之。

何哉public(set)实为更易

此乃Order 類於 PHP 8.3 中,其狀態變化自域內發生,項目重計後總變化數,然兩項均可自處讀取:模板、序列化器、控制器。

// PHP 8.3, the old way
final class Order
{
    private OrderStatus $status;
    private Money $total;

    public function __construct(
        private readonly OrderId $id,
        OrderStatus $status,
        Money $total,
    ) {
        $this->status = $status;
        $this->total = $total;
    }

    public function getId(): OrderId
    {
        return $this->id;
    }

    public function getStatus(): OrderStatus
    {
        return $this->status;
    }

    public function getTotal(): Money
    {
        return $this->total;
    }

    public function markPaid(): void
    {
        $this->status = OrderStatus::Paid;
    }
}

Enter fullscreen mode Exit fullscreen mode

三個無所事事之取值器。構造器唯為分派而存。形制同於 PHP 8.4:

// PHP 8.4, same semantics, one third the code
final class Order
{
    public function __construct(
        public readonly OrderId $id,
        public private(set) OrderStatus $status,
        public private(set) Money $total,
    ) {}

    public function markPaid(): void
    {
        $this->status = OrderStatus::Paid;
    }
}

Enter fullscreen mode 出全屏模式

读面无改。$order->status以 Twig 模板之方式而运作$order->getStatus()已矣。今书写之面,已专属于其类本身。控制器不能为也。$order->status = OrderStatus::Refunded. 将至Cannot modify private(set) property Order::$status from global scope.

此乃标题。今言模式。

范式一:阅无方,书有域

此乃显见之例,亦为多数博文所止之境。汝欲一属性,使任一调用者可读,然唯所属聚合方可变之。八四之前,汝撰一取值器及一私有字段。今,汝但书此字段一次。

final class EmailAddress
{
    public public(set) string $value {
        set (string $value) {
            if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
                throw new InvalidArgumentException(
                    "Invalid email: {$value}"
                );
            }
            $this->value = strtolower($value);
        }
    }

    public function __construct(string $value)
    {
        $this->value = $value;
    }
}

Enter fullscreen mode Exit fullscreen mode

public public(set)者,声宏而可读可写,遍及四方。配以属性钩子(PHP 8.4之另一大特征),则每赋值皆得验证,constructor内之赋值亦然。彼EmailAddress之class,今为值对象,五行业务逻辑,无getter-setter之繁文缛节。

堪记者:钩子于构造器赋值亦发。尔若欲绕过之,不可于构造器内行之,非更易属性声明不可。此乃特性,非谬误。尔之不变式自对象存在时即已成立.

模式二:无构建者之构建者对象

PHP中构建者模式已得清理。readonly于8.2中,复制语义于8.4中。非对称可见性乃最后之拼图。可无别法,流畅不变之对象,汝能书之。OrderBuilder班次。

final class Order
{
    public function __construct(
        public readonly OrderId $id,
        public private(set) OrderStatus $status = OrderStatus::Draft,
        public private(set) ?Address $shippingAddress = null,
        public private(set) array $items = [],
    ) {}

    public function withShippingAddress(Address $address): self
    {
        $clone = clone $this;
        $clone->shippingAddress = $address;
        return $clone;
    }

    public function withItem(OrderItem $item): self
    {
        $clone = clone $this;
        $clone->items = [...$this->items, $item];
        return $clone;
    }
}

入全景模式 出全屏模式

private(set)谓此属性可由类内(包括类之克隆内)所写。故$clone->shippingAddress = $address功成者,盖吾辈犹在也。Order吾等行之,外者可阅诸项。外者不可易之。

此乃使构建类于多数情形下过时之所在。构建类存于世,为别于最终对象,持半成之态。有克隆写入之法,则private(set)尔可同持二态于一类,而勿泄其变于世。

模式三:聚合根字段守卫

在领域驱动设计中,有聚合根,其拥有部分状态,并对其执行不变式约束。订单拥有其明细项。聚合外之人不得直接干预明细数组。在8.4之前,这意味着private array $items加上public function getItems(): array { return $this->items; },若需顾及调用者变更返回数组,还需加上防御性拷贝。

非对称可见性结合属性钩子,使防护显明且简练:

final class Order
{
    public function __construct(
        public readonly OrderId $id,
        public private(set) Money $total = new Money(0, 'EUR'),
        public private(set) array $items = [],
    ) {}

    public function addItem(OrderItem $item): void
    {
        if ($this->status !== OrderStatus::Draft) {
            throw new DomainException(
                "Cannot add items to a {$this->status->value} order"
            );
        }
        $this->items[] = $item;
        $this->recalculateTotal();
    }

    private function recalculateTotal(): void
    {
        $this->total = array_reduce(
            $this->items,
            fn(Money $sum, OrderItem $i) => $sum->add($i->subtotal()),
            new Money(0, 'EUR'),
        );
    }

    public public(set) OrderStatus $status = OrderStatus::Draft {
        set (OrderStatus $next) {
            if (!$this->status->canTransitionTo($next)) {
                throw new DomainException(
                    "Cannot move from {$this->status->value} to {$next->value}"
                );
            }
            $this->status = $next;
        }
    }
}

入全景模式 出全景模式

有两事当察。其一,$this->items[]= $item之效发自addItem()内,盖因吾处于类中。其二,状之属用public public(set) 佐以钩,意谓人人可指派,然钩主控迁转之防。此乃外唤驱动迁转(如工作流引擎、授权后之控制器)而域仍拒非法之变者,其形正也。

public(set) 之合,加钩之属,乃不对称显见之至配也。

模式四:选择性不可变之 DTO 输入

DTO 之弊,在于滥设 readonly class,致使其难于反序列化。欲使 DTO 之多数字段,成之既立,则不可更易。然犹需一二字段,可由设施所设(如中介件所定之请求 ID,或事件总线所注之关联 ID)。此不对称可见性,恰合此理。

final class CreateOrderCommand
{
    public function __construct(
        public readonly CustomerId $customerId,
        public readonly array $items,
        public readonly Address $shippingAddress,
        public public(set) ?string $correlationId = null,
        public public(set) ?string $idempotencyKey = null,
    ) {}
}

退出全屏模式

三域实为不可易。二域可书。凡人皆可设之,然值自基构流来,非自用户。命仍为值物。构架可注所需,无需君书设法.

public(set)于开域,则无域限之规。欲锁之于特定层,当易protected(set) 與之相需,框架融貫以延 DTO。吾當避之。巧則巧矣,然巧者,DTO 之弊也。

模式五:破處在何:序列化、Eloquent 轉換、Doctrine 製作

模式甚善。然其周遭之基礎未備。

Symfony Serializer (6.4 與 7.x)善处非对称可见性以达反序列化,盖因其用反射,且于写入时重属主之界。故$serializer->deserialize($json, Order::class, 'json')运作无碍,盖因序列化者权柄足矣,可书之。private(set)田野。新近之点发布,处理之甚洁。

雅致之属性投射(Laravel 11.x及12.x)是故噬整合之码。Eloquent以赋属性。__set() 在模型上,非在值对象上。若汝有定制之转换,能生非对称可见之值对象,则转换本身有效。然若汝于转换之 set() 法中,变易既有之实例,必遇 Cannot modify private(set) property。其解乃于 CastsAttributes::set() 中,恒返新之实例,勿变异。此亦佳习;PHP 8.4 但使之行之耳。

Doctrine ORM (3.x) 依反射之法,滋养实体,故私有及 private(set) 之属,滋养无碍。所困者,乃 Doctrine 之变轨耳。汝以法变易所踪之实体,Doctrine 必察其变。及至 clone 之(前述第二式),Doctrine 视其克隆为游离。勿杂克隆与只读之式于 Doctrine 所辖之实体;此框架,期其所辖之物,可变易也。

JMS Serializer(今之Symfony商家犹常用之)于3.31版本未予支持。其以公域赋值,致private(set)破环。当升迁至Symfony Serializer,或自撰定制之处理器.

惊险之所在:protected(set)合乎继承,复加构造器提升

此乃未载之边际情形。构造器提升之属性与protected(set)之形貌,于定义处尚佳。然于延伸之际,则异矣。

class Money
{
    public function __construct(
        public protected(set) int $amount,
        public readonly string $currency,
    ) {}
}

class TaxableMoney extends Money
{
    public function withTax(float $rate): self
    {
        $clone = clone $this;
        $clone->amount = (int) ($this->amount * (1 + $rate));
        return $clone;
    }
}

全屏模式入。 全屏模式出。

此法可行。子类处乎protected(set)之域,故赋值合律。然其患在:若第三方之庠序,延尔之类,而欲变amount于己之支类之法中,亦得逞焉。protected(set)之广,逾人所测。犹若以八三之法著一protected function setAmount(),是使诸子无论何地,皆属其书表之列.

若欲成事之后永不可变,当用public private(set)。若欲使子嗣可变,则用public protected(set),并载明其子系乃书表之约.

不可用之候

非事事物须有偏私之见。一请求数据传输对象(Request DTO),仅存于一次HTTP调用之间,经检验,付与用例,复归垃圾回收,此等物,何须乎?private(set)修饰之认知代价,实也。运行时之代价,零也,然有人读汝之码,疑汝何故费心,此代价非零也。

吾所荐之形也。

  • 聚根与实体之常:可.
  • 值物越界而行:可,配以readonly或钩子.
  • 内藏之数据传输对象,仅存于单次请求:不可,public readonly或素朴之public足矣.
  • 具公之API之库代码:可,凡契约为"调用者读,库写"之字段皆然.
  • ORM所管理之模型:唯以上所陈之例外。

PHP 8.4 之非对称可见性非革命也。乃缺失之拼图,使汝可止书取值类,始书真对象。五载之代码库,充斥private $foo益之public function getFoo()此正应运行 Rector 于相关规则,以收千行虚文也。

此五者之中,汝于码库首取何式?又首改何性?


若此有益

非对称可见性者,乃语言之特征,导人于小而诚之物,此物可存于框架易换之际。是书所载之架构层也:何以建PHP之应用,使其存续于Laravel、Symfony,或今年所用之任何框架。若此文中之模式有所共鸣,解耦PHP乃长篇版本,有章论聚合、值对象,及领域代码与框架代码之界。

Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework

有售于Kindle、平装本及精装本。英文、德文、日文版已出,葡文及西文版将即至。