|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Ubiquity\orm; |
|
4
|
|
|
|
|
5
|
|
|
use Ubiquity\db\Database; |
|
6
|
|
|
use Ubiquity\log\Logger; |
|
7
|
|
|
use Ubiquity\orm\parser\ManyToManyParser; |
|
8
|
|
|
use Ubiquity\db\SqlUtils; |
|
9
|
|
|
use Ubiquity\orm\parser\Reflexion; |
|
10
|
|
|
use Ubiquity\orm\traits\DAOUpdatesTrait; |
|
11
|
|
|
use Ubiquity\orm\traits\DAORelationsTrait; |
|
12
|
|
|
use Ubiquity\orm\parser\ConditionParser; |
|
13
|
|
|
use Ubiquity\orm\traits\DAOUQueries; |
|
14
|
|
|
|
|
15
|
|
|
/** |
|
16
|
|
|
* Gateway class between database and object model |
|
17
|
|
|
* @author jc |
|
18
|
|
|
* @version 1.1.0.0 |
|
19
|
|
|
* @package orm |
|
20
|
|
|
*/ |
|
21
|
|
|
class DAO { |
|
22
|
|
|
use DAOUpdatesTrait,DAORelationsTrait,DAOUQueries; |
|
23
|
|
|
|
|
24
|
|
|
|
|
25
|
|
|
/** |
|
26
|
|
|
* @var Database |
|
27
|
|
|
*/ |
|
28
|
|
|
public static $db; |
|
29
|
|
|
|
|
30
|
|
|
/** |
|
31
|
|
|
* Loads member associated with $instance by a ManyToOne relationship |
|
32
|
|
|
* @param object $instance |
|
33
|
|
|
* @param string $member |
|
34
|
|
|
* @param boolean|array $included if true, loads associate members with associations, if array, example : ["client.*","commands"] |
|
35
|
|
|
* @param boolean $useCache |
|
36
|
|
|
*/ |
|
37
|
|
|
public static function getManyToOne($instance, $member, $included=false,$useCache=NULL) { |
|
38
|
|
|
$fieldAnnot=OrmUtils::getMemberJoinColumns($instance, $member); |
|
39
|
|
|
if($fieldAnnot!==null){ |
|
40
|
|
|
$annotationArray=$fieldAnnot[1]; |
|
41
|
|
|
$member=$annotationArray["member"]; |
|
42
|
|
|
$value=Reflexion::getMemberValue($instance, $member); |
|
43
|
|
|
$key=OrmUtils::getFirstKey($annotationArray["className"]); |
|
44
|
|
|
$kv=array ($key => $value ); |
|
45
|
|
|
$obj=self::getOne($annotationArray["className"], $kv, $included, $useCache); |
|
|
|
|
|
|
46
|
|
|
if ($obj !== null) { |
|
47
|
|
|
Logger::info("DAO", "Loading the member " . $member . " for the object " . \get_class($instance),"getManyToOne"); |
|
48
|
|
|
$accesseur="set" . ucfirst($member); |
|
49
|
|
|
if (method_exists($instance, $accesseur)) { |
|
50
|
|
|
$instance->$accesseur($obj); |
|
51
|
|
|
$instance->_rest[$member]=$obj->_rest; |
|
52
|
|
|
return $obj; |
|
53
|
|
|
} |
|
54
|
|
|
} |
|
55
|
|
|
} |
|
56
|
|
|
} |
|
57
|
|
|
|
|
58
|
|
|
private static function _getOneToManyFromArray(&$ret, $array, $fkv, $mappedBy) { |
|
59
|
|
|
$elementAccessor="get" . ucfirst($mappedBy); |
|
60
|
|
|
foreach ( $array as $element ) { |
|
61
|
|
|
$elementRef=$element->$elementAccessor(); |
|
62
|
|
|
if (!is_null($elementRef)) { |
|
63
|
|
|
if(is_object($elementRef)) |
|
64
|
|
|
$idElementRef=OrmUtils::getFirstKeyValue($elementRef); |
|
65
|
|
|
else |
|
66
|
|
|
$idElementRef=$elementRef; |
|
67
|
|
|
if ($idElementRef == $fkv) |
|
68
|
|
|
$ret[]=$element; |
|
69
|
|
|
} |
|
70
|
|
|
} |
|
71
|
|
|
} |
|
72
|
|
|
|
|
73
|
|
|
/** |
|
74
|
|
|
* Assign / load the child records in the $member member of $instance. |
|
75
|
|
|
* @param object $instance |
|
76
|
|
|
* @param string $member Member on which a oneToMany annotation must be present |
|
77
|
|
|
* @param boolean|array $included if true, loads associate members with associations, if array, example : ["client.*","commands"] |
|
78
|
|
|
* @param boolean $useCache |
|
79
|
|
|
* @param array $annot used internally |
|
80
|
|
|
*/ |
|
81
|
|
|
public static function getOneToMany($instance, $member, $included=true,$useCache=NULL, $annot=null) { |
|
82
|
|
|
$ret=array (); |
|
83
|
|
|
$class=get_class($instance); |
|
84
|
|
|
if (!isset($annot)) |
|
85
|
|
|
$annot=OrmUtils::getAnnotationInfoMember($class, "#oneToMany", $member); |
|
86
|
|
|
if ($annot !== false) { |
|
87
|
|
|
$fkAnnot=OrmUtils::getAnnotationInfoMember($annot["className"], "#joinColumn", $annot["mappedBy"]); |
|
88
|
|
|
if ($fkAnnot !== false) { |
|
89
|
|
|
$fkv=OrmUtils::getFirstKeyValue($instance); |
|
90
|
|
|
$ret=self::_getAll($annot["className"], ConditionParser::simple($fkAnnot["name"] . "= ?",$fkv), $included, $useCache); |
|
91
|
|
|
self::setToMember($member, $instance, $ret, $class, "getOneToMany"); |
|
92
|
|
|
} |
|
93
|
|
|
} |
|
94
|
|
|
return $ret; |
|
95
|
|
|
} |
|
96
|
|
|
|
|
97
|
|
|
/** |
|
98
|
|
|
* @param object $instance |
|
99
|
|
|
* @param string $member |
|
100
|
|
|
* @param array $array |
|
101
|
|
|
* @param string $mappedBy |
|
102
|
|
|
*/ |
|
103
|
|
|
public static function affectsOneToManyFromArray($instance, $member, $array=null, $mappedBy=null) { |
|
104
|
|
|
$ret=array (); |
|
105
|
|
|
$class=get_class($instance); |
|
106
|
|
|
if (!isset($mappedBy)){ |
|
107
|
|
|
$annot=OrmUtils::getAnnotationInfoMember($class, "#oneToMany", $member); |
|
108
|
|
|
$mappedBy=$annot["mappedBy"]; |
|
109
|
|
|
} |
|
110
|
|
|
if ($mappedBy !== false) { |
|
111
|
|
|
$fkv=OrmUtils::getFirstKeyValue($instance); |
|
112
|
|
|
self::_getOneToManyFromArray($ret, $array, $fkv, $mappedBy); |
|
113
|
|
|
self::setToMember($member, $instance, $ret, $class, "getOneToMany"); |
|
114
|
|
|
} |
|
115
|
|
|
return $ret; |
|
116
|
|
|
} |
|
117
|
|
|
|
|
118
|
|
|
private static function setToMember($member, $instance, $value, $class, $part) { |
|
119
|
|
|
$accessor="set" . ucfirst($member); |
|
120
|
|
|
if (method_exists($instance, $accessor)) { |
|
121
|
|
|
Logger::info("DAO", "Affectation de " . $member . " pour l'objet " . $class,$part); |
|
122
|
|
|
$instance->$accessor($value); |
|
123
|
|
|
$instance->_rest[$member]=$value; |
|
124
|
|
|
} else { |
|
125
|
|
|
Logger::warn("DAO", "L'accesseur " . $accessor . " est manquant pour " . $class,$part); |
|
126
|
|
|
} |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
|
|
/** |
|
130
|
|
|
* Assigns / loads the child records in the $member member of $instance. |
|
131
|
|
|
* If $ array is null, the records are loaded from the database |
|
132
|
|
|
* @param object $instance |
|
133
|
|
|
* @param string $member Member on which a ManyToMany annotation must be present |
|
134
|
|
|
* @param boolean|array $included if true, loads associate members with associations, if array, example : ["client.*","commands"] |
|
135
|
|
|
* @param array $array optional parameter containing the list of possible child records |
|
136
|
|
|
* @param boolean $useCache |
|
137
|
|
|
*/ |
|
138
|
|
|
public static function getManyToMany($instance, $member,$included=false,$array=null,$useCache=NULL){ |
|
139
|
|
|
$ret=[]; |
|
140
|
|
|
$class=get_class($instance); |
|
141
|
|
|
$parser=new ManyToManyParser($instance, $member); |
|
142
|
|
|
if ($parser->init()) { |
|
143
|
|
|
if (is_null($array)) { |
|
144
|
|
|
$accessor="get" . ucfirst($parser->getMyPk()); |
|
145
|
|
|
$condition=" INNER JOIN `" . $parser->getJoinTable() . "` on `".$parser->getJoinTable()."`.`".$parser->getFkField()."`=`".$parser->getTargetEntityTable()."`.`".$parser->getPk()."` WHERE `".$parser->getJoinTable()."`.`". $parser->getMyFkField() . "`= ?"; |
|
146
|
|
|
$ret=self::_getAll($parser->getTargetEntityClass(),ConditionParser::simple($condition, $instance->$accessor()),$included,$useCache); |
|
147
|
|
|
}else{ |
|
148
|
|
|
$ret=self::getManyToManyFromArray($instance, $array, $class, $parser); |
|
149
|
|
|
} |
|
150
|
|
|
self::setToMember($member, $instance, $ret, $class, "getManyToMany"); |
|
151
|
|
|
} |
|
152
|
|
|
return $ret; |
|
153
|
|
|
} |
|
154
|
|
|
|
|
155
|
|
|
/** |
|
156
|
|
|
* @param object $instance |
|
157
|
|
|
* @param array $array |
|
158
|
|
|
* @param boolean $useCache |
|
159
|
|
|
*/ |
|
160
|
|
|
public static function affectsManyToManys($instance,$array=NULL,$useCache=NULL){ |
|
161
|
|
|
$metaDatas=OrmUtils::getModelMetadata(\get_class($instance)); |
|
162
|
|
|
$manyToManyFields=$metaDatas["#manyToMany"]; |
|
163
|
|
|
if(\sizeof($manyToManyFields)>0){ |
|
164
|
|
|
foreach ($manyToManyFields as $member){ |
|
165
|
|
|
self::getManyToMany($instance, $member,false,$array,$useCache); |
|
166
|
|
|
} |
|
167
|
|
|
} |
|
168
|
|
|
} |
|
169
|
|
|
|
|
170
|
|
|
private static function getManyToManyFromArray($instance, $array, $class, $parser) { |
|
171
|
|
|
$ret=[]; |
|
172
|
|
|
$continue=true; |
|
173
|
|
|
$accessorToMember="get" . ucfirst($parser->getInversedBy()); |
|
174
|
|
|
$myPkAccessor="get" . ucfirst($parser->getMyPk()); |
|
175
|
|
|
|
|
176
|
|
|
if (!method_exists($instance, $myPkAccessor)) { |
|
177
|
|
|
Logger::warn("DAO", "L'accesseur au membre clé primaire " . $myPkAccessor . " est manquant pour " . $class,"ManyToMany"); |
|
178
|
|
|
} |
|
179
|
|
|
if (count($array) > 0){ |
|
180
|
|
|
$continue=method_exists(reset($array), $accessorToMember); |
|
181
|
|
|
} |
|
182
|
|
|
if ($continue) { |
|
183
|
|
|
foreach ( $array as $targetEntityInstance ) { |
|
184
|
|
|
$instances=$targetEntityInstance->$accessorToMember(); |
|
185
|
|
|
if (is_array($instances)) { |
|
186
|
|
|
foreach ( $instances as $inst ) { |
|
187
|
|
|
if ($inst->$myPkAccessor() == $instance->$myPkAccessor()) |
|
188
|
|
|
array_push($ret, $targetEntityInstance); |
|
189
|
|
|
} |
|
190
|
|
|
} |
|
191
|
|
|
} |
|
192
|
|
|
} else { |
|
193
|
|
|
Logger::warn("DAO", "L'accesseur au membre " . $parser->getInversedBy() . " est manquant pour " . $parser->getTargetEntity(),"ManyToMany"); |
|
194
|
|
|
} |
|
195
|
|
|
return $ret; |
|
196
|
|
|
} |
|
197
|
|
|
|
|
198
|
|
|
/** |
|
199
|
|
|
* Returns an array of $className objects from the database |
|
200
|
|
|
* @param string $className class name of the model to load |
|
201
|
|
|
* @param string $condition Part following the WHERE of an SQL statement |
|
202
|
|
|
* @param boolean|array $included if true, loads associate members with associations, if array, example : ["client.*","commands"] |
|
203
|
|
|
* @param array|null $parameters |
|
204
|
|
|
* @param boolean $useCache use the active cache if true |
|
205
|
|
|
* @return array |
|
206
|
|
|
*/ |
|
207
|
|
|
public static function getAll($className, $condition='', $included=true,$parameters=null,$useCache=NULL) { |
|
208
|
|
|
return self::_getAll($className, new ConditionParser($condition,null,$parameters),$included,$useCache); |
|
209
|
|
|
} |
|
210
|
|
|
|
|
211
|
|
|
protected static function _getOne($className,ConditionParser $conditionParser,$included,$useCache){ |
|
212
|
|
|
$conditionParser->limitOne(); |
|
213
|
|
|
$retour=self::_getAll($className, $conditionParser, $included,$useCache); |
|
214
|
|
|
if (sizeof($retour) < 1){ |
|
215
|
|
|
return null; |
|
216
|
|
|
} |
|
217
|
|
|
return \reset($retour); |
|
218
|
|
|
} |
|
219
|
|
|
|
|
220
|
|
|
|
|
221
|
|
|
|
|
222
|
|
|
/** |
|
223
|
|
|
* @param string $className |
|
224
|
|
|
* @param ConditionParser $conditionParser |
|
225
|
|
|
* @param boolean|array $included |
|
226
|
|
|
* @param boolean $useCache |
|
227
|
|
|
* @return array |
|
228
|
|
|
*/ |
|
229
|
|
|
protected static function _getAll($className, ConditionParser $conditionParser, $included=true,$useCache=NULL) { |
|
230
|
|
|
$included=self::getIncludedForStep($included); |
|
231
|
|
|
$objects=array (); |
|
232
|
|
|
$invertedJoinColumns=null; |
|
233
|
|
|
$oneToManyFields=null; |
|
234
|
|
|
$manyToManyFields=null; |
|
235
|
|
|
|
|
236
|
|
|
$metaDatas=OrmUtils::getModelMetadata($className); |
|
237
|
|
|
$tableName=$metaDatas["#tableName"]; |
|
238
|
|
|
$hasIncluded=$included || (is_array($included) && sizeof($included)>0); |
|
239
|
|
|
if($hasIncluded){ |
|
240
|
|
|
self::_initRelationFields($included, $metaDatas, $invertedJoinColumns, $oneToManyFields, $manyToManyFields); |
|
241
|
|
|
} |
|
242
|
|
|
$condition=SqlUtils::checkWhere($conditionParser->getCondition()); |
|
243
|
|
|
$members=\array_diff($metaDatas["#fieldNames"],$metaDatas["#notSerializable"]); |
|
244
|
|
|
$query=self::$db->prepareAndExecute($tableName, $condition,$members,$conditionParser->getParams(),$useCache); |
|
245
|
|
|
$oneToManyQueries=[]; |
|
246
|
|
|
$manyToOneQueries=[]; |
|
247
|
|
|
$manyToManyParsers=[]; |
|
248
|
|
|
|
|
249
|
|
|
foreach ( $query as $row ) { |
|
250
|
|
|
$object=self::loadObjectFromRow($row, $className, $invertedJoinColumns, $oneToManyFields,$manyToManyFields,$members, $oneToManyQueries,$manyToOneQueries,$manyToManyParsers); |
|
251
|
|
|
$key=OrmUtils::getKeyValues($object); |
|
252
|
|
|
$objects[$key]=$object; |
|
253
|
|
|
} |
|
254
|
|
|
if($hasIncluded){ |
|
255
|
|
|
self::_affectsRelationObjects($manyToOneQueries, $oneToManyQueries, $manyToManyParsers, $objects, $included, $useCache); |
|
256
|
|
|
} |
|
257
|
|
|
return $objects; |
|
258
|
|
|
} |
|
259
|
|
|
|
|
260
|
|
|
public static function paginate($className,$page=1,$rowsPerPage=20,$condition=null,$included=true){ |
|
261
|
|
|
if(!isset($condition)){ |
|
262
|
|
|
$condition="1=1"; |
|
263
|
|
|
} |
|
264
|
|
|
return self::getAll($className,$condition." LIMIT ".$rowsPerPage." OFFSET ".(($page-1)*$rowsPerPage),$included); |
|
265
|
|
|
} |
|
266
|
|
|
|
|
267
|
|
|
public static function getRownum($className,$ids){ |
|
268
|
|
|
$tableName=OrmUtils::getTableName($className); |
|
269
|
|
|
self::parseKey($ids,$className); |
|
270
|
|
|
$condition=SqlUtils::getCondition($ids,$className); |
|
271
|
|
|
$keys=implode(",", OrmUtils::getKeyFields($className)); |
|
272
|
|
|
return self::$db->queryColumn("SELECT num FROM (SELECT *, @rownum:=@rownum + 1 AS num FROM {$tableName}, (SELECT @rownum:=0) r ORDER BY {$keys}) d WHERE ".$condition); |
|
273
|
|
|
} |
|
274
|
|
|
|
|
275
|
|
|
/** |
|
276
|
|
|
* @param array $row |
|
277
|
|
|
* @param string $className |
|
278
|
|
|
* @param array $invertedJoinColumns |
|
279
|
|
|
* @param array $oneToManyFields |
|
280
|
|
|
* @param array $members |
|
281
|
|
|
* @param array $oneToManyQueries |
|
282
|
|
|
* @param array $manyToOneQueries |
|
283
|
|
|
* @param array $manyToManyParsers |
|
284
|
|
|
* @return object |
|
285
|
|
|
*/ |
|
286
|
|
|
private static function loadObjectFromRow($row, $className, $invertedJoinColumns, $oneToManyFields, $manyToManyFields,$members,&$oneToManyQueries,&$manyToOneQueries,&$manyToManyParsers) { |
|
287
|
|
|
$o=new $className(); |
|
288
|
|
|
foreach ( $row as $k => $v ) { |
|
289
|
|
|
if(sizeof($fields=\array_keys($members,$k))>0){ |
|
290
|
|
|
foreach ($fields as $field){ |
|
291
|
|
|
$accesseur="set" . ucfirst($field); |
|
292
|
|
|
if (method_exists($o, $accesseur)) { |
|
293
|
|
|
$o->$accesseur($v); |
|
294
|
|
|
} |
|
295
|
|
|
} |
|
296
|
|
|
} |
|
297
|
|
|
$o->_rest[$k]=$v; |
|
298
|
|
|
if (isset($invertedJoinColumns) && isset($invertedJoinColumns[$k])) { |
|
299
|
|
|
$fk="_".$k; |
|
300
|
|
|
$o->$fk=$v; |
|
301
|
|
|
self::prepareManyToOne($manyToOneQueries,$v, $fk,$invertedJoinColumns[$k]); |
|
302
|
|
|
} |
|
303
|
|
|
} |
|
304
|
|
|
if (isset($oneToManyFields)) { |
|
305
|
|
|
foreach ( $oneToManyFields as $k => $annot ) { |
|
306
|
|
|
self::prepareOneToMany($oneToManyQueries,$o, $k, $annot); |
|
307
|
|
|
} |
|
308
|
|
|
} |
|
309
|
|
|
if (isset($manyToManyFields)) { |
|
310
|
|
|
foreach ( $manyToManyFields as $k => $annot ) { |
|
311
|
|
|
self::prepareManyToMany($manyToManyParsers,$o, $k, $annot); |
|
312
|
|
|
} |
|
313
|
|
|
} |
|
314
|
|
|
return $o; |
|
315
|
|
|
} |
|
316
|
|
|
|
|
317
|
|
|
/** |
|
318
|
|
|
* Returns the number of objects of $className from the database respecting the condition possibly passed as parameter |
|
319
|
|
|
* @param string $className complete classname of the model to load |
|
320
|
|
|
* @param string $condition Part following the WHERE of an SQL statement |
|
321
|
|
|
* @param array|null $parameters The query parameters |
|
322
|
|
|
* @return int count of objects |
|
323
|
|
|
*/ |
|
324
|
|
|
public static function count($className, $condition='',$parameters=null) { |
|
325
|
|
|
$tableName=OrmUtils::getTableName($className); |
|
326
|
|
|
if ($condition != '') |
|
327
|
|
|
$condition=" WHERE " . $condition; |
|
328
|
|
|
return self::$db->prepareAndFetchColumn("SELECT COUNT(*) FROM " . $tableName . $condition,$parameters); |
|
329
|
|
|
} |
|
330
|
|
|
|
|
331
|
|
|
/** |
|
332
|
|
|
* Returns an instance of $className from the database, from $keyvalues values of the primary key |
|
333
|
|
|
* @param String $className complete classname of the model to load |
|
334
|
|
|
* @param Array|string $keyValues primary key values or condition |
|
335
|
|
|
* @param boolean|array $included if true, charges associate members with association |
|
336
|
|
|
* @param array|null $parameters the request parameters |
|
337
|
|
|
* @param boolean $useCache use cache if true |
|
338
|
|
|
* @return object the instance loaded or null if not found |
|
339
|
|
|
*/ |
|
340
|
|
|
public static function getOne($className, $keyValues, $included=true,$parameters=null,$useCache=NULL) { |
|
341
|
|
|
$conditionParser=new ConditionParser(); |
|
342
|
|
|
if(!isset($parameters)){ |
|
343
|
|
|
$conditionParser->addKeyValues($keyValues,$className); |
|
344
|
|
|
}else{ |
|
345
|
|
|
$conditionParser->setCondition($keyValues); |
|
|
|
|
|
|
346
|
|
|
$conditionParser->setParams($parameters); |
|
347
|
|
|
} |
|
348
|
|
|
return self::_getOne($className, $conditionParser, $included, $useCache); |
|
349
|
|
|
} |
|
350
|
|
|
|
|
351
|
|
|
private static function parseKey(&$keyValues,$className){ |
|
352
|
|
|
if (!is_array($keyValues)) { |
|
353
|
|
|
if (strrpos($keyValues, "=") === false && strrpos($keyValues, ">") === false && strrpos($keyValues, "<") === false) { |
|
354
|
|
|
$keyValues="`" . OrmUtils::getFirstKey($className) . "`='" . $keyValues . "'"; |
|
355
|
|
|
} |
|
356
|
|
|
} |
|
357
|
|
|
} |
|
358
|
|
|
|
|
359
|
|
|
/** |
|
360
|
|
|
* Establishes the connection to the database using the past parameters |
|
361
|
|
|
* @param string $dbType |
|
362
|
|
|
* @param string $dbName |
|
363
|
|
|
* @param string $serverName |
|
364
|
|
|
* @param string $port |
|
365
|
|
|
* @param string $user |
|
366
|
|
|
* @param string $password |
|
367
|
|
|
* @param array $options |
|
368
|
|
|
* @param boolean $cache |
|
369
|
|
|
*/ |
|
370
|
|
|
public static function connect($dbType,$dbName, $serverName="127.0.0.1", $port="3306", $user="root", $password="", $options=[],$cache=false) { |
|
371
|
|
|
self::$db=new Database($dbType,$dbName, $serverName, $port, $user, $password, $options,$cache); |
|
372
|
|
|
try { |
|
373
|
|
|
self::$db->connect(); |
|
374
|
|
|
} catch (\Exception $e) { |
|
375
|
|
|
Logger::error("DAO", $e->getMessage()); |
|
376
|
|
|
throw $e; |
|
377
|
|
|
} |
|
378
|
|
|
} |
|
379
|
|
|
|
|
380
|
|
|
/** |
|
381
|
|
|
* Returns true if the connection to the database is established |
|
382
|
|
|
* @return boolean |
|
383
|
|
|
*/ |
|
384
|
|
|
public static function isConnected(){ |
|
385
|
|
|
return self::$db!==null && (self::$db instanceof Database) && self::$db->isConnected(); |
|
386
|
|
|
} |
|
387
|
|
|
} |
|
388
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.