The USX approach to ModuleCow's Laravel specific architecture
- 1.1 List: List of Laravel terminology.
Term | Definition |
---|---|
Service Providers | Service providers are classes that are instantiated during the application bootstrapping process (which occurs on every HTTP request). These are used to register all the application bindings, event listeners, middleware, routes, and more. Service providers are the central place to configure your application. |
Route | Laravel is an actual app, and as such it doesn't simply load static PHP files. Instead, it uses routing to direct the HTTP or API request to the proper Controller and the logic and response are handled in the application logic. Then you simply define endpoints and responses by listing the URI and logic handler in a routes file. |
Middleware | Middleware is a powerful method of injecting logic between either an incoming request or outgoing response and the client. Middleware is incredibly useful for authentication, authorization, verifying CSRF tokens, sending global data back to views, and more. Really anything that could stop, change, or log an incoming request before it reaches its Controller or for passing data back to the user on every response. Middleware provide a convenient mechanism for filtering HTTP requests entering your application. |
Controller | The "C" in MVC, Controllers group related request handling logic into a single class. Controller should be concise and pass any sophisticated logic to services or application classes more suited to the task. |
Request | Laravel's instance of the current incoming HTTP request. |
View | The "V" in MVC, Views are the presentation logic of your application. Views contain the HTML served by your application and separate your controller / application logic from your presentation logic. Laravel uses Blade templating engine for view rendering. |
Blade | Blade is the simple, yet powerful templating engine provided with Laravel. It is similar to Twig except that Blade is an extension of PHP and thus allows you to use native PHP functions within your view layer which is incredibly useful (think ucfirst, DateTime output, strtotime, etc). blade of course has a different syntax than Twig but it follows the same patterns of conditionals and loops to output content. |
Log | Log is Laravel's internal abstract layer on MonoLog, very similar to our shared Logger class in USX shared classes. Within ModuleCow you should no longer use the Logger class and instead use the native Laravel Log class. |
Session | Laravel's internal session handling instance. |
Artisan (Console) | Artisan is the command-line interface included with Laravel. Custom Laravel commands may be easily added and use the convenient Symfony Console classes which make writing CLI commands very easy. These can also easily be used by the Task Scheduler (more on that below) to run commands as crons. |
Broadcasting | Broadcasting is Laravel's internal system for Websocket integration. ModuleCow currently doesn't support WebSockets but hopefully it will soon, so this is a good term to be familiar with. |
Cache | Laravel's internal caching class. Laravel provides an expressive, unified API for various caching backends. |
Collections | The Collection class provides a fluent, convenient wrapper for working with arrays of data. This is really a powerful helper when dealing with array data and makes it easy to modify, pluck, convert, validate, find, and more. All methods are available at https://laravel.com/docs/5.5/collections#available-methods. Also, a form of the Collections class is also used interacting with Eloquent Model data returned from the database. More on that below. |
Events | Laravel's events provide a simple observer implementation, allowing you to subscribe and listen for various events that occur in your application. You may set up Event broadcasters and listeners to perform certain logic when a defined application event occurs. For example we use listeners on login success and failure to populate the LoginLogTbl . |
Storage (File Storage) | Laravel provides a powerful filesystem abstraction thanks to the wonderful Flysystem PHP package. This makes it much easier to work with, fetch, and store file data. |
Helpers (Helper Functions) | Laravel provides a load of global helper functions that may be used anywhere within the application! You should really take a moment to skim through them so you are not caught off guard when they are used within the logic with no definition in sight: https://laravel.com/docs/5.5/helpers. |
Laravel Email: Laravel provides a clean, simple API over the popular SwiftMailer library. This makes it much easier to work with sending emails from servers and even utilizes Blade templates to keep application logic separate from the presentation logic. | |
Queues | Currently ModuleCow doesn't have a queue handler, but hopefully it will soon. Laravel makes it easy to create and use queueable tasks. The Mail class listed above is queueable out of the box. |
Task Scheduling (Tasks) | In the past, you may have generated a Cron entry for each task you needed to schedule on your server. However, this can quickly become a pain, because your task schedule is no longer in source control and you must SSH into your server to add additional Cron entries. Laravel's command scheduler allows you to fluently and expressively define your command schedule within Laravel itself. When using the scheduler, only a single Cron entry is needed on your server.. |
Model | The "M" in MVC. Models are incredibly powerful abstraction layers for our database tables. What this means is we can define a single source of truth for data and relationships about a table and utilize the model to interact with the table instead of SQL directly. This provides the benefit of declaring relationships with other models (tables) to easily access joined data. It also takes care of the table name (meaning if the table name is changed you only update it once in the model and all model calls will still work fine), timestamps (CreatedAt, UpdatedAt, and DeletedAt for soft deletes), soft deletion, casting, automatic slugging, custom faux fields, and so much more. |
Eloquent | Eloquent is Laravel's ORM system for interacting with Models. The Eloquent ORM included with Laravel provides a beautiful, simple ActiveRecord implementation for working with your database. Each database table has a corresponding "Model" which is used to interact with that table. Models allow you to query for data in your tables, as well as insert new records into the table. |
Query Builder | Laravel's Query Builder should only be used when queries are too complex for Eloquent. Currently the initial ModuleCow uses only Eloquent. Query Builder should really be a last resort. This is because it bypases everything defined in the Model. We lose the DRYness of relationships and tables naming, and all the defined Model logic, relationships, and helpers. Your returned collection from the Query Builder has only a fraction of the methods available through an Eloquent query, and lastly, the speed improvements are very minor (look for better methods of speeding up your queries when possible such as indexing, additional/better query filters, or paring down your dataset). That all being said, sometimes Query Builder is the better/only solution. Laravel's database query builder provides a convenient, fluent interface to creating and running database queries. |
- 2.1 Application: ModuleCow's boilerplate application root directory structure.
├─ bootstrap // The Laravel application bootstrap directory. Only app.php should ever be modified in this directory.
├─ cache // The application bootstrap cache directory. This should never be modified.
├─ config // The application configuration files. These are often populated from environment variables.
├─ database // The application database migration and seed directory. We currently don't use this feature of Laravel.
├─ factories // Factories are used as Model seeding prototypes that can be used to populate large sets of data.
├─ migrations // Stores the database migrations.
├─ seeds // Seed dummy data into Models for sandbox and testing
├─ modules // Contains the actual application modules. See the next directory structure section below.
├─ public // This is the public directory. This should remain sparse. It's mostly populated by assets builds.
├─ resources // Contains the ModuleCow global resources used accross all modules.
├─ assets // Front-end assets such as JS, Vue, and SASS files. Currently only contains global JS app files.
├─ lang // Language variations for Blade output (not currently used)
├─ views // Global Blade templates and Blade error pages
├─ storage // Contains files generated by the Laravel framework. These should never be modified manually.
├─ tests // Contains automated tests.
├─ vendor // Vendor directory created by Composer PHP package manager. This should never be modified.
├─ node_modules // Vendor directory created by Yarn JS package manager. This should never be modified.
Note: The above Application directory structure is a concise and ModuleCow specific version of Laravel's Root Directory Structure documentation. It's highly recommended you read through Laravel's documentation as well.
- 2.2 Modules: ModuleCow's boilerplate
modules
directory structure.
├─ modules // The root module directory that contains each app module
├─ MODULE_NAME // The name of the ModuleCow module
├─ Config // Contains the module config file created by laravel-modules plugin
├─ Events // Contains the event broadcaster classes for the module
├─ Exceptions // Contains custom Exception handlers for the module
├─ Http // HTTP request handling classes for the module
├─ Controllers // Controller classes
├─ Middleware // Middleware classes
├─ Routes // Contains the route definition files for web and API routes
├─ Jobs // Contains the queueable jobs classes (logic for a queued process)
├─ Listeners // Contains the event listener classes for the module
├─ Mail // Email handler classes for the module
├─ Models // Model classes (core Framework module only)
├─ Observers // Model event observer classes (core Framework module only)
├─ Providers // Service provider classes
├─ Resources // Contains module resources for view layer
├─ assets // Front-end assets such as JavaScript, Vue, and SASS files
├─ lang // Language variations for Blade output (not currently used)
├─ views // Blade templates for the module
├─ Scopes // Model scope extension classes (core Framework module only)
├─ Services // Service classes for extended logic and abstraction within the module
├─ Tests // Test classes
├─ Traits // Reusable trait classes for the module
Note: The above Modules directory structure is a concise and Module specific version of Laravel's App Directory Structure documentation. It's highly recommended you read through Laravel's documentation as well.
- 3.1 No Empty Constructor: All class constructors must contain logic or not be defined.
Why? Constructor definitions are not required when no constructor logic is present. Not defining an unnecessary constructor keeps class code cleaner and more concise.
- 4.1 CRUD Methods: Controllers may only have some or none of the typical RESTful methods, but when one does it should follow these CRUD naming conventions.
Action | Controller Method | Request Method | Description |
---|---|---|---|
List All | index() |
GET |
List all entities |
Show One | show() |
GET |
Show a single entity |
Creation Form | create() |
GET |
Display form for creating a new entity |
Save New | store() |
POST |
Save/store a new entity in the database |
Edit Form | edit() |
GET |
Display form for editing an already existing entity |
Update | update() |
PUT or PATCH |
Save/update the edited entity |
Delete | destroy() |
DELETE |
Delete or soft delete the entity |
A
PUT
request is idempotent and when usingPUT
, it is assumed that you are sending the complete entity, and that complete entity replaces any existing entity in full. APATCH
request will only update the entity fields passed with the request. Usually aPUT
is acceptable for complete non-AJAX entity update forms andPATCH
is used when making an AJAX update that only changes one or several of the entities values on update.
-
5.1 Route Module Prefixing: All routes must begin with the module name prefix.
Why? Defines the module in the path for clarification and the dynamic navigation menus.
Bad
Route::get('/employees', 'EmployeeController@index');
Good
Route::get('/meta/employees', 'EmployeeController@index');
-
5.2 Dashed Slug Format: Defined route paths should always be in standard dashed slug format.
Why? It's the web standard for URI paths.
Bad
Route::get('/route/example/fooBar', 'FooController@bar');
Good
Route::get('/route/example/foo-bar', 'FooController@bar');
-
5.3 Route Naming: All GET routes should have a route name when defining a route in ModuleCow.
Why? Route names are used by our navigation menus and allow for easily recognizable route paths in the codebase.
Bad
Route::get('/route/example/foo-bar', 'FooController@bar');
Good
Route::get('/route/example/foo-bar', 'FooController@bar')->name('route.example.foo-bar');
-
5.4 Route Naming Convention: Route names should be dot (.) separated with parent path info defined first.
Why? Route names are used by our navigation menus and properly nested names are what allow for the navigation menus to display active page highlighting.
Bad
Route::get('/employees', 'EmployeeController@index')->name('employeesIndex'); Route::get('/employee/create', 'EmployeeController@create')->name('create.employees');
Good
Route::get('/employees', 'EmployeeController@index')->name('employees'); Route::get('/employee/create', 'EmployeeController@create')->name('employees.create');
-
5.5 Route Parameters: Route parameters should be used in place of querystring GET parameters for common, variable URI data.
Why? Route parameters are cleaner, self documenting in route definitions, core to Laravel and the Laravel Route class, and allow for dynamic population within navigation.
Bad
Route:
Route::get('/order', 'OrderController@show')->name('order.show');
Controller:
public function show (Request $request) { $orderID = $request->query('orderID'); $order = Order::findOrFail($orderID); ... }
Good
Route:
Route::get('/order/{id}', 'OrderController@show')->name('order.show');
Controller:
public function show (string $id) { $order = Order::findOrFail($id); ... }
-
5.6 AJAX Routing: AJAX routes must contain
/ajax/
within the path after the defining parent path prefix.Why? Helps to easily identify that a defined route is for AJAX (XHR) requests and adds consistency.
Bad
Route::patch('/meta/access-control/role/assign-pages', 'RoleController@assignPages');
Good
Route::patch('/meta/access-control/ajax/role/assign-pages', 'RoleController@assignPages');
-
6.1 Defining Arrays: New arrays should be defined using the bracket syntax.
Why? Same syntax across JS and PHP to keep consistency within the codebase. It's faster than calling the
array()
function (micro-optimization). It's cleaner and more concise leading to less bloat within the codebase.Bad
$fooBars = array(); $foos = array('bar', 'faz', 'baz');
Good
$fooBars = []; $foos = ['bar', 'faz', 'baz'];
-
6.2 Array Data Pushing: Use the "empty brackets" postfix syntax instead of
array_push()
whenever possible.Why? It's faster than calling the
array_push()
function, especially in loops. It's cleaner and more concise leading to less bloat within the codebase.Bad
$fooBars = ['foo']; array_push($fooBars, 'bar'); for ($i = 0; $length = 3, $i < $length, $i++) { array_push($fooBars, $i); }
Good
$fooBars = []; $fooBars[] = 'bar'; for ($i = 0; $length = 3, $i < $length, $i++) { $fooBars[] = $i; } // When inserting more than one new element it's acceptable to use `array_push()` array_push($fooBars, 'faz', 'baz');
-
7.1 Use Eloquent: Whenever possible Models and Eloquent should be used to create, read, update, and delete database table data.
Why? Models provide an incredibly powerful abstraction layers for our database tables. What this means is we can define a single source of truth for data and relationships about a table and utilize the model to interact with the table instead of SQL directly. This provides the benefit of declaring relationships with other models (tables) to easily access joined data. It also takes care of defining the table name only once, timestamps (CreatedAt, UpdatedAt, and DeletedAt), soft deletion, casting, automatic slugging, custom faux fields, and so much more.
Bad
DB::table('UserTbl')->where('userid', 1)->findOrFail(); // Join data DB::table('CustomerTbl')->leftJoin('OrderTbl', 'OrderTbl.CID', '=', 'CustomerTbl.CID')->get();
Good
User::findOrFail(1); // Join data Customer::with('order')->get(); // or with lazy loading $customer = Customer::findOrFail(1); $orders = $customer->orders;
-
7.2 Join Condition Order: When the Query Builder must be used with joins the joining table should be listed first in the
ON
join conditional.Why? Provides consistency.
Bad
DB::table('CustomerTbl')->leftJoin('OrderTbl', 'CustomerTbl.CID', '=', 'OrderTbl.CID')->get();
Good
DB::table('CustomerTbl')->leftJoin('OrderTbl', 'OrderTbl.CID', '=', 'CustomerTbl.CID')->get();
-
7.3 Specify Table with Complete Table Name: When it is required to specify a table with a field in the Query Builder you should use the full table name, not an obscure abbreviated alias.
Why? Provides clarity, easy to understand, semantic, and self documenting.
Bad
DB::table('CustomerTbl AS ct') ->leftJoin('OrderTbl AS ot', 'ot.CID', '=', 'ct.CID')->get() ->leftJoin('CustomerPhoneTbl AS cpt', function ($join) { $join->on('cpt.CID', '=', 'ct.CID'); $join->on('cpt.SeePhoneFlag', '=', DB::raw(1)); });
Good
DB::table('CustomerTbl') ->leftJoin('OrderTbl', 'OrderTbl.CID', '=', 'CustomerTbl.CID')->get() ->leftJoin('CustomerPhoneTbl', function ($join) { $join->on('CustomerPhoneTbl.CID', '=', 'CustomerTbl.CID'); $join->on('CustomerPhoneTbl.SeePhoneFlag', '=', DB::raw(1)); });