RelationOptions   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 169
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 5

Importance

Changes 0
Metric Value
wmc 13
lcom 2
cbo 5
dl 0
loc 169
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A define() 0 8 2
A defineMultiple() 0 4 1
A calculateOptions() 0 23 4
B proposedOptions() 0 55 5
1
<?php
2
/**
3
 * Spiral, Core Components
4
 *
5
 * @author Wolfy-J
6
 */
7
8
namespace Spiral\ORM\Helpers;
9
10
use Doctrine\Common\Inflector\Inflector;
11
use Spiral\ORM\Exceptions\OptionsException;
12
use Spiral\ORM\Record;
13
use Spiral\ORM\Schemas\Definitions\RelationDefinition;
14
15
/**
16
 * Provides ability to work with user defined relations and fill missing options based on source
17
 * and target contexts.
18
 */
19
class RelationOptions
20
{
21
    /**
22
     * @var RelationDefinition
23
     */
24
    private $definition;
25
26
    /**
27
     * Most of relations provides ability to specify many different configuration options, such
28
     * as key names, pivot table schemas, foreign key request, ability to be nullabe and etc.
29
     *
30
     * To simple schema definition in real projects we can fill some of this values automatically
31
     * based on some "environment" values such as parent/outer record table, role name, primary key
32
     * and etc.
33
     *
34
     * Example:
35
     * Record::INNER_KEY => '{outer:role}_{outer:primaryKey}'
36
     *
37
     * Result:
38
     * Outer Record is User with primary key "id" => "user_id"
39
     *
40
     * @var array
41
     */
42
    private $template;
43
44
    /**
45
     * Options calculated based on values provided by RelationDefinition and calculated via
46
     * template.
47
     *
48
     * @var array
49
     */
50
    private $options;
51
52
    /**
53
     * @param RelationDefinition $definition
54
     * @param array              $template Relation options template.
55
     */
56
    public function __construct(RelationDefinition $definition, array $template = [])
57
    {
58
        $this->definition = $definition;
59
        $this->template = $template;
60
        $this->options = $this->calculateOptions($definition->getOptions());
61
    }
62
63
    /**
64
     * Get value for a specific option. Attention, option MUST exist in template in order to
65
     * be retrievable.
66
     *
67
     * @param string $option
68
     *
69
     * @return mixed
70
     *
71
     * @throws OptionsException
72
     */
73
    public function define(string $option)
74
    {
75
        if (!array_key_exists($option, $this->options)) {
76
            throw new OptionsException("Undefined relation option '{$option}'");
77
        }
78
79
        return $this->options[$option];
80
    }
81
82
    /**
83
     * All relation options.
84
     *
85
     * @param array $options Options to be defined.
86
     *
87
     * @return array
88
     */
89
    public function defineMultiple(array $options): array
90
    {
91
        return array_intersect_key($this->options, array_flip($options));
92
    }
93
94
    /**
95
     * Calculate options based on given template
96
     *
97
     * @param array $userOptions Options provided by user.
98
     *
99
     * @return array Calculated options.
100
     */
101
    protected function calculateOptions(array $userOptions): array
102
    {
103
        foreach ($this->template as $property => $pattern) {
104
            if (isset($userOptions[$property])) {
105
                //Specified by user
106
                continue;
107
            }
108
109
            if (!is_string($pattern)) {
110
                //Some options are actually array of options
111
                $userOptions[$property] = $pattern;
112
                continue;
113
            }
114
115
            //Let's create option value using default proposer values
116
            $userOptions[$property] = \Spiral\interpolate(
117
                $pattern,
118
                $this->proposedOptions($userOptions)
119
            );
120
        }
121
122
        return $userOptions;
123
    }
124
125
    /**
126
     * Create set of options to specify missing relation definition fields.
127
     *
128
     * @param array $userOptions User options.
129
     *
130
     * @return array
131
     */
132
    protected function proposedOptions(array $userOptions): array
133
    {
134
        $source = $this->definition->sourceContext();
135
136
        $proposed = [
137
            //Relation name
138
            'relation:name'     => $this->definition->getName(),
139
140
            //Relation name in plural form
141
            'relation:plural'   => Inflector::pluralize($this->definition->getName()),
142
143
            //Relation name in singular form
144
            'relation:singular' => Inflector::singularize($this->definition->getName()),
145
146
            //Parent record role name
147
            'source:role'       => $source->getRole(),
148
149
            //Parent record table name
150
            'source:table'      => $source->getTable(),
151
152
            //Parent record primary key
153
            'source:primaryKey' => $source->getPrimary()->getName(),
154
        ];
155
156
        //Some options may use values declared in other definition fields
157
        $aliases = [
158
            Record::OUTER_KEY   => 'outerKey',
159
            Record::INNER_KEY   => 'innerKey',
160
            Record::PIVOT_TABLE => 'pivotTable',
161
        ];
162
163
        foreach ($aliases as $property => $alias) {
164
            if (isset($userOptions[$property])) {
165
                //Let's create some default options based on user specified values
166
                $proposed['option:' . $alias] = $userOptions[$property];
167
            }
168
        }
169
170
        if (!empty($this->definition->targetContext())) {
171
            $target = $this->definition->targetContext();
172
173
            $proposed = $proposed + [
174
                    //Outer role name
175
                    'target:role'       => $target->getRole(),
176
177
                    //Outer record table
178
                    'target:table'      => $target->getTable(),
179
180
                    //Outer record primary key
181
                    'target:primaryKey' => !empty($target->getPrimary()) ? $target->getPrimary()->getName() : '',
182
                ];
183
        }
184
185
        return $proposed;
186
    }
187
}