Showing posts with label PHP and Yii. Show all posts
Showing posts with label PHP and Yii. Show all posts

Tuesday, March 25, 2014

The author of Yii is Xue Qiang, who was a core programmer for Prado. On 2008, he invented Yii. The first release of Yii is on 3.Dec.2008.

Yii now is one of the best PHP framework, which supports MVC, DAO/ActiveRecord,I18N/L10N, Caching, Ajax, Role-based Control, Recaptcha, Plugin, Module, Gii, Event, Theme and Web services and etc.

The core Yii framework is base on quite a few great frameworks:


Framework Idea Reference
Prado Module Based application framework, Event-driven, Database abstraction layer, globalize and localize and etc.
Ruby on Rails Configuration Ideas, Active Record base and ORM
JQuery JQuery integrated
Symfony Filter and Plugin
Joomla Module based design and Message translation

With this blog series, we will have a deeper view on the source code of Yii on the following topics:
  1. Module based system: analyze the module and event-driven  base class(CComponent); Analyze the working principle of Modulized system; Analyze how WebApp create Controller.
  2. System components: study Yii system components, including logging router component, url management component, error handler component, cache component, role-based access control component and etc.
  3. Controller Layer: study Action and Filter, and give a in-depth analyze on these components
  4. Module Layer: study the DAO layer, Metadata and Command Builder and ORM
  5. View Layer, study how view is rendered, widget and client script component.

Monday, September 23, 2013

Yii: Install Elastica on Yii

Composer and Packagist

Composer is a brilliant dependency manager for PHP. List your project’s dependencies in a composer.json file and, with a few simple commands, Composer will automatically download your project’s dependencies and setup autoloading for you.
There are already a lot of PHP libraries that are compatible with Composer, ready to be used in your project. These “packages” are listed on Packagist, the official repository for Composer-compatible PHP libraries.

How to Install Composer

You can install Composer locally (in your current working directory; though this is no longer recommended) or globally (e.g. /usr/local/bin). Let’s assume you want to install Composer locally. From your project’s root directory:
curl -s https://getcomposer.org/installer | php
This will download composer.phar (a PHP binary archive). You can run this with php to manage your project dependencies. Please Note: If you pipe downloaded code directly into an interpreter, please read the code online first to confirm it is safe.

Install Elastica Using Composer

1.go to home folder, do 
 
vim composer.json
 
paste in: 
{
    "require": {
        "ruflin/Elastica": "dev-master"
    }
}
 
2. do:
 
composer install 

3. copy everything in to yii lib folder

4. when you want to use Elastica, just do:

require('[your base path]/protected/lib/elastica/autoload.php');

Monday, August 5, 2013

Nginx Configuration and Optimizing Tips and Tricks


Organize Nginx Configuration Files

Normally Nginx configuration files are located under /etc/nginx path.
One good way to organize configuration files is use Debian/Ubuntu Apache style setup:

## Main configuration file ##
/etc/nginx/nginx.conf
 
## Virtualhost configuration files on ##
/etc/nginx/sites-available/
/etc/nginx/sites-enabled/
 
## Other config files on (if needed) ##
/etc/nginx/conf.d/

Virtualhost files have 2 paths, because sites-available directory can contain any stuff, like test configs, just copied/created configs, old configs and so on. And sites-enabled contains only really enabled configurations, actually just only symbolic links to sites-available directory.

Remember add following includes at the end of your nginx.conf file:

## Load virtual host conf files. ##
include /etc/nginx/sites-enabled/*;
 
## Load another configs from conf.d/ ##
include /etc/nginx/conf.d/*;

Determine Nginx worker_processes and worker_connections

Default setup is okay for worker_processes and worker_connections, but these values could be little bit optimized:

max_clients = worker_processes * worker_connections

Just Nginx basic setup can handle hundreds of concurrent connection:

worker_processes  1;
worker_connections  1024;

Normally 1000 concurrent connection / per one server is good, but sometimes other parts like disks on server might be slow, and it causes that the Nginx is locked on I/O operations. To avoid locking use example following setup: one worker_precess / per processor core, like: Worker Processes

worker_processes [number of processor cores];

To check how many processor cores do you have, run following command:

cat /proc/cpuinfo |grep processor
processor : 0
processor : 1
processor : 2
processor : 3

So here is 4 cores and worker_processes final setup could be following:

worker_processes 4;

Worker Connections
Personally I stick with 1024 worker connections, because I don’t have any reason to raise this value. But if example 4096 connections per second is not enough then it’s possible to try to double this and set 2048 connections per process.

worker_processes final setup could be following:

worker_connections 1024;

I have seen some configurations where server admins are used too much Apache and think if I set Nginx worker_processes to 50 and worker_connections to 20000 then my server could handle all traffic once what we get monthly…but yes it’s not true. It’s just wasting of resources and might cause some serious problems…


Hide Nginx Server Tokens / Hide Nginx version number

his is good for security reasons hide server tokens / hide Nginx version number, especially, if run some outdated version of Nginx. This is very easy to do just set server_tokens off under http/server/location section, like:


Nginx Request / Upload Max Body Size (client_max_body_size)

If you want to allow users upload something or upload personally something over the HTTP then you should maybe increase post size. It can be done with client_max_body_size value which goes under http/server/location section. On default it’s 1 Mb, but it can be set example to 20 Mb and also increase buffer size with following configuration:

client_max_body_size 20m;
client_body_buffer_size 128k;

If you get following error, then you know that client_max_body_size is too low:
“Request Entity Too Large” (413)


Nginx Cache Control for Static Files (Browser Cache Control Directives)

Browser caching is import if you want save resources and bandwith. It’s easy setup with Nginx, following is very basic setup where logging (access log and not found log) is turned off and expires headers are set to 360 days.

location ~* \.(jpg|jpeg|gif|png|css|js|ico|xml)$ {
    access_log        off;
    log_not_found     off;
    expires           360d;
}

If you want more complicated headers or some other expiration by filetypes then you could configure those separately.


Nginx Pass PHP requests to PHP-FPM

Here you could use default tpc/ip stack or use directly Unix socket connection. You have to also setup PHP-FPM listen exactly same ip:port or unix socket (with Unix socket also socket permission have to be right). Default setup is use ip:port (127.0.0.1:9000) you could of course change ips and ports what PHP-FPM listens. Here is very basic configuration with Unix socket example commented out:

# Pass PHP scripts to PHP-FPM
location ~* \.php$ {
    fastcgi_index   index.php;
    fastcgi_pass    127.0.0.1:9000;
    #fastcgi_pass   unix:/var/run/php-fpm/php-fpm.sock;
    include         fastcgi_params;
    fastcgi_param   SCRIPT_FILENAME    $document_root$fastcgi_script_name;
    fastcgi_param   SCRIPT_NAME        $fastcgi_script_name;
}

It’s also possible to run PHP-FPM another server and Nginx another.


Prevent (deny) Access to Hidden Files with Nginx

It’s very common that server root or other public directories have hidden files, which starts with dot (.) and normally those is not intended to site users. Public directories can contain version control files and directories, like .svn, some IDE properties files and .htaccess files. Following deny access and turn off logging for all hidden files.

location ~ /\. {
    access_log off;
    log_not_found off; 
    deny all;
}

PHP-FPM Configuration Tips and Tricks


PHP-FPM Configuration files

Normally PHP-FPM configuration files are located on /etc/php-fpm.conf file and /etc/php-fpm.d path. This is normally excellent start and all pool configs goes to /etc/php-fpm.d directory. You need to add following include line on your php-fpm.conf file:

include=/etc/php-fpm.d/*.conf

PHP-FPM Global Configuration Tweaks

Set up emergency_restart_threshold, emergency_restart_interval and process_control_timeout. Default values for these options are totally off, but I think it’s better use these options example like following:

emergency_restart_threshold 10
emergency_restart_interval 1m
process_control_timeout 10s

What this mean? So if 10 PHP-FPM child processes exit with SIGSEGV or SIGBUS within 1 minute then PHP-FPM restart automatically. This configuration also sets 10 seconds time limit for child processes to wait for a reaction on signals from master.


PHP-FPM Pools Configuration

With PHP-FPM it’s possible to use different pools for different sites and allocate resources very accurately and even use different users and groups for every pool. Following is just example configuration files structure for PHP-FPM pools for three different sites (or actually three different part of same site):
/etc/php-fpm.d/site.conf
/etc/php-fpm.d/blog.conf
/etc/php-fpm.d/forums.conf

Just example configurations for every pool:
/etc/php-fpm.d/site.conf

[site]
listen = 127.0.0.1:9000
user = site
group = site
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/slowlog-site.log
listen.allowed_clients = 127.0.0.1
pm = dynamic
pm.max_children = 5
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 200
listen.backlog = -1
pm.status_path = /status
request_terminate_timeout = 120s
rlimit_files = 131072
rlimit_core = unlimited
catch_workers_output = yes
env[HOSTNAME] = $HOSTNAME
env[TMP] = /tmp
env[TMPDIR] = /tmp
env[TEMP] = /tmp

/etc/php-fpm.d/blog.conf

[blog]
listen = 127.0.0.1:9001
user = blog
group = blog
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/slowlog-blog.log
listen.allowed_clients = 127.0.0.1
pm = dynamic
pm.max_children = 4
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 200
listen.backlog = -1
pm.status_path = /status
request_terminate_timeout = 120s
rlimit_files = 131072
rlimit_core = unlimited
catch_workers_output = yes
env[HOSTNAME] = $HOSTNAME
env[TMP] = /tmp
env[TMPDIR] = /tmp
env[TEMP] = /tmp

/etc/php-fpm.d/forums.conf

[forums]
listen = 127.0.0.1:9002
user = forums
group = forums
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/slowlog-forums.log
listen.allowed_clients = 127.0.0.1
pm = dynamic
pm.max_children = 10
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 400
listen.backlog = -1
pm.status_path = /status
request_terminate_timeout = 120s
rlimit_files = 131072
rlimit_core = unlimited
catch_workers_output = yes
env[HOSTNAME] = $HOSTNAME
env[TMP] = /tmp
env[TMPDIR] = /tmp
env[TEMP] = /tmp

So this is just example how to configure multiple different size pools.


PHP-FPM Pool Process Manager (pm) Configuration

Best way to use PHP-FPM process manager is use dynamic process management, so PHP-FPM processes are started only when needed. This is almost same style setup than Nginx worker_processes and worker_connections setup. So very high values does not mean necessarily anything good. Every process eat memory and of course if site have very high traffic and server lot’s of memory then higher values are right choise, but servers, like VPS (Virtual Private Servers) memory is normally limited to 256 Mb, 512 Mb, 1024 Mb. This low RAM is enough to handle even very high traffic (even dozens of requests per second), if it’s used wisely.

It’s good to test how many PHP-FPM processes a server could handle easily, first start Nginx and PHP-FPM and load some PHP pages, preferably all of the heaviest pages. Then check memory usage per PHP-FPM process example with Linux top or htop command. Let’s assume that the server has 512 Mb memory and 220 Mb could be used for PHP-FPM, every process use 24 Mb RAM (some huge content management system with plugins can easily use 20-40 Mb / per PHP page request or even more). Then simply calculate the server max_children value: 220 / 24 = 9.17

So good pm.max_children value is 9. This is based just quick average and later this could be something else when you see longer time memory usage / per process. After quick testing it’s much easier to setup pm.start_servers value, pm.min_spare_servers value and pm.max_spare_servers value.

Final example configuration could be following:

pm.max_children = 9
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 200

Max request per process is unlimited by default, but it’s good to set some low value, like 200 and avoid some memory issues. This style setup could handle large amount of requests, even if the numbers seems to be small.

Tuesday, July 30, 2013

Yii and AWS: Configure Yii on EC2 AMI

First you need to install essential modules on AMI. If you like to use Nginx, please take a look at tutorial here. If you like Apache more, please see here.
I myself likes to use Nginx. However, by default AWS Nginx config does not contain sites-avaliable and sites-enabled folder. I recommend using sites-enable and available structure to keep things organized.

Configure Nginx

First we make two directories within nginx config.

cd /etc/nginx/
sudo mkdir sites-available
sudo mkdir sites-enabled

then we need to config nginx.conf, we construct a new config file.

sudo vim nginx.temp

Paste the following code into the file without modification.Refer to Yii official website.

worker_processes 4;
pid /var/run/nginx.pid;

events {
 worker_connections 768;
 # multi_accept on;
}

http {

 ##
 # Basic Settings
 ##

 sendfile on;
 tcp_nopush on;
 tcp_nodelay on;
 keepalive_timeout 65;
 types_hash_max_size 2048;
 # server_tokens off;

 # server_names_hash_bucket_size 64;
 # server_name_in_redirect off;

 include /etc/nginx/mime.types;
 default_type application/octet-stream;

 ##
 # Logging Settings
 ##

 access_log /var/log/nginx/access.log;
 error_log /var/log/nginx/error.log;

 ##
 # Gzip Settings
 ##

 gzip on;
 gzip_disable "msie6";

 # gzip_vary on;
 # gzip_proxied any;
 # gzip_comp_level 6;
 # gzip_buffers 16 8k;
 # gzip_http_version 1.1;
 # gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

 ##
 # nginx-naxsi config
 ##
 # Uncomment it if you installed nginx-naxsi
 ##

 #include /etc/nginx/naxsi_core.rules;

 ##
 # nginx-passenger config
 ##
 # Uncomment it if you installed nginx-passenger
 ##
 
 #passenger_root /usr;
 #passenger_ruby /usr/bin/ruby;

 ##
 # Virtual Host Configs
 ##

 include /etc/nginx/conf.d/*.conf;
 include /etc/nginx/sites-enabled/*;
}


#mail {
# # See sample authentication script at:
# # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
# 
# # auth_http localhost/auth.php;
# # pop3_capabilities "TOP" "USER";
# # imap_capabilities "IMAP4rev1" "UIDPLUS";
# 
# server {
#  listen     localhost:110;
#  protocol   pop3;
#  proxy      on;
# }
# 
# server {
#  listen     localhost:143;
#  protocol   imap;
#  proxy      on;
# }
#}

then we replace the config file

sudo mv nginx.temp nginx.conf

after that we edit a nginx server file, replace the with your own domain

sudo vim sites-avaliable/

copy and edit the server info to the file, replace the domain with your own domain info and the host_path and document to be the root of your hosted files.

server {
    set $host_path "/var/www/html/";
    set $document "/var/www/html/";
    server_name  "domain";

    #access_log  /var/log/nginx/access.log  main;

    root   $host_path;
    set $yii_bootstrap "index.php";
    charset utf-8;

    location / {
        index  index.html $yii_bootstrap;
        try_files $uri $uri/ /$yii_bootstrap?$args;
    }

    location ~ ^/(protected|framework|themes/\w+/views) {
        deny  all;
    }

    #avoid processing of calls to unexisting static files by yii
    location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
        try_files $uri =404;
    }

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    location ~ \.php {
        fastcgi_split_path_info  ^(.+\.php)(.*)$;

        #let yii catch the calls to unexising PHP files
        set $fsn /$yii_bootstrap;
        if (-f $document$fastcgi_script_name){
            set $fsn $fastcgi_script_name;
        }

        fastcgi_pass   unix:/var/run/php-fpm/php-fpm.sock;
        include fastcgi_params;
        fastcgi_param  SCRIPT_FILENAME  $document$fsn;

        #PATH_INFO and PATH_TRANSLATED can be omitted, but RFC 3875 specifies them for CGI
        fastcgi_param  PATH_INFO        $fastcgi_path_info;
        fastcgi_param  PATH_TRANSLATED  $document$fsn;
    }

    location ~ /\.ht {
        deny  all;
    }
}


make a symbolic link from sites-enabled to sites-available, so even the file is deleted in sites-enabled, the file in sites-available is still there.

sudo ln -s sites-available/domain sites-enabled/domain

Make sure that you session was started automatically in /etc/php.ini config "session.auto_start = 1" or it was started manually by session_start() or Yii similar function

$session=new CHttpSession;
$session->open();

And check your session status by session_status() function. restart nginx and the server, everything will be fine.

The following guide is a step by step guide on installing and configuring Apache, PHP (with APC) & MySQL (LAMP Stack) on an Amazon EC2 instance running Amazon Linux AMI. The procedure has been tested on Amazon Linux AMI, but should also apply in general to Fedora/Red Hat/CentOS distributions.

1. Install Linux updates, set time zones, followed by GCC and Make


sudo yum -y update
sudo ln -sf /usr/share/zoneinfo/Singapore \
      /etc/localtime
sudo yum install -y gcc make

2. Install PHP and PHP extensions


sudo yum install -y php
sudo yum install -y php-devel php-mysql php-pdo
sudo yum install -y php-pear php-mbstring


sudo pear install Log
sudo yum install -y pcre-devel

3. Install Apache httpd


sudo yum -y install httpd
sudo service httpd start

4. Install MySQL


sudo yum -y install mysql-server mysql
sudo service mysqld start

5. Install PHP APC module


sudo yum -y install httpd-devel
sudo pecl install apc-beta
echo "extension=apc.so" | sudo tee /etc/php.d/apc.ini

Or Simply:


sudo yum install -y php-pecl-apc

6. MySQL Configuration


sudo vi /etc/my.cnf


[mysqld]
...
skip-external-locking
long_query_time=1
slow_query_log
slow_query_log_file=/var/log/log-slow-queries.log
log-bin=mysql-bin
server-id= 1
...


[mysqld_safe]
...
myisam_recover_options
...


[mysqld]
...
key_buffer_size = 128M
max_allowed_packet = 3M
table_open_cache = 64
read_buffer_size = 2M
read_rnd_buffer_size = 8M
myisam_sort_buffer_size = 16M
thread_cache_size = 8
query_cache_size= 32M
thread_concurrency = 8
innodb_buffer_pool_size = 128M
innodb_log_file_size = 32M
innodb_additional_mem_pool_size = 8M
innodb_log_buffer_size = 4M
...

Log Slow Queries:


sudo touch /var/log/log-slow-queries.log
sudo chown mysql.mysql /var/log/log-slow-queries.log


sudo /sbin/chkconfig --levels 235 mysqld on
sudo service mysqld start

7. PHP Configuration Changes in php.ini


sudo nano /etc/php.ini


expose_php = Off
memory_limit = 128M
date.timezone = America/Indianapolis

Note: Location of error file


/var/log/httpd/error.log

8. Auto Start Apache in EC2 Amazon Linux


sudo /sbin/chkconfig --levels 235 httpd on
sudo service httpd start

If this tutorial was helpful to you, then please do not forget to leave a comment, or two. :)

A step by step tutorial on installing and configuring PHP-FPM, Nginx, & MySQL on an Amazon EC2 instance running Amazon Linux AMI. The procedure has been tested on Amazon Linux AMI, but should also apply in general to Fedora/Red Hat/CentOS distributions. A brief introduction to FastCGI, PHP-FPM, and Nginx was posted in the article Understanding PHP, PHP-FPM and Nginx.

1. Install Linux updates, set time zones, followed by GCC and Make


sudo yum -y update
sudo ln -sf /usr/share/zoneinfo/Singapore \
      /etc/localtime
sudo yum install -y gcc make

2. Install Nginx and PHP-FPM


sudo yum install -y nginx php-fpm

3. Install PHP extensions


sudo yum install -y php-devel php-mysql php-pdo \
      php-pear php-mbstring php-cli php-odbc \
      php-imap php-gd php-xml php-soap

4. Install PHP-APC


sudo yum install -y php-pecl-apc
sudo yum install -y pcre-devel

5. Install MySQL


sudo yum -y install mysql-server mysql

6. Nginx Configuration


sudo nano /etc/nginx/conf.d/default.conf

If you want the location of your web root in the default Amazon Linux AMI directory (/var/www/html):


location / {
    root   /var/www/html;
    index  index.php index.html index.htm;
}
location ~ \.php$ {
      fastcgi_pass   unix:/var/run/php-fpm/php-fpm.sock;
      fastcgi_index  index.php;
      fastcgi_param  SCRIPT_FILENAME  /usr/share/nginx/
                       html$fastcgi_script_name;
      include        fastcgi_params;
  }

If you want the location of your web root in the default Nginx directory (/usr/share/nginx/html):


location / {
    root   /usr/share/nginx/html;
    index  index.php index.html index.htm;
}
location ~ \.php$ {
      fastcgi_pass   unix:/var/run/php-fpm/php-fpm.sock;
      fastcgi_index  index.php;
      fastcgi_param  SCRIPT_FILENAME  /usr/share/nginx/
                       html$fastcgi_script_name;
      include        fastcgi_params;
  }

7. PHP-FPM Configuration


sudo nano /etc/php-fpm.d/www.conf


[...]
;listen = 127.0.0.1:9000
listen = /var/run/php-fpm/php-fpm.sock
;listen.owner = nobody
listen.owner = nginx
;listen.group = nobody
listen.group = nginx
;listen.mode = 0666
listen.mode = 0664

user = nginx
group = nginx
[...]

8. Auto Start Nginx, PHP-FPM, and MySQL in EC2 Amazon Linux


sudo chkconfig nginx on
sudo chkconfig mysqld on
sudo chkconfig php-fpm on

9. Auto Start Nginx, PHP-FPM, and MySQL in EC2 Amazon Linux


sudo service php-fpm start
sudo service nginx start

if everything is well, you go to the root of your address, it will show you the start page of nginx.

AWS: Understanding PHP, PHP-FPM, and Nginx

This small article is a refresher on Common Gateway Interface (CGI), FastCGI, PHP-FPM, and Nginx operations involving PHP.

What is CGI?

As stated in RFC 3875 (CGI protocol v1.1) designed by the Internet Society (ISOC),
The Common Gateway Interface (CGI) allows an HTTP server and a CGI script to share responsibility for responding to client requests. [...] The server is responsible for managing connection, data transfer, transport, and network issues related to the client request, whereas the CGI script handles the application issues such as data access and document processing.
The way it works is that when a web server such as Apache or Nginx receives a request from a client, it executes the command to the application. The cons of CGI therefore are that a new process is started for each received request, leading to a severely cluttered server.

What is Fast Common Gateway Interface (FastCGI)?

FastCGI is essentially an evolution of CGI and has become the standard. The most essential benefit of using FastCGI over CGI is that unlike CGI, it uses persistent processes that can handle multiple requests. In fact, such requests receive responses within the same connection and additional requests might not need to create separate connections.

PHP and Nginx

The best solution for PHP interacting with Nginx is perhaps the PHP FastCGI Process Manager or PHP-FPM. The main advantage of using PHP-FPM is that it uses less memory and CPU as compared to any other standalone applications. It also daemonizes PHP, thereby turning it into a background process while providing a command-line like script for managing various PHP processes.

Friday, July 12, 2013

Yii: About dirname(__FILE__)

Was helping a friend fix his php script today. He was not too sure about what “dirname(__FILE__)” did.
dirname() is a PHP function which returns the directory name of a file. For example if file abc.txt was in “/tmp/abc.txt” then the dirname() function would return “/tmp” .
Example Usage;
<?php
$file = “/tmp/abc.txt”;
$path = dirname($file); // $path will now contain /tmp
?>

What does dirname(__FILE__) and basename(dirname(__FILE__)) do then?

The __FILE__ constant represents the running script. It will return the full path and file name of the running script. For example, the __FILE__ constant on my server would return “/var/www/html/index.php” for my index.php file which is in the “/var/www/html/” directory.
The basename() command is normally used in conjunction with the dirname() function to strip the parent directory from a full file name. For example “/var/www/html/abc.txt” when passed through basename() would return abc.txt. basename() also works on directories. So, basename() on “/var/www/html” would return “html” since in Linux directories are files.
Example Usage;
Imagine __FILE__ represents /var/www/html/index.php
<?php
echo dirname(__FILE__); // returns /var/www/html
echo basename(__FILE__); //returns index.php
echo basename(dirname(__FILE__)); //returns html
?>

Wednesday, July 10, 2013

Relational databases do not support inheritance. If we need to store inheritance in the database, then we should somehow support it through code. This code should be efficient, so it should generate as less JOINs as possible. A common solution to this problem was described by Martin Fowler and named single table inheritance.

When we use this pattern, we store all the class tree data in a single table and use the type field to determine a model for each row.

As an example, we will implement the single table inheritance for the following class tree:

   Car
     |- SportCar
     |- FamilyCar

Getting ready

  • Create a new application by using yiic webapp as described in the official guide
  • Create and set up a database. Add the following table:
  • CREATE TABLE `car` (
        `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
        `name` varchar(255) NOT NULL,
        `type` varchar(100) NOT NULL,
        PRIMARY KEY (`id`)
    );
    
    INSERT INTO `car` (`name`, `type`) VALUES ('Ford Focus', 'family'),
    ('Opel Astra', 'family'),
    ('Kia Ceed', 'family'),
    ('Porsche Boxster', 'sport'),
    ('Ferrari 550', 'sport');
    

How to do it...

  1. First, we will create the car model protected/models/Car.php as follows:
  2. <?php
    
    class Car extends CActiveRecord {
    
        public static function model($className = __CLASS__) {
            return parent::model($className);
        }
    
        public function tableName() {
            return 'car';
        }
    
        protected function instantiate($attributes) {
            switch ($attributes['type']) {
                case 'sport':
                    $class = 'SportCar';
                    break;
                case 'family':
                    $class = 'FamilyCar';
                    break;
                default:
                    $class = get_class($this);
            }
            $model = new $class(null);
            return $model;
        }
    
    }
    
  3. Then, we implement protected/models/SportCar.php as follows:
  4. <?php
    
    class SportCar extends Car {
    
        public static function model($className = __CLASS__) {
            return parent::model($className);
        }
    
        public function defaultScope() {
            return array(
                'condition' => "type='sport'",
            );
        }
    
    }
    
  5. Also implement protected/models/FamilyCar.php as follows:
  6. <?php
    
    class FamilyCar extends Car {
    
        public static function model($className = __CLASS__) {
            return parent::model($className);
        }
    
        public function defaultScope() {
            return array(
                'condition' => "type='family'",
            );
        }
    
    }
    
  7. Now create protected/controllers/TestController.php as follows:
  8. <?php
    
    class TestController extends CController {
    
        public function actionIndex() {
            echo "

    All cars

    "; $cars = Car::model()->findAll(); foreach ($cars as $car) { // Each car can be of class Car, SportCar or FamilyCar echo get_class($car) . ' ' . $car->name . "
    "; } echo "

    Sport cars only

    "; $sportCars = SportCar::model()->findAll(); foreach ($sportCars as $car) { // Each car should be SportCar echo get_class($car) . ' ' . $car->name . "
    "; } } }
  9. Run test/index and you should get the following output:

How it works...

The base model Car is a typically used Yii AR model except two added methods. tableName explicitly declares the table name to be used for the model. For the Car model alone, this does not make sense but for child models, it will return the same car table which is just what we want—a single table for the entire class tree. instantiate is used by AR internally to create a model instance from the raw data when we call methods, such as Car::model()->findAll(). We use a switch statement to create different classes based on the type attribute and use the same class if the attribute value is either not specified or points to the non-existing class. SportCar and FamilyCar models simply set the default AR scope, so when we find models with SportCar::model()-> methods, we will get the SportCar model only.

There's more...

Use the following references to learn more about the single table inheritance pattern and Yii

Active Record implementation:
  • http://martinfowler.com/eaaCatalog/singleTableInheritance.html
  • http://www.yiiframework.com/doc/api/CActiveRecord/

Tuesday, July 9, 2013

Yii: Highlighting code with Yii

If you are posting code, be it company's internal wiki or a public developer's blog, it is always better to have the syntax highlighted, so ones who read the code will feel comfortable. Yii has Pear Text_Highlighter code-highlighting class bundled. It is used to highlight Yii definitive guide examples and we can use it to do the same for our application. In this recipe, we will create a simple application that will allow adding, editing, and viewing code snippets.

Getting ready

  • Create a new application by using yiic webapp as described in the official guide at the following URL:
  • http://www.yiiframework.com/doc/guide/en/quickstart.first-app
  • Set up a database connection and create a table named snippet as follows:
  • CREATE TABLE `snippet` (
       `id` int(11) unsigned NOT NULL auto_increment,
       `title` varchar(255) NOT NULL,
       `code` text NOT NULL,
       `html` text NOT NULL,
       `language` varchar(20) NOT NULL,
       PRIMARY KEY (`id`)
    );
    
  • Generate a Snippet model by using Gii

How to do it...

  1. First, we will tweak the protected/models/Snippet.php model code. Change rules method to the following:
  2.     public function rules() {
            return array(
                array('title, code, language', 'required'),
                array('title', 'length', 'max' => 255),
                array('language', 'length', 'max' => 20),
            );
        }
    
  3. Add methods to the same Snippet model:
  4.     protected function afterValidate() {
            $highlighter = new CTextHighlighter();
            $highlighter->language = $this->language;
            $this->html = $highlighter->highlight($this->code);
            return parent::afterValidate();
        }
    
        public function getSupportedLanguages() {
            return array(
                'php' => 'PHP',
                'css' => 'CSS',
                'html' => 'HTML',
                'javascript' => 'JavaScript',
            );
        }
    
  5. The model is ready. Now we will create a controller. Therefore, create protected/controllers/SnippetController.php as follows:
  6. order = 'id DESC';
            $models = Snippet::model()->findAll();
            $this->render('index', array(
                'models' => $models,
            ));
        }
    
        public function actionView($id) {
            $model = Snippet::model()->findByPk($id);
            if (!$model)
                throw new CException(404);
            $this->render('view', array(
                'model' => $model,
            ));
        }
    
        public function actionAdd() {
            $model = new Snippet();
            $data = Yii::app()->request->getPost('Snippet');
            if ($data) {
                $model->setAttributes($data);
                if ($model->save())
                    $this->redirect(array('view', 'id' => $model->id));
            }
            $this->render('add', array(
                'model' => $model,
            ));
        }
    
        public function actionEdit($id) {
            $model = Snippet::model()->findByPk($id);
            if (!$model)
                throw new CHttpException(404);
            $data = Yii::app()->request->getPost('Snippet');
            if ($data) {
                $model->setAttributes($data);
                if ($model->save())
                    $this->redirect(array('view', 'id' => $model->id));
            }
            $this->render('edit', array(
                'model' => $model,
            ));
        }
    
    }
    
  7. Now views; create protected/views/snippet/index.php as follows:
  8. Snippets

    1. title), array('view', 'id' => $model->id) ) ?>
  9. Create protected/views/snippet/view.php as follows:
  10. → title) ?>

    $model->id)) ?>
    html ?>
  11. Create protected/views/snippet/add.php as follows:
  12. → Add snippet

    renderPartial('_form', array('model' => $model))?>
  13. Create protected/views/snippet/edit.php as follows:
  14. → Edit snippet

    renderPartial('_form', array('model' => $model))?>
  15. Create protected/views/snippet/_form.php as follows:
  16. 
    
    • getSupportedLanguages()) ?>
  17. That is it. Now run the snippet controller and try creating code snippets, as shown in the following screenshot:
  18. When it is viewed, it will look similar to the following:

How it works...

The snippet model's function is used to store the code and snippet title. Additionally, we have added the html and language fields. The first one (html) is used to store HTML representing the highlighted code and the language field is used for the snippet language (PHP, HTML, CSS, JavaScript, and so on). We need to store these, as we need them when we edit the the snippet. As we remove the safe rule from the Snippet model, we make title, code, and language as the required fields. There is no rule for html which means that it cannot be set through the form directly. The afterValidate method—as its name states—is executed after the validation gives us no errors. In this method, we transform the code which is stored in the code field to HTML representing the highlighted code in the html field by using the Yii's CTextHighlighter class and passing the language value to it. getSupportedLanguages returns languages we want to support in the value-label array. We use this method in the snippet form.

There's more...

In order to learn more about code highlighting, you can use the following resources:

Yii code highlighter
  • http://www.yiiframework.com/doc/api/CTextHighlighter
  • http://pear.php.net/package/Text_Highlighter/

More code highlighters
If Text_Highlighter bundled with Yii does not fit your needs, then there are many code highlighters available on the Internet. A few good examples are:
  • http://qbnz.com/highlighter/
  • http://softwaremaniacs.org/soft/highlight/en/

Yii: Handling variable number of inputs

Sometimes an application requires a form with variable number of inputs. For example, a task management application can provide a screen where you can add one or more tasks to your task list. An example of such an application is shown in the following screenshot:

By default, the page will display one task and two buttons: Add task will add another empty task and Save will reload a form with all the tasks added. Let's check how we can solve it.

Getting ready

Create a fresh application using yiic webapp.

How to do it...

For our example, we will not save any data into the database. Instead, we will learn how to get a form with variable number of fields up and running, and how to collect data submitted with it.
  1. Therefore, we will start with the task model. As we agreed not to use database, CFormModel will be enough. protected/models/Task.php:
  2. Now, the controller protected/controllers/TaskController.php:
  3. setAttributes($taskData);
                    if ($model->validate())
                        $models[] = $model;
                }
            }
            if (!empty($models)) {
    // We've received some models and validated them.
    // If you want to save the data you can do it here.
            }
            else
                $models[] = new Task();
            $this->render('index', array(
                'models' => $models,
            ));
        }
    
        public function actionField($index) {
            $model = new Task();
            $this->renderPartial('_task', array(
                'model' => $model,
                'index' => $index,
            ));
        }
    
    }
    
  4. Now, the pretected/views/task/index.php view:
    • renderPartial('_task', array( 'model' => $models[$i], 'index' => $i, )) ?>
    'tasks-add')) ?> clientScript->registerCoreScript ("jquery") ?>
  5. Finally, a partial protected/views/task/_task.php:
  6. That is it. Now run the index action of the task controller and check it in action, as shown in the following screenshot:

How it works...

Let's review how it works starting from the controller's index action. As we are working with more than one data item, we need to collect them accordingly. Same as with a single model, a form will pass its data in $_POST['model_name']. The only difference is that in our case, there will be an array of data items as follows:
        if (!empty($_POST['Task'])) {
            foreach ($_POST['Task'] as $taskData) {
                $model = new Task();
                $model->setAttributes($taskData);
                if ($model->validate())
                    $models[] = $model;
            }
        }
For each data item, we are creating a Task model, setting its attributes with data item and if it is valid, storing a model into the $models array as follows:
if (!empty($models)) {
// We've received some models and validated them.
// If you want to save the data you can do it here.
        }
        else
            $models[] = new Task();
If the $models array is not empty, then there is data passed from the form and it is valid. If there is no data in the $models, then we still need at least one item to show it in the form. We are not actually saving data in this example. When we use Active Record models, we can actually save data using $model->save().Then, we just render the index view when we render a form as follows:

    renderPartial('_task', array(
        'model' => $models[$i],
        'index' => $i,
    ))
    ?>

As there are many models, we need to render fields for each one. That is done in the _task view partial:
  • Note how we use active labels and active fields. For each one, we specify a model and a name in format [model_index]field_name. The preceding code will generate the following HTML:
  • Fields such as Task[0][title] are a special PHP feature. When submitted, parameters with such names are automatically parsed into PHP arrays. Now, let's review how the Add task button works:
     'tasks-add')) ?>
    clientScript->registerCoreScript("jquery") ?>
    
    
    We are adding a button with a class tasks-add and a jQuery script that attaches the onClick handler to it. On click, we send an AJAX request to the field action of our controller with parameter index. Parameter's value equals the number of data items in the form. The field action responds with a part of an HTML form that we append to what we already have.

    There's more…

    You can find additional information about handling multiple inputs in the official guide at the following URL:
    1. http://www.yiiframework.com/doc/guide/en/form.table

    Yii: Including resources into the page

    Yii has a special class named CClientScript that can help to include scripts, CSS, and other resources into the page.

    How to do it...

    We will start with including a script. There are three types of scripts, namely, external scripts, core scripts, and inline scripts.

    1. External script is a script located in a file and accessible through its own URL. For example, to include a script with URL http://example.com/js/main.js, you can use the following code:
    2. Yii::app()->clientScript->registerScriptFile("http://example.com/js/main.js");
      
    3. In order to control the place where script will be inserted, you can pass one of the following constants as the second parameter:
    4. CClientScript::POS_HEADIn the head section right before the title element
      CClientScript::POS_BEGINAt the beginning of the body section
      CClientScript::POS_ENDAt the end of the body section
    5. Core scripts are the ones bundled with Yii, such as jQuery. You can include a core script in the following way:
    6. Yii::app()->clientScript->registerCoreScript('jquery');
      
    7. All packages are listed in framework/web/js/packages.php:
    8. return array(
                  'jquery' => array(
                      'js' => array(YII_DEBUG ? 'jquery.js' : 'jquery.min.js'),
                  ),
                  'yii' => array(
                      'js' => array('jquery.yii.js'),
                      'depends' => array('jquery'),
                  ),
                  'yiitab' => array(
                      'js' => array('jquery.yiitab.js'),
                      'depends' => array('jquery'),
                  ),
                  'yiiactiveform' => array(
                      'js' => array('jquery.yiiactiveform.js'),
                      'depends' => array('jquery'),
                  ),
                  'jquery.ui' => array(
                      'js' => array('jui/js/jquery-ui.min.js'),
                      'depends' => array('jquery'),
                  ),
                  ...
              );
      
    9. In this list, keys of the array such as jquery, yii, yiitab, yiiactiveform are the names you can use with registerClientScript and the corresponding arrays are the real script file names relative to framework/web/js/source that are actually loaded.
    10. Inline scripts are the scripts contained in a page body. Typically, these are ones different at every request. You can include these type of scripts in the following way:
    11. Yii::app()->clientScript->registerScript('myscript', 'echo "Hello, world!";', CClientScript::POS_READY);
      
      The first parameter is the unique script ID you have chosen. The second parameter is the script itself. The third one tells Yii where to include a script. Additionally there are two more positions to ones used for registerScriptFile:
      CClientScript::POS_LOADIn the window.onload() function
      CClientScript::POS_READYIn the jQuery's ready function
    Now, let's move on to CSS. There are two types of CSS: inline and external (no core type this time).
    1. In order to include an external CSS, you can use the following code:
    2. Yii::app()->clientScript->registerCssFile('http://example.com/css/main.css');
      
      Additionally, this method takes a second parameter which allows you to specify which media type you want to include CSS for, such as screen or print. There is no position parameter, as the only valid place to include a CSS is inside a head tag. Inline CSS should be avoided wherever possible, but in case you really need it, you can include it in the following way:
      Yii::app()->registerCss('myCSS', 'body {margin: 0; padding: 0}', 'all');
      
    3. In the preceding code, myCSS is the unique ID. Then, we have the actual CSS code and a media type.

    How it works...

    The Yii's CClientScript methods which we have reviewed do not include scripts and CSS instantly. Instead, resources are stored until application controller calls the render method. Then, it finds a proper place in layout and inserts all needed script and CSS blocks and tags. If we include a resource with the same name or URL twice, then it will be included only once. This allows us to efficiently use resources in a widget, view partial, or any reusable piece of code.

    There's more...

    We have reviewed the most common resource types: JavaScript and CSS. However, there is more.

    Using custom script packages

    While using Yii, you can leverage package features to manage script dependencies in the same way core dependencies are managed. The feature is described well at the following API page:
    http://www.yiiframework.com/doc/api/CClientScript#packages-detail

    Registering linked resources
    Yii::app()->clientScript->registerLinkTag(
                    'alternate', 'application/rss+xml', $this->createUrl('rss/articles')
            );
    
    CClientScript offers another method that allows you to register a custom <link tag. For example, it can be useful to add an RSS link for a specific controller action as follows:

    Registering meta tags

    In addition, CClientScript allows registering of meta tags such as description or keywords by using the registerMetaTag method. For example, in order to specify a document encoding, you can use the following code:
    Yii::app()->clientScript->registerMetaTag(' text/html;charset=utf-8', null, 'Content-Type');
    

    Further reading

    For further information, refer to the following URLs:
    • http://www.yiiframework.com/doc/api/CClientScript
    • http://www.yiiframework.com/doc/guide/1.1/en/topics.performance

    Yii: Working with JSON

    JSON is a very simple, compact, and therefore a widely used format for AJAX applications data exchange. Yii has a few handy ways to work with it. Therefore, let's create a simple application that will show news list and update it every two seconds.

    Getting ready

    1. Create a new application by using the yiic webapp tool.
    2. Create and setup a new database.
    3. Add a table named news with at least id, created_on, and title fields:
    4. CREATE TABLE `news` (
          `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
          `created_on` int(11) unsigned NOT NULL,
          `title` varchar(255) NOT NULL,
          PRIMARY KEY (`id`)
      )
      
    5. Generate a News model using Gii.

    How to do it...

    1. Create a new controller named protected/controllers/NewsController.php as follows:
    2. render('index');
          }
      
          public function actionData() {
              $criteria = new CDbCriteria();
              $criteria->order = 'created_on DESC';
              $criteria->limit = 10;
              $news = News::model()->findAll($criteria);
              echo CJSON::encode($news);
          }
      
          public function actionAddRandomNews() {
              $news = new News();
              $news->title = "Item #" . rand(1, 10000);
              $news->created_on = date("Y-m-d H:i:s", time());
              $news->save();
              echo "OK";
          }
      
      }
      
    3. Moreover, create a view named protected/views/news/index.php as follows:
    4. Loading…
      clientScript->registerCoreScript("jquery") ?>
    5. Now, run the index action of the news controller and try to add a few records into the news database table by running the addRandomNews action. Do not refresh the index page. News added will appear on the index page once every two seconds, as shown in the following screenshot:

    How it works...

    The index action does nothing special. It simply renders a view that includes a div container and some JavaScript code. As we are using jQuery, we need to ensure it is included:
    clientScript->registerCoreScript("jquery")?>
    
    Then, we define a function named updateNews and run it every 2,000 milliseconds using the core JavaScript setInterval function. In updateNews, we make an AJAX request to the data action of the same controller, to process the JSON response and replace the current content of placeholder div with formatted data. In actionData, we get the latest news and convert them to JSON format by passing the result to CJSON::encode.

    There's more

    For further information, refer to the following URLs:
    • http://api.jquery.com/category/ajax/
    • http://www.yiiframework.com/doc/api/CJSON/
    • http://www.yiiframework.com/doc/api/CClientScript/#registerCoreScript-detail

    Yii: Managing assets

    An ability to manage assets is one of the greatest parts of Yii. It is especially useful in the following cases:

    • When you want to implement an extension that stores its JavaScript, CSS, and images in its own folder and is not accessible from a browser
    • When you need to pre-process your assets: combine JavaScript, compress it, and so on
    • When you use assets multiple times per page and want to avoid duplicates

    While the first two cases could be considered as bonus ones, the third one solves many widget reusing problems.
    Let's create a simple Facebook event widget which will publish and use its own CSS, JavaScript, and an image.

    Getting Ready

    • Create a fresh Yii application using yiic webapp as described in the official guide
    • Check that assets directory under application's webroot (where index.php is) has write permissions; assets will be written there
    • Generate and download a preloader image from http://ajaxload.info/

    How to do it...

    Let's do some planning first. In Yii, you can place your widgets virtually inside any directory and often, it is protected/components. It is acceptable to have one or two classes inside, but when the number of classes increases, it can create problems. Therefore, let's place our widget into protected/extensions/facebook_events. Create an assets directory inside the widget and put inside the ajax-loader.gif you have just downloaded. Also, create facebook_events.css and facebook_events.js in the same directory.
    1. Therefore, let's start with the widget class itself protected/extensions facebook_events/EFacebookEvents.php:
    2. url, urlencode($this->keyword));
          }
      
          public function init() {
              $assetsDir = dirname(__FILE__) . '/assets';
              $cs = Yii::app()->getClientScript();
              $cs->registerCoreScript("jquery");
      // Publishing and registering JavaScript file
              $cs->registerScriptFile(
                      Yii::app()->assetManager->publish(
                              $assetsDir . '/facebook_events.js'
                      ), CClientScript::POS_END
              );
      // Publishing and registering CSS file
              $cs->registerCssFile(
                      Yii::app()->assetManager->publish(
                              $assetsDir . '/facebook_events.css'
                      )
              );
      // Publishing image. publish returns the actual URL
      // asset can be accessed with
              $this->loadingImageUrl = Yii::app()->assetManager->publish(
                      $assetsDir . '/ajax-loader.gif'
              );
          }
      
          public function run() {
              $this->render("body", array(
                  'url' => $this->getUrl(),
                  'loadingImageUrl' => $this->loadingImageUrl,
                  'keyword' => $this->keyword,
              ));
          }
      
      }
      
      
    3. Now let's define body view we are using inside run method protected/extensions/facebook_events/views/body.php:
    4. 
      
    5. We will need to put the following into facebook_events.js:
    6. jQuery(function($) {
          $(".facebook-events").each(function() {
              var url = $(this).data("url");
              var container = $(".data", this);
              $.getJSON(url, function(json) {
                  var html = "
        "; $.each(json.data, function() { html += "
      • " + "" + this.name + " "+this.location+"
      • "; }); html += "
      "; container.html(html); }); }); });
    7. Write the following in facebook_events.css created previously:
    8. .facebook-events {
          padding: 10px;
          width: 400px;
          float: left;
      }
      .facebook-events ul {
          padding: 0;
      }
      .facebook-events li {
          list-style: none;
          border: 1px solid #ccc;
          padding: 10px;
          margin: 2px;
      }
      
      
    9. That is it. Our widget is ready. Let's use it. Open your protected/views/site/index.php and add the following code to it:
    10. widget("ext.facebook_events.EFacebookEvents", array(
          'keyword' => 'php',
      ))
      ?>
      widget("ext.facebook_events.EFacebookEvents", array(
          'keyword' => 'jquery',
      ))?>
      
      
    11. Now, it is time to check our application homepage. There should be two blocks with Facebook events named php events and jquery events, as shown in the following screenshot:

    How it works...

    When we use $this->widget in the site/index view, two EFacebookEvents methods are run: init which publishes assets and connects them to the page, and run which renders widget HTML. First, we use CAssetManager::publish to copy our file into the assets directory visible from the web. It returns URL that can be used to access the resource. In the case of JavaScript and CSS, we use CClientScript methods that add necessary

    There's more...

    There is more about working with assets.

    What is inside the assets directory?
    Let's check our assets directory. It should look similar to the following:
    assets
    1a6630a0\
    main.css
    2bb97318\
    pager.css
    4ab2ffe\
    jquery.js
    …
    Directories such as 1a6630a0 are used to prevent collisions of files with similar names from
    different directories. The name of the directory is a hash of complete path to the published
    asset directory. Therefore, assets from the same directory are copied to the same place. This
    means that if you publish both the image and the CSS file, you can reference images from
    CSS using relative paths.

    Publishing an entire directory
    Using CAssetManager::publish, you can publish an entire directory recursively. The
    difference is that single files are monitored after being published, whereas directories are not.

    Further reading
    For further information, refer to the following URL:
    • http://www.yiiframework.com/doc/api/CAssetManager
    • http://www.yiiframework.com/doc/api/CClientScript
    • http://www.yiiframework.com/doc/api/CHtml#asset

    Yii: Loading a block through AJAX

    Nowadays, it's common when a part of a page is loaded asynchronously. Let's implement the quotes box which will display random quotes and will have the "Next quote" link to show the next one.

    Getting ready

    • Create a fresh Yii application using yiic webapp as described in the official guide
    • Configure application to use clean URLs

    How to do it...

    Carry out the following steps:
    1. Create a new controller named protected/controllers/QuoteController.php as follows:
    2. quotes[array_rand($this->quotes, 1)];
              }
              
              function actionIndex()
              {
                  $this->render('index', array(
                      'quote' => $this->getRandomQuote()
                  ));
              }
              function actionGetQuote()
              {
                  $this->renderPartial('_quote', array(
                      'quote' => $this->getRandomQuote(),
                  ));
              }
      }

    3. We will require two views. The first is protected/views/quote/index.php:

    4. Quote of the day

      renderPartial('_quote', array( 'quote' => $quote, ))?>
      '#quote-of-the-day'))?> }
      The second view named protected/views/quote/_quote.php is as follows:
       
      &ldquo;<?php echo $quote[0]?>&rdquo;, <?php echo $quote[1]?>
      
    5. That is it. Now, try to access your quote controller and click on the Next quote link, as shown in the following screenshot:

    How is works...

    First, we define a list of quotes in the controller's private property $quotes and create a method to get a random quote. In a real application, you will probably get a quote from a database using DAO or an active record.
    Then, we define a view for the index action and _quote which is used in the getQuote action that renders it without layout and the index view as a partial. In the index action, we use CHtml::ajaxLink to create a link which makes a request to the getQuote action and updates the HTML element with the ID of quote-of-the-day. This is done with a response CHtml::ajaxLink that generates the following code in the resulting HTML page (reformatted):
     
    
    
    As jQuery is being used, Yii includes it in the page automatically and does it only once, no matter how many times we are using it.

    There's more...

    If you want to customize the success callback, then you can do this by setting it through a third parameter as follows:
     
     'js:function(data){
       alert(data);
       }'))?>
    
    In some cases, it is not desirable to allow a non-AJAX access to the getQuote action. There are two ways through which we can limit its usage to AJAX-only. First, you can use the built-in ajaxOnly filter as follows:
    class QuoteController extends Controller
    {
      function filters()
      {
         return array(
           'ajaxOnly + getQuote',
         );
      }
    ...
    
    After adding this, ones who try to use the getQuote action directly will get an HTTP error: 400 Bad Request. The second way is to detect if request is made through AJAX with a special request method. For example, if we want to show the standard 404 "Not found" page, we can do this as follows:
    function actionGetQuote()
    {
      if(!Yii::app()->request->isAjaxRequest)
        throw new CHttpException(404);
      $this->renderPartial('_quote', array(
        'quote' => $this->getRandomQuote(),
      ));
    }
    
    Similarly, we can use one action to serve both AJAX and non-AJAX responses:
    function actionGetQuote()
    {
      $quote = $this->getRandomQuote();
      if(Yii::app()->request->isAjaxRequest)
      {
        $this->renderPartial('_quote', array(
          'quote' => $quote,
        ));
      }
      else
      {
        $this->render('index', array(
          'quote' => $quote,
        ));
      }
    }
    

    Prevent including a bundled jQuery

    Sometimes, you need to suppress including a bundled jQuery. For example, if your project code relies on version specific functionality. To achieve this, you need to configure a clientScript application component using protected/config/main.php as follows:
    return array(
      // …
      // application components
      'components'=>array(
        // …
        'clientScript' => array(
        'scriptMap' => array(
          'jquery.js'=>false,
          'jquery.min.js'=>false,
        ),
      ),
    ),
    
    // …
    );
    

    Further reading

    • http://api.jquery.com/
    • http://docs.jquery.com/Ajax/jQuery.ajax#options
    • http://www.yiiframework.com/doc/api/CHtml#ajax-detail
    • http://www.yiiframework.com/doc/api/CHtml#ajaxButton-detail
    • http://www.yiiframework.com/doc/api/CHtml#ajaxSubmitButtondetail
    • http://www.yiiframework.com/doc/api/CHtml#ajaxLink-detail

    Wednesday, July 3, 2013

    There are three ways to write a literal string in your program: using single quotes, double
    quotes, and the here document (heredoc) format derived from the Unix shell. These
    methods differ in whether they recognize special escape sequences that let you encode
    other characters or interpolate variables.

    Variable Interpolation

    When you define a string literal using double quotes or a heredoc, the string is subject
    to variable interpolation. Interpolation is the process of replacing variable names in the
    string with the values of those variables. There are two ways to interpolate variables
    into strings.
    The simpler of the two ways is to put the variable name in a double-quoted string or
    heredoc:
    $who = 'Kilroy';
    $where = 'here';
    echo "$who was $where";
    
    Kilroy was here
    The other way is to surround the variable being interpolated with curly braces. Using
    this syntax ensures the correct variable is interpolated. The classic use of curly braces
    is to disambiguate the variable name from surrounding text:
    $n = 12;
    echo "You are the {$n}th person";
    You are the 12th person
    
    Without the curly braces, PHP would try to print the value of the $nth variable.
    Unlike in some shell environments, in PHP strings are not repeatedly processed for
    interpolation. Instead, any interpolations in a double-quoted string are processed first
    and the result is used as the value of the string:
    $bar = 'this is not printed';
    $foo = '$bar'; // single quotes
    print("$foo");
    $bar
    

    Single-Quoted Strings

    Single-quoted strings do not interpolate variables. Thus, the variable name in the following
    string is not expanded because the string literal in which it occurs is singlequoted:
    $name = 'Fred';
    $str = 'Hello, $name'; // single-quoted
    echo $str;
    Hello, $name
    
    The only escape sequences that work in single-quoted strings are \', which puts a single
    quote in a single-quoted string, and \\, which puts a backslash in a single-quoted string.
    Any other occurrence of a backslash is interpreted simply as a backslash:
    $name = 'Tim O\'Reilly';// escaped single quote
    echo $name;
    $path = 'C:\\WINDOWS'; // escaped backslash
    echo $path;
    $nope = '\n'; // not an escape
    echo $nope;
    Tim O'Reilly
    C:\WINDOWS
    \n
    

    Double-Quoted Strings

    Double-quoted strings interpolate variables and expand the many PHP escape sequences.
    Table 1 lists the escape sequences recognized by PHP in double-quoted strings.

    Table 1: Escape sequences in double-quoted strings

    If an unknown escape sequence (i.e., a backslash followed by a character that is not
    one of those in Table 1) is found in a double-quoted string literal, it is ignored (if you
    have the warning level E_NOTICE set, a warning is generated for such unknown escape
    sequences):
    $str = "What is \c this?";// unknown escape sequence
    echo $str;
    What is \c this?
    

    Here Documents

    You can easily put multiline strings into your program with a heredoc, as follows:
    $clerihew = <<< EndOfQuote
    Sir Humphrey Davy
    Abominated gravy.
    He lived in the odium
    Of having discovered sodium.
    EndOfQuote;
    echo $clerihew;
    Sir Humphrey Davy
    Abominated gravy.
    He lived in the odium
    Of having discovered sodium.
    
    The <<< identifier token tells the PHP parser that you’re writing a heredoc. There
    must be a space after the <<< and before the identifier. You get to pick the identifier.
    The next line starts the text being quoted by the heredoc, which continues until it
    reaches a line that consists of nothing but the identifier.
    As a special case, you can put a semicolon after the terminating identifier to end the
    statement, as shown in the previous code. If you are using a heredoc in a more complex
    expression, you need to continue the expression on the next line, as shown here:
    printf(<<< Template
    %s is %d years old.
    Template
    , "Fred", 35);
    
    Single and double quotes in a heredoc are passed through:
    $dialogue = <<< NoMore
    "It's not going to happen!" she fumed.
    He raised an eyebrow. "Want to bet?"
    NoMore;
    echo $dialogue;
    "It's not going to happen!" she fumed.
    He raised an eyebrow. "Want to bet?"
    
    Whitespace in a heredoc is also preserved:
    $ws = <<< Enough
    boo
    hoo
    Enough;
    // $ws = " boo\n hoo";
    The newline before the trailing terminator is removed, so these two assignments are
    identical:
    $s = 'Foo';
    // same as
    $s = <<< EndOfPointlessHeredoc
    Foo
    EndOfPointlessHeredoc;
    
    If you want a newline to end your heredoc-quoted string, you’ll need to add an extra
    one yourself:
    $s = <<< End
    Foo
    End;

    Understanding PHP's Garbage Collection

    PHP uses reference counting and copy-on-write to manage memory. Copy-on-write
    ensures that memory isn’t wasted when you copy values between variables, and reference
    counting ensures that memory is returned to the operating system when it is no
    longer needed.
    To understand memory management in PHP, you must first understand the idea of a
    symbol table. There are two parts to a variable—its name (e.g., $name), and its value
    (e.g., "Fred"). A symbol table is an array that maps variable names to the positions of
    their values in memory.
    When you copy a value from one variable to another, PHP doesn’t get more memory
    for a copy of the value. Instead, it updates the symbol table to indicate that “both of
    these variables are names for the same chunk of memory.” So the following code doesn’t
    actually create a new array:
    $worker = array("Fred", 35, "Wilma");
    $other = $worker; // array isn't copied
    
    If you subsequently modify either copy, PHP allocates the required memory and makes
    the copy:
    $worker[1] = 36; // array is copied, value changed
    
    By delaying the allocation and copying, PHP saves time and memory in a lot of situations.
    This is copy-on-write.
    Each value pointed to by a symbol table has a reference count, a number that represents
    the number of ways there are to get to that piece of memory. After the initial assignment
    of the array to $worker and $worker to $other, the array pointed to by the symbol table
    entries for $worker and $other has a reference count of 2.1 In other words, that memory
    can be reached two ways: through $worker or $other. But after $worker[1] is changed,
    PHP creates a new array for $worker, and the reference count of each of the arrays is
    only 1.
    When a variable goes out of scope, such as function parameters and local variables do
    at the end of a function, the reference count of its value is decreased by one. When a
    variable is assigned a value in a different area of memory, the reference count of the old
    value is decreased by one. When the reference count of a value reaches 0, its memory
    is released. This is reference counting.
    Reference counting is the preferred way to manage memory. Keep variables local to
    functions, pass in values that the functions need to work on, and let reference counting
    take care of the memory management. If you do insist on trying to get a little more
    information or control over freeing a variable’s value, use the isset() and unset()
    functions.
    To see if a variable has been set to something—even the empty string—use isset():
    $s1 = isset($name); // $s1 is false
    $name = "Fred";
    $s2 = isset($name); // $s2 is true
    
    Use unset() to remove a variable’s value:
    $name = "Fred";
    unset($name); // $name is NULL
    

    PHP Function By Category

    This is a list of functions provided by PHP’s built-in extensions, grouped by the extension
    providing each function:



    Arrays

    array_change_key_casearray_intersect_assoc
    array_chunkarray_intersect_key
    array_combinearray_intersect_uassoc
    array_count_valuesarray_intersect_ukey
    array_diffarray_key_exists
    array_diff_assocarray_keys
    array_diff_keyarray_map
    array_diff_uassocarray_merge
    array_diff_ukeyarray_merge_recursive
    array_fillarray_multisort
    array_fill_keysarray_pad
    array_filterarray_pop
    array_fliparray_product
    array_intersectarray_push
    array_randcompact
    array_reducecount
    array_replacecurrent
    array_replace_recursiveeach
    array_reverseend
    array_searchextract
    array_shiftin_array
    array_slicekey
    array_splicekrsort
    array_sumksort
    array_udifflist
    array_udiff_assocnatcasesort
    array_udiff_uassocnatsort
    array_uintersectnext
    array_uintersect_assocprev
    array_uintersect_uassocrange
    array_uniquereset
    array_unshiftrsort
    array_valuesshuffle
    array_walksort
    array_walk_recursiveuasort
    arsortuksort
    asortusort

    Classes and Objects


    class_aliasget_object_vars
    class_existsget_parent_class
    get_called_classinterface_exists
    get_class_methodsis_a
    get_class_varsis_subclass_of
    get_classmethod_exists
    get_declared_classesproperty_exists
    get_declared_interfacestrait_exists
    get_declared_traits

    Date and Time


    checkdatedate_default_timezone_set
    date_default_timezone_getdate_parse_from_format
    date_parseidate
    date_sun_infolocaltime
    date_sunrisemicrotime
    date_sunsetmktime
    datestrftime
    getdatestrptime
    gettimeofdaystrtotime
    gmdatetime
    gmmktimetimezone_name_from_abbr
    gmstrftimetimezone_version_get

    Directories


    chdiropendir
    chrootreaddir
    closedirrewinddir
    dirscandir
    getcwd

    Errors and Logging


    debug_backtracerestore_error_handler
    debug_print_backtracerestore_exception_handler
    error_get_lastset_error_handler
    error_logset_exception_handler
    error_reportingtrigger_error

    Program Execution


    escapeshellargproc_nice
    escapeshellcmdproc_open
    execproc_terminate
    passthrushell_exec
    proc_closesystem
    proc_get_status

    Filesystem


    basenamecopy
    chgrpdirname
    chmoddisk_free_space
    chowndisk_total_space
    clearstatcachefclose
    feofis_executable
    fflushis_file
    fgetcis_link
    fgetcsvis_readable
    fgetsis_uploaded_file
    fgetssis_writable
    file_existslchgrp
    file_get_contentslchown
    file_put_contentslink
    filelinkinfo
    fileatimelstat
    filectimemkdir
    filegroupmove_uploaded_file
    fileinodeparse_ini_file
    filemtimeparse_ini_string
    fileownerpathinfo
    filepermspclose
    filesizepopen
    filetypereadfile
    flockreadlink
    fnmatchrealpath_cache_get
    fopenrealpath_cache_size
    fpassthrurealpath
    fputcsvrename
    freadrewind
    fscanfrmdir
    fseekstat
    fstatsymlink
    ftelltempnam
    ftruncatetmpfile
    fwritetouch
    globumask
    is_dirunlink

    Data Filtering


    filter_has_varfilter_input
    filter_idfilter_list
    filter_input_arrayfilter_var_array
    filter_var

    Functions


    call_user_func_arrayforward_static_call
    call_user_funcfunc_get_arg
    create_functionfunc_get_args
    forward_static_call_arrayfunc_num_args
    function_existsregister_tick_function
    get_defined_functionsunregister_tick_function
    register_shutdown_function

    PHP Options/Info


    assert_optionsini_get
    assertini_restore
    extension_loadedini_set
    gc_collect_cyclesmemory_get_peak_usage
    gc_disablememory_get_usage
    gc_enablephp_ini_loaded_file
    gc_enabledphp_ini_scanned_files
    get_cfg_varphp_logo_guid
    get_current_userphp_sapi_name
    get_defined_constantsphp_uname
    get_extension_funcsphpcredits
    get_include_pathphpinfo
    get_included_filesphpversion
    get_loaded_extensionsputenv
    getenvrestore_include_path
    getlastmodset_include_path
    getmygidset_time_limit
    getmyinodesys_get_temp_dir
    getmypidversion_compare
    getmyuidzend_logo_guid
    getoptzend_thread_id
    getrusagezend_version
    ini_get_all

    Mail


    mail

    Math


    absis_finite
    acosis_infinite
    acoshis_nan
    asinlcg_value
    asinhlog10
    atan2log1p
    atanlog
    atanhmax
    base_convertmin
    bindecmt_getrandmax
    ceilmt_rand
    cosmt_srand
    coshoctdec
    decbinpi
    dechexpow
    decoctrad2deg
    deg2radrand
    expround
    expm1sin
    floorsinh
    fmodsqrt
    getrandmaxsrand
    hexdectan
    hypottanh

    Miscellaneous Functions


    connection_abortedpack
    connection_statusphp_strip_whitespace
    constantsleep
    definesys_getloadavg
    definedtime_nanosleep
    get_browsertime_sleep_until
    highlight_fileuniqid
    highlight_stringunpack
    ignore_user_abortusleep

    Network


    checkdnsrrheader
    closelogheaders_list
    fsockopenheaders_sent
    gethostbyaddrinet_ntop
    gethostbynameinet_pton
    gethostbynamelip2long
    gethostnamelong2ip
    getmxrropenlog
    getprotobynamepfsockopen
    getprotobynumbersetcookie
    getservbynamesetrawcookie
    getservbyportsyslog
    header_remove

    Output Buffering


    flushob_get_level
    ob_cleanob_get_status
    ob_end_cleanob_gzhandler
    ob_end_flushob_implicit_flush
    ob_flushob_list_handlers
    ob_get_cleanob_start
    ob_get_contentsoutput_add_rewrite_var
    ob_get_flushoutput_reset_rewrite_vars
    ob_get_length

    Session Handling


    session_cache_expiresession_regenerate_id
    session_cache_limitersession_register_shutdown
    session_decodesession_save_path
    session_destroysession_set_cookie_params
    session_encodesession_set_save_handler
    session_get_cookie_paramssession_start
    session_idsession_status
    session_module_namesession_unset
    session_namesession_write_close

    Streams


    stream_bucket_appendstream_bucket_make_writeable
    stream_bucket_newstream_notification_callback
    stream_bucket_prependstream_resolve_include_path
    stream_context_createstream_select
    stream_context_get_defaultstream_set_blocking
    stream_context_get_optionsstream_set_chunk_size
    stream_context_get_paramsstream_set_read_buffer
    stream_context_set_defaultstream_set_timeout
    stream_context_set_optionstream_set_write_buffer
    stream_context_set_paramsstream_socket_accept
    stream_copy_to_streamstream_socket_client
    stream_encodingstream_socket_enable_crypto
    stream_filter_appendstream_socket_get_name
    stream_filter_prependstream_socket_pair
    stream_filter_registerstream_socket_recvfrom
    stream_filter_removestream_socket_sendto
    stream_get_contentsstream_socket_server
    stream_get_filtersstream_socket_shutdown
    stream_get_linestream_supports_lock
    stream_get_meta_datastream_wrapper_register
    stream_get_transportsstream_wrapper_restore
    stream_get_wrappersstream_wrapper_unregister
    stream_is_local

    Strings


    addcslashesget_html_translation_table
    addslasheshebrev
    bin2hexhebrevc
    chrhex2bin
    chunk_splithtml_entity_decode
    convert_cyr_stringhtmlentities
    convert_uudecodehtmlspecialchars_decode
    convert_uuencodehtmlspecialchars
    count_charsimplode
    crc32lcfirst
    cryptlevenshtein
    echolocaleconv
    explodeltrim
    fprintfmd5_file
    md5strip_tags
    metaphonestripcslashes
    money_formatstripos
    nl_langinfostripslashes
    nl2brstristr
    number_formatstrlen
    ordstrnatcasecmp
    parse_strstrnatcmp
    printfstrncasecmp
    quoted_printable_decodestrncmp
    quoted_printable_encodestrpbrk
    quotemetastrpos
    rtrimstrrchr
    setlocalestrrev
    sha1_filestrripos
    sha1strrpos
    similar_textstrspn
    soundexstrstr
    sprintfstrtok
    sscanfstrtolower
    str_getcsvstrtoupper
    str_ireplacestrtr
    str_padsubstr_compare
    str_repeatsubstr_count
    str_replacesubstr_replace
    str_rot13substr
    str_shuffletrim
    str_splitucfirst
    str_word_countucwords
    strcasecmpvfprintf
    strcmpvprintf
    strcollvsprintf
    strcspnwordwrap

    PHP Language Tokenizer


    token_get_all
    token_name

    URLs


    base64_decodeparse_url
    base64_encoderawurldecode
    get_headersrawurlencode
    get_meta_tagsurldecode
    http_build_queryurlencode

    Variables


    debug_zval_dumpis_object
    emptyis_resource
    floatvalis_scalar
    get_defined_varsis_string
    get_resource_typeisset
    gettypeprint_r
    intvalserialize
    is_arraysettype
    is_boolstrval
    is_callableunserialize
    is_floatunset
    is_intvar_dump
    is_nullvar_export
    is_numeric