Composer and Autoloading

Since we like Phalcon and composer, we're using both of them.

What I don't like is to have two different autoloaders so I wrote this part of code (which i'm not very proud of), to be able to use Phalcon autoloader instead of composer one :

$loader = new \Phalcon\Loader();
$namespaces = [
    // internal namespaces which could also be done proper using composer.json
];
// get namespaces from Composer
$map = require $config->application->vendorDir . 'composer/autoload_namespaces.php';

foreach ($map as $k => $values) {
    $k = trim($k, '\\');
    if (!isset($namespaces[$k])) {
        $dir = '/' . str_replace('\\', '/', $k) . '/';
        $namespaces[$k] = implode($dir . ';', $values) . $dir;
    }
}
$loader->registerNamespaces($namespaces);

$classMap = require $config->application->vendorDir . 'composer/autoload_classmap.php';
$loader->registerClasses($classMap);

$loader->register();

`

this code doesn't take care of PSR4 composer parts, but at this time it does the job.

What do you think of it ?

I've got one problem with it, Phalcon loader seems to lack the ability to have multiple dir for the same namespace which exists in composer. At this time i've got incubator & dev-tools in my projet, so one of the two projects is not treated in this autoloader. Is it me or does phalcon lacks this functionality ?

Duplicate from an issue posted on github (https://github.com/phalcon/incubator/issues/164), but I really don't know where it should be posted...

You just using composer one with proper psr-4 configuration in composer.json

edited Apr '14

@OlivierGarbé, I couldn't agree more. We need to look at what it would take the get this working.

What if we play with some of the config options in Composer, such as the Vendor-Dir. This way, everything Composer downloads goes into a folder that Phalcon already autoloads. Such as a composer.json as this (not tested, just thinking outload mostly)

{
    "require" : {
        "erusev/parsedown": "dev-master",
        "jeremykendall/password-validator": "dev-master"
    },

    "config" : {
        "vendor-dir" : "app/library"
    }
}

Maybe a simpler approach? I haven't played with it, but... looks promising.

@nexik : i don't want to use composer autoloader : if I use Phalcon is for it's performance and to take advantages of it's features. So using composer autoloading instead of phalcon one is completely feasable (and it works), but it's not my option.

@unisys12 : the Phalcon Loader don't work the same as composer one and each composer package has it's own subdirectory in which PSR-0 data are defined, so we can't "only" define a global autoloader directory. Moreover, composer has different autoloads types : PSR0 , PSR4, classes, requires ... so if we want to "change" this part we have to mimick all.



7.1k

When you install any plug-in from Composer, look closely at the files autoloadnamespaces.php and autoloadreal.php in vendor/composer/ folder. When you look at the detail http://docs.phalconphp.com/en/latest/reference/loader.html you will see, how to use those files. :)

HI @oleg578 !

I've looked onto these two files hence the code posted in my first post.

My problem is this one :

Phalcon loader seems to lack the ability to have multiple dir for the same namespace which exists in composer. At this time i've got incubator & dev-tools in my projet, so one of the two projects is not treated in this autoloader. Is it me or does phalcon lacks this functionality ?



7.1k

Phalcon, as well as Composer. fully PSR-0 compliant. You can use those files in Phalcon Loader, as wel as you use it in Composer. Look Namespace etc.

Well, taking a look at the forum repo, I found that in index.php of the public dir, all they did was add a require line to the Composer autoloader as well as one to the system loader.

/**
 * Include the loader
 */
require APP_PATH . "/app/config/loader.php";

/**
 * Include composer autoloader
 */
require APP_PATH . "/vendor/autoload.php";

Simple enough. You can take a look at what they are autloading in their composer.json.

As for not wanting to use the Composer autloader, because it might be slower, well... I wouldn't think you would take that big of a performance hit from it. That is unless you plan on making an app with as many dependicies as Laravel or Symfony.



7.1k

According to PSR, you can use namespaces in your Loader. This is work better and faster. You can use namespaces from your plugin from Composer repo. I already showed you how to do it. It has already been tested in the other projects. It's very simple.

$loader = new \Phalcon\Loader();

$loader->registerNamespaces([
    'YourNameSpace' => \dirname(__DIR__),
]);

That's all, which Phalcon Loader need.

Just add namespace from composer loader :

autoload_namespaces.php file example

<?php

// autoload_namespaces.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Phalcon' => array($vendorDir . '/phalcon/devtools/scripts'),
);

@Oleg : i've looked on those files before, and thats why i've done this part :

// get namespaces from Composer
$map = require $config->application->vendorDir . 'composer/autoload_namespaces.php';

foreach ($map as $k => $values) {
    $k = trim($k, '\\');
    if (!isset($namespaces[$k])) {
        $dir = '/' . str_replace('\\', '/', $k) . '/';
        $namespaces[$k] = implode($dir . ';', $values) . $dir;
    }
}
$loader->registerNamespaces($namespaces);

` because what you propose don't work : Phalcon loader want's an exact mapping between namespace and dir, whereas composer will add namespaces "as dir" after the dir

Well, I played around a bit tonight and found something sorta kinda interesting when considering Autoloading and Composer.

I had my app configurated to load my Composer dependicies in the following way:

$loader->registerNamespaces(
    array(
        'JeremyKendall\Password' => __DIR__ . '/../../vendor/jeremykendall/password-validator/src/JeremyKendall/Password/'
        )
)->register();

$loader->registerClasses(
    array(
        'ParseDown'   =>   __DIR__ . '/../../vendor/erusev/parsedown/Parsedown.php'
    )
)->register();
One is loaded through Namespaces, since it is properly namespaced and the other is not, so I used ClassMap.

This is a webgrind screen shot I took over the weekend, when comparing something else, with the above configuration. Phalcon_WebGrind

Today, I removed all this and placed the require statement in my index.php, as I stated above and got this result : Phalcon<em>WebGrind</em>Composer_Index

Notice the extra calls to Composer and the incresed time to execute? Notice how in the first image that there are no calls to Compser, of course, and Phalcon handles all the autoloading for all the files in one funtion call. A little more typing, but way more performent I think.

edited Apr '14

@unisys12 : that's my point and that's why i want to use a generic code with Phalcon loader using composer info.

Have you tried my code ? it should be between 1 and 2 in terme of time, but (from my point of view) the main advantage is that you really use composer info to autoload data.

little update on the loop as it wasn't working in all cases

foreach ($map as $k => $values) {
    $k = trim($k, '\\');
    if (!isset($namespaces[$k])) {
        $dir = '/' . str_replace('\\', '/', $k) . '/';
        $namespaces[$k] = implode($dir . ';', $values) . $dir;
    }
}

@Olivier, Yeah I tried it out this morning and it is not loading everything. When troubleshooting the issue, I noticed that Composer is placing Parsedown in the namespaces array, which it shouldn't because it is not namespaced at all. Weird, but when I require the composer autoloader in my index or call it the way I was originally (as stated above, manually entering each file or namespace) everything works. Also, even though the password validator library is properly namespaced, it is only loading the core file and no others. This causes methods that are further down in the library unreachable. I compared my current autoloader to the one in my Laravel version of this same project and everything is where it is suppose to be, weird issue. Yes, I performed a 'dump-autoload' and even a 'self-update'.

I will play with it more tonight, when I get home from work. I think you are on to something, it just needs a little more flushing out. If you can come up with a more performant way to utilize Composer, that would be great. Flush out the idea in PHP, rewrite it in Zephir and see if you can get it worked into the core. That would be awsome. I will try to help anyway I can.

edited Apr '14

it's due to an error in the parsedown composer.json :

https://github.com/erusev/parsedown/blob/master/composer.json#L16

it should be:

"autoload": { "classmap": ["Parsedown.php"] }

you should ask the author to fix

Edit : in fact not an error, but an authorized way from composer : https://getcomposer.org/doc/04-schema.md#psr-0

Hey @ogarbe! Just wanted to share a little something with you. I was playing around with Composer some more, at the suggestion of Max Surguy, and realized that I was not using an optimized autoload.php in my example above (about using require /../../vendor/autoload.php inside index.php). After running the command composer dump-autoload --optimize, I still had all the extra function calls from Composer, but found that it made little to no different in the total execution time of page loads. Were as before, it was adding 30+ ms to the page.

Was just wondering if you were running this command? If so, then we really need to take your code and flush it out.

I'm using registerFiles() with Phalcon Loader component to include Composer autoloader:

//public registerDirs (array $directories, [mixed $merge])
//Register directories in which “not found” classes could be found

//Include composer autoloader - Phalcon way
$loader->registerFiles([APP_PATH . '/vendor/autoload.php']);

//composer default
//require APP_PATH . '/vendor/autoload.php';
edited Feb '17

With the help of this thread i came to the following working solution.

Important notice though, it only works with PHP 5.5 or greater, since you need to run at least Phalcon 3.0.x. Include/require would do the job aswell.

<?php
$loader = new \Phalcon\Loader();

/**
 * Register directories for the Phalcon autoloader
 */
$loader->registerDirs(
    [
        $config->application->controllersDir,
        $config->application->modelsDir
    ]
);

/**
 * Register Files, composer autoloader
 */
$loader->registerFiles(
    [
        APP_PATH . '/vendor/autoload.php'
    ]
);

/**
 * Register Autoloader
 */
$loader->register();

@Meint-Willem IMHO, PHP 5.5 should not be used anyway as it is marked as deprecated. More important to note with this approach that it'll work only with Phalcon 3.0.x and not 2.0.x. as registerFiles() Loader method exist only in 3.0.x series.

edited Feb '17

@Meint-Willem IMHO, PHP 5.5 should not be used anyway as it is marked as deprecated. More important to note with this approach that it'll work only with Phalcon 3.0.x and not 2.0.x. as registerFiles() Loader method exist only in 3.0.x series.

I'm using PHP7 myself but you need PHP >= 5.5 to run Phalcon 3.0.x yeah, i'll add it to the above response, cheers



139
edited 26d ago

Thanks it worked for me after a little update:

/** * Register Files, composer autoloader */ $loader->registerFiles([$config->application->vendorDir . 'autoload.php']);

/** * Register Autoloader */ $loader->register();