November 15, 2015 23:56

CSRF tokens are great for security but there are rare occurrences where the token is invalid when it shouldn't be.

An invalid token is usually a temporary problem* - it's solved immediately on the next page load when their new session is created and token is refreshed. So it's bad UX to simply throw an exception and display a useless page or worse, a nasty error page.

*One case in which it wouldn't be temporary is if sessions aren't working properly at all; for example, if you're using the file session driver but your server is out of disk space or the folder isn't writeable.

A simple solution to this is to catch those exceptions and tell the user to "try again." Now it's just a slight annoyance to your user and not something that will completely stop them in their tracks. Modifying your handler like this takes care of that:

use Illuminate\Session\TokenMismatchException;

class VerifyCsrfToken extends BaseVerifier {
    public function handle($request, Closure $next)
    {
        try {
            return parent::handle($request, $next);
        } catch (TokenMismatchException $e) {
            if ($request->ajax()) {
                return response('CSRF error', 500);
            }

            return redirect()->back()
                ->withInput(\Input::except('_token'))
                ->withErrors(['Something went wrong, please try again']);
        }
    }
}

Laravel

October 17, 2015 21:29

Update

Laravel now comes with a Relationship morphMap which takes care of this all in one place. Simply add this to your AppServiceProvider

use Illuminate\Database\Eloquent\Relations\Relation;

public function boot()
{
    Relation::morphMap([
        'album' => Album::class,
        'photo' => Photo::class,
        'user'  => User::class,
    ]);
}

You can now use album, photo, etc. in your ENUMs instead of the full class name.


Original Post

Let's say you have a Comment class that has a morphTo relationship with many other models. However, you don't want to store the full class name of the relationship in your database because that's a waste of space, and namespaces might change.

First, every model that has comments should define the morphClass variable. This is just a shortened version of the class name.

class Album {
    protected $morphClass = 'album';
}
class Photo {
    protected $morphClass = 'photo';
}

Next, the Comment class should define an $entity_types array which is basically the inverse of what you just did. It maps the short name to the full class name.

class Comment {
    protected $entity_types = [
        'album' => \App\Album::class,
        'photo' => \App\Photo::class,
    ];
}

Finally, the Comment class should have an accessor for the entity_type column. This takes the database value ("photo", "album", etc.) and converts it to the full class name ("\App\Photo", "\App\Album", etc.)

/**
 * @param  string  $type  short name
 * @return string  full class name
 */
public function getEntityTypeAttribute($type)
{
    if ($type === null) {
        return null;
    }

    $type = strtolower($type);
    return array_get($this->entity_types, $type, $type);
}

Ideally, this should be either on your base model, or on a trait. That way the method can be reused on any class that has a morphTo relationship.

Laravel, PHP

October 15, 2015 15:26

By default, Laravel logs users in with their email and password. In some cases you might want them to use a username instead. This is pretty simple, just set the $username property on the AuthController class:

class AuthController extends Controller
{
    ...
    protected $username = 'username';
}

The AuthController uses the AuthenticatesUsers trait, which checks if the $username property is set, and if not, defaults to 'email'

If you want users to have the choice of logging in with email or username, it's a little more complex. You'll have to override the postLogin method in your controller.

class AuthController extends Controller
{
    use AuthenticatesAndRegistersUsers {
        AuthenticatesAndRegistersUsers::postLogin as laravelPostLogin;
    }

    /**
     * Login with email or username
     *
     * @param  Request  $request
     */
    public function postLogin(Request $request)
    {
        $field = filter_var($request->input('email'), FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
        $request->merge([$field => $request->input('email')]);
        $this->username = $field;

        return self::laravelPostLogin($request);
    }
}

To break that code down, first we use the trait, but we want to rename the original postLogin method so that we can use it later.

Then inside our postLogin method, we check if the email field that we're receiving is an email address or not. This assumes that you have some restrictions on usernames, e.g. that they're alphanumeric, or at the very least, don't allow the @ character.

Then it sets the $username attribute as either 'email' or 'username'.

Finally, it calls the laravelPostLogin method, which we had reassigned earlier, so that Laravel continues the login as before. That way we don't have to rewrite any of the logic in that original method.

Laravel, PHP

October 13, 2015 19:36

I actually thought of this while answering a question on StackOverflow

When querying sums of columns, I typically do something like this:

$query = $account->transactions()
    ->select(\DB::raw('SUM(amount) AS total'))
    ->first();
$total = $query->total;

Here's another way to accomplish the same thing. Of course if you end up fetching thousands of rows you'll probably want to just let MySQL do the math for you, but it's way easier to read and cleaner.

$amounts = $account->transactions()->lists('amount');
$total = $amounts->sum();

Laravel, MySQL, PHP

September 17, 2015 14:32

In Laravel, redirecting the user to one URL after logging in and another after registering (such as a welcome page) is very simple.

First, inside your AuthController, set the post-login URL as the default:

protected $redirectTo = '/url-after-login';

and then inside the create function, override it to the post-registration URL

protected function create(array $data)
{
    $this->redirectTo = '/url-after-register';

    return User::create([...]);
}

Laravel, PHP