Contents
- Variables
- Functions
- Objects and data structures
- Classes
- S: Single Responsibility Principle (SRP)
- O: Open/Closed Principle (OCP)
- L: Liskov Substitution Principle (LSP)
- I: Interface Segregation Principle (ISP)
- D: Dependency Inversion Principle (DIP)
Variables
Use meaningful and pronounceable variable names
Bad:
$ymdstr = $moment->format('y-m-d');
Good:
$currentDate = $moment->format('y-m-d');
Use the same vocabulary for the same type of variable
Bad:
getUserInfo();
getClientData();
getCustomerRecord();
Good:
getUser();
Use searchable names
We will read more code than we will ever write. That's why it's important to write code that is readable and searchable. When we give variables names that are useless for understanding our program, we hurt our future readers. Use names that are easy to search for.
Bad:
// What the heck is 86400 for?
addExpireAt(86400);
Good:
// Declare them as capitalized `const` globals.
interface DateGlobal {
const SECONDS_IN_A_DAY = 86400;
}
addExpireAt(DateGlobal::SECONDS_IN_A_DAY);
Use explanatory variables
Bad:
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/';
preg_match($cityZipCodeRegex, $address, $matches);
saveCityZipCode($matches[1], $matches[2]);
Not bad:
This is better, but we are still heavily dependent on the regular expression.
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/';
preg_match($cityZipCodeRegex, $address, $matches);
list(, $city, $zipCode) = $matches;
saveCityZipCode($city, $zipCode);
Good:
By naming the subpatterns, we reduce our dependence on the regular expression.
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(?.+?)\s*(?\d{5})?$/' ;
preg_match($cityZipCodeRegex, $address, $matches);
saveCityZipCode($matches['city'], $matches['zipCode']);
Avoid mental mapping
Don't force those who will read your code to translate the meaning of variables. Explicit is better than implicit.
Bad:
$l = ['Austin', 'New York', 'San Francisco'];
for ($i = 0; $i < count($l); $i++) {
$li = $l[$i];
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
// Wait, what is `$li` for again?
dispatch($li);
}
Good:
$locations = ['Austin', 'New York', 'San Francisco'];
foreach ($locations as $location) {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
dispatch($location);
});
Don't add unneeded context
If your class/object name already tells you something, don't project that association onto the variable name as well.
Bad:
$car = [
'carMake' => 'Honda',
'carModel' => 'Accord',
'carColor' => 'Blue',
];
function paintCar(&$car) {
$car['carColor'] = 'Red';
}
Good:
$car = [
'make' => 'Honda',
'model' => 'Accord',
'color' => 'Blue',
];
function paintCar(&$car) {
$car['color'] = 'Red';
}
Use default arguments instead of short-circuiting or conditionals
Bad:
function createMicrobrewery($name = null) {
$breweryName = $name ?: 'Hipster Brew Co.';
// ...
}
Good:
function createMicrobrewery($breweryName = 'Hipster Brew Co.') {
// ...
}
Functions
Function arguments (two or fewer ideally)
Limiting the number of function parameters is extremely important because it makes testing your function easier. Having more than three leads to a «combinatorial explosion», where you have to test a huge number of different cases for each individual argument.
The ideal case is no arguments at all. One or two is also fine, but three should be avoided. If you end up with more than that, they should be consolidated to reduce the count. Usually, if you have more than two arguments, your function is trying to do too much. In the cases where it isn't, most of the time a higher-level object will suffice as an argument.
Bad:
function createMenu($title, $body, $buttonText, $cancellable) {
// ...
}
Good:
class MenuConfig
{
public $title;
public $body;
public $buttonText;
public $cancelLabel = false;
}
$config = new MenuConfig();
$config->title = 'Foo';
$config->body = 'Bar';
$config->buttonText = 'Baz';
$config->cancelLabel = true;
function createMenu(MenuConfig $config) {
// ...
}
Functions should do one thing
This is by far the most important rule in software development. When functions do more than one thing, they are harder to compose, test, and reason about. When you can isolate a function to just a single action, it's much easier to refactor, and your code will read much cleaner. Even if you follow no other recommendation than this one, you'll still be ahead of many other developers.
Bad:
function emailClients($clients) {
foreach ($clients as $client) {
$clientRecord = $db->find($client);
if ($clientRecord->isActive()) {
email($client);
}
}
}
Good:
function emailClients($clients) {
$activeClients = activeClients($clients);
array_walk($activeClients, 'email');
}
function activeClients($clients) {
return array_filter($clients, 'isClientActive');
}
function isClientActive($client) {
$clientRecord = $db->find($client);
return $clientRecord->isActive();
}
Function names should say what they do
Bad:
function addToDate($date, $month) {
// ...
}
$date = new \DateTime();
// It's hard to tell from the function name what is added
addToDate($date, 1);
Good:
function addMonthToDate($month, $date) {
// ...
}
$date = new \DateTime();
addMonthToDate(1, $date);
Functions should only be one level of abstraction
When you have more than one level of abstraction, your function is usually doing too much. Splitting up functions leads to reusability and easier testing.
Bad:
function parseBetterJSAlternative($code) {
$regexes = [
// ...
];
$statements = split(' ', $code);
$tokens = [];
foreach($regexes as $regex) {
foreach($statements as $statement) {
// ...
}
}
$ast = [];
foreach($tokens as $token) {
// lex...
}
foreach($ast as $node) {
// parse...
}
}
Good:
function tokenize($code) {
$regexes = [
// ...
];
$statements = split(' ', $code);
$tokens = [];
foreach($regexes as $regex) {
foreach($statements as $statement) {
$tokens[] = /* ... */;
});
});
return $tokens;
}
function lexer($tokens) {
$ast = [];
foreach($tokens as $token) {
$ast[] = /* ... */;
});
return $ast;
}
function parseBetterJSAlternative($code) {
$tokens = tokenize($code);
$ast = lexer($tokens);
foreach($ast as $node) {
// parse...
});
}
Remove duplicate code
Do your best to avoid duplicate code. Duplicate code is bad because it means that if you need to change some logic, you'll have to do it in more than one place.
Imagine that you own a small restaurant and you keep track of your inventory: your tomatoes, onions, garlic, spices, and so on. If you keep several lists of what's in your refrigerators, then you have to update all of them whenever you cook a dish. If you have just one list, then there's only one place to update.
Often you have duplicate code because you're doing two or more things that have a lot in common. But a small difference between them forces you to write several functions that mostly do the same thing. Removing duplicate code means creating an abstraction that can handle all the differences with a single function/module/class.
Getting the abstraction right is critically important, which is why you should follow the SOLID principles described in the «Classes» section. Bad abstractions can be worse than duplicate code, so be careful! But if you can write good ones, do it! Don't repeat yourself, or you'll find that every time you make a change you have to update the code in several places.
Bad:
function showDeveloperList($developers) {
foreach($developers as $developer) {
$expectedSalary = $developer->calculateExpectedSalary();
$experience = $developer->getExperience();
$githubLink = $developer->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
function showManagerList($managers) {
foreach($managers as $manager) {
$expectedSalary = $manager->calculateExpectedSalary();
$experience = $manager->getExperience();
$githubLink = $manager->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
Good:
function showList($employees) {
foreach($employees as $employe) {
$expectedSalary = $employe->calculateExpectedSalary();
$experience = $employe->getExperience();
$githubLink = $employe->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
Don't use flags as function parameters
Flags tell your users that a function does more than one thing. Functions should do one thing. Split your functions apart if they follow different code branches based on boolean logic.
Bad:
function createFile($name, $temp = false) {
if ($temp) {
touch('./temp/'.$name);
} else {
touch($name);
}
}
Good:
function createFile($name) {
touch($name);
}
function createTempFile($name) {
touch('./temp/'.$name);
}
Avoid side effects
A function produces a side effect if it does anything other than take a value and return another value or values. A side effect might be writing to a file, changing a global variable, or accidentally wiring all of your money to a stranger.
But sometimes side effects are necessary. For example, that same writing to a file. It's better to do this in a centralized way. Don't have several functions and classes that write to a single file; use one single service for that. Just one.
The main goal is to avoid common pitfalls such as sharing state between several objects without any structure; using mutable data types that can be overwritten by anything; and having no centralized handling of side effects. If you can do this, you'll be happier than the vast majority of other programmers.
Bad:
// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
$name = 'Ryan McDermott';
function splitIntoFirstAndLastName() {
global $name;
$name = preg_split('/ /', $name);
}
splitIntoFirstAndLastName();
var_dump($name); // ['Ryan', 'McDermott'];
Good:
$name = 'Ryan McDermott';
function splitIntoFirstAndLastName($name) {
return preg_split('/ /', $name);
}
$newName = splitIntoFirstAndLastName($name);
var_dump($name); // 'Ryan McDermott';
var_dump($newName); // ['Ryan', 'McDermott'];
Don't write to global functions
Polluting the globals is a bad habit in any language, because you can conflict with another library, and the users of your API will be none the wiser until they get an exception in production. Here's an example: you need a configuration array. You write a global function like config(), but it can conflict with another library trying to do the same thing. That's why it's better to use the «singleton» design pattern and a simple configuration.
Bad:
function config() {
return [
'foo' => 'bar',
]
}
Good:
class Configuration {
private static $instance;
private function __construct() {/* */}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new Configuration();
}
return self::$instance;
}
public function get($key) {/* */}
public function getAll() {/* */}
}
$singleton = Configuration::getInstance();
Encapsulate conditionals
Bad:
if ($fsm->state === 'fetching' && is_empty($listNode)) {
// ...
}
Good:
function shouldShowSpinner($fsm, $listNode) {
return $fsm->state === 'fetching' && is_empty($listNode);
}
if (shouldShowSpinner($fsmInstance, $listNodeInstance)) {
// ...
}
Avoid negative conditionals
Bad:
function isDOMNodeNotPresent($node) {
// ...
}
if (!isDOMNodeNotPresent($node)) {
// ...
}
Good:
function isDOMNodePresent($node) {
// ...
}
if (isDOMNodePresent($node)) {
// ...
}
Avoid conditionals
This probably seems impossible. Hearing it for the first time, many people say: «How can I do anything without an if statement?» The second common question is: «Well, that's great, but why would I want to?» The answer lies in the clean-code rule discussed above: a function should do one thing. If you have classes and functions that contain an if statement, you're telling your users that the function does more than one thing. Don't forget — it should do just one thing.
Bad:
class Airplane {
// ...
public function getCruisingAltitude() {
switch ($this->type) {
case '777':
return $this->getMaxAltitude() - $this->getPassengerCount();
case 'Air Force One':
return $this->getMaxAltitude();
case 'Cessna':
return $this->getMaxAltitude() - $this->getFuelExpenditure();
}
}
}
Good:
class Airplane {
// ...
}
class Boeing777 extends Airplane {
// ...
public function getCruisingAltitude() {
return $this->getMaxAltitude() - $this->getPassengerCount();
}
}
class AirForceOne extends Airplane {
// ...
public function getCruisingAltitude() {
return $this->getMaxAltitude();
}
}
class Cessna extends Airplane {
// ...
public function getCruisingAltitude() {
return $this->getMaxAltitude() - $this->getFuelExpenditure();
}
}
Avoid type-checking (part 1)
PHP is untyped, which means your functions can accept arguments of any type. Sometimes this freedom even gets in the way, and it becomes tempting to do type-checking inside functions. But there are many ways to avoid it. The first thing to consider is consistent APIs.
Bad:
function travelToTexas($vehicle) {
if ($vehicle instanceof Bicycle) {
$vehicle->peddle($this->currentLocation, new Location('texas'));
} else if ($vehicle instanceof Car) {
$vehicle->drive($this->currentLocation, new Location('texas'));
}
}
Good:
function travelToTexas($vehicle) {
$vehicle->move($this->currentLocation, new Location('texas'));
}
Avoid type-checking (part 2)
If you're working with basic primitives (like strings and integers) and arrays, and can't use polymorphism, but you still feel the need for type-checking, then use type declarations or strict mode. This gives you static typing on top of the standard PHP syntax. The problem with manual type-checking is that doing it well requires so much verbosity that the artificial «type safety» you get doesn't make up for the loss of readability. Keep your PHP clean, write good tests, and do good code reviews. Or do all of that, but with strict PHP type declarations or strict mode.
Bad:
function combine($val1, $val2) {
if (is_numeric($val1) && is_numeric($val2)) {
return $val1 + $val2;
}
throw new \Exception('Must be of type Number');
}
Good:
function combine(int $val1, int $val2) {
return $val1 + $val2;
}
Remove dead code
Dead code is as bad as duplicate code. There's no reason to keep it in your codebase. If something isn't being called, get rid of it! If need be, dead code can be retrieved from your version history.
Bad:
function oldRequestModule($url) {
// ...
}
function newRequestModule($url) {
// ...
}
$req = new newRequestModule($requestUrl);
inventoryTracker('apples', $req, 'www.inventory-awesome.io');
Good:
function requestModule($url) {
// ...
}
$req = new requestModule($requestUrl);
inventoryTracker('apples', $req, 'www.inventory-awesome.io');
Objects and Data Structures
Use getters and setters
In PHP you can set the keywords public, protected and private for methods. With their help you'll control how an object's properties are changed.
- If you need to do more than just get an object's property, you don't have to find and change every accessor in the codebase.
- It makes it easier to add validation when you
set. - It lets you encapsulate the internal representation.
- With getters and setters it's easy to add logging and error handling.
- When inheriting such a class, you can override the default functionality.
- You can lazy-load an object's properties, for example by fetching them from a server.
This is also part of the Open/Closed Principle, which is one of the set of object-oriented design principles.
Bad:
class BankAccount {
public $balance = 1000;
}
$bankAccount = new BankAccount();
// Buy shoes...
$bankAccount->balance -= 100;
Good:
class BankAccount {
private $balance;
public function __construct($balance = 1000) {
$this->balance = $balance;
}
public function withdrawBalance($amount) {
if ($amount > $this->balance) {
throw new \Exception('Amount greater than available balance.');
}
$this->balance -= $amount;
}
public function depositBalance($amount) {
$this->balance += $amount;
}
public function getBalance() {
return $this->balance;
}
}
$bankAccount = new BankAccount();
// Buy shoes...
$bankAccount->withdrawBalance($shoesPrice);
// Get balance
$balance = $bankAccount->getBalance();
Objects should have private/protected members
Bad:
class Employee {
public $name;
public function __construct($name) {
$this->name = $name;
}
}
$employee = new Employee('John Doe');
echo 'Employee name: '.$employee->name; // Employee name: John Doe
Good:
class Employee {
protected $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
$employee = new Employee('John Doe');
echo 'Employee name: '.$employee->getName(); // Employee name: John Doe
Classes
Single Responsibility Principle (SRP)
As the book Clean Code says: «There should never be more than one reason for a class to change». It's tempting to cram a class full of all kinds of functionality, the way we do with the bags and backpacks we're allowed to bring as carry-on luggage on a plane. The problem is that your class won't be conceptually cohesive, and so there will be many reasons to change it. It's important to minimize the number of times you need to change a class. And that's important because when a class has too much functionality and you need to change part of it, it can be hard to understand how that will affect the dependent modules in your codebase.
Bad:
class UserSettings {
private $user;
public function __construct($user) {
$this->user = user;
}
public function changeSettings($settings) {
if ($this->verifyCredentials()) {
// ...
}
}
private function verifyCredentials() {
// ...
}
}
Good:
class UserAuth {
private $user;
public function __construct($user) {
$this->user = user;
}
protected function verifyCredentials() {
// ...
}
}
class UserSettings {
private $user;
public function __construct($user) {
$this->user = $user;
$this->auth = new UserAuth($user);
}
public function changeSettings($settings) {
if ($this->auth->verifyCredentials()) {
// ...
}
}
}
Open/Closed Principle (OCP)
As Bertrand Meyer put it: «Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification». What does that mean? Let users add new functionality without changing the code.
Bad:
abstract class Adapter {
protected $name;
public function getName() {
return $this->name;
}
}
class AjaxAdapter extends Adapter {
public function __construct() {
parent::__construct();
$this->name = 'ajaxAdapter';
}
}
class NodeAdapter extends Adapter {
public function __construct() {
parent::__construct();
$this->name = 'nodeAdapter';
}
}
class HttpRequester {
private $adapter;
public function __construct($adapter) {
$this->adapter = $adapter;
}
public function fetch($url) {
$adapterName = $this->adapter->getName();
if ($adapterName === 'ajaxAdapter') {
return $this->makeAjaxCall($url);
} else if ($adapterName === 'httpNodeAdapter') {
return $this->makeHttpCall($url);
}
}
protected function makeAjaxCall($url) {
// request and return promise
}
protected function makeHttpCall($url) {
// request and return promise
}
}
Good:
abstract class Adapter {
abstract protected function getName();
abstract public function request($url);
}
class AjaxAdapter extends Adapter {
protected function getName() {
return 'ajaxAdapter';
}
public function request($url) {
// request and return promise
}
}
class NodeAdapter extends Adapter {
protected function getName() {
return 'nodeAdapter';
}
public function request($url) {
// request and return promise
}
}
class HttpRequester {
private $adapter;
public function __construct(Adapter $adapter) {
$this->adapter = $adapter;
}
public function fetch($url) {
return $this->adapter->request($url);
}
}
Liskov Substitution Principle (LSP)
Behind this scary term hides a very simple idea. The formal definition: «If S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., objects of type S may be substituted for objects of type T) without altering any of the properties of the program (correctness, task performed, etc.)». An even scarier definition.
It can be explained more simply: if you have a parent and a child class, then they can be used interchangeably without producing incorrect results. Consider the classic example of a square and a rectangle. Mathematically, a square is a rectangle, but if you model this is-a relationship through inheritance, you'll run into trouble.
Bad:
class Rectangle {
private $width, $height;
public function __construct() {
$this->width = 0;
$this->height = 0;
}
public function setColor($color) {
// ...
}
public function render($area) {
// ...
}
public function setWidth($width) {
$this->width = $width;
}
public function setHeight($height) {
$this->height = $height;
}
public function getArea() {
return $this->width * $this->height;
}
}
class Square extends Rectangle {
public function setWidth($width) {
$this->width = $this->height = $width;
}
public function setHeight(height) {
$this->width = $this->height = $height;
}
}
function renderLargeRectangles($rectangles) {
foreach($rectangle in $rectangles) {
$rectangle->setWidth(4);
$rectangle->setHeight(5);
$area = $rectangle->getArea(); // Bad: Will return 25 for Square. Should be 20.
$rectangle->render($area);
});
}
$rectangles = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles($rectangles);
Good:
abstract class Shape {
private $width, $height;
abstract public function getArea();
public function setColor($color) {
// ...
}
public function render($area) {
// ...
}
}
class Rectangle extends Shape {
public function __construct {
parent::__construct();
$this->width = 0;
$this->height = 0;
}
public function setWidth($width) {
$this->width = $width;
}
public function setHeight($height) {
$this->height = $height;
}
public function getArea() {
return $this->width * $this->height;
}
}
class Square extends Shape {
public function __construct {
parent::__construct();
$this->length = 0;
}
public function setLength($length) {
$this->length = $length;
}
public function getArea() {
return $this->length * $this->length;
}
}
function renderLargeRectangles($rectangles) {
foreach($rectangle in $rectangles) {
if ($rectangle instanceof Square) {
$rectangle->setLength(5);
} else if ($rectangle instanceof Rectangle) {
$rectangle->setWidth(4);
$rectangle->setHeight(5);
}
$area = $rectangle->getArea();
$rectangle->render($area);
});
}
$shapes = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles($shapes);
Interface Segregation Principle (ISP)
According to the ISP, «Clients should not be forced to depend on interfaces they do not use».
A good example demonstrating the principle: classes that require large settings objects. It's recommended not to force clients to configure a lot of parameters, because for the most part they don't need them. Making them optional helps avoid a bloated interface.
Bad:
interface WorkerInterface {
public function work();
public function eat();
}
class Worker implements WorkerInterface {
public function work() {
// ....working
}
public function eat() {
// ...... eating in launch break
}
}
class SuperWorker implements WorkerInterface {
public function work() {
//.... working much more
}
public function eat() {
//.... eating in launch break
}
}
class Manager {
/** @var WorkerInterface $worker **/
private $worker;
public function setWorker(WorkerInterface $worker) {
$this->worker = $worker;
}
public function manage() {
$this->worker->work();
}
}
Good:
interface WorkerInterface extends FeedableInterface, WorkableInterface {
}
interface WorkableInterface {
public function work();
}
interface FeedableInterface {
public function eat();
}
class Worker implements WorkableInterface, FeedableInterface {
public function work() {
// ....working
}
public function eat() {
//.... eating in launch break
}
}
class Robot implements WorkableInterface {
public function work() {
// ....working
}
}
class SuperWorker implements WorkerInterface {
public function work() {
//.... working much more
}
public function eat() {
//.... eating in launch break
}
}
class Manager {
/** @var $worker WorkableInterface **/
private $worker;
public function setWorker(WorkableInterface $w) {
$this->worker = $w;
}
public function manage() {
$this->worker->work();
}
}
Dependency Inversion Principle (DIP)
This principle states:
- High-level modules should not depend on low-level ones. Both kinds should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
This may be hard to grasp at first, but if you've worked with PHP frameworks (like Symfony), then you've already encountered an implementation of this principle in the form of Dependency Injection (DI). These principles aren't identical, though: DI shields high-level modules from the details of their low-level modules and their configuration. This can be done through DI. The huge advantage is that it reduces the coupling between modules. Coupling is a very bad development pattern that makes refactoring code harder.
Bad:
class Worker {
public function work() {
// ....working
}
}
class Manager {
/** @var Worker $worker **/
private $worker;
public function __construct(Worker $worker) {
$this->worker = $worker;
}
public function manage() {
$this->worker->work();
}
}
class SuperWorker extends Worker {
public function work() {
//.... working much more
}
}
Good:
interface WorkerInterface {
public function work();
}
class Worker implements WorkerInterface {
public function work() {
// ....working
}
}
class SuperWorker implements WorkerInterface {
public function work() {
//.... working much more
}
}
class Manager {
/** @var Worker $worker **/
private $worker;
public function __construct(WorkerInterface $worker) {
$this->worker = $worker;
}
public function manage() {
$this->worker->work();
}
}
Chain methods together
This is a very useful and common pattern used in many libraries, for example PHPUnit and Doctrine. It makes your codebase more expressive and less verbose. That's why I recommend chaining methods together, and you'll see for yourself how clean your code becomes. At the end of each class function, just return this — and you'll be able to attach the next class method to it.
Bad:
class Car {
private $make, $model, $color;
public function __construct() {
$this->make = 'Honda';
$this->model = 'Accord';
$this->color = 'white';
}
public function setMake($make) {
$this->make = $make;
}
public function setModel($model) {
$this->model = $model;
}
public function setColor($color) {
$this->color = $color;
}
public function dump() {
var_dump($this->make, $this->model, $this->color);
}
}
$car = new Car();
$car->setColor('pink');
$car->setMake('Ford');
$car->setModel('F-150');
$car->dump();
Good:
class Car {
private $make, $model, $color;
public function __construct() {
$this->make = 'Honda';
$this->model = 'Accord';
$this->color = 'white';
}
public function setMake($make) {
$this->make = $make;
// NOTE: Returning this for chaining
return $this;
}
public function setModel($model) {
$this->model = $model;
// NOTE: Returning this for chaining
return $this;
}
public function setColor($color) {
$this->color = $color;
// NOTE: Returning this for chaining
return $this;
}
public function dump() {
var_dump($this->make, $this->model, $this->color);
}
}
$car = (new Car())
->setColor('pink')
->setMake('Ford')
->setModel('F-150')
->dump();
Prefer composition over inheritance
As the famous book «Design Patterns» by the Gang of Four says, you should prefer composition over inheritance wherever possible. There are many good reasons to use both inheritance and composition. The main point of this maxim is that if you instinctively lean toward inheritance, try to imagine whether composition could solve your problem better. In some cases it really is the more suitable option.
You'll ask: «And when is it better to choose inheritance?» It all depends on the specific problem, but you can use this list of situations where inheritance is preferable to composition:
- Your inheritance is an is-a relationship, not a has-a one. Example: Human → Animal vs. User → UserDetails.
- You can reuse code from the base classes. (Humans can move like animals.)
- You want to make global changes to derived classes by changing the base class. (Changing animals' calorie expenditure while moving.)
Bad:
class Employee {
private $name, $email;
public function __construct($name, $email) {
$this->name = $name;
$this->email = $email;
}
// ...
}
// Bad because Employees "have" tax data.
// EmployeeTaxData is not a type of Employee
class EmployeeTaxData extends Employee {
private $ssn, $salary;
public function __construct($ssn, $salary) {
parent::__construct();
$this->ssn = $ssn;
$this->salary = $salary;
}
// ...
}
Good:
class EmployeeTaxData {
private $ssn, $salary;
public function __construct($ssn, $salary) {
$this->ssn = $ssn;
$this->salary = $salary;
}
// ...
}
class Employee {
private $name, $email, $taxData;
public function __construct($name, $email) {
$this->name = $name;
$this->email = $email;
}
public function setTaxData($ssn, $salary) {
$this->taxData = new EmployeeTaxData($ssn, $salary);
}
// ...
}









Comments