• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..15-Dec-2021-

Cache/H15-Dec-2021-284134

Connector/H15-Dec-2021-2,9301,299

Exception/H15-Dec-2021-8046

Factory/H15-Dec-2021-3918

Fields/H15-Dec-2021-1,105588

Storage/H15-Dec-2021-14568

Views/H15-Dec-2021-3,6581,862

templates/default/H15-Dec-2021-

README.mdH A D15-Dec-202118.4 KiB438351

class.ActiveRecord.phpH A D15-Dec-202122.8 KiB972469

class.ActiveRecordList.phpH A D15-Dec-202118 KiB804375

class.CachedActiveRecord.phpH A D15-Dec-20212.8 KiB13990

maintenance.jsonH A D15-Dec-2021534 2222

README.md

1# How to use Active Record in ILIAS
2## Active Records
3### General
4The ActiveRecord-Implementation in ILIAS should help developers to get rid of multiple developments of CRUD functionality in their model classes. A lot of redundant code is to be found in ILIAS due to the implementations of read- and write processes to the persistent layer.
5ILIAS-ActiveRecord provides a lot of useful helpers such as a QueryBuilder, dynamic CRUD, ObjectCaching und data source maintenance.
6
7### Differences against other implementations
8ActiveRecord are well known in other frameworks and languages such as Ruby, .NET, CakePHP …
9Most ActiveRecords directly represent the persistent layer, mostly database-tables. Changes in the database are automatically represented by the model. Modifications on the class-members are only possible by modifying the database-field.
10This is the the only big difference between ILIAS-ActiveRecord and other ActiveRecord-Implementations. ILIAS-ActiveRecord describes the whole class-member in PHP-Code with the information for the persistent layer (such as data-type, length, …).
11Advantages of this implementation:
12
13- The class-member is represented in your PHP-class and not just dynamically loaded, you ‘see’ your members and let IDEs like PHPStorm automatically implement your setters and getters.
14- You ‘see’ directly the field-attributes of your member in the persistent layer.
15Information about your members can be accessed by field-classes.
16
17## Implement your ActiveRecord-Class
18### Structure of your model
19An ActiveRecord-Class normally extends from the abstract ActiveRecord. Let us use the following example:
20“We need a Message-Model. A Message has a title, a body, a sender and a receiver. Additionally the Message can be of the priority ‘low’, ‘normal’ or ‘high’ and can have a status like ‘new’ and ‘read’.”
21Our Model could look like this:
22
23```php
24<?php
25require_once('./Customizing/global/plugins/Libraries/ActiveRecord/class.ActiveRecord.php');
26require_once(dirname(__FILE__) . '/../../Connector/class.arConnectorSession.php');
27
28/**
29 * Class arMessage
30 *
31 * @author  Fabian Schmid <fs@studer-raimann.ch>
32 * @version 1.0.0
33 */
34class arMessage extends ActiveRecord {
35
36        const TYPE_NEW = 1;
37        const TYPE_READ = 2;
38        const PRIO_LOW = 1;
39        const PRIO_NORMAL = 5;
40        const PRIO_HIGH = 9;
41
42        const TABLE_NAME = 'ar_message';
43
44        /**
45         * @return string
46         */
47        static function returnDbTableName() {
48                return self::TABLE_NAME;
49        }
50
51        /**
52         * @var int
53         *
54         * @con_is_primary true
55         * @con_sequence true
56         * @con_has_field  true
57         * @con_fieldtype  integer
58         * @con_length     8
59         */
60        protected $id;
61        /**
62         * @var string
63         *
64         * @con_has_field true
65         * @con_fieldtype text
66         * @con_length    256
67         */
68        protected $title = '';
69        /**
70         * @var string
71         *
72         * @con_has_field true
73         * @con_fieldtype clob
74         * @con_length    4000
75         */
76        protected $body = '';
77        /**
78         * @var int
79         *
80         * @con_has_field  true
81         * @con_fieldtype  integer
82         * @con_length     1
83         */
84        protected $sender_id = 0;
85        /**
86         * @var int
87         *
88         * @con_has_field  true
89         * @con_fieldtype  integer
90         * @con_is_notnull true
91         * @con_length     1
92         */
93        protected $receiver_id = 0;
94        /**
95         * @var int
96         *
97         * @con_has_field  true
98         * @con_fieldtype  integer
99         * @con_length     1
100         * @con_is_notnull true
101         */
102        protected $priority = self::PRIO_NORMAL;
103        /**
104         * @var int
105         *
106         * @con_has_field  true
107         * @con_fieldtype  integer
108         * @con_length     1
109         * @con_is_notnull true
110         */
111        protected $type = self::TYPE_NEW;
112
113
114        /**
115         * @param mixed $body
116         */
117        public function setBody($body) {
118                $this->body = $body;
119        }
120
121
122        /**
123         * @return mixed
124         */
125        public function getBody() {
126                return $this->body;
127        }
128
129
130        /**
131         * @param int $priority
132         */
133        public function setPriority($priority) {
134                $this->priority = $priority;
135        }
136
137
138        /**
139         * @return int
140         */
141        public function getPriority() {
142                return $this->priority;
143        }
144
145
146        /**
147         * @param int $receiver_id
148         */
149        public function setReceiverId($receiver_id) {
150                $this->receiver_id = $receiver_id;
151        }
152
153
154        /**
155         * @return int
156         */
157        public function getReceiverId() {
158                return $this->receiver_id;
159        }
160
161
162        /**
163         * @param int $sender_id
164         */
165        public function setSenderId($sender_id) {
166                $this->sender_id = $sender_id;
167        }
168
169
170        /**
171         * @return int
172         */
173        public function getSenderId() {
174                return $this->sender_id;
175        }
176
177
178        /**
179         * @param string $title
180         */
181        public function setTitle($title) {
182                $this->title = $title;
183        }
184
185
186        /**
187         * @return string
188         */
189        public function getTitle() {
190                return $this->title;
191        }
192
193
194        /**
195         * @param int $type
196         */
197        public function setType($type) {
198                $this->type = $type;
199        }
200
201
202        /**
203         * @return int
204         */
205        public function getType() {
206                return $this->type;
207        }
208}
209
210?>
211```
212
213The class implements the public static Method ‘returnDbTableName’, which returns the identifier of the container in the persistent layer. The rest of the Class are Members, Setters and Getters. This Class is fully functional, no other methods have to be implemented to have full CRUD, Caching, Collections, Factory, …
214All Class-Members, which should be represented in the persistent layer, are additionally documented with PHPDoc.
215
216### Using CRUD
217After implementing your modelclass you can use the ActiveRecord CRUD commands to build and modify objects of the class:
218
219```php
220$arMessage = new arMessage();
221$arMessage->setTitle('Hello World');
222$arMessage->setBody('Development using ActiveRecord saves a lot of time');
223$arMessage->create();
224// OR
225$arMessage = new arMessage(3);
226echo $arMessage->getBody();
227// OR
228$arMessage = new arMessage(6);
229$arMessage->setType(arMessage::TYPE_READ);
230$arMessage->update();
231// OR
232$arMessage = arMessage::find(58); // find() Uses the ObjectCache
233$arMessage->delete();
234```
235
236### Fields and FieldList
237An ActiveRecord-Class-Member is described with the following attributes in PHPDoc. This information is used to provide a proper persistent layer access.
238
239| Attribute-Name | Description                                                                                               | Possible Values                                                                                  |
240|----------------|-----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
241| con_hasfield   | Defines whether the field is represented in the persistent layer or not (false doesn’t has to be written) | true/false                                                                                       |
242| con_is_primary | Member is primary key. Only one primary for one class possible.                                           | true/false                                                                                       |
243| con_sequence   | The (primary-)field has an auto-increment. This is needed in most of the cases                            | true/false                                                                                       |
244| con_is_notnull | Is member not_null (as in MySQL)                                                                          | true/false                                                                                       |
245| con_fieldtype  | All ilDB-Field-Types are currently supported                                                              | text, integer, float, date, time, timestamp, clob                                                |
246| con_length     | Length of the field in the persistent layer                                                               | determines from the fieldtype. See 'Databse Access and Database Schema' for further information. |
247
248All this information is parsed from the PHPDoc once per ActiveRecord-Class and request and are cached for all other instances of this type. So there should not be a remarkable performance-drop.
249This is an Example for a primary key $id:
250
251```php
252/**
253 * @var int
254 *
255 * @con_is_primary true
256 * @con_has_field  true
257 * @con_sequence  true
258 * @con_fieldtype  integer
259 * @con_length     8
260 */
261protected $id;
262```
263
264All the meta information can be access in the ActiveRecord:
265
266```php
267public function dummy() {
268        echo $this->arFieldList->getPrimaryField();
269        echo $this->arFieldList->getFieldByName('title')->getFieldType();
270        echo $this->getPrimaryFieldValue();
271}
272```
273
274## ActiveRecordList
275### Basics
276The ActiveRecordList-Class represents the Collection, Repository, ... The List is accessible through the ActiveRecord or in an own instance:
277
278```php
279/**
280 * @return arMessage[]
281 * @description a way to get all objects is to call get() directly on your class
282 */
283public static function getAllObjects() {
284        $array_of_arMessages = arMessage::get();
285
286        // OR
287
288        $arMessageList = new arMessageList();
289        $array_of_arMessages = $arMessageList->get();
290        return $array_of_arMessages;
291}
292```
293
294Both examples return an Array of arMessage-Objects. But The List provide more functionality, such as a QueryBuilder:
295
296
297```php
298public function getSome() {
299        $array_of_arMessages = arMessage::where(array('type' => arMessage::TYPE_READ))->orderBy('title')->get();
300
301        // OR
302
303        $arMessageList = new arMessageList();
304        $arMessageList->where(array('type'=> arMessage::TYPE_READ));
305        $arMessageList->orderBy('title');
306        $array_of_arMessages = $arMessageList->get();
307}
308```
309
310### Build a query
311**Where**
312
313| Method-Call                                                                                                                     | Query                                                                                       |
314|---------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|
315| ```arMessage::where(array('type' => arMessage::TYPE_READ));```                                                                  | ```SELECT * FROM ar_message WHERE ar_message.type = 1```                                    |
316| ```arMessage::where(array('type'=>arMessage::TYPE_NEW), '!=');```                                                               | ```SELECT * FROM ar_message WHERE ar_message.type != 1```                                   |
317| ```arMessage::where(array( 'type' => arMessage::TYPE_NEW, 'title' => '%test%' ), '=');```                                       | ```SELECT * FROM ar_message WHERE ar_message.type = 1 AND ar_message.title = '%test%'```    |
318| ```arMessage::where(array( 'type' => arMessage::TYPE_NEW, 'title' => '%test%' ), array( 'type' => '=', 'title' => 'LIKE' ));``` | ```SELECT * FROM ar_message WHERE ar_message.type = 1 AND ar_message.title LIKE '%test%'``` |
319| ```arMessage::where(array( 'type' => arMessage::TYPE_NEW ))->where(array( 'title' => '%test%' ), 'LIKE')```                     | ```SELECT * FROM ar_message WHERE ar_message.type = 1 AND ar_message.title LIKE '%test%'``` |
320
321
322**Oder By**
323
324| Method-Call                                                 | Query                                                         |
325|-------------------------------------------------------------|---------------------------------------------------------------|
326| ```arMessage::orderBy('title');```                          | ```SELECT * FROM ar_message ORDER BY title ASC```             |
327| ```arMessage::orderBy('title', 'DESC');```                  | ```SELECT * FROM ar_message ORDER BY title DESC```            |
328| ```arMessage::orderBy('title', 'DESC')->orderBy('type');``` | ```SELECT * FROM ar_message ORDER BY title DESC, type ASC'``` |
329
330
331**Limit**
332
333| Method-Call                     | Query                                       |
334|---------------------------------|---------------------------------------------|
335| ```arMessage::limit(0, 100);``` | ```SELECT * FROM ar_message LIMIT 0, 100``` |
336
337
338**Join**
339
340| Method-Call                                                                     | Query                                                                                                                    |
341|---------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|
342| ```arMessage::innerjoin('usr_data', 'receiver_id', 'usr_id');```                | ```SELECT ar_message.*, usr_data.* FROM ar_message INNER JOIN usr_data ON ar_message.receiver_id = usr_data.usr_id```    |
343| ```arMessage::leftjoin('usr_data', 'receiver_id', 'usr_id', array('email'));``` | ```SELECT ar_message.*, usr_data.email FROM ar_message LEFT JOIN usr_data ON ar_message.receiver_id = usr_data.usr_id``` |
344
345**Combining statements**
346
347| Method-Call                                                                     | Query                                                                                                                    |
348|---------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|
349| ```arMessage::innerjoin('usr_data', 'receiver_id', 'usr_id');```                | ```SELECT ar_message.*, usr_data.* FROM ar_message INNER JOIN usr_data ON ar_message.receiver_id = usr_data.usr_id```    |
350| ```arMessage::leftjoin('usr_data', 'receiver_id', 'usr_id', array('email'));``` | ```SELECT ar_message.*, usr_data.email FROM ar_message LEFT JOIN usr_data ON ar_message.receiver_id = usr_data.usr_id``` |
351
352### Get information
353**get();**
354arMessage::orderBy('title')->get(); will return an array of arMessage-Object.
355
356**getArray();**
357arMessage::orderBy('title')->getArray(); will return a 2D record-value array. getArray() can be filtered or the index oft he array can be set:
358arMessage::getArray(NULL, array('title')); will return an array with only the titles of the records.
359
360**getCollection();**
361If you build a query using the statements explaines above, you can store this Collection for further use. arMessage::getCollection(); return the ActiveRecordList-Object with all statements saves.
362
363**first();**
364Returns the first object from your query.
365
366**last();**
367Returns the last object from your query.
368
369### Use ActiveRecord for Sorting and Filters in Tables
370When using an Activerecord for presentation in ilTableGUI, this is an Example to use external sorting and external segmentation, which will increase performance on larger tables:
371
372```php
373protected function parseData() {
374        $this->determineOffsetAndOrder();
375        $this->determineLimit();
376        $arMessageList = arMessage::orderBy($this->getOrderField(), $this->getOrderDirection());
377        foreach ($this->filter as $field => $value) {
378                if ($value) {
379                        $arMessageList->where(array( $field => $value ));
380                }
381        }
382        $this->setMaxCount($arMessageList->count());
383        $arMessageList->limit($this->getOffset(), $this->getLimit());
384        $arMessageList->orderBy('title'); // Secord order field
385        $arMessageList->dateFormat('d.m.Y - H:i:s'); // All date-fields come in three ways: formatted, unix, unformatted (as in db)
386        $this->setData($arMessageList->getArray());
387}
388```
389
390## Connector
391
392ILIAS ActiveRecord uses the ilDB connection as default persistent layer. A connector is responsible for all connections to the persistent layer. It’s possible to write your own Connector, an example is delivered with the ActiveRecord. It uses the User-Session to store the objects. This connector is not fully functional (there is no Querybuilder). Use the abstract arConnector Class to implement your own connector.
393
394## Maintenance of data source
395ActiveRecord allows to maintain your persistent layer like the ILIAS Database for your class. There is no need to install the database on your own, ActiveRecord can install und update the table:
396**Please do not use installDB; and updateDB; for core-development. Using them will be reported as a bug.**
397
398### Generate DB-Update-Step to install your Class
399Use the already known DB-Update-Steps to generate your AR-Databases. There is a Helper-Script to auto-generate a Installation-Updatestep. Implement these two lines with your ActiveRecord somewhere in ILIAS-Code and run the site. It generates and Downloads a tet-file with the installation-Step:
400
401```php
402$arBuilder = new arBuilder(new arMessage());
403$arBuilder->generateDBUpdateForInstallation();
404```
405
406You can use these methods to delete or truncate your table even in dbupdate-Scripts:
407
408```php
409arMessage::resetDB(); // Truncates the Database
410$ilDB->dropTable(arMessage::TABLE_NAME, false); // Deletes the Database
411```
412
413It's not yet possible to generate e database-modification step with this feature. Please write those as usual and don't forget to represent your changes in your AR-based Class.
414
415### Generate Class-File from existing MySQL-Table (Beta)
416It’s possible to generate a PHP-Classfile for an existing MySQL-Datatable, e.g. with the Table usr_data:
417
418```php
419$arConverter = new arConverter('usr_data', 'arUser');
420$arConverter->downloadClassFile();
421```
422## Object-Cache
423Every ActiveRecord is being cached, developers don’t have to mind this task. The cache is updated on every object modification and is deleted after deleting the object.
424The object cache storage can be accessed if necessary:
425
426```php
427// e.g.
428
429$arMessageFour = new arMessage(4);
430arObjectCache::purge($arMessageFour);
431if (! arObjectCache::isCached('arMessage', 4)) {
432        arObjectCache::store(new arMessage(4));
433
434        return arObjectCache::get('arMessage', 4);
435}
436```
437
438