Tuesday, November 11, 2014

Active Relations from Yii 1.1 to Yii 2.0

In Yii 1.1.x relations were defined by an array within the model class. In Yii 2.0 that is no longer the case. Relations are now defined by getter methods within the model. For the complete specification see Working with Relational Data in the Yii 2.0 Guide.

Saturday, April 19, 2014

Making the switch from Yii 1.1 to Yii 2.0 - Part 2: Getting Started

Probably my biggest hurdle in getting started with Yii 2.0 (aside from getting my hosting company to upgrade my server from PHP5.2 to 5.4) has been understanding the changes to the application structure and the vast difference between the basic application and the advanced application. Since my personal site runs on a subdomain, this greatly complicated the issue for me. I needed something more than the basic application, but the advanced application was way too much overkill. I finally went with the advanced application, but now that I understand how the composer works to distribute the skeleton files, I'm looking forward to creating something that's between the two.

There is way too much to cover in a single blog, so this is just a look at the basic structure and front end changes that you need to know to get up and running in 2.0.

If you're past all of this and looking to dive into the nitty-gritty detail changes, I suggest you read the official documentation on upgrading.

Tuesday, March 11, 2014

Setting the CDbConnection for Active Records within a Yii Migration

I use migrations whenever possible within Yii applications to help ensure consistency between implementation. I have multiple development environments, a staging environment and a production environment. I really don't want to have to manually issue a lot of sql queries or yii commands if I don't have to. Migrations make that possible and can be integrated automatically in the build process for deployment.

Occasionally, I need to convert some old data into a new structure, and I can do this via the migrations as well!

Create a basic migration to translate data:

For example, to query a set of data from an old table, and create a new set of records from it (that may have additional logical processes added beforeSave etc.) I could start off by doing the following in my migration:

public function up()
{
  // Query for the existing record data
  $results = Yii::app()->db->createCommand()
    ->select('*')
    ->from('tbl_old')
    ->queryAll();

  // Ensure that if we fail, we start with a fresh slate
  try {
    foreach( $results as $row )
    { 
       $m = new NewModel();
       // assign attributes from $row[] that should be
       // mapped to the new model applying any new
       // logical manipulations required
       if (!$m->save())
       {
         throw new Exception(
            CVarDumper::dumpAsString( $m->errors )
         );
       }
    }
  } catch ( Exception $e )
  {
     echo $e->getMessage(), PHP_EOL;
     $this->truncate('tbl_new');
     // indicate migration failure
     return false;
  }

  // acknowledge success
  return true;
}


If you only have one db connection in your application, this will work fine. But, if you also have a test database that you need to migrate, it will quickly fail due to duplicate primary keys, or write double the data in your primary database, rather than your test database.

Update the migration to handle alternate connection components:

To update the migration to work with a different connection, we simply need to add a few lines that specify which connection to use, as follows:

public function up()
{
  // Query for the existing record data 
  // Use migration's db connection rather than Yii::app()->db
  $results = $this->getDbConnection()->createCommand()
    ->select('*')
    ->from('tbl_old')
    ->queryAll();

  // Set the static connection property for active records to the migration's 
  // connnection
  CActiveRecord::$db = $this->getDbConnection();
 
  try {
  //  ... the rest remains the same
  }
}


Now, I can run my migration on the alternate connection with no problems, and no pulling of hair!

./yiic migrate --connectionID=db_test


On the Yii2 front, I was side tracked by the fact that I can't find an equivalent to the CViewAction (which I use extensively), and so much of the main layout involves new widgets in static method form, but I will get back to it soon!

Tuesday, February 4, 2014

Making the switch from Yii 1.1 to Yii 2.0 - Part 1: Before You Begin

So, over the weekend I decided that it was time to dive into Yii2 and start getting familiar with it and be prepared for the official beta release coming up. I've been reading the forums about it since the 2.0 board was first opened, and I thought I was in a pretty good place to start from. The first thing I learned, was that I have a lot to learn before actually starting to convert any of my applications into Yii2.

What You Need to Know Before You Start!


Yii2 takes "full advantage" of some of the "newer" (I know it's been out a long time, but some of us haven't had the liberty to use it) capabilities of PHP - namely (pun intended) requiring PHP5.4 as a minimum, and using Composer to create new Yii applications. They've also switched out the standard PHPUnit tests in the application for Codeception.

If you're at all like me and have been nose to the grindstone on servers locked into PHP in the 5.3 (or lower) family, this will come as quite a system shock, even when you know it's coming ... I'm ashamed to admit that I looked at an array in 5.4 syntax and tried to figure out why there was JSON code in my PHP file.

So, forget all the implementation differences between Yii 1.1 and Yii 2.0 for now -- If you're not already fluent with PHP 5.4, Composer and Codeception, spend a week getting there.

Composer - PHP Package Manager

The documentation for installing Composer is very straightforward and can be found at http://getcomposer.org

Getting it running on my Ubuntu VM was very straight forward, but on my Macbook I had to go through quite a bit of shenanigans to get XCode updated (required by Composer). I ended up having to uninstall and reinstall XCode before Composer would work properly there. Though the PHP upgrade on the Macbook trumped the PHP upgrade on the Ubuntu machine. So scratch off an evening just getting Composer running smoothly across the board. I believe this will be the preferred method of releasing extensions, etc. so if you're planning to move in that arena and aren't already familiar with this, time to read up.

I'm still figuring out all the basics for it, but it seems like a very handy way to keep packages maintained, and I'm looking forward to really becoming familiar with it.

PHP 5.4: Namespaces and Arrays

Oh my. So, the namespaces take a lot of getting used to, seeing all the /path/to/myClass::$variable and it seems like it takes up a lot more characters to do something very simple. Lots of power there though, and once you get used to reading it and writing it that way, it will become second nature. For the full list of what's dramatically different, check out the PHP 5.4 migration guide.

Arrays can now be generated with short array tags, and you'll find this extensively in the Yii2 code.
$config = array('something'=>'else','sub'=>array('marine'=>'sandwich'));
becomes
$config = ['something'=>'else','sub'=>['marine'=>'sandwich']];
Much more concise, but will definitely throw you for a loop the first time you see it. It's going to be a while before I can read that as quickly as I can the old style.

CAUTION: Once you start looking ahead, it'll be hard to go back to the old 5.3 ways -- so be sure you're ready!

Codeception Testing Framework

I haven't looked into Codeception very far yet, but it essentially runs all the tests from a 'story' point of view. This is my personal homework for the next few days. If you're as dependent on TDD as you should be, you will want to brush up on this before diving in.

Finally, my favorite ...

Bootstrap 3.1.0

The new sample applications implement Bootstrap 3.1.0 which has been changed significantly from the Bootstrap v2. Bootstrap 3 is designed not just to be mobile responsive, but mobile first. If you're used to using any of the other CSS libraries or the v2 flavors of Bootstrap, spend a little time getting familiar with the changes that have been implemented in the v3 line. It's well worth it.

Once you're FULLY familiar with the syntax changes for 5.4 / 5.5 (whatever your flavor), and Namespaces no longer throw you for a loop, and Composer is installed and functional, it's finally time to start reading
Upgrade from v1 on the Yii2 docs.

I should also mention, my beloved "yiic" command is no more -- it's now "yii"! So if you get ambitious and download the sample applications, don't go searching for yiic, because you won't find it. ;) I'm excited about the advanced application and the init scripts to toggle configurations, but I'm getting ahead of myself here ...

Part 2 should make its appearance this weekend as I will dive into transitioning an old Yii 1.1 application into Yii 2.0

Thursday, January 30, 2014

Saving and Retrieving Encrypted Data in Yii

A few people have commented about using md5 or the PASSWORD() capabilities and how to retrieve their data from their MySQL database when updating user passwords and other encrypted data, so I thought it would be a good idea to spend a little time talking about how you can use the CSecurityManager to handle your encrypted data in a way that keeps it completely secure, but allows you to retrieve the original data.

This is not a new feature, it has been around since Yii 1.0, but it's easy to overlook it you don't handle a lot of encryption or need to worry about much other than the occasional casual user field.

The CSecurityManager works as an application component, so to access it you simply call
// $key is optional, it will default to NULL if you don't pass one
Yii::app()->securityManager->encrypt( $string, $key );

and

Yii::app()->securityManager->decrypt( $string, $key );

The most important thing to remember is that your key has to be the same when encrypting and decrypting! This may seem like a no-brainer, but if you're using different keys for various applications, components, or modules, you can quickly run into complications. I like to store the key as a constant that I can access with the model, so that the model can then intelligently report the data if needed. Of course, you need to consider the security of your models before implementing anything that will allow decrypted access.

Back in this old post about saving encrypted passwords I demonstrated the more traditional MySQL PASSWORD() approach. Using the CSecurityManager to handle your encryption will use essentially the same approach, only you'll update the encryptPassword function as follows:

const KEY_VALUE = 'someRandom123'; // <-- make this good

public function encryptPassword()
{
  // Nothing to encrypt
  if ( $this->userpass == '' )
    return;

  $this->userpass = Yii::app()->securityManager->encrypt( 
    $this->userpass, 
    self::KEY_VALUE 
  );
}

Note: I'm using the password field here since it's the previous example and the one thing most people actually make an effort to encrypt. You can encrypt any data that you save, so please don't feel that this example applies only to password data.

Now, if your administrators need to be able to access the encrypted data so that they can answer questions for people, you need a way for the model to see that data cleanly - hello decrypt! You should be VERY cautious about using this. If you handle the decrypted data lightly you're losing the point of encrypting it in the first place.

/**
 * Return the decrypted value of the field (does NOT assign
 * the decrypted value back to the attribute)
 * @return string
 */
public function decryptPassword()
{
  // Nothing to decrypt
  if ( $this->userpass == '' )
    return '';

  return Yii::app()->securityManager->decrypt( 
     $this->userpass, 
     self::KEY_VALUE
  );
    
}

Remember, as in the original example, you're not going to re-encrypt it before every save, only when the data has been updated or the record is new, unless you're decrypting it by default afterFind, which I do not recommend (see above note about the wisdom of handling decrypted secure data).

If you really want to get fancy with your data storage, you could run an encryption filter on all the attributes before saving, and then decrypt them afterFind so that it's impossible to read anything in the database directly. There may be some merit to that idea if you're on a shared database server, but if you trust your database security then that's probably a lot more complicated than you need to make your data storage for any data that isn't truly sensitive.


So, now looking back to the original post about authentication via database, we should update our authentication a bit:

public function authenticate()
{
  $record=User::model()->findByAttributes( array('username'=>$this->username) );
  if($record===null)
    $this->errorCode=self::ERROR_USERNAME_INVALID;
  else if( $this->password != $record->decryptPassword())  // <-- Note the change
    $this->errorCode=self::ERROR_PASSWORD_INVALID;
  else {
    $this->_id=$record->id;
    $this->setState('title', $record->title);
    $this->errorCode=self::ERROR_NONE;
  }
  return !$this->errorCode;
}