How to use composer psr-4 fallback correctly - php

I have one directory that is going to keep all "helper" classes and functions. Let's call the directory helpers.
I want to configure PSR-4 fallback directory to point to this helpers directory:
"autoload": {
"psr-4": {
"": "helpers/"
}
}
From Composer documentation:
... fallback directory where any namespace will be looked for.
So my understanding is that if my files/classes in that directory have PSR-4 compliant names my application should be able to find them there.
Now I created a file helpers/Logger.php with class Logger
What namespace should be for this class in order to 1) comply with PSR-4 and 2) just work?
I have tried
namespace Logger;
And load class as
$logger = new Logger();
But I receive error Class Logger not found
Deeper dive into composer code (loadClass() method) showed me that it actually finds and includes the file helpers/Logger.php, but the class still cannot be found for some reason.
According to PSR-4 namespace should be something like this:
namespace Helpers;
And class should be called like this:
$logger = new Helpers\Logger();
I have Class Helpers\Logger not found, but in addition the file helpers/Logger.php is not even included.
The logic inside Composer's loadClass() method for fallback is following:
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
........
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
So it actually tries to match the file name against the fully qualified class name.
So it seems that I am missing something in my understanding of PSR-4.
But what exactly?
Edit
To check it all I run a simple file from project root, all other libraries that are configured via composer are loaded correctly (for example, Pimple is working fine):
<?php
require_once __DIR__ . '/vendor/autoload.php';
$app = new \Pimple\Container();
/** Register logger */
$app['logger'] = function ($c) {
return new Helpers\Logger();
};
$app['logger']->info('test');

This fallback works as a definition of directory for global namespace. So yes, it may be used for autoloading any class in any namespace, but you still need to follow PSR-4 rules for them. Directory structure should represent namespaces structure. If you have rules like:
"autoload": {
"psr-4": {
"": "helpers/"
}
}
your Helpers\Logger class should be in helpers/Helpers/Logger.php, because in this way Composer will resolve class file path from autoloading rules.
helpers/Helpers/Logger.php
^ ^ ^
| | |
| | Class name part
| |
| Namespace part
|
Static prefix for global namespace

PSR4 is working case-sensitive, so if you place a class in the folder helpers and the class itself uses the namespace Helpers, that won't work.

Related

How to make sure the correct class file is loaded when calling / extending class

I did brows through that question page but the answers given, I felt, didn't sufficiently answer the question I was struggling with.
I have a little php project. In this project I define two classes both in their own files. The second class extends the first class. In the app.php file I instantiate the second class and call a method.
├── Models/
│ ├── Class1.php
│ └── Class2.php
└── app.php
<?php
// Class1.php
namespace Models\Class1;
/**
* Class1 does things
*/
class Class1 {
public function someMethod() {
// code
}
}
<?php
// Class2.php
namespace Models\Class2;
use \Models\Class1\Class1;
include('./Class1.php');
/**
* Class2 does other things
*/
class Class2 extends Class1 {
public function someMethod() {
// code
}
}
<?php
// app.php
use Models\Class1\Class1;
use Models\Class2\Class2;
require('Models/Class1.php');
require('Models/Class2.php');
$c1 = new Class1();
$c2 = new Class2();
Now I'm not a really experienced php programmer but I thought I had a solid grasp of this class/namespace business and including/requiring files, but apparently I don't.
If I copy the methods I need from Class1 to Class2 and not extend/include/use Class2 everything works fine, but want to extend Class1, so I don't have to repeat myself! Damn it!
This is what I get when running the app.php file.
Warning: include(../Models/Class1.php): failed to open stream: No such file or directory in C:\wamp64\www\project\Models\Class2.php on line 8
Warning: include(): Failed opening '../Models/Class1.php' for inclusion (include_path='.;C:\php\pear') in C:\wamp64\www\project\Models\Class2.php on line 7
I've been reading all I can find online about this stuff, have written and rewritten my code in a dozen different ways but have yet to find a solution. Some help would be greatly appreciated!
Quick Answer
Unless you've specified the full path to the include file, PHP will always try to resolve files according to the location of the entry script. In your case, your entry script seems to be app.php at the root of the application.
So in your Class2.php, instead of include('./Class1.php');, you should write include('Models/Class1.php'); and things should work. But while it fix a short term problem, your code would be really non-portable. Besides, including the same file twice would result in another error (e.g. re-declaring class).
Slightly Smarter Approach
A slightly smarter would be to do this in Class2.php instead.
<?php
// Class2.php
namespace Models\Class2;
use \Models\Class1\Class1;
include_once __DIR__ . '/Class2.php';
/**
* Class2 does other things
*/
class Class2 extends Class1 {
public function someMethod() {
// code
}
}
The variable __DIR__ will always be resolved to the directory of the script file, not the entry script.
But again, it is clumsy and error prone to do file includes by hand.
An Even Smarter Approach: Autoload
PHP supports file autoloading when a class is declare. That is to have a piece of code to do the file include when a class is called. If you want to have fun, you're welcome to write your own autoloader function and register to spl_autoload_register. With a combination of the __DIR__ technique we talked about, you can easily resolve the autoloading path from namespace.
A quick and ugly autoloading app.php would probably look like this:
<?php
// app.php
use Models\Class1\Class1;
use Models\Class2\Class2;
spl_autoload_register(function ($class_name) {
$realClassName = basename(str_replace('\\', DIRECTORY_SEPARATOR, $class_name));
include_once __DIR__ . DIRECTORY_SEPARATOR . 'Models' . DIRECTORY_SEPARATOR . $realClassName . '.php';
});
$c1 = new Class1();
$c2 = new Class2();
The Smart Approach: Composer Autoload
If you're learning OOP PHP today, I'd highly recommend you to learn Composer and PSR-4. PSR-4 defines how you should structure a PHP application for class autoloading. Composer implements a PSR-4 autoloader by default.
First you should comply with the namespace standard. The least change to do is to loose the extra "Class1" and "Class2" in your namespace:
<?php
// Class1.php
namespace Models;
/**
* Class1 does things
*/
class Class1 {
public function someMethod() {
// code
}
}
<?php
// Class2.php
namespace Models;
use \Models\Class1;
/**
* Class2 does other things
*/
class Class2 extends Class1 {
public function someMethod() {
// code
}
}
With a nicely structured application folder, namespace structure and a correctly written autoload.psr-4 section in composer.json, composer will help you to generate a class autoloader. Your composer.json would probably look like this:
{
"autoload": {
"psr-4": {
"Model\\": "Model/"
}
}
}
You may now run composer dump-autoload to create the autoloader. Then you can simply add this to your entry script app.php. When things are ready, you may simply add use and new statement anywhere in the application.:
<?php
// app.php
use Models\Class1;
use Models\Class2;
require_once './vendor/autoload.php';
$c1 = new Class1();
$c2 = new Class2();
All includes are handled by the autoloader. Hands free.
Remove the line
include('./Class1.php');

Can't get my autoloader to work with namespaces

I'm having an issue with my folder structure in combination with my PHP files and their namespaces.
Here's my folder structure:
myproject
|
-- entry.php
-- src
|
-- IndexController.php
My entry.php looks like this:
<?php
spl_autoload_register(function (String $class) {
$sourcePath = __DIR__ . DIRECTORY_SEPARATOR . 'src';
$replaceRootPath = str_replace('myproject\Controllers', $sourcePath, $class);
$replaceDirectorySeparator = str_replace('\\', DIRECTORY_SEPARATOR, $replaceRootPath);
$filePath = $replaceDirectorySeparator . '.php';
if (file_exists($filePath)) {
require($filePath);
}
});
$indexController = new myproject\Controllers\IndexController;
$result = $indexController->controlSomething('this thing');
print($result);
My src/IndexController.php looks like this:
<?php
namespace myproject\Controllers;
class IndexController
{
public function controlSomething(string $something): string {
return $something;
}
}
This works perfectly fine. However, I want a bit of a different folder structure. I would like to go one folder deeper to keep my Controllers somewhere organised. So I want a Controllers folder to keep my Controllers there. Changing my folder structure to this:
myproject
|
-- entry.php
-- src
|
-- Controllers
|
-- IndexController.php
results in an error saying Class 'myproject\Controllers\IndexController' not found.
How can I achieve this? I've tried adding /Controllers to the code where paths and namespaces are defined, but I keep getting this error.
In your current setup, you've mapped the folder src to the base namespace myproject\Controllers. Whatever comes after myproject\Controllers, is expected to mirror a subdirectory structure starting at src.
What follows is the following: when you put IndexController in the directory src\Controllers, the autoloader would only find it if the full class name were myproject\Controllers\Controllers\IndexController.
What you probably want to do instead, is map src to myproject directly, eg.
$replaceRootPath = str_replace('myproject', $sourcePath, $class);
The solution I tend to favour, and is the current industry standard for php, is to use composer as autoloader.
Once installed, configuring your autoloader would become as simple as this json segment:
"autoload": {
"psr-4": {
"myproject\\": "src"
}
},
And, optionally,
"autoload-dev": {
"psr-4": {
"myproject\\Test\\": "tests"
}
},
Using composer has the additional benefit of being able to pull from a vast collection of open source modules to simplify your life.
Try
<?php
namespace myproject\src\Controllers;

Namespaces used for test in official exemple don't work for me [duplicate]

I am new with this namespace thing.
I have 2 classes(separate files) in my base directory, say class1.php and class2.php inside a directory src/.
class1.php
namespace \src\utility\Timer;
class Timer{
public static function somefunction(){
}
}
class2.php
namespace \src\utility\Verification;
use Timer;
class Verification{
Timer::somefunction();
}
When I execute class2.php, i get the Fatal error that
PHP Fatal error: Class 'Timer' not found in path/to/class2.php at
line ***
I read somewhere on SO, that I need to create Autoloaders for this. If so, how do I approach into creating one, and if not, then what else is the issue?
UPDATE
I created an Autoloader which will require all the required files on top of my php script.
So, now the class2.php would end up like this.
namespace \src\utility\Verification;
require '/path/to/class1.php'
use Timer;
//or use src\utility\Timer ... both doesn't work.
class Verification{
Timer::somefunction();
}
This also does not work, and it shows that class not found. But, if I remove all the namespaces, and use's. Everything works fine.
We can solve the namespace problem in two ways
1) We can just use namespace and require
2) We can use Composer and work with the autoloading!
The First Way (Namespace and require) way
Class1.php (Timer Class)
namespace Utility;
class Timer
{
public static function {}
}
Class2.php (Verification Class)
namespace Utility;
require "Class1.php";
//Some interesting points to note down!
//We are not using the keyword "use"
//We need to use the same namespace which is "Utility"
//Therefore, both Class1.php and Class2.php has "namespace Utility"
//Require is usually the file path!
//We do not mention the class name in the require " ";
//What if the Class1.php file is in another folder?
//Ex:"src/utility/Stopwatch/Class1.php"
//Then the require will be "Stopwatch/Class1.php"
//Your namespace would be still "namespace Utility;" for Class1.php
class Verification
{
Timer::somefunction();
}
The Second Way (Using Composer and the autoloading way)
Make composer.json file. According to your example "src/Utility"
We need to create a composer.json file before the src folder. Example: In a folder called myApp you will have composer.json file and a src folder.
{
"autoload": {
"psr-4": {
"Utility\\":"src/utility/"
}
}
}
Now go to that folder open your terminal in the folder location where there is composer.json file. Now type in the terminal!
composer dump-autoload
This will create a vendor folder. Therefore if you have a folder named "MyApp"
you will see vendor folder, src folder and a composer.json file
Timer.php(Timer Class)
namespace Utility;
class Timer
{
public static function somefunction(){}
}
Verification.php (Verification Class)
namespace Utility;
require "../../vendor/autoload.php";
use Utility\Timer;
class Verification
{
Timer::somefunction();
}
This method is more powerful when you have a complex folder structure!!
You are going to need to implement an autoloader, as you have already read about it in SO.
You could check the autoloading standard PSR-4 at http://www.php-fig.org/psr/psr-4/ and you can see a sample implementation of PSR-4 autoloading and an example class implementation to handle multiple namespaces here https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader-examples.md.

Codeception/AspectMock Parent class not found by locator

I have a problem with Codeception/AspectMock.
When using custom autoloader and try to create an instance of a class which has parent form the same custom namespace I have this error:
PHP Fatal error: Uncaught InvalidArgumentException: Class [parent
class name] was not found by locator in
vendor/goaop/parser-reflection/src/ReflectionEngine.php:112
I have very simple setup:
<?php
require_once __DIR__ . '/vendor/autoload.php';
$kernel = AspectMock\Kernel::getInstance();
$kernel->init([
'debug' => true,
'includePaths' => [__DIR__. '/lib'],
]);
$kernel->loadFile(__DIR__ . '/autoload.php'); // custom autoloader
$b = new \lib\B();
Class \lib\B:
namespace lib;
class B extends A {}
Class \lib\A:
namespace lib;
class A
{
public function getName()
{
return static::class;
}
}
Class B is loaded via my custom autoloader, but then the locator tries to load parent class A via composer autoloader and returns this error. Is this a bug, or I'm doing something wrong?
The topic starter has already got an answer on GitHub.
In order to use custom autoloader you should re-init ReflectionEngine with composite class locator that will be able to locate your classes or you can use CallableLocator with closure for resolving paths.
Or, even better you could switch your code base to the PSR0/PSR-4
For example:
$kernel->loadFile(__DIR__ . '/autoload.php'); // custom autoloader
\Go\ParserReflection\ReflectionEngine::init(
new class implements \Go\ParserReflection\LocatorInterface {
public function locateClass($className) {
return (new ReflectionClass($className))->getFileName();
}
}
);
$b = new \lib\B(); // here you go
If you can easily do a find and replace on your codebase, maybe you could refactor your code to PSR-4 autoloading standards and do away with the need for a custom autoloader altogether.
This is the spec https://www.php-fig.org/psr/psr-4/. I'll try and explain it as simply as possible.
Imagine changing your lowercase namespace lib to Lib, and setting that namespace to the src/ directory in your composer.json:
"autoload": {
"psr-4": {
"Lib\\": "src/"
}
}
After setting that, run composer dumpautoload. Then all you need to do is search and replace namespace lib;, replacing with namespace Lib;.
An example class located in src/Form.php would have namespace Lib; at the top, followed by class Form.
<?php
namepace Lib;
class Form
{
// code
}
Namespaces use the folder naming convention. All classes directly in src/ have namespace Lib;. If there are subdirectories, the directory name becomes part of the namespace. For example a file in src/Form/Field/Text.php would have namespace Lib\Form\Field; class Text {}.
<?php
namepace Lib\Form\Field;
class Text
{
// code
}
You can see the full convention in the link above, but the general rule is make any folders begin with a capital letter, as with your classname, and the autoloader should be able to find all of your classes.
This is probably the best practice solution for you, and again as I said, only requires a little bit of file renaming and namespace tweaking. Good luck!

Include and namespace in php

I have created this factory class:
namespace ValidatorFactory;
foreach (glob("Types/*.php") as $filename)
{
include $filename;
}
class ValidatorFactory
{
public static function getValidator($betTypeId)
{
switch ($betTypeId)
{
case 1:
return new B();
break;
default:
return null;
}
}
}
Here is my A abstract class definition:
abstract class A
{
abstract function validateSchema($schema);
}
Here is my B class definition:
class B extends A
{
function validateSchema($schema)
{
return true;
}
}
Now I want to use the factory class in some other file in my project, here is how I am doing it:
$obj = ValidatorFactory::getValidator($someId);
I am using Laravel and via composer (and this tutorial) I tried to load the ValidatorFactory class like built in classes of laravel. Here is what I have added to the composer file:
"autoload": {
"classmap": [
...
],
"psr-0": {
"ValidatorFactory": "app/"
}
},
I have run composer update.
My problem is that my ValidatorFactory is not loaded because I am getting this error:
Class 'ValidatorFactory' not found
If I will add the namespace like this:
$obj = ValidatorFactory\ValidatorFactory::getValidator($someId);
I am getting other error, here it is:
Class 'ValidatorFactory\B' not found
So there are 2 problems, first related to larave/composer autoload (of the namespace). The second is related to includes and inheritance (as I suspect) in php.
How can I solve this two issues? Thanks!
Update:
As suggested in the answer, I have added the same namespace for all 3 involved classes A, B and the factory. No class including any other class.
In the class which calls the factory's getValidator function I am doing it like this:
$obj = ValidatorFactory\ValidatorFactory::getValidator($someId);
Still getting the error which says class B is unknown :(
Here is how the updated class B looks like:
namespace ValidatorFactory;
class B extends A
{
function validateSchema($schema)
{
return true;
}
}
The file structure looks like this: factory file located in app/dir1/ , Class A and B located in app/dir1/dir2/
Anything defined in an include is defined in the global namespace, so the abstract class definitions define the \A and \B classes, respectively (and not ValidatorFactory\A or ValidatorFactory\B). In order to use them the way you are, you would need to add a use statement for each of them after they are included.
But why include anything at all? This goes agains the very idea of an autoloader. If you want to declare a class, put it in one of your namespaces and let Laravel and composer do the hard work for you.
Update
return new B();
Try changing that line to:
return new \B();
And that will clear up your second error. As for the first, you declared your Validator factory to live in a namespace:
namespace ValidatorFactory;
Any code that uses that class (which is not in the same namespace) will need to either A) import the class with a use statement, or B) use the Fully-Qualified Namespace whenever referencing it:
use ValidatorFactory\ValidatorFactory;
// ...
$factory = new ValidatorFactory();
Or
$factory = new ValidatorFactory\ValidatorFactory();
Remember, all namespaces are for is to allow you and another developer to name a class exactly the same thing. For instance, perhaps I'm working on some sort of route mapping tool. I may want to define a Route class, because that name fits extremely well with my current problem domain. However, Taylor already beat me to the punch because there already is a Route class. Using a name space I can ensure that I can still name anything I want whatever I want however I want to and there won't be any conflicts with stuff that has already been named or stuff that has yet to be named.
Update 2
Namespaces in projects using composer need to mirror the directory path in which the underlying files are located (this is how the autoloader can find them- it basically looks at the namespace and classname and turns that into a directory path to the file, and appends a .php extension). If you have classes like this:
// File: app/dir1/dir2/A.php
class A{}
// File: app/dir1/dir3/B.php
class B{}
Then A will have to be in the namespace dir1\dir2, and its fully qualified name would be dir1\dir2\A. Similarly, class B would have to be in the namespace dir1\dir3 and its fully qualified name would be dir1\dir3\B.
From our chat discussion, we couldn't figure out why PSR-0 is not working for you. It turns out that PSR-4 configuration works fine. So I summarise here what we did in the discussion to use PSR-4 autoloading.
File structure:
app
|- commands
|- config
|- ...
|- MyValidator
|- MyValidatorFactory.php
|- A.php
|- B.php
|- ...
MyValidatorFactory.php
namespace MyValidator;
class MyValidatorFactory
{
...
return new B();
...
}
B.php
namespace MyValidator;
class B extends A
{
function validateSchema($schema)
{
return true;
}
}
And finally you can setup PSR-4 autoloading in your composer.json:
"autoload": {
...
"psr-4": {
"MyValidator\\": "app/MyValidator"
}
}
Bonus point:
With the psr-4 configuration above, you may also structure your namespaces and classes like this:
app
|- commands
|- config
|- ...
|- MyApp
|- Validators
|- MyValidatorFactory.php
|- A.php
|- B.php
|- ...
And set the psr-4 config to "MyApp\\": "app/MyApp", the autoloader will recognise your classes and use it like below:
new \MyApp\Validators\MyValidatorFactory;
new \MyApp\Validators\A;
new \MyApp\Validators\B;

Categories