您可能听过GoF的这句著名的话:“更喜欢类继承而不是类继承。” 然后,作为一个规则,人们长思了如何静态确定的继承与动态组成相比没有那么灵活 。
灵活性当然是有用的设计功能。 但是,在选择体系结构时,我们主要对可维护性,可测试性,代码可读性,模块重用感兴趣。 因此,使用这些好的设计标准,继承也是一个问题。 “现在,根本不使用继承吗?”-您问。
让我们看看通过继承在类之间建立强大的依赖关系如何使您的系统体系结构过于僵化和脆弱。 以及为什么在final
代码中使用最神秘和难以捉摸的关键字之一。 使用一个简单的横断面示例演示了提出的想法。 在本文的结尾,有一些技巧和工具可以方便地完成final
课程。

脆弱的基层问题
良好体系结构的主要标准之一是松耦合 ,它表征了软件模块之间的互连程度。 参与度低是GRASP模式列表的一部分,它描述了在班级之间分担责任的基本原则,这并非毫无道理。
参与度低有很多优点。
- 通过放松软件模块之间的依赖性,您可以通过创建更灵活的体系结构来促进系统的维护和支持。
- 有可能并行开发松耦合模块,而不会破坏其功能。
- 类的逻辑变得更加明显,正确使用该类及其预期目的变得更容易,并且很难使用它-错误。
传统上,系统中的依赖关系主要被理解为所使用的对象(服务)和使用的对象(客户端)之间的连接。 当服务是客户端的“一部分”时,这种关系对聚合关系进行建模( 具有关系 ),并且客户端将行为的责任转移到嵌入其中的服务。 依赖关系反转原理 ( DIP )在削弱客户端与服务之间的关系方面起着关键作用,它提出将模块之间的直接依赖关系转换为对通用抽象的对等依赖关系。
但是,您还可以通过放松is-a关系框架内的依赖关系来显着改善应用程序体系结构。 默认继承关系会创建紧密耦合 ,这在所有可能的依赖关系形式中最强,因此应非常谨慎地使用。

关于继承的紧密联系
父类和子类之间共享的代码量非常大。 当滥用继承概念时,这个问题开始特别明显地表现出来-仅将继承用于水平代码重用,而不是用于创建专门的子类。 因为继承是重用代码的最简单方法。 您只需要编写extends ParentClass
就可以了! 毕竟,它比聚合, 依赖项注入 (DI)和接口分配要简单得多。
传统上,通过使用范围的限制修饰符( private
, protected
)可以减少继承层次结构中类链接的数量。 甚至有人认为,类属性应仅使用private
修饰符声明。 而且,应该非常谨慎地将protected
修饰符应用于方法,因为 它鼓励父类和子类之间的依赖关系。
但是,继承的问题不仅在于隐藏属性和方法,而且还更深层次。 关于应用程序体系结构的许多文献,包括经典的GoF书籍,都对继承表示怀疑,并建议寻求更灵活的设计。 但这仅仅是灵活性吗? 我在下面提出将继承问题系统化的方法,然后考虑如何避免它们。
让我们将最简单的带有内部注释数组的注释块作为“实验兔”。 在任何项目中都可以找到大量带有内部集合的类似类。
class CommentBlock { private $comments = []; }
下面的示例以KISS样式故意简化,以显示代码中提出的思想。 您可以从本文中找到代码示例,以及在此存储库中使用它们的详细注释。
继承问题
让我们从最明显的问题开始,该问题主要在有关建筑的文献中给出。
继承违反隐藏原则
由于子类可以访问父类的实现细节,因此通常说继承违反了封装。
GoF,设计模式
尽管经典著作《四人帮》涉及封装违规问题,但说“继承违反了隐蔽性原则”会更准确。 毕竟, 封装是数据与为其处理而设计的方法的结合。 但是隐藏的原理仅确保将系统某些组件的访问限制为其他组件的实现细节。
遵循架构中的隐藏原理,可以确保模块通过稳定的接口接合。 并且,如果该类允许继承,那么它将自动提供以下类型的稳定接口:
- 此类所有客户端使用的公共接口 ;
- 所有子类使用的受保护接口 。
即 子类比公共API提供的功能要大得多。 例如,它可以影响隐藏在其protected
属性中的父类的内部状态。
通常,子类不需要访问继承框架中可用的父类的所有元素,但是,您不能为某些子类有选择地提供对protected
类成员的访问。 子类开始依赖于父类的受保护接口 。
通常,类继承的主要原因是通过重用其实现来扩展父类的功能。 如果最初通过受保护的接口访问父类的实现没有带来问题,那么随着系统的发展,程序员将开始在子类的方法中使用此访问,并加强层次结构中的链接。
现在,父类不仅被迫维护公共接口的稳定性,而且还必须维护受保护接口的稳定性,因为对它的任何更改都将导致子类的工作出现问题。 但是,不可能拒绝使用protected
类成员。 如果受保护的接口将完全匹配外部公共接口 ,即 父类将仅使用public
和private
成员,那么继承通常没有任何意义。
实际上, protected
关键字实际上不为类成员提供任何保护。 要获得此类成员的访问权,就可以从班级继承,并且在子班级的框架内,您将有一切机会违反隐瞒原则。 错误地使用类变得非常容易,这是体系结构不佳的最初标志之一。

通过受保护的界面违反隐藏原则
更重要的是,封装的元素(常量,属性,方法)不仅可以在子类中读取和调用,而且可以被覆盖。 这样的机会充满了潜在的危险-由于这种变化,子类的对象的行为可能变得与父类的对象不兼容。 在这种情况下,在代码中父类对象的行为可能导致无法预料的后果的那些点上替换子类的对象。
例如,我们添加CommentBlock
类的功能:
class CommentBlock { protected $comments = []; public function getComment(string $key): ?Comment { return $this->comments[$key] ?? null; } }
我们将从中继承自CustomCommentBlock
类CustomCommentBlock
,在其中我们利用了打破隐藏的所有可能性。
class CustomCommentBlock extends CommentBlock { public function setComments(array $comments): void { $this->comments = $comments; } public function getComment(string $key): ?Comment { foreach ($this->comments as $comment) { if ($comment->getKey() === $key) { return $comment; } } return null; } }
常见的隐瞒违规情况如下:
- 子类的方法显示父类的状态,并提供对父类的隐藏成员的访问。 设计父类时可能不会预见到这种情况,这意味着可能会违反其方法的逻辑。
在示例中,子类提供CustomCommentBlock::setComments()
setter方法,以修改隐藏在父类中的受保护CommentBlock::$comments
属性。 - 覆盖子类中父类方法的行为。 有时,开发人员认为此功能是通过创建具有修改行为的子类来解决父类问题的方法。
在此示例中,父类中的CommentBlock::getComment()
方法依赖于CommentBlock::$comments
关联数组中的键。 在子类中-到注释本身的键,可通过Comment::getKey()
方法访问。
香蕉猴丛林问题
面向对象语言的问题在于它们会拖延整个隐式环境。 您只想要一个香蕉,但结果得到了一只大猩猩,盛着这种香蕉,还有所有的丛林。
乔·阿姆斯特朗(Erlang)的创建者
依赖关系始终存在于系统体系结构中。 但是,继承带有许多复杂因素。
您可能会遇到这样的情况,随着软件产品的发展,类层次结构已经显着增长。 举个例子
class Block { } class CommentBlock extends Block { } class PopularCommentBlock extends CommentBlock { } class CachedPopularCommentBlock extends PopularCommentBlock { }
您继承并继承,但是您无法决定要继承哪些成员。 您继承了所有事物,并继承了整个层次结构树中所有类的成员。 此外,您对父类的实现,父类的父类等等都有很强的依赖性。 而且这些依赖关系不能以任何方式减弱(与DIP完成的聚合相反)。
更不用说这样一个事实,即在如此深层次中的叶子类几乎肯定会违反单一职责原则 ( SRP ),知道并且做得太多。 您从一个简单的Block
类开始开发,然后向其添加功能以选择注释,然后按流行度排序功能,附加的缓存...结果,您得到了一个类,它承担着很多责任,此外, 它们之间的连接松散 ( 内聚性低 )
您只想得到一根香蕉(在层次结构中创建一个叶子对象),而不必关心它如何到达最近的超市(行为的实现方式,该结果就是该对象)。 但是,通过继承,您不得不从丛林本身开始进行整个层次结构的实现。 您应该牢记丛林的功能及其实现的细微差别,而您要专注于香蕉。
结果,您的类的状态分布在许多父类中。 您只能通过封装和隐藏限制外部环境(丛林)对您的类的影响来解决此问题。 但是,通过继承是不可能实现的,因为 继承违反了隐瞒原则。
由于子类的实现分散在父类中,现在该如何在层次结构树中某个较深的位置测试子类? 为了进行测试,您将需要所有父类,并且不能以任何方式覆盖它们,因为 您参与的不是行为,而是实施。 由于无法轻松地隔离和测试您的类,因此您将继承很多问题-具有可维护性,可扩展性和重用性。
默认打开递归
但是,子类不仅依赖于父类的受保护接口 。 他还与他部分分享一种物理认识,依靠它并可以影响它。 这不仅违反了隐瞒原则,而且使儿童班级的行为特别令人困惑和不可预测。
面向对象的语言默认情况下提供开放递归。 在PHP中,使用伪变量$this
实现开放递归。 通过$this
方法调用在文献中称为自调用 。
自调用导致当前类中的方法调用,或者可以根据后期绑定在继承层次结构中向上或向下动态重定向。 以此为依据, 自我呼叫分为:
- down-call-对方法的调用,该方法的实现在层次结构较低的子类中被覆盖。
- up-call-对方法的调用,该方法的实现是从上级继承的父类继承的。 您可以通过
parent::method()
构造显式地在PHP中进行向上调用 。
在方法的实现中频繁使用下调用和上调用 ,使钩子类更加紧密,从而使体系结构变得脆弱。
让我们举个例子。 我们在父CommentBlock
类中实现getComments()
方法,该类返回一个注释数组。
class CommentBlock { public function getComments(): array { $comments = []; foreach ($this->comments as $key => $comment) { $comments[] = $this->getComment($key); } return $comments; } }
该方法依赖CommentBlock::getComment()
的逻辑,并通过关联数组$comments
的键CommentBlock::getComment()
$comments
。 在CommentBlock::getComments()
方法的CustomCommentBlock
类的上下文中,将执行CustomCommentBlock
CommentBlock::getComments()
方法的下调用 。 但是, CustomCommentBlock::getComment()
方法的行为与父类中预期的行为不同。 作为参数,此方法期望注释本身的key
属性。
结果,从父类自动继承的CommentBlock::getComments()
在行为上与CustomCommentBlock::getComment()
不兼容。 在CustomCommentBlock
上下文中调用getComments()
很可能会返回一个null
值数组。
由于参与度很高,因此在更改班级时,您不能仅关注其行为。 您被迫考虑所有类的内部逻辑,上下层次。 必须知道并记录父类中的向下调用的列表和顺序,这实质上违反了隐藏的原则。 实施细节成为父类合同的一部分。
副作用控制
在前面的示例中,由于父类和子类中的getComment()
方法的逻辑差异,问题很明显。 但是,仅控制类层次结构中方法行为的相似性是不够的。 如果这些方法有副作用,可能会遇到问题。
具有副作用的功能(具有副作用的功能)除了主要作用外,还会更改系统的某些状态-将结果返回给调用点。 副作用示例:
- 更改方法外部的变量(例如,对象属性);
- 更改方法局部的静态变量;
- 与外部服务的互动。
因此,这些副作用也是实现的一部分 ,也无法在继承过程中有效地隐藏它们。
想象一下, CommentBlock
需要在CommentBlock
类中包含viewComment()
方法,以获取其中一个注释的文本表示形式。
class CommentBlock { protected $comments = []; public function viewComment(string $key): string { return $this->comments[$key]->view(); } }
向子类添加副作用并指定其用途。 我们实现了CountingCommentBlock
类, CommentBlock
补充CommentBlock
能够对缓存中单个注释的视图进行计数。 让该类通过CounterInterface
接口(但是最终从PSR-16中排除 )接受在构造函数中注入 PSR-16兼容的缓存( 构造函数注入 )。 我们将使用increment()
方法以原子方式递增缓存中的计数器值。
class CountingCommentBlock extends CommentBlock { private $cache; public function __construct(CounterInterface $cache) { $this->cache = $cache; } public function viewComment(string $key): string { $this->cache->increment($key); return parent::viewComment($key); } }
一切正常。 但是,在某个时候,决定添加viewComments()
函数以形成该块中所有注释的文本表示。 将此方法添加到CommentBlock
基类,并且一目了然,由所有子类继承此方法的实现看起来非常方便,并且避免在子类中编写其他代码。
class CommentBlock { public function viewComments(): string { $view = ''; foreach ($this->comments as $key => $comment) { $view .= $comment->view(); } return $view; } }
但是,父类对子类的实现功能一无所知。 viewComments()
(responsibility) CountingCommentBlock
– .
:
$commentBlock = new CountingCommentBlock(new SomeCache()); $commentBlock->viewComments();
. , .
« » . , viewComments()
( ).
, . , , , . – « » (" Fragile base class "). «- » – (fragility).
, ? . , CommentBlock
, .
class CommentBlock { protected $comments = []; public function viewComment(string $key): string { return $this->comments[$key]->view(); } public function viewComments(): string { $view = ''; foreach ($this->comments as $key => $comment) { $view .= $comment->view(); } return $view; } }
CountingCommentBlock
.
class CountingCommentBlock extends CommentBlock { private $cache; public function __construct(CounterInterface $cache) { $this->cache = $cache; } public function viewComment(string $key): string { $this->cache->increment($key); return parent::viewComment($key); } public function viewComments(): string { foreach ($this->comments as $key => $comment) { $this->cache->increment($key); } return parent::viewComments(); } }
CommentBlock::viewComments()
:
$view .= $comment->view();
, viewComment()
, – . . viewComment()
viewComments()
. , CommentBlock::viewComment()
CommentBlock::viewComments()
:
class CommentBlock { public function viewComments(): string { $view = ''; foreach ($this->comments as $key => $comment) { $view .= $this->viewComment($key);
CommentBlock
, , . CommentBlock
– , «». .
, . CountingCommentBlock
. :
$commentBlock = new CountingCommentBlock(new SomeCache()); $commentBlock->viewComments();
:
CountingCommentBlock::viewComments() -> CommentBlock::viewComments() -> (n ) CountingCommentBlock::viewComment()
: CountingCommentBlock::viewComments()
CountingCommentBlock::viewComment()
. 即 – . CountingCommentBlock
, , !
, protected . , . . , .
. , . , . , , « - ».
, . , « ». « » $this
, private
, .
, . , , . PHP ( public
, protected
, private
) final
.
final
PHP . , , .. , , , . , final
.
PHP 5 final
, , . , .
#1.
...
#2
...
: , .
PHP, « final »
PHP . . final
, .
, final
– PHP, . , PHP : typehints , ..
final
, , . 即 , . .
, , private
, . , .
, , , API. , «» , . «» - . «» , .
API « ». , .
, final
.
final
« »
– . , public protected .
, , . PHP ( -) . – , , , .
, () . « » ( Template method ).
, :
- . , () . .
final
. – . - . . , . () .
, abstract , , final , . , , . , .. final
.
. , , . , . . .., , - , , , . :
. « » abstract final .
CommentBlock
.
abstract class CommentBlock { protected $comments = []; abstract public function viewComment(string $key): string; final public function viewComments(): string { $view = ''; foreach ($this->comments as $key => $comment) { $view .= $this->viewComment($key); } return $view; } }
SimpleCommentBlock
:
final class SimpleCommentBlock extends CommentBlock { public function viewComment(string $key): string { return $this->comments[$key]->view(); } }
, , :
final class CountingCommentBlock extends CommentBlock { private $cache; public function __construct(CounterInterface $cache) { $this->cache = $cache; } public function viewComment(string $key): string { $this->cache->increment($key); return $this->comments[$key]->view(); } }
«», . final final .
. , . , « » down-call , , . .
, , . , .
- , . extends
, implements
.
- . CommentBlock
:
interface CommentBlock { public function viewComment(string $key): string; public function viewComments(): string; }
:
final class SimpleCommentBlock implements CommentBlock { private $comments = []; public function viewComment(string $key): string { return $this->comments[$key]->view(); } public function viewComments(): string { $view = ''; foreach ($this->comments as $key => $comment) { $view .= $this->viewComment($key); } return $view; } }
, :
final class CountingCommentBlock implements CommentBlock { private $comments = []; private $cache; public function __construct(CounterInterface $cache) { $this->cache = $cache; } public function viewComment(string $key): string { $this->cache->increment($key); return $this->comments[$key]->view(); } public function viewComments(): string { $view = ''; foreach ($this->comments as $key => $comment) { $view .= $this->viewComment($key); } return $view; } }
implements
- ( association ) .
-, . - . final
, : , ..
. . , public
. implements
, () , , ( ISP ). , .
/ ( OCP )? final
implements
– PHP .
, , , . , implements
, . .
final
, . . – , implements
final
.
« », . implements
, final
.
, « », , . – SimpleCommentBlock
CountingCommentBlock
viewComments()
.
, viewComments()
, , , , . , . ( decorator pattern ). , « », – final
, implements
.
, CommentBlock
, .
interface CommentBlock { public function getCommentKeys(): array; public function viewComment(string $key): string; public function viewComments(): string; }
, getCommentKeys()
. . , protected , CommentBlock
.
SimpleCommentBlock
- «» . , , implements
final
.
final class SimpleCommentBlock implements CommentBlock { private $comments = []; public function getCommentKeys(): array { return array_keys($this->comments); } public function viewComment(string $key): string { return $this->comments[$key]->view(); } public function viewComments(): string { $view = ''; foreach ($this->comments as $key => $comment) { $view .= $this->viewComment($key); } return $view; } }
CountingCommentBlock
- «» – OCP . CountingCommentBlock
: CommentBlock
.
final class CountingCommentBlock implements CommentBlock { private $commentBlock; private $cache; public function __construct(CommentBlock $commentBlock, CounterInterface $cache) { $this->commentBlock = $commentBlock; $this->cache = $cache; } public function getCommentKeys(): array { return $this->commentBlock->getCommentKeys(); } public function viewComment(string $key): string { $this->cache->increment($key); return $this->commentBlock->viewComment($key); } public function viewComments() : string { $commentKeys = $this->getCommentKeys(); foreach ($commentKeys as $commentKey) { $this->cache->increment($commentKey); } return $this->commentBlock->viewComments(); } }
- CountingCommentBlock
. , viewComment()
. ( forwarding methods ).
, « ». getCommentKeys()
. , «» , , .
, . , - «» . , – ( , SOLID) ( , , ).
. SimpleCommentBlock
CountingCommentBlock
CommentBlock
, . -, .
, , final
, CommentBlock
.
SimpleCommentBlock
CountingCommentBlock
- « », . « » , . , « ». – .
, final
, . , «--» – . DIP , .
: SimpleCommentBlock
– ; CountingCommentBlock
– SimpleCommentBlock
, (). 即 , – SRP . , , ( cohesion ) .
« » – (, , ), , . , .
.
viewRandomComment()
SimpleCommentBlock
- CountingCommentBlock
. , – viewRandomComment()
. CountingCommentBlock::viewRandomComment()
.
, viewComments()
SimpleCommentBlock
. CountingCommentBlock
SimpleCommentBlock
, .
final class SimpleCommentBlock implements CommentBlock { public function viewRandomComment(): string { $key = array_rand($this->comments); return $this->comments[$key]->view(); } public function viewComments() : string { $view = ''; foreach ($this->comments as $key => $comment) { $view .= $this->comments[$key]->view(); } return $view; } }
: , , , , , .. .
PHP , , final
, . , , , . , . . – , – .
, . PHPDoc , . : .
, , (.. , public
protected
). PSR-19: PHPDoc tags , , . PHPDoc , « -».
JavaDoc @implSpec , . , PHPDoc API , .. public . @implSpec
, API . , protected .
class CommentBlock { public function viewComment(string $key): string { return $this->comments[$key]->view(); } }
PHPDoc @implSpec
. $key
. – view()
.
, @implSpec
:
- ( , , , ..).
parent::method()
.
, «» . , self-call ( $this
) , , :
class CommentBlock { final public function viewComments(): string { $view = ''; foreach ($this->comments as $key => $comment) { $view .= $this->viewComment($key); } return $view; } }
, viewComments()
final
. viewComment()
, . :
viewComment()
viewComments()
;viewComments()
viewComment()
.
, . , , . « », CommentBlock
CountingCommentBlock
, . viewComment()
viewComments()
, , .
, , . PHPDoc API : , , .
, , , , , , «». – . PHPDoc , .
, , . – final
, . . – « ».
:
, , final
. final
, . , . , ?
final
. , , . , , final
, .
final
« », , , , , . , protected . , final
– , , , public , .
, , . , , , . – final
.
, final
? 不行 - , Opensource , , . IDE . , .
, , final
. final
, . .
final
, . public ? – , ?
, . , final
, , protected .
final
, code review ( code review ? ;). , .
. , , code review . .
. final
PHP5 . , , ( , ) .
, , . « » , . , C# virtual
, – override
. PHP final
extandable
, « final
», extend
.
, – . . . .
. , . , . final
, , -. , , implements
.
, , final
.
final
:
final class SimpleCommentBlock { public function getCommentKeys(): array { } public function viewComment(string $key): string { } public function viewComments(): string { } }
. , final
. , . .
, public
. , , .
, .
interface CommentBlock { public function getCommentKeys(): array; public function viewComment(string $key): string; public function viewComments(): string; }
.
final class SimpleCommentBlock implements CommentBlock { }
-, . - CommentBlock
. - .
final class CountingCommentBlock implements CommentBlock { private $commentBlock; public function __construct(CommentBlock $commentBlock ) { $this->commentBlock = $commentBlock; } }
, . , final
, , - . , final
.
final
, . , «». , . final
.
final
final
– ( PHPUnit, Mockery ) «» ( test doubles ). , .
, :
final class SimpleCommentBlockTest extends TestCase { public function testCreatingTestDouble(): void { $mock = $this->createMock(SimpleCommentBlock::class); } }
:
Class "SimpleCommentBlock" is declared "final" and cannot be mocked.
, « » PHPUnit , :
class Mock_SimpleCommentBlock_591bc3f3 extends SimpleCommentBlock { }
«» . -, , .. . , -, , PHPUnit . , .
final
. : . .
– «» . , ( Post
, Comment
) - ( value object ) ( stable ) . . classical TDD ( mockist TDD )
, ( volatile ) . , , «» – , . , «» , . ( DIP ), , «» .
, . 即 :
interface CommentBlock { }
:
final class SimpleCommentBlock implements CommentBlock { }
«» :
final class CommentBlockTest extends TestCase { public function testCreatingTestDouble(): void { $mock = $this->createMock(CommentBlock::class); } }
«» , . :
, «» – , -. , ( , ). , « » « ».
, final
.
– -. -. , -, . Mockery .
class SimpleCommentBlockTest extends TestCase { public function testCreatingProxyDouble() { $simpleCommentBlock = new SimpleCommentBlock(); $proxy = Mockery::mock($simpleCommentBlock); $proxy->shouldReceive('viewComment') ->andReturn('text'); $this->assertEquals('text', $proxy->viewComment('1')); $this->assertNotInstanceOf(SimpleCommentBlock::class, $proxy); } }
– - . , instanceof
. ( type declarations ), - .
« » – PHP, , . Bypass Finals , final
. composer final
:
public function testUsingBypassFinals(): void { BypassFinals::enable(); $mock = $this->createMock(SimpleCommentBlock::class); }
final
, PHP . « » , final
. , IDE final
.
PHPStorm
PHPStorm . File | Settings | Editor | File and Code Templates Files PHP Class . final
.

File | New | PHP Class :
final class SimpleCommentBlock { }
, . . – .
PHPStorm Refactor | Extract | Interface . , . ( Replace class reference with interface where possible ) PHPDoc ( Move PHPDoc ).
:
interface CommentBlock { public function viewComment(string $key): string; }
:
final class SimpleCommentBlock implements CommentBlock { public function viewComment(string $key): string { } }
File | New | PHP Class -, . :
final class CountingCommentBlock implements CommentBlock { private $commentBlock; }
Code | Generate | Constructor . .
final class CountingCommentBlock implements CommentBlock { public function __construct(CommentBlock $commentBlock) { $this->commentBlock = $commentBlock; } }
– . Code | Generate | Implement Methods . , . PHPStorm «» , IntelliJ IDEA ReSharper .
final class CountingCommentBlock implements CommentBlock { public function viewComment(string $key): string {
PHPDoc . .
PHPStan
, . , , , final
. ( ).
PHPStan . « » . PHPStan . .
FinalRule
localheinz/phpstan-rules
. PHPStan\Rules\Rule
processNode()
.
. , FinalRule
« » allowAbstractClasses
. , , classesNotRequiredToBeAbstractOrFinal
.
, composer :
composer require --dev phpstan/phpstan composer require --dev localheinz/phpstan-rules
FinalRule
phpstan.neon
:
services: - class: Localheinz\PHPStan\Rules\Classes\FinalRule arguments: allowAbstractClasses: true classesNotRequiredToBeAbstractOrFinal: [] tags: - phpstan.rules.rule
( max
):
vendor/bin/phpstan -lmax analyse src
:
------ ------------------------------------------------------------------------ Line CommentBlock.php ------ ------------------------------------------------------------------------ 10 Class CommentBlock is neither abstract nor final. ------ ------------------------------------------------------------------------
JSON Continuous Integration .
结论
, : final
! IDE, .
, , . – SOLID . , , .
, . :
, - - . . . , , , , . , . ?
, – , . final
. ( cognitive load ) – . – .
: final
. . – . . . , . , .
final
. ( SOLID ) ( fragile ) . , .