Chain pattern


require "vendor/autoload.php";

use Chain\HomeStatus;
use Chain\Lock;
use Chain\Lights;
use Chain\Alarm;


$lock = new Lock();
$lights = new Lights();
$alarm = new Alarm();

$lock->setSuccessor($lights);
$lights->setSuccessor($alarm);

$homeStatus = new HomeStatus();
$lock->check($homeStatus);
echo PHP_EOL;


Strategy pattern


namespace Stra;

use Stra\Logger;
use Stra\DatabaseLogger;
use Stra\FileLogger;

class Strategy
{
    public function log($msg, Logger $logger = null)
    {
        if(is_null($logger)) {
            $logger = new DatabaseLogger();
        }

        $logger->log($msg);
    }
}

Test Drive


require 'vendor/autoload.php';

use Stra\Strategy;
use Stra\FileLogger;
use Stra\DatabaseLogger;

$app = new Strategy();
$app->log('Some action happened.', new FileLogger());

Factory pattern

Code


namespace Patterns;


/**
 * Design pattern - Factory
 *
 * @author: Hazrat Ali
 * @mail: iloveyii@yahoo.com
 * Date: 2016-07-30
 */
class ShapeFactory
{
    private static $types = ['Rectangle', 'Square'];

    public static function create($type)
    {
        if( ! in_array($type, self::$types)) {
            die('Invalid type ' . $type . ', valid types are ' . implode(',', self::$types) . EOL);
        }

        if($type == 'Rectangle') {
            return new Rectangle(50, 15);
        }

        if($type == 'Square') {
            return new Square(20);
        }
    }
}


SingletonX

Git repo


namespace Patterns;


/**
 * Design pattern - Singleton extended
 * The extended version contains an array of instances
 *
 * @author: Hazrat Ali
 * @mail: iloveyii@yahoo.com
 * Date: 2016-07-30
 */
class SingletonX
{
    private static $instances = [];
    private $dir = 'tr';
    private $language;
    private $translations;

    public function __construct($language)
    {
        $this->language = $language;
        $this->readTranslationFile('string.json');

        echo 'Object initiated with language ' , $language . EOL;
    }

    public static function getInstance($language)
    {
        if( ! isset(self::$instances[$language])) {
            self::$instances[$language] = new self($language);
        }

        return self::$instances[$language];
    }

    public function translate($text)
    {
        return isset($this->translations[$text]) ? $this->translations[$text] : false;
    }

    private function readTranslationFile($fileName)
    {
        $filePath = $this->getPathAbs($fileName);

        if(file_exists($filePath)) {
            $contents = file_get_contents($filePath);
        } else {
            $svContents = json_encode([
                'language'=>'språk',
                'code'=>$this->language,
                'country'=>'land',
                'city'=>'stad'
            ]);
            $frContents = json_encode([
                'language'=>'la langue',
                'code'=>$this->language,
                'country'=>'pays',
                'city'=>'ville'
            ]);

            if($this->language == 'sv') {
                file_put_contents($filePath, $svContents);
                $contents = $svContents;
            }

            if($this->language == 'fr') {
                file_put_contents($filePath, $frContents);
                $contents = $frContents;
            }
        }

        $this->translations = json_decode($contents, JSON_FORCE_OBJECT);
    }

    private function getPathAbs($fileName)
    {
        $path = sprintf("%s/%s/%s/%s", dirname(__FILE__), $this->dir, $this->language, $fileName);

        if( ! file_exists(dirname($path))) {
            mkdir(dirname($path), 0777, true);
        }

        return $path;
    }
}

Singleton pattern

Code

The singleton pattern is used to restrict the creation of only object of a class. This is useful when it is expensive (in terms of resources) to initiate new objects every time the same object is needed. This pattern specifically put this logic of maintaining only one instance inside the class itself.

This pattern can be used in situations like database connectivity, reading and writing to disk, sending emails.

In the example below we use a simple translation class which read a json file with translations. Only one instance is needed since reading file operations are slower. Therefore we can easily use this object as many times as needed for multiple words translation. Since it is a singleton so it will keep only one instance each time we use it in the code.

namespace Patterns;


/**
 * Design pattern - Singleton
 * @author: Hazrat Ali
 * @mail: iloveyii@yahoo.com
 * Date: 2016-07-30
 */
class Singleton
{
    private static $instance;
    private $dir = 'tr';
    private $language;
    private $translations;

    public function __construct($language)
    {
        $this->language = $language;
        $this->readTanslationFile('string.json');
    }

    public static function getInstance($language)
    {
        if( ! isset(self::$instance)) {
            self::$instance = new self($language);
        }

        return self::$instance;
    }

    public function translate($text)
    {
        return isset($this->translations[$text]) ? $this->translations[$text] : false;
    }

    private function readTanslationFile($fileName)
    {
        $filePath = $this->getPathAbs($fileName);

        if(file_exists($filePath)) {
            $contents = file_get_contents($filePath);
        } else {
            $contents = json_encode([
                'language'=>'språk',
                'code'=>$this->language,
                'country'=>'land',
                'city'=>'stad'
            ]);

            file_put_contents($filePath, $contents);
        }

        $this->translations = json_decode($contents, JSON_FORCE_OBJECT);
    }

    private function getPathAbs($fileName)
    {
        $path = sprintf("%s/%s/%s/%s", dirname(__FILE__), $this->dir, $this->language, $fileName);

        if( ! file_exists(dirname($path))) {
            mkdir(dirname($path), 0777, true);
        }

        return $path;
    }
}

Yii 2 Query Builder with strange backticks


 $query = (new Query())
            ->select(['itinerary.id, itinerary.from_vertex_id, itinerary.to_vertex_id,
             v1.name AS from_name, v2.name AS to_name, 
             CONCAT(v1.name, v2.name) AS it_name
             '])
            ->from('itinerary')
            ->innerJoin('vertex v1', 'itinerary.from_vertex_id = v1.id')
            ->innerJoin('vertex v2', 'itinerary.to_vertex_id = v2.id')
            ->orderBy('itinerary.id');

Solution: The select method above must always be passed with an array ( [] ), using it with string puts strange backticks around column names.

Yii excel export

It is very simple and a only a matter of a few lines of code to export your data (for example from mysql) into excel. Follow the following steps:
1. Download PHP Excel Library from
https://github.com/PHPOffice/PHPExcel/tree/1.8/Classes
And extract into protected/extensions/PHPExcel

2. Use an action like the following in your controller

public function actionExcel($id) {
  $criteria = new CDbCriteria;
  $criteria->condition="winner_id=$id OR loser_id=$id";
  $criteria->order = "date DESC";
  $models= Match::model()->findAll($criteria);
        
  $objPHPExcel = new PHPExcel();

  $objPHPExcel->setActiveSheetIndex(0);
  // set header
  $data=['Winner','Loser','Result','Date'];
  $objPHPExcel->getActiveSheet()->fromArray($data, NULL, 'A1');
  $i=2;
  // from db
  foreach ($models as $model) {
     $data = [
           CHtml::encode($model->getPlayerName($model->winner_id)),
           CHtml::encode($model->getPlayerName($model->loser_id)),
           $model->result,
           $model->date
      ];
      $objPHPExcel->getActiveSheet()->fromArray($data, NULL, 'A'.$i);
      $i++;
  }
        
      $objPHPExcel->getActiveSheet()->getDefaultRowDimension()->setRowHeight(20);
        
      $objPHPExcel->getActiveSheet()->setTitle('Matches list');
      foreach (range('A', $objPHPExcel->getActiveSheet()->getHighestDataColumn()) as $col) {
          $objPHPExcel->getActiveSheet()
              ->getColumnDimension($col)
              ->setAutoSize(true);
      }

      ob_end_clean();
      ob_start();

      header('Content-Type: application/vnd.ms-excel');
      header('Content-Disposition: attachment;filename="matches.xls"');
      header('Cache-Control: max-age=0');
      $objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel5');
      $objWriter->save('php://output');
        
    }


The phpexcel library can be dowloaded here PHPExcel.tar

Install Yii 2

Install Yii 2 via composer

1. Install composer from here https://getcomposer.org/doc/00-intro.md
Add its path in environment in windows or move to /bin/composer in Ubuntu to make it run globally.

2. Run the following commands

 composer global require "fxp/composer-asset-plugin:1.0.0-beta3"
 composer create-project --prefer-dist yiisoft/yii2-app-advanced yii-application

yii-application is the name of the dir where you want to install the application. Its an advanced app. you can specify yii2-app-basic to create basic app.

3. Initialize directory structure
cd to yii-application dir and run.

  php init

select 0 as dev environment. This command creates the basic structure of the directories.

4. Connect DB
Create a new database and adjust the components.db configuration in common/config/main-local.php accordingly.

5.
Apply migrations with console command yii migrate.

6.
Set document roots of your web server:
for frontend /path/to/yii-application/frontend/web/ and using the URL http://frontend/
for backend /path/to/yii-application/backend/web/ and using the URL http://backend/

🙂

Yii set default value in model rules

The yii default rule only works if there is no database default value set for the field. Therefore you should either set the default in DB or in Model rules like

   array('active','default', 'value'=> 1, 'setOnEmpty'=>TRUE),

Yii Rights Extension – Error 403. There must be at least one superuser! – SOLVED

In my previous post I demonstrated how to install Yii Rights extension easily and quickly. We commented out the code for checking superuser given below.

Now to address this issue simply edit file /modules/rights/components/RAuthorizer.php and comment out the lines near line no 304 and 305.

    //if( $superusers===array() )
         //  throw new CHttpException(403, Rights::t('core', 'There must be at least one superuser!'));

However it will make any user to use the Rights interface, even the not logged in user can use this module and change permission which is not good for a production environment.

In order to enable user permission for using the Rights module I could not find any solution on searching the internet. Then I come up with a manual but very easy procedure. I entered a two entries in the database manually. .

1. There must be a record in the table AuthItem table with the name (e.g. admin ). If there is no such record add it manually.
2. Now add a record to Rights table with the itemname similar in step 1. (e.g. admin). The other two columns type and weight does not really matter here so put any integer value (e.g. 0).
3. Now uncomment the code above for if( $superusers===array() ).

Now only the user admin will be able to use Rights module and grant or revoke permissions because in Rights Extension now admin is the superuser.

Cheers 🙂

Using YiiSmartMenu with Rights Extension

Yii Smart can be used out of the box (require only a few steps) with Yii Rights Extension. The purpose of the Yii Smart menu is to show those menu items to the user for which the logged in user has access to. Yii smart menu can work with Rights Extension, RBAC etc. In order to install Yii Rights you can read in my previous post.

Follow the following steps to use YiiSmartMenu widget.

1. Download the widget class from here  http://www.yiiframework.com/extension/yiismartmenu/ and copy it to your components folder i.e yii_app/protected/components.

2. In order to change top menu open yii_app/protected/views/layouts/main.php and replace echo occurrence of  $this->widget('zii.widgets.CMenu', ... TO

$this->widget('application.components.YiiSmartMenu', array(
       'partItemSeparator'=>'.',
       'upperCaseFirstLetter'=>true,

The partItemSeparator concatenates ControllerID to ActionID e.g. Site and Index would be concatenated as Site.Index. Rights use authitem in this format and so ‘.’ is compatible with Rights.
Similarly upperCaseFirstLetter makes ControllerID and ActionID uppercase like in Site.Index, again its compatible with Rights Extension. ( You can verify this in AuthItem table of Rights extension that each operation is formed as ControllerID.ActionID).

if you want to check access to an individual action in your code use:

     Yii::app()->user->checkAccess("Site.Index", $_GET)';

The above code will return true if the current logged in user has access to site/index action.
If you want any menu item should be not be checked by YiiSmartMenu and hence should be visible always e.g. a Home link you can add visible property of the menu item like:

    'items'=>array(
         array('label'=>'Home', 'url'=>array('/site/index'), 'visible' => true),

Please note that any top menu item for which permissions is in the form of ControllerID.* in the Rights extension does not work, it may be a bug in this extension. In order to sort this out simply grant permissions like ControllerID.ActionID to the top menu item in the Rights extension.

🙂