Yii 1.1: arraymodel

Allows to access, validate and manipulate array data located on the file system by using a model
24 followers

Introduction

Sometimes it may be benefical to put static data into an associative array on the file system instead of into the database. This may include some sort of configuration data which needs to be loaded every time or repeating static data, like cities, countries, regions, provinces etc. Unfortunately, in Yii there is no kind of model to access and/or manipulate this kind of data. At this time, EArrayModel comes in.

Usage

EArrayModel has been designed in such way that the structure of the class is almost exactly the same as CActiveRecord, except the specific database functionalities. It has validate, scenarios, find, save (insert/update) and delete methods, but also the overwritable events before...() and after...(). This model also allows to search the data by using conditions. To understand the main usage of this model, I'd like to refer to the tutorials about models and active record on the website of the Yii Framework.

EArrayModel (like every other yii model) also works with attributes. These attributes are linked to a single array element. You can (actually must) define which element of the array is linked to which attribute. You do this by defining the data structure via overriding the method arrayStructure() (See paragraph Creating the model class). Besides these normal attributes defined in the array structure, we also have the magic id attribute. This id attribute is always present and refers to the unique key of a data array row. This id can also be changed like every other attribute. If the changed id already exists in the data file, then the data row with that corresponding id is getting overwritten.

Inserting a new data row

$model = new City;
$model->name = 'Amsterdam';
$model->position_x = 205;
$model->position_y = 673;
$model->save();

Updating an existing data row

$model = City::model()->findById(1);
$model->position_x = 100;
$model->position_y = 201;
$model->save();

Deleting an existing data row

$model = City::model()->findById(1);
$model->delete();

Deleting multiple rows

City::model()->deleteAll(); // Deletes all rows
City::model()->deleteAll(/*condition*/); // Deletes all rows matched by the condition

Finding rows

$model = City::model()->findById(4); // Returns a single row with id 4
$model = City::model()->find(); // Returns the first found row
 
$models = City::model()->findAll(); // Returns all rows, conditions may be used
$models = City::model()->findAll(array(/*condition*/)); // Returns all rows matched by the condition
$models = City::model()->findAll(array(/*condition*/), 5, 10); // Returns 5 rows matched by the condition, beginning on row number 10 (does exactly work like LIMIT in MySQL)

Using conditions to find array data

A condition needs to be an array and must consist of conditional statements with the structure 'attribute => value'. Every conditional statement needs to be seperated by an AND or OR operator. The condition behaves like normal IF statements in PHP and may contain the following comparison operators:

>, >=, <, <=, = (or ==), !=, ===, !==, %, !%

In addition to PHP there are two more operators, % and !%. These operators stand for respectively 'contains' and 'not contains'. It will check whether the given string can be found or not found inside the value of a specific attribute. The operator will search case insensitive.

When no comparison operator has been used, the condition will automatically function like exact matching (= or ==).

Just like in PHP, the condition can contain sub-conditions, sub-sub-conditions, and so forth. It can be realised by just adding new arrays, containing conditional statements. In this way, an endless condition can be created. Some examples:

Contact::model()->findAll(array(array('id' => 1)); // Return data with ID 1 (exact matching)
Contact::model()->findAll(array(array('id >' => 5), 'AND', array('id >' => 15)); // Matches data with an ID bigger than 5 and smaller than 15
Contact::model()->findAll(array(array('name %' => 'asd'), 'OR', array('name %' => 'qwe'))); // Matches data which contains asd or qwe
Contact::model()->findAll(array(array(array('first_name' => 'John'), 'OR', array('first_name' => 'Matthew')), 'AND', array('last_name' => 'Smith'))); // Matches data where the first name is John or Matthew and the last name is Smith

To make conditions a little bit more readable and shorter, you can leave out some array elements:

Normal usage:

Contact::model()->findAll(array(array('id >' => 5)));

Shorter usage

Contact::model()->findAll(array('id >' => 5));

However, with the shorter usage you can't use the same attributes inside one condition statement. With the following example, only the last attribute will be checked:

Contact::model()->findAll(array('id' => 5, 'OR', 'id' => 7)); // Only row with ID 7 will be returned

The following on the other hand will work as aspected, because of the >= and <= comparison operators.

Contact::model()->findAll(array('id >=' => 5, 'AND', 'id <=' => 7)); // All rows with ID 5, 6 and 7 will be returned

This limitation is there because the condition 'array' can't have two or more elements with the same key. The first id will be overwritten by the last defined id. In this case you still need to use the normal usage instead.

Creating the model class

Every array model class must extend from EArrayModel and needs to override a couple of methods. The structure of this class differs from CActiveModel by the methods fileName() and arrayStructure().

Example EArrayModel class:

<?php
class RankArray extends ArrayModel
{
    /**
    * Returns the static model of the specified AM class.
    * @return the static model class
    */
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }
 
    /**
    * @return string the associated array data file location (must be writable, to make use of the saving functionalities)
    */
    public function fileName()
    {
        return 'application.data.ranks';
    }
 
    /**
    * @return array the associated array structure ('key' => 'attribute'). The list of
    * available attributes will be based on this list.
    */
    public function arrayStructure()
    {
        return array(
            'name' => array(
                'male' => 'name_male',
                'female' => 'name_female',
            ),
            'points' => 'points',
        );
    }
 
    /**
    * @return array validation rules for model attributes.
    */
    public function rules()
    {
        return array(
            array('name_male, name_female', 'length', 'max' => 20),
        );
    }
 
    /**
    * @return array validation rules for model attributes.
    */
    public function attributeLabels()
    {
        return array();
    }
}
?>

Associated example data file

The data file protected/data/ranks.php, related to the RankArray model of the previous paragraph

<?php
/**
* Data file generated by RankArray (ArrayModel)
* Date: June 30, 2011, 9:57 pm
*
* Allowed data structure:
*   array(
*       'name' => array(
*           'male' => 'name_male',
*           'female' => 'name_female',
*       ),
*       'points' => 'points',
*   )
*/
return array(
    1 => array(
        'name' => array(
            'male' => 'Cleaner',
            'female' => 'Cleaner',
        ),
        'points' => 5,
    ),
    2 => array(
        'name' => array(
            'male' => 'Bagman',
            'female' => 'Bagman',
        ),
        'points' => 50,
    ),
);

Misc

$model->isNew; // Checks whether the current data model is new, works exactly like isNewRecord
ModelName::model()->getData(); // Returns the raw data for manual data manipulation
ModelName::model()->setData($data); // Set raw data manually after manipulation
ModelName::model()->flushData(); // Cleans the cached data and forces to renew the data on next data file access

Technical notes

  • Data is getting cached inside a static variable. In this way, the models won't access the file system every time when it needs some data. The cached data will of course automatically be updated after saving.
  • When inserting, updating or deleting data rows, the data file is getting saved every time. Be carefull when you need to save a lot of different models at the same time.
  • Use this model only for static data whose actually need to be changed sporadic. For dynamic data it would be far more efficient to use database active record instead. Also, changing data on file systems will lock the files for a very short amount of time. That could give nice php errors when two models are saving at the same time.

Installation

  1. Extract arraymodel from archive to protected/extensions
  2. Create a model for each array data file and configure it
  3. Create the corresponding data file and eventually make it writable. You can fill the file with the content <?php return array(); ?> (in the latest version, that's not necessary anymore)
  4. You're off to go

Requirements

  • Yii 1.1 or above (tested on 1.1.7)
  • Common sense

Todo

  • Better error handling with nice error texts
  • Offer a corresponding data provider, like CActiveDataProvider. As a temporary solution, CArrayDataProvider in combination with $yourArrayModel->getData() can be used.

Resources

  • http://www.yiiframework.com/doc/guide/1.1/en/basics.model
  • http://www.yiiframework.com/doc/guide/1.1/en/form.model
  • http://www.yiiframework.com/doc/guide/1.1/en/database.ar

In addition, I would like to thank the developers behind Yii for creating such great framework! ;-)

Total 1 comment

#7082 report it
Yureshwar Ravuri at 2012/02/23 12:44am
I think yii comes with all the features that were here

I am using all the functionality without adding this extension to my application except misc. I am working with all the mentioned syntax's.

Can you please explain me what exactly does this extension do.

Thanks

Leave a comment

Please to leave your comment.

Create extension
Downloads