Today's Topic is a bit strange because it doesn't quit fit the theme of the Laravel Study Group. But, it's come up frequently enough that I felt it deserved a post.
How should I structure my Laravel application?
This one is easy. Use the structure that you're used to from whatever framework you used previously. Get used to working in Laravel and then you'll find ways in which you want your organization to change. Laravel grows with you. It gives you the chance to do what you know. When you're ready to do the next thing, it's there waiting for you like a faithful hound, always eager to please.
Either way, the basic idea is this...
Routing
Routing is what tells Laravel which code to run based on the URL. In Laravel routing is explicit. You must declare your entire routing table. This is strange for people coming from CodeIgniter where routing is implicit. If you have a controller it's routed. The routing system in CodeIgniter is basically just there to handle cases in which you want specific URLs.
In Laravel the routing layer is an abstraction. You can write your controllers and all of your site and if you do it right.. you can change the URLs via your routing table and the entire site will STILL work. The links that your code generates will automatically change and all is well in the world. Here's an example of a routing table.
// landing page
Route::get('/', 'home@index');
// auth
Route::any('login/(:all?)', 'auth@login');
Route::any('logout', 'auth@logout');
Route::any('signup', 'auth@signup');
Route::any('forgot_password', 'auth@forgot_password');
Route::any('reset_password/(:any)', 'auth@reset_password');
// secure routes
Route::group(array('before' => 'auth'), function()
{
Route::get('send_request', 'friends@send_request');
Route::any('search/(:all?)', 'search@index');
Route::controller(array(
'dashboard',
'messages',
'friends',
'admin.dashboard',
'admin.users',
));
Route::any('edit_profile', 'profile@edit');
Route::get('(:any)', 'profile@view');
});
Notice how at a glance we can see how the entire application works? I didn't want the user to go to http://site.com/auth/login in order to get to the login page so I manually declared all of the routes for the auth controller.
If I would have just done...
Route::controller('auth');
Then http://site.com/auth/login etc would work just fine. But, for that fine grained control I made a registration for each individual method. That's ok, this is very self-documenting. We know what we're working with here. People who inherit this project from me will easily understand how things function.
In addition, you can see how route groups are used to automatically run the auth filter. In this way, if I want a controller to be protected by authentication I can just move it into the group, or if i don't want it to be protected I can move it out. Again, it's clear to see for everyone involved what's happening here.
In addition, the routing table is a pure abstraction. I could name the auth controller anything and my URLs will still be the same. If a client requests that the URL changes for 'dashboard' for example. I can just change the way that it's routed and leave the controller alone. I use the same admin.user controller on MOST of my projects. Sometimes it's called participants etc, sometimes it's users, it can be what we need it to be.
Constructing the response
You'll often see tutorials that show a bunch of examples using routed closures.
Route::get('login', function()
{
// do stuff here
return View::make(...);
});
That's fine and dandy and many people find it novel because it's not what they're used to. But, just ignore this when you're getting started. Use controllers. We use MVC for a reason. Controllers are almost always the better way to store the "controller" logic. This may seem obvious, but it's a real issue with new Laravel developers. Code MVC in the same way that you always have.
Application flow, managing input, interacting with models, and generating responses. These are the things that the controller handles. The controller doesn't store validation rules, the controller doesn't have db queries, and a controller should in general end up being VERY short because all of the real logic lies within the models.
Modeling the domain
Modeling the domain is a fancy way of saying that we're taking the various types of information that our application interacts with (users, posts, apis, forms, whatever) and creating classes that REPRESENT that domain.
A TYPICAL user class would extend Eloquent (Laravel's ActiveRecord implementation). You may also have a model for the signup form more information about form models here. Your models are what contain your validation rules. If you have a user model, then your user model contains the rules that determine if a user is valid. Your signup form model might have entirely different validation rules because it's validating a form, and your user model is validating the user record itself. This is a matter of personal preference. However, I always validate both the form and the ActiveRecord model separately.
Your ORM is not your model
It's somewhat normal to see code like this in a controller...
$users = User::where('active', '=', 1)->get();
However, in this situation you're using your ORM as your model. An ORM is a tool that you use to generate your models. What are we REALLY trying to do here? We're trying to get a list of active users. We may need to do this 20 times across the site for various reasons. What if the requirements for what determines an active user changes?
This is probably better...
$users = User::active_users()->get();
From the model...
const STATUS_INACTIVE = 0;
const STATUS_ACTIVE = 1;
public static function active_users()
{
// If you don't know why we use static::where() here it's ok.
// Just think of it as User::where(...) except that it can be extended by another class.
return static::where('status', '=', User::STATUS_ACTIVE);
}
Notice that the model method doesn't do get() at the end. So, it's returning a query object instead of an array of user objects. This is so that we can use this method for many purposes. For example...
$five_users = User::active_users()->take(5)->get();
$paginated = User::active_users()->paginate(10);
Autoloading
There's no $this->load->model('user'); in Laravel. Laravel uses a PSR0 autoloader. In the application/start.php file you'll find the line...
Autoloader::directories(array(
path('app').'models',
path('app').'libraries',
));
This tells the autoloader that whenever you reference a class that isn't yet loaded then we should look in these folders for the file that matches the class. You can add additional directories here or you can register namespaces or direct class mappings with the autoloader. For now, just realize that THIS is the reason that the User class can be accessed. Not because Laravel just magically knows what models you have available. But, because the application/start.php file registers the models and libraries folder with the autoloader.
Sending the response
Routed code (for example, controller methods) need to have a response returned. The following is not a valid controller method...
echo View::make('home.index')->render();
You don't output ANYTHING EVER in a Laravel application. You "return" the response and Laravel handles it. If you don't do this then you'll have issues when you need Laravel to set headers (cookies for example).
Here are a few valid ways to end a controller action...
return "This is going to be output as a response.";
return View::make('auth.login');
return Redirect::to('login');
return Redirect::to_action('auth@login');
return Redirect::back();
return Response::json(array('this' => 'is json now'));
return Response::download('path/to/file.jpg', 'filename.jpg');
return Response::make("This is a custom response object.", $http_status_code, array('headers', 'go', 'here'));
You can create responses of any type and Laravel can handle them in one way or another. Note that in EVERY SINGLE example we're returning the response. Laravel will take that response and handle all of the header calls etc correctly. You may even be using bundles that require usage of headers and you have no idea maybe what they're doing. But, you're keeping yourself from breaking stuff by handling responses the correct way.
Some things to be aware of
So, those are the basics. But, I see people making some mistakes and I want to acknowledge them here.
Don't use Route::controller(Controller::detect()) for automatic routing. This algorithm has caused more support requests than I can count. I have been helping people in the #Laravel IRC channel for well over a year and this has come up over and over again. Please just don't use it. It'll work fine until it doesn't then you'll literally have no option other than to take it out and recreate your routes file like you should have in the first place.
Try not to just create a bunch of controller member variables. In CodeIgniter it's standard procedure to do $this->users in the controller and then to use that variable in the view. It's not the same thing as doing $this->users in a Laravel controller. Because $this isn't going to assign anything to some super-object. Instead, it's just a controller member variable. It will not be accessible in your views.
Simultaneously, if you're doing this then I wonder if you should be putting this code into a model or something where it may more belong. This is just something to be aware of.
How can I find more information about something specific?
It's hyper-important to realize that Laravel's classes are very well documented. Probably better than you're used to seeing. Give it a shot. Check out laravel/input.php if you have a question about Input::get() or Input::old(), or laravel/str.php if you have a question about Str::is() or whatever. Really give the source code a shot. Many of us have been involved in communities where the documentation was either terrible or the code was terrible. But, seriously give the combination of Laravel's documentation and Laravel's source a shot.
If all else fails or you just want to up your game join #Laravel on irc.freenode.net.
Some additional resources for beginners
Laravel Tips and Tricks for Beginners