The document discusses principles and techniques for writing clean code in PHP, including:
- The S.O.L.I.D. principles for object-oriented design (single responsibility, open/closed, Liskov substitution, etc.)
- Object calisthenics rules for PHP functions/methods like using single indentation levels and early returns.
- Refactoring code examples to follow these principles and rules to improve readability, maintainability and testability of PHP code.
14. interface Bird
{
public function setLocation($longitude, $latitude);
public function draw();
}
interface FlightfulBird extends Bird
{
public function setHeight($height);
}
52. EXCESSIVE USAGE OF OBJECTS IN
PHP (IF PHP <7!) DRASTICALLY
INCREASES MEMORY FOOTPRINT!
Guilherme Blanco
53. class Item
{
final public static function find($id)
{
if (is_string($id) && trim($id) != '') {
// do find ...
}
throw new InvalidArgumentException('$id must be a non-empty string');
}
final public static function create($id, array $data)
{
if ( ! is_string($id)) {
throw new InvalidArgumentException('$id must be a string');
}
if (empty(trim($id))) {
throw new InvalidArgumentException('$id must be a non-empty string');
}
// do create ...
}
}
54. class Item
{
final public static function find($id)
{
if (! is_string($id) || trim($id) === '') {
throw new InvalidArgumentException('$id must be a non-empty string');
}
// do find ...
}
final public static function create($id, array $data)
{
if (! is_string($id) || trim($id) === '') {
throw new InvalidArgumentException('$id must be a non-empty string');
}
// do create ...
}
}
55. class Item
{
final public static function find($id)
{
if (! is_string($id) || trim($id) === '') {
throw new InvalidArgumentException('$id must be a non-empty string');
}
// do find ...
}
final public static function create($id, array $data)
{
if (! is_string($id) || trim($id) === '') {
throw new InvalidArgumentException('$id must be a non-empty string');
}
// do create ...
}
}
56. final class Id
{
/** @var string */
public $value;
public function __construct($value)
{
if (! is_string($id) || trim($id) === '') {
$message = sprintf('%s must be a non-empty string', $value);
throw new InvalidArgumentException($message);
}
$this->value = $value;
}
public function getValue()
{
return $this->value;
}
}
57. class Item
{
final public static function find(Id $id)
{
// do find ...
}
final public static function create(Id $id, array $data)
{
// do create ...
}
}
61. JUST USE A NULL OBJECT!
Someone watching this talk, one day
62. final class NullObject
{
public function __get($property)
{
return new self;
}
public function __set($property, $value)
{
return new self;
}
public function __call($method, array $arguments)
{
return new self;
}
public function __callStatic($method, array $arguments)
{
return new self;
}
public function__toString()
{
return 'null';
}
}
63. WHY IS IT BAD?
▸ Hide encapsulation problem
▸ Hard to debug and handle exceptions
▸ Codebase must be structured to use NullObject
▸ Hard to read and understand
72. public function getPage($data) { ... }
"Get" from where?
public function startProcess() { ... }
$trx->process('site.login');
How?
WTF is that?
renderHomePage
forkIntoChildProcess
extendedTranslator
73. BENEFITS
▸ Readability
▸ Better exposing method’s intent
▸ Improved maintainability
▸ Good indicator of code duplication and encapsulation
75. OBJECTIVE
▸ Maximum 200 lines per class
(including docblock/documentation)
▸ 10 methods per class
▸ Up to 20 lines per method
▸ 15 classes/interfaces/traits per namespace
78. class MyRegistrationService
{
protected $userService;
protected $passwordService;
protected $logger;
protected $translator;
protected $entityManager;
protected $imageCropper;
// ...
}
Database interactions
should be on UserService
Rely on an Event system
and move this to a listener
Cross-cutting concerns. Should be auto-
injected by your DI through an interface hint
83. ANY CLASS THAT CONTAINS AN
ARRAY MUST NOT HAVE ANY
OTHER PROPERTY.
Guilherme Blanco
TEXT
84. class User
{
private $name;
// ...
private $albumList = array();
public function getPublicAlbumList()
{
$filteredAlbumList = array();
foreach ($this->albumList as $album) {
if ($album->getPrivacy() === AlbumPrivacy::PUBLIC) {
$filteredAlbumList[] = $album;
}
}
return $filteredAlbumList;
}
// ...
}
$publicAlbumList = $user->getPublicAlbumList();
85. class AlbumList extends Collection
{
public function getPublic()
{
$filteredAlbumList = array();
foreach ($this->value as $album) {
if ($album->getPrivacy() === AlbumPrivacy::PUBLIC) {
$filteredAlbumList[] = $album;
}
}
return $filteredAlbumList;
}
}
class User
{
private $name;
private $albumList = new AlbumList();
// ...
}
$publicAlbumList = $user->getAlbumList()->getPublic();
86. class AlbumList extends Collection
{
public function getPublic()
{
return new ArrayCollection(
array_filter(
$this->value,
function (Album $album) {
return $album->isPublic();
}
)
);
}
}
class User
{
private $name;
private $albumList = new AlbumList();
// ...
}
$publicAlbumList = $user->getAlbumList()->getPublic();
87. BENEFITS
▸ Single Responsibility Principle
▸ Collection operations implemented inside of Collection
▸ Usage of SPL classes
▸ Easy to group collections without concerns over their
members’ behavior
▸ Filtering, ordering, mapping, combining are good
example methods
89. class BankAccount
{
public $balance = 0;
public function deposit($amount)
{
$this->balance += $amount;
}
public function withdraw($amount)
{
$this->balance -= $amount;
}
}
// Example:
$account = new BankAccount();
$account->deposit(100.00);
// ...
$account->balance = 0;
// ...
$account->withdraw(10.00);
Balance can be modified without class being
notified, leading to unexpected errors.