Namespaces in vendor files and Cake2.x

Have you needed any vendor files (third party libs) that are already written in PHP5.3 and use namespaces?
You might find out that they usually don’t work out of the box.
The included class is not the problem, but all referenced classes inside this one and its namespace.

I also could not get it to work right away. With some help from tigrang at stackoverflow (namespaces-in-vendor-files-cakephp2-x-and-php5-4-3), though, I could manage to get the class running.

For future reference and for those who stumble upon the same issue here a quick guide.

The namespace loader

Since Cake2.x does not support namespaces yet (3.x then will) we have to write our own loader. You can either put it into your bootstrap or above the class which uses the namespaced vendor lib. I went with the second for now, but the first one would be a more long-term solution.

// PHP5.3 namespace loader for Cake2.x
spl_autoload_register(function ($class) {
    foreach (App::path('Vendor') as $base) {
        $path = $base . str_replace('\\', DS, $class) . '.php';
        if (file_exists($path)) {
            include $path;
            return;
        }
    }
});

That’s all you need. Now you can import your vendor file as usual:

// APP/Lib/MyClass.php
App::import('Vendor', 'PackageName', array('file' => 'PackageName/SubFolder/ClassName.php'));

class MyClass {

    protected $_Engine;

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

        if (!class_exists('PackageName\SubFolder\ClassName')) {
            throw new RuntimeException('Your desired vendor library cannot be found');
        }
        $this->_Engine = new PackageName\SubFolder\ClassName($settings);
    }
}

Using classes from the vendor namespace

At some point you might want to use more of the classes in the vendor namespace directly in your cake lib class. You cannot just use say the class "Rule" from "PackageName\SubFolder\SubSubFolder". You need to prepend the namespace to it:

$newRule = new PackageName\SubFolder\SubSubFolder\Rule($argument);

Same goes for if (class_exists()) {} or similar checks like:

if ($value instanceof PackageName\SubFolder\SubSubFolder\Value\URL) {}

Note: this article is PHP5.3 and above! You can also use this in your 2.x app if you have this minimal php version running (which shouldn’t be a problem nowadays – and with PHP5.4 upcoming already).

It shows that it is possible to use namespaced third party products in your Cake2.x apps.

7 Comments

  1. Thanks, Mark, but I have to tell you, that this is not working with vendor libraries in plugins.

    Or maybe I am doing it wrong?

    I am having the Paymill library in a plugin called "Payment" in (from app root): "Plugin/Payment/Vendor/Paymill"

    I am trying import the vendor library like this:

    App::import(
                'Vendor',
                'Payment.Paymill',
                array(
                    'file' => 'Paymill' . DS . 'lib' . DS . 'Paymill' . DS . 'Request.php',
                )
            );
    
            $paymillRequest = new Paymill\Request(Configure::read('Plugins.Payment.Paymill.API.PrivateKey'));

    I put the autoloader in the bootstrap.php of the Payment plugin.

    Now I get this:

    Error: Class ‘Paymill\API\Curl’ not found

    This is because App::path(‘vendor’) does not include the right paths for the autoloading.
    It tries only these two in the autoloader:

    app/Vendor/Paymill/API/Curl.php

    cakephp2.3/vendors/Paymill/API/Curl.php

    Am I doing something wrong here?

    Regards
    func0der

  2. Pretty dirty way, but I put this in the Payment plugins bootstrap.php:

        // Add Paymill library path to Vendor paths.
        $paymentPluginVendorPath = App::path('Vendor', 'Payment');
        $paymentPluginVendorPath = $paymentPluginVendorPath[0];
    
        App::build(
            array(
                'Vendor' => array(
                    $paymentPluginVendorPath . 'Paymill' . DS . 'lib' . DS,
                ),
            )
        );
  3. Hi Mark, I can’t make it work. Can you please help me out?
    I’m using this lib from https://github.com/jwage/purl.
    On the bottom of my bootstrap.php I put this code

    spl_autoload_register(function ($class) {
        foreach (App::path('Vendor') as $base) {
            $path = $base . str_replace('\\', DS, $class) . '.php';
            if (file_exists($path)) {
                include $path;
                return;
            }
        }
    });

    I imported the main class in the top of my controller

    App::uses('AppController', 'Controller');
    App::import('Vendor', 'purl', array('file' => 'purl/src/Purl/Url.php'));

    and us it in an action

    $url = new Purl\Url($this->request->data[‘Post’][‘url’]);
    debug($url->registerableDomain);

    and get this error

    Fatal Error

    Error: Class ‘Purl\AbstractPart’ not found
    File: C:\xampp\htdocs\mimosis\app\Vendor\purl\src\Purl\Url.php
    Line: 37

    Do you know what am i missing, Mark? Thank you.

  4. This is my code in bootstrap.php

    // Add 'vendors' as sub-package of 'Vendor' to the application
    App::build(array('Vendor/vendors' => array('%s' . 'vendors' . DS)), App::REGISTER);
    
    // Autoload the classes in the 'vendors'
    spl_autoload_register(function ($class) {
        foreach (App::path('Vendor/vendors') as $base) {
            $path = $base . str_replace('\\', DS, $class) . '.php';
            if (file_exists($path)) {
                include $path;
                return;
            }
        }
    });

    And in my Component:

    use PayPal\Rest\ApiContext;
    use PayPal\Auth\OAuthTokenCredential;

    use class in function:

    $this->apiContext = new ApiContext(
                new OAuthTokenCredential()
            );

    And that just worked for me!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.