I recently moved from Slim 2.X to Slim 3.X and I've found a problem that may be a little stupid but annoys me in some ways.
The newly Slim 3.X, as a difference between the older 2.X version, it implements a new container system using Dependency Injection Containers (DIC) build on Pimple.
As I've been reading, I find this a very great enhancement because it lets you manage your PHP app in a lot of different ways.
When I started playing around with the new things it has, I found something confusing that maybe is something I'm missing.
I use PHPStorm; one of the things that I like about this IDE is its code-completion and the facility for writing code and understanding classes (I'm actually a student, so this helps me a lot).
When I write a Slim route, if I want to access, for example, to my view object stored inside the container, I have to refer to it with the $this->view variable. The thing is that, normally, PHPStorm lists me the methods and properties inside an object when I mention it, but that didn't happen with the $this object.
I suppose that, inside a route, Slim stores all route functionalities and all the container objects into the $this assigner.
$container = $app->getContainer();
$container['view'] = new \Slim\Views\PhpRenderer('protected/views/');
$app->get('/products', function(Request $request, Response $response) {
$response = $this->view->render($response, 'products.php');
return $response;
})->setName('products');
When I access my /products route, it successfully renders my products template and shows what it's expected to show, so no problems with that.
The matter with it is that I want PHPStorm to know that $this variable inside a route it stores all the containers that are set previously before the route is called.
I thought about /* #var */ and /* #global */ or something like this but after searching for a while and trying different things, I haven't found anything that could work.
In conclusion, what I'm trying to say is if it's possible that PHPStorm could list me the properties and methods of the container objects like this:
but with the $this object inside a route:
Thanks!
The easiest way to do this is to have separate Action classes rather than use closures. This also has the benefit of being easier to test.
Firstly create your action, inject its dependencies into its constructor and write an `__invoke`` method that will be called by Slim:
class ProductsListAction {
protected $view;
public function __construct(\Slim\Views\PhpRenderer $view) {
$this->view = $view;
}
public function __invoke($request, $response, $args) {
$response = $this->view->render($response, 'products.php');
return $response;
}
}
For this to work, you now need a DIC factory for it:
$container['ProductsListAction'] = function ($c) {
return new ProductsListAction($c['view']);
};
You can now register your new action as a route callable:
$app->get('/products', 'ProductListAction');
Now, PhpStorm will correctly autocomplete within your ProductsListAction class.
I was trying to find something like this for Eclipse, and someone suggested you modify the properties with PHPDOCS of Slim\App. Since I didn't want to change the Slim files themself, I tried to make an empty class that extends Slim\App, and use PHPDOCS to add propeties to that:
/**
* OurApp
*
* Extends Slim\App with properties so we have code completion for a bunch of stuff!
*
* #property-read SomeClass $something
* #property-read SomeotherClass $someOtherThing
* #property-read string $someString
* #property-read \Slim\Views\PhpRenderer $renderer
*/
class OurApp extends \Slim\App {}
$app = new OurApp($settings);
And that works nicely. This way you can start typing $app-> and get the completion for whatever is in the standard Slim\App and get something, someOtherThing and someString, etc. For our project we changed to a bunch of values in the $container
items Dependency.php that we needed to access.
Related
I have been using the SymfonyCast on Symfony 4 to try and help me to connect the pieces. I have learned a lot about doctrine Symfony and twig and the environment that they are supposed to work together in. However, that is not what I have to build on. I need to build inside an existing project OpenEMR.
I posted my project code here:
https://github.com/juggernautsei/symfony_twig_doctrine_component/blob/master/library/financialreports/src/FinancialSummaryByInsuranceController.php
The controller works to load the twig template. Now, I am trying to populate the controller with data from the database. I have built the entity class and the repository class. I just can't figure out what to put in this line.
public function getpaidata($insurerid)
{
$payments = $this->repository
}
To access the class in the repository. The IDE suggested the code in the repository class.
public function getInsurerPaid(ArSession $insurerId)
{
/*$insurerPaid = $this->_em->getRepository($this->_entityName)->findBy([
"payer_id" => $insurerId
]);*/
$insurerPaid = $this->findBy([
'payer_id' => $insurerId
]);
return $insurerPaid;
}
But as I am typing in out the code in the controller, the IDE PHPStorm is not suggesting anything. So, I am stuck. I have tried the suggested code here
https://symfony.com/doc/2.0/book/doctrine.html#creating-an-entity-class
https://symfonycasts.com/screencast/symfony-doctrine/repository
but nothing tells me how to access the method that is in the repository class.
UPDATE:
The getpaiddata() method is now changed to
/**
* #return Fully hydrated object.
*/
public function getpaidata($insurerid)
{
$row = $this->repository->findBy([
"payer_id" => $insurerid
]);
return $row;
}
The problem is likely how you get $this->repository. If you fetch it via the entity manager, via $this->_em->getRepository($entityName) like in the commented snippet, the return value has a type hint which tells the IDE that it is just a generic EntityRepository instead of your custom repository class.
You can install the Symfony-plugin to PhpStorm, which will give you better autocompletion if your entity has the right annotation #Entity(repositoryClass="...").
In a typical Symfony 4 application you could also just inject the correct repository, instead of the EntityManager, e.g. in your constructor:
public function __construct(PaidDataRepository $repository)
{
$this->repository = $repository;
}
From what it looks like, OpenEMR has it's own way of creating the EntityManager using Connector::instance(). So this will probably not work for you easily, unfortunately.
Another way around this would be to just place a type hint above assigning your variable:
/** #var App\Repository\PaidDataRepository $repository */
$repository = $this->_em->getRepository(PaidData::class)
or, since you have a class variable you can put a similar annotation on there.
I'm looking for a optimised usage of the Doctrine Repositories inside the Symfony 4 Controllers.
At the moment i've to build the code like this:
/** #var ArticleRepository $repository */
$repository = $this->getRepository(Article::class);
$articles = $repository->findBySearchterm($search_term);
To tell truth, i don't like this approach. If i have to use $this->getRepository(Article::class), i've to tell PHPStorm through extra annotation, that the return of that method is of type ArticleController. Otherwise PHPStorm warns me, that the called method ->findBySearchterm($search_term); is unknown.
I would like to optimise this and use the ArticleRepository directly, maybe like this: ArticleRepository::findBySearchterm($search_term);
Is there a chance to build something, to access the Repository directly without the overhead of fetching the repository? In my opinion it would also increase the readability of the code.
You can inject the repository directly into the controller method like so:
public function index(ArticleRepository $repository)
{
$articles = $repository->findBySearchterm($search_term);
// The rest of the code
}
This is done by symfony autowiring
I would like to override the default View::make() method in Laravel, which can be used to return a view-response to the user.
(I think) I've already figured out that this method is stored inside Illuminate\View\Factory.php, and I've been reading about IoC container, while trying to make it work using some similar tutorials, but it just won't work.
I've already created a file App\Lib\MyProject\Extensions\View\Factory.php, containing the following code:
<?php namespace MyProject\Extensions\View;
use Illuminate\View\Factory as OldFactory;
class Factory extends OldFactory {
public static function make() {
// My own implementation which should override the default
}
}
where the MyProject folder is autoloaded with Composer. But I don't know how to use this 'modified' version of the Factory class whenever a static method of View (in particular View::make()) is being called. Some help would be great!
Thanks!
The call to View::make is, in Laravel speak, a call to the View facade. Facades provide global static access to a service in the service container. Step 1 is lookup the actual class View points to
#File: app/config/app.php
'aliases' => array(
'View' => 'Illuminate\Support\Facades\View',
)
This means View is aliased to the class Illuminate\Support\Facades\View. If you look at the source of Illuminate\Support\Facades\View
#File: vendor/laravel/framework/src/Illuminate/Support/Facades/View.php
class View extends Facade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'view'; }
}
You can see the facade service accessor is view. This means a call to
View::make...
is (more or less) equivalent to a call to
$app = app();
$app['view']->make(...
That is, the facade provides access to the view service in the service container. To swap out a different class, all you need to do is bind a different object in as the view service. Laravel provides an extend method for doing this.
App::extend('view', function(){
$app = app();
// Next we need to grab the engine resolver instance that will be used by the
// environment. The resolver will be used by an environment to get each of
// the various engine implementations such as plain PHP or Blade engine.
$resolver = $app['view.engine.resolver'];
$finder = $app['view.finder'];
$env = new \MyProject\Extensions\View($resolver, $finder, $app['events']);
// We will also set the container instance on this view environment since the
// view composers may be classes registered in the container, which allows
// for great testable, flexible composers for the application developer.
$env->setContainer($app);
$env->share('app', $app);
return $env;
});
Notice this is more than just instantiating an object. We need to instantiate it the same way as the original view service was instantiated and bound (usually with either bind or bindShared). You can find that code here
#File: vendor\laravel\framework\src\Illuminate\View\ViewServiceProvider.php
public function registerFactory()
{
$this->app->bindShared('view', function($app)
{
// Next we need to grab the engine resolver instance that will be used by the
// environment. The resolver will be used by an environment to get each of
// the various engine implementations such as plain PHP or Blade engine.
$resolver = $app['view.engine.resolver'];
$finder = $app['view.finder'];
$env = new Factory($resolver, $finder, $app['events']);
// We will also set the container instance on this view environment since the
// view composers may be classes registered in the container, which allows
// for great testable, flexible composers for the application developer.
$env->setContainer($app);
$env->share('app', $app);
return $env;
});
}
You can test if your binding worked with code like this
var_dump(get_class(app()['view']));
You should see your class name. Once your sure the binding has "taken", you're free to redefine whatever methods you want.
after learning procedural I'm trying to learning OOP in PHP, and after studying some theory I'm trying to apply it studing the use of the Laravel framework.
I've found on my book this part of code, for routing, but I can't really understand it in OOP.
If I'm not wrong, the first part seems to me a static method of a 'Route' class, but then I find the second part
'->where('id','[0-9]+');' that seems dynamic and relative to an instance and is confusing me.
Can someone please help me understanding?
Route::get('cats/{id}', function($id){
return "Cat #$id";
})->where('id', '[0-9]+');
If I'm not wrong, the first part seems to me a static method of a 'Route' class, Sorry but you are wrong here. Actually Laravel provides Facade class for each component and here Route is a Facade of underlying Router class. This is how that Facade class looks like:
<?php namespace Illuminate\Support\Facades;
/**
* #see \Illuminate\Routing\Router
*/
class Route extends Facade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'router'; }
}
You may noticed that, it contains only one method and it returns the original/underlying class name that contains the method, actually it's the key name using which the class is added into the IoC container. So, Laravel behind the scene, makes an instance of that Illuminate/Routing/Router.php class from the IoC container and calls the method, it's may looks unclear to you but it's a little tricky and it's not possible to answer in more details here but you may visit Laravel facade and get a better explanation of it.
So, finally, Laravel calls get() method from the Router.php class and it returns an instance of Route class/object and the where method of Route class then get called using method chaining (PHP-5 feature), that's all. Read the source code of classes, you'll get a better idea.
Check the Illuminate\Support\Facades folder, you can find so many facade classes which are actually a wrapper over it's original class/component. Also check out the IoC container in Laravel's documentation, it's necessary to get a clear idea of it to work with Laravel framework.
I would like to extend Laravels Router class (Illuminate\Routing\Router) to add a method I need a lot in my application.
But sadly I can't get this to work. I already extended other classes successfully so I really have no idea where my wrong thinking comes from.
Anyway, right into the code:
<?php
namespace MyApp\Extensions;
use Illuminate\Routing\Router as IlluminateRouter;
class Router extends IlluminateRouter
{
public function test()
{
$route = $this->getCurrentRoute();
return $route->getParameter('test');
}
}
So as you see I want to get the parameter set by {test} in routes.php with a simple call like:
Router::test();
Not sure how to go on now. Tried to bind it to the IOC-Container within my ServiceProvider in register() and boot() but I got no luck.
Whatever I try I get either a constructor error or something else.
All solutions I found are too old and the API has changed since then.
Please help me!
edit:
I already tried binding my own Router within register() and boot() (as said above) but it doesn't work.
Here is my code:
<?php
namespace MyApp;
use Illuminate\Support\ServiceProvider;
use MyApp\Extensions\Router;
class MyAppServiceProvider extends ServiceProvider {
public function register()
{
$this->app['router'] = $this->app->share(function($app)
{
return new Router(new Illuminate\Events\Dispatcher);
}
// Other bindings ...
}
}
When I try to use my Router now I have the problem that it needs an Dispatcher.
So I have to do:
$router = new Router(new Illuminate\Events\Dispatcher); // Else I get an exception :(
Also it simply does nothing, if I call:
$router->test();
:(
And if I call
dd($router->test());
I get NULL
Look at: app/config/app.php and in the aliases array. You will see Route is an alias for the illuminate router via a facade class.
If you look at the facade class in Support/Facades/Route.php of illuminate source, you will see that it uses $app['router'].
Unlike a lot of service providers in laravel, the router is hard coded and cannot be swapped out without a lot of work rewiring laravel or editing the vendor source (both are not a good idea). You can see its hardcoded by going to Illuminate / Foundation / Application.php and searching for RoutingServiceProvider.
However, there's no reason i can think of that would stop you overriding the router class in a service provider. So if you create a service provider for your custom router, which binds to $app['router'], that should replace the default router with your own router.
I wouldn't expect any issues to arise from this method, as the providers should be loaded before any routing is done. So overriding the router, should happen before laravel starts to use the router class, but i've not this before, so be prepared for a bit of debugging if it doesn't work straight away.
So I was asking in the official Laravel IRC and it seems like you simply can't extend Router in 4.1 anymore. At least that's all I got as a response in a pretty long dialogue.
It worked in Laravel 4.0, but now it doesn't. Oh well, maybe it will work in 4.2 again.
Other packages suffer from this as well: https://github.com/jasonlewis/enhanced-router/issues/16
Anyway, personally I'll stick with my extended Request then. It's not that much of a difference, just that Router would've been more dynamic and better fitting.
I'm using Laravel 4.2, and the router is really hard coded into the Application, but I extended it this way:
Edit bootstrap/start.php, change Illuminate\Foundation\Application for YourNamespace\Application.
Create a class named YourNamespace\Application and extend \Illuminate\Foundation\Application.
class Application extends \Illuminate\Foundation\Application {
/**
* Register the routing service provider.
*
* #return void
*/
protected function registerRoutingProvider()
{
$this->register(new RoutingServiceProvider($this));
}
}
Create a class named YourNamespace\RoutingServiceProvider and extend \Illuminate\Routing\RoutingServiceProvider.
class RoutingServiceProvider extends \Illuminate\Routing\RoutingServiceProvider {
protected function registerRouter()
{
$this->app['router'] = $this->app->share(function($app)
{
$router = new Router($app['events'], $app);
// If the current application environment is "testing", we will disable the
// routing filters, since they can be tested independently of the routes
// and just get in the way of our typical controller testing concerns.
if ($app['env'] == 'testing')
{
$router->disableFilters();
}
return $router;
});
}
}
Finally, create YourNamespace\Router extending \Illuminate\Routing\Router and you're done.
NOTE: Although you're not changing the name of the class, like Router and RoutingServiceProvider, it will work because of the namespace resolution that will point it to YourNamespace\Router and so on.