Skip to content

Instantly share code, notes, and snippets.

@Phillaf
Created August 30, 2017 19:54
Show Gist options
  • Save Phillaf/b25a72f0835eb778d58411f4076c8e83 to your computer and use it in GitHub Desktop.
Save Phillaf/b25a72f0835eb778d58411f4076c8e83 to your computer and use it in GitHub Desktop.

Currently we have entities that looks like this:

class MemberSession implements Entity
{
    private $memberId;
    private $subscriptionId;
    private $username;
    private $nickname;
    private $trial;
    private $expired;
    private $email;

    public function __construct(
        int $memberId = null,
        int $subscriptionId = null,
        string $username = '',
        string $nickname = '',
        bool $trial = false,
        bool $expired = false,
        string $email = ''
    ) {
        $this->memberId = $memberId;
        $this->subscriptionId = $subscriptionId;
        $this->username = $username;
        $this->nickname = $nickname;
        $this->trial = $trial;
        $this->expired = $expired;
        $this->email = $email;
    }
    
    // I some getters and setters here
    
    public function toArray() : array
    {
        return [
            MemberField::ID => $this->memberId,
            MemberField::SUBSCRIPTION_ID => $this->subscriptionId,
            MemberField::USERNAME => $this->username,
            MemberField::NICKNAME => $this->nickname,
            MemberField::EMAIL => $this->email,
            // wait, why don't we have the value for $trial and $expired here?
        ];
    }

And they are a pain to deal with

return (new MemberSessionBuilder)
    ->setMemberId($session->getMemberId())
    ->setSubscriptionId($session->getSubscriptionId())
    ->setUsername($session->getUsername())
    ->setNickname($request->get(MemberField::NICKNAME)) // did you notice what's going on here?
    ->setExpired($session->isExpired())
    ->setTrial($session->isTrial())
    ->build();

So instead of that, I propose this class

class MemberSession implements Entity
{
    private $data = [
      'memberId' => null,
      'subscriptionId' => null,
      'username' => null,
      'nickname' => null,
      'trial' => null,
      'expired' => null,
      'email' => null,
    ];
       
    // I some getters and setters here
}

And the Entity class can implement these methods

class Entity
{
    private $data;

    public function __construct(array $rawData)
    {
        $camelized = $this->camelizeKeys($rawData);                 // use matching field names or underscored matching field names
        $sanitized = array_intersect_key($camelized, $this->data);  // remove fields that are not part of the entity
        $this->data = array_merge($this->data, $sanitized);         // update affected values
    }

    public function toArray() : array
    {
        return $this->data; 
    }

    /**
     * @param string[] $rawData
     * @return string[]
     * */
    private function camelizeKeys(array $rawData) : array
    {
        $camelized = [];

        foreach ($rawData as $key => $value) {
            $camelized[$this->camelize($key)] = $value;
        }

        return $camelized;
    }

    private function camelize(string $word) : string
    {
        $camelized = str_replace('_', '', ucwords($word, '_'));
        return lcfirst($camelized);
    }
}

Here are a few advantages:

  • All allowed fields can be used in the constructor, they are also all optional. This can efficiently replace the builder.
  • All available fields are returned all the time by the toArray method. this is more consistant.
  • Entities can be copied in a single line of code new MemberSession($otherMemberSession->toArray())
  • They support matching structure from elasticsearch or mysql.

Other concerns:

Keys used in the array could be misspelled. This can be supported by adding something like this to the entity:

public function strictBuild(array $data)
{
    $camelized = $this->camelizeKeys($rawData);
    $sanitized = array_intersect_key($camelized, $this->data);
    if (!$this->validKeys($camelized, $sanitized)) {
      throw new \Exception('You are trying to fill an entity with unrecognized data');
    }
    $this->data = array_merge($this->data, $sanitized);
}

private function validKeys(array $camelized, array $sanitized)
{
    $supporteKeys = array_keys($sanitized);
    $providedKeys = array_keys($camelized);
    return count(array_diff($supportedKeys, $providedKeys)) == 0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment