Passed
Push — master ( 6df7c9...74bc2a )
by y
02:15
created

EAV   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 120
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 12
eloc 50
c 1
b 0
f 0
dl 0
loc 120
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A load() 0 8 1
A __construct() 0 2 1
A exists() 0 7 1
A save() 0 22 3
A loadAll() 0 15 4
A find() 0 14 2
1
<?php
2
3
namespace Helix\DB;
4
5
use Helix\DB;
6
use Helix\DB\SQL\Predicate;
7
8
/**
9
 * Array storage in an extension table.
10
 */
11
class EAV extends Table {
12
13
    /**
14
     * @param DB $db
15
     * @param $name
16
     */
17
    public function __construct (DB $db, $name) {
18
        parent::__construct($db, $name, ['entity', 'attribute', 'value']);
19
    }
20
21
    /**
22
     * Whether an entity has an attribute.
23
     *
24
     * @param int $id
25
     * @param string $attribute
26
     * @return bool
27
     */
28
    public function exists (int $id, string $attribute): bool {
29
        $exists = $this->cache(__FUNCTION__, function() {
30
            $select = $this->select(['COUNT(*) > 0']);
31
            $select->where('entity = ? AND attribute = ?');
32
            return $select->prepare();
33
        });
34
        return (bool)$exists([$id, $attribute])->fetchColumn();
35
    }
36
37
    /**
38
     * Pivots and self-joins to return a {@link Select} for the `entity` column,
39
     * matching on attribute and value.
40
     *
41
     * @see DB::match()
42
     *
43
     * @param array $match `[attribute => value]`. If empty, selects all IDs for entities having at least one attribute.
44
     * @return Select
45
     */
46
    public function find (array $match) {
47
        $select = $this->select([$this['entity']]);
48
        $prior = $this;
49
        foreach ($match as $attribute => $value) {
50
            $alias = $this->setName("{$this}__{$attribute}");
51
            $select->join("{$this} AS {$alias}", Predicate::all([
52
                $alias['entity']->isEqual($prior['entity']),
53
                $alias['attribute']->isEqual($attribute),
54
                $this->db->match($alias['value'], $value)
55
            ]));
56
            $prior = $alias;
57
        }
58
        $select->group($this['entity']);
59
        return $select;
60
    }
61
62
    /**
63
     * Returns an entity's attributes.
64
     *
65
     * @param int $id
66
     * @return array `[attribute => value]`
67
     */
68
    public function load (int $id): array {
69
        $load = $this->cache(__FUNCTION__, function() {
70
            $select = $this->select(['attribute', 'value']);
71
            $select->where('entity = ?');
72
            $select->order('attribute');
73
            return $select->prepare();
74
        });
75
        return $load([$id])->fetchAll(DB::FETCH_KEY_PAIR);
76
    }
77
78
    /**
79
     * Returns associative attribute-value arrays for the given IDs.
80
     *
81
     * @param int[] $ids
82
     * @return array[] `[id => attribute => value]
83
     */
84
    public function loadAll (array $ids): array {
85
        if (empty($ids)) {
86
            return [];
87
        }
88
        if (count($ids) === 1) {
89
            return [current($ids) => $this->load(current($ids))];
90
        }
91
        $loadAll = $this->select(['entity', 'attribute', 'value']);
92
        $loadAll->where($this->db->match('entity', SQL::marks($ids)));
93
        $loadAll->order('entity, attribute');
94
        $values = array_fill_keys($ids, []);
95
        foreach ($loadAll->getEach(array_values($ids)) as $row) {
96
            $values[$row['entity']][$row['attribute']] = $row['value'];
97
        }
98
        return $values;
99
    }
100
101
    /**
102
     * Replaces an entity's attributes with those given.
103
     * Stored attributes not given here are pruned.
104
     *
105
     * @param int $id
106
     * @param array $values `[attribute => value]`
107
     * @return $this
108
     */
109
    public function save (int $id, array $values) {
110
        $this->delete([
111
            $this['entity']->isEqual($id),
112
            $this['attribute']->isNotEqual(array_keys($values))
113
        ]);
114
        $upsert = $this->cache(__FUNCTION__, function() {
115
            switch ($this->db) {
116
                case 'sqlite':
117
                    return $this->db->prepare(
118
                        "REPLACE INTO {$this} (entity,attribute,value) VALUES (?,?,?)"
119
                    );
120
                default:
121
                    return $this->db->prepare(
122
                        "INSERT INTO {$this} (entity,attribute,value) VALUES (?,?,?)" .
123
                        " ON DUPLICATE KEY UPDATE value=VALUES(value)"
124
                    );
125
            }
126
        });
127
        foreach ($values as $attribute => $value) {
128
            $upsert->execute([$id, $attribute, $value]);
129
        }
130
        return $this;
131
    }
132
}