Completed
Push — master ( 558cc4...2f02d3 )
by Olivier
03:32
created

lib/ActiveRecord.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * This file is part of the ICanBoogie package.
5
 *
6
 * (c) Olivier Laviale <[email protected]>
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;
13
14
use ICanBoogie\ActiveRecord\Model;
15
16
/**
17
 * Active Record facilitates the creation and use of business objects whose data require persistent
18
 * storage via database.
19
 *
20
 * @property-read Model $model The model managing the active record.
21
 * @property-read string $model_id The identifier of the model managing the active record.
22
 */
23
class ActiveRecord extends Prototyped
24
{
25
	/**
26
	 * The identifier of the model managing the record.
27
	 *
28
	 *  @var string
29
	 */
30
	const MODEL_ID = null;
31
32
	/**
33
	 * Model managing the active record.
34
	 *
35
	 * @var Model
36
	 */
37
	private $model;
38
39
	/**
40
	 * Identifier of the model managing the active record.
41
	 *
42
	 * Note: Due to a PHP bug (or feature), the visibility of the property MUST NOT be private.
43
	 * https://bugs.php.net/bug.php?id=40412
44
	 *
45
	 * @var string
46
	 */
47
	protected $model_id;
48
49
	/**
50
	 * Initializes the {@link $model} and {@link $model_id} properties.
51
	 *
52
	 * @param string|Model|null $model The model managing the active record. A {@link Model}
53
	 * instance can be specified as well as a model identifier. If a model identifier is
54
	 * specified, the model is resolved when the {@link $model} property is accessed. If `$model`
55
	 * is empty, the identifier of the model is read from the {@link MODEL_ID} class constant.
56
	 *
57
	 * @throws \InvalidArgumentException if $model is neither a model identifier nor a
58
	 * {@link Model} instance.
59
	 */
60
	public function __construct($model = null)
61
	{
62
		if (!$model)
63
		{
64
			$model = static::MODEL_ID;
65
		}
66
67
		if (is_string($model))
68
		{
69
			$this->model_id = $model;
70
		}
71
		else if ($model instanceof Model)
72
		{
73
			$this->model = $model;
74
			$this->model_id = $model->id;
75
		}
76
		else
77
		{
78
			throw new \InvalidArgumentException("\$model must be an instance of ICanBoogie\\ActiveRecord\\Model or a model identifier. Given:" . (is_object($model) ? get_class($model) : gettype($model)));
79
		}
80
	}
81
82
	/**
83
	 * Removes the {@link $model} property.
84
	 *
85
	 * Properties whose value are instances of the {@link ActiveRecord} class are removed from the
86
	 * exported properties.
87
	 */
88
	public function __sleep()
89
	{
90
		$properties = parent::__sleep();
91
92
		unset($properties['model']);
93
94
		foreach (array_keys($properties) as $property)
95
		{
96
			if ($this->$property instanceof self)
97
			{
98
				unset($properties[$property]);
99
			}
100
		}
101
102
		return $properties;
103
	}
104
105
	/**
106
	 * Removes `model` from the output, since `model_id` is good enough to figure which model
107
	 * is used.
108
	 *
109
	 * @return array
110
	 */
111
	public function __debugInfo()
112
	{
113
		$array = (array) $this;
114
115
		unset($array["\0" . __CLASS__ . "\0model"]);
116
117
		return $array;
118
	}
119
120
	/**
121
	 * Returns the model managing the active record.
122
	 *
123
	 * @return Model
124
	 */
125
	protected function get_model()
126
	{
127
		return $this->model
128
			?: $this->model = ActiveRecord\get_model($this->model_id);
129
	}
130
131
	/**
132
	 * Returns the identifier of the model managing the active record.
133
	 *
134
	 * @return string
135
	 */
136
	protected function get_model_id()
137
	{
138
		return $this->model_id;
139
	}
140
141
	/**
142
	 * Saves the active record using its model.
143
	 *
144
	 * @return int|bool Primary key value of the active record, or a boolean if the primary key
145
	 * is not a serial.
146
	 */
147
	public function save()
148
	{
149
		$model = $this->get_model();
150
		$schema = $model->extended_schema;
151
		$primary = $model->primary;
152
		$properties = $this->alter_persistent_properties($this->to_array(), $model);
153
154
		if (!$this->model->parent)
155
		{
156
			# removes the primary key from the properties.
157
158
			if (is_array($primary))
159
			{
160
				return $model->insert($properties, [ 'on duplicate' => true ]);
161
			}
162
163
			#
164
165
			$primary_column = $primary ? $schema[ $primary ] : null;
166
167
			if (isset($properties[ $primary ]) && !$primary_column->auto_increment)
168
			{
169
				return $model->insert($properties, [ 'on duplicate' => true ]);
170
			}
171
		}
172
173
		#
174
175
		$key = null;
176
177
		if (isset($properties[$primary]))
178
		{
179
			$key = $properties[$primary];
180
			unset($properties[$primary]);
181
		}
182
183
		$rc = $model->save($properties, $key);
184
185
		if ($key === null && $rc)
186
		{
187
			$this->update_primary_key($rc);
188
		}
189
190
		return $rc;
191
	}
192
193
	/**
194
	 * Unless it's an acceptable value for a column, columns with `null` values are discarded.
195
	 * This way, we don't have to define every properties before saving our active record.
196
	 *
197
	 * @param array $properties
198
	 * @param Model $model
199
	 *
200
	 * @return array The altered persistent properties
201
	 */
202
	protected function alter_persistent_properties(array $properties, Model $model)
203
	{
204
		$schema = $model->extended_schema;
205
206
		foreach ($properties as $identifier => $value)
207
		{
208
			if ($value !== null || (isset($schema[$identifier]) && $schema[$identifier]->null))
209
			{
210
				continue;
211
			}
212
213
			unset($properties[$identifier]);
214
		}
215
216
		return $properties;
217
	}
218
219
	/**
220
	 * Updates primary key.
221
	 *
222
	 * @param array|string|int $primary_key
223
	 */
224
	protected function update_primary_key($primary_key)
225
	{
226
		$property = $this->model->primary;
0 ignored issues
show
The property $primary is declared protected in ICanBoogie\ActiveRecord\Table. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
227
228
		if (!$property)
229
		{
230
			throw new \LogicException("Unable to update primary key, model `$this->model_id` doesn't define one.");
231
		}
232
233
		$this->$property = $primary_key;
234
	}
235
236
	/**
237
	 * Deletes the active record using its model.
238
	 *
239
	 * @return bool `true` if the record was deleted, `false` otherwise.
240
	 *
241
	 * @throws \Exception in attempt to delete a record from a model which primary key is empty.
242
	 */
243
	public function delete()
244
	{
245
		$model = $this->model;
246
		$primary = $model->primary;
247
248
		if (!$primary)
249
		{
250
			throw new \LogicException("Unable to delete record, primary key is empty.");
251
		}
252
253
		return $model->delete($this->$primary);
254
	}
255
}
256