• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ICanBoogie / ActiveRecord / 4277335265

pending completion
4277335265

push

github

Olivier Laviale
ModelProvider is now iterable

2 of 2 new or added lines in 1 file covered. (100.0%)

1184 of 1455 relevant lines covered (81.37%)

23.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.15
/lib/ActiveRecord/ModelCollection.php
1
<?php
2

3
/*
4
 * This file is part of the ICanBoogie package.
5
 *
6
 * (c) Olivier Laviale <olivier.laviale@gmail.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
namespace ICanBoogie\ActiveRecord;
13

14
use ArrayAccess;
15
use ICanBoogie\Accessor\AccessorTrait;
16
use InvalidArgumentException;
17
use Throwable;
18
use Traversable;
19

20
use function array_keys;
21
use function get_debug_type;
22
use function is_array;
23
use function sprintf;
24

25
/**
26
 * Model collection.
27
 *
28
 * @property-read array<string, array> $definitions
29
 * @property-read array<string, Model> $instances
30
 *
31
 * @implements ArrayAccess<string, Model>
32
 */
33
class ModelCollection implements ArrayAccess, ModelProvider
34
{
35
    /**
36
     * @uses get_instances
37
     * @uses get_definitions
38
     * @uses get_connections
39
     */
40
    use AccessorTrait;
41

42
    /**
43
     * Instantiated models.
44
     *
45
     * @var array<string, Model>
46
     */
47
    private array $instances = [];
48

49
    /**
50
     * @return array<string, Model>
51
     */
52
    private function get_instances(): array
53
    {
54
        return $this->instances;
1✔
55
    }
56

57
    /**
58
     * Models definitions.
59
     *
60
     * @var array<string, array>
61
     */
62
    private array $definitions = [];
63

64
    /**
65
     * @return array<string, array>
66
     */
67
    private function get_definitions(): array
68
    {
69
        return $this->definitions;
2✔
70
    }
71

72
    /**
73
     * @param array<string, array> $definitions
74
     */
75
    public function __construct(
76
        public readonly ConnectionProvider $connections,
77
        array $definitions = []
78
    ) {
79
        foreach ($definitions as $id => $definition) {
71✔
80
            $this[$id] = $definition;
71✔
81
        }
82
    }
83

84
    public function getIterator(): Traversable
85
    {
86
        foreach (array_keys($this->definitions) as $id) {
1✔
87
            yield $id => fn() => $this->model_for_id($id);
1✔
88
        }
89
    }
90

91
    public function model_for_id(string $id): Model
92
    {
93
        return $this[$id];
2✔
94
    }
95

96
    /**
97
     * Checks if a model is defined.
98
     *
99
     * @param string $offset A Model identifier.
100
     */
101
    public function offsetExists($offset): bool
102
    {
103
        return isset($this->definitions[$offset]);
3✔
104
    }
105

106
    /**
107
     * Sets the definition of a model.
108
     *
109
     * The {@link Model::ID} and {@link Model::NAME} are set to the provided id if they are not
110
     * defined.
111
     *
112
     * @param string $offset A Model identifier.
113
     * @param array<string, mixed>|mixed $value A Model definition.
114
     *
115
     * @throws ModelAlreadyInstantiated in attempt to write a model already instantiated.
116
     */
117
    public function offsetSet($offset, $value): void
118
    {
119
        if (!is_array($value)) {
71✔
120
            throw new InvalidArgumentException(sprintf("Expected array, got %s.", get_debug_type($value)));
×
121
        }
122

123
        if (isset($this->instances[$offset])) {
71✔
124
            throw new ModelAlreadyInstantiated($offset);
1✔
125
        }
126

127
        $this->definitions[$offset] = $value + [
71✔
128

129
                Model::ID => $offset,
71✔
130
                Model::NAME => $offset
71✔
131

132
            ];
71✔
133
    }
134

135
    /**
136
     * Returns a {@link Model} instance.
137
     *
138
     * @param string $offset A Model identifier.
139
     *
140
     * @throws ModelNotDefined when the model is not defined.
141
     */
142
    public function offsetGet($offset): Model
143
    {
144
        if (isset($this->instances[$offset])) {
71✔
145
            return $this->instances[$offset];
65✔
146
        }
147

148
        if (!isset($this->definitions[$offset])) {
68✔
149
            throw new ModelNotDefined($offset);
1✔
150
        }
151

152
        return $this->instances[$offset] = $this
67✔
153
            ->instantiate_model($this
67✔
154
                ->resolve_model_attributes($this->definitions[$offset]));
67✔
155
    }
156

157
    /**
158
     * Unset the definition of a model.
159
     *
160
     * @param string $offset Model identifier.
161
     *
162
     * @throws ModelAlreadyInstantiated in attempt to unset the definition of an already
163
     * instantiated model.
164
     */
165
    public function offsetUnset($offset): void
166
    {
167
        if (isset($this->instances[$offset])) {
1✔
168
            throw new ModelAlreadyInstantiated($offset);
1✔
169
        }
170

171
        unset($this->definitions[$offset]);
1✔
172
    }
173

174
    /**
175
     * Install all the models.
176
     *
177
     * @throws Throwable
178
     */
179
    public function install(): void
180
    {
181
        foreach (array_keys($this->definitions) as $id) {
60✔
182
            $model = $this[$id];
60✔
183

184
            if ($model->is_installed()) {
60✔
185
                continue;
55✔
186
            }
187

188
            $model->install();
60✔
189
        }
190
    }
191

192
    /**
193
     * Uninstall all the models.
194
     *
195
     * @throws Throwable
196
     */
197
    public function uninstall(): void
198
    {
199
        foreach (array_keys($this->definitions) as $id) {
1✔
200
            $model = $this[$id];
1✔
201

202
            if (!$model->is_installed()) {
1✔
203
                continue;
1✔
204
            }
205

206
            $model->uninstall();
1✔
207
        }
208
    }
209

210
    /**
211
     * Check if models are installed.
212
     *
213
     * @return array<string, bool> An array of key/value pair where _key_ is a model identifier and
214
     * _value_ `true` if the model is installed, `false` otherwise.
215
     */
216
    public function is_installed(): array
217
    {
218
        $rc = [];
2✔
219

220
        foreach (array_keys($this->definitions) as $id) {
2✔
221
            $rc[$id] = $this[$id]->is_installed();
2✔
222
        }
223

224
        return $rc;
2✔
225
    }
226

227
    /**
228
     * Resolves model attributes.
229
     *
230
     * The methods replaces {@link Model::CONNECTION} and {@link Model::EXTENDING} identifier
231
     * with instances.
232
     *
233
     * @param array<string, mixed> $attributes
234
     *
235
     * @return array<string, mixed>
236
     */
237
    private function resolve_model_attributes(array $attributes): array
238
    {
239
        $attributes += [
67✔
240

241
            Model::CLASSNAME => Model::class,
67✔
242
            Model::CONNECTION => 'primary',
67✔
243
            Model::EXTENDING => null
67✔
244

245
        ];
67✔
246

247
        $connection = &$attributes[Model::CONNECTION];
67✔
248

249
        if ($connection && !$connection instanceof Connection) {
67✔
250
            $connection = $this->connections->connection_for_id($connection);
67✔
251
        }
252

253
        $extending = &$attributes[Model::EXTENDING];
67✔
254

255
        if ($extending && !$extending instanceof Model) {
67✔
256
            $extending = $this[$extending];
54✔
257
        }
258

259
        return $attributes;
67✔
260
    }
261

262
    /**
263
     * Instantiate a model with the specified attributes.
264
     *
265
     * @param array<string, mixed> $attributes
266
     */
267
    private function instantiate_model(array $attributes): Model
268
    {
269
        $class = $attributes[Model::CLASSNAME];
67✔
270

271
        return new $class($this, $attributes);
67✔
272
    }
273
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc