Silex: Can I mount controller providers recursively? - php

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());

Related

Using Illuminate DB outside of Laravel, can't use DB::table()

I have installed some Illuminate packages to use outside of Laravel. However I can't use any of the DB methods.
For example:
$this->query = DB::table((new Vehicle())->getTable())->query();
The above gives me this error:
Error: RuntimeException: A facade root has not been set. in /var/www/vendor/illuminate/support/Facades/Facade.php:218 Stack trace: #0 /var/www/app/Services/PickCarouselService.php(37): Illuminate\Support\Facades\Facade::__callStatic() #1
Within my Ide I get the following message on ::table:
Method 'table' not found in \Illuminate\Support\Facades\DB
I can use models and everything just fine, just not DB directly, how can I fix this?
Edit 1
How would I create my own instance of the DB facade? We do have the following, would it be something similar? If so, what would I pass into the DatabaseManager for the Factory?
$app->instance(Database::class, (new Database($app))->boot()->getConnection());
$app->alias(Database::class, 'database');
Edit 2
Don't think i'm doing this correctly as not too sure what I would pass in for the app.
$app = new Application();
$app->instance(Database::class, new DatabaseManager(null, new ConnectionFactory($app)));
$app->alias(DatabaseManager::class, 'db');
// Application
<?php
namespace App;
use Illuminate\Container\Container;
class Application extends Container
{
protected $basePath = '/var/www';
public function __construct()
{
$this->bindPathsInContainer();
$this->registerBaseBindings();
}
...
Managed to figure out how to create a custom facade. But nice if I could use it like DB::table, but this works:
$app = new Application();
$app->singleton('db.factory', function ($app) {
return new ConnectionFactory($app);
});
$app->singleton('db', function ($app) {
return new DatabaseManager($app, $app['db.factory']);
});
$this->query = app()->db->table('tablename');

Laravel Target is not instantiable while building

I created an Artisan command which worked and where I injected a Kafka client service as the first parameter and a concrete class BudgetsTransformer, as the second parameter.
class ConsumeBudgetsCommand extends Command {
public function __construct(FKafka $kafkaClient, BudgetsTransformer $transformer)
{
$this->kafkaClient = $kafkaClient;
$this->transformer = $transformer;
parent::__construct();
}
}
AppServiceProvider class looked like:
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind('kafka.client', function ($app) {
return new \Weq\FKafka\FKafka();
});
$this->app->bind('budget.transformer', function ($app) {
return new BudgetsTransformer();
});
}
public function boot()
{
$this->app->bind('consume:budgets', function ($app) {
return new ConsumeBudgetsCommand($app['kafka.client'], $app['budget.transformer']);
});
$this->commands('consume:budgets');
}
}
So far all is working properly. Then I decided to create a TransformerInterface which BudgedTransformer implements (and other future Transformers will implement it):
class BudgetsTransformer implements TransformerInterface
{
// ...
}
and I changed the signature in the command to inject the interface instead of the concrete class:
class ConsumeBudgetsCommand extends Command {
public function __construct(FKafka $kafkaClient, TransformerInterface $transformer)
{
$this->kafkaClient = $kafkaClient;
$this->transformer = $transformer;
parent::__construct();
}
}
But I get the following issue when I try to run some artisan command
In Container.php line 933:
Target [App\Transformers\TransformerInterface] is not instantiable while building [App\Console\Commands\ConsumeBudgetsCommand].
I run previously the issue the following artisan command just in case. cache:clear, clear-compiled, optimize and so on but no luck.
What I'm doing wrong? Should I bind the BudgetTransformer in a different way I'm doing now for passing and Interface instead of a concrete class?
I added:
$this->app->bind(TransformerInterface::class, BudgetsTransformer::class);
in AppServiceProvider::register() and I removed
$this->app->bind('budget.transformer', function ($app) {
return new BudgetsTransformer();
});
there, then I update in AppServiceProvider::boot() the command binding:
$this->app->bind('consume:budgets', function ($app) {
return new ConsumeBudgetsCommand($app['kafka.client'], $app[TransformerInterface::class]);
});
But still not working, anyway this approach (even working) will not resolve the issue since when I want to add another different transformer implementation, let's say CostTransformer which implements TransformerInterface is gonna always inject BudgetTransformer. So reading the documentation in the link, I found that Contextual Binding could be the solution, so I substituted by:
$this->app
->when(ConsumeBudgetsCommand::class)
->needs(TransformerInterface::class)
->give(function ($app) {
return new BudgetsTransformer();
});
So in that way, I will be able to inject different implementations of transformers to different commands by injecting the interface. But still not working.
Can someone tell me how exactly declare the command binding
$this->app->bind('consume:budgets', function ($app) {
return new ConsumeBudgetsCommand($app['kafka.client'], ???);
});
to use that Contextual binding?
For binding interfaces must be use this structure https://laravel.com/docs/5.5/container#binding-interfaces-to-implementations
$this->app->bind(TransformerInterface::class, BudgetsTransformer::class);
And
$this->app->bind('consume:budgets', function ($app) {
return new ConsumeBudgetsCommand($app['kafka.client'], $app->make(TransformerInterface::class));
});

Laravel 5.6 passing eloquent model as parameter to a function

I have a selection control on a blade form that is to be refreshed via ajax through this function:
function getOpciones(tbName) {
$.get('/ajax/read-data/' + tbName, function(data){
return (data);
});
}
The function takes a string variable 'tbName' whith the name of the table the control is related to, and passes it on as a parameter to the route:
Route::get('/ajax/read-data/{modelo}', 'AjaxController#readData');
Then the controller should get the parameter {modelo}, and retrieve the records in that table:
use App\RegFiscal;
public function readData($modelo) {
$arreglo = $modelo::all();
return response($arreglo);
}
But even though I am referencing the model with 'use App\RegFiscal', all I get is this error in laravel log:
2018-03-23 18:52:08] local.ERROR: exception
'Symfony\Component\Debug\Exception\FatalErrorException' with message
'Class 'RegFiscal' not found' in
C:\wamp64\www\laravel\cte\app\Http\Controllers\AjaxController.php:32
I´m new to Laravel, so needless to say I am lost and any help would be greatly appreciated. Thanks!
Just because you use App\RegFiscal doesn't mean $modelo is associated with it.
What you can do, though, is use app("App\\$modelo") to load in your model based on the parameter you get from the router. You would no longer need to use App\RegFiscal either.
$arreglo = app("App\\$modelo");
return response($arreglo::all());
This is assuming your model is stored in the default app directory within your Laravel project. If not you can change "App\" to where ever it is stored. If for example your model is in app\models\modelname.php it would be "App\Models\\$modelo".
You can do this as the following:
public function readData($modelo) {
$modelName = '\App' . '\\' . $modelo;
$class = new $modelName();
arreglo = $class::all();
return response($arreglo);
}
To those like me who wanted to inject it on a constructor, here's how to do it:
~$ php artisan make:provider MyProvider
Then override the register function like so:
class MyProvider implements ServiceProvider {
/** #override */
public function register() {
$this->app->bind(ShapeInterface::class, function ($app) {
return new Square($app->make(MyModel::class));
});
}
}
The ShapeInterface is a simple interface and Square is a simple class that implements the shape interface with a constructor parameter of the eloquent model.
class Square implements ShapeInterface {
private MyModel $model;
function __construct(MyModel $model) {
$this->model = $model;
}
...
}

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 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