Symfony couldn't autowire Response service into controller method - php

Trying to inject Symfony\Component\HttpFoundation\Response into controller method
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\Routing\Annotation\Route;
#[AsController]
class TestController extends AbstractController
{
#[Route(path: 'test', name: 'test', methods: ['GET'])]
public function test(Request $request, Response $response) : Response
{
return $response->setContent("Hello!");
}
}
But get an error
Could not resolve argument $response of "App\Controller\TestController::test()", maybe you forgot
to register the controller as a service or missed tagging it with the controller.service_arguments;?
(500 Internal Server Error)
Debug php bin/console debug:autowiring --all shows, that container have Response
...
Request represents an HTTP request.
Symfony\Component\HttpFoundation\Request
Response represents an HTTP response.
Symfony\Component\HttpFoundation\Response
...
Why request injected, but Response not?

There is no need to inject a response. You should create your own instance:
#[AsController]
class TestController extends AbstractController
{
#[Route(path: 'test', name: 'test', methods: ['GET'])]
public function test(Request $request) : Response
{
$response = new Response();
return $response->setContent("Hello!");
}
}

Related

Lumen Illuminate Http Client Request unresolvable dependency

I'm trying to store a data via POST method using Laravel\Lumen with Eloquent.
The database I'm using is MySQL.
Heres my CategoriesController.php:
<?php
namespace App\Http\Controllers;
use App\Category;
use Illuminate\Http\Client\Request;
class CategoriesController extends Controller
{
public function index()
{
return Category::all();
}
public function create(Request $request)
{
return response()
->json(
Category::create(['description' => $request->description]),
201
);
}
}
Here's my CategoryModel.php:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
protected $fillable = ['description'];
public function expense()
{
return $this->belongsTo(Expense::class);
}
}
In my App.php file I've uncommented the lines:
$app->withFacades();
$app->withEloquent();
My Web.php file:
$router->group(['prefix' => 'api'], function ($router){
$router->get('categories', 'CategoriesController#index');
$router->post('categories', 'CategoriesController#create');
});
Via Postman I'm sending the following post request:
{
"description" : "teste"
}
After the request was sented I'm having the 500 HTTP Status Response.
The error contains the following StackTrace:
BindingResolutionException HTTP 500 Internal Server Error
in C:\Fontes\PHP\budget-manager\vendor\illuminate\container\Container.php (line 1053)
*/ protected function unresolvablePrimitive(ReflectionParameter $parameter)
{
$message = "Unresolvable dependency resolving [$parameter] in class {$parameter->getDeclaringClass()->getName()}";
throw new BindingResolutionException($message);
}
I found the problem.
my VSCode was importing automatically the use Illuminate\Http\Client\Request dependency.
After a hard time dedicated to find what was wrong I figured that the correct dependency it's use Illuminate\Http\Request;.
Hope that this solution works for those who's having the same problems.

How to properly call laravel's middleware closure in phpunit?

How to properly call this middleware closure function in phpunit test so that $user will be set?
Auth::user is mocked and will receive the proper user object, but the function is not called when creating the controller instance.
Here is my code:
use App\Http\Controllers\Controller;
class CustomController extends Controller
{
private $user;
public function __construct()
{
$this->middleware(function ($request, $next) {
$this->user = Auth::user();
return $next($request);
});
}
}
Phpunit:
use Illuminate\Foundation\Testing\TestCase;
use Illuminate\Support\Facades\Auth;
use App\Models\User;
use App\Controllers\CustomController;
class CustomControllerTest extends TestCase
{
use CreatesApplication;
private $customController;
public function setUp()
{
Auth::shouldReceive('user')->andReturn(new User());
$this->customController = $this->app->make(CustomController::class);
}
}
You are resolving the controller out of the container, but then you aren't passing it any requests to handle. Your middleware code will only be executed when the controller is given something to handle.
As such, $this->middleware(...) simply says "execute this middleware when a request is sent through the pipeline".
Simply test a standard request hitting your endpoint, and then assert what you need to assert.
$this->get('your_endpoint');

Yii2: Web service Request and Response Logger

I am trying to create a Request and Response Logger for a web service created in Yii 2.0.5 for debugging purpose.
Basically my motive is to track all the request, request data, response data and for this purpose I am using Yii Events. So far I have coded something like this:
UserController
use yii\rest\ActiveController;
use yii\base\Event;
use yii\web\Response;
Event::on(ActiveController::className(), ActiveController::EVENT_AFTER_ACTION, ['app\models\LogHandler', 'saveRequest'], ['request' => Yii::$app->request->getRawBody(), 'response' => Yii::$app->response->content]);
class UserController extends ActiveController
{
public $modelClass = 'app\models\User';
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['contentNegotiator']['formats']['text/html'] = Response::FORMAT_XML;
return $behaviors;
}
// POST demo
public function actionDemo()
{
$data = array('status' => 200, 'message' => 'Success');
return $data;
}
}
In the above code if you have noticed then I have used the Class level Event handler (doc). Here I am trying to capture the Controllers EVENT_AFTER_ACTION event and passing the Request & Response object to my LogHandler's static method. However in my handler I am able to get the request's raw body but I am not able to get the response data which the actionDemo() is returning.
LogHandler
namespace app\models;
use yii\db\ActiveRecord;
class LogHandler extends ActiveRecord
{
public static function tableName()
{
return 'request_log';
}
public static function saveRequest($event)
{
// self::load($event);
// self::save();
var_dump($event);
}
}
How do I get the response data as well...
According to the Yii guide here, the best way to create a logger component is to override the class yii\log\Target. Then to send the logs, you need to override the abstract method export() of this class
Since this class is accessed after the life cycle of the request, you will have the request and response data Yii::$app->request and Yii::$app->response and you can access them to create whatever message you want. This is an example of the LogHandler class (this can be edited to include whatever details you want about the request and response)
namespace app\models;
use yii\db\ActiveRecord;
class LogHandler extends ActiveRecord {
$requestPath;
$responseBody;
public static function tableName() {
return 'request_log';
}
}
And an example of a logger class is as follows:
namespace app\components\Logs;
use yii\helpers\VarDumper;
use yii\log\Target;
use Yii;
class LoggerComponent extends Target {
public function export() {
$logMessage = new LogHandler();
$logMessage->requestPath = VarDumper::export(Yii::$app->request->absoluteUrl);
$logMessage->responseBody = VarDumper::export(Yii::$app->response->data);
$logMessage->save();
}
}

Laravel 5: Controller Testing - PHPUnit returns green

the following simple controller test makes a 'GET' request to the PostsController#index action:
<?php
class PostsControllerTest extends TestCase {
public function testIndex()
{
$response = $this->action('GET', 'PostsController#index');
}
}
In my understanding, if the index method does not exist in my controller, I shouldn't get the green light, when calling phpunit in my command line.
Yet my controller looks like this:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
class PostsController extends Controller
{
/**
* Display a listing of the resource.
*
* #return Response
*/
// public function index()
// {
// //
// return 'Posts Index';
//}
}
As you can clearly see the index method is commented out and I still get this:
**OK (1 test, 0 assertions)**
Any suggestions?
You aren't making any assertions. Your test isn't checking if $response is "OK".
Change your test to this:
public function testIndex()
{
$response = $this->action('GET', 'PostsController#index');
$this->assertEquals(200, $response->status());
}
This test asserts that the page responded with a 200 status code, which means it was successful.
You can read up on Laravel's testing here.

Extend Request class in Laravel 5

I'm new to Laravel (only experienced Laravel 5, so no legacy hang up here)
I'd like to know how to extend the core Request class. In addition to how to extend it, i'd like to know if it's a wise design decision to do so.
I've read through the documentation extensively (especially with regards to registering service providers and the manner in which it provides Facades access to entries within the dependency container) - but I can see (and find) no way to replace the \Illuminate\Http\Request instance with my own
Here is Official Document: Request Lifecycle
Content of app/Http/CustomRequest.php
<?php namespace App\Http;
use Illuminate\Http\Request as BaseRequest;
class CustomRequest extends BaseRequest {
// coding
}
add this line to public/index.php
$app->alias('request', 'App\Http\CustomRequest');
after
app = require_once __DIR__.'/../bootstrap/app.php';
change the code at public/index.php
Illuminate\Http\Request::capture()
to
App\Http\CustomRequest::capture()
I was working on the same issue today and I think it's worth mention that you may just change
Illuminate\Http\Request::capture()
to
App\Http\CustomRequest::capture()
without adding line
$app->alias('request', 'App\Http\CustomRequest');
because inside capture() method laravel actually binds provided class to service container with 'request' as a key
I guess you will have to extend also RequestForm. I use trait to avoid code duplication. Code below is relevant for Laravel 5.3.
app/Http/ExtendRequestTrait.php
<?php
namespace App\Http\ExtendRequestTrait;
trait ExtendRequestTrait {
methodFoo(){}
methodBar(){}
}
app/Http/Request.php
<?php
namespace App\Http;
use Illuminate\Http\Request as BaseRequest;
class Request extend BasicRequest {
use ExtendRequestTrait;
}
app/Http/FormRequest.php
<?php
namespace App\Http;
use Illuminate\Foundation\Http\FormRequest as BaseFormRequest;
class FormRequest extend BasicFormRequest {
use ExtendRequestTrait;
}
For phpunit test working you will have to override call method to make it using right Request class here Request::create.
test/TestCase.php
<?php
use App\Http\Request;
abstract class TestCase extends Illuminate\Foundation\Testing\TestCase{
// just copy Illuminate\Foundation\Testing\TestCase `call` method
// and set right Request class
public function call($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
{
$kernel = $this->app->make('Illuminate\Contracts\Http\Kernel');
$this->currentUri = $this->prepareUrlForRequest($uri);
$this->resetPageContext();
$request = Request::create(
$this->currentUri, $method, $parameters,
$cookies, $files,
array_replace($this->serverVariables, $server),
$content
);
$response = $kernel->handle($request);
$kernel->terminate($request, $response);
return $this->response = $response;
}
}
and don't forget to switch Illuminate\Http\Request::capture() to App\Http\Request::capture() in public/index.php file and to add $app->alias('request', 'App\Http\Request'); after or inside $app = require_once __DIR__.'/../bootstrap/app.php';
Yerkes answer inspired me to write a custom class, for use with pagination, but only on specific requests
<?php
namespace App\Http\Requests;
use Illuminate\Http\Request;
class PaginatedRequest extends Request
{
public function page(): int
{
return max(1, (int) ($this['page'] ?? 1));
}
public function perPage(): int
{
$perPage = (int) ($this['per_page'] ?? 100);
return max(1, min($perPage, 500));
}
public function offset(): int
{
return ($this->page() - 1) * $this->perPage();
}
}
I then also had to register a new ServiceProvider in /config/app.php, which looks like
<?php
namespace App\Providers;
use App\Http\Requests\PaginatedRequest;
use Illuminate\Support\ServiceProvider;
class PaginatedRequestServiceProvider extends ServiceProvider
{
public function boot()
{
$this->app->resolving(PaginatedRequest::class, function ($request, $app) {
PaginatedRequest::createFrom($app['request'], $request);
});
}
}
Now I can simply inject the PaginatedRequest in my controller methods only when I need it
<?php
namespace App\Http\Controllers;
use App\Http\Requests\PaginatedRequest;
class MyController
{
public function __invoke(PaginatedRequest $request)
{
$request->page();
// ...
}
}
I was able to add custom request object using FormRequest in Laravel 5.5 as follows.
First, just create FormRequest:
php artisan make:request MyRequest
Then just make it look like this:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class MyRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
//
];
}
}
You can then use MyRequest as drop-in replacement in any function that takes Request as parameter:
public function get(MyRequest $request)
{
}
I do realize that FormRequests are actually meant to be used for a different purpose, but whatever works.
Documentation on FormRequest: https://laravel.com/docs/5.0/validation#form-request-validation
In Laravel 5.5.x, I was able to extend the Request class for specific requests using the following:
ExtendedRequest class
<?php declare(strict_types = 1);
namespace App\Http\Request;
use Illuminate\Http\Request;
class ExtendedRequest extends Request {
public function hello() {
echo 'hello world!';
}
}
ExtendedRequestServiceProvider
<?php declare(strict_types = 1);
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Http\Request\ExtendedRequest;
class ExtendedRequestServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(ExtendedRequest::class, function ($app) {
return ExtendedRequest::capture();
});
}
}
Then in your Controllers
<?php
namespace App\Controllers;
use App\Http\Request\ExtendedRequest;
class TestController extends Controller
{
public function get(ExtendedRequest $request) {
echo $request->hello();
}
}
Hope this helps someone.

Categories