Silex Problems with PHP 5.3.2? - php

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.

Related

Slim with PHP-DI: Cannot find classes from autoloader

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

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.

Dependency injection not working with League\Route and League\Container

I'm building a web application right now and I'm facing a problem with my controller.
I want to send to my controller my League\Plate\Engine (registred in my Container) but I keep having the same error : Argument 3 passed to App\Controller\Main::index() must be an instance of League\Plates\Engine, array given
Here is my files :
dependencies.php
use League\Container\Container;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Yajra\Pdo\Oci8;
use League\Container\ReflectionContainer;
$container = new Container();
// Active auto-wiring
$container->delegate(
new ReflectionContainer
);
// Others dependencies
// ...
// Views
$container->add('view', function () {
$templates = new League\Plates\Engine();
$templates->addFolder('web', __DIR__ . '/templates/views/');
$templates->addFolder('emails', __DIR__ . '/templates/emails/');
// Extension
//$templates->loadExtension(new League\Plates\Extension\Asset('/path/to/public'));
//$templates->loadExtension(new League\Plates\Extension\URI($_SERVER['PATH_INFO']));
return $templates;
});
return $container;
routes.php
use League\Route\RouteCollection;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
$route = new RouteCollection($container);
// Page index
$route->get('/', 'App\Controller\Main::index');
// Others routes...
return $route;
Main.php
namespace App\Controller;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use League\Plates\Engine;
class Main
{
public function index(ServerRequestInterface $request, ResponseInterface $response, Engine $templates) {
//return $response->getBody()->write($this->templates->render('web::home'));
return $response;
}
}
Thank you in advance
EDIT
I've made a progress.
I extended the Main class to extends the abstract class BaseController which looks like this :
namespace App\Controller;
use League\Plates\Engine;
class BaseController
{
protected $templates;
public function __construct(Engine $templates) {
$this->templates = $templates;
}
}
The first error goes away, but another one show up. In the Main class, I would like to use the view object that I instanciate in the container, but the object passed to the constructor is an empty one :
Main.php
class Main extends BaseController
{
public function index(ServerRequestInterface $request, ResponseInterface $response) {
echo '<pre>'.print_r($this->templates,1).'</pre>'; // Return an empty Plate Engine object
return $response->getBody()->write($this->templates->render('web::home'));
//return $response;
}
}
And this doesn't explain why the first error shows up
EDIT 2
After some digging, I finally make it works, but I sense that something is wrong.
I replaced in the container the term view by the namespace of the Engine class :
$container->add('League\Plates\Engine', function () {
// The same as before
});
In Main.php I've updated the index function like this :
public function index(ServerRequestInterface $request, ResponseInterface $response) {
$body = $response->getBody();
$body->write($this->templates->render('web::home'));
return $response->withBody($body);
}
And the page doesn't throw a 500 error and the html file is displayed correctly.
But, what if I want to change the template engine by Twig for example ? This would mean I'll need to change all the call to $container->get('League\Plate\Engine'); by $container->get('What\Ever'); ? That's not very practical!
I probably missed something!
And the problem will rise again when I'll want to use my PDO object... or every other object.
Ok so I solved my problem by registering my Controllers classes in the container itself.
For example, for displaying the index page, the Main class call the index function. In my container, I call
$container->add('App\Controller\Main')
->withArgument($container->get('view'));
To summary :
bootstap.php (called by index.php)
require __DIR__ . '/../../vendor/autoload.php';
$dotenv = new \Dotenv\Dotenv(__DIR__ . '/../');
$dotenv->load();
$config = new Config(__DIR__ . '/../config/');
$container = require __DIR__ . '/../dependencies.php';
$route = require __DIR__ . '/../routes.php';
$response = $route->dispatch($container->get('request'), $container->get('response'));
$container->get('emitter')->emit($response);
dependencies.php
$container = new Container();
// activate auto-wiring
$container->delegate(
new ReflectionContainer
);
// Others dependencies...
// Views
$container->add('view', function () {
$templates = new League\Plates\Engine();
$templates->addFolder('web', __DIR__ . '/templates/views/');
$templates->addFolder('emails', __DIR__ . '/templates/emails/');
// Extension
//$templates->loadExtension(new League\Plates\Extension\Asset('/path/to/public'));
//$templates->loadExtension(new League\Plates\Extension\URI($_SERVER['PATH_INFO']));
return $templates;
});
// THIS IS THE TRICK
$container->add('App\Controller\Main')
->withArgument($container->get('view'));
// others controllers...
return $container;
routes.php
$route = new RouteCollection($container);
// Page index
$route->get('/', 'App\Controller\Main::index');
// Others routes...
return $route;
I have no idea why or how this work !
This works, because you are registering with the container your controller class, and telling the container to dependency inject your View class in the constructor..
The line you added, here, is doing that:
$container->add('App\Controller\Main')
->withArgument($container->get('view'));
The Hello World documentation example explains it perfectly.
http://container.thephpleague.com/3.x/constructor-injection/

Silex: Can I mount controller providers recursively?

I wanted to group my api routes as follows:
$app->mount('/api/v1.0', function($api) use($app) {
$api->mount('/documents', new DocumentsApiControllerProvider());
});
where the DocumentsApiControllerProvider is defined like that:
use Silex\Application;
use Silex\Api\ControllerProviderInterface;
use App\Controllers\DocumentsApiController;
class DocumentsApiControllerProvider implements ControllerProviderInterface {
public function connect(Application $app) {
$app['api.documents.controller'] = function() use($app) {
return new DocumentsApiController();
};
$controller = $app['controllers_factory'];
$controller->get('/folders/{folder_id}/content/pages/{page}', 'api.documents.controller:getPage');
return $controller;
}
}
This results in the following exception being thrown:
Fatal error: Uncaught exception 'LogicException' with message 'The "mount" method takes either a "ControllerCollection" instance or callable.'
Whereas non-recursive mounting works as expected:
$app->mount('/api/v1.0/documents', new DocumentsApiControllerProvider());
There's a discussion regarding the topic: https://github.com/silexphp/Silex/pull/1345, yet I couldn't understand was the ability added or ignored?
How do I achieve this?
Update
Well, I decided to mount api routed straight into the Silex\Application instance (nesting/grouping!), just with the same prefix - 'api/v1.0/':
$app->mount("/api/v1.0/documents", new DocumentsApiControllerProvider());
$app->mount("/api/v1.0/otherstuff", new OtherStuffApiControllerProvider());

How to unit test Slim framework applications

I've been trying to unit test modifying examples of other peoples code and each time I get to the point my tests are running without errors - I just get the same failures when I expect them to pass. There isn't a great deal of documentation online, and I don't really know where else to go with this. Can anyone see where in my code I'm going wrong:
bootstrap.php (phpunit bootstrap file)
This is basically just a container for the $app object. I initiate the $app object with the same files I initiate my real application with (routes, config).
<?php
/**
* This makes our life easier when dealing with paths. Everything is relative
* to the application root now.
*/
chdir(dirname('../'));
// require composer autoloader for loading classes
require 'vendor/autoload.php';
// app container class - singleton pattern
class App
{
// Hold an instance of the class
private static $instance;
public static function getInstance()
{
if (!isset(self::$instance)) {
// Instantiate a Slim application:
$app = new \Slim\Slim(array(
'mode' => getenv('APPLICATION_ENV') ?: 'production',
));
// set configuration
require 'app/config.php';
// include the routes (always after we've instantiated our app instance)
require 'app/routes.php';
self::$instance = $app;
}
return self::$instance;
}
}
Next, is my test file with one test:
AccountsControllerTest.php
<?php
use Slim\Environment;
class AccountsControllerTest extends PHPUnit_Framework_TestCase {
public static function get($path)
{
Environment::mock(array(
'REQUEST_METHOD' => 'GET',
'PATH_INFO' => $path,
));
$app = App::getInstance();
//$app->middleware[0]->call();
$app->response()->finalize();
return $app->response();
}
public function testIndex() {
$response = $this->get('/accounts');
$this->assertContains('Accounts', $response->getBody());
}
}
Hopefully it's a little clear what I'm trying to do. Basically just check for the presence of "Accounts" (which, when I load in the browser, is present)
Below is the result I get back:
$ vendor/bin/phpunit
PHPUnit 4.3.4 by Sebastian Bergmann.
Configuration read from /var/www/shared-views-slim/phpunit.xml
F
Time: 33 ms, Memory: 4.75Mb
There was 1 failure:
1) AccountsControllerTest::testIndex
Failed asserting that '' contains "Accounts".
/var/www/shared-views-slim/tests/app/controllers/AccountsControllerTest.php:30
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
UPDATE:
routes.php
<?php
// Define a HTTP GET route:
$app->group('/accounts', function () use ($app) {
$controller = new App\Controllers\AccountsController($app);
// index
$app->get('/', function () use ($controller) {
$controller->index();
});
// show
$app->get('/:id', function ($id) use ($controller) {
$controller->show($id);
})->conditions(array('id' => '[1-9]([0-9]*)'));
// create
// form
$app->get('/create', function () use ($controller) {
$controller->create();
});
// action
$app->post('/', function () use ($controller) {
$controller->create();
});
// update
// form
$app->get('/:id/edit', function ($id) use ($controller) {
$controller->update($id);
})->conditions(array('id' => '[1-9]([0-9]*)'));
// action
$app->put('/:id', function ($id) use ($controller) {
$controller->update($id);
});
// delete
// form
$app->get('/:id/delete', function ($id) use ($controller) {
$controller->delete($id);
})->conditions(array('id' => '[1-9]([0-9]*)'));
//action
$app->delete('/:id', function ($id) use ($controller) {
$controller->delete($id);
});
});

Categories