Passed
Push — master ( 6822a9...ff844e )
by y
01:33
created

Junction::offsetGet()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Helix\DB;
4
5
use Helix\DB;
6
use LogicException;
7
use ReflectionClass;
8
use ReflectionException;
9
10
/**
11
 * Represents a junction table, derived from an annotated interface.
12
 *
13
 * Interface Annotations:
14
 *
15
 * - `@junction TABLE`
16
 * - `@foreign CLASS COLUMN` or `@for CLASS COLUMN`
17
 */
18
class Junction extends Table {
19
20
    /**
21
     * `[column => Record]`
22
     *
23
     * @var Record[]
24
     */
25
    protected $records = [];
26
27
    /**
28
     * @param DB $db
29
     * @param string $interface
30
     * @return Junction
31
     */
32
    public static function fromInterface (DB $db, string $interface) {
33
        try {
34
            $ref = new ReflectionClass($interface);
35
        }
36
        catch (ReflectionException $exception) {
37
            throw new LogicException('Unexpected ReflectionException', 0, $exception);
38
        }
39
        $doc = $ref->getDocComment();
40
        $records = [];
41
        foreach (explode("\n", $doc) as $line) {
42
            if (preg_match('/@for(eign)?\s+(?<class>\S+)\s+(?<column>\S+)/', $line, $foreign)) {
43
                $records[$foreign['column']] = $db->getRecord($foreign['class']);
44
            }
45
        }
46
        preg_match('/@junction\s+(?<table>\S+)/', $doc, $junction);
47
        return new static($db, $junction['table'], $records);
48
    }
49
50
    /**
51
     * @param DB $db
52
     * @param string $table
53
     * @param Record[] $records
54
     */
55
    public function __construct (DB $db, string $table, array $records) {
56
        parent::__construct($db, $table, array_keys($records));
57
        $this->records = $records;
58
    }
59
60
    /**
61
     * Returns a {@link Select} for entities referenced by a foreign key.
62
     *
63
     * The `Select` is literal and can be iterated directly.
64
     *
65
     * @param string $key The column for the record to select.
66
     * @param array $match
67
     * @return Select
68
     */
69
    public function getCollection (string $key, array $match = []) {
70
        $record = $this->records[$key];
71
        $select = $record->select();
72
        $select->join($this, $this[$key]->isEqual($record['id']));
73
        foreach ($this->db->matchArray($match) as $condition) {
74
            $select->where($condition);
75
        }
76
        return $select;
77
    }
78
79
    /**
80
     * `INSERT IGNORE` to link entities.
81
     *
82
     * @param int[] $ids Keyed by column.
83
     * @return int Rows affected.
84
     */
85
    public function link (array $ids): int {
86
        $link = $this->cache(__FUNCTION__, function() {
87
            $columns = implode(',', array_keys($this->columns));
88
            $slots = implode(',', SQL::slots(array_keys($this->columns)));
89
            if ($this->db->getDriver() === 'sqlite') {
90
                return $this->db->prepare("INSERT OR IGNORE INTO {$this} ({$columns}) VALUES ({$slots})");
91
            }
92
            return $this->db->prepare("INSERT IGNORE INTO {$this} ({$columns}) VALUES ({$slots})");
93
        });
94
        return $link($ids)->rowCount();
95
    }
96
}