class Config
{
private $data;
public function init() // or load()
{
$this->data = $this->fileReader->load('cache.xml');
}
public function getValue($key)
{
return $this->data[$key];
}
}
2.1. Object decomposition MUST follow the [SOLID principles].
2.2. Object MUST be ready for use after instantiation. No additional public initialization methods are allowed.
Not recommended | Recommended |
---|---|
class Config
{
private $data;
public function init() // or load()
{
$this->data = $this->fileReader->load('cache.xml');
}
public function getValue($key)
{
return $this->data[$key];
}
} |
class Config
{
private $data;
public function getValue($key)
{
if ($this->data === null) {
$this->data = $this->fileReader->load('cache.xml');
}
return $this->data[$key];
}
} |
2.3. Class constructor can have only dependency assignment operations and/or argument validation operations. No other operations are allowed.
2.3.1. Constructor SHOULD throw an exception when validation of an argument has failed.
class Composite
{
/**
* @var RendererInterface[]
*/
private $renderers;
/**
* @param RendererInterface[] $renderers
* @throws InvalidArgumentException
*/
public function __construct(array $renderers)
{
foreach ($renderers as $renderer) {
if (!$renderer instanceof RendererInterface) {
throw new InvalidArgumentException(
sprintf('Instance of the phrase renderer is expected, got %s instead.', get_class($renderer))
);
}
}
$this->renderers = $renderers;
}
}
2.3.2. Events MUST NOT be triggered in constructors.
Not recommended | Recommended |
---|---|
class Config
{
private $data;
public function __construct($fileReader, $eventManager)
{
$this->data = $fileReader->read('cache.xml');
$eventManager->dispatch('config_read_after');
}
}
class Config
{
private $fileReader;
private $eventManager;
public function __construct($fileReader, $eventManager)
{
$this->eventManager = $eventManager;
$this->fileReader = $fileReader;
}
public function getData($key)
{
if ($this->data === null) {
$this->data = $this->fileReader->read('cache.xml');
$this->eventManager->dispatch('config_read_after');
}
return $this->data[$key];
}
}
|
2.4. All dependencies MUST be requested by the most generic type that is required by the client object.
Not recommended | Recommended |
---|---|
interface SessionAdapterInterface
{}
RedisSessionAdapter implements SessionAdapterInterface
{}
class SessionManager
{
public function __construct(RedisSessionAdapter $sessionAdapter)
{}
}
// Breaks polymorphism principle, restricts what types can be passed at the runtime.
interface SessionAdapterInterface
{}
RedisSessionAdapter implements SessionAdapterInterface
{}
class SessionManager
{
public function __construct(SessionAdapterInterface $sessionAdapter)
{}
}
|
2.5. Proxies and interceptors MUST NEVER be explicitly requested in constructors.
2.6. Inheritance SHOULD NOT be used. Composition SHOULD be used for code reuse.
Not recommended | Recommended |
---|---|
class AbstractController extends Action
{
// ...
protected function validate(
$request
) {}
protected function generateHash(
$request
) {}
}
class Edit extends AbstractController
{
public function execute()
{
$errors = $this->validate(
$request
);
// ...
$hash = $this->generateHash(
$request
);
// ...
}
}
// Smaller classes, one responsibility, more flexible, easy to understand, more testable.
class Edit extends Action
{
public function __constructor(
ValidatorInterface $validator,
HashGeneratorInterface $hashGenerator
) {}
public function execute()
{
$errors = $this->validator->validate($request);
// ...
$hash = $this->hashGenerator->generateHash($request);
}
}
|
2.7. All non-public properties and methods SHOULD be private.
2.8. Abstract classes MUST NOT be marked as public @api
.
2.9. Service classes (ones that provide behavior but not data, like EventManager
) SHOULD NOT have a mutable state.
2.10. Only data objects or entities (Product, Category, etc.) MAY have any observable state.
2.11. "Setters" SHOULD NOT be used. They are only allowed in Data Transfer Objects.
2.12. "Getters" SHOULD NOT change the state of an object.
2.13. Static methods SHOULD NOT be used.
2.14. [Temporal coupling] MUST be avoided {% collapsible Example #1: %}
Not recommended | Recommended |
---|---|
{% highlight php %}
$url = new Url();
$url->setBaseUrl($baseUrl);
echo $url->get('custom/path'); // prints full URL
// Developer forgot or didn’t know that you need to call setBaseUrl $url = new Url(); echo $url->get('custom/path'); // Throws exception, which makes issue smaller. If it doesn't throw and exception, it could lead to a hidden bug more likely. // Method with out parameters that doesn’t return anything could be sign of temporal coupling. {% endhighlight %} |
{% highlight php %}
$url = new Url($baseUrl);
echo $url->get('custom/path');
// Or $url = new Url(); echo $url->get($baseUrl, 'custom/path'); // Only one way to use API, no temporal coupling. {% endhighlight %} |
{% collapsible Example #2: %}