On Composer's autoload optimization page:
Note: You should not enable any of these optimizations in development as they all will cause various problems when adding/removing classes. The performance gains are not worth the trouble in a development setting.
I can definitely see problems for the level 2 optimizations (authoritative class map) on a development environment, but I can't determine what the problems are for level 1 optimizations (class map generation) if I follow the PSR-4 standard.
If I add a class that didn't get generated in the class map, it will fall back to PSR-4 rules to look for the class.
If I refactor (move) a class to a different namespace, it will also not find it in the class map and attempt to resolve it using PSR-4 rules.
What are the potential issues with the generated class map on dev environment with a project that complies with PSR-4?
Level 1 optimizations may create problems if you will move class to different directory without changing namespace. Since there may be multiple ways to resolve single namespace, such changes will be properly handled by Composer, but may fail when you have a outdated classmap.
Example:
"autoload": {
"psr-4": {
"app\\": "src",
"app\\db\\": "src/drafts/db"
}
},
Class app\db\Entity may be placed in src/drafts/db/Entity.php or src/db/Entity.php and in this order Composer will search for class file. Normally if you move a file from src/drafts/db to src/db Composer will finally find this class. But if you have outdated classmap, Composer will blindly include non-existing file and you will get a fatal error.
In addition to this apcu-autoloader option will also cache misses. So if you request a non-existent app/db/NewEntity class, and then add this class, Composer will not detect this change since it has cached info that this class does not exist.
These are generally edge cases, usually you will never notice such nuances. But this is still possible and unnoticeable performance boost on development environment is not worth the risk of losing a few hours on debugging cache-related issues of Composer autoloader.
Related
Is it possible to create a package for a class without a namespace, and instantiate it in my scripts without using the language construct use?
eg:
<?php
require_once 'vendor/autoload.php';
// use \Something_MyClass; (I don't want to use this 'use')
$mc = new MyClass(); // instead of $mc = new Something\MyClass()
?>
Copy of my comment below, just to clarify:
"This is because I am still using php 5.2. For legacy reasons I can't upgrade php to 5.3 or beyond. I can't use namespacing, but I want to use composer for autoloading and dependency manageament."
It's not possible.
Even if you register with spl_autoload_register a function that, upon new MyClass(), will include a file with class Something_MyClass, that same line will fail, because you're trying to instantiate a class that doesn't exist.
Your best course of action is to upgrade your PHP to 5.3 (although the lowest supported version as of now is 7.1, and I highly recommend going there). You could, of course, create your own package manager (as YvesLeBorg suggested in comments), but that will get you even deeper into legacy, and make it even harder to maintain and upgrade PHP in the future, not to mention potential bugs and extra maintenance overhead.
You can perfectly use PHP 5.2 syntax in your source code and combine it with Composer, even with Composer PSR-0 autoloading. Composer does not enforce any rules regarding the PHP version.
The only thing that you cannot work around is that Composer itself requires PHP 5.3.2 to run. But everything else is up to you.
In more details, your only option for "namespaced classes" with PHP 5.2 is to use the now dreaded Under_Score_Class_Name variant, which can be autoloaded with PSR-0. You'll always have to use the full classname (use and proper namespaces would allow you to use shorter names). That example classname must reside in the path /Under/Score/Class/Name.php and would be autoloaded with "autoload": {"psr-0": {"Under": "/"}}. You have no way to shorten that path with PSR-0 (one of the reasons PSR-4 was invented), it must always contain every word between underscores as a folder level, starting with the first word. The only thing you can change is that Composer does not require you to use a folder in the main directory of the library. You could use the path src/Under/Score/Class/Name.php and autoload with "psr-0": {"Under_": "src/"}.
Running Composer with a PHP version 5.3.2 or greater probably boils down to a) installing it and then b) explicitly point to this version when running Composer, like /usr/local/bin/php7.2.23/php composer.phar install.
In case you don't want to use long paths, simply using class names in the root namespace is allowed - note however that they must not duplicate existing class names from PHP itself. Without a common class name prefix like Under_ from the example above, your autoloading could be "psr-0": {"": "src/"}. Note however that this tells the Composer autoloader that every class may be found in your library. Composer will keep track of where it found something and not try again anywhere else, but it will still affect performance. So it is best to go with the common prefix.
I am currently working as a student on php project which has grown since the beginning of time and has about 1800 php-files.
The problem is: it is completely without namespaces, or any of the PSR-4, etc. recommendations. The technical debt is strong with this one :).
We want to use composer (and twig and some libraries more) and having problems including this (especially composer). I think it's because of the overwrite of __autoload() via spl_autoload_register() in the composer-autoloader?
Is there a good and fast way to start integrating namespaces without rewriting the whole project?
You can still use Composer with PSR-0 or classmap.
I'd probably go with the classmap first. Advantages: Can deal with multiple classes per file. Can deal with arbitrary file structures.
Once you achieved using Composer's autoloading, you can start removing either the existing autoloader or those require_once/include_once that are likely spread all over the place.
Once you've got rid of all that file loading legacy and have established Composer autoloading, you can try to organize the code according to PSR-0. This will probably require you to rename files and reposition them. It might also be the case that the classes don't have any identifiable prefixes - which is bad for your PSR-0 autoloading because all these files would belong into one single folder.
Note that until this point you haven't changed the name of any class, so the code should run without any changes.
Using namespaces does not have a very noticeable advantage. You'd be forced to rename all classes, rename all usages of that class name, and all in all it won't provide any noticeable benefit if used alone.
On the other hand, you sound like you do want to refactor everything else as well, so switching to namespaces can be used as a signal for "newer" code.
You can use PSR-4 and PSR-0 at the same time, so it won't affect your renaming of classes (besides the necessary class name changes in all the places).
Given the following directories at the root of a project:
app/, A combination of static resources and class-mapped project code
src/, Configured to load namespaced PHP code
vendor/, Packages managed by composer.json
Regardless of what framework I happen to be using, would it be correct to make use of the src directory for portable code intended to be usable outside of the project, or is that the role of the vendor directory?
Are there any issues that might arise as a result of using the src directory for code intended to be shared?
This seems like a reasonable design approach. Composer lets you do this by putting your own classes to be autoloaded into a directory and then referring to it in your composer.json . See this question for how: Using Composer's Autoload
However, this won't handle versioning of your code under src/ . If you are going to be separately publishing this, it might be worth learning how to make your library installable through Composer and once you have checked it in to a Git repo somewhere, reference it like this:
"require": {
"me/testlib": "1.0.*"
}
"repositories": [
{
"type": "vcs",
"url": "https://github.com/username/hello-world"
}
]
Sure, then you'll be maintanining multiple source trees, one for your project and one for each library, but that's a good thing right? ("Separation of concerns".)
Your approach looks like a valid one in the general case.
It will however become invalid if the amount of data app or anywhere else outweighs the amount of usable code in src. In other words: If you only host about one shared class in src, and one gigabyte of assets in app that isn't needed for the class, it would be stupid to make this a library.
But in such situation, it would be easy to simply move that class into a composer library on it's own and again include it. Because of autoloading the class will still be there (although the file changed it's location) and be usable in other code. Any any other software requiring the whole bunch of assets will continue to work even if that class is moved.
When moving code into libraries, it might be worth considering that smaller usually is better, but too small is worthless. You don't want to require every single class on it's own - but a reasonable collection of classes with the same topic make an excellent candidate.
In composer documentation section about autoloading I found at least two means to load classes that not corresponds to psr-0/4. First one is to specify classmap property of composer.json file and second one is fill include-path property in my composer.json.
As I can see include-path is more plain feature while classmap caused scanning for classes in specified locations. Who can explain which one should I use in different cases?
Avoid using include-path. It is meant for old software that expects the include_path setting to contain some directories, so that require_once "Relative/Path/To/Class.php" works from ANY location (think of the way PEAR works). Using too many paths impacts performance, because PHP needs to scan starting from the first directory, until it finds the relative path requested.
Classmaps are an always working solution if the classes do not conform to PSR-0 or PSR-4. Autoloading works by knowing the name of the class and finding out the file this class is contained in. PSR-0/4 define a way to know the file name by using and splitting the class name. Classmaps however know every class name and their file name directly. The bad thing about classmaps is that if they grow too large, they also affect performance because loading a huge classmap and then only using about 1% of the contained classes has a big overhead.
include-path and classmap are not mutually exclusive. In fact, they might be both needed: To load the first class, you'd need the classmap (otherwise you'd be forced to explicitly use require_once), and if that file will load dependencies using relative paths inside require_once (and does not know about autoloading), a proper include path has to be set.
If you ever have the chance to change it, I highly recommend to avoid setting the include path, and only use the classmap feature to autoload classes (which means you should remove any include/require functions in that code base). It would be even better if your code can be transformed to be PSR-0 compliant, but this usually is a huge rewriting task for old code with barely any benefit. You'd have to worry about performance only if you really have a HUGE framework with many files and classes - which usually is not the case with older code bases.
I have created a handler class that derives from AbstractProcessingHandler. I've seen that I can put it in src/MyNamespace/MyBundle/Monolog/, but it worries me a bit because this handler is used in several others bundles where I log data. So the other bundles will need MyBundle to work properly, only because of this handler.
I tried to put my handler class in lib/ but it does not seem to work (maybe I have to do something special with Autoload?).
Or should I create a new bundle specifically for this handler?
Edit: I can't really place my custom handler class in vendor/monolog/monolog/src/Monolog/Handler because then I would not be able to add it to my git repository: there is a conflict because this folder is managed by another git repository (created by Composer)
On Monolog's end there is really no restriction on where to put it or how you call it. The key is only that it implements monolog's HandlerInterface or extends from one of the existing handlers.
Now it depends what your handler is, if it's generic stuff that other people could use you could submit it as a pull request to monolog.
If not, you can either create an own composer package for it, or put it in src/Acme/Monolog/FooHandler or something like that, so it stays in your application but is clearly out of a bundle. The downside is that you need to configure it as a service in one of your bundles, so you still have some sort of dependency on a bundle there.
Maybe having it as its own bundle would make sense then. But it's quite a lot of boilerplate for just one class.
If all your bundles are application specific and very unlikely to be extracted out of it, having cross-bundles dependencies is fine though IMO.
The dependency is anyway not very strong since one bundle could contain the handler and configure it. The other bundles can still log to monolog, even if the handler isn't present, they can log. It just won't go to that specific handler. Nothing should break.
As you see, it's just a lot of trade-offs, and it's hard to say which solution is the most fitting without knowing more about your project.
If you want to have your handler class in lib/ you will need to add the lib/ folder to your composer.json autoload section. For example:
"autoload": {
"psr-0": { "": ["src/", "lib/"] }
}
Take a look at the Composer documentation:
Basic Usage
Autoload
I think the common approach here is to use a "Bridge" dir in your Bundle with a clear dependency. If you have other bundles that rely on this, what we've done is create a ServiceBundle which is basically for all shared services across all bundles within the application. This might not work well for you if you have plans of distributing this bundle, but may otherwise.