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

ICanBoogie / ActiveRecord / 4437457574

pending completion
4437457574

push

github

Olivier Laviale
Case of a nullable belong_to

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

1356 of 1686 relevant lines covered (80.43%)

34.68 hits per line

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

93.88
/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 ICanBoogie\ActiveRecord;
17
use LogicException;
18
use RuntimeException;
19
use Throwable;
20

21
use function array_keys;
22
use function get_debug_type;
23

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

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

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

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

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

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

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

91
    /**
92
     * @return Model<int|string, ActiveRecord>
93
     */
94
    public function model_for_id(string $id): Model
95
    {
96
        return $this->offsetGet($id);
95✔
97
    }
98

99
    public function model_for_activerecord(string|ActiveRecord $class_or_activerecord): Model
100
    {
101
        $class = $class_or_activerecord instanceof ActiveRecord
1✔
102
            ? $class_or_activerecord::class
×
103
            : $class_or_activerecord;
1✔
104

105
        foreach ($this->definitions as $id => $definition) {
1✔
106
            if ($class === $definition->activerecord_class) {
1✔
107
                return $this->model_for_id($id);
1✔
108
            }
109
        }
110

111
        throw new RuntimeException("Unable to find model for $class");
×
112
    }
113

114
    /**
115
     * Checks if a model is defined.
116
     *
117
     * @param string $offset A Model identifier.
118
     */
119
    public function offsetExists($offset): bool
120
    {
121
        return isset($this->definitions[$offset]);
3✔
122
    }
123

124
    /**
125
     * Sets the definition of a model.
126
     *
127
     * @param string $offset A Model identifier.
128
     * @param array<string, mixed>|mixed $value A Model definition.
129
     *
130
     * @throws ModelAlreadyInstantiated in attempt to write a model already instantiated.
131
     */
132
    public function offsetSet($offset, $value): void
133
    {
134
        if (!$value instanceof ModelDefinition) {
101✔
135
            throw new LogicException("Expected ModelConfig instance, given: " . get_debug_type($value));
×
136
        }
137

138
        if (isset($this->instances[$offset])) {
101✔
139
            throw new ModelAlreadyInstantiated($offset);
1✔
140
        }
141

142
        $this->definitions[$offset] = $value;
101✔
143
    }
144

145
    /**
146
     * Returns a {@link Model} instance.
147
     *
148
     * @param string $offset A Model identifier.
149
     *
150
     * @throws ModelNotDefined when the model is not defined.
151
     */
152
    public function offsetGet($offset): Model
153
    {
154
        if (isset($this->instances[$offset])) {
98✔
155
            return $this->instances[$offset];
82✔
156
        }
157

158
        if (!isset($this->definitions[$offset])) {
98✔
159
            throw new ModelNotDefined($offset);
1✔
160
        }
161

162
        return $this->instances[$offset] = $this
97✔
163
            ->instantiate_model($this->definitions[$offset]);
97✔
164
    }
165

166
    /**
167
     * Unset the definition of a model.
168
     *
169
     * @param string $offset Model identifier.
170
     *
171
     * @throws ModelAlreadyInstantiated in attempt to unset the definition of an already
172
     * instantiated model.
173
     */
174
    public function offsetUnset($offset): void
175
    {
176
        if (isset($this->instances[$offset])) {
1✔
177
            throw new ModelAlreadyInstantiated($offset);
1✔
178
        }
179

180
        unset($this->definitions[$offset]);
1✔
181
    }
182

183
    /**
184
     * Install all the models.
185
     *
186
     * @throws Throwable
187
     */
188
    public function install(): void
189
    {
190
        foreach (array_keys($this->definitions) as $id) {
73✔
191
            $model = $this[$id];
73✔
192

193
            if ($model->is_installed()) {
73✔
194
                continue;
1✔
195
            }
196

197
            $model->install();
73✔
198
        }
199
    }
200

201
    /**
202
     * Uninstall all the models.
203
     *
204
     * @throws Throwable
205
     */
206
    public function uninstall(): void
207
    {
208
        foreach (array_keys($this->definitions) as $id) {
1✔
209
            $model = $this[$id];
1✔
210

211
            if (!$model->is_installed()) {
1✔
212
                continue;
1✔
213
            }
214

215
            $model->uninstall();
1✔
216
        }
217
    }
218

219
    /**
220
     * Check if models are installed.
221
     *
222
     * @return array<string, bool> An array of key/value pair where _key_ is a model identifier and
223
     * _value_ `true` if the model is installed, `false` otherwise.
224
     */
225
    public function is_installed(): array
226
    {
227
        $rc = [];
2✔
228

229
        foreach (array_keys($this->definitions) as $id) {
2✔
230
            $rc[$id] = $this[$id]->is_installed();
2✔
231
        }
232

233
        return $rc;
2✔
234
    }
235

236
    /**
237
     * Instantiate a model with the specified attributes.
238
     *
239
     * @param ModelDefinition $attributes
240
     */
241
    private function instantiate_model(ModelDefinition $attributes): Model
242
    {
243
        $class = $attributes->model_class;
97✔
244

245
        return new $class(
97✔
246
            $this->connections->connection_for_id($attributes->connection),
97✔
247
            $this,
97✔
248
            $attributes
97✔
249
        );
97✔
250
    }
251
}
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