Slim with PHP-DI: Cannot find classes from autoloader - php

I'm trying to switch from the pimple container that comes bundled with Slim, to PHP-DI and I'm having an issue with getting the autowiring to work. As I'm restricted to using PHP 5.6, I'm using Slim 3.9.0 and PHP-DI 5.2.0 along with php-di/slim-bridge 1.1.
My project structure follows along the lines of:
api
- src
| - Controller
| | - TestController.php
| - Service
| - Model
| - ...
- vendor
- composer.json
In api/composer.json I have the following, and ran composer dumpautoload:
{
"require": {
"slim/slim": "3.*",
"php-di/slim-bridge": "^1.1"
},
"autoload": {
"psr-4": {
"MyAPI\\": "src/"
}
}
}
My api/src/Controller/TestController.php file contains a single class:
<?php
namespace MyAPI\Controller;
class TestController
{
public function __construct()
{
}
public function test($request,$response)
{
return $response->write("Controller is working");
}
}
I initially tried to use a minimal setup to get the autowiring working, just using the default configuration. index.php
<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
require '/../../api/vendor/autoload.php';
$app = new \DI\Bridge\Slim\App;
$app->get('/', TestController::class, ':test');
$app->run();
However, this returned the error:
Type: Invoker\Exception\NotCallableException
Message: 'TestController'is neither a callable nor a valid container entry
The only two ways I could get it to work, is to place the TestController class in index.php directly (which makes me think PHP-DI isn't playing well with the autoloader) or to use the following extension of \DI\Bridge\Slim\App. However, as I need to explicitly register the controller class, this kinda defeats the point of using autowiring (unless I'm missing the point):
use DI\ContainerBuilder;
use Psr\Container\ContainerInterface;
use function DI\factory;
class MyApp extends \DI\Bridge\Slim\App
{
public function __construct() {
$containerBuilder = new ContainerBuilder;
$this->configureContainer($containerBuilder);
$container = $containerBuilder->build();
parent::__construct($container);
}
protected function configureContainer(ContainerBuilder $builder)
{
$definitions = [
'TestController' => DI\factory(function (ContainerInterface $c) {
return new MyAPI\Controller\TestController();
})
];
$builder->addDefinitions($definitions);
}
}
$app = new MyApp();
$app->get('/', ['TestController', 'test']);
$app->run();

If you want to call the test method the syntax is :
$app->get('/', TestController::class .':test');
// or
$app->get('/', 'TestController:test');
rather than
$app->get('/', TestController::class ,':test');
cf https://www.slimframework.com/docs/v3/objects/router.html#container-resolution

Related

Render Twig templates in Controller - Slim 4 Framework

Like said in the title, I looking to rendering twig template in controller using Slim 4 framework.
I searched on the Internet, but I didn't find a solution that works for me and well-explained.
If someone can explains me how can I make this to work.
Here the code a my files:
index.php
<?php
use App\Controllers\HomeController;
use DI\Container;
use Slim\Factory\AppFactory;
require __DIR__ . '/../vendor/autoload.php';
### Container ###
$container = new Container();
### Slim ###
AppFactory::setContainer($container);
$app = AppFactory::create();
### Twig ###
$container = $app->getContainer();
$container['view'] = function ($c) {
return $c;
};
$container['HomeController'] = function ($c) {
return new HomeController($c['view']);
};
### Routes ###
$app->get('/',HomeController::class . ":home");
$app->get('/home', HomeController::class . ":home");
### Run ###
$app->run();
HomeController.php
<?php
namespace App\Controllers;
use Psr\Container\ContainerInterface;
use Slim\Psr7\Request;
use Slim\Psr7\Response;
class HomeController
{
private $app;
public function __construct(ContainerInterface $app)
{
$this->app = $app;
}
public function home(Request $request, Response $response)
{
$this->app->get('view')->render($response, 'home.twig');
}
}
composer.json
{
"require": {
"slim/slim": "4.*",
"slim/psr7": "^1.0",
"slim/twig-view": "^3.0",
"php-di/php-di": "^6.0"
},
"autoload": {
"psr-4": {
"App\\": "app/"
}
}
}
project structure
And when I start the server, the console gives me:
PHP 7.3.11-0ubuntu0.19.10.2 Development Server started at Tue Feb 18 16:33:41 2020
Listening on http://localhost:8080
Document root is /home/thomas/Code/2020/S4/prog_web/homelogin/public
Press Ctrl-C to quit.
[Tue Feb 18 16:33:44 2020] PHP Fatal error: Uncaught Error: Cannot use object of type DI\Container as array in /home/thomas/Code/2020/S4/prog_web/homelogin/public/index.php:17
Stack trace:
#0 {main}
thrown in /home/thomas/Code/2020/S4/prog_web/homelogin/public/index.php on line 17
Thank in advance.
You cannot use DI\Container object as an array. You should use set() method to add a service to the container instead.
Replace these lines:
$container['view'] = function ($c) {
return $c;
};
$container['HomeController'] = function ($c) {
return new HomeController($c['view']);
};
with these:
$container->set('view', function () {
return \Slim\Views\Twig::create('../resources/views', ['cache' => false]);
});
$container->set('HomeController', function () use ($container) {
return new HomeController($container);
});
And then you should return a response (ResponseInterface) in the home() method of your HomeController class:
public function home(Request $request, Response $response)
{
return $this->app->get('view')->render($response, 'templates/home.twig');
}
Note that your home.twig file is located in the templates directory.
Read more about Dependency Container and Twig in Slim.

PHP Slim 3 Framework - where can I put my controller file?

I registering a controller with the container, but it seems not working because it doesn't match to the correct location.
\slim\src\routes.php
<?php
// Routes
$app->get('/dd', 'App\controllers\HomeController:home');
\slim\App\controllers\HomeController.php
<?php
class HomeController
{
protected $container;
// constructor receives container instance
public function __construct(ContainerInterface $container) {
$this->container = $container;
}
public function home($request, $response, $args) {
// your code
// to access items in the container... $this->container->get('');
return $response;
}
public function contact($request, $response, $args) {
// your code
// to access items in the container... $this->container->get('');
return $response;
}
}
My project folder structure:
\slim
  \public
    index.php
    .htaccess
  \App
    \controllers
      HomeController.php
  \src
    dependencies.php
    middleware.php
    routes.php
    settings.php
  \templates
    index.phtml
  \vendor
    \slim
Maybe I should to setting \slim\src\settings.php?
Because it show Slim Application Error:
Type: RuntimeException Message: Callable
App\controllers\HomeController does not exist File:
D:\htdocs\slim\vendor\slim\slim\Slim\CallableResolver.php Line: 90
Last, I also refer to these articles:
https://www.slimframework.com/docs/objects/router.html#container-resolution
PHP Slim Framework Create Controller
PHP Slim Framework Create Controller
How can i create middleware on Slim Framework 3?
How can i create middleware on Slim Framework 3?
Add psr-4 to your composer file so that you're able to call your namespaces.
{
"require": {
"slim/slim": "^3.12
},
"autoload": {
"psr-4": {
"App\\": "app"
}
}
}
This PSR describes a specification for autoloading classes from file paths. Then in your routes.php file add this at the top :
<?php
use app\controllers\HomeController;
// Routes
$app->get('/dd', 'App\controllers\HomeController:home');
and finally in your HomeController.php file add :
<?php
namespace app\controllers;
class HomeController
{
//.. your code
}
hope this helps...:)

Routing HTTP requests to static class methods

I just started using Slim Framework to create my rest API. Everything works well until I try to route HTTP request to a static class method (I used the anonymous function before). Below is my new route code on index.php:
include "vendor/autoload.php";
$config = ['settings' => [
'addContentLengthHeader' => false,
'displayErrorDetails' => true,
'determineRouteBeforeAppMiddleware' => true
]
];
$app = new \Slim\App($config);
$app->get('/user/test', '\App\Controllers\UserController:test');
$app->run();
And below is my UserController class on UserController.php
class UserController{
public function test($request, $response, $args){
$array = ['message'=>'your route works well'];
return $response->withStatus(STAT_SUCCESS)
->withJson($array);
}
}
Error details:
Type : RuntimeException
Message: Callable \Controllers\UserController does not exist
File : /var/www/html/project_api/vendor/slim/slim/Slim/CallableResolver.php
Below is my project folder tree
project_api/
index.php
vendor/
slim/slim/Slim/CallableResolver.php
Controllers/
UserController.php
my composer.json
{
"require": {
"slim/slim": "^3.8",
"sergeytsalkov/meekrodb": "*",
"slim/http-cache": "^0.3.0"
}
},
"autoload": {
"psr-4": {
"Controllers\\": "Controllers/"
}
}
It seems that your namespace is define improperly. In your composer.json, class UserController under the namespace Controllers.
you should define a namespace at the top of your UserController.php:
namespace Controllers;
and change $app->get() in your index.php to:
$app->get('/user/test', 'Controllers\UserController:test');

Dependency Injection Slim Framework 3

I'm using Slim Framework 3 to create an API. The app structure is: MVCP (Model, View, Controller, Providers).
Is it possible to have Slim Dependency Inject all my classes?
I'm using composer to autoload all my dependencies.
My directory structure looks like this:
/app
- controllers/
- Models/
- services/
index.php
/vendor
composer.json
Here's my composer.json file.
{
"require": {
"slim/slim": "^3.3",
"monolog/monolog": "^1.19"
},
"autoload" : {
"psr-4" : {
"Controllers\\" : "app/controllers/",
"Services\\" : "app/services/",
"Models\\" : "app/models/"
}
}
}
Here's my index.php file. Again, the dependencies are being auto injected by composer
<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
require '../vendor/autoload.php';
$container = new \Slim\Container;
$app = new \Slim\App($container);
$app->get('/test/{name}', '\Controllers\PeopleController:getEveryone');
$app->run();
My controller looks like this
<?php #controllers/PeopleController.php
namespace Controllers;
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
class PeopleController
{
protected $peopleService;
protected $ci;
protected $request;
protected $response;
public function __construct(Container $ci, PeopleService $peopleService)
{
$this->peopleService = $peopleService;
$this->ci = $ci;
}
public function getEveryone($request, $response)
{
die($request->getAttribute('name'));
return $this->peopleService->getAllPeoples();
}
}
My PeopleService file looks like this:
<?php
namespace Services;
use Model\PeopleModel;
use Model\AddressModel;
use Model\AutoModel;
class PeopleService
{
protected $peopleModel;
protected $autoModel;
protected $addressModel;
public function __construct(PeopleModel $peopleModel, AddressModel $addressModel, AutoModel $autoModel)
{
$this->addressModel = $addressModel;
$this->autoModel = $autoModel;
$this->peopleModel = $peopleModel;
}
public function getAllPeopleInfo()
{
$address = $this->addressModel->getAddress();
$auto = $this->autoModel->getAutoMake();
$person = $this->peopleModel->getPeople();
return [
$person[1], $address[1], $auto[1]
];
}
}
Models/AddressModels.php
<?php
namespace Model;
class AddressModel
{
public function __construct()
{
// do stuff
}
public function getAddress()
{
return [
1 => '123 Maple Street',
];
}
}
Models/AutoModel.php
namespace Model;
class AutoModel
{
public function __construct()
{
// do stuff
}
public function getAutoMake()
{
return [
1 => 'Honda'
];
}
}
Models/PeopleModel.php
<?php
namespace Model;
class PeopleModel
{
public function __construct()
{
// do stuff
}
public function getPeople()
{
return [
1 => 'Bob'
];
}
}
ERROR
I'm getting the following error now:
PHP Catchable fatal error: Argument 2 passed to Controllers\PeopleController::__construct() must be an instance of Services\PeopleService, none given, called in /var/www/vendor/slim/slim/Slim/CallableResolver.php on line 64 and defined in /var/www/app/controllers/PeopleController.php on line 21
THE QUESTION
How do I dependency inject all my classes? Is there a way to automagically tell Slim's DI Container to do it?
When you reference a class in the route callable Slim will ask the DIC for it. If the DIC doesn't have a registration for that class name, then it will instantiate the class itself, passing the container as the only argument to the class.
Hence, to inject the correct dependencies for your controller, you just have to create your own DIC factory:
$container = $app->getContainer();
$container['\Controllers\PeopleController'] = function ($c) {
$peopleService = $c->get('\Services\PeopleService');
return new Controllers\PeopleController($c, $peopleService);
};
Of course, you now need a DIC factory for the PeopleService:
$container['\Services\PeopleService'] = function ($c) {
$peopleModel = new Models\PeopleModel;
$addressModel = new Models\AddressModel;
$autoModel = new Models\AutoModel;
return new Services\PeopleService($peopleModel, $addressModel, $autoModel);
};
(If PeopleModel, AddressModel, or AutoModel had dependencies, then you would create DIC factories for those too.)

Silex Problems with PHP 5.3.2?

I tried the examples from Silex and put my Controllers in a seperate directory and class.
No the controller method will get the Request and Application objects passed by default. This works on my dev machine which has 5.3.14 but not on the default Ubuntu 5.3.2. It give me:
PHP Catchable fatal error: Argument 1 passed to Sv\Controller\Index::index() must be an instance of Symfony\Component\HttpFoundation\Request, none given, called in /site/include/app/bootstrap.php on line 23 and defined in /site/include/app/Sv/Controller/Index.php on line 46
Here's my bootstrap PHP:
<?php
require_once __DIR__ . '/../vendor/autoload.php';
use Sv\Repository\PostRepository;
use Sv\Controller;
$app = new Silex\Application();
// define global service for db access
$app['posts.repository'] = $app->share( function () {
return new Sv\Repository\PostRepository;
});
// register controller as a service
$app->register(new Silex\Provider\ServiceControllerServiceProvider());
$app['default.controller'] = $app->share(function () use ($app) {
return new Controller\Index();
});
$app['service.controller'] = $app->share(function () use ($app) {
return new Controller\Service($app['posts.repository']);
});
// define routes
$app->get('/', 'default.controller:index');
$app->get('/next', 'default.controller:next');
$service = $app['controllers_factory'];
$service->get('/', "service.controller:indexJsonAction");
// mount routes
$app->mount('/service', $service);
// definitions
$app->run();
and here's the Controller code:
namespace Sv\Controller;
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
class Index
{
public function index(Request $request, Application $app)
{
return 'Route /index reached';
}
public function next(Request $request, Application $app)
{
return 'Route /next reached';
}
}
Why does this not work?
I hope this is not the same problem that prevents me from using ZF2 under PHP 5.3.2...
Silex requires PHP 5.3.3, as you can see in their composer.json:
"require": {
"php": ">=5.3.3",
...
And it also stated in the README file:
Silex works with PHP 5.3.3 or later.
This is due to the fact that Symfony2 is no longer supporting PHP 5.3.2.

Categories