I'm a beginner in Symfony & have wasted hours in this problem.
I am trying to call a function isLoggedIn from another function of same class. In the isLoggedIn function, I need to get/check a session variable and return the value of that variable. But the Request $request object is somehow not accessible in the isLoggedIn function, while the Request object works well in other functions of the same class.
My code:
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use AppBundle\Entity\Users;
class DefaultController extends Controller
{
/**
* #Route("/", name="homepage")
*/
public function indexAction(Request $request)
{
$Authuser = $this->isLoggedIn();
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->getParameter('kernel.project_dir')).DIRECTORY_SEPARATOR,
'Authuser' => $Authuser
]);
}
public function isLoggedIn(Request $request) ////GETTING ERROR IN THIS LINE////
{
$session = $request->getSession();
if($session->get('Authuser')!=null) $Authuser = $session->get('Authuser');
else $Authuser = null;
return $Authuser;
}
/**
* #Route("/logout", name="logout")
*/
public function logoutAction(Request $request)
{
$session = $request->getSession();
$session->remove('user_id');
return $this->redirectToRoute('homepage');
}
}
?>
Exact error that I'm getting:
Uncaught PHP Exception
Symfony\Component\Debug\Exception\ContextErrorException: "Catchable
Fatal Error: Argument 1 passed to
AppBundle\Controller\DefaultController::isLoggedIn() must be an
instance of Symfony\Component\HttpFoundation\Request, none given,
called in
/Applications/MAMP/htdocs/srfood/src/AppBundle/Controller/DefaultController.php
on line 21 and defined"
I have searched a lot but getting no solutions, moreover I need to know what according to Symfony is different in isLoggedIn function from the other functions of same class, because I tried adding a route to the function, adding Action suffix etc etc so that it behaves like rest of the functions, but I just can't understand how this function is different for symfony?! Thanks!
PS- I am using Symfony 3.3.
You can't declare Request parameters for non action functions in controller.
Try call isLoggedIn in an action with $request parameters without declare it in the definition of the function.
Hope it could help you.
Related
A lot of pieces to this so here's the meat. Code very slightly tweaked for brevity.
Extended class:
<?php
namespace App\Http;
use Illuminate\Http\Request as LaravelRequest;
class Request extends LaravelRequest
{
}
Middleware:
<?php
namespace App\Http\Middleware;
use App\Http\Request as CustomizedRequest;
use Closure;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Request;
class CustomizeRequest
{
protected $app;
protected $customizedRequest;
public function __construct(Application $app, CustomizedRequest $customizedRequest){
$this->app = $app;
$this->customizedRequest = $customizedRequest;
}
public function handle(Request $request, Closure $next){
$this->app->instance(
'request',
Request::createFrom($request, $this->customizedRequest);
);
return $next($this->customizedRequest);
}
}
Routes:
Route::get('/books1/{id}',[BookController::class, 'frontend1']);
Route::get('/books2/{id}',[BookController::class, 'frontend2']);
Controller:
<?php
namespace App\Http\Controllers;
use App\Models\Book;
class BookController extends Controller
{
public function frontend1(\Illuminate\Http\Request $request){
dump($request);
dump($request->all());
dump($request->route('id'));
return Book::all();
}
public function frontend2(\App\Http\Request $request){
dump($request);
dump($request->all());
dump($request->route('id'));
return Book::all();
}
}
The /books1/5?foo=bar and frontend1() path works. $request is populated as expected.
The /books2/5?foo=bar and frontend2() path is broken. $request has vast amounts of missing data, like it was instantiated with nothing.
Evidently if I type-hint my subclass instead of the more generic parent, it's causing some kind of broken instantiation. From an OO perspective I think this should be perfectly fine and I do specifically need my subclass being provided so prefer that type-hint. Is something deep within Laravel tripping this up? Is this some obscure PHP behavior I haven't seen before?
This is kind of tricky.
First of all, you need to be familiar with the service container and dependency injection. Here is the full doc: https://laravel.com/docs/8.x/container
When you type hint a class inside a controller method, Laravel will try to understand what it should do with it.
If nothing is registered inside the service container, it will try to make a new instance of it.
\Illuminate\Http\Request is bound as a singleton (https://laravel.com/docs/8.x/container#binding-a-singleton).
While a simple bind will return a new instance at each call, a singleton will always return the exact same instance.
Here is a quick demo:
\App\Models\User::class is a class that is not explicitly bound.
When you try to resolve it using the service container, it will not find it and will try to make a new instance:
$u1 = app(\App\Models\User::class);
// Searching \App\Models\User::class...
// Cannot find \App\Models\User::class...
// returning new \App\Models\User();
$u2 = app(\App\Models\User::class);
// same process again
$u3 = app(\App\Models\User::class);
// and again
// You can check these instances are indeed different by checking their hash:
dd(
spl_object_hash($u1), // 000000004af5213500000000220f0bc0 (52135)
spl_object_hash($u2), // 000000004af5213400000000220f0bc0 (52134)
spl_object_hash($u3) // 000000004af5213700000000220f0bc0 (52137)
);
But since \Illuminate\Http\Request::class is bound by Laravel, it follows a different path:
$r1 = app(\Illuminate\Http\Request::class);
// Searching \Illuminate\Http\Request::class...
// Found it! Bound as a singleton.
// returning new \Illuminate\Http\Request() and storing the
// instance in case it is required again later;
$r2 = app(\Illuminate\Http\Request::class);
// Searching \Illuminate\Http\Request::class...
// Found it and already called! Returning the stored instance ($r1)
$r3 = app(\Illuminate\Http\Request::class);
// Searching \Illuminate\Http\Request::class...
// Found it and already called! Returning the stored instance ($r1)
// Their hash are the same
dd(
spl_object_hash($u1), // 0000000011f522cf0000000077704cd1
spl_object_hash($u2), // 0000000011f522cf0000000077704cd1
spl_object_hash($u3) // 0000000011f522cf0000000077704cd1
);
Now, what's happening?
Under the hood, when a new request is made to your app and before hitting the controller method, Laravel will do a lot of things to prepare the \Illuminate\Http\Request instance.
For instance, it will setup the route resolver inside Illuminate\Routing\Router:
/**
* Return the response for the given route.
*
* #param \Illuminate\Http\Request $request
* #param \Illuminate\Routing\Route $route
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function runRoute(Request $request, Route $route)
{
// here
$request->setRouteResolver(function () use ($route) {
return $route;
});
//
$this->events->dispatch(new RouteMatched($route, $request));
return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}
Each time Laravel internally call a method like this:
protected function method(Request $request){
// do something to $request
}
$request is always the same instance, because it is bound as a singleton.
We are now in your controller.
public function frontend1(\Illuminate\Http\Request $request){
// Searching \Illuminate\Http\Request::class...
// Found it and already called!
// Returning the stored instance that has been prepared through all
// Laravel core classes
dump($request);
dump($request->all()); //well prepared
dump($request->route('id')); //well setup
return Book::all();
}
public function frontend2(\App\Http\Request $request){
// Searching \App\Http\Request::class...
// Cannot find \App\Http\Request::class...
// returning new \App\Http\Request();
dump($request);
dump($request->all()); //nothing
dump($request->route('id')); //empty
return Book::all();
}
If you are still here, how to solve this problem?
The easiest way is to use a FormRequest, initially designed to handle form validation, but if you return an empty rules array, you should be able to do everything you did with your custom \App\Http\Request instance:
<?php
namespace App\Http;
use Illuminate\Foundation\Http\FormRequest;
class Request extends FormRequest
{
public function rules()
{
return [];
}
}
Try again, everything should work fine, since this is a feature specially designed to replace the initial \Illuminate\Http\Request object.
The full doc is here: https://laravel.com/docs/8.x/validation#creating-form-requests
I am building a custom exception controller in Symfony 4 to overwrite the ExceptionController class included in the Twig bundle.
I am doing this as per the Symfony documentation for customizing error pages.
# config/packages/twig.yaml
twig:
exception_controller: App\Controller\Error::handleException
The reason I am using a custom exception controller is because I need to pass some additional variable to the template that are given by a custom BaseController class.
The Symfony docs mention the following about using a custom controller:
The ExceptionListener class used by the TwigBundle as a listener of the kernel.exception event creates the request that will be dispatched to your controller. In addition, your controller will be passed two parameters:
exception
A FlattenException instance created from the exception being handled.
logger
A DebugLoggerInterface instance which may be null in some circumstances.
I need the FlattenException service to determine the error code but its not clear from the docs how these parameters are passed to the custom exception controller.
Here is my custom exception controller code:
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Debug\Exception\FlattenException;
class Error extends BaseController {
protected $debug, // this is passed as a parameter from services.yaml
$code; // 404, 500, etc.
public function __construct(BaseController $Base, bool $debug) {
$this->debug = $debug;
$this->data = $Base->data;
// I'm instantiating this class explicitly here, but have tried autowiring and other variations that all give an error.
$exception = new FlattenException();
$this->code = $exception->getStatusCode(); // empty
}
public function handleException(){
$template = 'error' . $this->code . '.html.twig';
return new Response($this->renderView($template, $this->data));
}
}
From the documentation page you are linking, at the very beginning of the chapter Overriding the default template the documentation actually cross link you to the class \Symfony\Bundle\TwigBundle\Controller\ExceptionController, and this shows you how to use it.
So as per Symfony's own ExceptionController, the FlattenException is actually an argument of the action showAction:
<?php
namespace App\Controller;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
class Error extends BaseController {
protected $debug; // this is passed as a parameter from services.yaml
protected $code; // 404, 500, etc.
protected $data;
public function __construct(BaseController $base, bool $debug) {
$this->debug = $debug;
$this->data = $base->data;
}
public function showAction(Request $request, FlattenException $exception, DebugLoggerInterface $logger = null) {
// dd($exception); // uncomment me to see the exception
$template = 'error' . $exception-> getStatusCode() . '.html.twig';
return new Response($this->renderView($template, $this->data));
}
}
I am following a tutorial to write 2 classes for filtering threads in a forum application.I got this error in line
$threads = Thread::latest()->filter($filters); // in threadscontroller
Error:
Method Illuminate\Database\Query\Builder::filter does not exist.
ThreadsController with index method:
<?php
namespace App\Http\Controllers;
use App\Thread;
use App\Channel;
use App\Filters\ThreadFilters;
use Illuminate\Http\Request;
class ThreadsController extends Controller
{
public function __construct(){
$this->middleware('auth')->only('store','create');
}
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index(Channel $channel,ThreadFilters $filters)
{
$threads = Thread::latest()->filter($filters);
if($channel->exist){
$threads->where('channel_id',$channel->id);
}
$threads = $threads->get();
return view('threads.index',compact('threads'));
}
This is the abstract class Filters:
<?php
namespace App\Filters;
use Illuminate\Http\Request;
abstract class Filters{
protected $request;
protected $builder;
protected $filters = [];
public function __construct(Request $request){
$this->request = $request;
}
public function apply($builder){
$this->builder = $builder;
foreach($this->getFilters() as $filter=>$value){ //filter by,value yunus mesela.
if(method_exist($this,$filter)){
$this->$filter($value);
}
}
return $this->builder;
}
public function getFilters(){
return $this->request->intersect($this->filters);
}
}
Here ThreadFilters.php which extends filters class:
<?php
namespace App\Filters;
use App\User;
use Illuminate\Http\Request;
class ThreadFilters extends Filters
{
protected $filters =['by'];
protected function by($username){
$user = User::where('name',$username)->firstorFail();
return $this->builder->where('user_id',$user->id);
}
}
If I change latest to all, I get this error:
Type error: Argument 1 passed to
Illuminate\Support\Collection::filter() must be callable or null,
object given, called in
Also can anyone explain me what is $builder doing in those classes?
latest() is a modifier shortcut, equivalent to orderBy('created_at', 'desc'). All it does is add the ORDER BY constraint to the query.
filter() is a method on the Collection class. That method does not exist in the query builder, hence the "method not found" error you're receiving.
It does not appear that your filter class should be used with the resulting Collection. Rather, it adds conditionals to your original query. Try implementing it like this:
// Remove the filters() method here.
$threads = Thread::latest();
if ($channel->exist) {
$threads->where('channel_id', $channel->id);
}
// Pass your query builder instance to the Filters' apply() method.
$filters->apply($threads);
// Perform the query and fetch results.
$threads = $threads->get();
Also, for future questions, including the tutorial you're attempting/following can provide beneficial context to those helping you. :)
If you change latest to all, you're getting a Laravel Collection. So you are calling filter() on a Collection ($threads = Thread::all()->filter($filters);).
If you take a look into the code, you'll see, that the where() method of the array class gets called, which calls PHP's array_filter method. As you can see, a callable must be given.
But you are passing an Object to the filter method, $filters, which is an ThreadFilters-Object -> method injection here:
public function index(Channel $channel,ThreadFilters $filters) ...
Your error message answers your question in a great way:
Type error: Argument 1 passed to Illuminate\Support\Collection::filter() must be callable or null, object given, called in
I would like to create a question which has many surveys. In the questions Model:
public function surveys()
{
return $this->belongsToMany(Survey::class, 'survey__surveyquestions');
}
And in the controller when saving a new question:
private $questions;
public function __construct(QuestionsRepository $questions)
{
parent::__construct();
$this->questions = $questions;
}
public function store(Request $request)
{
$this->questions->create($request->all());
$this->questions->surveys()->attach($request->surveys);
return redirect()->route('admin.survey.questions.index')
->withSuccess(trans('core::core.messages.resource created', ['name' => trans('survey::questions.title.questions')]));
}
But I get the following error when it gets to the attach line:
(1/1) FatalErrorException Call to undefined method
Modules\Survey\Repositories\Eloquent\EloquentQuestionsRepository::surveys()
I notice the error mentions EloquentQuestionsRepository but I have added no methods in there so it's just an empty class:
class EloquentQuestionsRepository extends EloquentBaseRepository implements QuestionsRepository
{
}
QuestionRepository:
interface QuestionsRepository extends BaseRepository
{
}
As explained in the response to the main post - the constructor resolves the QuestionsRepository to instance of EloquentQuestionsRepository, which by the look of it is not what the store method needs.
What I would probably do is to make call to create method directly on the model and remove constructor all together - that is unless you need the instance of QuestionsRepository anywhere else in your controller:
public function store(Request $request)
{
$question = Question::create($request->all());
$question->surveys()->attach($request->surveys);
...
}
Also - I'm not sure passing $request->all() is the best thing to do - I'd probably use $request->only(...) or $request->all(...) specifying which items you want to get from the request rather than passing everything from the request to the create method.
On the other note - you could also use Form Request, which would validate data for your before passing it to the store method.
https://laravel.com/docs/5.5/validation#form-request-validation
I have a BaseController that provides the foundation for most HTTP methods for my API server, e.g. the store method:
BaseController.php
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
I then extend on this BaseController in a more specific controller, such as the UserController, like so:
UserController.php
class UserController extends BaseController {
public function __construct(UserRepository $repo)
{
$this->repo = $repo;
}
}
This works great. However, I now want to extend UserController to inject Laravel 5's new FormRequest class, which takes care of things like validation and authentication for the User resource. I would like to do this like so, by overwriting the store method and using Laravel's type hint dependency injection for its Form Request class.
UserController.php
public function store(UserFormRequest $request)
{
return parent::store($request);
}
Where the UserFormRequest extends from Request, which itself extends from FormRequest:
UserFormRequest.php
class UserFormRequest extends Request {
/**
* 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 [
'name' => 'required',
'email' => 'required'
];
}
}
The problem is that the BaseController requires a Illuminate\Http\Request object whereas I pass a UserFormRequest object. Therefore I get this error:
in UserController.php line 6
at HandleExceptions->handleError('2048', 'Declaration of Bloomon\Bloomapi3\Repositories\User\UserController::store() should be compatible with Bloomon\Bloomapi3\Http\Controllers\BaseController::store(Illuminate\Http\Request $request)', '/home/tom/projects/bloomon/bloomapi3/app/Repositories/User/UserController.php', '6', array('file' => '/home/tom/projects/bloomon/bloomapi3/app/Repositories/User/UserController.php')) in UserController.php line 6
So, how can I type hint inject the UserFormRequest while still adhering to the BaseController's Request requirement? I cannot force the BaseController to require a UserFormRequest, because it should work for any resource.
I could use an interface like RepositoryFormRequest in both the BaseController and the UserController, but then the problem is that Laravel no longer injects the UserFormController through its type hinting dependency injection.
In contrast to many 'real' object oriented languages, this kind of type hinting design in overridden methods is just not possible in PHP, see:
class X {}
class Y extends X {}
class A {
function a(X $x) {}
}
class B extends A {
function a(Y $y) {} // error! Methods with the same name must be compatible with the parent method, this includes the typehints
}
This produces the same kind of error as your code. I would just not put a store() method in your BaseController. If you feel that you are repeating code, consider introducing for example a service class or maybe a trait.
Using a service class
Below a solution that makes use of an extra service class. This might be overkill for your situation. But if you add more functionality to the StoringServices store() method (like validation), it could be useful. You can also add more methods to the StoringService like destroy(), update(), create(), but then you probably want to name the service differently.
class StoringService {
private $repo;
public function __construct(Repository $repo)
{
$this->repo = $repo;
}
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
}
class UserController {
// ... other code (including member variable $repo)
public function store(UserRequest $request)
{
$service = new StoringService($this->repo); // Or put this in your BaseController's constructor and make $service a member variable
return $service->store($request);
}
}
Using a trait
You can also use a trait, but you have to rename the trait's store() method then:
trait StoringTrait {
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
}
class UserController {
use {
StoringTrait::store as baseStore;
}
// ... other code (including member variable $repo)
public function store(UserRequest $request)
{
return $this->baseStore($request);
}
}
The advantage of this solution is that if you do not have to add extra functionality to the store() method, you can just use the trait without renaming and you do not have to write an extra store() method.
Using inheritance
In my opinion, inheritance is not so suitable for the kind of code reuse that you need here, at least not in PHP. But if you want to only use inheritance for this code reuse problem, give the store() method in your BaseController another name, make sure that all classes have their own store() method and call the method in the BaseController. Something like this:
BaseController.php
/**
* Store a newly created resource in storage.
*
* #return Response
*/
protected function createResource(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
UserController.php
public function store(UserFormRequest $request)
{
return $this->createResource($request);
}
You can move your logic from BaseController to trait, service, facade.
You can not override existing function and force it to use different type of argument, it would break stuff. For example, if you later would write this:
function foo(BaseController $baseController, Request $request) {
$baseController->store($request);
}
It would break with your UserController and OtherRequest because UserController expects UserController, not OtherRequest (which extends Request and is valid argument from foo() perspective).
As others have mentioned, you cannot do what you want to do for a host of reasons. As mentioned, you can solve this problem with traits or similar. I am presenting an alternative approach.
At a guess, it sounds like you are trying to follow the naming convention put forth by Laravel's RESTful Resource Controllers, which is kind of forcing you to use a particular method on a controller, in this case, store.
Looking at the source of ResourceRegistrar.php we can see that in the getResourceMethods method, Laravel does either a diff or intersect with the options array you pass in and against the default values. However, the those defaults are protected, and include store.
What this means is that you can't pass anything to Route::resource to force some override of the route names. So let's rule that out.
A simpler approach would be to simply set up a different method just for this route. This can be achieved by doing:
Route::post('user/save', 'UserController#save');
Route::resource('users', 'UserController');
Note: As per the documentation, the custom routes must come prior to the Route::resource call.
The declaration of UserController::store() should be compatible with BaseController::store(), which means (among other things) that the given parameters for both the BaseController as well as UserController should be exactly the same.
You actually cán force the BaseController to require a UserFormRequest, it's not the prettiest solution, but it works.
By overwriting there is no way you can replace Request with UserFormRequest, so why not use both? Giving both methods an optional parameter for injecting the UserFormRequest object. Which would result in:
BaseController.php
class BaseController {
public function store(Request $request, UserFormRequest $userFormRequest = null)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
}
UserController.php
class UserController extends BaseController {
public function __construct(UserRepository $repo)
{
$this->repo = $repo;
}
public function store(UserFormRequest $request, UserFormRequest $userFormRequest = null)
{
return parent::store($request);
}
}
This way you can ignore the parameter when using BaseController::store() and inject it when using UserController::store().
The easiest and cleanest way I found to circumvent that problem was to prefix the parent methods with an underscore. For example:
BaseController:
_store(Request $request) { ... }
_update(Request $request) { ... }
UserController:
store(UserFormRequest $request) { return parent::_store($request); }
update(UserFormRequest $request) { return parent::_update($request); }
I feel like creating service providers is an overkill. What we're trying to circumvent here is not the Liskov substitution principle, but simply the lack of proper PHP reflection. Type-hinting methods is, in itself, a hack after all.
This will force you to manually implement a store and update in every child controller. I don't know if that's bothersome for your design, but in mine, I use custom requests for each controller, so I had to do it anyway.