Monday, July 18, 2011

How to output related values in json

CJSON::encode() is nice for outputting arrays of models or a dataProvider, but it doesnt output relations.

here's how to do it. This function will output an array of models, with their related values (as deep as you wanna go), in JSON format. Instead of showing every attribute, you indicate the ones you want to output using $attributeNames. The key is the CHtml::value() function, that can output related values easily.
/**
   * takes an array of models and their attributes names and outputs them as json. works with relations unlike CJSON::encode()
   * @param $models array an array of models, consider using $dataProvider->getData() here
   * @param $attributeNames string a comma delimited list of attribute names to output, for relations use relationName.attributeName
      * @return void doesn't return anything, but changes content type to json and outputs json and exits
   */

  function json_encode_with_relations(array $models, $attributeNames) {
    $attributeNames = explode(',', $attributeNames);

    $rows = array(); //the rows to output
    foreach ($models as $model) {
      $row = array(); //you will be copying in model attribute values to this array 
      foreach ($attributeNames as $name) {
        $name = trim($name); //in case of spaces around commas
        $row[$name] = CHtml::value($model, $name); //this function walks the relations 
      }
      $rows[] = $row;
    }
    header('Content-type:application/json');
    echo CJSON::encode($rows);
    exit(); //or Yii::app()->end() if you want the log to show in debug mode
  }

//usage:

$myModels = $myDataProvider->getData(); //or you can use $model->findAll();

$modelAttributeNames = 'id, subject, body, someStatisticalRelationName, someRelationName.attribute, someRelationName.someOtherRelationName.attribute';

json_encode_with_relations( $myModels, $modelAttributeNames );

Friday, July 15, 2011

The minimum necessary site structure to run Yii, Part ii

In a previous post , I discussed how to make a minimal site with Yii. Well, here's an even smaller one, with only two files, maybe five lines of code, and no folders:

index.php:
<?
//load yii framework (you'll likely have to change the path):
require_once( dirname(__FILE__).'/../../yii/framework/yii.php' );
//create the app. note that it does not require a config file, you can just pass in an array:
$app = Yii::createWebApplication( array (
  'basePath' => dirname( __FILE__ ) , // if you don't want Protected folder
  'controllerPath' => '' // if you don't want Controllers folder
))->run();

?>
SiteController.php:
<?
class SiteController extends CController
{
  public function actionIndex()
  {
    echo 'hello world';
  }
}
?>
Now, this doesn't work with Gii or crazy things like assets, but that's probably the smallest Yii installation you can make

Friday, July 8, 2011

Using CPropertyValue to convert types

Yii's CPropertyValue class is interesting. It has some useful methods to convert values. Some examples:
var_dump( CPropertyValue::ensureArray( '1,2,a' ) );
/* output: array 0 => string '1,2,3' (length=5) */

var_dump( CPropertyValue::ensureArray( '(1,2,"a")' ) );
/* output:
  array
    0 => int 1
    1=> int 2
    2 => string 'a' (length=1)
 */

var_dump( CPropertyValue::ensureBoolean( "TRUE" ) );
/* output: boolean true */

var_dump( CPropertyValue::ensureBoolean( "yii" ) );
/* output: boolean false */

var_dump( CPropertyValue::ensureFloat( "3.9" ) );
/* output: float 3.9  */

var_dump( CPropertyValue::ensureInteger( "3.9" ) );
/* output: int 3 */

var_dump( CPropertyValue::ensureObject( array("a"=>1, "b"=>2) ) );
/* output:
object(stdClass)[53]
  public 'a' => int 0
  public 'b' => int 1
 */

var_dump( CPropertyValue::ensureString( 5 ) );
/* output: string 5 (length=1) */
ensureEnum ensures that a value is among a list of enumerated values. pass it in a value and a class name that extends CEnumerable and it will throw an error if value is not among the enumerated values.
class SortDirection extends CEnumerable
{
  const Asc="Asc";
  const Desc="Desc";
}

var_dump( CPropertyValue::ensureEnum( "Up" , 'SortDirection' ) );
/* output: throws an error */

var_dump( CPropertyValue::ensureEnum( "Asc" , 'SortDirection' ) );
/* output: string 'Asc' (length=3) */

How to use the Filter Validator in Yii

Here's a quick example on how to use the Filter Validator in Yii.

The filter validator isn't actually a validator, it's a filter :) You give it a php function name to run, and it will run that function on the value of the attribute, when validate() is called.
class Address extends CFormModel /* or CActiveRecord */
{
  public $postal_code;
  public $street;

  public function rules()
  {
    return array(
 //call trim() when validate() is called
 array('street', 'filter', 'filter'=>'trim'),

 /* call my custom function filterPostalCode when validate() is called
  * you can pass it any callback function, but it should only have one parameter
  */
 array('postal_code', 'filter', 'filter'=>array( $this, 'filterPostalCode' )),

 /* if you are going to filter, then you should put the required validator last, as the validators are called in order */
 array('postal_code, street', 'required'),
    );
  }

  public function filterPostalCode($value)
  {
    //strip out non letters and numbers
    $value = preg_replace('/[^A-Za-z0-9]/', '', $value);
    return strtoupper($value);
  }
}
Usage:
$address = new Address;
$address->postal_code ="m5w-1e6";
$address->street = " 123 main street ";
$address->validate();
echo "$address->street $address->postal_code";
$address->postal_code ="/*-*/*-*/-";
$address->street = "     ";
$address->validate();
var_dump($address->errors);
Output:

123 main street M5W1E6
array
  'postal_code' => 
    array
      0 => string 'Postal Code cannot be blank.' (length=28)
  'street' => 
    array
      0 => string 'Street cannot be blank.' (length=23)