In fatfree framework,the route can do such things instead of writing lines of codes in callback function
$f3->route('GET /about','WebPage->display');
Where WebPage->display is class method and the class will be instanced before the method is invoked,I'm thinking of ways to do the same in slim framework but I don't have a clue while reading their doc on their official website,can you help me?
You are correct, as of the time of writing there is still nothing I can find in the docs about mapping routes to classes in the Slim Framework.
But the functionality does exist, since version 2.4.0 (November 2013). It's called "Class Controllers".
The link above provides a simple example, but I will provide one as well.
You can use classes as controller class instances, as callbacks for your Slim app routes (and their parameters):
$app->get('/user/:id/', '\User:find');
This will call the find method of the User class and pass the value if the :id parameter as the first parameter to the find method. So your callback class might look something like this:
class User {
public function find($theId) {
// Do something with $theId...
}
}
It is unfortunate that this useful Slim Framework feature has not found its way to the official documentation (yet).
Related
I created a simple test for my new Laravel 7 application. But when I run php artisan test I get the following error.
Target [Illuminate\Contracts\View\Factory] is not instantiable.
The error doesn't appear when I go to the page in the browser.
$controller = new HomeController();
$request = Request::create('/', 'GET');
$response = $controller->home($request);
$this->assertEquals(200, $response->getStatusCode());
Although "Just write feature tests" may seem like a cop-out ("They're not unit tests!"), it is sound advice if you do not want to get bogged down by framework-specific knowledge.
You see, this is one of those problems that come from using facades, globals, or static methods. All sorts of things happen outside of your code (and thus your test code) in order for things to work.
The problem
To understand what is going on, you first need to know how Laravel utilizes Containers and Factories in order to glue things together.
Next, what happens is:
Your code (in HomeController::home() calls view() somewhere.
view() calls app() to get the factory that creates Views1
app() calls Container::make
Container::make calls Container::resolve1
Container::resolve decides the Factory needs to be built and calls Container::build to do so
Finally Container::build (using PHP's ReflectionClass figures out that \Illuminate\Contracts\View\Factory can not be Instantiated (as it is an interface) and triggers the error you see.
Or, if you're more of a visual thinker:
The reason that the error is triggered is that the framework expects the container to be configured so that a concrete class is known for abstracts (such as interfaces).
The solution
So now we know what is going on, and we want to create a unit-test, what can we do?
One solution might seem to not use view. Just inject the View class yourself! But if you try to do this, you'll quickly find yourself going down a path that will lead to basically recreating loads of framework code in userland. So not such a good idea.
A better solution would be to mock view() (Now it is really a unit!). But that will still require recreating framework code, only, within the test code. Still not that good.[3]
The easiest thing is to simply configure the Container and tell it which class to use. At this point, you could even mock the View class!
Now, purists might complain that this is not "unit" enough, as your tests will still be calling "real" code outside of the code-under-test, but I disagree...
You are using a framework, so use the framework! If your code uses glue provided by the framework, it makes sense for the test to mirror this behavior. As long as you don't call non-glue code, you'll be fine![4]
So, finally, to give you an idea of how this can be done, an example!
The example
Lets say you have a controller that looks a bit like this:
namespace App\Http\Controllers;
class HomeController extends \Illuminate\Routing\Controller
{
public function home()
{
/* ... */
return view('my.view');
}
}
Then your test[5] might look thus:
namespace Tests\Unit\app\Http\Controllers;
use App\Http\Controllers\HomeController;
use Illuminate\Contracts\View\Factory;
class HomeControllerTest extends \PHPUnit\Framework\TestCase
{
public function testHome()
{
/*/ Arange /*/
$mockFactory = $this->createMock(Factory::class);
app()->instance(Factory::class, $mockFactory);
/*/ Assert /*/
$mockFactory->expects(self::once())
->method('make')
->with('my.view')
;
/*/ Act /*/
(new HomeController())->home();
}
}
A more complex example would be to also create a mock View and have that be returned by the mock factory, but I'll leave that as an exercise to the reader.
Footnotes
app() is asked for the interface Illuminate\Contracts\View\Factory, it is not passed a concrete class name
The reason Container::make does nothing other than call another function is that the method name make is defined by PSR-11 and the Laravel container is PSR compliant.
Also, the Feature test logic provided by Laravel already does all of this for you...
Just don't forget to annotate the test with #uses for the glue that is needed, to avoid warnings when PHPUnit is set to strict mode regarding "risky" tests.
Using a variation of the "Arrange, Act, Assert" pattern
This is not how you test endpoints in Laravel. You should let Laravel instantiate the application as it is already setup in the project, the examples you can see here.
What you already wrote can be rewritten to something like this.
$response = $this->call('GET', route('home')); // insert the correct route
$response->assertOk(); // should be 200
For the test to work, you should extend the TestCase.php, that is located in your test folder.
If you're finding this in The Future and you see #Pothcera's wall of text, here's what you need to know:
The ONLY reason he's doing any of that and the ONLY reason you're seeing this in the first place in a Unit test is because he and you haven't changed from PHPUnit\Framework\TestCase to Tests\TestCase in the test file. This exception doesn't exist when you extend the test case that includes app().
My advice would be to simply extend the correct base test case and move on with your life.
Note: as of version 2.8, Symfony provided autowire: true for service configuration, and as of version 3.3, Symfony provided alias (instead of autowire_types) to alias a concrete object to an interface for automatic dependency injection into 'controllers as services'. There's also a bundle to allow autowiring for controller 'action' methods, although I've moved away from this and have focussed more on a variation of the ADR pattern (which is, basically, a single 'action' class with an interface method and not shoving a load of actions methods within a single class which eventually makes for an architectural nightmare). This is, effectively, what I've been looking for all these years and now no longer need to 'hook-in' a decent recursive dependency injector (auryn) as the framework now handles what it should have four years previous. I'll leave this answer here in case anyone wants to trace the steps that I did to see how the kernel works and some of the options available at this level.
Note: Although this question primarily targets Symfony 3, it should also be relevant to users of Symfony 2 as the kernel logic doesn't seem to have changed much.
I want to change how controllers are instantiated in Symfony. The logic for their instantiation currently resides in HttpKernel::handle and, more specifically, HttpKernel::handleRaw. I want to replace call_user_func_array($controller, $arguments) with my own injector performing that specific line instead.
The options I have tried thus far:
Extending HttpKernel::handle with my own method and then having this called by symfony
http_kernel:
class: AppBundle\HttpKernel
arguments: ['#event_dispatcher', '#controller_resolver', '#request_stack']
The downside of this is that, because handleRaw is private, I can't extend it without hacky reflection and so I would have to copy and paste a tonne of code.
Creating and registering a new controller resolver
controller_resolver:
class: AppBundle\ControllerResolver
arguments: []
This was a fundamental misunderstanding I had so I thought I'd document it here. The resolver's job is to resolve where to find the controller as a callable. It hasn't actually been called yet. I am more than happy with how Symfony takes the routes from routes.yml and figures out the class and method to call for the controller as a callable.
Adding an event listener on kernel.request
kernel.request:
class: MyCustomRequestListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 33 /** Important, we'll get to why in a minute **/ }
Taking a look at the Http Kernel Component Documentation, we can see that it has the following typical purpose:
To add more information to the Request, initialise parts of the system, or return a Response if possible (e.g. a security layer that denies access).
I figured that by creating a new listener, using my custom injector to create my controller, and then return a response in that listener, would bypass the rest of the code that instantiates the controller. This is what I want! But there's a major flaw with this:
The Symfony Profiler doesn't show up or any of that stuff, it's just my response and that's it. Dead. I found that I can switch the priority from 31 to 33 and have it switch between my code and Symfonys, and I believe this is because of the router listener priority. I feel I'm going down the wrong path here.
Listening on the kernel.controller event.
No, this allows me to change the callable that will be called by call_user_func_array(), not how the controller is actually instantiated, which is my goal.
I've documented my ideas but I'm out. How can I achieve the following?
Change how the controllers are instantiated and then executed, specifically call_user_func_array() which is in a bloody private method (thanks Symfony)
Fall back to the default controller instantiation if mine doesn't work
Allow everything else to work as expected, like the profiler loading
Be able to bundle this up with an extension for other users
Why do I want to do this?
Controllers can have many different methods for different circumstances and each method should be able to typehint for what it individually requires rather than having a constructor take all the things, some of which may not even be used depending on the controller method being executed. Controllers don't really adhere to the Single Responsibility Principle, and they're an 'object edge case'. But they are what they are.
I want to replace how controllers are created with my own recursively autowiring injector, and also how they are executed, again with recursive introspection via my injector, as the default Symfony package does not seem to have this functionality. Even with the latest "autowire" service option in Symfony 2.8+.
The controller resolver actually does two things. The first is to get the controller. The second is to get a list of arguments for a given action.
$arguments = $this->resolver->getArguments($request, $controller);
$response = call_user_func_array($controller, $arguments);
It is the getArguments method that you could override to implement your special "action method injection" functionality. You just need to determine what arguments the action method needs and return an array of them.
Based on a different question, I also think you might be misunderstanding the autowire functionality. Autowire really only applies to constructor injection. It's not going to help with action method injection.
If the getArguments does not solve your requirement then overriding the handle method is really your only option. Yes there is quite a bit of code to copy/paste from handleRaw but that is because there is quite a bit to do in there. And even if handleRaw was protected you would still have to copy/paste the code just to get at the one line you want to replace.
Why don't you return your own callable from custom ControllerResolverInterface that would instantiate Controller in a way you want and call it?
It would be basically a decorator.
You can extend Symfony\Component\HttpKernel\Controller\ControllerResolver with your own implementation of instantiateController() method, or you can implement ControllerResolverInterface from the scratch.
UPD:
When Symfony makes a call_user_func_array($controller, $arguments); call in handleRaw(), the $controller variable is what you've returned from your custom ControllerResolver. That means you can return any callable from your resolver (it can be [$this, "callController"] f.e.) and inside this callable you would create a new Controller with Auryn and call it.
UPD2:
If you're still struggling with this, I'll add an example because you might miss what I meant here.
use Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver;
class AutowiringControllerResolver extends ControllerResolver
{
// ... constructor stuff; assume $injector is a part of this class
protected function createController($controller)
{
$controller = parent::createController($controller);
return function (...$arguments) use ($controller) {
// you can do with resolved $arguments whatever you want
// or you can override getArguments() method and return
// empty array to discard getArguments() functionality completely
return $this->injector->execute($controller);
};
}
protected function instantiateController($classname)
{
return $this->injector->make($classname);
}
}
Listener is too late to solve your needs, since it excludes Dependency Injection container, which is crucial to create a valid object (~= service).
You are probably looking for Controller Autowiring feature.
If that's so, you might find solution or at least inspiration in this bundle: http://www.tomasvotruba.cz/blog/2016/03/10/autowired-controllers-as-services-for-lazy-people/
It meets your needs in these points:
autowire injector
fallback to default (FrameworkBundle's) controller resolver, if not found
it also should keep all the flow working, since there are no hacks during controller resolving process
I'm confused about an aspect of OOP/Laravel.
I'm following an OOP tutorial (nothing to do with Laravel) that states that you can only use the Scope Resolution Operator when the method you are calling is static.
So I created a quick script;
class Student {
function welcome_students($var="Hello") {
echo "{$var} students.";
}
}
echo Student::welcome_students("Greetings") ."<br />";
And I get the error;
Strict Standards: Non-static method Student::welcome_students() should
not be called statically in /static_modifier.php on line 11 Greetings students.
But in Laravel 5, I've noticed that I've been using calls like
`ClassName::whereIn($var = `
in quite a few of my Controllers. I've checked the package where the whereIn method is stored and it's not static. It's just a public function.
So how is Laravel 5 allowing me to get away with it? I'm in development mode so I don't know why I'm not seeing that same message.
There are some fairly advanced concepts here that need to be understood in order to fully grasp how this is happening.
First, this would only work for the facades in Laravel. These can be found in the app.php config file in the aliases array. Each of these facades can be thought of as entry points for their real classes which are in the Laravel core. So even though the syntax is telling you that you are calling static methods, what's really happening is Laravel is resolving the underlying classes to these proxy classes and calling methods non-statically on those.
You can see this better if you go to some of those facade classes where you will see the methods you are calling are not actually present on those classes.
To really understand how this is happening, read up on Laravel's inversion of control container (IoC), its use of Facades, and the php magic method __callStatic, and the php method class_alias which is what Laravel is using to setup the aliases.
Again, these are fairly complex concepts so don't get discouraged if they seem confusing or the purpose eludes you.
Basically, the workflow looks like this...
You call Config::get()
Laravel looks up the alias for Config which is a facade.
Using the __callStatic magic method, the facade figures out the underlying class to instantiate and calls the appropriate method on that non-statically.
http://laravel.com/docs/5.0/facades#explanation
http://laravel.com/docs/4.2/ioc#introduction
http://php.net/manual/en/language.oop5.overloading.php#object.callstatic
First of all, your function is not static, you need change it if you want to call it like Student::welcome_students()
public static function welcome_students($var="Hello") {
echo "{$var} students.";
}
I have a route group with 6 routes inside it. I check the parameters on every route against the database.
What's the best way to achieve this? Build a class for it?
When working with a database, it really depends on how much you will be using it. Slim has no database integration, so you could access the database by either using basic php or using a ORM tool to assist you in talking with your database.
One orm that is quick and easy to use is idiorm http://idiorm.readthedocs.org/en/latest/index.html
There are other more robust things out there and it all depends on what you are trying to accomplish. Really, to give a "best" solution, we would need more information on the situation and also how you would quantify "best."
Try use Slim Middleware.
Add your check part as one middleware, and share a middleware for all routes. Then you will be happy.
Depending on your needs, you can also try using class instances as route callbacks.
Define a base class with all the database functionality and use "Class Controllers" available since version 2.4.0 (November 2013).
class Base {
// Define all your shared DB methods and properties here
}
Use child classes of your base class as controller class instances, as callbacks for your Slim app routes (and their parameters):
$app->get('/user/:id/', '\Base\User:find');
Getting an instance of $app in the callback class instance is easy enough:
class User extends \Base {
public function find($theUserId) {
// ...
// You can easily get access to $app here, too:
// $app = \Slim\Slim::getInstance();
}
}
Hello and thanks for reading.
I'll get straight to the point: I have a website project that I've been building using CodeIgniter 1.7.3, which I have thoroughly enjoyed using, however I have been contemplating upgrading to CI 2.0+.
I tried a straight copy, just moved my folders for controllers, models and views over to a CI 2.0 framework, but I got a 500 server error when I tried to view my pages.
After doing some investigation I discovered that all of your controllers must now use "CI_Controller" as their parent class. Also I noticed that if you want to include a constructor in your controller class that it must use the syntax "function __construct()" as its name and of the parent class. It seems that CI 2.0+ no longer supports using a constructor that has the same name as the class name, e.g. "class Blogs extends CI_controller{ function Blogs(){parent::__construct();}}" is no longer supported?
I've been reading the CI Change Log, but all I see are bug fixes, and new features, nothing about compatibility issues with older versions of CI?
Does anyone else know of any other secret little pitfalls?
Thanks,
H
CI 2.x removed all compatibility with PHP4 and also updated a number of standards to be compliant with PHP 5.3 going forward. One of these is the constructor issue you have encountered. As of PHP 5.3, the function ClassName() is no longer the constructor for a class, it is simply another function. You must explicitly declare a __construct function to perform any tasks that need to be done when a new instance of the class is created. Given this, you should see it no longer makes sense to call parent::ClassName() in your child constructor as that function would no longer be the parent's constructor.
Another pitfall I recently had to work out is how the $_GET array is now handled. In the 1.x versions, you could use query strings to pass back extra information and still use URI segments to route to controllers and functions. This is especially useful for AJAX calls where you may not always know all the parameters being sent to and from the server in a particular request. In the 2.x versions, the config.php file contains a new option, $config['allow_get_array']. This must be set to TRUE if you want to use query strings otherwise the input class clears out the $_GET array as part of CI's initialization routine on each request.
Something which isn't a pitfall but you may find useful is the new options in config/autoload.php that allows you to add new application directories to your project. If you work on a number of different projects with CI and want to keep any useful libraries you write in a single location, you can now add that location to $autoload['packages']. CI expects any path in this array to contain the sub-directories "controllers", "models", "libraries" and "helpers". It won't complain if you don't have those directories but you will at least need them for anything you intend to load, i.e. libraries would live in /libraries as with the main application folder.
Have you read the official guide for upgrading from 1.7.x to 2.x ?
so in short
Update Models and Controllers to
extend CI_Model and CI_Controller
Update Parent Constructor calls
class Wow extends CI_Controller {
function __construct()
{
parent::__construct();
//your stuff
}
function index()
{
// just for example
}
}