Reusable code and the import of vendors files

Published on and tagged with cakephp  plugin  vendor

If you write reusable code with CakePHP (e.g. a component) you often forget (or at least I do) that the code could be used either in applications or in plugins. In most cases though, this doesn’t really matter, as your code will work fine in both “environments”. However, consider the following snippet:

public function example() {
    App::import('Vendor', 'example', array('file' => 'Example.php'));
    $example = new Example();
    ...
}

This snippet works fine if it is used in an application, but it will fail with a “Class not found” error if the snippet and the corresponding vendors file (“Example.php”) are put into a plugin. The reason for the failure is that you have to use the plugin name as a prefix when importing something from a plugin, even if you use App::import() from within the respective plugin:

// test is the plugin name
App::import('Vendor', 'test.example', array('file' => 'Example.php'));

The consequence of this behavior is that your code has to know in which “environment” it runs if you want to make it really reusable. An alternative is to “circumvent” the application “environment” and to distribute your code, and the required vendors files, as a plugin.

In the OpenID component I solved this problem by determining in which “vendors” directory the required library is. And if it is in the plugin’s “vendors” directory, I set an instance variable $importPrefix with the plugin name. This prefix is then used whenever a file is imported.

Here is the relevant code:

class OpenidComponent extends Object {
    private $importPrefix = '';

    public function __construct() {
        parent::__construct();

        $pathToVendorsFolder = $this->getPathToVendorsFolderWithOpenIDLibrary();
		
        if ($pathToVendorsFolder == '') {
            exit('Unable to find the PHP OpenID library');
        }
		
        if ($this->isPathWithinPlugin($pathToVendorsFolder)) {
            $this->importPrefix = $this->getPluginName() . '.';
        }

        $this->importCoreFilesFromOpenIDLibrary();
    }

    private function getPathToVendorsFolderWithOpenIDLibrary() {
        $pathToVendorsFolder = '';
		
        if ($this->isPathWithinPlugin(__FILE__)) {
            $pluginName = $this->getPluginName();
			
            if (file_exists(APP.'plugins'.DS.$pluginName.DS.'vendors'.DS.'Auth')) {
                $pathToVendorsFolder = APP.'plugins'.DS.$pluginName.DS.'vendors'.DS;
            }
        }

        if ($pathToVendorsFolder == '') {
            if (file_exists(APP.'vendors'.DS.'Auth')) {
                $pathToVendorsFolder = APP.'vendors'.DS;
            } elseif (file_exists(VENDORS.'Auth')) {
                $pathToVendorsFolder = VENDORS;
            }
        }
		
        return $pathToVendorsFolder;
    }
	
    private function getPluginName() {
        $result = array();
        if (preg_match('#'.DS.'plugins'.DS.'(.*)'.DS.'controllers#', __FILE__, $result)) { 
            return $result[1];
        }

        return false;
    }

    private function importCoreFilesFromOpenIDLibrary() {
        App::import('Vendor', $this->importPrefix.'consumer', array('file' => 'Auth'.DS.'OpenID'.DS.'Consumer.php'));
        App::import('Vendor', $this->importPrefix.'sreg', array('file' => 'Auth'.DS.'OpenID'.DS.'SReg.php'));
    }

    private function isPathWithinPlugin($path) {
        return strpos($path, DS.'plugins'.DS) ? true : false;
    }
}

This code is meant to give you an idea of how it could be solved in that specific situation, and not as a solution you can copy 1:1…

Happy baking!

3 comments baked

© daniel hofstetter. Licensed under a Creative Commons License