├── .gitignore
├── Commands
└── ClearCache.php
├── DataMapper
├── EntityTrait.php
├── ModelDefinitionCache.php
├── QueryBuilder.php
├── QueryBuilderInterface.php
├── RelationDef.php
└── ResultBuilder.php
├── Examples
├── Controllers
│ ├── Crud.php
│ ├── Like.php
│ ├── Related.php
│ ├── SubQuery.php
│ └── Where.php
├── Entities
│ ├── Color.php
│ ├── Role.php
│ ├── User.php
│ └── UserDetail.php
├── Models
│ ├── ColorModel.php
│ ├── RoleModel.php
│ ├── UserDetailModel.php
│ └── UserModel.php
└── Setup.php
├── Extensions
├── Database
│ ├── BaseBuilder.php
│ └── MySQLi
│ │ ├── Builder.php
│ │ ├── Connection.php
│ │ ├── Forge.php
│ │ └── Result.php
├── Entity.php
└── Model.php
├── Hooks
└── PreController.php
├── Interfaces
└── OrmEventsInterface.php
├── LICENSE
├── Migration
├── ColumnTypes.php
└── Table.php
├── ModelParser
├── ModelItem.php
├── ModelParser.php
├── PropertyItem.php
├── TypeScript
│ ├── Index.php
│ ├── Model.php
│ ├── ModelDefinition.php
│ └── ModelInterface.php
└── Xamarin
│ ├── Model.php
│ └── ModelDefinition.php
├── README.md
├── composer.json
└── debug_helper.php
/.gitignore:
--------------------------------------------------------------------------------
1 | #-------------------------
2 | # Operating Specific Junk Files
3 | #-------------------------
4 |
5 | # OS X
6 | .DS_Store
7 | .AppleDouble
8 | .LSOverride
9 |
10 | # OS X Thumbnails
11 | ._*
12 |
13 | # Windows image file caches
14 | Thumbs.db
15 | ehthumbs.db
16 | Desktop.ini
17 |
18 | # Recycle Bin used on file shares
19 | $RECYCLE.BIN/
20 |
21 | # Windows Installer files
22 | *.cab
23 | *.msi
24 | *.msm
25 | *.msp
26 |
27 | # Windows shortcuts
28 | *.lnk
29 |
30 | # Linux
31 | *~
32 |
33 | # KDE directory preferences
34 | .directory
35 |
36 | # Linux trash folder which might appear on any partition or disk
37 | .Trash-*
38 |
39 | #-------------------------
40 | # Environment Files
41 | #-------------------------
42 | # These should never be under version control,
43 | # as it poses a security risk.
44 | .env
45 | .vagrant
46 | application/.env
47 | Vagrantfile
48 |
49 | #-------------------------
50 | # Temporary Files
51 | #-------------------------
52 | writable/cache/*
53 | !writable/cache/index.html
54 | !writable/cache/.htaccess
55 |
56 | writable/logs/*
57 | !writable/logs/index.html
58 | !writable/logs/.htaccess
59 |
60 | writable/session/*
61 | !writable/session/index.html
62 | !writable/session/.htaccess
63 |
64 | writable/uploads/*
65 | !writable/uploads/index.html
66 | !writable/uploads/.htaccess
67 |
68 | writable/debugbar/*
69 |
70 | writable/tmp/*
71 | !writable/tmp/index.html
72 | !writable/tmp/.htaccess
73 |
74 | php_errors.log
75 |
76 | #-------------------------
77 | # User Guide Temp Files
78 | #-------------------------
79 | user_guide_src/build/*
80 | user_guide_src/cilexer/build/*
81 | user_guide_src/cilexer/dist/*
82 | user_guide_src/cilexer/pycilexer.egg-info/*
83 |
84 | #-------------------------
85 | # Test Files
86 | #-------------------------
87 | #tests/coverage*
88 |
89 | # Don't save phpunit under version control.
90 | phpunit
91 |
92 | #-------------------------
93 | # Composer
94 | #-------------------------
95 | vendor/
96 | composer.lock
97 |
98 | #-------------------------
99 | # IDE / Development Files
100 | #-------------------------
101 |
102 | # Modules Testing
103 | _modules/*
104 |
105 | # phpenv local config
106 | .php-version
107 |
108 | # Jetbrains editors (PHPStorm, etc)
109 | .idea/
110 | *.iml
111 |
112 | # Netbeans
113 | nbproject/
114 | nbbuild/
115 | dist/
116 | nbdist/
117 | nbactions.xml
118 | nb-configuration.xml
119 | .nb-gradle/
120 |
121 | # Sublime Text
122 | *.tmlanguage.cache
123 | *.tmPreferences.cache
124 | *.stTheme.cache
125 | *.sublime-workspace
126 | *.sublime-project
127 | .phpintel
128 | /api/
129 |
130 | # Visual Studio Code
131 | .vscode/
132 |
133 | /results/
134 | /phpunit*.xml
135 |
136 | composer.phar
--------------------------------------------------------------------------------
/Commands/ClearCache.php:
--------------------------------------------------------------------------------
1 | clearCache();
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/DataMapper/EntityTrait.php:
--------------------------------------------------------------------------------
1 | _getModel()->getPrimaryKey();
19 | return !empty($this->{$primaryKey}) || (!is_null($this->all) && count($this->all));
20 | }
21 |
22 | //
23 |
24 | public function find($id = null) {
25 | $entity = $this->_getModel()->find($id);
26 | foreach(get_object_vars($entity) as $name => $value)
27 | $this->{$name} = $value;
28 | return $entity;
29 | }
30 |
31 | //
32 |
33 | //
34 |
35 | /**
36 | * @param Entity|null $related
37 | * @param string|null $relatedField
38 | */
39 | public function save($related = null, $relatedField = null) {
40 | if($related instanceof Entity) {
41 | if($related->exists()) {
42 | foreach ($related as $relatedItem) {
43 | $this->saveRelation($relatedItem, $relatedField);
44 | }
45 | }
46 | } else {
47 | $result = $this->_getModel()->save($this);
48 | if(!is_bool($result))
49 | $this->{$this->_getModel()->getPrimaryKey()} = $result;
50 | }
51 | }
52 |
53 | public function insert() {
54 | $result = $this->_getModel()->insert($this);
55 | if(!is_bool($result))
56 | $this->{$this->_getModel()->getPrimaryKey()} = $result;
57 | }
58 |
59 | /**
60 | * @param Entity $related
61 | * @param string|null $relationName
62 | */
63 | public function saveRelation($related, $relationName = null) {
64 | if(!$this->exists() |! $related->exists()) return;
65 |
66 | if(!$relationName) $relationName = get_class($related->_getModel());
67 |
68 | $thisModel = $this->_getModel();
69 | $relatedModel = $related->_getModel();
70 |
71 | $relation = $thisModel->getRelation($relationName);
72 | if(empty($relation)) return;
73 | $relation = $relation[0];
74 |
75 | $relationShipTable = $relation->getRelationShipTable();
76 |
77 | if($relationShipTable == $thisModel->getTableName()) {
78 | if(in_array($relation->getJoinOtherAs(), $thisModel->getTableFields())) {
79 |
80 | // Check if opposite relation is hasOne
81 | $opposite = $relatedModel->getRelation($relation->getOtherField());
82 | if(!empty($opposite) && $opposite[0]->getType() == RelationDef::HasOne) {
83 | $related->deleteRelation($this, $relation->getOtherField());
84 | }
85 |
86 | $this->{$relation->getJoinOtherAs()} = $related->{$relatedModel->getPrimaryKey()};
87 | $this->save();
88 | }
89 | } else if($relationShipTable == $relatedModel->getTableName()) {
90 | if(in_array($relation->getJoinSelfAs(), $relatedModel->getTableFields())) {
91 |
92 | // Check if this relation is hasOne
93 | if($relation->getType() == RelationDef::HasOne) {
94 | $this->deleteRelation($related, $relationName);
95 | }
96 |
97 | $related->{$relation->getJoinSelfAs()} = $this->{$thisModel->getPrimaryKey()};
98 | $related->save();
99 | }
100 | } else {
101 |
102 | Database::connect()
103 | ->table($relationShipTable)
104 | ->insert([
105 | $relation->getJoinSelfAs() => $this->{$thisModel->getPrimaryKey()},
106 | $relation->getJoinOtherAs() => $related->{$relatedModel->getPrimaryKey()}
107 | ]);
108 |
109 | }
110 |
111 | if($thisModel instanceof OrmEventsInterface && $this instanceof Entity && $related instanceof Entity) {
112 | $thisModel->postAddRelation($this, $related);
113 | }
114 | }
115 |
116 | //
117 |
118 | //
119 |
120 | public function deleteAll() {
121 | /** @var Entity $item */
122 | foreach($this as $item) $item->delete();
123 | }
124 |
125 | /**
126 | * @param Entity|null $related
127 | */
128 | public function delete($related = null) {
129 | if($this->exists()) {
130 | if(is_null($related)) {
131 |
132 | $thisModel = $this->_getModel();
133 | if(in_array('deletion_id', $this->_getModel()->getTableFields())) {
134 | foreach(OrmExtension::$entityNamespace as $entityNamespace) {
135 | $name = $entityNamespace . 'Deletion';
136 | if(class_exists($name)) {
137 | /** @var Entity $deletion */
138 | $deletion = new $name();
139 | $deletion->save();
140 | $this->deletion_id = $deletion->{$deletion->_getModel()->getPrimaryKey()};
141 | $this->save();
142 | break;
143 | }
144 | }
145 | } else
146 | $thisModel->delete($this->{$thisModel->getPrimaryKey()});
147 |
148 | if($thisModel instanceof OrmEventsInterface && $this instanceof Entity)
149 | $thisModel->postDelete($this);
150 |
151 | } else {
152 | $this->deleteRelation($related);
153 | }
154 | }
155 | }
156 |
157 | /**
158 | * @param Entity|string $related
159 | * @param string|null $relationName
160 | */
161 | public function deleteRelation($related, $relationName = null) {
162 | if(!$relationName) $relationName = get_class($related->_getModel());
163 |
164 | $thisModel = $this->_getModel();
165 | if(is_string($related))
166 | $relatedModel = new $related();
167 | else
168 | $relatedModel = $related->_getModel();
169 |
170 | $relation = $thisModel->getRelation($relationName);
171 | if(empty($relation)) return;
172 | $relation = $relation[0];
173 |
174 | $relationShipTable = $relation->getRelationShipTable();
175 |
176 | if($relationShipTable == $thisModel->getTableName()) {
177 | if(in_array($relation->getJoinOtherAs(), $thisModel->getTableFields())) {
178 | $this->{$relation->getJoinOtherAs()} = null;
179 | $this->save();
180 | }
181 | } else if($relationShipTable == $relatedModel->getTableName()) {
182 | if(in_array($relation->getJoinSelfAs(), $relatedModel->getTableFields())) {
183 | if(is_string($related)) {
184 | // TODO Handle updated and updated_by_id
185 | Database::connect()
186 | ->table($relationShipTable)
187 | ->update(
188 | [$relation->getJoinSelfAs() => 0],
189 | [$relation->getJoinSelfAs() => $this->{$thisModel->getPrimaryKey()}]);
190 | } else {
191 | $related->{$relation->getJoinSelfAs()} = null;
192 | $related->save();
193 | }
194 | }
195 | } else {
196 |
197 | Database::connect()
198 | ->table($relationShipTable)
199 | ->delete([
200 | $relation->getJoinSelfAs() => $this->{$thisModel->getPrimaryKey()},
201 | $relation->getJoinOtherAs() => $related->{$relatedModel->getPrimaryKey()}
202 | ]);
203 |
204 | }
205 |
206 | unset($this->{$relation->getSimpleName()});
207 | //$this->resetStoredFields();
208 |
209 | if($thisModel instanceof OrmEventsInterface && $this instanceof Entity && $related instanceof Entity) {
210 | $thisModel->postDeleteRelation($this, $related);
211 | }
212 | }
213 |
214 | //
215 |
216 | //
217 |
218 | public function allToArray(bool $onlyChanged = false, bool $cast = true, bool $recursive = false, array $fieldsFilter = null) {
219 | $items = [];
220 | foreach($this as $item)
221 | $items[] = $item->toArray($onlyChanged, $cast, $recursive, $fieldsFilter);
222 | return $items;
223 | }
224 |
225 | public function allToArrayWithFields(array $fieldsFilter = null, bool $onlyChanged = false, bool $cast = true, bool $recursive = false) {
226 | return $this->allToArray($onlyChanged, $cast, $recursive, $fieldsFilter);
227 | }
228 |
229 | public function toArray(bool $onlyChanged = false, bool $cast = true, bool $recursive = false, array $fieldsFilter = null): array {
230 | $item = [];
231 |
232 | // Fields
233 | $fields = ModelDefinitionCache::getFieldData($this->getSimpleName());
234 | foreach($fields as $fieldData) {
235 | $fieldName = $fieldData->name;
236 |
237 | if(in_array($fieldName, $this->hiddenFields)) {
238 | continue;
239 | }
240 |
241 | if ($fieldsFilter != null && !in_array($fieldName, $fieldsFilter)) {
242 | continue;
243 | }
244 |
245 | $field = $this->{$fieldName};
246 | if (is_string($field)) {
247 | switch($fieldData->type) {
248 | case 'bigint':
249 | case 'int':
250 | $item[$fieldName] = is_null($this->{$fieldName}) ? null : (int)$this->{$fieldName};
251 | break;
252 | case 'float':
253 | case 'double':
254 | case 'decimal':
255 | $item[$fieldName] = (double)$this->{$fieldName};
256 | break;
257 | case 'tinyint':
258 | $item[$fieldName] = (bool)$this->{$fieldName};
259 | break;
260 | case 'varchar':
261 | case 'text':
262 | case 'time':
263 | $item[$fieldName] = (string)$this->{$fieldName};
264 | break;
265 | case 'datetime':
266 | if($this->{$fieldName} != null && $this->{$fieldName} != "0000-00-00 00:00:00") {
267 | $item[$fieldName] = (string)strtotime($this->{$fieldName});
268 | try {
269 | $foo = new DateTime($this->{$fieldName}, new DateTimeZone("Europe/Copenhagen"));
270 | $foo->setTimeZone(new DateTimeZone("UTC"));
271 | $item[$fieldName] = $foo->format('c');
272 | } catch(\Exception $e) {
273 |
274 | }
275 | } else $item[$fieldName] = null;
276 | break;
277 | default:
278 | $item[$fieldName] = $this->{$fieldName};
279 | }
280 | } else {
281 | $item[$fieldName] = $this->{$fieldName};
282 | }
283 | }
284 |
285 | // Relations
286 | /** @var RelationDef[] $relations */
287 | $relations = ModelDefinitionCache::getRelations($this->getSimpleName());
288 | foreach($relations as $relation) {
289 | $fieldName = $relation->getSimpleName();
290 | switch($relation->getType()) {
291 | case RelationDef::HasOne:
292 | if(isset($this->{$fieldName}) && $this->{$fieldName}->exists())
293 | $item[$fieldName] = $this->{$fieldName}->toArray();
294 | break;
295 | case RelationDef::HasMany:
296 | $fieldName = plural($fieldName);
297 | if(isset($this->{$fieldName}) && $this->{$fieldName}->exists())
298 | $item[$fieldName] = $this->{$fieldName}->allToArray();
299 | break;
300 | }
301 | }
302 |
303 |
304 | return $item;
305 | }
306 |
307 | //
308 |
309 | //
310 |
311 | public function count() {
312 | return !is_null($this->all) ? count($this->all) : ($this->exists() ? 1 : 0);
313 | }
314 |
315 | public function add($item) {
316 | if(is_null($this->all)) $this->all = [];
317 | $this->all[] = $item;
318 | $this->idMap = null;
319 | }
320 |
321 | public function remove($item) {
322 | if(is_null($this->all)) $this->all = [];
323 | if(($key = array_search($item, $this->all)) !== false) {
324 | unset($this->all[$key]);
325 | }
326 | $this->idMap = null;
327 | }
328 |
329 | public function removeById($id) {
330 | $item = $this->getById($id);
331 | if($item) $this->remove($item);
332 | }
333 |
334 | private $idMap = null; // Initialized when needed
335 | private function initIdMap() {
336 | $this->idMap = [];
337 | $primaryKey = $this->_getModel()->getPrimaryKey();
338 | foreach($this as $item) $this->idMap[$item->{$primaryKey}] = $item;
339 | }
340 | public function getById($id) {
341 | if(is_null($this->idMap)) $this->initIdMap();
342 | return isset($this->idMap[$id]) ? $this->idMap[$id] : null;
343 | }
344 |
345 | public function hasId($id) {
346 | if(is_null($this->idMap)) $this->initIdMap();
347 | return isset($this->idMap[$id]);
348 | }
349 |
350 | public function clear() {
351 | $this->all = [];
352 | }
353 |
354 | /**
355 | * @return ArrayIterator
356 | */
357 | public function getIterator(): ArrayIterator {
358 | return new ArrayIterator(!is_null($this->all) ? $this->all : ($this->exists() ? [$this] : []));
359 | }
360 |
361 | //
362 |
363 |
364 | public function getTableFields() {
365 | return $this->_getModel()->getTableFields();
366 | }
367 |
368 | public function resetStoredFields() {
369 | foreach($this->getTableFields() as $field) {
370 | $this->stored[$field] = $this->{$field};
371 | }
372 | }
373 |
374 | }
375 |
--------------------------------------------------------------------------------
/DataMapper/ModelDefinitionCache.php:
--------------------------------------------------------------------------------
1 | cache)) {
25 | static::$instance->init();
26 | }
27 | return static::$instance;
28 | }
29 |
30 | private $config;
31 |
32 | public $cache;
33 |
34 | public function init() {
35 | $this->config = new Cache();
36 | $this->cache = Services::cache($this->config, false);
37 | }
38 |
39 |
40 | public static function setFields($entity, $fields) {
41 | static::setData($entity.'_fields', $fields);
42 | }
43 |
44 | public static function getFields($entity, $tableName = null) {
45 | $fields = static::getData($entity.'_fields');
46 | if(is_null($fields)) {
47 | $fieldData = ModelDefinitionCache::getFieldData($entity, $tableName);
48 | $fields = [];
49 | if($fieldData) {
50 | foreach($fieldData as $field) $fields[] = $field->name;
51 | }
52 | ModelDefinitionCache::setFields($entity, $fields);
53 | }
54 | return $fields;
55 | }
56 |
57 | public static function setFieldData($entity, $fields) {
58 | static::setData($entity.'_field_data', $fields);
59 | }
60 |
61 | public static function getFieldData($entity, $tableName = null) {
62 | $fieldData = static::getData($entity.'_field_data');
63 | if(is_null($fieldData)) {
64 | if(is_null($tableName)) {
65 |
66 | foreach(OrmExtension::$modelNamespace as $modelNamespace) {
67 | $modelName = $modelNamespace . $entity . 'Model';
68 | if(class_exists($modelName)) {
69 | /** @var Model $model */
70 | $model = new $modelName();
71 | $tableName = $model->getTableName();
72 | }
73 | }
74 | }
75 |
76 | $db = Database::connect();
77 | try {
78 | $fieldData = $db->getFieldData($tableName);
79 | ModelDefinitionCache::setFieldData($entity, $fieldData);
80 | } catch(\Exception $e) {
81 | // Ignore table doesn't exist
82 | if($e->getCode() != 1146) {
83 | throw $e;
84 | }
85 | }
86 | }
87 | return $fieldData;
88 | }
89 |
90 | public static function setRelations($entity, $relations) {
91 | static::setData($entity.'_relations', $relations);
92 | }
93 |
94 | /**
95 | * @param $entity
96 | * @return RelationDef[]
97 | * @throws \Exception
98 | */
99 | public static function getRelations($entity) {
100 | $relations = static::getData($entity.'_relations');
101 | if (!$relations) {
102 | foreach (OrmExtension::$modelNamespace as $modelNamespace) {
103 | $modelName = $modelNamespace . $entity . 'Model';
104 | if (class_exists($modelName)) {
105 | /** @var Model $model */
106 | $model = new $modelName();
107 | break;
108 | }
109 | }
110 | $relations = [];
111 | if (isset($model)) {
112 | foreach ($model->hasOne as $name => $hasOne) {
113 | $relations[] = new RelationDef($model, $name, $hasOne, RelationDef::HasOne);
114 | }
115 | foreach ($model->hasMany as $name => $hasMany) {
116 | $relations[] = new RelationDef($model, $name, $hasMany, RelationDef::HasMany);
117 | }
118 | }
119 | ModelDefinitionCache::setRelations($entity, $relations);
120 | }
121 | return $relations;
122 | }
123 |
124 |
125 |
126 |
127 |
128 | private static function setData($name, $data, $ttl = YEAR) {
129 | $instance = ModelDefinitionCache::getInstance();
130 | if (isset($instance->cache)) {
131 | $instance->cache->save($name, $data, $ttl);
132 |
133 | // Change file permissions so other users can read and write
134 | $cacheInfo = $instance->cache->getCacheInfo();
135 | if (isset($cacheInfo[$name])) {
136 | chmod($cacheInfo[$name]['server_path'], 0775);
137 | }
138 | }
139 | }
140 |
141 | private $memcache = [];
142 | private static function getData($name) {
143 | try {
144 | $instance = ModelDefinitionCache::getInstance();
145 | if(!isset($instance->memcache[$name])) {
146 | $data = $instance->cache->get($name);
147 | if($data) $instance->memcache[$name] = $data;
148 | return $data;
149 | } else {
150 | return $instance->memcache[$name];
151 | }
152 | } catch(\Exception $e) {
153 | return null;
154 | }
155 | }
156 |
157 | public function clearCache($rmDir = false) {
158 | $this->memcache = [];
159 | $this->cache->clean();
160 | if ($rmDir && is_dir($this->config->storePath)) {
161 | rmdir($this->config->storePath);
162 | }
163 | }
164 |
165 |
166 | }
167 |
--------------------------------------------------------------------------------
/DataMapper/QueryBuilder.php:
--------------------------------------------------------------------------------
1 |
11 |
12 | /**
13 | * @param string|array $relationName
14 | * @param null $fields
15 | * @return Model
16 | */
17 | public function includeRelated($relationName, $fields = null, bool $withDeleted = false): Model {
18 | $parent = $this->_getModel();
19 | $relations = $this->getRelation($relationName);
20 |
21 | // Handle deep relations
22 | $last = $this;
23 | $table = null;
24 | $prefix = '';
25 | $relationPrefix = '';
26 | foreach ($relations as $relation) {
27 | $table = $last->addRelatedTable($relation, $prefix, $table, $withDeleted);
28 | $prefix .= plural($relation->getSimpleName()) . '_';
29 | $relationPrefix .= plural($relation->getSimpleName()) . '/';
30 |
31 | // Prepare next
32 | $builder = $last->_getBuilder();
33 | $selecting = $last->isSelecting();
34 | $relatedTablesAdded =& $last->relatedTablesAdded;
35 | $last = $relation->getRelationClass();
36 | $last->_setBuilder($builder);
37 | $last->setSelecting($selecting);
38 | $last->relatedTablesAdded =& $relatedTablesAdded;
39 |
40 | if (!$parent->hasIncludedRelation($relationPrefix)) {
41 | $parent->addIncludedRelation($relationPrefix, $relation);
42 | $this->selectIncludedRelated($parent, $relation, $table, $prefix, $fields);
43 | }
44 | }
45 |
46 | return $parent;
47 | }
48 |
49 | /**
50 | * @param Model $parent
51 | * @param RelationDef $relation
52 | * @param string $table
53 | * @param string $prefix
54 | * @param null $fields
55 | */
56 | private function selectIncludedRelated($parent, $relation, $table, $prefix, $fields = null) {
57 | $selection = [];
58 |
59 | $relationClassName = $relation->getClass();
60 | /** @var Model $related */
61 | $related = new $relationClassName();
62 |
63 | if (is_null($fields)) $fields = $related->getTableFields();
64 | foreach ($fields as $field) {
65 | $new_field = $prefix . $field;
66 |
67 | // Prevent collisions
68 | if (in_array($new_field, $parent->getTableFields())) continue;
69 |
70 | $selection[] = "{$table}.{$field} AS {$new_field}";
71 | }
72 |
73 | $this->select(implode(', ', $selection));
74 | }
75 |
76 | //
77 |
78 | //
79 |
80 | public function whereRelated($relationName, $field, $value, $escape = null): Model {
81 | $table = $this->handleWhereRelated($relationName);
82 | $model = $this->_getModel();
83 | $model->where("{$table[0]}.{$field}", $value, $escape, false);
84 | return $model;
85 | }
86 |
87 | public function whereInRelated($relationName, $field, $value, $escape = null): Model {
88 | $table = $this->handleWhereRelated($relationName);
89 | $model = $this->_getModel();
90 | $model->whereIn("{$table[0]}.{$field}", $value, $escape, false);
91 | return $model;
92 | }
93 |
94 | public function whereNotInRelated($relationName, $field, $value, $escape = null): Model {
95 | $table = $this->handleWhereRelated($relationName);
96 | $model = $this->_getModel();
97 | $model->whereNotIn("{$table[0]}.{$field}", $value, $escape, false);
98 | return $model;
99 | }
100 |
101 | public function whereBetweenRelated($relationName, $field, $min, $max, $escape = null): Model {
102 | $table = $this->handleWhereRelated($relationName);
103 | $model = $this->_getModel();
104 | $model->whereBetween("{$table[0]}.{$field}", $min, $max, $escape, false);
105 | return $model;
106 | }
107 |
108 | public function whereNotBetweenRelated($relationName, $field, $min, $max, $escape = false): Model {
109 | $table = $this->handleWhereRelated($relationName);
110 | $model = $this->_getModel();
111 | $model->whereNotBetween("{$table[0]}.{$field}", $min, $max, $escape, false);
112 | return $model;
113 | }
114 |
115 | public function orWhereRelated($relationName, $field, $value, $escape = null): Model {
116 | $table = $this->handleWhereRelated($relationName);
117 | $model = $this->_getModel();
118 | $model->orWhere("{$table[0]}.{$field}", $value, $escape, false);
119 | return $model;
120 | }
121 |
122 | public function orWhereInRelated($relationName, $field, $value, $escape = null): Model {
123 | $table = $this->handleWhereRelated($relationName);
124 | $model = $this->_getModel();
125 | $model->orWhereIn("{$table[0]}.{$field}", $value, $escape, false);
126 | return $model;
127 | }
128 |
129 | public function orWhereNotInRelated($relationName, $field, $value, $escape = null): Model {
130 | $table = $this->handleWhereRelated($relationName);
131 | $model = $this->_getModel();
132 | $model->orWhereNotIn("{$table[0]}.{$field}", $value, $escape, false);
133 | return $model;
134 | }
135 |
136 | //
137 |
138 | //
139 |
140 | public function likeRelated($relationName, $field, $match = '', $side = 'both', $escape = null, $insensitiveSearch = false, bool $withDeleted = false): Model {
141 | $table = $this->handleWhereRelated($relationName, $withDeleted);
142 | $model = $this->_getModel();
143 | $model->like("{$table[0]}.{$field}", $match, $side, $escape, $insensitiveSearch, false);
144 | return $model;
145 | }
146 |
147 | public function notLikeRelated($relationName, $field, $match = '', $side = 'both', $escape = null, $insensitiveSearch = false, bool $withDeleted = false): Model {
148 | $table = $this->handleWhereRelated($relationName, $withDeleted);
149 | $model = $this->_getModel();
150 | $model->notLike("{$table[0]}.{$field}", $match, $side, $escape, $insensitiveSearch, false);
151 | return $model;
152 | }
153 |
154 | public function orLikeRelated($relationName, $field, $match = '', $side = 'both', $escape = null, $insensitiveSearch = false, bool $withDeleted = false): Model {
155 | $table = $this->handleWhereRelated($relationName, $withDeleted);
156 | $model = $this->_getModel();
157 | $model->orLike("{$table[0]}.{$field}", $match, $side, $escape, $insensitiveSearch, false);
158 | return $model;
159 | }
160 |
161 | public function orNotLikeRelated($relationName, $field, $match = '', $side = 'both', $escape = null, $insensitiveSearch = false, bool $withDeleted = false): Model {
162 | $table = $this->handleWhereRelated($relationName, $withDeleted);
163 | $model = $this->_getModel();
164 | $model->orNotLike("{$table[0]}.{$field}", $match, $side, $escape, $insensitiveSearch, false);
165 | return $model;
166 | }
167 |
168 | //
169 |
170 | //
171 |
172 | /**
173 | * @param Model $query
174 | * @param string $alias
175 | * @return Model
176 | */
177 | public function selectSubQuery($query, $alias) {
178 | $model = $this->_getModel();
179 | $query = $this->parseSubQuery($query);
180 | $model->select("{$query} AS {$alias}");
181 | return $model;
182 | }
183 |
184 | /**
185 | * @param Model $query
186 | * @param null $value
187 | * @return Model
188 | */
189 | public function whereSubQuery($query, $operator, $value = null, $escape = null) {
190 | $model = $this->_getModel();
191 | $field = $this->parseSubQuery($query);
192 | $model->where("{$field} {$operator}", $value, $escape, false);
193 | return $model;
194 | }
195 |
196 | /**
197 | * @param Model $query
198 | * @param null $value
199 | * @return Model
200 | */
201 | public function orWhereSubQuery($query, $operator, $value = null, $escape = null) {
202 | $model = $this->_getModel();
203 | $field = $this->parseSubQuery($query);
204 | $model->orWhere("{$field} {$operator}", $value, $escape, false);
205 | return $model;
206 | }
207 |
208 | public function orderBySubQuery($query, $direction = '', $escape = null) {
209 | $model = $this->_getModel();
210 | $field = $this->parseSubQuery($query);
211 | $model->orderBy($field, $direction, $escape, false);
212 | return $model;
213 | }
214 |
215 | /**
216 | * @param Model $query
217 | * @return mixed
218 | */
219 | protected function parseSubQuery($query) {
220 | $model = $this->_getModel();
221 |
222 | $query->bindReplace('${parent}', $this->getTableName());
223 |
224 | $sql = $query->compileSelect_();
225 | $sql = "({$sql})";
226 |
227 | // Data::sql($sql);
228 |
229 | $sql = $this->bindMerging($sql, $query->getBindKeyCount(), $query->getBinds());
230 |
231 | $tableName = $model->db->protectIdentifiers($model->getTableName());
232 | $tableNameThisQuote = preg_quote($model->getTableName());
233 | $tableNameQuote = preg_quote($tableName);
234 | $tablePattern = "(?:{$tableNameThisQuote}|{$tableNameQuote}|\({$tableNameQuote}\))";
235 |
236 | $fieldName = $model->db->protectIdentifiers('__field__');
237 | $fieldName = str_replace('__field__', '[-\w]+', preg_quote($fieldName));
238 | $fieldPattern = "([-\w]+|{$fieldName})";
239 |
240 | // Pattern ends up being [^_](table|`table`).(field|`field`)
241 | $pattern = "/([^_:]){$tablePattern}\.{$fieldPattern}/i";
242 |
243 | // Replacement ends up being `table_subquery`.`$1`
244 | $tableSubQueryName = $model->db->protectIdentifiers($model->getTableName() . '_subquery');
245 | $replacement = "$1{$tableSubQueryName}.$2";
246 | $sql = preg_replace($pattern, $replacement, $sql);
247 |
248 | // Replace all "table table" aliases
249 | $pattern = "/{$tablePattern} {$tablePattern} /i";
250 | $replacement = "{$tableName} {$tableSubQueryName} ";
251 | $sql = preg_replace($pattern, $replacement, $sql);
252 |
253 | // Replace "FROM table" for self relationships
254 | $pattern = "/FROM {$tablePattern}([,\\s])/i";
255 | $replacement = "FROM {$tableName} $tableSubQueryName$1";
256 | $sql = preg_replace($pattern, $replacement, $sql);
257 | $sql = str_replace("\n", " ", $sql);
258 |
259 | // Data::sql($sql);
260 |
261 | return str_replace('${parent}', $this->getTableName(), $sql);
262 | }
263 |
264 | //
265 |
266 | //
267 |
268 | public function groupByRelated($relationName, $by, $escape = null): Model {
269 | $table = $this->handleWhereRelated($relationName);
270 | $model = $this->_getModel();
271 | $model->groupBy("{$table[0]}.{$by}", $escape, false);
272 | return $model;
273 | }
274 |
275 | public function havingRelated($relationName, $key, $value, $escape = null): Model {
276 | $table = $this->handleWhereRelated($relationName);
277 | $model = $this->_getModel();
278 | $model->having("{$table[0]}.{$key}", $value, $escape, false);
279 | return $model;
280 | }
281 |
282 | public function orHavingRelated($relationName, $key, $value, $escape = null): Model {
283 | $table = $this->handleWhereRelated($relationName);
284 | $model = $this->_getModel();
285 | $model->orHaving("{$table[0]}.{$key}", $value, $escape, false);
286 | return $model;
287 | }
288 |
289 | public function orderByRelated($relationName, $orderby, $direction = '', $escape = null): Model {
290 | $table = $this->handleWhereRelated($relationName);
291 | $model = $this->_getModel();
292 | $model->orderBy("{$table[0]}.{$orderby}", $direction, $escape, false);
293 | return $model;
294 | }
295 |
296 |
297 | //
298 |
299 |
300 | //
301 |
302 | public function getTableFields() {
303 | return $this->_getModel()->allowedFields;
304 | }
305 |
306 | //
307 |
308 | //
309 |
310 | public function handleWhereRelated($relationName, bool $withDeleted = false) {
311 | $relations = $this->getRelation($relationName);
312 |
313 | // Handle deep relations
314 | $last = $this;
315 | $table = null;
316 | /** @var RelationDef $relation */
317 | $relation = null;
318 | $prefix = '';
319 | foreach ($relations as $relation) {
320 | $table = $last->addRelatedTable($relation, $prefix, $table, $withDeleted);
321 | $prefix .= plural($relation->getSimpleName()) . '_';
322 |
323 | // Prepare next
324 | $builder = $last->_getBuilder();
325 | $selecting = $last->isSelecting();
326 | $relatedTablesAdded =& $last->relatedTablesAdded;
327 | $last = $relation->getRelationClass();
328 | $last->_setBuilder($builder);
329 | $last->setSelecting($selecting);
330 | $last->relatedTablesAdded =& $relatedTablesAdded;
331 | // Data::debug(get_class($last), $table);
332 | }
333 |
334 | return [$table, $last];
335 | }
336 |
337 | private $relatedTablesAdded = [];
338 |
339 | /**
340 | * @param RelationDef $relation
341 | * @param string $prefix
342 | * @param string $this_table
343 | * @return string
344 | */
345 | public function addRelatedTable(RelationDef $relation, $prefix = '', $this_table = null, bool $withDeleted = false) {
346 | if (!$this_table) {
347 | $this_table = $this->getTableName();
348 | }
349 | //Data::debug("QueryBuilder::addRelatedTable", 'Name='.$relation->getSimpleName(), 'Prefix='.$prefix, 'Table='.$this_table);
350 |
351 | $related = $relation->getRelationClass();
352 | $relationShipTable = $relation->getRelationShipTable();
353 |
354 | // If no selects, select this table
355 | if (!$this->isSelecting()) {
356 | $this->select($this->getTableName() . '.*');
357 | }
358 |
359 | $addSoftDeletionCondition = !$withDeleted && $related->useSoftDeletes;
360 | $deletedField = $related->deletedField;
361 |
362 | if (($relation->getClass() == $relation->getName()) && ($this->getTableName() != $related->getTableName())) {
363 | $prefixedParentTable = $prefix . $related->getTableName();
364 | $prefixedRelatedTable = $prefix . $relationShipTable;
365 | } else { // Used when relation is custom named
366 | $prefixedParentTable = $prefix . plural($relation->getSimpleName()) . '_' . $related->getTableName();
367 | $prefixedRelatedTable = $prefix . plural($relation->getSimpleName()) . '_' . $relationShipTable;
368 | }
369 |
370 | if ($relationShipTable == $this->getTableName() && in_array($relation->getJoinOtherAs(), $this->getTableFields())) {
371 |
372 | foreach ([$relation->getJoinSelfAs(), 'id'] as $joinSelfAs) {
373 | if (in_array($joinSelfAs, $related->getTableFields())) {
374 | if (!in_array($prefixedParentTable, $this->relatedTablesAdded)) {
375 | $cond = "{$prefixedParentTable}.{$joinSelfAs} = {$this_table}.{$relation->getJoinOtherAs()}";
376 | if ($addSoftDeletionCondition) {
377 | $cond .= " AND {$prefixedParentTable}.{$deletedField} IS NULL";
378 | }
379 | $this->join("{$related->getTableName()} {$prefixedParentTable}", $cond, 'LEFT OUTER');
380 |
381 | $this->relatedTablesAdded[] = $prefixedParentTable;
382 | }
383 | $match = $prefixedParentTable;
384 | break;
385 | }
386 | }
387 |
388 | } else if ($relationShipTable == $related->getTableName() && in_array($relation->getJoinSelfAs(), $related->getTableFields())) {
389 |
390 | foreach ([$relation->getJoinOtherAs(), 'id'] as $joinOtherAs) {
391 | if (in_array($joinOtherAs, $this->getTableFields())) {
392 | if (!in_array($prefixedParentTable, $this->relatedTablesAdded)) {
393 | $cond = "{$this_table}.{$joinOtherAs} = {$prefixedParentTable}.{$relation->getJoinSelfAs()}";
394 | if ($addSoftDeletionCondition) {
395 | $cond .= " AND {$prefixedParentTable}.{$deletedField} IS NULL";
396 | }
397 | $this->join("{$related->getTableName()} {$prefixedParentTable}", $cond, 'LEFT OUTER');
398 |
399 | $this->relatedTablesAdded[] = $prefixedParentTable;
400 | }
401 | $match = $prefixedParentTable;
402 | break;
403 | }
404 | }
405 |
406 | } else {
407 |
408 | // Use a join table. We have to do two joins now. First the join table and then the relation table.
409 |
410 | $joinMatch = null;
411 | $match = null;
412 | foreach ($relation->getJoinOtherAsGuess() as $joinOtherAs) {
413 | if (in_array($joinOtherAs, $this->getTableFields())) {
414 | if (!in_array($prefixedRelatedTable, $this->relatedTablesAdded)) {
415 | $cond = "{$this_table}.{$joinOtherAs} = {$prefixedRelatedTable}.{$relation->getJoinSelfAs()}";
416 | $this->join("{$relationShipTable} {$prefixedRelatedTable}", $cond, 'LEFT OUTER');
417 |
418 | $this->relatedTablesAdded[] = $prefixedRelatedTable;
419 | }
420 | $joinMatch = $prefixedRelatedTable;
421 |
422 | // Second join
423 | if (!in_array($prefixedParentTable, $this->relatedTablesAdded)) {
424 | $cond = "{$prefixedParentTable}.{$related->getPrimaryKey()} = {$prefixedRelatedTable}.{$relation->getJoinOtherAs()}";
425 | if ($addSoftDeletionCondition) {
426 | $cond .= " AND {$prefixedParentTable}.{$deletedField} IS NULL";
427 | }
428 | $this->join("{$related->getTableName()} {$prefixedParentTable}", $cond, 'LEFT OUTER');
429 |
430 | $this->relatedTablesAdded[] = $prefixedParentTable;
431 | }
432 | $match = $prefixedParentTable;
433 | break;
434 | }
435 | }
436 |
437 | // If we still have not found a match, this is probably a custom join table
438 | if (is_null($joinMatch)) {
439 | if (!in_array($prefixedRelatedTable, $this->relatedTablesAdded)) {
440 | $cond = "{$this_table}.{$this->getPrimaryKey()} = {$prefixedRelatedTable}.{$relation->getJoinSelfAs()}";
441 | $this->join("{$relationShipTable} {$prefixedRelatedTable}", $cond, 'LEFT OUTER');
442 | $this->relatedTablesAdded[] = $prefixedRelatedTable;
443 | }
444 | }
445 |
446 | if (is_null($match)) {
447 | if (!in_array($prefixedParentTable, $this->relatedTablesAdded)) {
448 | $cond = "{$prefixedParentTable}.{$related->getPrimaryKey()} = {$prefixedRelatedTable}.{$relation->getJoinOtherAs()}";
449 | $this->join("{$related->getTableName()} {$prefixedParentTable}", $cond, 'LEFT OUTER');
450 | $this->relatedTablesAdded[] = $prefixedParentTable;
451 | }
452 | $match = $prefixedParentTable;
453 | }
454 |
455 | }
456 |
457 | return $match ?? '';
458 | }
459 |
460 | /**
461 | * @param string|array $name
462 | * @param bool $useSimpleName
463 | * @return RelationDef[] $result
464 | * @throws \Exception
465 | */
466 | public function getRelation($name, $useSimpleName = false) {
467 | // Handle deep relations
468 | if (is_array($name)) {
469 | $last = $this;
470 | $result = [];
471 | foreach ($name as $ref) {
472 | $relations = $last->getRelation($ref, $useSimpleName);
473 | if (count($relations) == 0) {
474 | throw new \Exception("Failed to find relation $name for " . get_class($this));
475 | }
476 | $relation = $relations[0];
477 | $last = $relation->getRelationClass();
478 | $result[] = $relation;
479 | }
480 | return $result;
481 | }
482 |
483 | foreach ($this->getRelations() as $relation) {
484 | if ($useSimpleName) {
485 | if ($relation->getSimpleName() == $name) return [$relation];
486 | } else {
487 | if ($relation->getName() == $name) return [$relation];
488 | }
489 | }
490 |
491 | throw new \Exception("Failed to find relation $name for " . get_class($this));
492 | }
493 |
494 | /**
495 | * @return RelationDef[]
496 | */
497 | public function getRelations() {
498 | $entityName = $this->_getModel()->getEntityName();
499 | $relations = ModelDefinitionCache::getRelations($entityName);
500 | return $relations;
501 | }
502 |
503 | //
504 |
505 | }
506 |
--------------------------------------------------------------------------------
/DataMapper/QueryBuilderInterface.php:
--------------------------------------------------------------------------------
1 | setParentClass($model);
35 | $this->setType($type);
36 | if (is_string($data)) {
37 | $this->setName($data);
38 | $this->setClass($data);
39 | } else if (is_array($data)) {
40 | $this->setName($name);
41 | if (isset($data['class'])) {
42 | $this->setClass($data['class']);
43 | } else {
44 | $this->setClass($name);
45 | }
46 | if (isset($data['otherField'])) {
47 | $this->setOtherField($data['otherField']);
48 | }
49 | if (isset($data['joinSelfAs'])) {
50 | $this->setJoinSelfAs($data['joinSelfAs']);
51 | } else if ($type == self::HasOne) {
52 | $this->setJoinSelfAs("{$name}_{$model->getPrimaryKey()}");
53 | }
54 | if (isset($data['joinOtherAs'])) {
55 | $this->setJoinOtherAs($data['joinOtherAs']);
56 | }
57 | if (isset($data['joinTable'])) {
58 | $this->setJoinTable($data['joinTable']);
59 | }
60 | if (isset($data['cascadeDelete'])) {
61 | $this->setCascadeDelete($data['cascadeDelete']);
62 | }
63 | }
64 |
65 | if (!isset($this->otherField)) {
66 | $this->setOtherField(get_class($model));
67 | }
68 | if(!isset($this->joinSelfAs)) {
69 | $this->setJoinSelfAs("{$this->getSimpleOtherField()}_{$model->getPrimaryKey()}");
70 | }
71 | if(!isset($this->joinOtherAs)) {
72 | $this->setJoinOtherAs("{$this->getSimpleName()}_{$model->getPrimaryKey()}");
73 | }
74 | // Data::debug('Def:', get_class($this->getParent()), $this->getName(), $this->getJoinSelfAs(), $this->getJoinOtherAs());
75 |
76 | if (!isset($this->joinTable)) {
77 | $relationClassName = $this->getClass();
78 | /** @var Model $relationClass */
79 | $relationClass = new $relationClassName();
80 | if (!$relationClass instanceof Model) {
81 | throw new Exception("Invalid relation {$this->getName()} for " . get_class($model));
82 | }
83 | $joins = [$model->getTableName(), $relationClass->getTableName()];
84 | sort($joins);
85 | $this->setJoinTable(strtolower(implode('_', $joins)));
86 | }
87 | }
88 |
89 | public function getRelationShipTable() {
90 | $model = $this->getRelationShipTableModel();
91 | return $model ? $model->getTableName() : $this->getJoinTable();
92 | }
93 |
94 | public function getRelationShipTableModel() {
95 | $parent = $this->getParent();
96 | $related = $this->getRelationClass();
97 |
98 | // See if the relationship is in parent table
99 | if (array_key_exists($this->getName(), $parent->hasOne)
100 | || in_array($this->getName(), $parent->hasOne)) {
101 | if (in_array($this->getJoinOtherAs(), $parent->getTableFields())) {
102 | return $parent;
103 | }
104 | }
105 |
106 | if (array_key_exists($this->getOtherField(), $related->hasOne)
107 | || in_array($this->getOtherField(), $related->hasOne)) {
108 | if (in_array($this->getJoinSelfAs(), $related->getTableFields()))
109 | return $related;
110 | }
111 |
112 | // No? Then it must be a join table
113 | return null;
114 | }
115 |
116 | private $relationClass;
117 |
118 | public function getRelationClass(): Model {
119 | if (is_null($this->relationClass)) {
120 | $className = $this->getClass();
121 | $this->relationClass = new $className();
122 | }
123 | return $this->relationClass;
124 | }
125 |
126 | public function getSimpleName() {
127 | $namespace = explode('\\', $this->getName());
128 | $trim = strtolower(preg_replace('/(?getClass()), 0, -5);
137 | }
138 |
139 | public function getSimpleOtherField() {
140 | $namespace = explode('\\', $this->getOtherField());
141 | $trim = strtolower(preg_replace('/(?
149 |
150 | /**
151 | * @return string
152 | */
153 | public function getName(): string {
154 | return $this->name;
155 | }
156 |
157 | /**
158 | * @param string $name
159 | */
160 | public function setName(string $name): void {
161 | $this->name = $name;
162 | }
163 |
164 | /**
165 | * @return string
166 | */
167 | public function getClass(): string {
168 | return $this->class;
169 | }
170 |
171 | /**
172 | * @param string $class
173 | */
174 | public function setClass(string $class): void {
175 | $this->class = $class;
176 | }
177 |
178 | /**
179 | * @return string
180 | */
181 | public function getOtherField(): string {
182 | return $this->otherField;
183 | }
184 |
185 | /**
186 | * @param string $otherField
187 | */
188 | public function setOtherField(string $otherField): void {
189 | $this->otherField = $otherField;
190 | }
191 |
192 | /**
193 | * @return string
194 | */
195 | public function getJoinSelfAs(): string {
196 | return $this->joinSelfAs;
197 | }
198 |
199 | /**
200 | * @param string $joinSelfAs
201 | */
202 | public function setJoinSelfAs(string $joinSelfAs): void {
203 | $this->joinSelfAs = $joinSelfAs;
204 | }
205 |
206 | public function getJoinSelfAsGuess(): array {
207 | return [
208 | $this->getJoinSelfAs(),
209 | $this->getParent()->getPrimaryKey(),
210 | ];
211 | }
212 |
213 | /**
214 | * @return string
215 | */
216 | public function getJoinOtherAs(): string {
217 | return $this->joinOtherAs;
218 | }
219 |
220 | /**
221 | * @param string $joinOtherAs
222 | */
223 | public function setJoinOtherAs(string $joinOtherAs): void {
224 | $this->joinOtherAs = $joinOtherAs;
225 | }
226 |
227 | public function getJoinOtherAsGuess(): array {
228 | return [
229 | $this->getJoinOtherAs(),
230 | $this->getParent()->getPrimaryKey(),
231 | ];
232 | }
233 |
234 | /**
235 | * @return string
236 | */
237 | public function getJoinTable(): string {
238 | return $this->joinTable;
239 | }
240 |
241 | /**
242 | * @param string $joinTable
243 | */
244 | public function setJoinTable(string $joinTable): void {
245 | $this->joinTable = $joinTable;
246 | }
247 |
248 | /**
249 | * @return bool
250 | */
251 | public function isCascadeDelete(): bool {
252 | return $this->cascadeDelete;
253 | }
254 |
255 | /**
256 | * @param bool $cascadeDelete
257 | */
258 | public function setCascadeDelete(bool $cascadeDelete): void {
259 | $this->cascadeDelete = $cascadeDelete;
260 | }
261 |
262 | /**
263 | * @return int
264 | */
265 | public function getType(): int {
266 | return $this->type;
267 | }
268 |
269 | /**
270 | * @param int $type
271 | */
272 | public function setType(int $type): void {
273 | $this->type = $type;
274 | }
275 |
276 | /**
277 | * @return Model
278 | */
279 | public function getParent(): Model {
280 | return new $this->parentClass;
281 | }
282 |
283 | /**
284 | * @param Model $parent
285 | */
286 | public function setParentClass(Model $parent): void {
287 | $this->parentClass = get_class($parent);
288 | }
289 |
290 | //
291 | }
292 |
--------------------------------------------------------------------------------
/DataMapper/ResultBuilder.php:
--------------------------------------------------------------------------------
1 | includedRelations[$fullName] = $relation;
22 | }
23 |
24 | protected function hasIncludedRelation(string $fullName): bool {
25 | return isset($this->includedRelations[$fullName]);
26 | }
27 |
28 | /**
29 | * @param Entity[] $result
30 | */
31 | protected function arrangeIncludedRelations(&$result) {
32 | //Data::debug(get_class($this), "arrangeIncludedRelations for", count($result), 'entities with', count($this->includedRelations), 'relations');
33 |
34 | $relations = $this->includedRelations;
35 | ksort($relations);
36 |
37 | foreach($result as $row) {
38 | //$row->resetStoredFields(); // TODO Brug CI's
39 |
40 | foreach($relations as $relationPrefix => $relation) {
41 | $fullName = str_replace('/', '_', $relationPrefix);
42 |
43 | // Deep relation
44 | $current = $row;
45 | $deepRelations = explode('/', trim($relationPrefix, '/'));
46 | array_pop($deepRelations);
47 | foreach($deepRelations as $prefix) {
48 | if($relation->getType() == RelationDef::HasOne) {
49 | $current = $current->{singular($prefix)};
50 | } else {
51 | $current = $current->{$prefix};
52 | }
53 | }
54 |
55 | $entityName = $relation->getEntityName();
56 | /** @var Entity $entity */
57 | $entity = new $entityName();
58 |
59 | $attributes = [];
60 | foreach($relation->getRelationClass()->getTableFields() as $field) {
61 | $fieldName = "{$fullName}{$field}";
62 | if(isset($row->{$fieldName})) {
63 | $attributes[$field] = $row->{$fieldName};
64 | }
65 | }
66 | $entity->setAttributes($attributes);
67 | if(!$entity->exists()) continue;
68 | //$entity->resetStoredFields();
69 |
70 | $relationName = $relation->getSimpleName();
71 | switch($relation->getType()) {
72 | case RelationDef::HasOne:
73 | $current->{$relationName} = $entity;
74 | break;
75 | case RelationDef::HasMany:
76 | $relationName = plural($relationName);
77 | if(!isset($current->{$relationName})) {
78 | $current->{$relationName} = clone $entity;
79 | $current->{$relationName}->all = [];
80 | }
81 | $current->{$relationName}->all[] = $entity;
82 | break;
83 | }
84 |
85 | }
86 |
87 | }
88 |
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/Examples/Controllers/Crud.php:
--------------------------------------------------------------------------------
1 | cache->clean();
16 | Setup::run();
17 | }
18 |
19 | public function create() {
20 | $this->printBefore();
21 |
22 | // Create new user
23 | $user = new User();
24 | $user->name = "New user";
25 | $user->save();
26 |
27 | $this->printAfter();
28 |
29 | $this->response->setJSON(Data::getStore());
30 | $this->response->send();
31 | }
32 |
33 | public function read() {
34 | // Find all users
35 | $users = (new UserModel())->findAll();
36 | Data::set('users', $users->all);
37 |
38 | $this->response->setJSON(Data::getStore());
39 | $this->response->send();
40 | }
41 |
42 | public function update() {
43 | $this->printBefore();
44 |
45 | // Find and Update user
46 | /** @var User $user */
47 | $user = (new UserModel())->find(2);
48 | $user->name = "Updated user (2)";
49 | $user->save();
50 |
51 | $this->printAfter();
52 |
53 | $this->response->setJSON(Data::getStore());
54 | $this->response->send();
55 | }
56 |
57 | public function delete() {
58 | $this->printBefore();
59 |
60 | // Find and Delete user
61 | $user = (new UserModel())->find(2);
62 | $user->delete();
63 |
64 | $this->printAfter();
65 |
66 | $this->response->setJSON(Data::getStore());
67 | $this->response->send();
68 | }
69 |
70 | public function save_relations() {
71 | $this->printBefore();
72 |
73 | // Insert user and relations
74 | $user = new User();
75 | $user->name = 'With all roles';
76 | $user->save();
77 | $roleModel = new RoleModel();
78 | $user->save($roleModel->find(1));
79 | $user->save($roleModel->find(2));
80 | $colorModel = new ColorModel();
81 | $user->save($colorModel->find(1));
82 |
83 | $this->printAfter();
84 |
85 | $this->response->setJSON(Data::getStore());
86 | $this->response->send();
87 | }
88 |
89 | public function delete_relations() {
90 | //$this->printBefore();
91 |
92 | // Find and delete relations
93 | $user = (new UserModel())->find(1);
94 | $roleModel = new RoleModel();
95 | $user->delete($roleModel->find(1));
96 | $colorModel = new ColorModel();
97 | $user->delete($colorModel->find(1));
98 | Data::lastQuery();
99 |
100 | $this->printAfter();
101 |
102 | $this->response->setJSON(Data::getStore());
103 | $this->response->send();
104 | }
105 |
106 | public function select_update() {
107 | //$this->printBefore();
108 |
109 | $userModel = new UserModel();
110 | $user = $userModel->find(1);
111 | $user->name = null;
112 | $userModel->save($user);
113 | Data::lastQuery();
114 |
115 | $this->printAfter();
116 |
117 | $this->response->setJSON(Data::getStore());
118 | $this->response->send();
119 | }
120 |
121 | public function group_by() {
122 | $users = (new UserModel())
123 | ->groupByRelated(RoleModel::class, 'name')
124 | ->find();
125 | Data::set('by role', $users->allToArray());
126 | Data::lastQuery();
127 | $users = (new UserModel())
128 | ->groupByRelated(ColorModel::class, 'name')
129 | ->find();
130 | Data::set('by color', $users->allToArray());
131 | Data::lastQuery();
132 |
133 | $this->response->setJSON(Data::getStore());
134 | $this->response->send();
135 | }
136 |
137 | public function having() {
138 | $users = (new UserModel())
139 | ->includeRelated(RoleModel::class)
140 | ->havingRelated(RoleModel::class, 'name', 'admin')
141 | ->find();
142 | Data::set('having admins', $users->allToArray());
143 | Data::lastQuery();
144 | $users = (new UserModel())
145 | ->includeRelated(ColorModel::class)
146 | ->havingRelated(ColorModel::class, 'name', 'green')
147 | ->find();
148 | Data::set('having green', $users->allToArray());
149 | Data::lastQuery();
150 |
151 | $this->response->setJSON(Data::getStore());
152 | $this->response->send();
153 | }
154 |
155 | public function order_by() {
156 | $users = (new UserModel())
157 | ->orderByRelated(ColorModel::class, 'name')
158 | ->find();
159 | Data::set('order by color asc', $users->allToArray());
160 | Data::lastQuery();
161 | $users = (new UserModel())
162 | ->orderByRelated(ColorModel::class, 'name', 'desc')
163 | ->find();
164 | Data::set('order by color desc', $users->allToArray());
165 | Data::lastQuery();
166 |
167 | $this->response->setJSON(Data::getStore());
168 | $this->response->send();
169 | }
170 |
171 |
172 |
173 |
174 |
175 |
176 | private function printBefore() {
177 | $users = (new UserModel())
178 | ->includeRelated(RoleModel::class)
179 | ->find();
180 | Data::set('users_before', $users->allToArray());
181 | }
182 |
183 | private function printAfter() {
184 | $users = (new UserModel())
185 | ->includeRelated(RoleModel::class)
186 | ->find();
187 | Data::set('users_after', $users->allToArray());
188 | }
189 |
190 | }
--------------------------------------------------------------------------------
/Examples/Controllers/Like.php:
--------------------------------------------------------------------------------
1 | cache->clean();
15 | Setup::run();
16 | }
17 |
18 | public function simple() {
19 | $model = new UserModel();
20 | $user = $model
21 | ->like('name', 'green')
22 | ->find();
23 | Data::set('user', $user->allToArray());
24 | Data::lastQuery();
25 |
26 | $this->response->setJSON(Data::getStore());
27 | $this->response->send();
28 | }
29 |
30 | public function not() {
31 | $model = new UserModel();
32 | $user = $model
33 | ->notLike('name', 'green')
34 | ->find();
35 | Data::set('user', $user->allToArray());
36 | Data::lastQuery();
37 |
38 | $this->response->setJSON(Data::getStore());
39 | $this->response->send();
40 | }
41 |
42 | public function or() {
43 | $model = new UserModel();
44 | $user = $model
45 | ->like('name', 'red')
46 | ->orLike('name', 'green')
47 | ->find();
48 | Data::set('user', $user->allToArray());
49 | Data::lastQuery();
50 |
51 | $this->response->setJSON(Data::getStore());
52 | $this->response->send();
53 | }
54 |
55 | public function or_not() {
56 | $model = new UserModel();
57 | $user = $model
58 | ->like('name', 'red')
59 | ->orNotLike('name', 'green')
60 | ->find();
61 | Data::set('user', $user->allToArray());
62 | Data::lastQuery();
63 |
64 | $this->response->setJSON(Data::getStore());
65 | $this->response->send();
66 | }
67 |
68 | public function related() {
69 | $model = new UserModel();
70 | $user = $model
71 | ->likeRelated(ColorModel::class, 'name', 'green')
72 | ->find();
73 | Data::set('user', $user->allToArray());
74 | Data::lastQuery();
75 |
76 | $this->response->setJSON(Data::getStore());
77 | $this->response->send();
78 | }
79 |
80 | public function not_related() {
81 | $model = new UserModel();
82 | $user = $model
83 | ->notLikeRelated(ColorModel::class, 'name', 'green')
84 | ->find();
85 | Data::set('user', $user->allToArray());
86 | Data::lastQuery();
87 |
88 | $this->response->setJSON(Data::getStore());
89 | $this->response->send();
90 | }
91 |
92 | public function or_related() {
93 | $model = new UserModel();
94 | $user = $model
95 | ->likeRelated(ColorModel::class, 'name', 'red')
96 | ->orLikeRelated(ColorModel::class, 'name', 'green')
97 | ->find();
98 | Data::set('user', $user->allToArray());
99 | Data::lastQuery();
100 |
101 | $this->response->setJSON(Data::getStore());
102 | $this->response->send();
103 | }
104 |
105 | public function or_not_related() {
106 | $model = new UserModel();
107 | $user = $model
108 | ->likeRelated(ColorModel::class, 'name', 'red')
109 | ->orNotLikeRelated(ColorModel::class, 'name', 'green')
110 | ->find();
111 | Data::set('user', $user->allToArray());
112 | Data::lastQuery();
113 |
114 | $this->response->setJSON(Data::getStore());
115 | $this->response->send();
116 | }
117 |
118 | }
--------------------------------------------------------------------------------
/Examples/Controllers/Related.php:
--------------------------------------------------------------------------------
1 | cache->clean();
17 | Setup::run();
18 | }
19 |
20 | public function simple() {
21 | $model = new UserModel();
22 | $model
23 | ->includeRelated(UserDetailModel::class)
24 | ->whereRelated(RoleModel::class, 'name', 'employee');
25 | $users = $model->find();
26 | Data::lastQuery();
27 |
28 | foreach($users as $user) {
29 | $user->color->find();
30 | $user->roles->find();
31 | }
32 |
33 | Data::set('user', $users->allToArray());
34 |
35 | $this->response->setJSON(Data::getStore());
36 | $this->response->send();
37 | }
38 |
39 | public function in() {
40 | $model = new UserModel();
41 | $user = $model
42 | ->whereInRelated(RoleModel::class, 'name', ['admin', 'employee'])
43 | ->find();
44 | Data::set('user', $user->allToArray());
45 | Data::lastQuery();
46 |
47 | $this->response->setJSON(Data::getStore());
48 | $this->response->send();
49 | }
50 |
51 | public function not_in() {
52 | $model = new UserModel();
53 | $user = $model
54 | ->whereNotInRelated(RoleModel::class, 'name', ['admin', 'employee'])
55 | ->find();
56 | Data::set('user', $user->allToArray());
57 | Data::lastQuery();
58 |
59 | $this->response->setJSON(Data::getStore());
60 | $this->response->send();
61 | }
62 |
63 | public function or() {
64 | $model = new UserModel();
65 | $user = $model
66 | ->whereRelated(ColorModel::class, 'name', 'green')
67 | ->orWhereRelated(ColorModel::class, 'name', 'blue')
68 | ->find();
69 | Data::set('user', $user->allToArray());
70 | Data::lastQuery();
71 |
72 | $this->response->setJSON(Data::getStore());
73 | $this->response->send();
74 | }
75 |
76 | public function or_in() {
77 | $model = new UserModel();
78 | $user = $model
79 | ->whereRelated(ColorModel::class, 'name', 'green')
80 | ->orWhereInRelated(ColorModel::class, 'name', ['blue', 'red'])
81 | ->find();
82 | Data::set('user', $user->allToArray());
83 | Data::lastQuery();
84 |
85 | $this->response->setJSON(Data::getStore());
86 | $this->response->send();
87 | }
88 |
89 | public function or_not_in() {
90 | $model = new UserModel();
91 | $user = $model
92 | ->whereRelated(ColorModel::class, 'name', 'green')
93 | ->orWhereNotInRelated(ColorModel::class, 'name', ['blue'])
94 | ->find();
95 | Data::set('user', $user->allToArray());
96 | Data::lastQuery();
97 |
98 | $this->response->setJSON(Data::getStore());
99 | $this->response->send();
100 | }
101 |
102 | public function between() {
103 | $model = new UserModel();
104 | $user = $model
105 | ->whereBetweenRelated(ColorModel::class, 'id', 2, 3)
106 | ->find();
107 | Data::set('user', $user->allToArray());
108 | Data::lastQuery();
109 |
110 | $this->response->setJSON(Data::getStore());
111 | $this->response->send();
112 | }
113 |
114 | public function not_between() {
115 | $model = new UserModel();
116 | $user = $model
117 | ->whereNotBetweenRelated(ColorModel::class, 'id', 2, 3)
118 | ->find();
119 | Data::set('user', $user->allToArray());
120 | Data::lastQuery();
121 |
122 | $this->response->setJSON(Data::getStore());
123 | $this->response->send();
124 | }
125 |
126 | }
--------------------------------------------------------------------------------
/Examples/Controllers/SubQuery.php:
--------------------------------------------------------------------------------
1 | cache->clean();
16 | Setup::run();
17 | }
18 |
19 | public function select() {
20 | $subQuery = (new RoleModel())
21 | ->select('COUNT(*)', true, false)
22 | ->where('name', '${parent}.name', false);
23 |
24 | $model = new UserModel();
25 | $user = $model
26 | ->select('*')
27 | ->selectSubQuery($subQuery, 'admin_roles')
28 | ->find();
29 | Data::set('user', $user->allToArray());
30 | Data::lastQuery();
31 |
32 | $this->response->setJSON(Data::getStore());
33 | $this->response->send();
34 | }
35 |
36 | public function where() {
37 | $selectQuery = (new RoleModel())
38 | ->select('COUNT(*)')
39 | ->whereRelated(UserModel::class, 'id', '${parent}.id', false);
40 |
41 | $whereQuery = (new RoleModel())
42 | ->select('COUNT(*) as count')
43 | ->whereRelated(UserModel::class, 'id', '${parent}.id', true)
44 | ->having('count >', '1');
45 |
46 | $model = new UserModel();
47 | $user = $model
48 | //->selectSubQuery($selectQuery, 'roles_count')
49 | ->whereSubQuery($whereQuery)
50 | ->find();
51 | //Data::set('user', $user->allToArray());
52 | Data::lastQuery();
53 |
54 | $this->response->setJSON(Data::getStore());
55 | $this->response->send();
56 | }
57 |
58 | }
--------------------------------------------------------------------------------
/Examples/Controllers/Where.php:
--------------------------------------------------------------------------------
1 | cache->clean();
14 | Setup::run();
15 | }
16 |
17 | public function simple() {
18 | $model = new UserModel();
19 | $user = $model
20 | ->where('id', 2)
21 | ->find();
22 | Data::set('user', $user->toArray());
23 | Data::lastQuery();
24 |
25 | $this->response->setJSON(Data::getStore());
26 | $this->response->send();
27 | }
28 |
29 | public function in() {
30 | $model = new UserModel();
31 | $user = $model
32 | ->whereIn('id', [2, 3])
33 | ->find();
34 | Data::set('user', $user->allToArray());
35 | Data::lastQuery();
36 |
37 | $this->response->setJSON(Data::getStore());
38 | $this->response->send();
39 | }
40 |
41 | public function not_in() {
42 | $model = new UserModel();
43 | $user = $model
44 | ->whereNotIn('id', [2, 3])
45 | ->find();
46 | Data::set('user', $user->allToArray());
47 | Data::lastQuery();
48 |
49 | $this->response->setJSON(Data::getStore());
50 | $this->response->send();
51 | }
52 |
53 | public function or() {
54 | $model = new UserModel();
55 | $user = $model
56 | ->where('id', 2)
57 | ->orWhere('id', 3)
58 | ->find();
59 | Data::set('user', $user->allToArray());
60 | Data::lastQuery();
61 |
62 | $this->response->setJSON(Data::getStore());
63 | $this->response->send();
64 | }
65 |
66 | public function or_in() {
67 | $model = new UserModel();
68 | $user = $model
69 | ->where('id', 2)
70 | ->orWhereIn('id', [3, 4])
71 | ->find();
72 | Data::set('user', $user->allToArray());
73 | Data::lastQuery();
74 |
75 | $this->response->setJSON(Data::getStore());
76 | $this->response->send();
77 | }
78 |
79 | public function or_not_in() {
80 | $model = new UserModel();
81 | $user = $model
82 | ->where('id', 2)
83 | ->orWhereNotIn('id', [3, 4])
84 | ->find();
85 | Data::set('user', $user->allToArray());
86 | Data::lastQuery();
87 |
88 | $this->response->setJSON(Data::getStore());
89 | $this->response->send();
90 | }
91 |
92 | public function between() {
93 | $model = new UserModel();
94 | $user = $model
95 | ->whereBetween('id', 2, 4)
96 | ->find();
97 | Data::set('user', $user->allToArray());
98 | Data::lastQuery();
99 |
100 | $this->response->setJSON(Data::getStore());
101 | $this->response->send();
102 | }
103 |
104 | public function not_between() {
105 | $model = new UserModel();
106 | $user = $model
107 | ->whereNotBetween('id', 2, 4)
108 | ->find();
109 | Data::set('user', $user->allToArray());
110 | Data::lastQuery();
111 |
112 | $this->response->setJSON(Data::getStore());
113 | $this->response->send();
114 | }
115 |
116 | public function or_between() {
117 | $model = new UserModel();
118 | $user = $model
119 | ->where('id', 1)
120 | ->orWhereBetween('id', 2, 4)
121 | ->find();
122 | Data::set('user', $user->allToArray());
123 | Data::lastQuery();
124 |
125 | $this->response->setJSON(Data::getStore());
126 | $this->response->send();
127 | }
128 |
129 | public function or_not_between() {
130 | $model = new UserModel();
131 | $user = $model
132 | ->where('id', 1)
133 | ->orWhereNotBetween('id', 2, 4)
134 | ->find();
135 | Data::set('user', $user->allToArray());
136 | Data::lastQuery();
137 |
138 | $this->response->setJSON(Data::getStore());
139 | $this->response->send();
140 | }
141 |
142 | }
--------------------------------------------------------------------------------
/Examples/Entities/Color.php:
--------------------------------------------------------------------------------
1 | name = 'admin';
19 | $admin->save();
20 | $employee = new Role();
21 | $employee->name = 'employee';
22 | $employee->save();
23 |
24 | $green = new Color();
25 | $green->name = 'green';
26 | $green->save();
27 | $blue = new Color();
28 | $blue->name = 'blue';
29 | $blue->save();
30 | $red = new Color();
31 | $red->name = 'red';
32 | $red->save();
33 |
34 | $detail1 = new UserDetail();
35 | $detail1->address = "User 1 street";
36 | $detail1->save();
37 | $detail2 = new UserDetail();
38 | $detail2->address = "User 2 street";
39 | $detail2->save();
40 | $detail3 = new UserDetail();
41 | $detail3->address = "User 3 street";
42 | $detail3->save();
43 | $detail4 = new UserDetail();
44 | $detail4->address = "User 4 street";
45 | $detail4->save();
46 | $detail5 = new UserDetail();
47 | $detail5->address = "User 5 street";
48 | $detail5->save();
49 | $detail6 = new UserDetail();
50 | $detail6->address = "User 6 street";
51 | $detail6->save();
52 |
53 | $user = new User();
54 | $user->name = "Green admin";
55 | $user->save();
56 | $user->save($green);
57 | $user->save($admin);
58 | $user->save($detail1);
59 | $detail1->save($user);
60 | $user = new User();
61 | $user->name = "Blue admin";
62 | $user->save();
63 | $user->save($blue);
64 | $user->save($admin);
65 | $user->save($detail2);
66 | $user = new User();
67 | $user->name = "Red admin";
68 | $user->save();
69 | $user->save($red);
70 | $user->save($admin);
71 | $user->save($detail3);
72 | $user = new User();
73 | $user->name = "Green employee";
74 | $user->save();
75 | $user->save($green);
76 | $user->save($employee);
77 | $user->save($detail4);
78 | $user = new User();
79 | $user->name = "Blue employee";
80 | $user->save();
81 | $user->save($blue);
82 | $user->save($employee);
83 | $user->save($detail5);
84 | $user = new User();
85 | $user->name = "Red employee";
86 | $user->save();
87 | $user->save($red);
88 | $user->save($employee);
89 | $user->save($detail6);
90 | }
91 |
92 | private static function addTables() {
93 | $forge = Database::forge();
94 | $forge->dropTable('users', true);
95 | $forge->dropTable('roles', true);
96 | $forge->dropTable('roles_users', true);
97 | $forge->dropTable('colors', true);
98 | $forge->dropTable('user_details', true);
99 |
100 | $forge->addField([
101 | 'id' => [
102 | 'type' => 'INT',
103 | 'unsigned' => true,
104 | 'auto_increment' => true,
105 | ],
106 | 'name' => [
107 | 'type' => 'VARCHAR',
108 | 'constraint' => '255',
109 | ],
110 | 'color_id' => [
111 | 'type' => 'INT',
112 | 'unsigned' => true
113 | ],
114 | 'user_detail_id' => [
115 | 'type' => 'INT',
116 | 'unsigned' => true
117 | ]
118 | ]);
119 | $forge->addPrimaryKey('id');
120 | $forge->createTable('users');
121 |
122 |
123 | $forge->addField([
124 | 'id' => [
125 | 'type' => 'INT',
126 | 'unsigned' => true,
127 | 'auto_increment' => true,
128 | ],
129 | 'name' => [
130 | 'type' => 'VARCHAR',
131 | 'constraint' => '255',
132 | ]
133 | ]);
134 | $forge->addPrimaryKey('id');
135 | $forge->createTable('roles');
136 |
137 |
138 | $forge->addField([
139 | 'id' => [
140 | 'type' => 'INT',
141 | 'unsigned' => true,
142 | 'auto_increment' => true,
143 | ],
144 | 'role_id' => [
145 | 'type' => 'INT',
146 | 'unsigned' => true,
147 | ],
148 | 'user_id' => [
149 | 'type' => 'INT',
150 | 'unsigned' => true,
151 | ]
152 | ]);
153 | $forge->addPrimaryKey('id');
154 | $forge->createTable('roles_users');
155 |
156 |
157 | $forge->addField([
158 | 'id' => [
159 | 'type' => 'INT',
160 | 'unsigned' => true,
161 | 'auto_increment' => true,
162 | ],
163 | 'name' => [
164 | 'type' => 'VARCHAR',
165 | 'constraint' => '255',
166 | ]
167 | ]);
168 | $forge->addPrimaryKey('id');
169 | $forge->createTable('colors');
170 |
171 |
172 | $forge->addField([
173 | 'id' => [
174 | 'type' => 'INT',
175 | 'unsigned' => true,
176 | 'auto_increment' => true,
177 | ],
178 | 'address' => [
179 | 'type' => 'VARCHAR',
180 | 'constraint' => '255',
181 | ]
182 | ]);
183 | $forge->addPrimaryKey('id');
184 | $forge->createTable('user_details');
185 | }
186 |
187 | }
--------------------------------------------------------------------------------
/Extensions/Database/BaseBuilder.php:
--------------------------------------------------------------------------------
1 | bindsKeyCount;
7 | }
8 |
9 | /**
10 | * @param mixed $key
11 | * @param null $value
12 | * @param bool $escape
13 | * @return \CodeIgniter\Database\BaseBuilder|BaseBuilder
14 | */
15 | public function where($key, $value = null, bool $escape = null) {
16 | return parent::where($key, $value, $escape);
17 | }
18 |
19 | /**
20 | * @param string $sql
21 | * @param array $bindKeyCount
22 | * @param array $binds
23 | * @return string
24 | */
25 | public function bindMerging($sql, $bindKeyCount, $binds) {
26 | // Ensure my keys are part of the bindsKeyCount
27 | // If not, CI4 will overwrite values https://github.com/codeigniter4/CodeIgniter4/issues/7049
28 | foreach ($binds as $key => $value) {
29 | if (!isset($bindKeyCount[$key])) {
30 | $bindKeyCount[$key] = 1;
31 | }
32 | }
33 |
34 | $this->bindsKeyCount = array_merge($bindKeyCount, $this->bindsKeyCount);
35 | foreach($binds as $key => [$value, $escape]) {
36 | $newKey = $this->setBind($key, $value, $escape);
37 | $sql = str_replace(":$key:", ":$newKey:", $sql);
38 | }
39 | return $sql;
40 | }
41 |
42 | /**
43 | * @param string $search
44 | * @param string $replace
45 | * @return string
46 | */
47 | public function bindReplace($search, $replace) {
48 | foreach($this->getBinds() as $key => [$value, $escape]) {
49 | $this->binds[$key] = [str_replace($search, $replace, $value), $escape];
50 | }
51 | }
52 |
53 | public function compileSelect_(): string {
54 | return parent::compileSelect();
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/Extensions/Database/MySQLi/Builder.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view
9 | * the LICENSE file that was distributed with this source code.
10 | */
11 |
12 | namespace OrmExtension\Extensions\Database\MySQLi;
13 |
14 | use OrmExtension\Extensions\Database\BaseBuilder;
15 |
16 | /**
17 | * Builder for MySQLi
18 | */
19 | class Builder extends BaseBuilder
20 | {
21 | /**
22 | * Identifier escape character
23 | *
24 | * @var string
25 | */
26 | protected $escapeChar = '`';
27 |
28 | /**
29 | * Specifies which sql statements
30 | * support the ignore option.
31 | *
32 | * @var array
33 | */
34 | protected $supportedIgnoreStatements = [
35 | 'update' => 'IGNORE',
36 | 'insert' => 'IGNORE',
37 | 'delete' => 'IGNORE',
38 | ];
39 |
40 | /**
41 | * FROM tables
42 | *
43 | * Groups tables in FROM clauses if needed, so there is no confusion
44 | * about operator precedence.
45 | *
46 | * Note: This is only used (and overridden) by MySQL.
47 | */
48 | protected function _fromTables(): string
49 | {
50 | if (! empty($this->QBJoin) && count($this->QBFrom) > 1) {
51 | return '(' . implode(', ', $this->QBFrom) . ')';
52 | }
53 |
54 | return implode(', ', $this->QBFrom);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Extensions/Database/MySQLi/Connection.php:
--------------------------------------------------------------------------------
1 | $value)
41 | {
42 | $this->__set($key, $value);
43 | }
44 |
45 | // Fill relations
46 | if ($data) {
47 |
48 | $relations = ModelDefinitionCache::getRelations($this->getSimpleName());
49 | /** @var RelationDef[] $relationFields */
50 | $relationFields = [];
51 | foreach ($relations as $relation) {
52 | $relationFields[$relation->getSimpleName()] = $relation;
53 | }
54 |
55 | $fields = $this->getTableFields();
56 | foreach ($data as $key => $value) {
57 | $method = 'set' . str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $key)));
58 |
59 | if (method_exists($this, $method)) {
60 | $this->$method($value);
61 | } else { // Does not require property to exist, properties are managed by OrmExtension from database columns
62 | if (in_array($key, $fields)) { // Does require field to exists
63 | $this->$key = $value;
64 | } else if (isset($relationFields[singular($key)])) { // Auto-populate relation
65 | $relation = $relationFields[singular($key)];
66 | switch ($relation->getType()) {
67 | case RelationDef::HasOne:
68 | $entityName = $relation->getEntityName();
69 | $this->$key = new $entityName($value);
70 | break;
71 | case RelationDef::HasMany:
72 | $entityName = $relation->getEntityName();
73 | $this->{$key} = new $entityName();
74 | /** @var Entity $relationMany */
75 | $relationMany = $this->{$key};
76 | foreach ($value as $v) {
77 | $relationMany->add(new $entityName($v));
78 | }
79 | break;
80 | }
81 | }
82 | }
83 | }
84 | }
85 |
86 | return $this;
87 | }
88 |
89 | private function getSimpleName() {
90 | return substr(strrchr(get_class($this), '\\'), 1);
91 | }
92 |
93 | private $_model;
94 |
95 | /**
96 | * @return Model|QueryBuilderInterface
97 | */
98 | public function _getModel(): Model {
99 | if (!$this->_model) {
100 | foreach (OrmExtension::$modelNamespace as $modelNamespace) {
101 | $name = $modelNamespace . $this->getSimpleName() . 'Model';
102 | if (class_exists($name)) {
103 | $this->_model = new $name();
104 | break;
105 | }
106 | }
107 | }
108 | return $this->_model;
109 | }
110 |
111 | /**
112 | * Override System/Entity, cause of error "Undefined variable: result"
113 | * @param string $key
114 | * @return mixed|null
115 | */
116 | public function __get(string $key) {
117 | helper('inflector');
118 | $result = parent::__get($key);
119 |
120 | if (is_null($result) && $key != $this->_getModel()->getPrimaryKey()) {
121 | // Check for relation
122 | foreach ($this->_getModel()->getRelations() as $relation) {
123 | if ($relation->getSimpleName() == singular($key)) {
124 | $className = $relation->getEntityName();
125 | $this->{$key} = new $className();
126 | /** @var Entity $entity */
127 | $entity = $this->attributes[$key];
128 | $entityModel = $entity->_getModel();
129 |
130 | // Data::debug(get_class($this), $relation->getName(), $relation->getJoinSelfAs(), $this->{$relation->getJoinOtherAs()});
131 | // $entity->_getModel()->whereRelated($relation->getOtherField(), $this->_getModel()->getPrimaryKey(), $this->{$this->_getModel()->getPrimaryKey()});
132 | // $entity->_getModel()->whereRelated($relation->getOtherField(), $relation->getJoinSelfAs(), $this->{$this->_getModel()->getPrimaryKey()});
133 |
134 | [$lastJoinTable, $lastJoinModel] = $entityModel->handleWhereRelated($relation->getOtherField());
135 |
136 | $field = null;
137 | $value = null;
138 | $relationShipTableFields = $lastJoinModel->getTableFields();
139 |
140 | $joinSelfAsGuess = $relation->getJoinSelfAsGuess();
141 | if (in_array($joinSelfAsGuess[0], $relationShipTableFields)) {
142 | $field = $joinSelfAsGuess[0];
143 | $value = $this->{$field};
144 | } else if (in_array($joinSelfAsGuess[1], $relationShipTableFields)) {
145 | $field = $joinSelfAsGuess[1];
146 | $value = $this->{$field};
147 | } else {
148 | $joinOtherAsGuess = $relation->getJoinOtherAsGuess();
149 | if (in_array($joinOtherAsGuess[0], $relationShipTableFields)) {
150 | $value = $this->{$joinOtherAsGuess[0]};
151 | } else if (in_array($joinOtherAsGuess[1], $relationShipTableFields)) {
152 | $value = $this->{$joinOtherAsGuess[1]};
153 | }
154 | }
155 |
156 | if (is_null($field) || is_null($value)) {
157 | // Undefined relationship table. We have to relay on relationship definitions
158 | $field = $relation->getJoinSelfAs();
159 | $value = $this->{$relation->getJoinOtherAs()};
160 | }
161 |
162 | $entity
163 | ->_getModel()
164 | ->whereRelated(
165 | $relation->getOtherField(),
166 | $field,
167 | $value
168 | );
169 | $result = $entity;
170 | break;
171 | }
172 | }
173 | }
174 |
175 | return $result;
176 | }
177 |
178 | /**
179 | * @param string $key
180 | * @param mixed $value
181 | * @return \CodeIgniter\Entity\Entity|Entity
182 | */
183 | public function __set(string $key, $value = null) {
184 | parent::__set($key, $value);
185 | return $this;
186 | }
187 |
188 | /**
189 | * @return Entity
190 | */
191 | public function first() {
192 | return isset($this->all) ? reset($this->all) : $this;
193 | }
194 |
195 | public function setAttributes(array $data) {
196 | // Type casting
197 | $fieldData = ModelDefinitionCache::getFieldData($this->getSimpleName());
198 | $fieldName2Type = [];
199 | foreach ($fieldData as $field) $fieldName2Type[$field->name] = $field->type;
200 |
201 | foreach ($data as $field => $value) {
202 | if (isset($fieldName2Type[$field])) {
203 | switch ($fieldName2Type[$field]) {
204 | case 'int':
205 | $data[$field] = is_null($value) ? null : (int)$value;
206 | break;
207 | case 'float':
208 | case 'double':
209 | case 'decimal':
210 | $data[$field] = is_null($value) ? null : (double)$value;
211 | break;
212 | case 'tinyint':
213 | $data[$field] = (bool)$value;
214 | break;
215 | case 'datetime':
216 | $data[$field] = $value ? date('Y-m-d H:i:s', strtotime($value)) : null;
217 | break;
218 | default:
219 | $data[$field] = $value;
220 | }
221 | }
222 | }
223 |
224 | return parent::setAttributes($data);
225 | }
226 |
227 | public function getOriginal($key = null) {
228 | if ($key) {
229 | return $this->original[$key];
230 | } else {
231 | return $this->original;
232 | }
233 | }
234 |
235 | public function hasChanged(string $key = null, $checkRelations = false): bool {
236 | if ($key === null && $checkRelations == false) {
237 | // CI4 will check original against attributes. Attributes holds everything, including relations
238 | // Remove relations before checking
239 | $tableFields = [];
240 | foreach ($this->getTableFields() as $tableField) {
241 | $tableFields[$tableField] = $tableField;
242 | }
243 | $original = array_intersect_key($this->original, $tableFields);
244 | $attributes = array_intersect_key($this->attributes, $tableFields);
245 | return $original !== $attributes;
246 | } else
247 | return parent::hasChanged($key);
248 | }
249 |
250 | }
251 |
--------------------------------------------------------------------------------
/Extensions/Model.php:
--------------------------------------------------------------------------------
1 | setCodeIgniterModelStuff();
41 | parent::__construct($db, $validation);
42 | }
43 |
44 | //
45 |
46 | use QueryBuilder;
47 |
48 | public $hasOne = [];
49 | public $hasMany = [];
50 |
51 | protected function _getModel(): Model {
52 | return $this;
53 | }
54 |
55 | protected $builder;
56 | protected function _getBuilder(): BaseBuilder {
57 | return $this->builder;
58 | }
59 |
60 | public function _setBuilder(BaseBuilder $builder) {
61 | $this->builder = $builder;
62 | }
63 |
64 | private function appendTable(&$key) {
65 | $key = "{$this->getTableName()}.{$key}";
66 | }
67 |
68 | //
69 |
70 | private $selecting = false;
71 | public function isSelecting() {
72 | return $this->selecting;
73 | }
74 | public function setSelecting($selecting) {
75 | $this->selecting = $selecting;
76 | }
77 |
78 | /**
79 | * @param string|array $select
80 | * @param boolean $escape
81 | * @param boolean $appendTable
82 | * @return BaseBuilder|Model
83 | */
84 | public function select($select = '*', bool $escape = null, $appendTable = true): Model {
85 | $this->selecting = true;
86 | if($appendTable) {
87 | if(strpos($select, '.') === false) {
88 | $selects = explode(',', $select);
89 | foreach($selects as &$select) {
90 | $select = trim($select);
91 | $this->appendTable($select);
92 | }
93 | $select = implode(', ', $selects);
94 | }
95 | }
96 | return parent::select($select, $escape);
97 | }
98 |
99 | //
100 |
101 | //
102 |
103 | /**
104 | * @param mixed $key
105 | * @param mixed $value
106 | * @param boolean $escape
107 | * @param bool $appendTable
108 | * @return BaseBuilder|Model
109 | */
110 | public function where($key, $value = null, bool $escape = null, $appendTable = true) {
111 | if($appendTable) $this->appendTable($key);
112 | return parent::where($key, $value, $escape);
113 | }
114 |
115 | /**
116 | * @param $key
117 | * @param int $min
118 | * @param int $max
119 | * @param null $escape
120 | * @param bool $appendTable
121 | * @return BaseBuilder|Model
122 | */
123 | public function whereBetween($key, $min = 0, $max = 0, $escape = null, $appendTable = true) {
124 | if($appendTable) $this->appendTable($key);
125 | return parent::where("`$key` BETWEEN \"{$min}\" AND \"{$max}\"", null, $escape);
126 | }
127 |
128 | /**
129 | * @param $key
130 | * @param int $min
131 | * @param int $max
132 | * @param bool $escape
133 | * @param bool $appendTable
134 | * @return BaseBuilder|Model
135 | */
136 | public function whereNotBetween($key, $min = 0, $max = 0, $escape = false, $appendTable = true) {
137 | if($appendTable) $this->appendTable($key);
138 | return parent::where("$key NOT BETWEEN \"{$min}\" AND \"{$max}\"", null, $escape);
139 | }
140 |
141 | /**
142 | * @param string $key
143 | * @param mixed $values
144 | * @param boolean $escape
145 | * @param boolean $appendTable
146 | * @return BaseBuilder|Model
147 | */
148 | public function whereIn(string $key = null, $values = null, bool $escape = null, $appendTable = true) {
149 | if($appendTable) $this->appendTable($key);
150 | if($values instanceof Entity) {
151 | $ids = [];
152 | foreach($values as $value) $ids[] = $value->{$this->getPrimaryKey()};
153 | $values = $ids;
154 | }
155 | if(is_string($values)) $values = [$values];
156 | return parent::whereIn($key, $values, $escape);
157 | }
158 |
159 | /**
160 | * @param string $key
161 | * @param mixed $values
162 | * @param boolean $escape
163 | * @param boolean $appendTable
164 | * @return BaseBuilder|Model
165 | */
166 | public function whereNotIn(string $key = null, $values = null, bool $escape = null, $appendTable = true) {
167 | if($appendTable) $this->appendTable($key);
168 | if($values instanceof Entity) {
169 | $ids = [];
170 | foreach($values as $value) $ids[] = $value->{$this->getPrimaryKey()};
171 | $values = $ids;
172 | }
173 | return parent::whereNotIn($key, $values, $escape);
174 | }
175 |
176 | //
177 |
178 | //
179 |
180 | /**
181 | * @param mixed $key
182 | * @param mixed $value
183 | * @param boolean $escape
184 | * @param boolean $appendTable
185 | * @return BaseBuilder|Model
186 | */
187 | public function orWhere($key, $value = null, bool $escape = null, $appendTable = true) {
188 | if($appendTable) $this->appendTable($key);
189 | return parent::orWhere($key, $value, $escape);
190 | }
191 |
192 | /**
193 | * @param $key
194 | * @param int $min
195 | * @param int $max
196 | * @param null $escape
197 | * @param bool $appendTable
198 | * @return BaseBuilder|Model
199 | */
200 | public function orWhereBetween($key, $min = 0, $max = 0, $escape = null, $appendTable = true) {
201 | if($appendTable) $this->appendTable($key);
202 | return parent::orWhere("`$key` BETWEEN {$min} AND {$max}", null, $escape);
203 | }
204 |
205 | /**
206 | * @param $key
207 | * @param int $min
208 | * @param int $max
209 | * @param bool $escape
210 | * @param bool $appendTable
211 | * @return BaseBuilder|Model
212 | */
213 | public function orWhereNotBetween($key, $min = 0, $max = 0, $escape = false, $appendTable = true) {
214 | if($appendTable) $this->appendTable($key);
215 | return parent::orWhere("$key NOT BETWEEN {$min} AND {$max}", null, $escape);
216 | }
217 |
218 | /**
219 | * @param string $key
220 | * @param array $values
221 | * @param boolean $escape
222 | * @param boolean $appendTable
223 | * @return BaseBuilder|Model
224 | */
225 | public function orWhereIn(string $key = null, array $values = null, bool $escape = null, $appendTable = true) {
226 | if($appendTable) $this->appendTable($key);
227 | return parent::orWhereIn($key, $values, $escape);
228 | }
229 |
230 | /**
231 | * @param string $key
232 | * @param array $values
233 | * @param boolean $escape
234 | * @param boolean $appendTable
235 | * @return BaseBuilder|Model
236 | */
237 | public function orWhereNotIn(string $key = null, array $values = null, bool $escape = null, $appendTable = true) {
238 | if($appendTable) $this->appendTable($key);
239 | return parent::orWhereNotIn($key, $values, $escape);
240 | }
241 |
242 |
243 | //
244 |
245 | //
246 |
247 | /**
248 | * @param mixed $field
249 | * @param string $match
250 | * @param string $side
251 | * @param boolean $escape
252 | * @param boolean $insensitiveSearch
253 | * @param bool $appendTable
254 | * @return BaseBuilder|Model
255 | */
256 | public function like($field, string $match = '', string $side = 'both', bool $escape = null, bool $insensitiveSearch = false, $appendTable = true) {
257 | if($appendTable) $this->appendTable($field);
258 | return parent::like($field, $match, $side, $escape, $insensitiveSearch);
259 | }
260 |
261 | /**
262 | * @param mixed $field
263 | * @param string $match
264 | * @param string $side
265 | * @param boolean $escape
266 | * @param boolean $insensitiveSearch
267 | * @param bool $appendTable
268 | * @return BaseBuilder|Model
269 | */
270 | public function notLike($field, string $match = '', string $side = 'both', bool $escape = null, bool $insensitiveSearch = false, $appendTable = true) {
271 | if($appendTable) $this->appendTable($field);
272 | return parent::notLike($field, $match, $side, $escape, $insensitiveSearch);
273 | }
274 |
275 | /**
276 | * @param mixed $field
277 | * @param string $match
278 | * @param string $side
279 | * @param boolean $escape
280 | * @param boolean $insensitiveSearch
281 | * @param bool $appendTable
282 | * @return BaseBuilder|Model
283 | */
284 | public function orLike($field, string $match = '', string $side = 'both', bool $escape = null, bool $insensitiveSearch = false, $appendTable = true) {
285 | if($appendTable) $this->appendTable($field);
286 | return parent::orLike($field, $match, $side, $escape, $insensitiveSearch);
287 | }
288 |
289 | /**
290 | * @param mixed $field
291 | * @param string $match
292 | * @param string $side
293 | * @param boolean $escape
294 | * @param boolean $insensitiveSearch
295 | * @param bool $appendTable
296 | * @return BaseBuilder|Model
297 | */
298 | public function orNotLike($field, string $match = '', string $side = 'both', bool $escape = null, bool $insensitiveSearch = false, $appendTable = true) {
299 | if($appendTable) $this->appendTable($field);
300 | return parent::orNotLike($field, $match, $side, $escape, $insensitiveSearch);
301 | }
302 |
303 | //
304 |
305 | //
306 |
307 | /**
308 | * @param string $by
309 | * @param boolean $escape
310 | * @param boolean $appendTable
311 | * @return BaseBuilder|Model
312 | */
313 | public function groupBy($by, bool $escape = null, $appendTable = true) {
314 | if($appendTable) $this->appendTable($by);
315 | return parent::groupBy($by, $escape);
316 | }
317 |
318 | /**
319 | * @param string|array $key
320 | * @param mixed $value
321 | * @param boolean $escape
322 | * @param boolean $appendTable
323 | * @return BaseBuilder|Model
324 | */
325 | public function having($key, $value = null, bool $escape = null, $appendTable = true) {
326 | if($appendTable) $this->appendTable($key);
327 | return parent::having($key, $value, $escape);
328 | }
329 |
330 | /**
331 | * @param string|array $key
332 | * @param mixed $value
333 | * @param boolean $escape
334 | * @param boolean $appendTable
335 | * @return BaseBuilder|Model
336 | */
337 | public function orHaving($key, $value = null, bool $escape = null, $appendTable = true) {
338 | if($appendTable) $this->appendTable($key);
339 | return parent::orHaving($key, $value, $escape);
340 | }
341 |
342 | /**
343 | * @param string $orderBy
344 | * @param string $direction ASC, DESC, RANDOM or IS NULL
345 | * @param boolean $escape
346 | * @param boolean $appendTable
347 | * @return BaseBuilder|Model
348 | */
349 | public function orderBy(string $orderBy, string $direction = '', bool $escape = null, $appendTable = true) {
350 | if($appendTable) $this->appendTable($orderBy);
351 |
352 | if(is_null($direction) || $direction == 'null') {
353 | return $this->orderByHack($orderBy, 'IS NULL', $escape);
354 | } else if(in_array($direction, ['null asc', 'null desc'])) {
355 | [$_, $nullDirection] = explode(' ', $direction);
356 | return $this->orderByHack($orderBy, "IS NULL $nullDirection", $escape);
357 | }
358 |
359 | return parent::orderBy($orderBy, $direction, $escape);
360 | }
361 |
362 | private function orderByHack($orderby, $direction = '', $escape = null) {
363 | $direction = strtoupper(trim($direction));
364 |
365 | if(empty($orderby))
366 | return $this;
367 | else if ($direction !== '') {
368 | $direction = in_array($direction, ['IS NULL', 'IS NULL DESC', 'IS NULL ASC'], true) ? ' ' . $direction : '';
369 | }
370 |
371 | is_bool($escape) || $escape = $this->db->protectIdentifiers;
372 |
373 | if ($escape === false)
374 | {
375 | $qb_orderby[] = [
376 | 'field' => $orderby,
377 | 'direction' => $direction,
378 | 'escape' => false,
379 | ];
380 | }
381 | else
382 | {
383 | $qb_orderby = [];
384 | foreach (explode(',', $orderby) as $field)
385 | {
386 | $qb_orderby[] = ($direction === '' &&
387 | preg_match('/\s+(ASC|DESC)$/i', rtrim($field), $match, PREG_OFFSET_CAPTURE)) ? [
388 | 'field' => ltrim(substr($field, 0, $match[0][1])),
389 | 'direction' => ' ' . $match[1][0],
390 | 'escape' => true,
391 | ] : [
392 | 'field' => trim($field),
393 | 'direction' => $direction,
394 | 'escape' => true,
395 | ];
396 | }
397 | }
398 |
399 | $this->builder()->QBOrderBy = array_merge($this->builder()->QBOrderBy, $qb_orderby);
400 |
401 | return $this;
402 | }
403 |
404 | //
405 |
406 | //
407 |
408 | /**
409 | * @param string $not
410 | * @param string $type
411 | * @return BaseBuilder|Model
412 | */
413 | public function groupStart(string $not = '', string $type = 'AND ') {
414 | return parent::groupStart($not, $type);
415 | }
416 |
417 | /**
418 | * @return BaseBuilder|Model
419 | */
420 | public function orGroupStart() {
421 | return parent::orGroupStart();
422 | }
423 |
424 | /**
425 | * @return BaseBuilder|Model
426 | */
427 | public function groupEnd() {
428 | return parent::groupEnd();
429 | }
430 |
431 | //
432 |
433 | /**
434 | * @param null $id
435 | * @return array|object|null|Entity
436 | */
437 | public function find($id = null) {
438 | $result = parent::find($id);
439 | // Clear
440 | $this->setSelecting(false);
441 | return $result;
442 | }
443 |
444 |
445 | //
446 |
447 |
448 | //
449 |
450 | use ResultBuilder;
451 |
452 | /**
453 | * Called after find
454 | * @param array $data
455 | * @return mixed
456 | */
457 | protected function handleResult(array $data) {
458 | if(empty($data['data'])) {
459 | $data['data'] = new $this->returnType();
460 | return $data;
461 | }
462 | $result = $data['data'];
463 |
464 | if($result instanceof Entity) {
465 | $result = [$result];
466 | }
467 |
468 | $this->arrangeIncludedRelations($result);
469 |
470 | // Convert from array to single Entity
471 | if(is_array($result) && count($result) > 0) {
472 | $first = clone $result[0];
473 | foreach($result as $item) {
474 | $first->add($item);
475 | }
476 | $result = $first;
477 | } else {
478 | $result = new $this->returnType();
479 | }
480 |
481 | $data['data'] = $result;
482 | return $data;
483 | }
484 |
485 | //
486 |
487 |
488 |
489 | //
490 |
491 | /** @var Entity $entityToSave */
492 | private $entityToSave;
493 | /** @var array $updatedData */
494 | private $updatedData;
495 |
496 | /**
497 | * @param Entity $entity
498 | * @param bool $isNew
499 | * @return void
500 | */
501 | protected function prepareSave($entity, $isNew) {
502 | $this->entityToSave = $entity;
503 |
504 | if($isNew && $this->useTimestamps && in_array($this->createdField, $this->getTableFields())) {
505 | if(empty($entity->{$this->createdField}))
506 | $entity->{$this->createdField} = $this->setDate();
507 | }
508 | if(!$isNew && $this->useTimestamps && in_array($this->updatedField, $this->getTableFields())) {
509 | $entity->{$this->updatedField} = $this->setDate();
510 | }
511 | }
512 |
513 | /**
514 | * @param bool $result
515 | * @param Entity $entity
516 | * @param bool $isNew
517 | * @return mixed
518 | */
519 | protected function completeSave($result, $entity, $isNew) {
520 | if($result && empty($entity->{$this->getPrimaryKey()}))
521 | $entity->{$this->getPrimaryKey()} = $result;
522 |
523 | //$entity->resetStoredFields();
524 | $entity->syncOriginal();
525 |
526 | if($this instanceof OrmEventsInterface) {
527 | if($isNew)
528 | $this->postCreation($entity);
529 | else {
530 | // Clean up updateData, CI4 likes to put primaryKey in every update
531 | if(isset($this->updatedData[$this->getPrimaryKey()])) {
532 | $updatedPrimaryKey = $this->updatedData[$this->getPrimaryKey()];
533 | if($updatedPrimaryKey['old'] == $updatedPrimaryKey['old'])
534 | unset($this->updatedData[$this->getPrimaryKey()]);
535 | }
536 |
537 | $this->postUpdate($entity, $this->updatedData);
538 | }
539 | }
540 |
541 | return $result;
542 | }
543 |
544 | /**
545 | * @param Entity $entity
546 | * @return bool
547 | * @throws \ReflectionException
548 | */
549 | public function save($entity): bool {
550 | $isNew = empty($entity->{$this->getPrimaryKey()}) || is_null($entity->{$this->getPrimaryKey()});
551 | $this->prepareSave($entity, $isNew);
552 |
553 | $result = $this->saveAndReturnId($entity);
554 | return $this->completeSave($result, $entity, $isNew);
555 | }
556 |
557 | /**
558 | * @param $data
559 | * @return int
560 | * @throws \ReflectionException
561 | */
562 | private function saveAndReturnId($data) {
563 | if(empty($data)) return true;
564 |
565 | if(is_object($data) && isset($data->{$this->primaryKey})) {
566 | try {
567 | parent::update($data->{$this->primaryKey}, $data);
568 | } catch (DataException $e) {
569 | if ($e->getMessage() == 'There is no data to update.') {
570 | // Ignore empty update exceptions
571 | }
572 | }
573 | return $data->{$this->primaryKey};
574 | } elseif (is_array($data) && !empty($data[$this->primaryKey])) {
575 | try {
576 | parent::update($data[$this->primaryKey], $data);
577 | } catch (DataException $e) {
578 | if ($e->getMessage() == 'There is no data to update.') {
579 | // Ignore empty update exceptions
580 | }
581 | }
582 | return $data[$this->primaryKey];
583 | } else {
584 | return parent::insert($data, true);
585 | }
586 | }
587 |
588 | /**
589 | * @param Entity $entity
590 | * @param bool $returnID
591 | * @return bool|int|string|void
592 | * @throws \ReflectionException
593 | */
594 | public function insert($entity = null, bool $returnID = true) {
595 | $this->prepareSave($entity, true);
596 | $result = parent::insert($entity, false);
597 | return $this->completeSave($result, $entity, true);
598 | }
599 |
600 | /**
601 | * Called before update
602 | * @param array $data
603 | * @return array
604 | */
605 | public function modifyUpdateFields($data) {
606 | $this->updatedData = [];
607 | if($this->entityToSave instanceof Entity) {
608 | $fields = $data['data'];
609 | $original = $this->entityToSave->getOriginal();
610 | foreach($fields as $field => $value) {
611 | $this->updatedData[$field] = [
612 | 'old' => isset($original[$field]) ? $original[$field] : null,
613 | 'new' => $fields[$field]
614 | ];
615 | }
616 | if(empty($fields)) { // Set the id field, CI dont like empty updates
617 | $fields[$this->getPrimaryKey()] = $original[$this->getPrimaryKey()];
618 | }
619 | $data['data'] = $fields;
620 | }
621 | unset($this->entityToSave);
622 | return $data;
623 | }
624 |
625 | /**
626 | * Called before insert
627 | * @param array $data
628 | * @return array
629 | */
630 | public function modifyInsertFields($data) {
631 | if($this->entityToSave instanceof Entity) {
632 | $fields = $data['data'];
633 | foreach($fields as $field => $value) {
634 | if(is_null($value))
635 | unset($fields[$field]);
636 | }
637 | if(empty($fields)) { // Set the id field, CI dont like empty updates
638 | $fields[$this->getPrimaryKey()] = 0;
639 | }
640 | $data['data'] = $fields;
641 | }
642 | unset($this->entityToSave);
643 | return $data;
644 | }
645 |
646 | //
647 |
648 |
649 | //
650 |
651 | protected $table = null;
652 | protected $returnType = null;
653 | protected $entityName = null;
654 |
655 | private function setCodeIgniterModelStuff() {
656 | if (!isset($this->table)) {
657 | $this->table = $this->getTableName();
658 | }
659 | if (!isset($this->entityName)) {
660 | $this->entityName = $this->getEntityName();
661 | }
662 |
663 | if (!isset($this->returnType)) {
664 | foreach (OrmExtension::$entityNamespace as $entityNamespace) {
665 | $this->returnType = $entityNamespace . $this->getEntityName();
666 | if (class_exists($this->returnType)) {
667 | break;
668 | }
669 | }
670 | }
671 |
672 | if (!isset($this->allowedFields) || count($this->allowedFields) == 0) {
673 | $this->allowedFields = ModelDefinitionCache::getFields($this->getEntityName(), $this->table);
674 | }
675 |
676 | $this->afterFind[] = 'handleResult';
677 | $this->beforeUpdate[] = 'modifyUpdateFields';
678 | $this->beforeInsert[] = 'modifyInsertFields';
679 | if(in_array('deletion_id', $this->allowedFields)) {
680 | $this->useSoftDeletes = true;
681 | $this->deletedField = 'deletion_id';
682 | }
683 | }
684 |
685 | public function getTableName() {
686 | helper('inflector');
687 | return strtolower(preg_replace('/(?getEntityName())));
688 | }
689 |
690 | public function getEntityName() {
691 | $namespace = explode('\\', get_class($this));
692 | return substr(end($namespace), 0, -5);
693 | }
694 |
695 | public function getPrimaryKey() {
696 | return $this->primaryKey;
697 | }
698 |
699 | //
700 |
701 | }
702 |
--------------------------------------------------------------------------------
/Hooks/PreController.php:
--------------------------------------------------------------------------------
1 | defaultGroup;
26 | }
27 |
28 | $table = new Table();
29 | $table->dbGroup = $config->{$group};
30 | $table->name = $name;
31 | $table->db = Database::connect($group);
32 | $table->forge = Database::forge($group);
33 | return $table;
34 | }
35 |
36 | public function create($primaryKeyName = 'id', $primaryKeyType = ColumnTypes::INT, $autoIncrement = true) {
37 | $sql = "CREATE TABLE IF NOT EXISTS `$this->name` (
38 | `{$primaryKeyName}` {$primaryKeyType} ".($autoIncrement ? 'AUTO_INCREMENT' : '').",
39 | PRIMARY KEY (`{$primaryKeyName}`)
40 | ) ENGINE=InnoDB ".($autoIncrement ? 'AUTO_INCREMENT=1' : '')
41 | ." CHARACTER SET {$this->dbGroup['charset']} COLLATE {$this->dbGroup['DBCollat']};";
42 | $this->db->query($sql);
43 | return $this;
44 | }
45 |
46 | public function hasColumn($name) {
47 | $db = $this->db->getDatabase();
48 | $sql = "SELECT count(*) as count FROM information_schema.columns
49 | WHERE `table_schema` = '{$db}'
50 | AND `table_name` = '{$this->name}'
51 | AND `column_name` = '{$name}';";
52 | $result = $this->db->query($sql);
53 | return $result->getResultArray()[0]['count'];
54 | }
55 |
56 | public function column($name, $type, $default = null) {
57 | if(!$this->hasColumn($name)) {
58 | $sql = "ALTER TABLE `{$this->name}` ADD `{$name}` {$type}";
59 | if($default) {
60 | if(is_string($default)) $default = "\"$default\"";
61 | $sql .= " DEFAULT {$default}";
62 | }
63 | $sql .= ";";
64 | $this->db->query($sql);
65 |
66 | ModelDefinitionCache::getInstance()->clearCache();
67 | }
68 | return $this;
69 | }
70 |
71 | public function dropTable() {
72 | $sql = "DROP TABLE IF EXISTS `{$this->name}`";
73 | $this->db->query($sql);
74 | return $this;
75 | }
76 |
77 | public function truncate() {
78 | if($this->db->resetDataCache()->tableExists($this->name)) {
79 | $this->db->query("TRUNCATE {$this->name}");
80 | }
81 | }
82 |
83 | public function dropColumn($name) {
84 | if($this->hasColumn($name)) {
85 | $sql = "ALTER TABLE {$this->name} DROP COLUMN {$name};";
86 | $this->db->query($sql);
87 | }
88 | return $this;
89 | }
90 |
91 | public function timestamps() {
92 | return $this
93 | ->column('created', ColumnTypes::DATETIME)
94 | ->column('updated', ColumnTypes::DATETIME);
95 | }
96 |
97 | public function softDelete() {
98 | return $this
99 | ->column('deletion_id', ColumnTypes::INT)
100 | ->addIndex('deletion_id');
101 | }
102 |
103 | public function createdUpdatedBy() {
104 | return $this
105 | ->column('created_by_id', ColumnTypes::INT)
106 | ->column('updated_by_id', ColumnTypes::INT);
107 | }
108 |
109 | public function addIndex($names) {
110 | $args = func_get_args();
111 | $name = $args[0];
112 |
113 | $indexes = [];
114 | if(count($args) > 1)
115 | $indexes = array_slice($args, 1);
116 |
117 | if(count($indexes) > 0)
118 | $indexes = implode(', ', $indexes);
119 | else
120 | $indexes = "`$name`";
121 |
122 | if($this->hasIndex($name)) {
123 | //Data::debug("Index $name already exists in {$this->name}");
124 | } else {
125 | $this->db->query("ALTER TABLE `{$this->name}` ADD INDEX `$name` ($indexes)");
126 | //Data::debug("Index $name added to {$this->name}");
127 | }
128 |
129 | return $this;
130 | }
131 |
132 | public function hasIndex($name) {
133 | if(!$this->hasColumn($name))
134 | Data::debug(get_class($this), "column", $name, 'does not exist on table', $this->name);
135 | $sql = "SELECT TABLE_SCHEMA, COUNT(1) IndexIsThere FROM INFORMATION_SCHEMA.STATISTICS
136 | WHERE table_schema=DATABASE() AND table_name='{$this->name}' AND index_name='$name';";
137 | $result = $this->db->query($sql);
138 | $data = null;
139 | foreach($result->getResult() as $row) {
140 | if(strcmp($row->TABLE_SCHEMA, $this->db->getDatabase()) === 0)
141 | $data = $row;
142 | }
143 | return isset($data) ? $data->IndexIsThere : false;
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/ModelParser/ModelItem.php:
--------------------------------------------------------------------------------
1 | path = $path;
28 |
29 | $isEntity = true;
30 | try {
31 | $rc = new \ReflectionClass("\App\Entities\\{$path}");
32 | try {
33 | $item->isResource = $rc->implementsInterface('\RestExtension\ResourceEntityInterface');
34 | } catch(Exception $e) {
35 |
36 | }
37 | } catch(\Exception $e) {
38 | try {
39 | $rc = new \ReflectionClass("\App\Interfaces\\{$path}");
40 | } catch(\Exception $e) {
41 | return false;
42 | }
43 | $isEntity = false;
44 | }
45 | $item->name = substr($rc->getName(), strrpos($rc->getName(), '\\') + 1);
46 |
47 | $comments = $rc->getDocComment();
48 | $lines = explode("\n", $comments);
49 | $isMany = false;
50 | foreach($lines as $line) {
51 | if(strpos($line, 'Many') !== false) $isMany = true;
52 | if(strpos($line, 'OTF') !== false) $isMany = false;
53 | $property = PropertyItem::parse($line, $isMany);
54 | if($property)
55 | $item->properties[] = $property;
56 | }
57 |
58 | // Append static properties
59 | if($isEntity) {
60 | $item->properties[] = new PropertyItem('id', 'int', true, false);
61 | $item->properties[] = new PropertyItem('created', 'string', true, false);
62 | $item->properties[] = new PropertyItem('updated', 'string', true, false);
63 | $item->properties[] = new PropertyItem('created_by_id', 'int', true, false);
64 | $item->properties[] = new PropertyItem('created_by', 'User', false, false);
65 | $item->properties[] = new PropertyItem('updated_by_id', 'int', true, false);
66 | $item->properties[] = new PropertyItem('updated_by', 'User', false, false);
67 | $item->properties[] = new PropertyItem('deletion_id', 'int', true, false);
68 | $item->properties[] = new PropertyItem('deletion', 'Deletion', false, false);
69 | }
70 |
71 | return $item;
72 | }
73 |
74 | /**
75 | * @return bool|ApiItem
76 | */
77 | public function getApiItem() {
78 | $entityName = "\App\Entities\\{$this->path}";
79 | /** @var Entity $entity */
80 | $entity = new $entityName();
81 | try {
82 | return ApiItem::parse($entity->getResourcePath());
83 | } catch(\ReflectionException $e) {
84 | }
85 | return false;
86 | }
87 |
88 | public function toSwagger() {
89 | $item = [
90 | 'title' => $this->name,
91 | 'type' => 'object',
92 | 'properties' => []
93 | ];
94 | foreach($this->properties as $property) {
95 | $item['properties'][$property->name] = $property->toSwagger();
96 | }
97 | return $item;
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/ModelParser/ModelParser.php:
--------------------------------------------------------------------------------
1 | models = $models;
33 | return $parser;
34 | }
35 |
36 | public function generateSwagger($overrideWithStatic = false, $schemaReferences = null, $scope = null) {
37 | $json = [];
38 |
39 | // Append Static Schemas (From Schemas folder)
40 | $schemas = self::loadStatics();
41 | foreach($schemas as $schema) {
42 | $schemaName = substr($schema, 0, -5);
43 | $json[$schemaName] = json_decode(file_get_contents(self::$staticPath . '/' . $schema));
44 | }
45 |
46 | // Append schemaReferences recursively
47 | // $list = [];
48 | // foreach($schemaReferences as $schemaReference) {
49 | // $this->appendSchemaReferencesRecursively($schemaReference, $list);
50 | // }
51 | // $schemaReferences = $list;
52 |
53 | if($scope) {
54 | self::$staticPath = self::$staticPath.'/'.$scope;
55 | $schemas = self::loadStatics();
56 | foreach($schemas as $schema) {
57 | $schemaName = substr($schema, 0, -5);
58 | $json[$schemaName] = json_decode(file_get_contents(self::$staticPath . '/' . $schema));
59 | }
60 | }
61 |
62 | foreach($this->models as $model) {
63 | if($schemaReferences && !in_array($model->name, $schemaReferences)) continue;
64 |
65 | $staticName = $model->name.'.json';
66 | if($overrideWithStatic && in_array($staticName, $schemas)) {
67 | $schema = str_replace("\n", '', file_get_contents(self::$staticPath . '/' . $staticName));
68 | $jsonSchema = json_decode($schema);
69 | $json[$model->name] = $jsonSchema ? $jsonSchema : $schema;
70 | } else
71 | $json[$model->name] = $model->toSwagger();
72 | }
73 | return $json;
74 | }
75 |
76 | private function appendSchemaReferencesRecursively($name, &$list) {
77 | if(in_array($name, $list)) return;
78 | $list[] = $name;
79 |
80 | $modelItem = ModelItem::parse($name);
81 | foreach($modelItem->properties as $property) {
82 | if(!$property->isSimpleType)
83 | $this->appendSchemaReferencesRecursively($property->typeScriptType, $list);
84 | }
85 | }
86 |
87 | public function generateTypeScript($debug = false) {
88 | if(!file_exists(WRITEPATH.'tmp/models/definitions/')) mkdir(WRITEPATH.'tmp/models/definitions/', 0777, true);
89 |
90 | $renderer = Services::renderer(__DIR__.'/TypeScript', null, false);
91 |
92 | foreach($this->models as $model) {
93 | Data::debug($model->name.' with '.count($model->properties).' properties');
94 |
95 | // Definition
96 | $content = $renderer->setData(['model' => $model], 'raw')->render('ModelDefinition', ['debug' => false], null);
97 | if($debug) echo $content;
98 | else
99 | file_put_contents(WRITEPATH.'tmp/models/definitions/'.$model->name.'Definition.ts', $content);
100 |
101 | // Model
102 | $content = $renderer->setData(['model' => $model], 'raw')->render('Model', ['debug' => false], null);
103 | if($debug) echo $content;
104 | else
105 | file_put_contents(WRITEPATH.'tmp/models/'.$model->name.'.ts', $content);
106 |
107 | }
108 |
109 | $content = $renderer->setData(['models' => $this->models], 'raw')->render('Index', ['debug' => false], null);
110 | file_put_contents(WRITEPATH.'tmp/models/index.ts', $content);
111 | }
112 |
113 | public function generateXamarin($debug = false) {
114 | if(!file_exists(WRITEPATH.'tmp/xamarin/models/Definitions/')) mkdir(WRITEPATH.'tmp/xamarin/models/Definitions/', 0777, true);
115 |
116 | $renderer = Services::renderer(__DIR__.'/Xamarin', null, false);
117 |
118 | foreach($this->models as $model) {
119 | Data::debug($model->name.' with '.count($model->properties).' properties');
120 |
121 | // Definition
122 | $content = $renderer->setData(['model' => $model], 'raw')->render('ModelDefinition', ['debug' => false], null);
123 | if($debug) echo $content;
124 | else
125 | file_put_contents(WRITEPATH.'tmp/xamarin/models/Definitions/'.$model->name.'Definition.cs', $content);
126 |
127 | // Model
128 | $content = $renderer->setData(['model' => $model], 'raw')->render('Model', ['debug' => false], null);
129 | if($debug) echo $content;
130 | else
131 | file_put_contents(WRITEPATH.'tmp/xamarin/models/'.$model->name.'.cs', $content);
132 |
133 | }
134 | }
135 |
136 |
137 | /**
138 | * @param $model
139 | * @return ModelItem
140 | */
141 | private static function parseModels($model) {
142 | return ModelItem::parse(substr($model, 0, -4));
143 | }
144 |
145 | private static function loadModels($includeInterfaces = false) {
146 | $files = scandir(APPPATH. 'Entities');
147 | if($includeInterfaces && is_dir(APPPATH. 'Interfaces')) {
148 | $files = array_merge($files, scandir(APPPATH . 'Interfaces'));
149 | }
150 |
151 | $models = [];
152 | foreach($files as $file) {
153 | if($file[0] != '_' && substr($file, -3) == 'php') {
154 | $models[] = $file;
155 | }
156 | }
157 | return $models;
158 | }
159 |
160 | private static function loadStatics() {
161 | $schemas = [];
162 | if(is_dir(self::$staticPath)) {
163 | $files = scandir(self::$staticPath);
164 | foreach($files as $file) {
165 | if($file[0] != '_' && substr($file, -4) == 'json') {
166 | $schemas[] = $file;
167 | }
168 | }
169 | }
170 | return $schemas;
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/ModelParser/PropertyItem.php:
--------------------------------------------------------------------------------
1 | name = $name;
23 | $this->typeScriptType = $type;
24 | $this->xamarinType = $type;
25 | $this->isSimpleType = $isSimpleType;
26 | $this->isMany = $isMany;
27 | $this->setType($type);
28 | }
29 |
30 | public static function validate($line) {
31 | if(strpos($line, '@property') === false) return false;
32 | $line = substr($line, strlen(' * @property '));
33 | $parts = explode(' ', $line);
34 | if(count($parts) < 2) return false;
35 | return $parts;
36 | }
37 |
38 | public static function parse($line, $isMany = false) {
39 | $parts = PropertyItem::validate($line);
40 | if(!$parts) return false;
41 |
42 | $type = array_shift($parts);
43 | $item = new PropertyItem();
44 | $item->name = substr(array_shift($parts), 1);
45 | $item->isMany = $isMany;
46 |
47 | if(count($parts))
48 | $item->comment = implode(' ', $parts);
49 |
50 | $item->setType($type);
51 |
52 | return $item;
53 | }
54 |
55 | public function setType($type) {
56 | $this->isSimpleType = true;
57 |
58 | $this->rawType = $type;
59 |
60 | switch($type) {
61 | case 'int':
62 | case 'double':
63 | $this->typeScriptType = 'number';
64 | break;
65 | case 'string|double':
66 | $this->typeScriptType = 'string';
67 | break;
68 | case 'boolean':
69 | case 'bool':
70 | $this->typeScriptType = 'boolean';
71 | break;
72 | case 'string':
73 | $this->typeScriptType = 'string';
74 | break;
75 | case 'int[]':
76 | $this->typeScriptType = 'number[]';
77 | break;
78 | default:
79 | $this->typeScriptType = $type;
80 | $this->isSimpleType = false;
81 | break;
82 | }
83 | // Xamarin
84 | switch($type) {
85 | case 'string|double':
86 | $this->xamarinType = 'string';
87 | break;
88 | case 'boolean':
89 | case 'bool':
90 | $this->xamarinType = 'bool';
91 | break;
92 | default:
93 | $this->xamarinType = $type;
94 | break;
95 | }
96 | }
97 |
98 | public function toSwagger() {
99 | $item = [];
100 |
101 | $type = $this->rawType;
102 | switch($this->rawType) {
103 | case 'int[]':
104 | $type = 'integer[]';
105 | break;
106 | case 'int':
107 | $type = 'integer';
108 | break;
109 | case 'bool':
110 | $type = 'boolean';
111 | break;
112 | }
113 |
114 | if($this->isSimpleType)
115 | $item['type'] = $type;
116 | else
117 | $item['type'] = "{$type}"; //"#/components/schemas/{$this->type}";
118 |
119 | if(strpos($type, '[]') !== false) {
120 | $item['items'] = ['type' => substr($item['type'], 0, -2)];
121 | $item['type'] = 'array';
122 | }
123 |
124 | return $item;
125 | }
126 |
127 | public function getCamelName() {
128 | return camelize($this->name);
129 | }
130 |
131 | }
132 |
--------------------------------------------------------------------------------
/ModelParser/TypeScript/Index.php:
--------------------------------------------------------------------------------
1 |
5 | export {=$model->name?> as =$model->name?>} from "./=$model->name?>";
6 |
7 |
--------------------------------------------------------------------------------
/ModelParser/TypeScript/Model.php:
--------------------------------------------------------------------------------
1 |
5 | /**
6 | * Created by ModelParser
7 | * Date: =date('d-m-Y')?>.
8 | * Time: =date('H:i')?>.
9 | */
10 | import {=$model->name?>Definition} from './definitions/=$model->name?>Definition';
11 |
12 | export class =$model->name?> extends =$model->name?>Definition {
13 |
14 | constructor(json?: any) {
15 | super(json);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/ModelParser/TypeScript/ModelDefinition.php:
--------------------------------------------------------------------------------
1 |
4 | /**
5 | * Created by ModelParser
6 | */
7 | properties as $property) : ?>
11 | isSimpleType && !in_array($property->typeScriptType, $imported)) :
12 | $imported[] = $property->typeScriptType; ?>
13 | import {=$property->typeScriptType?>} from '../=$property->typeScriptType?>';
14 |
15 |
17 | import {BaseModel} from '../BaseModel';
18 | isResource && $model->getApiItem()) { ?>
19 | import {Api} from '../../http/Api/Api';
20 |
21 |
22 | export class =$model->name?>Definition extends BaseModel {
23 | properties as $property) : ?>
24 | =$property->name?>?: =$property->typeScriptType?>=$property->isSimpleType?'':''?>=$property->isMany?"[]":""?>;
25 |
26 |
27 | constructor(data?: any) {
28 | super();
29 | this.populate(data);
30 | }
31 |
32 | public populate(data?: any, patch = false) {
33 | if (!patch) {
34 | properties as $property) : ?>
35 | delete this.=$property->name?>;
36 |
37 | }
38 |
39 | if (!data) return;
40 | properties as $property) : ?>
41 | if (data.=$property->name?> != null) {
42 | isMany): ?>
43 | this.=$property->name?> = data.=$property->name?>.map((i: any) => new =$property->typeScriptType?>(i));
44 |
45 | isSimpleType): ?>
46 | this.=$property->name?> = data.=$property->name?>;
47 |
48 | this.=$property->name?> = new =$property->typeScriptType?>(data.=$property->name?>);
49 |
50 |
51 | }
52 |
53 | }
54 | isResource && $model->getApiItem()) { ?>
55 | =$model->getApiItem()->generateTypeScriptModelFunctions()?>
56 |
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/ModelParser/TypeScript/ModelInterface.php:
--------------------------------------------------------------------------------
1 |
5 | /**
6 | * Created by ModelParser
7 | * Date: =date('d-m-Y')?>.
8 | * Time: =date('H:i')?>.
9 | */
10 | properties as $property) : ?>
14 | isSimpleType && !in_array($property->typeScriptType, $imported) && $property->typeScriptType != $model->name) :
15 | $imported[] = $property->typeScriptType; ?>
16 | import {=$property->typeScriptType?>Interface} from "./=$property->typeScriptType?>Interface";
17 |
18 |
20 |
21 | export interface =$model->name?>Interface {
22 | properties as $property) : ?>
23 | =$property->name?>?: =$property->typeScriptType?>=$property->isSimpleType?'':'Interface'?>=$property->isMany?"[]":""?>;
24 |
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/ModelParser/Xamarin/Model.php:
--------------------------------------------------------------------------------
1 |
5 | /**
6 | * Created by ModelParser
7 | * Date: =date('d-m-Y')?>.
8 | * Time: =date('H:i')?>.
9 | */
10 | using System;
11 | using =config('OrmExtension')->xamarinModelsNamespace?>.Definitions;
12 |
13 | namespace =config('OrmExtension')->xamarinModelsNamespace?>
14 | {
15 | public class =$model->name?> : =$model->name?>Definition
16 | {
17 |
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/ModelParser/Xamarin/ModelDefinition.php:
--------------------------------------------------------------------------------
1 |
4 | /**
5 | * Created by ModelParser
6 | */
7 | using System.Collections.Generic;
8 | using Newtonsoft.Json;
9 | using =config('OrmExtension')->xamarinBaseModelNamespace?>;
10 |
11 | namespace =config('OrmExtension')->xamarinModelsNamespace?>.Definitions
12 | {
13 |
14 | [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
15 | public class =$model->name?>Definition : BaseModel
16 | {
17 | properties as $property) : ?>
18 |
19 | [JsonProperty("=$property->name?>")]
20 | isMany) { ?>
21 | public List<=$property->xamarinType?>> =ucfirst($property->getCamelName())?> { get; set; }
22 |
23 | public =$property->xamarinType?> =ucfirst($property->getCamelName())?> { get; set; }
24 |
25 |
26 |
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CodeIgniter 4 OrmExtension
2 |
3 | [](https://packagist.org/packages/4spacesdk/ci4ormextension) [](https://packagist.org/packages/4spacesdk/ci4ormextension) [](https://packagist.org/packages/4spacesdk/ci4ormextension) [](https://packagist.org/packages/4spacesdk/ci4ormextension) [](https://packagist.org/packages/4spacesdk/ci4ormextension)
4 |
5 | ## What is CodeIgniter 4 OrmExtension?
6 | OrmExtension is an Object Relational Mapper written in PHP for CodeIgniter 4.
7 | It is designed to map your Database tables into easy to work with objects, fully aware of the relationships between each other.
8 | OrmExtension is based on the same idea as the original [WanWizard DataMapper](https://datamapper.wanwizard.eu/) for CodeIgniter 2. But totally rewritten to fit CodeIgniter 4.
9 |
10 |
11 | ## Installation
12 | Step 1)
13 |
14 | `composer require 4spacesdk/ci4ormextension`
15 |
16 | Step 2)
17 |
18 | Create new file `app/Config/OrmExtension.php` and add this content
19 | ```php
20 | whereRelated(ColorModel::class, 'name', 'green')
207 | ->find();
208 | ```
209 | Select users with the role admin and color blue:
210 | ```php
211 | $userModel = new UserModel();
212 | $users = $userModel
213 | ->whereRelated(RoleModel::class, 'name', 'admin')
214 | ->whereRelated(ColorModel::class, 'name', 'blue')
215 | ->find();
216 | ```
217 | Select all users and include their color:
218 | ```php
219 | $userModel = new UserModel();
220 | $users = $userModel
221 | ->includeRelated(ColorModel::class)
222 | ->find();
223 | ```
224 | Select users with role admin and include their color:
225 | ```php
226 | $userModel = new UserModel();
227 | $users = $userModel
228 | ->includeRelated(ColorModel::class)
229 | ->whereRelated(RoleModel::class, 'name', 'admin')
230 | ->find();
231 | ```
232 |
233 | #### Result
234 | The return from `find()` has been changed. `Find` will always return a entity class related to the calling model. It will never be null or an array of entities. This is a good think - because now we have some consistent to work with. The entity is traversable, so we can use it in a loop!
235 | Check these examples:
236 | ```php
237 | $userModel = new UserModel();
238 | $user = $userModel
239 | ->where('id', 1)
240 | ->find();
241 | echo json_encode($user->toArray());
242 | ```
243 | ```json
244 | {
245 | "id": 1,
246 | "name": "Martin"
247 | }
248 | ```
249 |
250 | ```php
251 | $userModel = new UserModel();
252 | $users = $userModel->find();
253 | echo json_encode($users->allToArray());
254 | ```
255 | ```json
256 | [
257 | {
258 | "id": 1,
259 | "name": "Martin"
260 | },
261 | {
262 | "id": 2,
263 | "name": "Kevin"
264 | }
265 | ]
266 | ```
267 |
268 | `toArray()` returns an array with one user's properties. `allToArray()` returns an array of multiple user's properties. These methods are great for json encoding.
269 |
270 |
271 | ### Working with Entities
272 | Relations can be accessed as magic properties.
273 | This will echo null, because the color is an empty entity. It has not yet been retrieved from the database.
274 | ```php
275 | $userModel = new UserModel();
276 | $users = $userModel->find();
277 | foreach($users as $user) {
278 | echo $user->color->name;
279 | }
280 | ```
281 |
282 | We can retrieve the color with an include:
283 | ```php
284 | $userModel = new UserModel();
285 | $users = $userModel
286 | ->includeRelated(ColorModel::class)
287 | ->find();
288 | foreach($users as $user) {
289 | echo $user->color->name;
290 | }
291 | ```
292 | This will echo the actual color name, because OrmExtension has prefetched the color from the `find`.
293 |
294 | We can also retrieve the color afterwards:
295 | ```php
296 | $userModel = new UserModel();
297 | $users = $userModel->find();
298 | foreach($users as $user) {
299 | $user->color->find();
300 | echo $user->color->name;
301 | }
302 | ```
303 |
304 | A user can have multiple roles and we want to access only the role named admin. For this we have to access the model from the entity to do a `where`.
305 | ```php
306 | $userModel = new UserModel();
307 | $user = $userModel->find(1);
308 | $role = $user->roles->_getModel()
309 | ->where('name', 'admin')
310 | ->find();
311 | echo $role->name; // "admin"
312 | ```
313 |
314 |
315 | ### Deep relations
316 | For this purpose we will look at another example. Let's say an `user` has `books` and `books` has `color`. A `book` is shared between many users but can only have one color. A color can be shared between many books.
317 | ```php
318 | class UserModel {
319 | public $hasMany = [
320 | BookModel::class
321 | ];
322 | }
323 | class BookModel {
324 | public $hasOne = [
325 | ColorModel::class
326 | ],
327 | public $hasMany = [
328 | UserModel::class
329 | ];
330 | }
331 | class ColorModel {
332 | public $hasMany = [
333 | BookModel::class
334 | ];
335 | }
336 | ```
337 |
338 | We want to select all users with green books.
339 | ```php
340 | $userModel = new UserModel();
341 | $users = $userModel
342 | ->whereRelated([BookModel::class, ColorModel::class], 'name', 'green')
343 | ->find();
344 | ```
345 | To access deep relations, simply put them in an array.
346 |
347 |
348 | ### Soft deletion
349 | OrmExtension provides an extended soft deletion. Create a model and entity for `Deletion`.
350 | **Entity**
351 | ```php
352 | generateSwagger();
397 | ```
398 | Attach `$schemes` to swagger components and you have all your models documented.
399 | ```php
400 | $parser = ModelParser::run();
401 | $parser->generateTypeScript();
402 | ```
403 | This will generate typescript models as classes and interfaces. Find the files under `writeable/tmp`.
404 |
405 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "4spacesdk/ci4ormextension",
3 | "description": "ORM Extension for CodeIgniter 4",
4 | "authors": [
5 | {
6 | "name": "Martin Hansen",
7 | "email": "martin@4spaces.dk"
8 | }
9 | ],
10 | "license": "MIT",
11 | "require": {
12 | "php": ">=7.1",
13 | "4spacesdk/ci4debugtool": ">=1.0.0"
14 | },
15 | "autoload": {
16 | "psr-4": {
17 | "OrmExtension\\": ""
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/debug_helper.php:
--------------------------------------------------------------------------------
1 | log('notice', $msg);
9 | }
10 |
11 | };
12 |
--------------------------------------------------------------------------------