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/