Registry::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
namespace Fwk\Db\Registry;
3
4
use Fwk\Db\Accessor;
5
use Fwk\Db\Connection;
6
use Fwk\Db\Events\AbstractEntityEvent;
7
use Fwk\Db\Events\AfterSaveEvent;
8
use Fwk\Db\Events\FreshEvent;
9
use Fwk\Db\EventSubscriberInterface;
10
use Fwk\Db\Exception;
11
use Fwk\Db\Exceptions\UnregisteredEntityException;
12
use Fwk\Db\Table;
13
use Fwk\Db\Workers\DeleteEntityWorker;
14
use Fwk\Db\Workers\SaveEntityWorker;
15
use Fwk\Events\Dispatcher;
16
use Fwk\Events\Event;
17
use \SplObjectStorage;
18
19
class Registry implements \Countable, \IteratorAggregate
20
{
21
    const ACTION_SAVE           = 'save';
22
    const ACTION_DELETE         = 'delete';
23
24
    /**
25
     * Storage handler
26
     *
27
     * @var SplObjectStorage
28
     */
29
    protected $store;
30
31
    /**
32
     * Table name
33
     *
34
     * @var string
35
     */
36
    protected $tableName;
37
38
    /**
39
     * @var integer
40
     */
41
    protected $_priority    = \PHP_INT_MAX;
0 ignored issues
show
Coding Style introduced by
$_priority does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
42
43
    /**
44
     * Constructor
45
     *
46
     * @param string $tableName
47
     *
48
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
49
     */
50
    public function __construct($tableName)
51
    {
52
        $this->tableName    = $tableName;
53
        $this->store        = new SplObjectStorage();
54
    }
55
56
    /**
57
     * Stores an object into registry
58
     *
59
     * @param mixed $object
60
     *
61
     * @return Entry
62
     */
63
    public function store($object, array $identifiers = array(), $state = RegistryState::UNKNOWN, array $data = array())
64
    {
65
        if ($this->contains($object)) {
66
            return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Fwk\Db\Registry\Registry) is incompatible with the return type documented by Fwk\Db\Registry\Registry::store of type Fwk\Db\Registry\Entry.

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...
67
        }
68
69
        $entry = Entry::factory($object, $identifiers, $state, $data);
70
71
        $dispatcher = $entry->data('dispatcher', new Dispatcher());
72
        $listeners  = $entry->data('listeners', array());
73
74
        /**
75
         * @todo Put this one elsewhere
76
         */
77
        $dispatcher->on(AfterSaveEvent::EVENT_NAME, array($this, 'getLastInsertId'));
78
79
        $dispatcher->addListener($object);
80
        if ($object instanceof EventSubscriberInterface) {
81
            foreach ($object->getListeners() as $key => $listener) {
82
                if (is_object($listener) && !is_callable($listener)) {
83
                    $dispatcher->addListener($listener);
84
                } elseif (is_callable($listener)) {
85
                    $dispatcher->on($key, $listener);
86
                }
87
            }
88
        }
89
90
        foreach ($listeners as $key => $listener) {
91
            if (is_object($listener) && !is_callable($listener)) {
92
                $dispatcher->addListener($listener);
93
            } elseif (is_callable($listener)) {
94
                $dispatcher->on($key, $listener);
95
            }
96
        }
97
98
        $this->store->attach($entry);
99
100
        return $entry;
101
    }
102
103
    /**
104
     * Fetches an object
105
     *
106
     * @param  array $identifiers
107
     *
108
     * @return object|null
109
     */
110
    public function get(array $identifiers, $entityClass = null)
111
    {
112
        foreach ($this->store as $entry) {
113
            if ($entry->match($identifiers, $entityClass)) {
114
                return $entry->getObject();
115
            }
116
        }
117
118
        return null;
119
    }
120
121
    /**
122
     * Returns the table name
123
     *
124
     * @return string
125
     */
126
    public function getTableName()
127
    {
128
        return $this->tableName;
129
    }
130
131
132
    /**
133
     *
134
     * @param mixed $object
135
     *
136
     * @return Entry
137
     */
138
    public function getEntry($object)
139
    {
140
        foreach ($this->store as $entry) {
141
            if ($entry->matchObject($object)) {
142
                return $entry;
143
            }
144
        }
145
146
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Fwk\Db\Registry\Registry::getEntry of type Fwk\Db\Registry\Entry.

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...
147
    }
148
149
    /**
150
     *
151
     * @return Entry
152
     */
153
    protected function getEntryByIdentifiers(array $identifiers, $className = null)
154
    {
155
        foreach ($this->store as $entry) {
156
            if ($entry->match($identifiers, $className)) {
157
                return $entry;
158
            }
159
        }
160
161
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Fwk\Db\Registry\Registry::getEntryByIdentifiers of type Fwk\Db\Registry\Entry.

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...
162
    }
163
164
    /**
165
     * Returns an Event Dispatcher attached to a stored object
166
     *
167
     * @return Dispatcher
168
     * @throws UnregisteredEntityException if the $object is not registered
169
     */
170
    public function getEventDispatcher($object)
171
    {
172
        $entry = $this->getEntry($object);
173
        if ($entry === false) {
174
            throw new UnregisteredEntityException(sprintf('Unregistered entity (%s)', get_class($object)));
175
        }
176
177
        return $entry->data('dispatcher', new Dispatcher());
178
    }
179
180
    /**
181
     *
182
     * @param mixed $object
183
     * @param \Fwk\Events\Event $event
184
     *
185
     * @return void
186
     */
187
    public function fireEvent($object, Event $event)
188
    {
189
        $this->getEventDispatcher($object)->notify($event);
190
    }
191
192
    /**
193
     * Listener to fetch last insert ID on auto-increment columns
194
     *
195
     * @param AbstractEntityEvent $event
196
     *
197
     * @return void
198
     */
199
    public function getLastInsertId(AbstractEntityEvent $event)
200
    {
201
        $connx  = $event->getConnection();
202
        $table  = $connx->table($this->getTableName());
203
        $obj    = $event->getEntity();
204
        $entry  = $this->getEntry($obj);
205
206
        if (false === $entry) {
207
            return;
208
        }
209
210
        foreach ($table->getColumns() as $column) {
211
            if (!$column->getAutoincrement()) {
212
                continue;
213
            }
214
215
            $columnName     = $column->getName();
216
            $access         = Accessor::factory($obj);
217
            $test           = $access->get($columnName);
218
219
            if (!empty($test)) {
220
                continue;
221
            }
222
223
            $lastInsertId   = $connx->lastInsertId();
224
            $access->set($columnName, $lastInsertId);
225
            $ids = $entry->getIdentifiers();
226
            $ids[$columnName] = $lastInsertId;
227
            $entry->setIdentifiers($ids)->fresh();
228
        }
229
    }
230
231
    /**
232
     * Removes an object from the registry
233
     *
234
     * @param  mixed $object
235
     *
236
     * @return Registry
237
     * @throws UnregisteredEntityException if the $object is not registered
238
     */
239
    public function remove($object)
240
    {
241
        $entry = $this->getEntry($object);
242
        if ($entry === false) {
243
            throw new UnregisteredEntityException(sprintf('Unregistered entity (%s)', get_class($object)));
244
        }
245
246
        $this->store->detach($entry);
247
        unset($entry);
248
249
        return $this;
250
    }
251
252
    /**
253
     * Removes an object from its identifiers
254
     *
255
     * @param array $identifiers
256
     * @param string $className
0 ignored issues
show
Documentation introduced by
Should the type for parameter $className not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
257
     *
258
     * @return Registry
259
     */
260
    public function removeByIdentifiers(array $identifiers, $className = null)
261
    {
262
        $entry = $this->getEntryByIdentifiers($identifiers, $className);
263
        if (false !== $entry) {
264
            $this->store->detach($entry);
265
            unset($entry);
266
        }
267
268
        return $this;
269
    }
270
271
    /**
272
     * Tells if the registry contains an instance of the object
273
     *
274
     * @param mixed $object
275
     *
276
     * @return boolean
277
     */
278
    public function contains($object)
0 ignored issues
show
Coding Style introduced by
function contains() does not seem to conform to the naming convention (^(?:is|has|should|may|su...ster|unregister|exists)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
279
    {
280
        return false !== $this->getEntry($object);
281
    }
282
283
    /**
284
     *
285
     * @param object $object
286
     *
287
     * @return integer
288
     * @throws UnregisteredEntityException
289
     */
290
    public function getState($object)
291
    {
292
        $entry = $this->getEntry($object);
293
        if ($entry === false) {
294
            return RegistryState::UNREGISTERED;
295
        }
296
297
        return $entry->getState();
298
    }
299
300
    /**
301
     *
302
     * @return array
303
     */
304
    public function toArray()
305
    {
306
        $arr = array();
307
        foreach ($this->store as $entry) {
308
            $arr[] = $entry->getObject();
309
        }
310
311
        return $arr;
312
    }
313
314
    /**
315
     *
316
     * @return integer
317
     */
318
    public function count()
319
    {
320
        return count($this->store);
321
    }
322
323
    /**
324
     *
325
     * @return \ArrayIterator
326
     */
327
    public function getIterator()
328
    {
329
        return new \ArrayIterator($this->toArray());
330
    }
331
332
333
    public function markForAction($object, $action, array $listeners = array())
334
    {
335
        $entry  = $this->getEntry($object);
336
        if (false === $entry) {
337
            $entry = $this->store($object, array(), RegistryState::REGISTERED, array('listeners' => $listeners));
338
        }
339
340
        $entry->setAction($action, $this->_priority);
341
        $this->_priority--;
342
    }
343
344
    /**
345
     *
346
     * @return \SplPriorityQueue
347
     */
348
    public function getWorkersQueue()
349
    {
350
        $queue  = new \SplPriorityQueue();
351
352
        foreach ($this->store as $entry) {
353
            if (!$entry->hasAction()) {
354
                continue;
355
            }
356
357
            $action     = $entry->getAction();
358
            $object     = $entry->getObject();
359
360
            $access     = new Accessor($object);
361
            $relations  = $access->getRelations();
362
            foreach ($relations as $key => $relation) {
363
                $relation->setParent($object, $this->getEventDispatcher($object));
364
            }
365
366
            switch ($action) {
367
                case self::ACTION_DELETE:
368
                    $worker     = new DeleteEntityWorker($object);
369
                    break;
370
371
                case self::ACTION_SAVE:
372
                    $worker     = new SaveEntityWorker($object);
373
                    break;
374
375
                default:
376
                    throw new Exception(sprintf("Unknown registry action '%s'", $action));
377
            }
378
379
            $worker->setRegistry($this);
380
            $queue->insert($worker, $entry->getActionPriority());
381
        }
382
383
        return $queue;
384
    }
385
386
    /**
387
     * Mark current object values (Accessor) as initial values
388
     *
389
     * @param mixed $object
390
     *
391
     * @return void
392
     */
393
    public function defineInitialValues($object, Connection $connection = null, Table $table = null)
394
    {
395
        $entry = $this->getEntry($object);
396
        if (false === $entry) {
397
            throw new UnregisteredEntityException(sprintf('Unregistered entity (%s)', get_class($object)));
398
        }
399
400
        $entry->fresh();
401
402
        if ($connection !== null && $table !== null) {
403
            $entry->data('dispatcher')->notify(new FreshEvent($connection, $table, $object));
404
        }
405
    }
406
407
    /**
408
     *
409
     * @param  mixed $object
410
     *
411
     * @return array
412
     */
413
    public function getChangedValues($object)
414
    {
415
        $entry = $this->getEntry($object);
416
        if (false === $entry) {
417
            throw new UnregisteredEntityException(sprintf('Unregistered entity (%s)', get_class($object)));
418
        }
419
420
        return $entry->getChangedValues();
421
    }
422
423
    /**
424
     *
425
     */
426
    public function clear()
427
    {
428
        unset($this->store);
429
        $this->store        = new SplObjectStorage();
430
        $this->_priority    = \PHP_INT_MAX;
431
432
        return $this;
433
    }
434
435
    /**
436
     * @return SplObjectStorage
437
     */
438
    public function getStore()
439
    {
440
        return $this->store;
441
    }
442
}