Autoload for controller classes in slim framework - php

I'm trying to build a site with slim and autoloading my controller classes for the routes. I'm currently setting up the base structure and testing with a single route with nothing more than a simple "Test" output.
I prevously did this stuff with defining a spl_autoload_register function, but as this approach isn't recommended by slim and composer I want to do it right and I'm not trying to autoload my classes.
My Project is set up as this:
The class BlockController inside the file with the same Name under Controller is inside a namespace defined with namespace MyAPI\Controller;
app/Controller/BlockController.php
namespace MyAPI\Controller;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class BlockController
{
public function getList(Request $request, Response $response, $args)
{
return $response->withStatus(200)
->withHeader('Content-Type', 'text/html')
->write("Test");
}
}
I'm loading the dependencies and settings and after that all my routes (which contains currently only some small ones for testing my architecture):
public/index.php:
require __DIR__ . '/../vendor/autoload.php';
$settings = require __DIR__ . '/../app/settings.php';
$app = new \Slim\App($settings);
require __DIR__ . '/../app/dependencies.php';
require __DIR__ . '/../app/routes.php';
$app->run();
app/routes.php (is very simple, will be extended with more Route-files):
require 'Routes/BlockRoute.php';
app/Routes/BlockRoute.php:
use MyAPI\Controller\BlockController;
$container["BlockController"] = function ($container) {
return new BlockController($container);
};
$app->group('/block', function() use ($container) {
$this->get('[/]', 'BlockController::getList');
});
So the first command inside BlockRoute.php is the use of the BlockController-namespace. Everything under app/ should have the Base-Namespace MyAPI.
As described in the slim-documentation I planned to do that with to autoload feature of composer, so I modified my composer.json and added the following:
{
"require": { .. },
"autoload": {
"psr-4": {
"MyAPI\\": "app"
}
}
}
Edit: updated path to app-folder after the answer from Adam Lavin
After that I ran composer update. Is that the right command for those changes? Or should I use composer install? Couldn't find any more information what I have to do after making those additions in the autoload-section.
When I run the site now with the php webserver and navigate to this route /block I get the following RuntimeException:
Callable BlockController::getList does not exist
File: C:\Prog\src\vendor\slim\slim\Slim\CallableResolver.php
So the problem is that BlockController doesn't get included/autoloaded correctly but I don't understand why or what exactly the problem is. I tried to find some examples of working configurations with slim+composer+autoloading of classes but couldn't find something related.
Any input appreciated.

Since you're pointing MyApp\\ to ../src (the same directory as composer), the autoloader is going to try and find the controller in src/Controllers/BlockController.php.
It should be pointing to ../src/app, though since composer.json is in the src folder it can be simplified to app in the resulting composer.json file.
{
"require": { .. },
"autoload": {
"psr-4": {
"MyAPI\\": "app"
}
}
}
Additionally, In your example, the namespace of the BlockController is MoinAPI\Controllers, and should be MyAPI\Controllers.
And finally, in slim, you use a single colon instead of a double to refer to a callable route. BlockController::getList should be BlockController:getList

Run this from within docker container, or using the same php binary that composer used.
composer dump-autoload -o -vvv #-o fixed my problem in my case

Related

Composer autoload not including my custom namespaces (Silex)

I'm developing a REST API with Silex and I'm facing a problem regarding autoloading of my custom librairy. It looks like Composer's autoload is not including it, because when I include it myself it works.
# The autoload section in composer.json
# Tried with :
# "Oc\\": "src/Oc"
# "Oc\\": "src/"
# "": "src/"
"autoload": {
"psr-4": {
"Oc\\": "src/"
}
}
<?php
// api/index.php <-- public-facing API
require_once __DIR__.'/../vendor/autoload.php';
$app = require __DIR__.'/../src/app.php';
require __DIR__.'/../src/routes.php'; // <--
$app->run();
<?php
// src/routes.php
// When uncommented, it works!
//include('Oc/ParseImport.php');
use Symfony\Component\HttpFoundation\Response;
use Oc\ParseImport;
$app->get('/hello', function () use ($app) {
return new Response(Oc\ParseImport(), 200);
});
<?php
// src/Oc/ParseImport.php
namespace Oc {
function ParseImport() {
return 'foobar!';
}
}
I run composer dumpautoload after each composer.json manipulation, and I do see the line 'Oc\\' => array($baseDir . '/src/Oc') (or anything I tried) in vendor/composer/autoload_psr4.php.
I can't figure out what is wrong.
Almost everything you did was correct.
When trying to autoload classes in a namespace, given that a class is named Oc\Foo and is located in the file src/Oc/Foo.php, the correct autoloading would be "PSR-4": { "Oc\\": "src/Oc" }.
However, you do not have a class. You have a function. And functions cannot be autoloaded by PHP until now. It has been proposed more than once (the one proposal I found easily is https://wiki.php.net/rfc/function_autoloading), but until now this feature hasn't been implemented.
Your alternative solutions:
Move the function into a static method of a class. Classes can be autoloaded.
Include the function definition as "files" autoloading: "files": ["src/Oc/ParseImport.php"] Note that this approach will always include that file even if it isn't being used - but there is no other way to include functions in PHP.
As illustration see how Guzzle did it:
Autoloading in composer.json
Conditional include of functions based on function_exists
Function definition

PSR-4 autoloading not working - cannot find class

I'm trying to split up a long file into smaller chunks, so I created an src folder, and am trying to reference it from the main Extension.php file (which loads and works fine, by the way).
So, I add the src folder to the psr-4 autoloading array:
"psr-4": {
"Bolt\\Extension\\AndyJessop\\SurveyMonkey\\": [
"",
"src/"
]
}
I create the Test.php file inside src:
<?php
namespace Bolt\Extension\AndyJessop\SurveyMonkey;
class Test
{
public function test() {
return 'success';
}
}
In the Extension.php file (which is under the same namespace), I have this function that is called:
use Bolt\Extension\AndyJessop\SurveyMonkey\Test;
public function testing(){
return Test::test();
}
But I get the following error:
Error: Class 'Bolt\Extension\AndyJessop\SurveyMonkey\Test' not found
File: extensions/local/andyjessop/surveymonkey/Extension.php
First, either run composer update or composer dump-autoload to generate the autoload system.
Next, make sure that you include (require_once is preferable) the autoload at the top of your entrypoint(s):
require_once __DIR__ . '/path/to/vendor/autoload.php';
N.B.: if you have PHP 5.3 or lower, replace __DIR__ with dirname(__FILE__).

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)

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

How do you register a namespace with Silex autoloader

I'm experimenting with creating an extension with the Silex php micro framework for user authentication but I can't seem to get the autoloader to work. Can anyone shed any light?
I have a directory structure like this (truncated)
usertest
|_lib
| |_silex.phar
| |_MyNamespace
| |_UserExtension.php
| |_User.php
|_www
|_index.php
The pertinent bits of index.php, which serves as the bootstrap and the front controller look like this:
require '../lib/silex.phar';
use Silex\Application;
use MyNamespace\UserExtension;
$app = new Application();
$app['autoloader']->registerNamespace( 'MyNamespace', '../lib' );
$app->register( new UserExtension() );
The class I'm trying to load looks similar this:
namespace MyNamespace;
use Silex\Application;
use Silex\ExtensionInterface;
class UserExtension implements ExtensionInterface {
public function register( Application $app ) {
$app['user'] = $app->share( function() use( $app ) {
return new User();
});
}
}
All pretty straight forward except it throws this error:
Fatal error: Class 'MyNamespace\UserExtension' not found in /home/meouw/Projects/php/usertest/www/index.php on line 8
I have dabbled with symfony2 and have successfully followed the instructions for setting up the universal class loader, but in this instance I am stumped. Am I missing something? Any help would be appreciated.
In recent versions of Silex the autoloader is deprecated and you should register all your namespaces through the composer.json file which imo is a nicer solution because you are centralizing your autoloading definitions.
Example:
{
"require": {
"silex/silex": "1.0.*#dev"
},
"autoload": {
"psr-0": {
"MyNameSpace": "src/"
}
}
}
In fact when you try to access the autoloader in any recent version of Silex the following RuntimeException is thrown:
You tried to access the autoloader service. The autoloader has been removed from Silex. It is recommended that you use Composer to manage your dependencies and handle your autoloading. See http://getcomposer.org for more information.
I'd use
$app['autoloader']->registerNamespace('MyNamespace', __DIR__.'/../lib');
Deprecated - As of 2014-10-21 PSR-0 has been marked as deprecated.
PSR-4 is now recommended as an alternative
That is why you should use PSR-4 syntax in composer.json
{
"require": {
"silex/silex": "1.0.*#dev",
},
"autoload": {
"psr-4": {
"Vendor\\Namespace\\": "/path"
}
}
}
To register namespaces, just call registerNamespaces() like this:
$app = new Silex\Application();
$app['autoloader']->registerNamespaces(array(
'Symfony' => __DIR__.'/../vendor/',
'Panda' => __DIR__.'/../vendor/SilexDiscountServiceProvider/src',
'Knp' => __DIR__.'/../vendor/KnpSilexExtensions/',
// ...
));
Both adding appropriate statement to the autoload section of composer.json and registering namespaces directly calling registerNamespace was not working for me, until I executed composer update in the projects folder.

Categories