Skip to content

Instantly share code, notes, and snippets.

@df2k2
Last active May 27, 2018 05:30
Show Gist options
  • Save df2k2/4689b040ac293508330b7dfefec1092b to your computer and use it in GitHub Desktop.
Save df2k2/4689b040ac293508330b7dfefec1092b to your computer and use it in GitHub Desktop.
example1-1

Markdown Syntax

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');
    }
}
    </td>
   <td>
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];
    }
}
    </td>
</tr>

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.
    </td>
    <td>
interface SessionAdapterInterface
{}

RedisSessionAdapter implements SessionAdapterInterface
{}

class SessionManager
{
    public function __construct(SessionAdapterInterface $sessionAdapter)
    {}
}
    </td>
</tr>

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.
    </td>
    <td>
class Edit extends Action
{
    public function __constructor(
        ValidatorInterface $validator,
        HashGeneratorInterface $hashGenerator
    ) {}

    public function execute()
    {
        $errors = $this->validator->validate($request);
        // ...
        $hash = $this->hashGenerator->generateHash($request);
    }
}
    </td>
</tr>

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 %}

{% endcollapsible %}

{% collapsible Example #2: %}

Not recommended Recommended
{% highlight php %} class Edit extends Action { public function execute() { // ... $product = $productResource->load($product, $productSku, 'sku'); $this->registry->register('product', $product); } }

class View extends Template { public function getProductName() { $product = $this->registry->get('product'); return $product->getName(); } }

{% endhighlight %}

{% highlight php %} class Edit extends Action { public function execute() { // ... $product = $productRepository->get($productSku); } }

class View extends Template { public function getProductName() { // ... $product = $productRepository->get($productSku); return $product->getName(); } } // More flexible, no dependencies between classes, no temporal coupling.

{% endhighlight %}

{% endcollapsible %}


2.15. Method chaining in class design MUST be avoided.

2.16. [Law of Demeter] SHOULD be obeyed.

2.17. Patterns

2.17.1. Proxies SHOULD be used for lazy-loading optional dependencies.

2.17.2. Composites SHOULD be used when there is a need to work with a tree as a single object.

{% collapsible Example: %} You need to read configuration from different sources (like database or filesystem) and want to make the reading process configurable: allow extensions to add more configuration sources. In this case, you can create a ConfigReaderInterface with a composite implementation - ConfigReaderComposite, and configure particular readers as children of a composite reader. {% endcollapsible %}

2.17.3. Strategy SHOULD be used when there are multiple algorithms for performing an operation.

class Config
{
private $data;
public function getValue($key)
{
if ($this->data === null) {
$this->data = $this->fileReader->load('cache.xml');
}
return $this->data[$key];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment