RSS
 
06. Aug. 2012

Namespaces in vendor files and Cake2.x

06 Aug

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.

2.33 avg. rating (57% score) - 3 votes
 
7 Comments

Posted by Mark in CakePHP

 

Tags: , , , ,

Leave a Reply

Tip:
If you need to post a piece of code use {code type=php}...{/code}.
Allowed types are "php", "mysql", "html", "js", "css".

Please do not escape your post (leave all ", <, > and & as they are!). If you have encoded characters and need to reverse ("decode") it, you can do that here!
 

 
  1. Carlos Gant

    June 11, 2013 at 16:09

    You should add a return after the include in the autoload function

     
  2. Mark

    August 6, 2013 at 16:51

    Good point. Corrected it 🙂

     
  3. func0der

    January 25, 2014 at 14:32

    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

     
  4. func0der

    January 26, 2014 at 14:35

    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,
    			),
    		)
    	);
     
  5. func0der

    January 26, 2014 at 14:37

    Any better solutions?

     
  6. Frans

    March 19, 2014 at 16:13

    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-&gt;request-&gt;data[‘Post’][‘url’]);
    debug($url-&gt;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.

     
  7. kble

    March 1, 2017 at 08:06

    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!