Wednesday, November 23, 2011

How to read a JSON POST with Yii, and save it to the database

Let's say you are sending a json-encoded object to your create action, and want to save it in your database. here's how:

public function actionCreate() {
 
//read the post input (use this technique if you have no post variable name):
  $post = file_get_contents("php://input");

  //decode json post input as php array:
  $data = CJSON::decode($post, true);

  //contact is a Yii model:
  $contact = new Contact();

  //load json data into model:
  $contact->attributes = $data;
//this is for responding to the client:
  $response = array();

  //save model, if that fails, get its validation errors:
  if ($contact->save() === false) {
    $response['success'] = false;
    $response['errors'] = $contact->errors;
  } else {
    $response['success'] = true;
    
    //respond with the saved contact in case the model/db changed any values
    $response['contacts'] = $contact; 
  }

  //respond with json content type:
  header('Content-type:application/json');
  
//encode the response as json:
  echo CJSON::encode($response);

  //use exit() if in debug mode and don't want to return debug output
  exit();
}

Friday, October 28, 2011

Abandoning Jquery and Server-Generated HTML, moving to ExtJS + Yii backend

Our team wasted far too long trying to get our web app working with jQuery. Finally we discovered the joys of ExtJS 4, and are moving all development there.

Still using Yii as the backend for its nice models and controllers, as well as RBAC.

Saturday, August 20, 2011

Which databases does Yii support?

Yii uses PDO, which is good, as it supports parameters, which can help to prevent SQL injection attacks.

As of Yii 1.1.8, it supports the following:

  • PostgreSQL
  • MySQL
  • sqlite 3 and sqlite 2
  • Microsoft SQL Server
  • Oracle
Yii also supports dblib

Some other useful database features supported by Yii include:
  • transactions
  • schema caching for ActiveRecord
  • query caching
  • null conversion
  • database cache dependencies
  • performance profiling

Wednesday, August 10, 2011

Use session instead of stateful forms

Here's why:


  1. If you decide to use AJAX later on (in particular I mean to HIJAX your forms), you'll have to set and get the YII_PAGE_STATE variable for every action, which is a pain in the ass. 
  2. page state is broken when you try to use partial rendering. Even if the partially rendered form is a complete CActiveForm, YII_PAGE_STATE will end up blank. 
I am referring to pages that post back to themselves and that have related sub-items for which you need to maintain state. 

Saturday, August 6, 2011

How to install phpunit on Windows

the PHPUnit installer appears to be written by someone with mild autism....there's no other explanation for it.

either way, here's how i was able to install phpunit:


open the command prompt, type: (enter for each line)
  1. pear channel-update pear.php.net
  2. pear upgrade-all
  3. pear channel-discover pear.phpunit.de
  4. pear channel-discover components.ez.no
  5. pear channel-discover pear.symfony-project.com
  6. pear update-channels
  7. pear install -a -f phpunit/PHPUnit

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)

Wednesday, June 29, 2011

CClientScript positioning

CClientScript is useful for including css files and javscript files, while avoiding duplication.

Things to know:

1. Yii injects css files just above the <title> tag. So, if you want to always override some included yii style, put your <styles> or <link rel=stylesheets> AFTER the <title> tag, and it will get loaded after the yii styles.

2. I recommend putting all your <styles> and <link rel=stylsheets> in the <head> and all your <scripts> just before </body>. <scripts> are blocking, so your page will load faster if the <scripts> are at the bottom. use:

Yii::app()->getClientScript()->coreScriptPosition = CClientScript::POS_END;

3. if you want to include some inline javascript in a view, but make it load at the bottom, after say jquery, use the registerScript() method. To format your js nicely, you can use the Heredoc syntax:

/* load some formatted js into a php variable: */
$js = <<<EOF
var = 'some javascript here!';
    function() { return 'you can format it as you like, and include php $variables'; };
EOF;

/* write the script at the bottom of the document  */
Yii::app()->getClientScript()->registerScript("some id", $js, CClientScript::POS_END);

Thursday, June 16, 2011

Shades of Grey (or Gray :)

black(Safe 16 SVG Hex3) #000000
gray1 #030303gray2 #050505
gray3 #080808
gray4 #0A0A0Agray5 #0D0D0D
gray6 #0F0F0F
gray7 #121212gray8 #141414
gray9 #171717
gray10 #1A1A1Agray11 #1C1C1C
gray12 #1F1F1F
gray13 #212121gray14 #242424
gray15 #262626
gray16 #292929gray17 #2B2B2B
gray18 #2E2E2E
gray19 #303030gray20(Safe Hex3) #333333
gray21 #363636
gray22 #383838gray23 #3B3B3B
gray24 #3D3D3D
gray25 #404040gray26 #424242
gray27 #454545
gray28 #474747gray29 #4A4A4A
gray30 #4D4D4D
gray31 #4F4F4Fgray32 #525252
gray33(Hex3) #555555
gray34 #575757gray35 #595959
gray36 #5C5C5C
gray37 #5E5E5Egray38 #616161
gray39 #636363
gray40(Safe Hex3) #666666dimgrey(SVG) #696969
dimgray(SVG) #696969
gray42 #6B6B6Bgray43 #6E6E6E
gray44 #707070
gray45 #737373gray46 #757575
gray47 #787878
gray48 #7A7A7Agray49 #7D7D7D
grey(16 SVG) #808080
gray50 #7F7F7Fgray(16 SVG) #808080
gray51 #828282
gray52 #858585gray53 #878787
gray54 #8A8A8A
gray55 #8C8C8Cgray56 #8F8F8F
gray57 #919191
gray58 #949494gray59 #969696
gray60(Safe Hex3) #999999
gray61 #9C9C9Cgray62 #9E9E9E
gray63 #A1A1A1
gray64 #A3A3A3gray65 #A6A6A6
darkgray(SVG) #A9A9A9
gray66 #A8A8A8darkgrey(SVG) #A9A9A9
gray67 #ABABAB
sgilightgray(Hex3) #AAAAAAgray68 #ADADAD
gray69 #B0B0B0
gray70 #B3B3B3gray71 #B5B5B5
gray72 #B8B8B8
gray73 #BABABAgray74 #BDBDBD
silver(16 SVG) #C0C0C0
gray #BEBEBEgray75 #BFBFBF
gray76 #C2C2C2
gray77 #C4C4C4gray78 #C7C7C7
gray79 #C9C9C9
verylightgrey #CDCDCDgray80(Safe Hex3) #CCCCCC
gray81 #CFCFCF
gray82 #D1D1D1gray83 #D4D4D4
lightgrey(SVG) #D3D3D3
lightgray(SVG) #D3D3D3gray84 #D6D6D6
gray85 #D9D9D9
gainsboro(SVG) #DCDCDCgray86 #DBDBDB
gray87 #DEDEDE
gray88 #E0E0E0gray89 #E3E3E3
gray90 #E5E5E5
gray91 #E8E8E8gray92 #EBEBEB
gray93 #EDEDED
gray94 #F0F0F0gray95 #F2F2F2
whitesmoke(SVG) #F5F5F5
gray97 #F7F7F7gray98 #FAFAFA
gray99 #FCFCFC
white(Safe 16 SVG Hex3) #FFFFFF

Cross-Browser CSS Horizontal Centering


/* use for inline (such as text or images) children */
.hcenter-child
{
text-align:center;
}
/*reset the text align, as it inherits*/
.hcenter-child *
{
text-align:left;
}

/* use for block children (ex divs) */
.hcenter
{
width:96%; /*needs any width other than auto*/
margin-left:auto;
margin-right:auto;
}

Cross-Browser CSS Border Radius


.round-borders
{
-moz-border-radius:5px;
-webkit-border-radius:5px;
border-radius:5px;

/*see http://tumble.sneak.co.nz/post/928998513/fixing-the-background-bleed*/
-moz-background-clip: padding;
-webkit-background-clip: padding-box;
background-clip: padding-box;
/*use a behavior file for border radius for IE6-8. see http://www.impressivewebs.com/css3-rounded-corners-in-internet-explorer/ 
or try the jquery corner plugin */

}


Cross-Browser CSS Opacity

.opacity
{
opacity:.5; /*50%*/
filter:alpha(opacity=50);
zoom:1; /*iefix*/
}



Cross-Browser CSS Linear Gradients


.gradient
{

background-color: #DDDDDD; /*fallback*/
background-image: -webkit-gradient(linear, left top, left bottom, from(#FFFFFF), to(#CCCCCC)); /*safari4+,chrome*/
background-image: -webkit-linear-gradient(top, #FFFFFF, #CCCCCC); /*chrome10+,safari5.1+*/
background-image: -moz-linear-gradient(top, #FFFFFF, #CCCCCC); /*ff3.6+*/
background-image: -ms-linear-gradient(top, #FFFFFF, #CCCCCC); /*ie10*/
background-image: -o-linear-gradient(top, #FFFFFF, #CCCCCC); /*opera11.1+*/
background-image: linear-gradient(top, #FFFFFF, #CCCCCC);

zoom:1; /*iefix*/
/*GradientType 0=vertical, 1=horizontal*/
filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorStr='#FFFFFF', EndColorStr='#CCCCCC');

}

Cross-Browser CSS Box Shadows

I'm starting a series on cross-browser CSS.

Here's how to make a box shadow in Safari, Chrome, Firefox, Opera, and IE5.5+

.box-shadow
{

/* parameters: horizontal-offset vertical-offset blur-radius color*/

-moz-box-shadow:5px 5px 5px #cccccc; /*ff3.5+*/

-webkit-box-shadow:5px 5px 5px #cccccc; /*safari3.2+*/

box-shadow:5px 5px 5px #cccccc; /*chrome3+, opera10.5+, ie9+*/

/* strength=shadow length. direction=angle in degrees clockwise from midnight. color=name or #nnnnnn*/

filter: progid:DXImageTransform.Microsoft.Shadow(Strength=5, Direction=135, Color=#cccccc); /*ie5.5-8*/

zoom:1; /*iefix*/

}

Tuesday, June 14, 2011

How to fix font and color issues when you put a yii widget inside a jquery widget

If you put a Yii widget inside a jQueryUI widget, then it doesn't look quite right. Here's how to fix it

put this style sheet after you load jQueryUI's style sheet:

<style>
  /*use your site's font and font size*/
  .ui-widget {font-family:inherit; font-size:inherit;}

  /* make form controls inside jqueryui widgets use site font */
  .ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button {font-family:inherit; }

  /* links go black inside jqueryui. this changes colour of autocomplete however... */
  .ui-widget-content a {color:#06C;}
</style>

Alternatively, you could just edit jQueryUI's style sheet, or roll your own theme.

Also, the blueprint css framework makes jQueryUI's datepicker look wrong. Look for the typography section of screen.css, and comment out this section:

/* thead th      { background: #c3d9ff; } */

Thursday, April 28, 2011

Yii » Missing Features

This will be an ongoing list. Check back in the future.

  1. Yii needs a grid that you can edit inline - maybe jqgrid for version 2?
  2. The listData->groupField works nicely on dropDownLists, but it would be nice it it worked on checkBoxLists and radioButtonLists too (displaying the category name and indenting associated controls)
  3. No concurrency control. It would be nice in Gii if it could generate models that saved the original record values, as well as using them to check for concurrency when saving. 
  4. Easier to use some of the new HTML 5 controls, like "search"
  5. Built-in basic search on lists (much like on grids)
  6. Easier tabular input
  7. When you do ajax on a grid (sort or page), it reloads the whole page, finds the grid content, and replaces itself. This is a little inefficient ;) . It would be better if Gii could split the grids out to separate partial views. 
  8. Yii seems to get confused sometimes when you have nested JQuery UI controls
  9. It would be better if the built-in Blueprint theme was mobile-friendly out of the box, and by that I mean side by side columns that collapse on top of each other on small width screens, perhaps switch to the 1140 grid system
  10. use superfish on multi-level menus out' the box
  11. ZeroClipboard, jQuery WYSIWYG editor, and fullcalendar support out' the box.
  12. phpMailer, a flash/html5/silverlight multi-uploader, vcard, pdf, ics support out' the box

Sunday, April 17, 2011

How to hide the url address bar on a web page on Blackberry

Here's how to minimize the url address bar in a web page on a blackberry (blackberry 6 anyways).

Put this script at the bottom of the page, or call it ondocumentready:

if(navigator.userAgent.toLowerCase().indexOf('blackberry')>0)window.scrollTo(0,40);

It causes the browser to scroll down 40 pixels, which essentially makes the browser bar hide itself.

Tuesday, April 12, 2011

How to make vCard QR Codes that are compatible with iPhone, Android and Blackberry

I've been working on a web page where you can scan a company's vCard easily on your smartphone and have it saved to your contact list. It's definitely a pain in the ass. Here is a sample QR code with a vCard in it:



Here are some tips:
  • use the Image Chart Editor on Google Code - you have to look for the QR Code chart in the gallery. Basically, you send it a URL with the vCard in the querystring and it sends back a QR Code image. Don't forget to url-encode your querystring. 
  • You don't need to email a vCard to iPhone, it can read them directly from a QR Code with the right app.
  • I tried a few different QR Code readers for iPhone and most of them sucked at parsing vCards. The best that i found was Qrafter, and it's free.
  • I used Barcode Scanner on Android. 
  • Android didn't want to accept a URL, so I put it in the NOTE as well. Any tips?
  • With Blackberry 6, use AppWorld, hit the menu, then choose Scan a barcode. 
  • Blackberry can't read vCards directly from a QR Code, but you can qr-code a URL that returns a vCard. You'll want to send back this PHP header: header("Content-type:text/x-vcard");
Young, bored, and know Java? Please write a decent QR code reader for blackberry :)

Here is a vCard that I tested with Windows 7, Blackberry 6, iPhone 4 and Android 2.3.3

Thursday, March 31, 2011

How to implement simple and easy search functionality on an index page

Here's how to implement simple and easy search functionality on an index page:

1. Let's say your controller looks like this:
public function actionIndex()
{
  $dataProvider=new CActiveDataProvider('Model');
  $this->render('index',array(
  'dataProvider'=>$dataProvider,
  ));
}
2. Change it to this:
public function actionIndex()
{
    $criteria = new CDbCriteria();

    if(isset($_GET['q']))
    {
      $q = $_GET['q'];
      $criteria->compare('attribute1', $q, true, 'OR');
      $criteria->compare('attribute2', $q, true, 'OR');
    }

    $dataProvider=new CActiveDataProvider("Model", array('criteria'=>$criteria));

    $this->render('index',array(
      'dataProvider'=>$dataProvider,
    ));
}
The above will read in the "q" (for query) parameter, and use the compare function to create the sql to search a few attributes for that value. Note the use of the 'OR' operator.

3. In your index view, add this:
<form method="get">
<input type="search" placeholder="search" name="q" value="<?=isset($_GET['q']) ? CHtml::encode($_GET['q']) : '' ; ?>" />
<input type="submit" value="search" />
</form>
The above creates a form that will submit to itself using the querystring. It displays a search input box, which is a text input box with a "cancel" command. It works in most browsers and defaults to a text field in the rest. When the user hits the search button, the form is submitted and the data is filtered by the search value.

Wednesday, March 30, 2011

How to hide index.php in the url in a Yii website

to hide "index.php" from the url on a website, add an .htaccess file to your web root, with this text:

Options +FollowSymLinks
IndexIgnore */*
RewriteEngine on

# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

# otherwise forward it to index.php
RewriteRule . index.php

and in /protected/config/main.php, set:


'urlManager'=>array(
'urlFormat'=>'path',
   'showScriptName'=>false,
   'caseSensitive'=>false,

Friday, March 25, 2011

Adventures in CSS: a seal of approval

Here's how to make a nice "seal of approval" using only cascading style sheets. This only works in REAL browsers for now (webkit/firefox), but there are other options for IE

Approved!


it should look like this image:




Here we have a simple div tag:

<div class="seal">Approved!</div>

And we apply some css styles to it:

1. to make it a circle, apply border-radius:50%;
2. to rotate the div, use -webkit-transform: rotate(20deg);
3. give the background a nice radial gradient with: -webkit-gradient(radial, 50% 0%, 1, 50% 0%, 200, from(#B00000), to(#600000));
4. give it a subtle shadow with: box-shadow: 2px 2px 2px gray;

Here's the full CSS:



.seal
{
    height:200px;
    width:200px;
    text-align:center;
    color:white;
    font-family:Trebuchet MS;
    font-size:xx-large;
    line-height:200px; /*center text vertically*/
    background-color:#600000; /*fallback for other browsers*/

    border-radius:50%;
    -moz-border-radius:50%;

    -webkit-transform:rotate(20deg);
    -moz-transform: rotate(20deg);

    background: -webkit-gradient(radial, 50% 0%, 1, 50% 0%, 200, from(#B00000), to(#600000));
    background: -moz-radial-gradient(50% 0%, cover, #B00000, #600000);

    box-shadow: 2px 2px 2px gray;
    -moz-box-shadow: 2px 2px 2px gray;
}


Wednesday, March 16, 2011

How to override Yii's block radio button list labels and make them inline

By default, Yii radio buttons, radiobuttonlists, and checkboxlists look dumb. The label is on a different line, which is not a great design.

Here's how to fix it:

In your form style sheet (form.css), set:

input[type=radio] + label, input[type=checkbox] + label { display:inline !important; }

that's it, you're done.

What it does is says anytime you have a radio button with a label element directly after it, make that label inline instead of block, meaning that there won't be a line break.

I would also recommend changing Yii's default checkbox label positioning to before the label. 

Friday, March 11, 2011

How to change a file extension in NetBeans

I had a .html file that I wanted to change to .php, but NetBeans doesn't let you change the extension with the Rename or Refactor menu option.

To change the file extension in NetBeans, right-click the file and choose Properties. In the Properties window, under Extension, change the value, and hit Close. 

Wednesday, March 9, 2011

Yii: How to set a value in the parent layout from a child view

Say you have a search box at the top of your page, but you want to make it contextual to the area that you're in. For example, on the contacts view, you want the search button to say "Search Contacts". Your search button is on the layouts/main.php page. Here's how:

open protected/components/Controller.php

add a variable, such as "searchButtonArea"

in layouts/main.php, add that value to the search buttons, such as <?="Search $this->searchButtonArea"?>

then, in your contact views, set $this->searchButtonArea = "Contacts";

Or, add a property to the ContactsController, $searchButtonArea='Contacts';

Saturday, March 5, 2011

How to get debug information about your Yii application quickly

<?php

var_dump(Yii::app());

phpinfo();

?>
will tell you a lot about your current Yii app. This will show your database connection passwords, so be careful.

How to make a mobile theme in Yii

Making a mobile theme in Yii is pretty easy. Getting it running smoothly is more work. Here's how:

Making the Theme


1. under /themes/ create a folder "mobile"

2. copy main.php from /protected/views/layouts/ to the /themes/mobile/views/layouts/ folder. You don't need to copy anything else. Views, etc will be inherited.

3. The blueprint CSS framework doesn't seem to be too mobile friendly. By default, there will be a lot of horizontal scrolling. So, in /themes/mobile/views/layouts/main.php, remove these lines:

<!-- blueprint CSS framework -->
<link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->request->baseUrl; ?>/css/screen.css" media="screen, projection" />
<link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->request->baseUrl; ?>/css/print.css" media="print" />
<!--[if lt IE 8]>
<link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->request->baseUrl; ?>/css/ie.css" media="screen, projection" />
<![endif]-->

You can test it out by setting 'theme'=>'mobile' in /protected/config/main.php

Now, you want to detect the browser type and set the right theme (which i'll cover next), BUT you also want to give users a choice. For example, you should be able to test the mobile site on your desktop web browser, without changing the user-agent string.

* one thing you may not know is that Yii looks for the classic theme on start, which is empty. But you can put views or layouts there and they will be used without configuration change.

You can add some styling to the mobile theme to make it look better. Here are some items to consider:



<style>
    input[type=text], input[type=password], textarea {max-width:260px;}
    h1,h2,h3,h4,h5,h6 {font-weight:normal;}
    html, body, #content {margin:0; padding:0;}
    #content h1, #logo, #footer, .portlet-title {display:none;}
    #sidebar {padding:0; margin:0; margin-top:5px;}
    * {float:none; }
</style>


Wiring up the Theme


So, do this. In /protected/config/main.php, set
'onBeginRequest'=>array('MyClass', 'BeginRequest'),
in /protected/components/ create a file MyClass.php like this:
class MyClass
{
  public function BeginRequest(CEvent $event)
  {
    Yii::app()->theme = Yii::app()->session['theme'];
  }
}
This will look for a theme name in the session. If it does not exist, Yii will use no theme (it will look like it does normally).

To switch themes, make a new controller. In /protected/controllers/ create BrowserController.php, and add this text:
class BrowserController extends Controller
{
    public function actionIndex($theme)
    {
      Yii::app()->session['theme'] = $theme;
      $referrer = Yii::app()->request->urlReferrer ? Yii::app()->request->urlReferrer : "/mob"; //or whatever your website root is
        Yii::app()->request->redirect($referrer);
    }
}
The above will take a theme name as a querystring parameter and put it in session, and redirect to either the HTTP Referrer or to the root of your site. You could also look for this directly in BeginRequest

To switch to the mobile theme, go to webroot/browser/mobile and to switch back go to webroot/browser/anythingelse

Wednesday, February 23, 2011

How to send a Yii log using GMail

PHP's mail() function does not support authentication, so you cannot use it to send emails using GMail's SMTP servers.

If you want to log Yii errors to an email account, Yii uses mail() by default, so you have to override this. Here's how:

Download and install PHP Mailer from http://phpmailer.worxware.com/. Put the PHP files in your protected/components folder

Create a new file in your protected/components folder called CPhpMailerLogRoute.php, with the following code:
class CPhpMailerLogRoute extends CEmailLogRoute
{
    protected function sendEmail($email, $subject, $message)
    {
        $mail = new phpmailer();
        $mail->IsSMTP();
        $mail->Host = "smtp.gmail.com";
        $mail->SMTPAuth = true;
        $mail->SMTPSecure = "tls";
        $mail->Port = 587;
        $mail->Username = "your gmail username";
        $mail->Password = "your gmail password"; //best to keep this in your config file
        $mail->Subject = $subject;
        $mail->Body = $message;
        $mail->addAddress($email);
        $mail->send();
    }
}
and then, in your configuration file [protected/config/main.php], in the 'components'=>'log'=>'routes' section, add the following:

array('class'=>'CPhpMailerLogRoute', 'levels'=>'error', 'emails'=>'user@example.com')

There you have it. Now you can send Yii errors via your GMail account.

Thursday, February 10, 2011

Simple HTML Dom is nice

Recently, I had to read a web page, make some minor changes to it, and save the results. I started with DOMDocument, but couldn't figure out how to write to a DOMNode.

Then I found Simple HTML DOM, and it's a nice piece of software.

It's as easy as this:
$html = file_get_html($url);

$someNode = $html->find($someXpath);
$someNode->innertext = "Hello, World!";

echo $html->save();
That's it!

Tuesday, January 25, 2011

How to run a cron job with Yii

Sometimes you need to run code on a timer, such as sending out email reminders based on database tables. This is how to do it.

1. In /protected/commands, add a file GoCommand.php with this content:
<?php

class GoCommand extends CConsoleCommand
{
    public function run($args)
    {
      echo 'Hello, world';
    }
}

?>
2. If you want to use your models, components and db connection, in /protected/config/console.php, copy over your 'import' and 'components' arrays from your main.config:

<?php

// This is the configuration for yiic console application.
return array(
 'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..',
 'name'=>'My Console Application',

     // autoloading model and component classes
 'import'=>array(
  'application.models.*',
  'application.components.*',
 ),

     // application components
 'components'=>array(

  'db'=>array(
   'connectionString' => 'mysql:host=localhost;dbname=my_db',
   'emulatePrepare' => true,
   'username' => 'root',
   'password' => 'password',
   'charset' => 'utf8',
   'tablePrefix'=>'tbl_',
  ),


 ),
);
3. then using the command prompt (cmd.exe in Windows), change directories to the protected folder of your web app, and type
yiic Go
and it should run your command.

You can use your models and components and most parts of the Yii framework, but web application stuff won't work, like sessions.

4. In Windows, you can use Task Scheduler to run the PHP file using the command line. Here's an article on how to do it:

http://www.devx.com/DevX/Article/39900/1763/page/3

Friday, January 14, 2011

Creating an instant search box for the CListView

We have our index page, with a CListView displaying a list of items, in this case contacts.

We want a single "instant" search box, that will filter the contacts list instantly.

Your index.php view page should look something like this:
<?php
$this->breadcrumbs=array(
'Contacts',
);

$this->menu=array(
array('label'=>'Create Contact', 'url'=>array('create')),
array('label'=>'Manage Contact', 'url'=>array('admin')),
);

//this adds a javascript event listener to the search box that will query the server with each keystroke. yw0 is the id of the clistview. q is the id of the text input box:
Yii::app()->clientScript->registerScript('search', "
  $('input#q').keyup(function(){
  $.fn.yiiListView.update('yw0', {
  data: $(this).serialize()
  });
  return false;
});
");
?>

<h1>Contacts</h1>

<!-- add a search box: -->
<input type="text" id="q" name="q" />

<?php $this->widget('zii.widgets.CListView', array(
'dataProvider'=>$dataProvider,
'itemView'=>'_view',
)); ?>
Change your actionIndex function in your ContactController to look something like this:
public function actionIndex() {
  $model = new Contact($scenario='search');
  $model->unsetAttributes();
  $model->first_name = $_GET['q'];

  //add the ->search() call: 
  $this->render('index',array('dataProvider'=>$model->search()))
}

Now when you type in the field, it should filter the contacts by their first name.

Improvements:
  • change onkeyup so it fires with a few characters and after a small delay
  • add a new search() function that will search several attributes at once using "OR" instead of "AND". This can be done in the compare() function with the 4th argument.
  • Some heavy caching on the server so that the database is not overwhelmed.

Tuesday, January 11, 2011

Things I learned today about Yii and PHP

You can embed a value in a string by using double quotes, BUT it doesn't work for child objects. Example: "hi, my name is $user->name" outputs "hi, my name is neil", but "hi, my name is $account->user->name" does not work. BUT, if you wrap it in curly braces, it works, like this: "hi, my name is {$account->user->name}"


If you are using a client certificate with cUrl, then the certificate path is absolute. Example: curl_setopt($handle, CURLOPT_SSLCERT, 'absolute path to cert here');


In SimpleXML, if you use the simplexml_load_string function, and the xml tags have hyphens, then you have to use curly braces to access them. Example: $xml = simplexml_load_string($some_xml); $value = $xml->{'some-xml-tag-with-hyphens'};


Use strttotime to parse strings as dates.


I think I found a bug in Yii. Database column was DECIMAL(2,1), which should allow for example "3.1" as a value, but Yii restricts the length of input to two characters, and it should be three, due to the decimal. Can anyone confirm?


I hate SOAP, and I hate XML. Much love for REST and JSON.


cUrl is slightly faster than file_get_contents for http requests.


Yii is nice, but Zend sure has a lot of useful functions. I'll stick with Yii as my framework, but make liberal use of Zend Framework library. Same for ezComponents.


The blueprint css framework is pretty nice. Yii doesn't use all of it, so I would recommend investigating it.


You can't use an array reference with a function call, like you can in JavaScript. Example: echo functionThatReturnsAnArray()[0]; does not work.

    Thursday, January 6, 2011

    A related hyperlink column in CGridView, CDetailView, CListView

    Say you have an order table and it has a status id. You want to display the status name and not the id, as a hyperlink, that links to the status view page, where you can see other info about that status. Here's how to do it:

    relation name: status_relation
    id field: status_id
    name field: name
    related controller name: status_controller
    label: Status

    In your 'columns' array in a CGridView declaration:

    array('name'=>'status_id', 'type'=>'raw', 'value'=>'CHtml::link($data->status_relation->name, array("status_controller/view", "id"=>$data->status_id))', 'header'=>'Status'),

    watch your single and double quotes

    $data is the name of the model record when inside a CGridView

    This method allows for searching and sorting, as long as you adjust your model's search function as per the previous post.

    In your 'attributes' array in your CDetailView:
    array('label'=>'Status', 'type'=>'raw', 'value'=>CHtml::link($model->status_relation->name, array('status_controller/view','id'=>$model->status_id))),
    

    In the _view of your CListView:
    CHtml::link(CHtml::encode($data->status->name), array('status_contoller/view', 'id'=>$data->status_id)); 
    

    Using relations in views

    [this is copied and edited from my post on stackoverflow]

    Relations are easier if you setup a foreign key in your database first. To do this you need to use MySQL (not SQLite) with the InnoDB engine (not MyISAM), and the field in question needs an index and foreign key on it. Then, Gii will setup the relations function for you automatically. Otherwise, you'll have to do it manually in the relations() function of the model in question.

    Imagine if you will, you have an integer field "status_id" on an order table, that relates to a status table "status_table", that has a column "name". You want to show the related "name", and not the status id.

    To use a related value in a View:

    In protected/views/[name]/view.php, in the CDetailView attributes array, change

    'status_id'

    to

    array('label'=>'Status', 'value'=>$model->status_relation->name)

    where status_relation is the name of the relation defined in the model

    To use a related value in an Index view, change protected/views/[name]/_view.php (note the underscore):

    $data->status_id

    to

    $data->status_relation->name

    To use a related value in an Admin view, in the CGridView widget function, in the columns array, replace

    'status_id'

    with

    array('name'=>'status_id', 'header'=>'Status', 'value'=>'$data->status_relation->name')

    (note the use of the variable $data, and not say $model or $dataProvider, and the quotes around $data).

    In your model's search function:
    public function search()
    {
    
      $criteria=new CDbCriteria;
    
      //add the magic letter 't' to refer to the 'main' (not the related) table:
      $criteria->compare('t.id',$this->id);
      $criteria->compare('status_relation.name',$this->status_id, true);
    
      //load the related table at the same time:
      $criteria->with=array('status_relation');
    
      return new CActiveDataProvider(get_class($this), array('criteria'=>$criteria,));
    }
    
    To use a drop-down menu, in protected/views/[name]/_form.php:

    change

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

    to

    $form->dropDownList($model,'status_id', CHtml::listData(StatusModel::model()->findAll(), 'status_id', 'name'));

    Wednesday, January 5, 2011

    Using Path URLs and hiding index.php

    Add a .htaccess file to your web application root (same level but not inside protected). Copy and paste one if necessary.

    The file contents should be this:

    Options +FollowSymLinks
    IndexIgnore */*
    RewriteEngine on

    # if a directory or a file exists, use it directly
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d

    # otherwise forward it to index.php
    RewriteRule . index.php


    Then in /protected/config/main.php, set 'showScriptName'=>false in the urlManager section.

    Tuesday, January 4, 2011

    Where to get help

    I'm not always available to answer questions on this blog, so if you're looking for help, I would suggest

    StackOverflow

    Or

    The Yii Forum

    What to install

    This post describes the common software tools that I recommend for developing with Yii.

    1. You need PHP, a web server such as Apache, and likely a database server such as MySql. You can install them individually, or use a package such as WAMP or XAMPP. I recommend XAMPP, because you can't uninstall WAMP.

    http://www.apachefriends.org/en/xampp.html

    2. You'll want an IDE, such as Netbeans or Eclipse PDT. I prefer Netbeans as it's simpler and faster.

    http://netbeans.org/features/php/

    [You'll need Java installed to run the above IDEs]

    3. You'll want the xdebug debugger for PHP:

    http://www.xdebug.org/

    4. If your apps will send emails, then I'd recommend PHPMailer:

    http://phpmailer.worxware.com/

    5. If you are planning on doing test-driven development (and you should), then you'll want PHPUnit and Selenium.

    http://www.phpunit.de/manual/current/en/index.html

    http://seleniumhq.org/

    6. And obviously the Yii framework!

    If you're on Windows, then you may need 7-zip to open the Yii .gz installer file.

    http://www.7-zip.org/

    You should also read this, to setup code completion in Netbeans:

    http://www.yiiframework.com/wiki/83/netbeans-ide-and-yii-projects/