Laravel 5.1 has just been released, I would like to know how could I tell the AuthController to get the login & register view from a custom directory? the default is: resources/views/auth...
The trait AuthenticateAndRegisterUsers only has this:
trait AuthenticatesAndRegistersUsers
{
use AuthenticatesUsers, RegistersUsers {
AuthenticatesUsers::redirectPath insteadof RegistersUsers;
}
}
The code you're showing there only fills one function: it tells our trait to use the redirectPath from the AuthenticatesUsers trait rather than the one from RegistersUsers.
If you check inside the AuthenticatesUsers trait instead, you will find a getLogin() method. By default, this one is defined as
public function getLogin()
{
return view('auth.login');
}
All you have to do to get another view is then simply overwriting the function in your controller and returning another view. If you for some reason would like to load your views from a directory other than the standard resources/Views, you can do so by calling View::addLocation($path) (you'll find this defined in the Illuminate\View\FileViewFinder implementation of the Illuminate\View\ViewFinderInterface.
Also, please note that changing the auth views directory will do nothing to change the domain or similar. That is dependent on the function name (as per the definition of Route::Controller($uri, $controller, $names=[]). For more details on how routing works, I'd suggest just looking through Illuminate\Routing\Router.
for those who is using laravel 5.2, you only need to override property value of loginView
https://github.com/laravel/framework/blob/5.2/src/Illuminate/Foundation/Auth/AuthenticatesUsers.php
public function showLoginForm()
{
$view = property_exists($this, 'loginView')
? $this->loginView : 'auth.authenticate';
if (view()->exists($view)) {
return view($view);
}
return view('auth.login');
}
so to override the login view path, you only need to do this
class yourUserController {
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
.....
protected $loginView = 'your path';
}
Related
I've created a GitHub repo to better understand the problem here. I have two actions on two different controllers bound to the same route.
http://localhost/sameControllerRoute/{identifier}/values
[Route("sameControllerRoute")]
public class FirstController : Controller
{
public FirstController()
{
// different EF Core DataContext than SecondController and possibly other dependencies than SecondController
}
[HttpGet("{identifier}/values")]
public IActionResult Values(string identifier, DateTime from, DateTime to) // other parameters than SecondController/Values
{
return this.Ok("Was in FirstController");
}
}
[Route("sameControllerRoute")]
public class SecondController : Controller
{
public SecondController()
{
// different EF Core DataContext than FirstController and possibly other dependencies than FirstController
}
[HttpGet("{identifier}/values")]
public IActionResult Values(string identifier, int number, string somethingElse) // other parameters than FirstController/Values
{
return this.Ok("Was in SecondController");
}
}
Since there are two matching routes, the default ActionSelector fails with:
'[...] AmbiguousActionException: Multiple actions matched. [...]'
which is comprehensible.
So I thought I can implement my own ActionSelector. In there I would implement the logic that resolves the issue of multiple routes via same logic depending on the 'identifier' route value (line 27 in code)
If 'identifier' value is a --> then FirstController
If 'identifier' value is b --> then SecondController
and so on...
protected override IReadOnlyList<ActionDescriptor> SelectBestActions(IReadOnlyList<ActionDescriptor> actions)
{
if (actions.HasLessThan(2)) return base.SelectBestActions(actions); // works like base implementation
foreach (var action in actions)
{
if (action.Parameters.Any(p => p.Name == "identifier"))
{
/*** get value of identifier from route (launchSettings this would result in 'someIdentifier') ***/
// call logic that decides whether value of identifier matches the controller
// if yes
return new List<ActionDescriptor>(new[] { action }).AsReadOnly();
// else
// keep going
}
}
return base.SelectBestActions(actions); // fail in all other cases with AmbiguousActionException
}
But I haven't found a good solution to get access to the route values in ActionSelector. Which is comprehensible as well because ModelBinding hasn't kicked in yet since MVC is still trying to figure out the Route.
A dirty solution could be to get hold of IHttpContextAccessor and regex somehow against the path.
But I'm still hoping you could provide a better idea to retrieve the route values even though ModelBinding hasn't happend yet in the request pipeline.
Not sure that you need to use ActionSelector at all for your scenario. Accordingly, to provided code, your controllers works with different types of resources (and so they expect different query parameters). As so, it is better to use different routing templates. Something like this for example:
FirstController: /sameControllerRoute/resourceA/{identifier}/values
SecondController: /sameControllerRoute/resourceB/{identifier}/values
In the scope of REST, when we are talking about /sameControllerRoute/{identifier}/values route template, we expect that different identifier means the same resource type, but different resource name. And so, as API consumers, we expect that all of the following requests are supported
/sameControllerRoute/a/values?from=20160101&to=20170202
/sameControllerRoute/b/values?from=20160101&to=20170202
/sameControllerRoute/a/values?number=1&somethingElse=someData
/sameControllerRoute/b/values?number=1&somethingElse=someData
That is not true in your case
I ended up implementing the proposed solution by the ASP.NET team. This was to implement an IActionConstrain as shown here:
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
namespace ActionConstraintSample.Web
{
public class CountrySpecificAttribute : Attribute, IActionConstraint
{
private readonly string _countryCode;
public CountrySpecificAttribute(string countryCode)
{
_countryCode = countryCode;
}
public int Order
{
get
{
return 0;
}
}
public bool Accept(ActionConstraintContext context)
{
return string.Equals(
context.RouteContext.RouteData.Values["country"].ToString(),
_countryCode,
StringComparison.OrdinalIgnoreCase);
}
}
}
https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.ActionConstraintSample.Web/CountrySpecificAttribute.cs
I have upgrade Laravel from 4.2 to laravel5.3 but I can't access Authentication data inside of Constructor of Controller
I have as below Middleware but it never work for me
use App\Http\Controllers\BaseController;
use Closure;
use Illuminate\Contracts\Auth\Guard;
use Redirect;
use Auth;
use App\User;
class DashboardController extends BaseController
{
public $user;
public function __construct(Guard $guard, User $user)
{
$this->middleware(function ($request, $next) {
$this->user = Auth::user();
return $next($request);
});
//$this->userID = Auth::user()?Auth::user()->id:null;
dd($user);// Result attributes: []
dd($guard);
dd($this->user);
}
}
The result after DD()
dd($guard);
DD($this->user);
NULL
It will return Null when I dd user property.
This is to be expected. The reason you have to assign the user inside the middleware closure is because the session middleware hasn't run yet. So, the closure you have above won't actually be called until later in the execution process.
If you move the dd($this->user) to inside the middleware closure or in to your one of you route methods in that controller it should be working absolutely fine.
Also, just FYI, in your middleware closure you can get the user instance from the request i.e. $request->user() will give you the authenticated user.
Hope this help!
I want to create a maintenance Page for my cake website by checking a Database Table for a maintenance flag using a sub-function of my AppController "initilize()" method. If the flag is set, i throw my custom MaintenanceException(Currently containing nothing special):
class MaintenanceException extends Exception{
}
To handle it, I implemented a custom App Exception Renderer:
class AppExceptionRenderer extends ExceptionRenderer {
public function maintenance($error)
{
return "MAINTENANCE";
}
}
I am able to see this maintenance Text on my website if I set my DB flag to true, but I could not find any information in cake's error handling documentation (http://book.cakephp.org/3.0/en/development/errors.html) on how I can actually tell the Exception renderer to render view "maintenance" with Template "infopage".
Can I even us that function using the ExceptionRenderer without a custom error controller? And If not, how should a proper ErrorController implementation look like? I already tried this:
class AppExceptionRenderer extends ExceptionRenderer {
protected function _getController(){
return new ErrorController();
}
public function maintenance($error)
{
return $this->_getController()->maintenanceAction();
}
}
together with:
class ErrorController extends Controller {
public function __construct($request = null, $response = null) {
parent::__construct($request, $response);
if (count(Router::extensions()) &&
!isset($this->RequestHandler)
) {
$this->loadComponent('RequestHandler');
}
$eventManager = $this->eventManager();
if (isset($this->Auth)) {
$eventManager->detach($this->Auth);
}
if (isset($this->Security)) {
$eventManager->detach($this->Security);
}
$this->viewPath = 'Error';
}
public function maintenanceAction(){
return $this->render('maintenance','infopage');
}
}
But this only throws NullPointerExceptions and a fatal error. I am really dissapointed by the cake manual as well, because the code examples there are nowhere close to give me an impression of how anything could be done and what functionality I actually have.
Because I had some more time today, I spent an hour digging into the cake Source and found a solution that works well for me (and is propably the way it should be done, altough the cake documentation does not really give a hint):
Step 1: Override the _template(...)-Method of the ExceptionRenderer in your own class. In my case, I copied the Method of the parent and added the following Code at the beginning of the method:
$isMaintenanceException = $exception instanceof MaintenanceException;
if($isMaintenanceException){
$template = 'maintenance';
return $this->template = $template;
}
This tells our Renderer, that the error Template called "maintentance"(which should be located in Folder: /Error) is the Error Page content it should render.
Step 2: The only thing we have to do now (And its is kinda hacky in my opinion, but proposed by the cake documentation in this exact way) is to set the layout param in our template to the name of the base layout we want to render with. So just add the following code on top of your error template:
$this->layout = "infopage";
The error controller I created is actually not even needed with this approach, and I still don't know how the cake error controller actually works. maybe I will dig into this if I have more time, but for the moment.
I want to change the the "views/layouts/main.php" to display the login form whenever the user isn't authenticated.
So I changed the "siteController" actionIndex like that:
public function actionIndex() {
$loginForm = new LoginForm();
$this->render('index', array('loginForm'=>$loginForm));
}
And then call it in "views/layouts/main.php" like that:
if(Yii::app()->user->isGuest):
echo $loginForm;
else :
echo 'JJJ';
endif;
Then when I go to my website, It display the error: "Undefined variable: loginForm".
I don't know how to fix this? :(
Define new property in your controller class:
public $loginForm;
In your main.php access it like:
echo $this->loginForm;
If you pass variable in your render method it will be available inside view file only, but not in layout file.
It's because the index template is loaded before main template. So, better way to do hat you want, is to define a public property in your Controller. I suggest you to define this property in Controller class because SiteController and *Controller extends it.
Then, you can run this.
if(Yii::app()->user->isGuest) {
echo $this->loginForm;
} else {
echo 'JJJ';
}
Pay attention, because in this way of work you go out MVC pattern. This way of work force you to define a LoginForm in each action. I suggest you to do that:
Leave clean your calls to render file.
public function actionIndex() {
$this->render('index');
}
And add a getLoginForm method in you Controller class obtaining:
if(Yii::app()->user->isGuest) {
echo $this->getLoginForm();
} else {
echo 'JJJ';
}
There are a couple issues here. Firstly, you are creating an object called $loginForm and assigning it a value of new LoginForm();
$loginForm = new LoginForm();
I'm not sure if you are doing this on purpose and LoginForm() is a function or a method that returns something, but I have a feeling you were intending to do:
$loginForm = new LoginForm;
Which creates a new instance of the class LoginForm (which is a default Yii webapp CFormModel class). Even if that is the case, there are better ways to do this.
The easiest way is to call a renderPartial of the already existing login.php view (located in protected/views/site/login.php) inside your index.php view like so:
if(Yii::app()->user->isGuest) {
$this->renderPartial("loginform",array("model"=>new LoginForm));
} else {
echo 'JJJ';
}
This renders the view login.php (without rendering the layout because we have already rendered the layout - here's the docs on render and renderPartial) and pass it a new instance of the model LoginForm assigned to a variable called $model.
You will most likely have to edit the look of login.php view to make it "fit", but keep in mind that this view is being used in the SiteController actionLogin as well.
All that's left to do then is modify your actionIndex to handle the form submission (you can just copy the existing SiteController actionLogin functionality)
Another nicer solution would be to create a widget for the login form which can be used all over your application. I'm not going to go into that, but you can read up about it here on SO or check out this tutorial or this one.
I want to register user script globally, to be available all over the site. Now I insert in every action in my controllers:
Yii::app()->clientScript->registerScriptFile(Yii::app()->baseUrl . '/js/script.js');
But really I understand that it's not good way...
If you are looking forward to use themes in your project, i would put some css and scripts in layout file (views/layouts/my-layout-file.php). Because if you changing theme you will be using another css and maybe sometimes another scripts, so you would not want to mix it together.
But some main css and scipts, that didn't change accross themes, i would put in main Controller (protected/components/Controller.php)
And all other controllers (/protected/controllers/) would extend this class Controller
class PageController extends Controller {
And so if all your controllers using on parent class, you can edit just parent class and add something like this
public function beforeRender( $view ) {
...
Yii::app()->clientScript->registerScriptFile(Yii::app()->baseUrl . '/js/script.js');
...
return true;
}
And all your actions will be now using same script.
EDIT: #realtebo (in comments) pointed out to use 'beforeRender' not 'beforeAction'.
See more: Understanding the view rendering flow
You can do this in this way : initiate init function in base controller class having path protected/components/controller.php
public function init()
{
Yii::app()->clientScript->registerScriptFile(Yii::app()->baseUrl . '/js/script.js');
parent::init();
}
The best way to register global js and css files I think is registering them in beforeRender() method (not in beforeAction() - because if you render json or xml this may destroy your structure) of some BaseController.
U can do like this:
1. create private attribute $_assetsUrl;
2. then in the module or controller
public function getAssetsUrl()
{
if ($this->_assetsUrl===null)
{
$assetsPath = $this->basePath.DIRECTORY_SEPARATOR.'assets';
$this->_assetsUrl = Yii::app()->assetManager->publish($assetsPath,false,-1,YII_DEBUG);
if (Yii::app()->theme!==null && is_dir($assetsPath.DIRECTORY_SEPARATOR.Yii::app()->theme->name))
$this->_assetsUrl .= DIRECTORY_SEPARATOR.Yii::app()->theme->name;
}
return $this->_assetsUrl;
}
Hope this was useful, see also this link http://www.yiiframework.com/wiki/148/understanding-assets/