Include CodeIgniter Models using spl_autoload

CodeIgniter’s models may be added to autoload.php (which will always load them even if they’re not required) or must be manually loaded using code such as:

$this->load->model('views\account\logon');

While it’s only one line of code, it soon becomes tedious having to have a similar line across every action in every controller. Consequently, I created a CodeIgniter library (after taking inspiration from Doctrine’s ClassLoader) which uses spl_autoload. Class_loader automates the process of loading classes within a defined namespace and only requires a single line of configuration; therefore no changes to autoload.php or manual calls to load are required.

Class_loader.php:

<?php if (!defined('BASEPATH')) exit('No direct script access allowed');

/**
 * Class_loader is a CodeIgniter library which uses SPL autoload to
 * automatically include classes within the defined namespace.
 * 
 * @author Andrew Mackrodt <andrew@ajmm.org>
 * @version 2011.06.23
 */
class Class_loader
{
    /**
     * @param string $namespace The namespace to apply the autoloader.
     * @param string $includePath
     *     The parent folder of the root namespace. Defaults to CodeIgniter's
     *     APPPATH if no value is given.
     */
    public function register($namespace, $includePath = null)
    {
        if (!empty($namespace) && $namespace[0] == '\\')
        {
            // Strip leading namespace separator.
            $namespace = substr($namespace, 1);
        }

        if (empty($includePath))
        {
            // Set the include path to the CI application folder.
            $includePath = APPPATH;
        }

        // Ensure the include path ends with the OS directory separator.
        $includePath = rtrim($includePath, '\\/').DIRECTORY_SEPARATOR;

        // Register the auto-loader.
        $classLoader = new _ClassLoader($namespace, $includePath);
        spl_autoload_register(array($classLoader, 'loadClass'));
    }
}

/**
 * A helper class to be used by Class_loader. 
 */
class _ClassLoader
{
    private $namespace;
    private $includePath;

    public function __construct($namespace = null, $includePath = null)
    {
        $this->namespace = $namespace;
        $this->includePath = $includePath;
    }

    public function loadClass($className)
    {
        if ($this->namespace !== null
                && strpos($className, $this->namespace.'\\') !== 0)
        {
            return false;
        }
        
        // Convert a namespace to a path using the OS directory separator.
        $classPath = str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $className);
        
        require_once $this->includePath.$className.'.php';
        return true;
    }
}

Usage:

The classes you wish to autoload must contain a namespace and be located appropriately, e.g. I have a Script class which is located at APPPATH/models/views/shared/Script.php and has the namespace models\views\shared.

$this->class_loader->register('models\views', APPPATH);

Deny Access to a Directory with IIS

Apache is the dominant server for PHP content although IIS can be a respectable substitute. However, upon first inspection IIS does not appear to be as flexible as Apache, particularly regarding dynamic configuration through .htaccess. There are generally three main areas where .htaccess files are used:

  • URL rewriting
  • Providing basic or digest authentication
  • Denying access to a directory

The CodeIgniter framework for example contains an .htaccess file in the root of the application and system folders to deny direct access to important site resources. Additionally, we may wish to have an uploads folder where file access is managed through our PHP application; we cannot use ACLs in such an example as the anonymous IIS user must have access to the resource yet the client should not be able to access it directly.

Apache lets us define this behavior easily by adding one line to an .htaccess file in the protected directory:

deny from all

The client will now receive a 403 Forbidden error should they try to to access the directory or any resource within. ASP.NET developers may use forms or windows authentication to address the problem but these are not easily available from a PHP application. Luckily IIS 7 includes a Request Filtering module which allows us to deny access to a directory using a web.config file:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <security>
            <requestFiltering>
                <denyUrlSequences>
                    <add sequence="/" />
                </denyUrlSequences>
            </requestFiltering>
        </security>
    </system.webServer>
</configuration>

This configuration will send the client a 404.5 Not Found status. While the status code is not as accurate as Apache’s 403 status, it does at least prevent access to the directory and it’s resources.

Continue reading

IIS Directory Permissions and File Uploads

IIS 7 requires the following user accounts to have read access to your webroot: IIS_IUSRS and IUSR. For a typical ASP.NET application IIS_IUSRS may also be granted write permissions for specific directories or files. However, if impersonation is used (e.g. fastcgi.impersonate is recommended for PHP), then the IUSR account must also be granted write permissions to the resources.

The following command-line actions will set the permissions correctly (note: replace C:\inetpub\wwroot with your webroot):

ICACLS "C:\inetpub\wwwroot" /Grant "IIS_IUSRS":(OI)(CI)M
ICACLS "C:\inetpub\wwwroot" /Grant "IUSR":(OI)(CI)M

Using PHP Reflection to read a Protected Property

Sometimes it’s necessary to obtain the value of a protected or private property. Fortunately PHP 5.3 includes a new reflection method named setAccessible which allows us to do so.

The Method:

/**
 * Get the value of a property using reflection.
 *
 * @param object|string $class
 *     The object or classname to reflect. An object must be provided
 *     if accessing a non-static property.
 * @param string $propertyName The property to reflect.
 * @return mixed The value of the reflected property.
 */
public static function getReflectedPropertyValue($class, $propertyName)
{
    $reflectedClass = new ReflectionClass($class);
    $property = $reflectedClass->getProperty($propertyName);
    $property->setAccessible(true);

    return $property->getValue($class);
}

Example Usages:

getReflectedPropertyValue($foo, 'myProtectedProperty');
getReflectedPropertyValue('Foo', 'myProtectedStaticProperty');
getReflectedPropertyValue('Foo\Bar', 'myProtectedStaticProperty');

SQL Error (1064) when Creating a Trigger in MySQL

“SQL Error (1064): You have an error in your SQL syntax” occurs when a query has been incorrectly delimited. This is a common cause of confusion when creating triggers as the internal statement needs to be terminated in addition to the create or alter trigger statement. MySQL uses a semi-colon as the default delimiter and does not work with nested delimiters, e.g.

CREATE TRIGGER ... AFTER INSERT ON ...
FOR EACH ROW
BEGIN
    INSERT ...;
END;

Notice how there are two semi-colon delimiters in the above example. We need to temporarily define a different delimiter to workaround this problem, the following example uses a double forward slash:

DELIMITER //;

Now we need to add the new delimiter at the end of our statement so that it looks like this:

CREATE TRIGGER ... AFTER INSERT ON ...
FOR EACH ROW
BEGIN
    INSERT ...;
END;//

Assuming that there are no other syntax errors, the statement should execute without any errors. To restore the delimiter to a semi-colon use the following query:

DELIMITER ;//

Included below is a full example with the necessary table schema:
Continue reading

Doctrine 2 CustomStringFunction and Undefined Index

Doctrine 2 allows for user defined functions to be created, this allows us to add vendor specific functions such as MySQL’s CONCAT_WS. I had the need to implement my own functions in a project I’ve been working on as DQL does not support a way to CONCAT expressions that contain null values without returning null (i.e. there is no IFNULL or ISNULL function).

After reading the Adding your own functions to the DQL language documentation and attempting to implement my own function I was repeatedly getting the following error: Undefined index and Class name must be a valid object or a string. I had a feeling that this was to do with my custom function not being auto-loaded although I was certain that I had followed the documentation correctly.

Having looked through Doctrine’s ORM classes I discovered a function named CustomFunctionsReturningStrings inside Parser.php. I set a breakpoint inside the function and was surprised to see that it never gets called. This is a bug in versions of Doctrine prior to 2.0.5.

If you are unable or do not wish to upgrade between versions but still need this fix you can apply the following patch to \Doctrine\ORM\Query\Parser.php:

2304c2304,2305
<                 return $this->FunctionsReturningStrings();
---
>                 // do NOT directly go to FunctionsReturningString() because it doesnt check for custom functions.
>                 return $this->FunctionDeclaration();

Integrating Doctrine 2 with CodeIgniter 2

An easy to follow tutorial for integrating Doctrine 2 with CodeIgniter 2 is available at doctrine-project. Unfortunately, there are a few configuration problems which would prevent the proper loading of proxies if that configuration were to be used exactly as it is.

The problem is related to how the Proxies ClassLoader has been registered. PHP will attempt to load proxies which have the namespace Proxies from the directory {APPATH}/models/proxies/Proxies.

$proxiesClassLoader = new ClassLoader('Proxies', APPPATH.'models/proxies');
$proxiesClassLoader->register();

However, Doctrine has been configured to write proxies to the directory {APPATH}/models/proxies.

$config->setProxyDir(APPPATH.'/models/proxies');
$config->setProxyNamespace('Proxies');

Additionally, the suggested configuration registers a DefaultAnnotationDriver using the directory {APPPATH}/models/Entities which is unlikely to exist.

$driverImpl = $config->newDefaultAnnotationDriver(array(APPPATH.'models/Entities'));
$config->setMetadataDriverImpl($driverImpl);

The configuration and instructions shown below address these issues and contain some other useful notes on the integration.
Continue reading