Composer PSR-4 PHPUnit class not found in same directory - php

I have the following project structure
src/
├─ MyPackage/
├─ MySDK.php
└─ SDKHelper.php
test/
├─ MySDKTest.php
└─ TestUtils.php
composer.json
composer.lock
My composer.json file looks like this:
"autoload": {
"psr-4": {
"MyPackage\\": "src/MyPackage"
}
}
Everything worked great, and MySDKTest.php unit tests were passing, until I tried adding some utility methods to a third file, TestUtils.php. When I try to call TestUtils::utilityMethod() from MySDKTest.php, phpunit complains that the class TestUtils is not found.
I've been reading about adding an autoload-dev key, variations whereof I have tried, but so far, it appears that nothing is working. I should clarify that I am able to use MySDK and SDKHelper methods inside MySDKTest. MySDKTest.php looks like this when it works:
use MyPackage\MySDK;
class MySDKTest extends PHPUnit_Framework_TestCase {
public function testPackage() {
$sdk = new MySDK();
$sdk->exampleMethod();
}
}

It should be pretty simple. Composer's PSR-4 autoloader just defines a mapping from a namespace to a folder.
Are your tests namespaced correctly? It looks like they're not since you have a use at the top of your test class. If MySDK is in the namepace MyPackage (fully qualified MyPackage\MySDK), I would expect MySDKTest to also be in the MyPackage namespace at MyPackage\MySDKTest. It doesn't have to be that way - you could put the tests in a different namespace if you prefer.
Regardless, the reason it's not working is you didn't register the test folder with the autoloader. The way it looks like your stuff is currently set up, your autoloader config should look like this:
{
"autoload": {
"psr-4": { "MyPackage\\": ["src/MyPackage/", "test/"] }
}
}
Also you'd need to change use MyPackage\MySDK; to namespace MyPackage; in your test class.
Note
Your folder structure is a little weird. I would expect test to match src. So it would be like this:
test/
├─ MyPackage/
├─ MySDK.php
└─ SDKHelper.php
Adjust the namespace accordingly:
{
"autoload": {
"psr-4": { "MyPackage\\": ["src/MyPackage", "test/MyPackage"] }
}
}

Related

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;

Composer PSR-0 autoloading dont work

I have problem with autoloading module controller class from my app. I try to configure composer.json but still not working. Probably am doing somthing wrong with this directory structure. I try examples from composer doc but again dont work...
Directory structure:
|- admin
|----- modules
|--------- Menu
|------------Controller
|--------------MenuController.php
Lets see composer.json
{
"autoload": {
"psr-0": { "Admin\\Modules\\": "" }
}
}
I try to set path but nothing again // "Admin\Modules\": "admin/modules"
Menu controller:
namespace Admin\Modules\Menu\Controller;
class MenuController extends AbstractAdminBaseController
FrontController
require 'vendor/autoload.php';
new \Admin\Modules\Menu\Controller\MenuController();
All time Class not found. I try 100 examples from google and nothing. Any example how to slowe this problem? Thanks
Update structure:
├───admin
│ └───modules
│ └───Menu
│ └───controller
│ └───MenuController.php
├───vednor
│ └───autoload.php
│ └───composer
│ └───autoload_classmap.php
│ └───autoload_namespaces.php
│ └───autoload_psr4.php
│ └───autoload_real.php
│ └───ClassLoader.php
├───public
├───assets
├───index.php
├───composer.json
├───composer.lock
Your PSR-0 will never work, because this standard dictates that the path to the file has to be EXACTLY like the classname. Note that your first part of the namespace is "Admin", but the first directory part is only "admin" - cases must match exactly, or it won't work (or will only work with case insensitive filesystems).
You will succeed with using PSR-4, though. Why? Because with PSR-4, the given namespace prefix is removed from the full classname, and the rest is being converted into a path that is searched in the directory given for the namespace prefix.
Example for your case:
"autoload": {
"psr-0": { "Admin\\Modules\\": "" }
}
Will not work because the files are in path admin/modules, but must be in Admin/Modules.
"autoload": {
"psr-4": { "Admin\\Modules\\": "admin/modules/" }
}
Will work because the prefix Admin\Modules\ is removed and the remaining class name is being converted to a path and added to admin/modules.
Ah, one gotcha! It will NOT work, because you chose to name the class ...\Controller\..., but the path once again .../controller/....
Honestly, I'd highly recommend to convert your file names and location to PSR-4 compatibility, even for the prefixed directories that you'd be able to work around with Composer. This will eliminate the surprising lowercase directory structures I see.
I mean: Why is that controller directory even lower case in the first place if every class located there is Controller? I really can't understand this.

Composer psr-0 autoloading of custom namespaces does not work

I have trouble adding my own namespaces to composer with PSR-0. I have read this and this but I still can't make it work
composer.json
{
"require": {
"klein/klein": "2.0.x",
"doctrine/orm": "2.4.4"
},
"autoload": {
"psr-0": {
"mynamespace": "src/"
}
}
}
The src folder is placed inside the same directory as composer.json
The src directory has the following structure
src
└── mynamespace
├── Keys.php
Keys.php
<?php
namespace mynamespace\Keys;
define ("API_KEY", "XXXXXXXXXXXX");
?>
index.php
use Klein\Klein;
use mynamespace\Keys;
require_once __DIR__ . '/vendor/autoload.php';
$klein = new Klein(); // works
echo API_KEY; // Undefined constant
You can only load classes, interfaces and traits with autoloading.
Because all you do is add a use clause which does not do anything by itself with autoloading (i.e. it does not load something), and you do not use a class, nothing happens.
I recommend using class constants. They may be put into classes or interfaces:
namespace mynamespace;
interface Keys {
const API_KEY = 'XXXXXXXX';
}
use mynamespace/Keys;
echo Keys::API_KEY;

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;

Proper Namespacing in Laravel

I'm trying to namespace my Models in Laravel, and also have them be autoloaded automagically like the normal models do in the root of app/models. composer dump-autoload works, but I don't want to run that after I create a new model.
#app/models/MyNamespace/Thing.php
<?php
namespace App\Models\MyNamespace;
class Thing {
// ...
}
#app/routes.php
<?php
Route::get('test', function(){
$thing = new App\Models\MyNamespace\Thing;
});
If I run composer dump-autoload everything is fine, otherwise I get a class not found exception.
What can I do to make that kind of structure work without rebuilding the classmap every time I make a new class? I think PSR-0 is where your namespaces correlate directly with your directory structure, and it appears as though my classes are adhering to that...
I've also tried using the Workbench which works great, but ideally I'd like to have, for ex: app/models/MyNamespace, app/models/AnotherNamespace.
This is not a Laravel 'problem', this is something that will work, exactly the same way, on every application that uses Composer.
If your classes are following the psr-0 rules (directory structure matters!), you can configure it in your composer.json
{
"autoload": {
"psr-0": {"MyNamespace\\": "app/models"}
}
}
Execute
composer dump-autoload
Once and it it will show in your autoload_namespaces.php. After that Composer will be able to find your classes by its namespaces, no need to dump-autoload again.
To explain better how it works. If you do
"psr-0": {"MyNamespace\\": "app/models"}
You must use it this way:
$user = new MyNamespace\User.php
Because Composer adds your namespace to the end of your namespace path and it will expect to find User.php in
/var/www/yourappdir/app/models/MyNamespace/User.php
So, by doing
"psr-0": { "App\\Models\\": "" }
You are telling Composer that ALL /var/www/yourappdir/App/Models subfolders can contain namespaced files of the App\Models namespace. And you'll be able to address files like:
$user = new App\Models\User.php
$user = new App\Models\MyNamespace\User.php
$user = new App\Models\MyNamespace\Foo\Bar\User.php
And if you do
"psr-0": { "App\\Foo": "" }
Composer will be able to address those namespaces
/var/www/yourappdir/App/Foo
/var/www/yourappdir/App/FooBar
/var/www/yourappdir/App/Foo/Bar
/var/www/yourappdir/App/Foo/Bar/Baz
But if you do
"psr-0": { "App\\Foo\\": "" }
It will be able to address just
/var/www/yourappdir/App/Foo
/var/www/yourappdir/App/Foo/Bar
/var/www/yourappdir/App/Foo/Bar/Baz

Categories