Composer, autoloading & class calling after autoloading - php

I was wondering, I am currently trying to change my programming style to work with composer and its package system (my god why didn't I use it sooner?) but I was wondering I am trying to work following the PSR-4 standard.
And got the following php file
<?php
require_once(__DIR__ . '/vendor/autoload.php');
$class = new vendorname\packagename\classname;
$example = new vendorname\packagename\subpackage\classname2;
Is that good practice to use with composer and the PSR-4 standard?
Where the the classes are existing in:
- /vendor
-- /vendorname
--- /packagename
--- /src
---- classname.php
---- /subpackage
----- classname2.php
I am currently thinking it is, but I just want to make sure that I am using it correctly according to the PSR-4 standard :-).

Typically that is the way to go, but some packages use different namespaces (mainly packages that need to have legacy support, for as far as I have encountered). Therefore after you composer require the package and it's installed, you should check the files for the namespaces that are used. But yes, you are right, because the way you say it is how it's typically done.
Also usually the package's readme/website has some examples on how to construct their objects.
Example: The Monolog logger package has a file /vendor/monolog/monolog/src/Monolog/Logger.php that is in the Monolog namespace, not in the Monolog\Monolog\Src\Monolog namespace. Just be sure to check it, but most of the times examples on the package maintainer/owner's website will tell you how to use the package. In this example the readme on Github tells you how the package is used.

Most of packages include readme referring it's namespace. you can access classes using that namespace. if you can't found you can check from 'your_project_root/vendor/vendor_name/package_name/composer.json'.
"autoload": {
"psr-4": {"Monolog\\": "src/Monolog"}
},
For this example 'Monolog' is the namespace and this is the best way to follow psr standards.
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// create a log channel
$log = new Logger('name');
$log->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));
// add records to the log
$log->warning('Foo');
$log->error('Bar');
You can see all this standards in https://www.php-fig.org/psr/
Additionally you can check how this packages loaded in 'autoload_psr4.php' located in
'your_project_root/vendor/composer/autoload_psr4.php'
when you run composer require, composer update or composer dump-autoload command. this file will updated.

Related

Autoload files for PHP Package - get_declared_classes() not returning expected results

I have a package I am creating here
It's a standard composer PHP package with a Symfony command to generate Avro files.
When registering the package command in the bin/avro directory I add the following
require file_exists(__DIR__ . '/../vendor/autoload.php')
? __DIR__ . '/../vendor/autoload.php'
: __DIR__ . '/../../../../vendor/autoload.php';
This should, if my understanding is correct, autoload all files for a project where the package is loaded in.
I run the following in an empty Laravel project for example
composer require lukecurtis93/avrogenerate
./vendor/bin/avro generate
The code here which uses get_declared_classes() - excerpt:
// ...
$arr = [];
foreach (get_declared_classes() as $className) {
if (in_array(Avroable::class, class_implements($className))) {
$arr[] = $className;
}
}
// ...
- does not return any files stored in my App/Models directory for example which are in the Laravel App (or any others for that matter).
What am I doing incorrectly? Shouldn't these files be available from get_declared_classes()? Is there additional configuration I need to do for a package?
This should, if my understanding is correct, autoload all files for a project where the package is loaded in.
You have misunderstood what "autoload" means in this context. It doesn't mean "automatically find and load all classes at once"; it means "automatically load classes on demand".
There is a description of how this works in the official manual. The short version is that whenever you try to use a class that isn't defined yet, PHP passes the class name to a callback; that callback can do whatever it likes to define that class, normally loading a file based on some naming convention.
So, until you've tried to use a class, it will remain undefined, and get_declared_classes will not know about it.
The class_exists function will, by default, trigger the autoloader, which might be helpful to you. If you really need to dynamically list all the classes in a package, you'll need to find some other way of iterating the files on disk.
Is there additional configuration I need to do for a package?
IMSoP already explained the details for PHP autoload when you want to use get_declared_classes().
An alternative to that is to re-use the Composer classmap, however it requires to generate it, the --optimize argument of composer dump-autoload does this. Given the Composer Autloader has been dumped that way, the list of "declared" classes is quite easy to obtain:
$autoload = require file_exists(__DIR__ . '/../vendor/autoload.php')
? __DIR__ . '/../vendor/autoload.php'
: __DIR__ . '/../../../../vendor/autoload.php';
$classmap = $autoload->getClassMap();
Then $classmap is an associative array with the class-names as key and the (absolute) path (w/ relative segments) to the file.
This is with the downside that the autoloader must be dumped with a specific configuration. And with the benefit that you'd be ready in no time.
Better Reflection
The package roave/better-reflection provides a class-listing for composer.json and //vendor-dir/composer/installed.json without relying on the class-map of the Composer autoloader.
You could add it as a requirement and then it would be available with the autoloader.
Getting the list of classes is within their documentation1, it depends on the path to the project. You could make it an argument option for your utility as I don't know how to obtain the root project path from the autoloader instance, the following would assume the default vendor-dir configuration, YMMV:
$projectAutoload = file_exists(__DIR__ . '/../vendor/autoload.php')
? __DIR__ . '/../vendor/autoload.php'
: __DIR__ . '/../../../../vendor/autoload.php';
$autload = require $projectAutoload;
$projectRoot = dirname($projectAutload, 2);
It does not implement 100% of the Composer autoloader configuration, but I've not seen another component that does apart from Composer itself. Just FYI when you wonder in integration testing, it is in the 99.5% camp, you may not even miss anything at all in your use-case.
$astLocator = (new BetterReflection())->astLocator();
$reflector = new DefaultReflector(new AggregateSourceLocator([
(new MakeLocatorForComposerJsonAndInstalledJson)($projectRoot, $astLocator),
new PhpInternalSourceLocator($astLocator, new ReflectionSourceStubber())
]));
$classes = $reflector->reflectAllClasses();
$classNames = array_map(fn (ReflectionClass $rfl) => $rfl->getName(), $classes);
Better Reflection also is not the fastest horse in the stable. Have decent CPU and fast disk I/O. The benefit of it are its features and in your specific case you can use it for preview (get_declared_classes() stays empty, no autoloading involved, you may use the gathered information to do your inheritance checks without loading the files into PHP - I can imagine this is probably good to have for your utility).
Write it yourself
It is relatively easy to write an ad-hoc loading of all PHP files in the vendor folder so that get_declared_classes() has them afterwards. But with such a direct implementation you can easily run into fatal errors, which don't help with your cause, also if you want to adhere to the composer packages, you need to give it some love. That depends also on which packages (and package style) you want to support. I've not much about a clue of your project, so in case you may still want to try it for yourself, some pointers:
The Composer autoloader configuration is in their schema, additional information is available in the optimizing section for auto-loading.
Remember that a call of require or include returns the autoloader instance. The class it not #internal, which means the public interface is stable to use.
The other thing good to know is that in //vendor-dir/composer/installed.json you find a list of all installed packages and some details about the dev install.
The format/layout of the installed.json depends on the composer version, good to know if you want to support all Composer versions.
I'm not aware there is a composer configuration matrix / change-log over all versions. Make your own research.
There are four kind of autoloaders in Composer: psr-0, psr-4, classmap and files (this is documented).
File extensions are *.php and *.inc (this is documented).
classmap can have excludes, with * and ** globs, the later implicitly appended if no globs in the exclude option argument (this is documented).
Composer also contains the class scanner for class map creation. IIRC it is internal but looked relatively easy to extract as it is merely ~60 lines of straight forward code, most of it regex operations. The regexes and processing around looked quite battle-proven the times I checked. You may need this if you want to come close to --optimize / class-map generation as Composer does.
Expect to stumble over Symfony or Phpunit files occasionally.
Compare "Inspecting code and dependencies of a composer-based project"

How to use one module in two different application/project in yii2

I have module created in the basic project of yii2 and now i want to access or use that module another project/application of mine....
How can I achieve this.
please help me out here.
To use module in different apps there are 3 things you need.
The module must not be dependent on classes from core project. For any class that needs to be implemented by core project the module should define interface and depend on that interface instead of class itself.
The module should use different namespace than app and autoloader must know how to load classes from that namespace. (more about that later)
You have to add module in your config in same way you've added it in first project.
The points 1 and 3 are pretty much self-explaining. If are not sure how to add module in config see the yii2 guide.
Now back to the second point. While naive way of copying module over to second project would work it will turn maintaining the module into nightmare because each change would have to be done in each copy of module. So it's better to keep the code of module in one place and make it available for each project. There are multiple ways of doing that.
If you want to, you can turn your module into extension and make it publicly available through packagist as it was suggested by M. Eriksson in comments. After that you would simply add your extension through composer as any other dependency.
Composer also allows you to define and use private repositories if you don't want to publish your module at packagist. See composer documentation for more details.
The most trivial way is to simply put the code into separate folder outside of project. If you do that, you have to make sure that autoloaders in your projects are capable of finding the files locations to load classes. There are two options how to do that. In any case you will want to avoid conflicts with namespaces used by your project, that's why you need to use different namespace.
Let's assume that you've put your module files into folder /path/to/modules/myModule and all classes in your module belongs to namespace modules\myModule. You have to make sure that your webserver can access that folder and that it can run php scripts there.
First option is to use Yii's autoloader. That autoloader uses aliases to look for classes. If you add #modules alias and point it to /path/to/modules folder, the Yii autoloader will try to look for any class from modules\* namespace in /path/to/modules folder. You can add the alias in your config file (web.php, console.php or any other config file you use):
return [
// ...
'aliases' => [
'#modules' => '/path/to/modules',
// ... other aliases ...
],
];
The second option is to use project's composer.json file to set autoloader generated by composer to load your classes.
{
"autoload": {
"psr-4": {
"modules\\": "/path/to/modules"
}
}
}
You can find more info about this in composer's documentation.
Don't forget to run composer dump-autoload after you change autoload settings in your composer.json file to update the generated autoloader.

Reference .phar classes installed globally with composer

I've created a small command line tool in PHP and I've also packed that as a PHAR archive.
Next thing I did was publish my archive to packagist.org aka composer.
I can now install my PHAR package through composer like so:
composer global require acme/mypackage
This installs my package fine. And I'm able to run it through command as well.
So far so good, but here comes the problem I´m currently facing.
I have another project should use acme/mypackage. I want that project to reference a class that is packed into that PHAR. Something like this:
<?php
class SomeClass extends AcmeClass {
}
The problem is that the PHP code doesn't recognize the AcmeClass class. Makes sense, because it´s obviously "globally" installed somewhere on the system.
How do other libraries solve this issue? If I'm not mistaken then PHPUnit does something similar right?
How can I solve this issue?
You'll need to add a composer.json file to the root of your project:
The first (and often only) thing you specify in composer.json is the require key. You're simply telling Composer which packages your project depends on.
{
"require": {
"monolog/monolog": "1.0.*"
}
}
Next, you'll need to autoload your dependencies.
For libraries that specify autoload information, Composer generates a vendor/autoload.php file. You can simply include this file and you will get autoloading for free.
require 'vendor/autoload.php';
https://getcomposer.org/doc/01-basic-usage.md

PHP Zend library results in Fatal Error: class 'Zend\[whatever]' not found

I'm hoping someone can spot what I've forgotten to do. Here are my steps:
Downloaded and unpacked the ZendFramework-2.3.5 into /usr/share.
Updated include_path in my php.ini file to include '/usr/share/ZendFramework-2.3.5/library' per the INSTALL.md, and restarted Apache to confirm the path is set (now ".:/usr/share/php:/usr/share/ZendFramework-2.3.5/library").
Created a test script in my web document root (using the class 'CamelCaseToUnderscore' as an example):
use Zend\Filter\Word\CamelCaseToUnderscore;
$filter = new CamelCaseToUnderscore();
echo $filter->filter('BigsAndLittles');
...and I get the fatal error "class 'zend\filter\word\camelcasetoseparator' not found".
In order to do use Zend classes like this, do I need to do some additional configuration or create an autoloader or something to find them? Seems like this should have worked. If I include the CamelCaseToUnderscore.php file in a require_once statement, then I get a fatal error that it's parent class doesn't exist (CamelCaseToSeparator.php). What am I missing?
You can use require 'Zend/Mvc/Application.php' to test if your include path is correct, but you will need an autoloader:
http://framework.zend.com/manual/current/en/modules/zend.loader.standard-autoloader.html.
You can find an example here (lines 18-20):
https://github.com/zendframework/zf2/blob/master/demos/Zend/Feeds/consume-feed.php
I strongly suggest using composer as it will save you a lot of time troubleshooting your include paths, but it also allows you manage version better. It makes it easier for other developers and to deploy your code.
Starting with composer is very easy, just install it and create composer.json:
https://getcomposer.org/doc/01-basic-usage.md#composer-json-project-setup
Run:
composer require zendframework/zendframework
Composer will download all libraries to vendor folder and will generate an autoloader, all you have to do is to include
require 'vendor/autoload.php';
https://getcomposer.org/doc/01-basic-usage.md#autoloading
Most popular PHP frameworks use composer for managing dependencies:
https://github.com/zendframework/zf2/blob/master/composer.json
https://github.com/symfony/symfony/blob/2.7/composer.json

Own project via composer

I've got some libraries loaded through composer, and I'm wondering if it's possible to add my own library in the /vendor map, and then to have the composer autoloader load it? The structure would be something like /vendor/mylibrary/ and then a namespace mylibrary.
Would this be possible? Also would it be possible to add a different map to the composer autoloader? Like for example /app/src/ and then to have it load all the classes in that folder? Or do I have to make my own loader for that?
Thanks
Reading the composer documentation:
You can even add your own code to the autoloader by adding an autoload field to composer.json.
{
"autoload": {
"psr-0": {"Acme": "src/"}
}
}
Composer will register a PSR-0 autoloader for the Acme namespace.
You define a mapping from namespaces to directories. The src directory would be in your project root, on the same level as vendor directory is. An example filename would be src/Acme/Foo.php containing an Acme\Foo class.
After adding the autoload field, you have to re-run install to
re-generate the vendor/autoload.php file.
So basically, you just need to follow PSR-0, and tell composer where to find your library, by adding that line to your composer.json
Yes.You can achieve it. Configure your composer.json file as following:
{
"autoload": {
"classmap": [ "classes" ]
}
Here classes is the name of the directory where you have all your application related classes.Vendor related class should be auto detected as well. Just add the following line to achieve both at the same time:
require 'vendor/autoload.php';
And you can use the namesapce to reference your class like the following:
use classes\Model\Article;
Yes, of course it is possible to add own libraries and you should feel highly encouraged to do so. If your library is available publicly, you can simply register it at packagist.org. If not, it's a bit more complicated, but not impossible.
If your project does not follow the PSR-0 standard, composer will create a classmap for you. A custom autoloader is not supported.
I'd recommend you to read the (really excellent) documentation about all this and come back, if you're running into problems.
http://getcomposer.org/doc/

Categories