Can't get my autoloader to work with namespaces - php

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;

Related

How to use composer psr-4 fallback correctly

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.

Composer PSR-4 PHPUnit class not found in same directory

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"] }
}
}

Implementing namespaces in fatfree framework

I am trying to use namespaces in fatfree framework, but somehow its not able to find the class following is my setup
routes.ini
[routes]
GET /=Src\Controllers\Index->index
index.php
namespace Src\Controllers;
class Index {
function index($f3) {
$f3->set('name','world');
echo View::instance()->render('template.htm');
}
}
Global index.php
// Retrieve instance of the framework
$f3=require('lib/base.php');
// Initialize CMS
$f3->config('config/config.ini');
// Define routes
$f3->config('config/routes.ini');
// Execute application
$f3->run();
UPDATE:
Error:
Not Found
HTTP 404 (GET /)
• index.php:13 Base->run()
UPDATE 2:
config.ini
[globals]
; Where the framework autoloader will look for app files
AUTOLOAD=src/controllers/
; Remove next line (if you ever plan to put this app in production)
DEBUG=3
; Where errors are logged
LOGS=tmp/
; Our custom error handler, so we also get a pretty page for our users
;ONERROR=""
; Where the framework will look for templates and related HTML-support files
UI=views/
; Where uploads will be saved
UPLOADS=assets/
I'm not sure what's going wrong.
Thanks In advance.
The autoloader of the Fat-Free Framework is very basic. It expects you to define one or more autoloading folders, each of them will map to the root namespace.
So let's say you define $f3->set('AUTOLOAD','app/;inc/') and your file structure is:
- app
- inc
- lib
|- base.php
- index.php
Then a class named MyClass belonging to the Foo\Bar namespace (full path: Foo\Bar\MyClass) should be stored in either app/foo/bar/myclass.php or inc/foo/bar/myclass.php (remember: we specified two autoloading folders).
NB: don't forget to specify namespace Foo\Bar at the beginning of myclass.php (the autoloader won't do it for you).
--
So to answer your specific problem, having the following file structure:
- lib
|- base.php
- src
|- controllers
|- index.php
- index.php
three configurations are possible:
Config 1
$f3->set('AUTOLOAD','src/controllers/')
Then all the files under src/controllers/ will be autoloaded, but remember : src/controllers/ maps to the root namespace, so that means the Index class should belong to the root namespace (full path: \Index).
Config 2
$f3->set('AUTOLOAD','src/')
Then all the files under src/ will be autoloaded, which means the Index class should belong to the Controllers namespace (full path: \Controllers\Index).
Config 3
$f3->set('AUTOLOAD','./')
Then all the files under ./ will be autoloaded, which means the Index class should belong to the Src\Controllers namespace (full path: \Src\Controllers\Index).
Fat-Free is always the root namespace "\". (the following might be wrong) Since F3 loads your classes through the autoloader, you always have to add the root namespace to your own namespaces. In this case you have to change it to
namespace \Src\Controllers;
And of course you have to change it in your routes.ini too.
GET /=\Src\Controllers\Index->index
To help you finding those issues in the future you can higher the DEBUG value with
$f3->set('DEBUG', 2); // 0-3; 0=off, 3=way too much information
Try this config - Your class:
namespace Creo\Controllers;
Framework routes
GET|POST / = \Creo\Controllers\IndexController->indexAction
folder location
_your_app_dir/app/Creo/Controllers
Your bootstrap file (in this case in _your_app_dir/app/)
spl_autoload_register(function ($className) {
$filename = __DIR__ . '/' . str_replace('\\', '/', $className) . ".php";
if (file_exists($filename)) {
include($filename);
if (class_exists($className)) {
return true;
}
}
return false;
});
Hope this will help.

PHP Autoloading with Composer using Namespaces

I previously had a pretty simple autoload script working nicely, but as I've noticed that Doctrine2 is using Composer for this, I thought it might be nice to streamline everything. Unfortunately, Composer does not seem to be working as I understood it to.
Here is the relevant part of my composer.json
"autoload": {
"psr-0": {
"": "models/",
"Catalog2\\Config": "class/"
}
}
Note that the "": "models/" line used by Doctrine2 has been working just fine. After I ran composer update , the bottom part of my vendor/composer/autoload_namespaces.php looks like so:
'Doctrine\\Common\\' => array($vendorDir . '/doctrine/common/lib'),
'Catalog2\\Config' => array($baseDir . '/class'),
'' => array($baseDir . '/models'),
So far so good, I think. In my routes.php file (basically a front-controller) I have the following:
<?php
use Catalog2\Config;
//autoload classes
require_once __DIR__.'/vendor/autoload.php';
try {
$router = new Router;
} catch(Exception $e ) {
echo "<strong>Can't create router object</strong><br/>";
}
Here Catalog2\Config\Router should be calling my class/Router.php, which begins as follows:
<?php
namespace Catalog2\Config;
class Router {
protected $resource; //what are we manipulating? A product? An order?
protected $action; //what are we doing with that resource?
When I go to the page I get this:
Fatal error: Class 'Router' not found in /home/tom/Code/productCatalog2/routes.php on line 14
What is going wrong here? I repeat that Doctrine2 was able to autoload my model code from /models, so why aren't my changes working?
According to PSR-0 the namespace prefix will be included to the path.
So the complete filename for your class must be:
class/Catalog2/Config/Router.php
Meanwhile PSR-4 would behave like you expected: it will just match the namespace prefix and will not append it additionally to the given path.
References:
https://getcomposer.org/doc/04-schema.md#autoload
PS: you probably want the namespace prefix to be "Catalog2\\Config\\" (see the trailing slash)

Will a directory with a period break autoload resolution based on a namespace in Zend Framework?

I have a folder in my library folder which is named after my website. The folder path is like:
~\www\library\myWebsite.com
If I'm using Zend autoloader to load the namespace of everything in the library path, will I have any trouble autoloading a class from that file with a namespace like this:
\myWebsite.com\myClass::myFunction();
I have looked online for documentation on this and I can't find any info about using periods in this way.
I tried it and the complication is in PHP. I think Zend is registering the namespace fine, because when I call \Zend_Load_Autoloader::getRegisteredNamespaces() it shows that it's registered. but when I call the static method from the fully qualified namespace, php gives an error of this:
Fatal error: Undefined constant 'myWebsite' in /home/jesse/www/application/controllers/MyController.php on line 15
It seems like PHP is terminating the namespace identifier, during parsing, at the . (period character). This is dissapointing because to me having a library named after the website was important to my design.
I will rename the directory to myWebsitecom or possibly make the .com it's own sub directory like
myWebsite\com and incorporate that into my namespace tree like: \MyNamespace\Com\MyClass::myFunction();
The easiest way to find out is to try it.
If it doesn't work, you could always write a custom autoloader to make it work. I don't have much experience with php namespaces, but the autoloader would look something like this (I imagine you'll have to tinker with it a bit to determine the correct file path given the class name):
<?php
class My_Loader_Autoloader_MyWebsite implements Zend_Loader_Autoloader_Interface {
/**
* (non-PHPdoc)
* #see Zend_Loader_Autoloader_Interface::autoload()
*/
public function autoload($class) {
if (strtolower(substr($class, 0, 9)) == 'mywebsite') {
$file = realpath(APPLICATION_PATH . '/../library/myWebsite.com/' . $class);
if ($file) {
require_once $file;
return $class;
}
}
return false;
}
}
then put this in your bootstrap:
$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->pushAutoloader(new My_Loader_Autoloader_MyWebsite());
and if this class must be in that myWebsite.com directory, you could just cheat and throw in a require in there too:
require_once(APPLICATION_PATH . '/../library/myWebsite.com/Loader/Autoloader/MyWebsite.php');

Categories