Monday, March 22, 2010

Image class

Ok, here I go. The very first violation of my own rule - not posting anything obvious.
This time however I needed to find a good class Image that works with GD and is designed nicely. After 10 min of search I decided to write my own. So, for the lack of better place, I will put it here. If you are not into the obvious - skip this post.

<?

 /**
  * Class Image
  * @author Alexander Podgorny
  * @license GPL
  */

 class Image {
  const RESIZE_FILL   = 2; // When resizing, constrain proportions, keep largest dimension
  const RESIZE_FIT  = 3; // When resizing, constrain proportions, keep smallest dimension
  const RESIZE_STRETCH  = 1; // When resizing, do not constrain proportions, keep both dimensions
  
  const TYPE_JPEG   = 10;
  const TYPE_GIF   = 11;
  const TYPE_PNG   = 12;
  
  private $_sFileName = '';
  private $_oImage  = null;
  private $_nW   = 0;
  private $_nH  = 0;
  
  public function __construct($sFileName=null) {
   if ($sFileName) {
    $this->open($sFileName);
   }
  }
  
  public function __destruct() {
   imagedestroy($this->_oImage);
  }
  
  public function open($sFileName) {
   if (!file_exists($sFileName)) {
    throw new Exception('File could not be found: '.$sFileName);
   }
   $this->_sFileName  = $sFileName;
   $sExtension   = strtolower(strrchr($sFileName, '.'));
   switch ($sExtension) {
          case '.jpg':
          case '.jpeg':
              $this->_oImage = imagecreatefromjpeg($sFileName);
              break;
          case '.gif':
              $this->_oImage = imagecreatefromgif($sFileName);
              break;
          case '.png':
              $this->_oImage = imagecreatefrompng($sFileName);
              break;
          default:
     throw new Exception('Unknown file type: '.$sFileName);
      }   
   $this->_nW   = imagesx($this->_oImage);
   $this->_nH  = imagesy($this->_oImage);
   return $this;
  }
  
  public function resize($nW, $nH, $nOption=self::RESIZE_FIT) {
   $nSrcRatio = $this->_nW / $this->_nH;
   $nDstRatio = $nW / $nH;
   
   switch ($nOption) {
    case self::RESIZE_STRETCH:
     // do nothing;
     break;
    case self::RESIZE_FILL:
     if ($nDstRatio > $nSrcRatio) {
      $nH = $nW / $nSrcRatio;
     } else {
      $nW = $nSrcRatio * $nH;
     }
     break;
    case self::RESIZE_FIT:
    default:
     if ($nDstRatio > $nSrcRatio) {
      $nW = $nSrcRatio * $nH;
     } else {
      $nH = $nW / $nSrcRatio;
     }
     break;
   }
   $nW = floor($nW);
   $nH = floor($nH);
   $oImageResized = imagecreatetruecolor($nW, $nH);
   imagecopyresampled(
    $oImageResized, 
    $this->_oImage, 
    0, 0, 0, 0, 
    $nW, 
    $nH, 
    $this->_nW, 
    $this->_nH
   );
   imagedestroy($this->_oImage);
   $this->_oImage = $oImageResized;
   $this->_nW = $nW;
   $this->_nH = $nH;
   return $this;
  }
  
  public function save($sFileName=null, $nQuality=null) {
   if (!$sFileName) { $sFileName = $this->_sFileName; }
   $sExtension   = strtolower(strrchr($sFileName, '.'));
   if (!$sFileName) {
    throw new Exception('Could not save file. File name is not supplied');
   }
   switch ($sExtension) {
          case '.jpg':
          case '.jpeg':
           if ($nQuality !== null) {  // otherwise use built in default
      if ($nQuality < 0)   { $nQuality = 0; }
            if ($nQuality > 100) { $nQuality = 100; }
           }
              imagejpeg($this->_oImage, $sFileName, $nQuality);
              break;
          case '.gif':
              imagegif($this->_oImage, $sFileName);
              break;
          case '.png':
           if ($nQuality !== null) {  // otherwise use built in default
      if ($nQuality > 9) { $nQuality = 9; }
      if ($nQuality < 0) { $nQuality = 0; }
           }
              imagepng($this->_oImage, $sFileName, $nQuality);
              break;
          default:
     throw new Exception('Unknown file type: '.$sFileName);
     break;
      }   
   return $this;
  }
  
  public function output($nType=self::TYPE_JPEG, $nQuality=null) {
   switch ($nType) {
          case self::TYPE_JPEG:
           if ($nQuality) {  // otherwise use built in default
      if ($nQuality < 0)   { $nQuality = 0; }
            if ($nQuality > 100) { $nQuality = 100; }
           }
           header('Content-type: image/jpeg');
              imagejpeg($this->_oImage, null, $nQuality);
              break;
          case self::TYPE_GIF:
           header('Content-type: image/gif');
              imagegif($this->_oImage, null);
              break;
          case self::TYPE_PNG:
           if ($nQuality) {  // otherwise use built in default
      if ($nQuality > 9) { $nQuality = 9; }
      if ($nQuality < 0) { $nQuality = 0; }
           }
           header('Content-type: image/png');
              imagepng($this->_oImage, null, $nQuality);
              break;
          default:
     throw new Exception('Unknown file type');
     break;
      }   
   return $this;
  }
  
 }

?>

Sunday, March 7, 2010

Implied Class Pattern with __autoload()

One more thing about not-so-obvious capabilities of PHP: Implied Class Pattern.

Let's say you are implementing a Model layer in your MVC where each model class corresponds to one table. So if your table name is "member" than you might want to have class MemberModel. Then you create table "city", then "state", then forty more tables. So you end up with forty more classes that share exactly the same functionality with one only difference: table name.

Here is how every one of your files might look like:

<?php
    class MemberModel extends Model {}
?>

Of course you will try to use __autoload() to dynamically load them at run time.
But loading extra forty (or more) files translates into reading from the disk just as many times. Not the most efficient solution.

Another inefficient thing about this approach is having to write those files every time you add a table to your database.

All of this can be avoided with Implied Class Pattern. Consider the following code:

function __autoload($sClassName) {
    $bLoaded = false;
    foreach ($_ENV['AUTOLOAD_PATHS'] as $sAutoloadPath) {
        $sFilePath = $sAutoloadPath.$sClassName.'.php';
        if (file_exists($sFilePath)) {
            require_once $sFilePath;
            $bLoaded = true;
            break;
        }
    }
    if (!$bLoaded) {
        // Implied classes
        if (preg_match('/Model$/', $sClassName)) {
            eval("class $sClassName extends Model {}");
        } else {
            throw new Exception('Class '.$sClassName.' not found');
        }
    }
}

This way whenever you need a specific subclass of Model it is created for you "on-the-fly" avoiding multiple redundant files and disk activity.

Of course if you need to add a model-specific functionality, you can always add that extra file into your autoloaded directory and it will load the implemented class before it tries to imply it. So win-win.

To summarize: Where do you use Implied Class Pattern? Whenever you have multiple subclasses of one base class with little or no difference from one another.

Enjoy.

Tuesday, March 2, 2010

Wrapping it up nicely :: Base Object :: Part 6

So, now we have the whole library. Now we need a name. I will call it dotPHP. Just because I like the name.

Now, why not even do the definition and extension in one line of code? I will define a DotPHP class to help us contain it all within a nice namespace:

class DotPHP {
        public static function defineClass($sClassName, $mBase=null) {
            eval('class '.$sClassName.' extends Object {}');
            if ($mBase) {
                $sClassName::extend($mBase);
            }
        }
    }

Now the all the code is done in a fewer lines of code:

DotPHP::defineClass('A', array(
        'method3' => function($owner) { print 'method3'; }
    ));
    DotPHP::defineClass('B', 'A');
    
    print $o2->method3(); // output: method3

Please leave some feedback so that I know if you guys like the syntax.

Extending PHP classes at run time :: Base Object :: Part 5

In part 4 of this article we have extended objects at run time. Now wouldn't it be nice if we could do the same with classes? Also at run time.

Sure we can. Unfortunately, the __staticCall, __staticGet and __staticSet are not implemented in PHP 5.3, so we will have to improvise while still keeping the syntax pretty.

I am not going to list all the multiple modifications I did to the base object class, I will just dump the whole thing here, take a look:

class Object {
        
        /**************** PRIVATE ****************/
        
        /**************** PROTECTED **************/
        
        protected static $_aClassProperties = array();
        protected $_aProperties = array();
        
        /**************** STATIC ****************/

        public static function construct() {
            $aArgs = func_get_args();
            $nArgCount = count($aArgs);
            $aArgTokens = array();
            for ($i=0; $i<$nArgCount; $i++) {
                $aArgTokens[] = '$aArgs['.$i.']';
            }
            $sArgTokens = implode(',', $aArgTokens);
            $sEval = 'return new '.get_called_class().'('.$sArgTokens.');';
            return eval($sEval);
        }

        /**************** PUBLIC ****************/
        
        public function __construct() {
            if (!isset(self::$_aClassProperties[get_called_class()])) {
                self::$_aClassProperties[get_called_class()] = array();
            }
            $this->extend(self::$_aClassProperties[get_called_class()]);
        }
        public function __call($sMethod, $aArgs) {
            if (isset($this->_aProperties[$sMethod])) {
                $f = $this->_aProperties[$sMethod];
                $nArgCount = count($aArgs);
                $aArgTokens = array('$this');
                for ($i=0; $i<$nArgCount; $i++) {
                    $aArgTokens[] = '$aArgs['.$i.']';
                }
                $sArgTokens = implode(',', $aArgTokens);
                $sEval = 'return $f('.$sArgTokens.');';
                return eval($sEval);
            } else {
                throw new Exception('Method '.$sMethod.' does not exist in class '.get_class($this));
            }
        }
        public function __get($sProperty) {
            return isset($this->_aProperties[$sProperty]) 
                ? $this->_aProperties[$sProperty] 
                : null;
        }
        public function __set($sProperty, $mValue) {
            $this->_aProperties[$sProperty] = $mValue;
            return $mValue; 
        }
        public function extend($mBase) {
            $sClass = get_called_class();
            if (!isset(self::$_aClassProperties[$sClass])) {
                self::$_aClassProperties[$sClass] = array();
            }
            
            if ($this) {                             // object call
                if (!is_array($mBase)) {
                    $mBase = $mBase->_aProperties;
                }
                $this->_aProperties = array_merge(
                    $this->_aProperties, 
                    $mBase
                );
            } else {                                 // static call
                if (is_string($mBase)) {            // class name is supplied
                    $mBase = self::$_aClassProperties[$mBase];
                }
                self::$_aClassProperties[$sClass] = array_merge(
                    self::$_aClassProperties[$sClass],
                    $mBase
                );
            }
        }
    }

Suffice it to say that I have made the following things possible:

class A extends Object {};
    A::extend(array(
        'method3' => function($owner) { print 'method3'; }
    ));
    class B extends Object {};
    B::extend('A');
    
    $o1 = Object::construct();
    $o1->method1 = function($owner) { return 'method1'; };
    $o2 = B::construct();
    $o2->method2 = function($owner) { return 'method2'; };
    $o2->extend($o1);
    print $o2->method1(); // output: method1
    print $o2->method2(); // output: method2
    print $o2->method3(); // output: method3 

To rephrase the above: now we can use the same extend() method to respond to static calls. Specifically two types of static calls extend([string ClassName]) and extend([array aProperties]).

All the properties extended at class level are copied to the object when it is instantiated. Of course if a constructor is overridden in subclass, it would have to call parent::__construct().

Cool?

Extending objects in PHP at run-time :: Base Object :: Part 4

So, on our quest to add JavaScript flexibility to PHP we need another thing common in many JS frameworks: ability to extend objects at run time.

This is super easy based on what we have already done.
Add this method to your class Object:

public function extend(Object $oAnotherObject) {
        $this->_aProperties = array_merge(
            $this->_aProperties, 
            $oAnotherObject->_aProperties
        );
    }

Now you can copy dynamic methods and properties from Object to Object at run time like so:

$o1 = Object::construct();
    $o1->method1 = function($owner) { return 'method1'; };
    $o2 = Object::construct();
    $o2->method2 = function($owner) { return 'method2'; };
    $o2->extend($o1);
    print $o2->method1(); // output: method1
    print $o2->method2(); // output: method2

Dynamically adding methods in PHP :: Base Object :: Part 3

If you are a seasoned javascript developer, coding in PHP must feel like riding a bike in a straight jacket. Some of the features that you miss so much may still be added. If not through direct syntax, but through your own ingenuity.

In this article we will enable dynamic methods. Worth mentioning that this feature will only work in PHP 5.3 since closures were added only at that point.

So, add the following method to your class Object:

public function __call($sMethod, $aArgs) {
    if (isset($this->_aProperties[$sMethod])) {
        $f = $this->_aProperties[$sMethod];
        $nArgCount = count($aArgs);
        $aArgTokens = array('$this');
        for ($i=0; $i<$nArgCount; $i++) {
            $aArgTokens[] = '$aArgs['.$i.']';
        }
        $sArgTokens = implode(',', $aArgTokens);
 $sEval = 'return $f('.$sArgTokens.');';
 return eval($sEval);
    }
}
Now you can add dynamic methods almost like in JavaScript:
$oMyObject = new Object();
$oMyObject->myNewMethod = function($owner, $someparam) { print $someparam; };
$oMyObject->myNewMethod('Yay! Javascript-style methods!');
Note the extra "$owner" parameter. It is reserved to use instead of "$this" in this implementation. Closure implementation in php is still not smart enough to accept $this from the scope of the object they are called from. So, now you are like: "huh? oooh! yeah! ...but...". Take a moment to digest it. Breathe. To answer some questions that may be forming in your mind: Yes, it does all the things a method should do: 1. As I mentioned, use $owner instead of $this, and it will be provided for you. Say if you have a protected or private property $myPrivateProp, you can access it like so:
$oMyObject->getPrivateProp = function($owner) { return $owner->myPrivateProp; };
2. Pass by reference is supported if you specify it in both places: definition and call. Like so:
$ref = 1;
    $oMyObject->test = function($owner, &$ref) { return $ref = 2; };
    print $ref; // outputs: 1
    print $oMyObject->test(&$ref);
    print $ref; // outputs: 2

Static Constructor in PHP :: Base Object :: Part 1

In these series I will show a few neat examples of what is possible with PHP5-5.3. I will not reiterate on the obvious, but try to expose the features that are not widely explored.

Starting from this first article I will be building a base object for our library: class Object, so that every class extending it would inherit all the features we add here.

The first feature is indirectly available to us from PHP5.0.

INLINE STATIC CONSTRUCTOR:

class Object {
    public static function construct() {
        $aArgs = func_get_args();
        $nArgCount = count($aArgs);
        $aArgTokens = array('$this');
        for ($i=0; $i<$nArgCount; $i++) {
            $aArgTokens[] = '$aArgs['.$i.']';
        }
        $sArgTokens = implode(',', $aArgTokens);
        $sEval = 'return new '.get_called_class().'('.$sArgTokens.');';
        return eval($sEval);
    }
}
What does it do? Well, consider this code:
$oMyObject = new Object();
    $oMyObject->myMethod();
You can now do:
Object::construct()->myMethod();

Notice, you can still assign the return value to $oMyObject for the future reference if needed.

Stay tuned, more interesting patters coming up.

Dynamically adding properties in PHP :: Base Object :: Part 2

I promised in my previous article that I will not talk about the obvious in PHP. However, this one deserves mentioning, since we will be building on top of it as we go along.

Add this to our class Object that we have started in the Part 1 of this article.
All it does - it makes our object property-extensible.

protected $_aProperties = array();
...
public function __get($sProperty) {
    return isset($this->_aProperties[$sProperty]) 
        ? $this->_aProperties[$sProperty] 
        : null;
}
public function __set($sProperty, $mValue) {
    $this->_aProperties[$sProperty] = $mValue;
    return $mValue; 
}

Now you can set properties dynamically, like so:

$oMyObject->non_existing_property = 6;
print $oMyObject->non_existing_property; // outputs: 6