Friday, November 16, 2012

PHP is_int() vs. is_numeric()

These two functions seem similar, don't they? In fact, is_int() seems like a more specific version of is_numeric(). You may, for example, want to make sure that the ID of a blog post that is being passed through the query string is not only numeric but, specifically, an integer. After all, 7.145 is numeric, but you want to make sure the incoming value hasn't been tinkered with and is simply a 7. You'd be tempted to do something like the following:

if(isset($_GET['blog_id']) && is_int($_GET['blog_id'])) {
     // do something with the blog ID
}

Unfortunately, this won't work. It turns out that is_int() will return false when the value passed to it is a string, whereas is_numeric is perfectly capable of evaluating a string and deciding whether it's something numeric. So, even if the value of blog_id as passed through the query string is a 7, this will return false.

"Wait... What?" you may be saying to yourself, right now. Yes, you read that correctly. It will be false, even if it's a 7. That's because (and here's the important bit) anything passed in the query string is considered by PHP to be a string. Even though there are no quotes around it, even though, to you and me, it's a number 7, PHP reads that as a string. This goes for all superglobals, in fact. In short, anything that is accessed through a '$_<something or other>' will return as a string: $_POST, $_SESSION, you get the idea.

One way around this might be to cast the value as an int, like so:

(int)$_GET['blog_id']

Or you could settle for is_numeric(), knowing that it will be fine with scientific notation, floats, etc. But that might be enough to make sure the value is safe, even if it doesn't match.

The two main takeaways for me, here, are that:

  1. Values stored in superglobals translate as strings in PHP.
  2. is_numeric() can reliably check to see whether strings are numeric or not, whereas is_int() will return false for strings, even if it looks like an integer.
Hope this helps someone!

Sunday, September 9, 2012

Getting the 'protected' Directory's Parent in Yii

I needed the directory just above "protected." Here's how you do it:


Yii::getPathOfAlias('webroot')

Accessing a Module from Anywhere

I recently had the need to access the configuration variables for a module from within a controller that didn't own the module. It was a little frustrating that I was inside a widget that was a component of the module. Someone new to Yii or to MVC might assume, "Hey! I'm in the modules own widget. Why can't I get to the variables?" But think of it a bit like a stream: data moves down from controllers, which do the processing bits, and flow to relatively "dumb" things, like views which are only supposed to display, you know, stuff. If things were to move the other direction, it would like the stream getting backed up or even polluted. So, we keep things flowing in the right direction, letting the smarter parts of the app pass data down to the intentionally less smart parts.

All that being said, I *could* have passed the values to the widget - which is basically a view - but this was already a relatively poorly designed 3rd party module, and I just wanted to move forward. (This is also a very useful module that provides a lot of functionality that I'd prefer not to write myself, so I'm making modifications to get it to work a little better.) This meant finding some other way to gain access to some variables that had been set in the config file for the module. This is done like so:


// other Yii confi things go here, like components, import, etc

'import' => array(

      ...

),

'components' => array(

     ...

),

// and here is where the module goes

'modules' => array(

     'moduleName' => array(

          'moduleVar1' => 'value',

          'moduleVar2' => 'otherValue',

          'moduleArray' => array(
               'key' => 'val'
         ),

     ),

// More things happen here


In this example, I wanted to do something like find out what the value of "moduleVar1" was. There's a way to get at these sorts of things by basically starting from the top (the application level) and working your way down by using Yii::app(). Here, for example, is how you can see which modules are loaded across the entire application:


print_r(Yii::app()->getModules());


To get a specific module, you do this:

Yii::app()->getModule('moduleName');


To get the value for a variable from that module, you'd do this:

Yii::app()->getModule('moduleName')->moduleVar1


And actually, I had an array of values called 'config' like 'moduleArray' above, in which case I needed to get the value of a key of that array, like so:

Yii::app()->getModule('moduleName')->moduleArray['key']


Hope this helps someone.


Monday, July 30, 2012

Migrating a Non-MVC Site to MVC Gradually


I'm currently working on a (really awful) site that is unfortunately not using an MVC (model-view-controller) framework. In fact, this (incredibly horrid) application doesn't use any kind of best practices. It might even be considered a top candidate for the poster child for How To Do Nearly Everything Poorly: it isn't DRY (don't repeat yourself), tightly-coupled, almost wholly uncommented, and on and on. You get the idea.* But it's also in production, and management has been in constant "need" of adding features and "fixing" things. I am sure that none of you know what I'm talking about...

By some miracle, I have found myself with a moment to breathe and to reflect on the architecture while the business tests a major upcoming release, so I've started poking around to see how to address our greatest pain points. Our dev team has been lamenting the lack of an MVC framework for some time, now. I have been a big fan of the Yii Framework for over a year, and I think I've managed to convince a couple of people that matter (including my main team mate) that it's an excellent choice for our app. The problem is convincing the Powers that Be to let us rewrite the entire site from the ground up! I just don't think they're going to allow us that kind of time to make changes that they won't immediately see.



Rather than simply give up, however, I've been using this momentary slow-down to consider how we might make the changes gradually and try a couple of things out. After a bit of Googling and reading up at StackOverflow, things were looking a bit bleak; most people who responded to questions about how to gradually migrate from a non-MVC site to an MVC framework pretty much said, "You have to do it all at once. Forget doing it sections at a time!" (Perhaps I was missing the more constructive articles, but the ones I saw were very similar to each other.) I found that to be defeatist and not particularly creative. It seems to me that, given the right combination of tools, you can do just about anything. I was pretty certain I'd actually worked on projects that went back and forth between different code bases that worked in parallel, using Apache's mod_rewrite to determine which root to use to serve up documents.

That became my mission, this morning - to figure out how to fall back to the old codebase if my attempts to respond to a request using a controller/action pair were unsuccessful. Although I haven't put it through serious rigors, yet, I'm happy to say that my early tests are actually working. I wanted to provide the basic approach, here, in case it helps someone else. Feel free to comment on it for improvements.

First off, some additional background:

We're using LAMP - Linux, Apache, MySQL, PHP. It couldn't get more classic LAMP than that, unless we used Perl. So, I'm discussing this in terms of PHP features, and I'll be using mod_rewrite, because I can. So there.

I also have the ability to edit my Apache conf files. If you can edit just your vhosts directives, you can probably do this. I'm not sure it works if you're using .htaccess. I think it might not, but perhaps someone can help with that.

It seems like we'd like to use Yii, but it's possible that we'll start with a much simpler (less nice) homemade framework that will be easier to then refactor into Yii. (We're going to have to redesign the entire db, and I think it will be better to hold off on Yii until that's done.)

I'm writing this with the assumption that you know something about MVC frameworks.

Finally, the site has a relatively straightforward directory structure. There are some twists and turns, but let's just say they dumped most of the public files in the main document root - call it "application" - and the administrative areas into a subdirectory called "admin." There are images and include directories, as well. In a nutshell, the original application structure looks something like this:

.
|----- application
|   |----- index.php
|   |----- faq.php
|   |----- contact.php
|   |----- images (images go here)
|   |----- includes (some includes)
|   |   |----- header.php
|   |   |----- footer.php
|   | ----- admin
|   |   |----- index.php
|   |   |----- do_important_things.php
|   |   |----- mangle_accounts.php

I think you get the idea. With "application" as the Apache document root, you can refer to "images" as "/images" from anywhere in the app and it "just works." The includes directory is just "/includes" - very convenient. And there's an admin directory with it's own index.php. Everything is accessed by going to the domain followed by some file or directory, e.g., www.crummyapp.com/faq.php or www.crummyapp.com/admin/mangle_accounts.php.

But I really despise this app, and I want to set up an MVC framework and start rewriting functionality like that handled in do_important_things.php in a way that lets me continue to use mangle_accounts.php until I've had a chance to rebuild it and then not simply delete the old file but somehow send it to a fiery File Hell. Until that time, it has to work. Well, it has to "work" as well is it does now.

My main strategy will be to:

  1. Add a front controller to intercept the request for my new MVC way.
  2. Leave the old files intact, somehow, and let them live, for the time being.
  3. Not get too crazy about models and views, just yet. Or maybe I will. But I'm mainly interested in that front controller. 
  4. Not try to set up a fresh Yii app and move it all over there in one fell swoop. This is an in-between solution, for now, that will borrow a lot from Yii, because it's awesome and because it will make later migration to Yii easier.


Oh, if I had a nickel for every time I'd lamented, "My kingdom for a front controller!" And although I do want all the other goodies that come with MVC, the first step will be getting a front controller working. Why is that? Because that's really the lynch pin for everything else. If I can gain control of that initial request, I can do pretty much whatever I want with it. It doesn't need to be fancy - I just need to be able to wrangle control away from the old faq.php and take my requests somewhere else. Whether that ends up initially being cleaner static files that are then further refactored into views and models and such isn't as important to me right now. (One thing I've learned as I've gotten older, my young grasshoppers, is that patience is a really good thing. Rome wasn't built in a day, etc., etc.)

My new application directory structure is something like this:


.
|----- application
|   |----- index.php (front controller)
|   |----- images
|   |----- css
|   | ----- protected
|   |   |----- config
|   |   |----- controllers
|   |   |----- models
|   |   |----- views


|   | ----- framework (core libraries for the framework)

That's probably familiar to a lot of you. The "framework" directory is like the "yii" directory. The "application" directory is still the document root. index.php is the front controller, as labeled.

The main question is, "How in the world do I make this new thing work in parallel with the old thing?" To answer that, I started by planning the directory structure. I decided to leave the old application in it's own directory at the top of the document root. Here's what it all looks like combined:


.
|----- application

|   |----- index.php (front controller)
|   |----- images
|   |----- css
|   |----- protected
|   |   |----- config
|   |   |----- controllers
|   |   |----- models
|   |   |----- views


|   |----- framework (core libraries for the framework)
|   |----- oldapplication

|   |   |----- index.php
|   |   |----- faq.php
|   |   |----- contact.php
|   |   |----- images
|   |   |----- includes (some includes)
|   |   |   |----- header.php
|   |   |   |----- footer.php
|   |   |----- admin
|   |   |   |----- index.php
|   |   |   |----- do_important_things.php
|   |   |   |----- mangle_accounts.php


All of the pre-existing files are now in a separate directory, oldapplication.

If that's where I left it, assuming I was using a .htaccess file typical of MVC frameworks like this (Google it - they're everywhere), when the visitor arrived at the application, Apache would serve up the index.php front controller file. That would do some not terribly fancy front controller stuff that I won't get into, but the main thing to note is that, like most MVC front controllers, it would be expecting the URL to contain some reference to a controller and an action. For example, the URL might look like:

http://www.crummyapp.com/home/index

in which case it would know to execute the "index" function (the action) contained in the controller named "home" (i.e., protected/controllers/HomeController.php).

That's great for my new sections of the app, but what if I want to oldapplication/faq.php where it is, for now, and serve it up old-school? Unfortunately, my standard .htaccess file would have a hard time with that.

The solution came, in part, from this article: MVC Framework Routing (static content vs. dynamic). The writer modified their app's Apache config file, adding the following to their vhosts directives (edited to match my directory structure):


RewriteEngine On
RewriteCond %{DOCUMENT_ROOT}/oldapplication%{REQUEST_URI} !-f
RewriteCond %{DOCUMENT_ROOT}/oldapplication/admin/%{REQUEST_URI} !-f
RewriteRule ^(.*)$ %{DOCUMENT_ROOT}/index.php [L]
RewriteRule (.*) %{DOCUMENT_ROOT}/oldapplication$1 [L]


A brief dissection, line by line:

  1. Make sure the RewriteEngine is set to "On" or this might not work at all.
  2. Once that is taken care of, there are two conditions specified by the "RewriteCond" directive. The first condition checks to see if the request is NOT a file in the old application's main directory. The next condition checks to see if the request is NOT a file in the old application's "admin" subdirectory. (The "!-f" means, "see if this is not a regular file.")
  3. After that, is the first RewriteRule. That RewriteRule is what happens if those two conditions are BOTH true. (The conditions are basically chained together by an "and" unless I explicitly use "OR" which isn't what I want.)
  4. That second RewriteRule is not preceded by any conditions, so it stands as-is.

Without getting into too much detail about mod_rewrite, these lines will check for the existence of the requested file in both the original app's home directory and in it's admin subdirectory. If it cannot find the file, it will go to the front controller (index.php in the doc root). But, if those conditions fail, and it doesn't go to the front controller, it proceeds to the next rule which will rewrite the request as www.crummyapp.com/oldapplication/whatevertherequestwas.php.

So, that's pretty great! I can now serve up both old and new files. If it doesn't find the old, it will go to the MVC version. Nice! But I still had a problem, at that point. Do you remember those "images" and "includes" directories? Well, throughout the application, there are references to "/images" and "/includes" - ugh! I'm not going to edit all of those URLs!

To solve that problem, I used PHP's ability to set configuration options dynamically using ini_set(). The configuration setting I needed to edit is called "include_path" which does what it sounds like it does, sets the default include path for PHP. In other words, when PHP goes looking to include something, it checks any and all include paths specified until it runs out of them. The line of code looks like this:

ini_set("include_path", ini_get("include_path").";".$_SERVER['DOCUMENT_ROOT']."/oldapplication");

So, the include_path setting now has the "oldapplication"directory in it when looking for things like "images."

Believe it or not, the basic tests I've run serve up the old files beautifully, with no broken images or missing included classes, headers, or whatever. Neat!

One loose end that I still need to address is:

index.php - Because this is the default index page in any directory of my application, it is in conflict with my front controller's name. I could rename the front controller (and I might just do that), but it is still going to be a problem for those links that have the directory name but do not include index.php as the desired file! (This is a pretty common thing to do, e.g., http://www.crummyapp.com/admin/.) mod_rewrite won't know which filename is being requested, so I think I have to go through and explicitly include that for all URLs that need it.

There are probably others. We'll see.

Anyway, I'm pretty excited, because that was a pretty big hurdle, and it appears to be working! If I run into other things, I'll try to remember to post them here.

Cheers!

* I did NOT have a hand in writing or designing this thing. My present employer purchased it from a competitor that was sinking like the Titanic, and no techies had a chance to look behind the curtain before they agreed to buy it. 'Nuff said.

Thursday, March 1, 2012

Helpful Mod Rewrite Tips

This didn't solve the problem I was trying to fix, but I didn't want to forget about it! It's a wealth of useful info:

http://www.askapache.com/htaccess/modrewrite-tips-tricks.html

Monday, January 30, 2012

Vim Notes

I love vim. I mean, there are a couple of great GUI editors that I like, but when I'm working in a terminal, vim is not just my editor of choice, but I actually prefer it to a bunch of other GUI editors I could choose from. The problem is that there is SO much to know. I've got the basics down, since I've been using it for over 20 years, but I honestly haven't taken the time to learn some of the more intermediate to advanced features until recently. I thought I'd keep some notes along the way in this blog post. Enjoy!

To paste in yanked lines and have them indent with the surrounding code, use ']p' instead of just 'p'
Use '>' to indent and '5>>' to indent 5 lines

Source: http://stackoverflow.com/questions/235839/how-do-i-indent-multiple-lines-quickly-in-vi

Using :split to open another document is great. But you don't always want the two windows to be equally divided, you need to make some adjustments. You could do this:

ctrl-w +

but you might have to do it over and over. Instead of typing that twenty times, just do this:

20 ctrl-w +

More goodies:
http://www.oualline.com/vim-cook.html#copy_block