Completed
Push — master ( 6738b5...e02aed )
by Iqbal
02:36
created

Repository::first()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 7
rs 9.4285
cc 1
eloc 4
nc 1
nop 0
1
<?php
2
/*
3
 * This file is part of the Borobudur-Cqrs package.
4
 *
5
 * (c) Hexacodelabs <http://hexacodelabs.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Borobudur\Cqrs\ReadModel\Repository;
12
13
use Borobudur\Bus\Message\MessageMapperTrait;
14
use Borobudur\Cqrs\Collection;
15
use Borobudur\Cqrs\Exception\InvalidArgumentException;
16
use Borobudur\Cqrs\ReadModel\ReadModelInterface;
17
use Borobudur\Cqrs\ReadModel\Repository\Scope\ScopeInterface;
18
use Borobudur\Cqrs\ReadModel\Storage\Finder\FinderInterface;
19
use Borobudur\Cqrs\ReadModel\Storage\StorageInterface;
20
21
/**
22
 * @author      Iqbal Maulana <[email protected]>
23
 * @created     8/19/15
24
 */
25
class Repository implements RepositoryInterface
26
{
27
    use MessageMapperTrait;
28
29
    /**
30
     * @var StorageInterface
31
     */
32
    private $storage;
33
34
    /**
35
     * @var string
36
     */
37
    private $table;
38
39
    /**
40
     * @var string
41
     */
42
    private $class;
43
44
    /**
45
     * @var FinderInterface
46
     */
47
    private $finder;
48
49
    /**
50
     * Constructor.
51
     *
52
     * @param StorageInterface $storage
53
     * @param string           $table
54
     * @param string           $class
55
     */
56
    public function __construct(StorageInterface $storage, $table, $class)
57
    {
58
        $this->storage = $storage;
59
        $this->table = $table;
60
        $this->class = $class;
61
    }
62
63
    /**
64
     * {@inheritdoc}
65
     */
66
    public function save(ReadModelInterface $model)
67
    {
68
        $this->assertSameInstance($model);
69
        $this->storage->save($model, $this->table);
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75
    public function count()
76
    {
77
        return $this->finder()->count();
78
    }
79
80
    /**
81
     * {@inheritdoc}
82
     */
83
    public function findById($id)
84
    {
85
        return $this->addCriteria(array('id' => $id))->first();
86
    }
87
88
    /**
89
     * {@inheritdoc}
90
     */
91
    public function scopes(array $scopes)
92
    {
93
        foreach ($scopes as $scope) {
94
            $this->scope($scope);
95
        }
96
97
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Borobudur\Cqrs\ReadModel\Repository\Repository) is incompatible with the return type declared by the interface Borobudur\Cqrs\ReadModel...sitoryInterface::scopes of type Borobudur\Cqrs\ReadModel\ReadModelInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
98
    }
99
100
    /**
101
     * {@inheritdoc}
102
     */
103
    public function scope(ScopeInterface $scope)
104
    {
105
        $scope->scoping($this->finder());
106
107
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Borobudur\Cqrs\ReadModel\Repository\Repository) is incompatible with the return type declared by the interface Borobudur\Cqrs\ReadModel...ositoryInterface::scope of type Borobudur\Cqrs\ReadModel\ReadModelInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113
    public function findOne(array $fields)
114
    {
115
        return $this->addCriteria($fields)->first();
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121
    public function find(array $fields)
122
    {
123
        return $this->addCriteria($fields)->get();
124
    }
125
126
    /**
127
     * {@inheritdoc}
128
     */
129
    public function findAll()
130
    {
131
        return $this->get();
132
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137
    public function remove($id)
138
    {
139
        $this->storage->remove($id, $this->table);
140
    }
141
142
    /**
143
     * Get finder that used for find data with complex criteria.
144
     *
145
     * @return FinderInterface
146
     */
147
    protected function finder()
148
    {
149
        if (null === $this->finder) {
150
            $this->finder = $this->storage->finder($this->table, $this->class);
151
        }
152
153
        return $this->finder;
154
    }
155
156
    /**
157
     * Add criteria.
158
     *
159
     * @param array $fields
160
     *
161
     * @return static
162
     */
163
    protected function addCriteria(array $fields)
164
    {
165
        foreach ($fields as $field => $value) {
166
            $this->finder()->where($this->finder()->expr()->equal($field, $value));
167
        }
168
169
        return $this;
170
    }
171
172
    /**
173
     * Get a collection.
174
     *
175
     * @return Collection
176
     */
177
    protected function get()
178
    {
179
        $results = $this->finder()->get();
180
        $this->finder = null;
181
182
        return $results;
183
    }
184
185
    /**
186
     * Find first record.
187
     *
188
     * @return ReadModelInterface
189
     */
190
    protected function first()
191
    {
192
        $result = $this->finder()->limit(1)->first();
193
        $this->finder = null;
194
195
        return $result;
196
    }
197
198
    /**
199
     * Assert that read model instance should be same.
200
     *
201
     * @param ReadModelInterface $model
202
     */
203
    protected function assertSameInstance(ReadModelInterface $model)
204
    {
205
        if (ltrim($this->class, '\\') !== ltrim(get_class($model), '\\')) {
206
            throw new InvalidArgumentException(sprintf(
207
                'Repository "%s" only accept read model "%s", but got "%s".',
208
                get_called_class(),
209
                $this->class,
210
                get_class($model)
211
            ));
212
        }
213
    }
214
}
215