{ claus.conrad }

Laravel 8 from Scratch

https://laracasts.com/series/laravel-8-from-scratch

Course notes: Laracasts > Learn Laravel Path > Laravel 8 From Scratch

01 - An Animated Introduction to MVC

  • The router loads the controller.
  • The controller loads the information necessary to provide a response.
  • Eloquent models are a good place to store data/object-related domain knowledge and business logic.
  • The view generates the HTML structure using the data from the controller.

02 - Initial Environment Setup and Composer

03 - The Laravel Installer Tool

  • After installing the laravel/installer package (preferably globally) using Composer (and adding ~/.composer/vendor/bin/ to the PATH), you can run laravel new DIR_NAME to simplify the creation of a new project slightly.
    composer global require laravel/installer
    

04 - Why Do We Use Tools

  • In general, to solve a problem.
  • In the case of Laravel, to create a [web application](…/Web development/).
  • In the case of this course, to create a blog.

05 - How a Route Loads a View

  • Routes for the browser are defined in ./routes/web.php.
  • The callback function can return a view, or also an object (for a JSON response).

06 - Include CSS and JavaScript

  • Files in the public directory are static files available at the root of the application.
  • In real life, a CSS/JS bundler would be used to transform files in resources.
  • Add a Route::get (or presumably, other method named after a HTTP method) invocation to ./routes/web.php.

08 - Store Blog Posts as HTML Files

  • file_get_contents() is a PHP method that returns the contents of a file as a string.
  • Dynamic parts in routes are called “wildcards” and are wrapped in {} in the argument to a static Route method, e.g. Route::get('posts/{slug}'.
  • dd() is a Laravel helper method that stands for “die and dump”, useful for quick debugging.
  • ddd() is another Laravel helper method, displaying more information about the request (maybe it means “die, dump and debug”).

09 - Route Wildcard Constraints

  • Route::get() returns an object with methods that can limit (constrain) what the wildcards in the URL are allowed to match, e.g. to avoid a security issue where user input is directly translated to a path, allowing access to arbitrary files on the server:
    • where() uses a [regular expression](…/Regular expressions/) to limit what the wildcard may match
    • whereAlpha() limits a wildcard to A-Z characters
    • whereNumber() limits a wildcard to numbers

10 - Use Caching for Expensive Operations

  • Helper methods available in routes definitions:
    • cache->remember() caches data for a specific amount of seconds or until an explicit expiration date/time
      • The method both returns already cached data as well as stores not already cached data (by calling the callback in the third argument)
    • now() returns the current date/time and by calling addMinutes() etc. on it, can be used to generate a time in the future (or past)
    • redirect() generates a redirect to another URL

11 - Use the Filesystem Class to Read a Directory

  • Models are usually in the folder ./app/Models and the namespace App\Models.
  • Helper methods for getting absolute filesystem paths:
    • base_path() -> .
    • app_path() -> ./app
    • resource_path() -> ./resources
  • ModelNotFoundException is a Laravel-provided exception that can be used when a model (record) is not found (e.g. in the database, filesystem, etc.).
  • The Illuminate\Support\Facades\File facade gives static access to methods for working with the file system.
  • If the class allows it, you can cast an object to a specific type by writing the desired type in parentheses before the object, e.g.
    (string) $var
    
  • The SplFileInfo class has a method getContents() that returns the file contents as a string.
  • The PHP method array_map loops over an array, does something (callback) with each item, and returns an array of the “transformed” items.

12 - Find a Composer Package for Post Metadata

  • If you find yourself iterating over an array and filling/returning a different array, that’s a use case for array_map() - or a Laravel collection.
  • collect() is a Laravel helper method that wraps an array in a Illuminate\Support\Collection object.
  • A Collection has these methods, all of which may be chained.

13 - Collection Sorting and Caching Refresher

  • sortBy sorts a Collection by an attribute of its items, ascending. sortByDesc does the same, but sorts descending.
  • cache()->rememberForever() caches objects until they are cleared.
  • Other methods of the Cache are e.g. put, store, forget, get.

14 - Blade: The Absolute Basics

  • The Blade templating language is compiled down to PHP files stored in ./storage/framework/views.
  • Only files with names ending with .blade.php are processed by Blade.
  • The views are typically stored in ./resources/views.
  • Raw PHP code can be used in Blade templates.
  • {{ $var }} echoes a variable, sending it through htmlspecialchars to escape (sanitize) HTML.
  • {!! $var !!} echoes a variable without escaping HTML, similar to <?= $var; ?> or <?php echo $var; ?>.
    • Only use this for trusted (pre-sanitized) content to prevent XSS.
  • There are a number of Blade directives, e.g. @foreach/@endforeach, @if/@endif, @unless/@endunless.
  • The @loop variable makes it easy to style odd/even rows, treat the last/first iteration differently, etc.
  • @dd is available to die-and-dump variables.

15 - Blade Layouts Two Ways

  1. Template Inheritance
    1. Create a parent layout .blade.php file with @yield directives (where the content should go)
    2. Create a child view that @extends the layout
    3. Use the @section/@endsection directive in the child layout to provide the content
  2. Blade components
    • A Blade component wraps a piece of HTML.
    • Components are .blade.php files typically in the ./resources/views/components directory.
    • In a Blade component layout, slots are defined using the regular syntax for echoing out variables, e.g. {{ $content }}.
    • Instead of @extending a layout, a child view uses the layout with a tag similar to HTML, where the component’s name (the part of the filename before .blade.php) is prefixed with x-. For example, if the layout was in ./resources/views/components/layout.blade.php, the child view would include this layout as follows:
      <x-layout>
        ...
      </x-layout>
      
    • The variables that are echoed out in the layout can be passed through from the child view in different ways:
      • As an attribute:
        <x-layout content="...">
        </x-layout>
        
      • As a child tag:
        <x-layout>
          <x-slot name="content">
          ...
          </x-slot>
        </x-layout>
        
      • As everything within the tag, if the slot (that is echoed out in the layout component) has the special name $slot:
        <html>
        	<body>
        		{{ $slot }}
        	</body>
        </html>
        
        <x-layout>
          ...
        </x-layout>
        
  • None of the approaches is better than the other, but Blade components are newer and the remainder of the course will focus more on them.

16 - A Few Tweaks and Consideration

17 - Environment Files and Database Connections

  • With the following artisan commands, when running locally, invoke them like this:
    php artisan COMMAND
    
  • When using Sail (Docker containers managed by Laravel), invoke them like this:
    ./vendor/bin/sail artisan COMMAND
    
  • The application’s configuration files are in the ./config directory.
  • Most actual configuration values are set in the ./.env file (which is environment-specific).
  • When using Sail, connect to the database within the database container using the command ./vendor/bin/sail mysql (substitute mysql with mariadb, psql or redis as appropriate).

18 - Migrations: The Absolute Basics

  • To create the initially defined database tables, run:
    artisan migrate
    
  • Database migrations are defined in subclasses of Illuminate\Database\Migrations\Migration in ./database/migrations.
  • The up method applies the migration and the down method reverses (rolls it back).
  • Applied migrations are logged in the migrations table.
  • Migrations are applied in batches (all the new ones since the last migration was done). Rolling back reverts the complete batch. To rollback the last applied batch:
    artisan migrate:rollback
    
  • To drop and re-create the database:
    artisan migrate:fresh
    
  • The APP_ENV value “production” has a special meaning for artisan, resulting in warnings when attempting destructive operations.

19 - Eloquent and the Active Record Pattern

  • Eloquent is the official ORM of the Laravel framework.
  • Each model has a corresponding database table. Each instance of a model represents a single record in the table. This is also called Active Record pattern.
  • Tinker is a REPL (console) interface to a Laravel application, similar to the Django shell.
    • Tinkerwell is a commercial GUI REPL interface for macOS, [Windows](…/Microsoft Windows/) and Linux.
  • bcrypt() is a Laravel helper method that encrypts a string (e.g. for storing a password in the database).
  • To persist (store) a model to the database, call its save() method.
  • Eloquent automatically maintains the date/time columns created_at and updated_at.

20 - Make a Post Model and Migration

  • To create a migration to create a new table “posts”, run:
    artisan make:migration create_posts_table
    
    • Laravel uses the argument (“create_posts_table”) as part of the migration filename, but also parses the desired table name “posts”.
  • To create a model called “Post”, run:
artisan make:model Post
  • Laravel convention: Use the singular name (“Post”) for the model class and the plural, lower-cased name (“posts”) for the database table.

21 - Eloquent Updates and HTML Escaping

  • Stay with the default and escape displayed data, unless you (the developer(s)) are in control of it.

22 - 3 Ways to Mitigate Mass Assignment Vulnerabilities

  • A mass assignment vulnerability happens when a malicious user sends an unexpected field in a HTTP request (e.g. is_admin set to true) and the developer then provides all request fields to the model for storing, unintentionally allowing the user to elevate their privileges.
  • To prevent such attacks, Eloquent models have a fillable property, containing the names of the attributes thay may be “mass assigned”.
  • The reverse option is to have a guarded property in the model, containing names of attributes that may not be mass assigned.
  • A third option is never to assign a generic array to the model for storing (build up the object manually).

23 - Route Model Binding

  • If the wildcard in a route matches an argument to a callback function and that argument has a type which is an Eloquent model, Laravel assumes that the wildcard is the primary key, and automatically provides the fetched model to the callback function. For example, with the following code, with the HTTP request GET https://host:port/posts/2:
    Route::get('posts/{post}'), function (Post $post) {
      return view('post', [
        'post' => $post
      ]);
    });
    
    • The view ./resources/views/post.blade.php is loaded and the Post resulting from the query SELECT * FROM posts WHERE id = 2 is provided to it.
    • The behavior to use the primary key can be modified in the Model by overriding public function getRouteKeyName() and returning the value of the property that should be used instead.
  • If the wildcard contains a colon, the part after the colon represents the (unique) property that should be used to fetch the model, instead of its primary key. For example, with the following code, with the HTTP request GET https://host:port/posts/a-b-c:
    Route::get('posts/{post:slug}'), function (Post $post) {
      return view('post', [
    	'post' => $post
      ]);
    });
    
    • The view ./resources/views/post.blade.php is loaded and the Post resulting from the query SELECT * FROM posts WHERE slug = 'a-b-c' LIMIT 1 is provided to it.

24 - Your First Eloquent Relationship

  • To add a relation, we need another model and migration. Previously we learned artisan make:migration and artisan make:model. An easier way is to call artisan make:model -m, which will create a migration at the same time.
  • To add the “foreign key”, or pointer from one model to another, we can create a public function with the desired name - e.g. category() - and let it return a relationship:
    class Post extends Model {
      // ...
      public function category() {
        return $this->belongsTo(Category::class);
      }
    }
    
  • Now we can use $post->category (as if it was a property, thanks to magic accessors) to fetch the category pointed to by the value of the category_id column in the table.

25 - Show All Posts Associated With a Category

  • The opposite of a belongsTo() relationship is hasMany():
    class Category extends Model {
      // ...
      public function posts() {
        return $this->hasMany(Post::class);
      }
    }
    

26 - Clockwork, and the N+1 Problem

  • We can log stuff using the \Illuminate\Support\Facades\Log facade.
  • An alternative is to use Laravel’s logger() helper function.
  • The default logging location is ./storage/logs/laravel.log.
  • The \Illuminate\Support\Facades\DB facade can listen() to database queries. The argument to its callback function is a Query, with the SQL code (prepared statement) in its sql property and actual query parameters in its bindings property.
  • The Composer package itsgoingd/clockwork can be used to log database queries and more, and make them available in the browser during debugging.
    • Using the route GET /clockwork
    • Using a browser extension
  • By design, Laravel lazy-loads relationships (i.e. the reference/ID/foreign key is loaded from the referencing table, but nothing is loaded from the referenced table).
  • The method with() on an Eloquent model can be used to tell which relationship(s) we want to load together with the model to avoid the N+1 problem.

27 - Database Seeding Saves Time

  • The directory ./database/seeders contains DatabaseSeeder, which allows populating the database (e.g. after the initial migration) - making it easier to modify the database structure during development without losing the sample data necessary for testing.
  • The models factory() method can be used in a Seeder to create a number of objects with random dummy data for testing.
  • The artisan command db:seed invokes the Seeder. So does the --seed argument to the artisan command migrate:fresh.
  • The models truncate() method deletes all objects of that model (truncating the database table).

28 - Turbo Boost With Factories

  • The User Model uses the trait Illuminate\Database\Eloquent\Factories\HasFactory to add the static factory() method mentioned in the previous lesson.
  • Its behavior is defined in ./database/factories/UserFactory.php.
  • The UserFactory class has a property model indicating which model it can create data for (the User model).
  • Any new Model created with Artisan gets the HasFactory trait - but in order for the factory to work, by convention, one has to create a corresponding class in ./database/factories.
  • The artisan command make:factory can be used to create such a class.
  • The same result can be achieved by using the --factory argument for the artisan command make:model.
  • When giving artisan make:model the argument --all, in addition to the model, a migration, seeder, factory and resource controller are being scaffolded.
  • The factory method definition() should return an array with the keys and values that should be set on the “random model”.
  • The keys that reference other models (e. g. “user_id”, “category_id”, etc.) can have an invocation of the corresponding models factory method as their value, effectively creating a new dummy instance of that other model for each dummy instance of the model.
  • Individual properties of the dummy instances created by the factories’ create() method can be set to desired values by providing those as an array to the create() method. (The other properties defined in the factory class’ definition() will still be generated randomly.)

29 - View All Posts By An Author

  • Laravel uses the names of relationship functions in models to discover the foreign key by appending “_id”, for example the following code assumes that the table posts has a column user_id which also is a foreign key to another table (presumably called users):
    class Post extends Model {
      public function user() {
        return $this->belongsTo(User::class);
      }
    }
    
  • However this convention can be overridden by providing the actual name of the column/foreign key as the second argument to belongsTo().

30 - Eager Load Relationships on an Existing Model

  • Using the $with property in a Model, we can force Eloquent to always load related models (i.e., from other tables referenced via foreign keys) together with that model.
  • Beware that this results in extra queries by default, which can hurt performance if there are situations where the related data is not needed.
  • In such cases the without() method can be used to selectively disable the eager-loading of specific relationships.

31 - Convert the HTML and CSS to Blade

32 - Blade Components and CSS Grids

  • Add a colon before the attribute name to pass the value of a variable to a Blade component. Such an attribute is called a “prop” and needs to be declared at the top of the component using the directive @props().
  • Attributes (not props) can be accessed within a Blade component using the $attributes array.

    You may specify which attributes should be considered data variables using the @props directive at the top of your component’s Blade template. All other attributes on the component will be available via the component’s attribute bag.

    Source

  • Timestamps are instances of Carbon, which builds upon PHP functions to simplify working with date/time objects (similar to Moment.js in JavaScript).
  • Carbon provides the method diffForHumans() to format the relative difference between now and a timestamp in a human-readable way (e.g. “1 day ago”).
  • The skip() method can be used to skip the first X items of a collection when iterating over its items.

33 - Convert the Blog Post Page

34 - A Small JavaScript Dropdown Detour

  • Add style="display:none" to avoid flashing content that would be hidden by JavaScript after the page is loaded.
  • The ucwords() PHP function converts the first character of each word in a string to uppercase.
  • The method is() on a model instance can be used to compare it to another instance using the primary key, presumably because the === operator does not work if the database row has been fetched twice.

35 - How to Extract a Dropdown Blade Component

  • When using a Blade component, all children that are not wrapped in <x-slot>...</x-slot> get rendered in the default slot called $slot.
  • The Request instance returned by the request() helper can be used to inspect the request.
    • Its is() method compares the current request path to a string (which may contain wildcards).
    • It may be safer to use its routeIs() method to compare it to a named route.

36 - Quick Tweaks and Clean-Up

  • {{-- ... --}} comments out stuff in Blade templates.
  • The PHP method implode() joins array elements with a string and returns a string.

37 - Search (The Messy Way)

  • The get() method, typically at the end of a chain of Eloquent calls, can be interpreted as “We are done, execute the SQL queries”.

38 - Search (The Cleaner Way)

  • Controllers can be scaffolded with the artisan command make:controller. By convention, their name ends with Controller and starts with a singular noun. They are located in the ./app/Http/Controllers directory.
  • Instead of a callback function, the second argument to the Route methods (get, etc.) can be an array with the Controller class and the name of the controller method that should be called for this route.
  • Query scopes add methods to Eloquent models that filter the rows queried from the database. The names of such methods have to start with scope, e.g. public function scopeFilter(). When using this (assumed) method in an Eloquent query, only the part after scope, with the first letter lower-cased, is used, e.g.:
    MyModel::latest()->filter();
    
    • Query scope methods receive the current query as an argument.
    • They are equal to filtering the results “manually” using the where() method, but make the code easier to read (and encourage reuse).
    • The argument (e.g. $query) can be modified as is, and does not have to be returned by the query scope method.
  • If the request() helper is provided an array of strings, it returns an associative array of those keys from the request with their corresponding values.
  • Alternatively, the request returned from request() (when called without arguments) has an only() method that also returns an array of specific keys/values from the request.
  • Eloquent queries have a when() method that evaluates if a condition is truthy and only then invokes a callback with the query and the “truthy value” (e.g. to modify the query if the user has entered a search term).

39 - Advanced Eloquent Query Constraints

  • The second argument to the Eloquent where() method is expected to be a value. If it should reference a column, use whereColumn() instead.
  • whereHas() is an easier method to filter by a value from a referenced table.
  • The where() and first() methods can be combined into firstWhere().

40 - Extract a Category Dropdown Blade Component

  • The artisan command make:component creates a component (*.blade.php file) and associates a PHP class in the directory ./app/View/Components with it.
  • That class has a render() method that can provide a view model to a component, similar to how a route provides one to a view.
  • You should typically follow the naming convention CONTROLLER (PLURAL) . ACTION FUNCTION for views, i.e. if the Controller is called “PostController” and the function (as indicated by the route) is called “show”, the view should be called “posts.view”.
  • Each dot represents a directory, i.e. the view “posts.view” corresponds to the template ./resources/views/posts/view.blade.php.

41 - Author Filtering

42 - Merge Category and Search Queries

  • The PHP function http_build_query converts an array to a querystring.
  • The except() method of the Request object (as e.g. returned by the request() helper) returns all request parameters except the one(s) with the provided name(s).

43 - Fix a Confusing Eloquent Query Bug

  • To group multiple WHERE clauses in parentheses (in the resulting SQL query), group them in a where() method and provide a closure (anonymous function) that receives the $query as its sole argument and adds the where() calls that should be grouped.

44 - Laughably Simple Pagination

  • When you return an Eloquent object from a route, Laravel converts it to JSON.
  • The view()->paginate() method takes the request param ?page into account, and provides all the logic (I18N strings, current and total page number, etc.) required to display a pager.
  • The Eloquent model has a links() method that returns pre-rendered HTML for a pager, including Tailwind classes.
  • The artisan command vendor:publish copies the resource files shipped by a Composer package from the ./vendor/... directory to the ./resources/... directory, so they can be customized for the app.
  • The boot() method in the AppServiceProvider class (in ./app/Providers/AppServiceProvider.php) can be used to configure application-wide settings, such as whether the Paginator should use Bootstrap or Tailwind for styling.
  • The view()->simplePaginate() method takes the request param ?page into account, and provides logic to display a simple pager (just “Previous” and “Next”, no page numbers - which also makes it a bit more performant).
  • The Paginator method withQueryString() takes the filters in the querystring into account when rendering the pager.

45 - Build a Register User Page

  • The Request’s validate() method accepts an associative array of form fields and validation rules. Multiple validation rules can be separated by the | character or provided as an array of multiple strings, e.g.
    request()->validate([
    	'name' => 'required|max:255',
    	'password' => ['required', 'min:7']
    ]);
    
  • If validation…
    • fails: Laravel redirects the user back to the form;
    • succeeds: the method returns the validated attributes.
  • The @csrf (Blade) directive generates and outputs a token to prevent CSRF attacks as a hidden form input field.

46 - Automatic Password Hashing With Mutators

  • Illuminate\Support\Facades\Hash::check() checks whether some plain text matches a hashed string.
  • Side notes about facades:
    • A facade is a class wrapping a complex library to provide a simpler and more readable interface to it.

    • Facades provide a “static” interface to classes that are available in the application’s service container. Laravel ships with many facades which provide access to almost all of Laravel’s features.

    • Laravel facades serve as “static proxies” to underlying classes in the service container, providing the benefit of a terse, expressive syntax while maintaining more testability and flexibility than traditional static methods.

  • An Eloquent mutator “pipes” a value through a certain function whenever it is set (stored).
    • By convention, a mutator function is called setATTRIBUTE_NAMEAttribute, e.g. “setPasswordAttribute” for a function that hashes a password before storing it.
    • It should set $this->attributes['ATTRIBUTE_NAME'] to the mutated value.
  • The opposite of a mutator is an accessor.
    • Such functions are named getATTRIBUTE_NAMEAttribute, e.g. “getUsernameAttribute”.
    • They should return the “demutated”, desired value.

47 - Failed Validation and Old Input Data

  • The @error Blade directive (followed by @enderror) provides validation errors for the form input with the given name.
  • Inside those two directives, the $message attribute can be rendered to output the actual error message.
  • The helper method old() provides the “old” values (from the previous form submission) so the invalid form can be re-rendered without the user having to type everything again.
  • The variable $errors is set to an “error bag” containing all validation errors that occurred during a form submission.
    • It always exists, even if there are no validation errors.
    • Its all() method returns all errors, which can then be iterated over, e.g. with a foreach statement, to show all errors in one place.
    • Its any() method returns whether there are any errors to display.

48 - Show a Success Flash Message

  • The session()->flash() method stores something (e.g. flash messages) in the session for one page request only.
  • The session() helper method or the session()->get() method retrieve a value with a given key from the session storage.
  • The redirect() method can be chained with a with() method, which is shorthand for flashing a message.

49 - Login and Logout

  • The auth() helper is functionally equivalent to the Auth facade.
  • auth()->login($user) logs the provided user in.
  • Middleware does something with requests before they reach the core of your application logic.
  • The middleware() method (e.g. in ./routes/web.php) adds middleware to a route.
  • The middleware 'guest' ensures that a route does not work for logged-in users.
  • Two types of middleware:
    • Global, run for all requests, specified in the property $middlewareGroups in ./app/Http/Kernel.php
    • Route-specific, can optionally be added to a route, specified in the property $routeMiddleware in ./app/Http/Kernel.php
  • The Blade directive @guest/@endguest renders content for anonymous users only, while @auth/@endauth renders content for signed-in users only.
  • The signed-in user can be retrieved using auth()->user().
  • auth()->logout() destroys the user’s session.

50 - Build the Log In Page

  • auth()->attempt() attempts to log the user in with the provided credentials.
  • The back() helper redirects to the previous URL, maybe ->withErrors() (a developer-provided array of form elements and corresponding error messages) and ->withInput() (the user’s input to the original form).
  • An even simpler approach is to throw a ValidationException using its static constructor ::withMessages and provide the form elements that fail validation and corresponding error messages, which will automatically flash the user’s original input to the session for redisplay.
  • To avoid the attack called “session fixation”, a user’s session ID should be regenerated whenever the user gets logged in.

51 - Laravel Breeze Quick Peek

  • Laravel Breeze is an authentication starterkit that provides predesigned Blade templates styled with Tailwind CSS.
  • It can be installed into an existing project using composer require laravel/breeze --dev followed by artisan breeze:install.

52 - Write the Markup for a Post Comment

53 - Table Consistency and Foreign Key Constraints

  • In migrations:
    • The id() method creates columns of the type “unsignedBigInteger”, so a “manually created” foreign key column that references another table’s id column - if created by the id() method - must be of that type.
    • The foreignId() method automatically creates a column of the correct type.
    • The chainable constrained() method automatically references the correct table for a foreign key, if the convention of calling the column OTHERTABLENAME_id is followed. Otherwise the name of the referenced table should be provided.
    • The chainable cascadeOnDelete() method is shorthand for onDelete('cascade').

54 - Make the Comments Section Dynamic

55 - Design the Comment Form

  • When you find yourself reusing the same Tailwind classes over and over, consider extracting them to a Blade component.

56 - Activate the Comment Form

  • If possible, when creating Controller methods, try to stick to “the seven RESTful actions”:
    • index
    • show
    • create
    • store
    • edit
    • update
    • destroy
  • As an alternative to using the request() helper method, one can inject the Request class into a Controller methods arguments.
  • To avoid being protected from the mass assignment injection attack, either
    • Set $guarded = []; in models, or
    • call Model::unGuard() e.g. in AppServiceProvider->boot().
    • When doing this, remember not to create/update models using all request parameters (without filtering them).

57 - Some Light Chapter Clean Up

  • Convention: Begin the filename of a partial view (that is included in another Blade file) with an underscore.

[58 - Mailchimp API Tinkering]

  • All configuration is kept in files in the ./config directory.
  • API tokens for external services are usually kept in ./config/services.php.
  • The config() helper provides access to values from the files in the ./config directory, using dot notation - e.g. if to retrieve the value of the key key in the array mailchimp in the file ./config/services.php, one would ask for config('services.mailchimp.key').

59 - Make the Newsletter Form Work

60 - Extract a Newsletter Service

  • Automatic resolution is a Laravel feature to inject dependencies into methods in many places instead of having to instantiate them manually, e.g. some Service into a Route’s action method.
  • The null-safe assignment operator ??= assigns the right-side value to the left-side variable only if the left-side variable is null.
  • When specifying a Controller to handle a route without specifying an action (method) name, the Controller’s __invoke() method will be called. We could call these “single-action Controllers”.

61 - Toy Chests and Contracts

  • If automatic resolution fails because a constructor requires a parameter that can’t simply be instantiated, Laravel will throw a \Illuminate\Contracts\Container\BindingResolutionException.
  • The AppServiceProvider->register() method is a logical place to to register objects into the service container using app()->bind().
  • Objects can be retrieved from the service container using app()->get() or the shorthand helper method resolve().

62 - Limit Access to Only Admins

  • The class \Symfony\Component\HttpFoundation contains constants for all HTTP status codes.

63 - Create the Publish Post Form

64 - Validate and Store Post Thumbnails

  • Files uploaded by users are instances of \Illuminate\Http\UploadedFile.
  • Filesystem “disks” are configured in ./config/filesystems.php and can be local, accessed via SFTP, on AWS S3 etc.
  • artisan storage:link creates symbolic links from the ./public directory (the served webroot) to stored files. These links are also configured at the bottom of ./config/filesystems.php.
  • The asset() helper method prepends the protocol and domain to a given path.

65 - Extract Form-Specific Blade Components