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 /**
15 * Model collection.
16 *
17 * @property-read Connections $connections
18 * @property-read array[string]array $definitions
19 * @property-read array[string]Model $instances
20 */
21 class Models implements \ArrayAccess
22 {
23 /**
24 * Instanciated models.
25 *
26 * @var array[string]Model
27 */
28 protected $instances = [];
29
30 /**
31 * Models definitions.
32 *
33 * @var array[string]array
34 */
35 protected $definitions = [];
36
37 /**
38 * Connections manager.
39 *
40 * @var Connections
41 */
42 protected $connections;
43
44 /**
45 * Initializes the {@link $connections} and {@link $definitions} properties.
46 *
47 * @param Connections $connections Connections manager.
48 * @param array[string]array $definitions Model definitions.
49 */
50 public function __construct(Connections $connections, array $definitions=[])
51 {
52 $this->connections = $connections;
53
54 foreach ($definitions as $id => $definition)
55 {
56 $this[$id] = $definition;
57 }
58 }
59
60 public function __get($property)
61 {
62 switch ($property)
63 {
64 case 'connections': return $this->connections;
65 case 'definitions': return new \ArrayIterator($this->definitions);
66 case 'instances': return new \ArrayIterator($this->instances);
67 }
68 }
69
70 /**
71 * Checks if a model is defined.
72 *
73 * @return bool
74 */
75 public function offsetExists($id)
76 {
77 return isset($this->definitions[$id]);
78 }
79
80 /**
81 * Sets the definition of a model.
82 *
83 * The {@link Model::ID} and {@link Model::NAME} are set to the provided id if they are not
84 * defined.
85 *
86 * @param string $id Identifier of the model.
87 * @param array $definition Definition of the model.
88 *
89 * @throws ModelAlreadyInstantiated in attempt to write a model already instantiated.
90 */
91 public function offsetSet($id, $definition)
92 {
93 if (isset($this->instances[$id]))
94 {
95 throw new ModelAlreadyInstantiated($id);
96 }
97
98 $this->definitions[$id] = $definition + [
99
100 Model::ID => $id,
101 Model::NAME => $id
102 ];
103 }
104
105 /**
106 * Returns a {@link Model} instance.
107 *
108 * @param string $id Identifier of the model.
109 *
110 * @return Model
111 *
112 * @throws ModelNotDefined when the model is not defined.
113 */
114 public function offsetGet($id)
115 {
116 if (isset($this->instances[$id]))
117 {
118 return $this->instances[$id];
119 }
120
121 if (!isset($this->definitions[$id]))
122 {
123 throw new ModelNotDefined($id);
124 }
125
126 $properties = $this->definitions[$id] + [
127
128 Model::CONNECTION => 'primary'
129 ];
130
131 if (is_string($properties[Model::CONNECTION]))
132 {
133 $properties[Model::CONNECTION] = $this->connections[$properties[Model::CONNECTION]];
134 }
135
136 return new Model($properties);
137 }
138
139 /**
140 * Unset the definition of a model.
141 *
142 * @throws ModelAlreadyInstantiated in attempt to unset the definition of an already
143 * instantiated model.
144 */
145 public function offsetUnset($id)
146 {
147 if (isset($this->instances[$id]))
148 {
149 throw new ModelAlreadyInstantiated($id);
150 }
151
152 unset($this->definitions[$id]);
153 }
154 }
155
156 /*
157 * EXCEPTIONS
158 */
159
160 /**
161 * Exception thrown when a requested model is not defined.
162 */
163 class ModelNotDefined extends ActiveRecordException
164 {
165 public function __construct($id, $code=500, \Exception $previous=null)
166 {
167 parent::__construct("Model not defined: $id.", $code, $previous);
168 }
169 }
170
171 /**
172 * Exception thrown in attempt to set/unset the definition of an already instantiated model.
173 */
174 class ModelAlreadyInstantiated extends ActiveRecordException
175 {
176 public function __construct($id, $code=500, \Exception $previous=null)
177 {
178 parent::__construct("Model already instanciated: $id.", $code, $previous);
179 }
180 }