Changes and new features for CModel

Do you have any major changes or feature requests for CModel and CFormModel? Please discuss in this thread. Thanks.

Would be nice to have certain methods like:

  • toArray();

  • toJSON();

Let’s discuss AR in a different thread. In this thread, we focus on the base model class. This is important because the base model serves as the common ground for other concrete model classes such as AR.

How are you going to use .toJSON()? What is the difference between .attributes and .toArray()?

The differences are that attributes its just about the fields of the table the model belongs to and both methods .toJSON and .toArray could also return the related models attributes in the requested format.

How I am going to use .toJSON? To have a client representation of the model and its relations to power my client JavaScript UI Elements.

For example, instead of loading a table with my AJAX call -CGridView paging, delete or update call, I just get a JSON or array representation and then update my UI elements, so to speed up the server execution time and allow the creation of more powerful and richer elements on the client.

If you look for example a ExtJS javascript library, you find out how client elements do automatically update themselves by JSON/Array responses from the server.

Edit: I imagine a future where a proper JQuery UI client library ‘plugs’ seamlessly into Yii allowing programmers to decide where to place the creation of its elements, Zii for server, Jii on the client? With dialogs, sliders, grids, tabs, etc, that communicate with Yii as easy as Yii does its things on the server side.

Another way is that the model could be initialized from JSON




   // call to external URL that returns JSON object with attributes and relation objects

   $json = EHttp::getUrl('http://www.external.com/getCountry?id=2');


   $model = new Country();

   $model->fromJSON($json);


   // now model has been initialized from JSON object

   // we can now display it on the view



Antonio Ramirez

  1. Why moving JSON encoding into model?

  2. How not to encode some properties?

  3. How to specify if we need to encode related models or not?

  1. Why moving JSON encoding into model?

To have a client representation of the model and its relations to power my JavaScript UI Elements, and possibly model information server exchange

  1. How not to encode some properties?

  2. How to specify if we need to encode related models or not?

This one is harder to answer, maybe through a flag on the methods




   return $model->toJSON(array('attributes'=>array('id'=>false),'relations'=>array('country'=>false)));



  1. I understand the purpose of an encoding itself. But why can’t we add related models handling to the current CJSON::encode? Why making encoding a part of the model?

Both ways are acceptable… didn’t think about improving CJSON

I think keeping JSON-related things in JSON class is better since after adding JSON I’ll need converting to XML etc.

Again, about the topic. It’s about CModel and CFormModel. So:

— attributes.

— errors.

— scenario.

— validators.

— events.

to get an instance of the class, the declaration of the method ‘model’ will not be needed anymore in child classes, and can be simplfied in CModel like




public static function model(){

   $modelClass=get_called_class();

   //same old logic

}



calling myModel::someMethod will now be possible with get_called_class and __callStatic, the implementation can be something like




public static function __callStatic($method,$args){

	return call_user_func_array(array(self::model(),$method),$args);

}

//usage

MyModel::setAttributes($_POST['MyModel'])->validate();

MyModel::findAll();



what do you think ?

This should be better discussed in CActiveRecord thread, as this thread is about the base model.

The declaration of the model() method is no longer needed, using PHP 5.3.

However, we probably will still use something like this to do query:




Post::model()->findAll().



The main reason is that we need a place to store the query criteria so that you can modify it using scopes, etc.

We may seek for a pattern to separate the finder class and the object storage class, though. Not decided yet.

I am actually refering to CModel, findAll is just an example of a very common usage of CModel + AR, my other example is still valid


CModel::setAttributes($attributes)->validate()

that refers to CModel in general, including CFormModel and AR

actually, by declaring __callStatic like I just posted, will do that, as it calls self::model() in __callStatic, returning an instance of the model

What you can do is




$model=MyFormModel::setScenario('create')->setAttributes($_POST);

if($model->validate()){

   echo 'works!!';

}



1 change needed to perform the code above is that setter methods needs to return $this;

this change may cause confusion at first, but will simplify things ,9 less chars to write, ‘model()->’ , a lot of times

as for the others functions of CModel, like samdark wrote

I’m very happy with the way it works now

I think we shouldn’t mix object and finder methods like $model=MyFormModel::setScenario(‘create’)->setAttributes($_POST). This looks too magical. $model = new MyFormModel(); is much more explicit.

btw., the fact that scopes are currently applied to all model objects instead of specific one confuses developers a lot.

Not sure, if this is also more related to AR only, but maybe it still makes sense in CModel:

I want automatic handling of localized formats for dates and numbers (both for displaying and parsing formatted values).

I would suggest, that for any attribute of <name> there should be an implicit attribute formatted<name>. This virtual attribute would be used as attribute name in forms:




<?php 

// Input for 'amount':

$form->textField($model,'formattedAmount');

It would handle formatting and parsing of localized numbers and dates. The model should parse formattedAmount into amount onBeforeValidate and, in case of an error, add errors to both, amount and formattedAmount. The formats should be configurable per attribute, scenario and target language in the model. We could have a method formats() that works similar to rules() but returns a number/date format for an attribute.

For AR some of this could be automated, as we know if something is a number or a date. We could have a global default format for numbers and dates.

I was thinking about implementing a behavior for this. But for full I18N support, something like this should be supported by the core.

Ah, prefixes. Could models - especially FormModels - also handle data types?




// in some controller

$model = new LoginForm();

if (isset($_POST['LoginForm']))

{

  $model->attributes = $_POST['LoginForm'];


  //

  // now, here all the attributes should have been converted into the correct datatype  

  //

}


// variant 1 - using prefixes

class LoginForm extends FormModel

{

  public $strUser = '';

  public $strPswd = '';

  public $bAutoLogin = false;

  public $iSessionTimeout = 60*60;

}


// variant 2 - using doc comments

class LoginForm extends FormModel

{

  /**

   * @var string

   */

  public $user = '';


  /**

   * @var string

   */

  public $pswd = '';


  /**

   * @var bool

   */

  public $autoLogin = false;


  /**

   * @var integer

   */

  public $sessionTimeout = 60*60;

}




Mike

Formatting is a part of the view layer. Any practical example where your way is better than just calling appropriate formatter in the view?

Ben

Aren’t they converted if proper rules such as boolean are used?

It’s also about parsing these values back into DB format. So it’s a simple automatism for back and forth conversion. We also have other view related properties in the model, e.g. labels or error messages.

The only workaround for now is to write custom getters/setters for each number/date column. This is very repetitive as it’s most often using the same formatting schema. Having support for this would make localization much less cumbersome when it comes to dates and numbers.

Is this a trick question? Is there a way to make it work? :unsure:

Hm… Here’s what I’m trying to do and the results:





// --- Model -----------------------------


class LoginForm extends CFormModel

{

  public $username='';

  public $password='';

  public $rememberMe=true;

  public $sessionTimeout=3600;


  public function rules()

  {

    return array(

      array('username, password', 'required'),

      array('rememberMe', 'boolean'),

      array('sessionTimeout', 'numerical', 'integerOnly' => true),

      array('password', 'authenticate'),

    );

  }

}


// --- in Controller -----------------------------


public function actionLogin()

{

  $model=new LoginForm;


  if(isset($_POST['LoginForm']))

  {

    CVarDumper::dump( $_POST['LoginForm'], 3, true );

    // array

    // (

    //     'username' => 'demo'

    //     'password' => 'demo'

    //     'rememberMe' => '1'

    //     'sessionTimeout' => '3600'

    // )


    // I like type safety. So ideally, conversion should happen on assignment

    $model->attributes = $_POST['LoginForm'];

    CVarDumper::dump( $model, 3, true );

    // LoginForm#1

    // (

    //     [username] => 'demo'

    //     [password] => 'demo'

    //     [rememberMe] => '1'

    //     [sessionTimeout] => '3600'

    //     ...

    // )


    // Up to now, the model still holds string data. Maybe after validation...

    $model->validate();

    CVarDumper::dump( $model, 3, true );

    // LoginForm#1

    // (

    //     [username] => 'demo'

    //     [password] => 'demo'

    //     [rememberMe] => '1'

    //     [sessionTimeout] => '3600'

    //     ...

    // )


    [...]

  }

}



What I liked the data to be is (at least after validation):




// LoginForm#1

// (

//     [username] => 'demo'

//     [password] => 'demo'

//     [rememberMe] => true

//     [sessionTimeout] => 3600

//     ...

// )



///////////////////

// EDIT:

Using validation rules for type conversion instead of prefixes or doc comments looks like a good idea to me.

Ben

It doesn’t produce true because http://www.yiiframework.com/doc/api/1.1/CBooleanValidator/#trueValue-detail is 1 by default. That’s to be able to write value into DB but you can change it if needed. You’re right about numerical validator. It doesn’t convert value into integer. Conversion via validators looks like a good idea.