So I'm trying to migrate a small site to Laravel. Trying to come to grips with basic code organization. For starters, I'd like to set up my site with basic controller and model(s) so that this url:
http://example.com/product/123
Will display product information for record with id=123 in my db table named products.

My post is intended to show what I've managed to grok so far. My questions are at the bottom. Any commentary or insights or *****ing and moaning about Laravel are welcome and encouraged.

The docs on controllers have some interesting/maddening instructions on how to create controllers with a command. This is the basic command

php artisan make:controller FooController

It creates a file, app/Http/Controllers/FooController.php, which looks like this:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class FooController extends Controller
{
    //
}

This command does not expose one's controller on the website. You still have to define a route for it. You can do that by adding a line like this to routes/web.php:

Route::get('product/{id}', 'FooController@method_name');

It's disappointing a bit that the controller is not automatically exposed when you create it (as with CodeIgniter) and it's also tedious to have to add all kinds of routes manually. It's especially irritating to have to learn yet another weird little syntax and a whole slew of complicated rules about routing, but I guess it's kind of nice that one has a great deal of control over the routes. One can also apparently specify all kinds of namespace and middleware particulars etc. -- which looks to be pretty powerful -- but I haven't yet grasped how to take advantage of these features.

One can also create a 'resource' controller -- this is apparently a controller that handles CRUD for some particular data type. This command constructs a resource controller FooController for a model BarModel:

php artisan make:controller FooController --resource --model=BarModel

If BarModel doesn't exist, it will create a stub file app/BarModel.php:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class BarModel extends Model
{
    //
}

This command creates a much more elaborate controller with methods for the various CRUD actions:

<?php

namespace App\Http\Controllers;

use App\BarModel;
use Illuminate\Http\Request;

class FooController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        //
    }

/**
 * Show the form for creating a new resource.
 *
 * @return \Illuminate\Http\Response
 */
public function create()
{
    //
}

/**
 * Store a newly created resource in storage.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
public function store(Request $request)
{
    //
}

/**
 * Display the specified resource.
 *
 * @param  \App\BarModel  $barModel
 * @return \Illuminate\Http\Response
 */
public function show(BarModel $barModel)
{
    //
}

/**
 * Show the form for editing the specified resource.
 *
 * @param  \App\BarModel  $barModel
 * @return \Illuminate\Http\Response
 */
public function edit(BarModel $barModel)
{
    //
}

/**
 * Update the specified resource in storage.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \App\BarModel  $barModel
 * @return \Illuminate\Http\Response
 */
public function update(Request $request, BarModel $barModel)
{
    //
}

/**
 * Remove the specified resource from storage.
 *
 * @param  \App\BarModel  $barModel
 * @return \Illuminate\Http\Response
 */
public function destroy(BarModel $barModel)
{
    //
}
}

Interestingly, these controllers go right into the app/Https folder -- there's no option to create cli or api controllers. And apparently no default location for them either. Even with auto-generation of model and controller, one must still manually add a route:

Route::resource('photos', 'PhotoController');

I'd like to whinge about the routing of of PUT/PATCH for update operations as indicated in the table in the docs on resource controllers. Since HTML forms don't support PUT or PATCH methods, one must add an extra input in these forms to spoof the PUT input method:

<input type="hidden" name="_method" value="PUT">

What pedantry is this? I'd love to poke the jerk in the eye who decided this was a good idea.

While it's great to have these stub files generated, and I'm aware of Laravel's ability to automatically inject dependencies based on type-hinting, I'm a bit confused about a variety of things.

1) How do I connect this newly generated BarModel class to a database table?
The laravel documentation doesn't have a section on models.

2) How do I best protect some of these methods (insert/update/delete) while allowing all access to others?
The authentication functionality seems like the canonical/orthodox way to go here and I'm hoping to toe that line, but I also recall this warning when reading the docs on controllers:

You may assign middleware to a subset of controller actions; however, it may indicate your controller is growing too large. Instead, consider breaking your controller into multiple, smaller controllers.

3) What's with the code organization?
I see that auto-generated models live in the app directory. Do all of my models go in there? Seems odd not to have a Models directory somewhere. If I do create a Models directory, how would this affect my namespacing commands and autoloading? Also, the app dir has Console and Http but no Api directory despite having a built-in routes/api.php.

Any help/commentary would be much appreciated. So far, this whole framework seems completely ad-hoc.

    NogDog;11060687 wrote:

    I think they hide most of the info about models here: https://laravel.com/docs/5.4/eloquent

    OK. I've been choking down the documentation in order one section at a time. The docs all read like some interminable inside joke about some tv show I never saw.

      I had the same problem a year or two ago when I was using it: "Where are the models?" 🙂

        Reading those docs posted by NogDog. Some interesting suggestions/requirements for your tables to play nice with Laravel's model classes. Also some interesting deviations from these suggestions/requirements
        "Eloquent" (the name of this makes me want to gag) assumes a table name which is derived from the class name. Quoth the docs:

        Note that we did not tell Eloquent which table to use for our Flight model. By convention, the "snake case", plural name of the class will be used as the table name unless another name is explicitly specified. So, in this case, Eloquent will assume the Flight model stores records in the flights table.


        How the snake case equivalent of Flight gets pluralized is not explained.
        * Eloquent will also assume that each table has a primary key column named id. In addition, Eloquent assumes that the primary key is an incrementing integer value, which means that by default the primary key will be cast to an int automatically. This is not the case for the cache or session tables defined when you run php artisan session:table or php artisan cache:table. The default sessions table has an id of type varchar which is unique, but not primary. The default cache table has no id column at all.
        * By default, Eloquent expects created_at and updated_at columns to exist on your tables.

        One can override these default requirements with specific variable definitions in one's model classes.

          Pretty much the same thing Rails does. You can override all those defaults, but it's generally easier to go with the flow, and then you don't have to worry about it. (And you don't have to deal with people telling you "That's not the Rails way" when you ask for help.)

          I suspect when naming classes, if they are multi-word, you probably want to end with a noun, so that you know how it will be pluralized, e.g. "BigFuzzyCat" will become "big_fuzzy_cats", but I'm not sure what "CatThatLikesToSleep" will become.

            sneakyimp;11060741 wrote:

            How the snake case equivalent of Flight gets pluralized is not explained.

            I noted that as well. I assumed that since the "Flight" class is an object and the corresponding table is presumed to be meant to hold multiple copies of same, it was, hence, plural.

              I've encountered my first 'not the rails way' issue. I have a hierarchical tree structure encoded in a table called 'tree'. My model is called tree. Apparently 'the rails way' is ALWAYS to add an "s" :glare:

              Just to recap, create the model via cli:

              php artisan make:model Tree

              Create a CategoryController to make use of it (tree structure in this application will be to describe 'categories' :

              php artisan make:controller CategoryController --resource --model=Tree

              Create a route for the CategoryController in routes/web.php

              Route::resource('categories', 'CategoryController');

              Edit the Category controller to fetch the root items of my tree:

                  /**
                   * Display a listing of the resource.
                   *
                   * @return \Illuminate\Http\Response
                   */
                  public function index()
                  {
                      // fetch the root-level tree items
                  	$categories = \App\Tree::where('parent_id', 0)
                             ->orderBy('name', 'asc')
                             ->get();
              		var_dump($categories);    	
                  }

              Then visit http://example.com/categories and see my lovely error:

              QueryException in Connection.php line 647: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'my_project.trees' doesn't exist (SQL: select * from trees where parent_id = 0 order by name asc)

              The problem is resolved by adding a class var to my Tree model:

              	/**
              	 * The table associated with the model. I chose to do this because the
              	 * "laravel way" assumes one adds an "s" to the model name to get the table name
              	 *
              	 * @var string
              	 */
              	protected $table = 'tree';
              

              I wonder what happens if you have a model named Tardis? Would it be Tardiss?

                Tardises?

                Tardisses?

                Tardi?

                Tardae?

                  NogDog;11060769 wrote:

                  Tardises?

                  Tardisses?

                  Tardi?

                  Tardae?

                  Ahem.

                  "That's not the Rails way"

                    OK here seems to be a thorny problem. I created a resource controller using the automated means (see 'tree' discussion above) and for some reason the $tree object injected into my CategoryController::show method has no attributes. I'm guessing this might have something to do with the table name mismatch.

                    Details. My controller method in app/Https/Controllers/CategoryController.php:

                        /**
                         * Display the specified resource.
                         *
                         * @param  \App\Tree  $tree
                         * @return \Illuminate\Http\Response
                         */
                        public function show(Tree $tree)
                        {
                    		// we need to display the children of $tree
                    		var_dump($tree);
                    
                    }
                    

                    This is definitely the method this is handling the request, thanks to this route in routes/web.php:

                    Route::resource('categories', 'CategoryController');

                    I know it's the one because I change it and my output changes.

                    Note above how I added this to the Tree controller:

                    protected $table = 'tree';

                    The problem is that I get no error, but the $tree object I'm var dumping when I visit http://example.com/categories/1 has no attributes:

                    object(App\Tree)#217 (24) {
                      ["table":protected]=>
                      string(4) "tree"
                      ["connection":protected]=>
                      NULL
                      ["primaryKey":protected]=>
                      string(2) "id"
                      ["keyType":protected]=>
                      string(3) "int"
                      ["incrementing"]=>
                      bool(true)
                      ["with":protected]=>
                      array(0) {
                      }
                      ["perPage":protected]=>
                      int(15)
                      ["exists"]=>
                      bool(false)
                      ["wasRecentlyCreated"]=>
                      bool(false)
                      ["attributes":protected]=>
                      array(0) {
                      }
                      ["original":protected]=>
                      array(0) {
                      }
                      ["casts":protected]=>
                      array(0) {
                      }
                      ["dates":protected]=>
                      array(0) {
                      }
                      ["dateFormat":protected]=>
                      NULL
                      ["appends":protected]=>
                      array(0) {
                      }
                      ["events":protected]=>
                      array(0) {
                      }
                      ["observables":protected]=>
                      array(0) {
                      }
                      ["relations":protected]=>
                      array(0) {
                      }
                      ["touches":protected]=>
                      array(0) {
                      }
                      ["timestamps"]=>
                      bool(true)
                      ["hidden":protected]=>
                      array(0) {
                      }
                      ["visible":protected]=>
                      array(0) {
                      }
                      ["fillable":protected]=>
                      array(0) {
                      }
                      ["guarded":protected]=>
                      array(1) {
                        [0]=>
                        string(1) "*"
                      }
                    }
                    

                      Fishs
                      Sheeps

                      The plural of "mouse" is "mice", and the plural of "house" is "hice".
                      The plural of "ox" is "oxen", and the plural of "box" is "boxen".
                      The plural of "daily" is "dailies", and the plural of "day" is "daies".
                      The plural of "oaf" is "oafs", and the plural of "loaf" is "loafs".
                      The plural of "baby" is "babies", and the plural of "flyby" is "flybies"
                      The plural of "flyby" is "flybys", and the plural of "ruby" is "rubys".
                      The plural of "die" is "dies", and the plural of "die" is "dice".

                      It's not the Rails Way to have tables for these things.

                        Weedpacket;11060777 wrote:

                        Fishs
                        Sheeps

                        The plural of "mouse" is "mice", and the plural of "house" is "hice".
                        The plural of "ox" is "oxen", and the plural of "box" is "boxen".
                        The plural of "daily" is "dailies", and the plural of "day" is "daies".
                        The plural of "oaf" is "oafs", and the plural of "loaf" is "loafs".
                        The plural of "baby" is "babies", and the plural of "flyby" is "flybies"
                        The plural of "flyby" is "flybys", and the plural of "ruby" is "rubys".
                        The plural of "die" is "dies", and the plural of "die" is "dice".

                        It's not the Rails Way to have tables for these things.

                        Everything about this comment confuses the ish outta me. I was starting to point out each error but then I figured you did it on purpose...

                          Weedpacket;11060777 wrote:

                          Fishs
                          Sheeps

                          The plural of "mouse" is "mice", and the plural of "house" is "hice".
                          The plural of "ox" is "oxen", and the plural of "box" is "boxen".
                          The plural of "daily" is "dailies", and the plural of "day" is "daies".
                          The plural of "oaf" is "oafs", and the plural of "loaf" is "loafs".
                          The plural of "baby" is "babies", and the plural of "flyby" is "flybies"
                          The plural of "flyby" is "flybys", and the plural of "ruby" is "rubys".
                          The plural of "die" is "dies", and the plural of "die" is "dice".

                          It's not the Rails Way to have tables for these things.

                          +1 :evilgrin:

                            Derokorian;11060779 wrote:

                            Everything about this comment confuses the ish outta me. I was starting to point out each error but then I figured you did it on purpose...

                            I too am confused, but when I start to explain the solution here, you may start to understand what he means.

                            Someone suggested that I change my method's type-hinting definition to use a different variable name. Changing $tree in the auto-generated code to $category fixes my problem:

                                /**
                                 * Display the specified resource.
                                 *
                                 * @param  \App\Tree  $tree
                                 * @return \Illuminate\Http\Response
                                 */
                                public function show(Tree $category)
                                {
                            		// we need to display the children of $tree
                            		var_dump($category);
                            
                            }

                            Why does this fix the problem? I'm not entirely sure, but it has something to do with Laravel resource routing and "model binding" and this route that I defined to connect my CategoryController to my website's /categories path:

                            Route::resource('categories', 'CategoryController');
                            

                            Somehow, because I specified the word category in those two values, Laravel -- in its finite and highly questionable wisdeom -- decided that it would use the word 'category' as the parameter name that it would try to inject into my CategoryController. I have no idea if this value was derived from CategoryController or whether Laravel somehow converted "categories" to its singular variation, "category." It is with a growing sense of horror that I suspect the latter. At this point, the inner functioning of Laravel is a black box to me. The routing command I have above creates numerous routing rules for a variety of urls. One can inspect the routing rules with a command:

                            $ php artisan route:list
                            +--------+-----------+----------------------------+--------------------+-------------------------------------------------+--------------+
                            | Domain | Method    | URI                        | Name               | Action                                          | Middleware   |
                            +--------+-----------+----------------------------+--------------------+-------------------------------------------------+--------------+
                            |        | GET|HEAD  | /                          |                    | Closure                                         | web          |
                            |        | GET|HEAD  | api/user                   |                    | Closure                                         | api,auth:api |
                            |        | GET|HEAD  | categories                 | categories.index   | App\Http\Controllers\CategoryController@index   | web          |
                            |        | POST      | categories                 | categories.store   | App\Http\Controllers\CategoryController@store   | web          |
                            |        | GET|HEAD  | categories/create          | categories.create  | App\Http\Controllers\CategoryController@create  | web          |
                            |        | GET|HEAD  | categories/{category}      | categories.show    | App\Http\Controllers\CategoryController@show    | web          |
                            |        | PUT|PATCH | categories/{category}      | categories.update  | App\Http\Controllers\CategoryController@update  | web          |
                            |        | DELETE    | categories/{category}      | categories.destroy | App\Http\Controllers\CategoryController@destroy | web          |
                            |        | GET|HEAD  | categories/{category}/edit | categories.edit    | App\Http\Controllers\CategoryController@edit    | web          |
                            |        | GET|HEAD  | products                   | products.index     | App\Http\Controllers\ProductController@index    | web          |
                            |        | POST      | products                   | products.store     | App\Http\Controllers\ProductController@store    | web          |
                            |        | GET|HEAD  | products/create            | products.create    | App\Http\Controllers\ProductController@create   | web          |
                            |        | GET|HEAD  | products/{product}         | products.show      | App\Http\Controllers\ProductController@show     | web          |
                            |        | PUT|PATCH | products/{product}         | products.update    | App\Http\Controllers\ProductController@update   | web          |
                            |        | DELETE    | products/{product}         | products.destroy   | App\Http\Controllers\ProductController@destroy  | web          |
                            |        | GET|HEAD  | products/{product}/edit    | products.edit      | App\Http\Controllers\ProductController@edit     | web          |
                            +--------+-----------+----------------------------+--------------------+-------------------------------------------------+--------------+
                            

                              Thought I'd just look at the pluralisation rules themselves. Lessee... Eloquent\ - sorry, Illuminate\Database\Eloquent\Model.php; Model::getTable() looks like the method. It calls [noparse]Str::plural()[/noparse].

                              Searching for the Str class I find Illuminate\Support\Str.php and I also see a Pluralizer.php there. That's okay: all [noparse]Str::plural()[/noparse] does is call [noparse]Pluralizer::plural()[/noparse].

                              As an aside, I'm not really a fan of autoloading classes.

                              Ah-ha. First there's a list of "uncountable" nouns, which has both "fish" and "sheep", and also "data", "metadata", "equipment", "wheat", "love" and "jedi" (but neither "butter", "rice" nor "hate").
                              After skipping those words it calls [noparse]Inflector::pluralize()[/noparse].

                              Inflector? Ah, there's a use statement at the top of the file: [font=monospace]use Doctrine\Common\Inflector\Inflector[/font].

                              Oh, okay. So it actually uses Doctrine's inflector. So I'm sorry, but your class is in another library.

                              So I pull doctrine/inflector off of Github, and it's a list of regexes for regular forms, a list of uninflected words (e.g., those ending with "fish" or "sheep", so those didn't need calling out in Illuminate, but also "cookie" for some reason), and another list of irregular plurals (such as "loaf"=>"loaves" and "ox"=>"oxen", but it also has rules that are already catered for by the regular regular expressions, and has the wrong form for the plural of "ganglion").

                              Since I went to that bother, I put the list of words I had in my post through: it got "flyby" wrong, and to say that the plural of the noun "die" is "dies" is actually right or wrong depending on what sort of die we're talking about.

                              And yes: I am short on stuff to do right now; why do you ask?

                                Heh...now you have me thinking how...

                                The plural of "die" is "dice" [edit], except when it is "dies.[/edit] (Thanks, Weedpacket.)
                                The third person singular tense of "die" is "dies".
                                The third person singular tense of "dice" is "dices".

                                I'm glad I never had to learn English as a second language. 🙂

                                (And don't even get me started on trying to explain the pronunciation rules that would apply to "tough", "trough", "though", and "through". :rolleyes: )

                                  Unless the die is a shape for steering the shaping of metal, in which case the plural is "dies".

                                    Weedpacket;11060793 wrote:

                                    And yes: I am short on stuff to do right now; why do you ask?

                                    This was time well-spent -- in my reckoning at least. Thank you!

                                    EDIT: why do you dislike auto-loading classes? Autoloading seems like a blessing to me.

                                      For me it just conflicts with the idea of self-documenting code (like, there is no indication that a class Foo is used and therefore needs to be included from somewhere until you get to some arbitrary point in the code), it imposes an extra degree of coupling between what you name things and the filesystem (there needs to be a way to derive the required file path from just the name of the class), and then you get different systems using different autoloading mechanisms and they get into an argument about where things should be searched for. (It's only incidental that you can't use autoloading when you're using the PHP Shell)