Repository::addCriteria()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
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\Specification\SpecificationInterface;
18
use Borobudur\Cqrs\ReadModel\Storage\Finder\FinderInterface;
19
use Borobudur\Cqrs\ReadModel\Storage\Finder\Pagination;
20
use Borobudur\Cqrs\ReadModel\Storage\StorageInterface;
21
22
/**
23
 * @author      Iqbal Maulana <[email protected]>
24
 * @created     8/19/15
25
 */
26
class Repository implements RepositoryInterface
27
{
28
    use MessageMapperTrait;
29
30
    /**
31
     * @var Pagination
32
     */
33
    protected $pagination;
34
35
    /**
36
     * @var StorageInterface
37
     */
38
    private $storage;
39
40
    /**
41
     * @var string
42
     */
43
    private $table;
44
45
    /**
46
     * @var string
47
     */
48
    private $class;
49
50
    /**
51
     * @var FinderInterface
52
     */
53
    private $finder;
54
55
    /**
56
     * Constructor.
57
     *
58
     * @param StorageInterface $storage
59
     * @param string           $table
60
     * @param string           $class
61
     */
62
    public function __construct(StorageInterface $storage, $table, $class)
63
    {
64
        $this->storage = $storage;
65
        $this->table = $table;
66
        $this->class = $class;
67
    }
68
69
    /**
70
     * {@inheritdoc}
71
     */
72
    public function save(ReadModelInterface $model)
73
    {
74
        $this->assertSameInstance($model);
75
        $this->storage->save($model, $this->table);
76
77
        return $model;
78
    }
79
80
    /**
81
     * {@inheritdoc}
82
     */
83
    public function count()
84
    {
85
        return $this->finder()->count();
86
    }
87
88
    /**
89
     * {@inheritdoc}
90
     */
91
    public function findById($id)
92
    {
93
        return $this->addCriteria(array('id' => $id))->first();
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99
    public function withSpecifications(array $specifications)
100
    {
101
        foreach ($specifications as $specification) {
102
            $this->withSpecification($specification);
103
        }
104
105
        return $this;
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111
    public function withSpecification(SpecificationInterface $specification)
112
    {
113
        $specification->matching($this->finder(), $this);
114
115
        return $this;
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121
    public function withPagination($limit, $page)
122
    {
123
        $this->pagination = new Pagination($limit, $page);
124
125
        return $this;
126
    }
127
128
    /**
129
     * {@inheritdoc}
130
     */
131
    public function findOne(array $fields)
132
    {
133
        return $this->addCriteria($fields)->first();
134
    }
135
136
    /**
137
     * {@inheritdoc}
138
     */
139
    public function find(array $fields)
140
    {
141
        return $this->addCriteria($fields)->get();
142
    }
143
144
    /**
145
     * {@inheritdoc}
146
     */
147
    public function findAll()
148
    {
149
        return $this->get();
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155
    public function remove($id)
156
    {
157
        $this->storage->remove($id, $this->table);
158
    }
159
160
    /*
161
     * {@inheritdoc}
162
     */
163
    public function reset()
164
    {
165
        $this->finder = null;
166
        $this->pagination = null;
167
168
        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::reset 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...
169
    }
170
171
    /**
172
     * Get finder that used for find data with complex criteria.
173
     *
174
     * @return FinderInterface
175
     */
176
    protected function finder()
177
    {
178
        if (null === $this->finder) {
179
            $this->finder = $this->storage->finder($this->table, $this->class);
180
        }
181
182
        return $this->finder;
183
    }
184
185
    /**
186
     * Add criteria.
187
     *
188
     * @param array $fields
189
     *
190
     * @return static
191
     */
192
    protected function addCriteria(array $fields)
193
    {
194
        foreach ($fields as $field => $value) {
195
            $this->finder()->where($this->finder()->expr()->equal($field, $value));
196
        }
197
198
        return $this;
199
    }
200
201
    /**
202
     * Get a collection.
203
     *
204
     * @return Collection
205
     */
206
    protected function get()
207
    {
208
        $results = $this->paginate()->finder()->get();
209
        if (null !== $this->pagination) {
210
            $results->setPagination($this->pagination);
211
        }
212
213
        $this->reset();
214
215
        return $results;
216
    }
217
218
    /**
219
     * Find first record.
220
     *
221
     * @return ReadModelInterface
222
     */
223
    protected function first()
224
    {
225
        $result = $this->finder()->limit(1)->first();
226
        $this->reset();
227
228
        return $result;
229
    }
230
231
    /**
232
     * Assert that read model instance should be same.
233
     *
234
     * @param ReadModelInterface $model
235
     */
236
    protected function assertSameInstance(ReadModelInterface $model)
237
    {
238
        if (ltrim($this->class, '\\') !== ltrim(get_class($model), '\\')) {
239
            throw new InvalidArgumentException(sprintf(
240
                'Repository "%s" only accept read model "%s", but got "%s".',
241
                get_called_class(),
242
                $this->class,
243
                get_class($model)
244
            ));
245
        }
246
    }
247
248
    /**
249
     * Build pagination.
250
     */
251
    private function paginate()
252
    {
253
        if (null !== $this->pagination && 0 !== ($total = $this->finder()->count())) {
254
            $this->pagination->calc($total);
255
            $this->finder()->limit($this->pagination->limit, $this->pagination->offset);
256
        }
257
258
        return $this;
259
    }
260
}
261