Download the PHP package parshikovpavel/final-keyword without Composer
On this page you can find all versions of the php package parshikovpavel/final-keyword. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download parshikovpavel/final-keyword
More information about parshikovpavel/final-keyword
Files in parshikovpavel/final-keyword
Package final-keyword
Short Description Examples to examine from the article on habr.com
License MIT
Homepage https://github.com/parshikovpavel/final-keyword
Informations about the package final-keyword
Why restrict inheritance with final
?
The repository contains the code examples for learning from the article on habr.com
Use the following composer
command to fetch the source code examples:
The source code is separated according to the structure of the article.
For convenience, below are links to source code files and examples of their use to reproduce cases from the article. This information is also separated according to the structure of the article.
Introduction
The initial version of CommentBlock
class.
Inheritance issues
The inheritance violates the principle of the information hiding
CustomCommentBlock
child class demonstrating violation of the principle of the information hiding.
Banana-monkey-jungle problem
CachedPopularCommentBlock
classes are examples of a deep inheritance hierarchy.
Open recursion by default
CommentBlock::getComments()
method makes
a self-call of $this->getComments()
and relies on the implementation of behavior in the CommentBlock
class.
CommentBlock::getComments()
implementations, which is automatically inherited by CustomCommentBlock
, is incompatible with the behavior of CustomCommentBlock::getComment()
method. So getting a list of comments doesn't work correctly. You can see this by executing the following test:
Control of side effects
test result below demonstrates this thought:
Base class fragility
An inheritable base class are "fragile" because seemingly safe modifications to it, may cause the derived classes to malfunction. The programmer cannot determine whether a base class change is safe simply by examining in isolation the methods of the base class. So the implementation detail of the base and the derived classes become tightly related.
For example, during code refactoring the programmer change a single line in CommentBlock::viewComments()
method to simplify the code and to avoid code duplicate in the future.
The base class logic remains valid and it continues to pass the tests successfully. However base class isn't completely isolated. As a result, calling CountingCommentBlock::viewComments()
causes double increment of a view counter value. You can explore the problem in detail by studying the corresponding test:
Applying the final keyword to improve design
Template method pattern
The CommentBlock
is an abstract superclass which defines the skeleton of subclasses.
The CommentBlock::viewComment()
method concrete implementations of which are provided by subclasses.
The final SimpleCommentBlock::viewComment()
method which just returns a string view of the comment.
The final CountingCommentBlock::viewComment()
. In addition to returning a string view of the comment, this method increments the counter value in the cache.
Prefer interface implementation to inheritance
Let's avoid any class coupling by implementation details.
The CommentBlock
is an interface which defines the contract and hides implementation details.
The viewComments()
method.
Prefer aggregation to inheritance
The aggregation is the loosest relationship type. Let's use the aggregation in the form of the decorator pattern to replace the inheritance.
The CommentBlock
is an interface which defines the contract and hides implementation details.
The SimpleCommentBlock
is a base final class with base behavior that implements the mentioned interface.
The CountingCommentBlock::getCommentKeys()
is a simple single-line function that just transfers responsibility for the execution to the nested object.
CommentBlock
interface. Clients interacts with them transparently through the interface.
SimpleCommentBlock
и CountingCommentBlock
are coupled through an aggregation. For this reason they are devoid of all disadvantages of the inheritance: the base class fragility problem, the information hiding principle violation, etc. Changing the implementation detail of the base class doesn't affect the behavior and the structure of the derived class. As shown below all assertions which verify the CountingCommentBlock
behavior are successful.
A class must be prepared for the inheritance
The inheritance violates the principle of the information hiding. So it's necessary to document in PHPDoc not only a public interface but also the internal implementation details.
The this method's PHPDoc describes the use of parameters and the existing side effects.
The its PHPDoc reveals the schema of using all non-final methods.
A class must be prepared for the aggregation
The general schema to create a loosely coupled design consists of the following steps:
- An initial class (
SimpleCommentBlock
) is introduced into a design with thefinal
keyword and the inheritance restriction. - To expand the functionality of the class you need to analyze the base class behavior, form its contract and to formally describe it as an interface (
CommentBlock
). - Introduce into a design a derived decorator class (
CountingCommentBlock
) which expands the functionality of the base class and implements the same interface. An instance of the base class (SimpleCommentBlock
) is injected into the constructor of the derived class (CountingCommentBlock
) through the interface (CommentBlock
).
Using final classes in tests
Most unit test libraries use the inheritance to construct test doubles (stubs, mocks, etc). Therefore an attempt to mock the PHPUnit test:
results in a warning like this:
You can use two approaches to solve this problem.
-
Design approach. The test double is another simplified dummy contract implementation. So you should
SimpleCommentBlock
concrete final class. -
Magic approach. It's used if you don't have the necessary interface to create the test double as the use of such behavior through the interface isn't provided for by business tasks. In this case you have no choice but to remove the
final
inheritance restriction.The first approach is to use a proxy double which contains the original test double but has no
final
restriction. You can implement it manually, but it's better to use the ready-made Mockery implementation in the PHPUnit test.The second approach is to apply PHP magic to remove
final
keyword during loading files. Also the ready-made implementation is available as the Bypass library. It's enough to enable removingfinal
keywords in the PHPUnit test before loading a class file.
Tools for convenient work with final classes
Static analysis tools allow to find some problems in the code without actually running it. However, they can be used not only to search for typical errors, but also to control the code style. The most popular analyzer is PHPStan. It enables you to extend the functionality quite easily by writing custom rules. As an example you can examine and use the ready-made FinalRule
from the unofficial third-party localheinz/phpstan-rules
extension. The rule must be registered as a service in the phpstan.neon
configuration file. Issue the analyse
command and PHPStan will report an error when a non-abstract class is not final
.