Building Web Apps with Laravel Module 1 – Start here: Laravel 13 in 2026
Project structure, configuration and Artisan
Follow a request from public/index.php, find where each file of a Laravel 13 project lives, read settings with config() and see why config:cache empties env().
What you will learn
- Trace a web request from public/index.php through the kernel, the middleware and the router to the code that answers it
- Locate the folder of each kind of file in a Laravel 13 project, and predict where a make command writes a new class
- Configure a setting with .env and a config file, and read it in code with config()
- Explain why env() outside the config files returns null after config:cache, and fix code that depends on it
- Inspect a project's routes, settings and tables with route:list, config:show, db:show and Tinker
Before you start
What you’ll learn · Before you start
What you will learn
- Trace a web request from public/index.php through the kernel, the middleware and the router to the code that answers it
- Locate the folder of each kind of file in a Laravel 13 project, and predict where a make command writes a new class
- Configure a setting with .env and a config file, and read it in code with config()
- Explain why env() outside the config files returns null after config:cache, and fix code that depends on it
- Inspect a project's routes, settings and tables with route:list, config:show, db:show and Tinker
Before you start
On this page
A new Laravel 13 project keeps 33 files of its own in seven folders, and the framework finds every one of them by convention, without a line of setup from you. Every web request, even one for a page that does not exist, enters through one file, and every setting that differs between your laptop and a server comes from one other file, .env.
Here you follow one request from public/index.php to the response, map the folders and the make commands that fill them, carry a setting from .env through a config file into your code, and reproduce the deploy-day bug in which env() returns null. The project is Pathshala, the coaching-centre app created in the install lesson.
Every request enters through public/index.php
Ask the development server for two URLs: /up, the health check that the skeleton defines, and /no-such-page, which nothing defines. Both are answered by Laravel:
from session import serve
# Two requests to the development server: a URL the skeleton defines (/up, its health check) and one that nothing
# defines. Both are answered by Laravel, through public/index.php; only the status code and the path are printed.
serve(
"""
curl -s -o /dev/null -w "%{http_code} /up\\n" http://127.0.0.1:8125/up
curl -s -o /dev/null -w "%{http_code} /no-such-page\\n" http://127.0.0.1:8125/no-such-page
""",
port=8125,
) Output
# Terminal 1: start the server
$ php artisan serve --host=127.0.0.1 --port=8125 --no-reload
# Terminal 2: send requests
$ curl -s -o /dev/null -w "%{http_code} /up\n" http://127.0.0.1:8125/up
200 /up
$ curl -s -o /dev/null -w "%{http_code} /no-such-page\n" http://127.0.0.1:8125/no-such-page
404 /no-such-page
# Terminal 1 showed this, then Ctrl+C stopped the server:
INFO Server running on [http://127.0.0.1:8125].
Press Ctrl+C to stop the server
… /up ............................................... ~ …
… /no-such-page ..................................... ~ …
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 front_door_session.py
/up answered 200 and /no-such-page answered 404, and the server’s own terminal logged both. Neither URL is a file in the project. The web server’s document root is public/, and every path that is not a real file there goes to public/index.php, the front controller: The one PHP file that every web request enters through, public/index.php in Laravel. It loads Composer's autoloader, builds the application and hands the request to the framework., which hands the request to Laravel. The file is short, and four of its lines do the work:
from session import run
# The lines of public/index.php that load the framework, the route files bootstrap/app.php wires in, and the
# application's own list of service providers.
run(
"""
grep -n -e require -e handleRequest public/index.php
grep -n -e "web:" -e "commands:" -e "health:" bootstrap/app.php
cat bootstrap/providers.php
""",
app=True,
) Output
$ grep -n -e require -e handleRequest public/index.php
10: require $maintenance;
14:require __DIR__.'/../vendor/autoload.php';
18:$app = require_once __DIR__.'/../bootstrap/app.php';
20:$app->handleRequest(Request::capture());
$ grep -n -e "web:" -e "commands:" -e "health:" bootstrap/app.php
10: web: __DIR__.'/../routes/web.php',
11: commands: __DIR__.'/../routes/console.php',
12: health: '/up',
$ cat bootstrap/providers.php
<?php
use App\Providers\AppServiceProvider;
return [
AppServiceProvider::class,
];
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 entry_files_session.py
Line 10 serves the maintenance page while php artisan down is in effect. Line 14 loads Composer’s autoloader, so PHP can find any class by its name. Line 18 runs bootstrap/app.php, which builds the application and names its route files: web for pages, commands for console commands and health: '/up', the URL that answered 200. Line 20 passes the request to the framework, and bootstrap/providers.php lists the app’s own service providers, only AppServiceProvider so far. Step through what happens after line 20, and watch where the 404 left the path:
The drawing shows the whole scene; its 9 steps are listed below.
Every step as text
- One file receives every URL. The web server hands every request to public/index.php, /up and /no-such-page alike. Its first lines look for storage/framework/maintenance.php, the file that php artisan down writes, and answer from it when it exists.
- The autoloader, then the app. Line 14 loads Composer's autoloader, so PHP can find any class by its name. Line 18 runs bootstrap/app.php, which builds the application object with the route files and the middleware settings it names.
- Bootstrappers run in order. handleRequest passes the request to the HTTP kernel, whose bootstrappers run first and always in the same order. They load .env, load the config files, set up error handling, register the facades, then register and boot the service providers.
- Providers register, then boot. Every provider's register method runs before any provider's boot method, so boot code can use whatever the others registered. Your own providers are listed in bootstrap/providers.php.
- Global middleware runs. Middleware that every request passes runs next, before any route is chosen. It includes the maintenance-mode check and the trimming of strings in the input.
- The router looks for a route. The router compares the method and the path with every route it knows. For /no-such-page it finds none, so the request goes straight to a 404 response.
- The web group wraps the route. A route from routes/web.php also passes the web middleware group. It encrypts cookies, starts the session, shares validation errors with the views and runs the CSRF check, PreventRequestForgery.
- Your code returns a response. The route's closure or controller action runs and returns a response, such as a view, a redirect or JSON. For /up, the health route that bootstrap/app.php names answers with status 200.
- The response goes back out. The response passes back out through the same middleware, which may add cookies or headers, and handleRequest calls send() to write it to the browser.
The order matters for the rest of this module. Configuration is loaded before any provider boots, providers boot before any route is matched, and the web middleware group, with the session and the CSRF check, wraps only the routes of routes/web.php.
Pointing the web server at the project folder
A server whose document root is the project folder, not public/, can hand .env and other configuration files to anyone who asks for them. Laravel’s deployment guide says to send every request to public/index.php and never to move index.php to the project root.
Checkpoint
Check: The one entry point and the request path
Your code lives in app, and every other folder has one job
Before you add anything, list every file that a new project keeps in its working folders. vendor/, where Composer put the 109 packages, storage/, where the framework writes while it runs, and public/ are left out:
from session import run
# Every file a new project keeps in its seven working folders (vendor/, storage/ and public/ are left out).
run(
"""
find app bootstrap config database resources routes tests -type f | sort
find app bootstrap config database resources routes tests -type f | wc -l
""",
app=True,
) Output
$ find app bootstrap config database resources routes tests -type f | sort
app/Http/Controllers/Controller.php
app/Models/User.php
app/Providers/AppServiceProvider.php
bootstrap/app.php
bootstrap/cache/.gitignore
bootstrap/cache/packages.php
bootstrap/cache/services.php
bootstrap/providers.php
config/app.php
config/auth.php
config/cache.php
config/database.php
config/filesystems.php
config/logging.php
config/mail.php
config/queue.php
config/services.php
config/session.php
database/.gitignore
database/database.sqlite
database/factories/UserFactory.php
database/migrations/0001_01_01_000000_create_users_table.php
database/migrations/0001_01_01_000001_create_cache_table.php
database/migrations/0001_01_01_000002_create_jobs_table.php
database/seeders/DatabaseSeeder.php
resources/css/app.css
resources/js/app.js
resources/views/welcome.blade.php
routes/console.php
routes/web.php
tests/Feature/ExampleTest.php
tests/TestCase.php
tests/Unit/ExampleTest.php
$ find app bootstrap config database resources routes tests -type f | wc -l
33
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 files_session.py
Of the 33 files, only three are PHP classes in app/: the base Controller, the User model and AppServiceProvider. The rest are configuration, routes, database files, views and tests, each in the folder where the framework looks for that kind of file. The table is the map for the rest of this track:
| Folder | What lives there | Who adds files |
|---|---|---|
app/Http/Controllers |
Classes that answer requests | make:controller |
app/Models |
Eloquent models, one per table | make:model |
app/Providers |
Service providers, run at boot | make:provider |
bootstrap |
app.php, providers.php, cache/ |
the framework |
config |
One file per area, 10 in all | you, or make:config |
database |
Migrations, factories, seeders, SQLite file | make:migration and others |
resources |
Blade views, CSS and JavaScript sources | you, or make:view |
routes |
web.php for pages, console.php for commands |
you |
tests |
Feature and Unit tests |
make:test |
public |
index.php and files the browser may fetch |
the framework and the build |
storage |
Logs, compiled views, sessions, caches | the framework |
vendor |
Composer’s packages | Composer only |
Laravel’s directory guide adds two rules that explain the paths. Composer loads app/ under the App namespace, so app/Models/User.php is the class App\Models\User. And folders such as app/Policies do not exist until a make command first needs them.
Fixing a bug inside vendor/
An edit to a file under vendor/ works until the next composer install or composer update writes the folder again, and it never reaches your repository, because the skeleton’s .gitignore leaves vendor/ out. Put the change in your own code under app/.
Artisan make commands put each new class in its folder
Pathshala groups its students into batches, and a batch needs a model, a table, a controller and more. One Artisan: Laravel's command-line tool, run as php artisan inside the project folder. Its commands create files (make:model), show what the app sees (route:list, about) and manage caches (config:cache). command writes the starting file of each:
from session import run
# One make:model with --all, the model it wrote, the seven empty actions of its controller, and the routes after it.
run(
"""
php artisan make:model Batch -a
cat app/Models/Batch.php
grep -n "public function" app/Http/Controllers/BatchController.php
php artisan route:list
""",
app=True,
) Output
$ php artisan make:model Batch -a
INFO Model [app/Models/Batch.php] created successfully.
INFO Factory [database/factories/BatchFactory.php] created successfully.
INFO Migration [database/migrations/YYYY_MM_DD_HHMMSS_create_batches_table.php] created successfully.
INFO Seeder [database/seeders/BatchSeeder.php] created successfully.
INFO Request [app/Http/Requests/StoreBatchRequest.php] created successfully.
INFO Request [app/Http/Requests/UpdateBatchRequest.php] created successfully.
INFO Controller [app/Http/Controllers/BatchController.php] created successfully.
INFO Policy [app/Policies/BatchPolicy.php] created successfully.
$ cat app/Models/Batch.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Batch extends Model
{
/** @use HasFactory<\Database\Factories\BatchFactory> */
use HasFactory;
}
$ grep -n "public function" app/Http/Controllers/BatchController.php
14: public function index()
22: public function create()
30: public function store(StoreBatchRequest $request)
38: public function show(Batch $batch)
46: public function edit(Batch $batch)
54: public function update(UpdateBatchRequest $request, Batch $batch)
62: public function destroy(Batch $batch)
$ php artisan route:list
GET|HEAD / ............................................... routes/web.php:5
GET|HEAD storage/{path} storage.local › vendor/laravel/framework/src/Illum…
PUT storage/{path} storage.local.upload › vendor/laravel/framework/sr…
GET|HEAD up vendor/laravel/framework/src/Illuminate/Foundation/Configurati…
Showing [4] routes
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 make_model_session.py
-a, short for --all, wrote 8 files into 7 folders, and two of the folders, app/Http/Requests and app/Policies, were new. Every path follows from the table above, which is how you can predict where any make command writes. The migration’s name begins with the moment it was created, shown in the recording as YYYY_MM_DD_HHMMSS, and that stamp is the order in which migrations run. The model is an empty class that uses HasFactory, and the controller has the seven resource actions, each with an empty body.
The last command is the one to remember: route:list still shows the same 4 routes. A make command such as this one writes classes from templates and adds no route, so no URL reaches BatchController until you add a route, which the first CRUD lesson does in one line. php artisan make:model --help lists the smaller options: -m for a migration, -f a factory, -s a seeder, -c a controller, -r a resource controller, -R form requests and --policy a policy.
Key idea
Every kind of file has one folder, and make commands write into it, so a path such as app/Policies/BatchPolicy.php tells you what a file is before you open it.
Checkpoint
Check: Which folder holds which file · What make commands write
A setting travels from .env through a config file to config()
Pathshala prints a centre code on every receipt, and each centre has its own. The code must not be written into the PHP, yet the app must read it like any other setting. Laravel splits the job between two files. The environment file: The .env file in a Laravel project. It holds the values that differ from one computer to the next, such as the database settings, the debug flag and the application key, and stays out of version control., .env, holds the values that differ between computers. A file in config/ decides which settings exist, which variable feeds each one and what the default is:
<?php
return [
/*
|--------------------------------------------------------------------------
| Centre code
|--------------------------------------------------------------------------
|
| The code printed on Pathshala's receipts. Every centre sets its own
| CENTRE_CODE in .env; a project without one uses PSH-000.
|
*/
'centre_code' => env('CENTRE_CODE', 'PSH-000'),
/*
|--------------------------------------------------------------------------
| Batch size
|--------------------------------------------------------------------------
|
| How many students one batch may hold. Values from .env arrive as
| strings, so the cast makes it a whole number.
|
*/
'batch_size' => (int) env('BATCH_SIZE', 30),
]; The session adds the centre’s line to .env, then reads both settings and the app’s name:
from session import run
# A setting from .env, read through config/pathshala.php (the overlay), then a real environment variable that wins
# over the line in .env.
run(
"""
echo CENTRE_CODE=PSH-042 >> .env
php artisan tinker --execute="var_dump(config('pathshala.centre_code'), config('pathshala.batch_size'), config('app.name'));"
php artisan config:show pathshala
export APP_NAME=Pathshala
php artisan tinker --execute="var_dump(config('app.name'));"
""",
app=True,
overlay=("config-file",),
) Output
$ echo CENTRE_CODE=PSH-042 >> .env
$ php artisan tinker --execute="var_dump(config('pathshala.centre_code'), config('pathshala.batch_size'), config('app.name'));"
string(7) "PSH-042"
int(30)
string(7) "Laravel"
$ php artisan config:show pathshala
pathshala ..................................................................
centre_code ........................................................ PSH-042
batch_size .............................................................. 30
$ export APP_NAME=Pathshala
$ php artisan tinker --execute="var_dump(config('app.name'));"
string(9) "Pathshala"
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 config_value_session.py
config('pathshala.centre_code') names the file, then the key, and returned PSH-042 from .env. BATCH_SIZE is set nowhere, so the default applied, and the (int) cast made it int(30). config:show pathshala prints the same two values as the framework resolved them. The last two commands show the rule that surprises people: .env says APP_NAME=Laravel, but a variable that the real environment already holds wins, so after export APP_NAME=Pathshala the app’s name is Pathshala. That is how a server can set values without changing .env.
Without a cast, every .env value reaches the config file as a string, except a few reserved words that the configuration guide lists:
In .env |
What env() returns |
|---|---|
true or (true) |
the boolean true |
false or (false) |
the boolean false |
empty or (empty) |
an empty string |
null or (null) |
null |
anything else, such as 25 |
a string, such as "25" |
Keep .env out of Git, and .env.example in it
.env holds this computer’s values, the application key and database passwords among them, and the configuration guide says not to commit it. Commit .env.example instead, with every variable the app needs and a placeholder value, so a teammate knows to set CENTRE_CODE.
Checkpoint
Check: From .env to config()
After config:cache, env() outside the config files returns null
A controller that prints receipts reads env('CENTRE_CODE') directly. It works on the laptop. On the server, the deploy runs php artisan config:cache, as Laravel’s deployment guide recommends, and from then on the receipts have no centre code. The session reproduces it with the same config file:
from session import run
# env() and config() before and after config:cache. One tinker reads both values, config:cache writes the cached
# file, about --only=cache reports the state, and the second tinker shows what env() returns once the cache is in
# place.
run(
"""
echo CENTRE_CODE=PSH-042 >> .env
php artisan tinker --execute="var_dump(env('CENTRE_CODE'), config('pathshala.centre_code'));"
php artisan config:cache
php artisan about --only=cache
php artisan tinker --execute="var_dump(env('CENTRE_CODE'), config('pathshala.centre_code'));"
""",
app=True,
overlay=("config-file",),
) Output
$ echo CENTRE_CODE=PSH-042 >> .env
$ php artisan tinker --execute="var_dump(env('CENTRE_CODE'), config('pathshala.centre_code'));"
string(7) "PSH-042"
string(7) "PSH-042"
$ php artisan config:cache
INFO Configuration cached successfully.
$ php artisan about --only=cache
Cache ......................................................................
Config .............................................................. CACHED
Events .......................................................... NOT CACHED
Routes .......................................................... NOT CACHED
Views ........................................................... NOT CACHED
$ php artisan tinker --execute="var_dump(env('CENTRE_CODE'), config('pathshala.centre_code'));"
NULL
string(7) "PSH-042"
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 cache_session.py
Before the cache, both calls return PSH-042. config:cache writes every value to one file, bootstrap/cache/config.php, and about --only=cache now reports Config as CACHED. After that, env('CENTRE_CODE') is NULL while config('pathshala.centre_code') is still PSH-042. The configuration cache: The file bootstrap/cache/config.php that php artisan config:cache writes, with every configuration value already worked out. While it exists, Laravel does not read .env, so env() outside the config files no longer sees its values. also freezes the values when the real environment itself changes:
from session import run
# While the configuration is cached, the real environment and config() keep separate values until config:clear
# removes the cache. The session caches a .env with CENTRE_CODE=PSH-042, then exports CENTRE_CODE=PSH-099 and
# reads both again, then clears the cache and reads them once more.
run(
"""
echo CENTRE_CODE=PSH-042 >> .env
php artisan config:cache
export CENTRE_CODE=PSH-099
php artisan tinker --execute="var_dump(env('CENTRE_CODE'), config('pathshala.centre_code'));"
php artisan config:clear
php artisan tinker --execute="var_dump(env('CENTRE_CODE'), config('pathshala.centre_code'));"
""",
app=True,
overlay=("config-file",),
) Output
$ echo CENTRE_CODE=PSH-042 >> .env
$ php artisan config:cache
INFO Configuration cached successfully.
$ export CENTRE_CODE=PSH-099
$ php artisan tinker --execute="var_dump(env('CENTRE_CODE'), config('pathshala.centre_code'));"
string(7) "PSH-099"
string(7) "PSH-042"
$ php artisan config:clear
INFO Configuration cache cleared successfully.
$ php artisan tinker --execute="var_dump(env('CENTRE_CODE'), config('pathshala.centre_code'));"
string(7) "PSH-099"
string(7) "PSH-099"
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 cache_env_clear_session.py
After export CENTRE_CODE=PSH-099, env() sees the new value at once, but config() keeps PSH-042 until config:clear removes the cache. Step through the same requests to see where each value comes from:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- A request with no cache. Before any configuration is read, the first bootstrapper loads .env into the environment of the process. A variable the server had already set keeps its own value, which is why the exported APP_NAME won over the line in .env.
- The config file calls env(). The next bootstrapper runs every file in config/. pathshala.php calls env('CENTRE_CODE', 'PSH-000') and gets PSH-042. BATCH_SIZE is in neither place, so its default, 30, is used.
- Your code asks config(). config('pathshala.centre_code') names the file, then the key, and returns PSH-042. Your code never reads .env itself.
- config:cache freezes the values. php artisan config:cache runs every config file once, env() calls included, and writes the results to bootstrap/cache/config.php.
- The next request skips .env. The bootstrapper that loads .env sees the cache file and returns at once. The line CENTRE_CODE=PSH-042 is still in .env, but nothing reads it any more.
- config() answers from the cache. config('pathshala.centre_code') still returns PSH-042, now read from the cache file instead of from config/pathshala.php.
- env() in your code gets NULL. env('CENTRE_CODE') called anywhere outside config/ looks only at the real environment of the process, and CENTRE_CODE is not there, so it returns null.
- A server variable waits. Export CENTRE_CODE=PSH-099 on the server and env() sees PSH-099 at once, but config() keeps PSH-042 until php artisan config:clear, or the next config:cache, reads the configuration again.
The cause is one early return in the framework. LoadEnvironmentVariables, the bootstrapper that loads .env, stops at once when the configuration is cached, so .env is never read and env() sees only the variables of the real environment. The config files ran their env() calls when the cache was built, which is why their values survive. The configuration guide states the rule that follows:
Key idea
Only config files call env(); everything else calls config(). With that rule, caching the configuration changes how fast settings load, never what they are.
The cache is for servers. The guide says not to run config:cache while you develop, because every change to a setting would then need a new cache, and php artisan optimize caches the configuration, events, routes and views together for a deploy.
env() in a controller
$code = env('CENTRE_CODE'); in a controller works until the first config:cache, then returns null. Read the setting through its config file instead, as $code = config('pathshala.centre_code');, and the cache makes no difference to your code.
Checkpoint
Check: config:cache and env()
Four Artisan commands show what the framework sees
Three questions come up every day: is the route registered, which settings are in force, and what is in the database? Each has a one-line answer, and a fourth command runs a line of PHP inside the app:
from session import run
# Three questions about a project, each answered by one Artisan command: which routes exist, which session settings
# are in force, and which tables the database holds. Then one line of PHP in Tinker.
run(
r"""
php artisan route:list --except-vendor
php artisan config:show session
php artisan db:show
php artisan tinker --execute="echo App\Models\User::count(), ' users', PHP_EOL;"
""",
app=True,
) Output
$ php artisan route:list --except-vendor
GET|HEAD / .......................................... routes/web.php:5
Showing [1] routes
$ php artisan config:show session
session ....................................................................
driver ............................................................ database
lifetime ............................................................... 120
expire_on_close ...................................................... false
encrypt .............................................................. false
files ........................................... storage/framework/sessions
connection ............................................................ null
table ............................................................. sessions
store ................................................................. null
lottery ⇁ 0 .............................................................. 2
lottery ⇁ 1 ............................................................ 100
cookie ..................................................... laravel-session
path ..................................................................... /
domain ................................................................ null
secure ................................................................ null
http_only ............................................................. true
same_site .............................................................. lax
partitioned .......................................................... false
serialization ......................................................... json
$ php artisan db:show
SQLite .............................................................. 3.53.4
Connection .......................................................... sqlite
Database .......................................... database/database.sqlite
Host .......................................................................
Port .......................................................................
Username ...................................................................
URL ........................................................................
Open Connections ...........................................................
Tables ................................................................... 9
Schema / Table ........................................................ Size
main / cache ............................................................. —
main / cache_locks ....................................................... —
main / failed_jobs ....................................................... —
main / job_batches ....................................................... —
main / jobs .............................................................. —
main / migrations ........................................................ —
main / password_reset_tokens ............................................. —
main / sessions .......................................................... —
main / users ............................................................. —
$ php artisan tinker --execute="echo App\Models\User::count(), ' users', PHP_EOL;"
0 users
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 inspect_session.py
route:list --except-vendor hides the routes that the framework itself registers and leaves the one route of routes/web.php; without the option the list had 4. config:show session prints the session settings as resolved: the database driver and a lifetime of 120 minutes, both set in .env. db:show names the connection and lists 9 tables. The skeleton’s three migrations build eight of them, from users to failed_jobs, and migrations records which migrations have run. Tinker: A prompt opened with php artisan tinker, where each PHP line you type runs inside your loaded application: read a config() value, call a helper or query a model. ran one line against the loaded app and counted 0 users; without --execute, php artisan tinker opens a prompt where you type such lines one after another.
php artisan list shows every command, and php artisan help followed by a command’s name describes it with its options, as make:model --help did above. The install lesson’s php artisan about adds the summary of versions, drivers and caches.
Checkpoint
Check: Commands that inspect a project
Practise: from the front door to a cached setting
Try these on your own Pathshala project, then take the self-check below, which starts with the idea you have shown least.
- Run
php artisan serve, open/upand a URL that does not exist, and find both requests in the server’s terminal. - Before you run
php artisan make:model Student -mf, write down the two extra files it will create and their folders. Then compare with its INFO lines. - Create
config/pathshala.phpfrom this lesson, addCENTRE_CODEto.envand a placeholder to.env.example, and check the value withphp artisan config:show pathshala. - Run
php artisan config:cache, read the value withenv()and withconfig()in Tinker, then runphp artisan config:clear, so the cache does not stay on while you develop.
Practice that adapts to you
Practise this lesson
13 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher): why does env() return null after php artisan config:cache, and how do you avoid it?
config:cache runs every config file once and writes the results to bootstrap/cache/config.php. While that file exists, the bootstrapper that loads .env returns early, so the variables in .env never reach the process, and env() sees only real environment variables, the ones the server or the shell set. A name that exists only in .env, such as CENTRE_CODE, therefore gives null. Values read through config() are unaffected, because the config files called env() while the cache was built and the results are stored in it. The fix is a rule for the whole codebase: call env() only in config files, and read every setting elsewhere with config('file.key'). Then the cache makes settings faster to load and changes nothing your code sees.
An interviewer who likes that answer often moves on to settings on a live server, with two longer questions:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
After a deploy that ran php artisan config:cache, Pathshala's pages use the new mail server, but emails sent from queued jobs still go through the old one until someone restarts the machine hours later. Explain why the two parts of the app disagree, and change the deploy so that it cannot happen again.
-
Fresher to mid
A new Pathshala centre went live without CENTRE_CODE on its server. config/pathshala.php has the default PSH-000, so for a week every receipt carried the wrong code and nothing failed. How would you make a missing or malformed required setting stop the deploy at once instead of hiding behind a default?
Flashcards · 6 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
Which file does every web request to a Laravel app enter through, and what does it do?
public/index.php, the front controller. It loads Composer's autoloader, builds the application from bootstrap/app.php and hands the request to the kernel, whose bootstrappers, middleware and router take it from there.
-
Where does your own code live in a new project, and what do the other folders hold?
In app/: controllers, models and service providers. Every other folder has one job: config has one file per area, database the migrations, factories and seeders, resources the Blade views and CSS and JavaScript sources, routes web.php and console.php, tests the tests, public the files a browser may fetch, storage logs and caches.
-
After php artisan make:model Batch -a, which URL reaches BatchController?
None yet. The command wrote 8 files from templates into the folders for their kinds, two of them new (app/Http/Requests and app/Policies), and route:list still showed the same 4 routes. A controller answers only once you add a route for it.
-
How does a setting travel from .env to your code?
.env holds the values that differ between computers; a file in config/ reads one with env() and a default; your code reads the result with config('file.key'). A real environment variable set by the server or shell wins over the same name in .env.
-
Why does env('CENTRE_CODE') return null in a controller after php artisan config:cache?
While bootstrap/cache/config.php exists, Laravel does not load .env at all, so env() sees only real environment variables. Call env() only inside config files and read values everywhere else with config(), which the cache already holds.
-
Which commands show what the framework actually registered, resolved and stored?
php artisan route:list for routes, config:show for resolved settings, db:show for the database and its tables, and php artisan tinker to run PHP inside the loaded app. Check with them instead of guessing.
Key takeaways
- Every web request enters through
public/index.php, which loads Composer’s autoloader, builds the app frombootstrap/app.phpand hands the request to the kernel’s bootstrappers, the middleware and the router. - A new project keeps 33 files of its own in seven folders, each folder has one job, and
makecommands write each new class into the folder for its kind without adding any route. .envholds the values that differ between computers, a file inconfig/reads them withenv()and a default, your code reads the result withconfig('file.key'), and a real environment variable wins over.env.- After
config:cache, Laravel does not read.env, soenv()outside the config files returnsnull, and later changes wait forconfig:clearor a new cache. route:list,config:show,db:showand Tinker show what the framework actually registered, resolved and stored, so you check instead of guessing.
Terms in this lesson
- Artisan
- Laravel's command-line tool, run as php artisan inside the project folder. Its commands create files (make:model), show what the app sees (route:list, about) and manage caches (config:cache).
- configuration cache
- The file bootstrap/cache/config.php that php artisan config:cache writes, with every configuration value already worked out. While it exists, Laravel does not read .env, so env() outside the config files no longer sees its values.
- environment file
- The .env file in a Laravel project. It holds the values that differ from one computer to the next, such as the database settings, the debug flag and the application key, and stays out of version control.
- front controller
- The one PHP file that every web request enters through, public/index.php in Laravel. It loads Composer's autoloader, builds the application and hands the request to the framework.
- Tinker
- A prompt opened with php artisan tinker, where each PHP line you type runs inside your loaded application: read a config() value, call a helper or query a model.
References
- Laravel 13.x documentation, Request lifecycle (Laravel (laravel.com))
- Laravel 13.x documentation, Directory structure (Laravel (laravel.com))
- Laravel 13.x documentation, Configuration (Laravel (laravel.com))
- Laravel 13.x documentation, Artisan console (Laravel (laravel.com))
- Laravel 13.x documentation, Deployment (Laravel (laravel.com))
- Laravel 13.x documentation, Queues (Laravel (laravel.com))
- Laravel framework v13.34.0 source, LoadEnvironmentVariables.php (Laravel (GitHub))
- Laravel framework v13.34.0 source, the HTTP kernel (Laravel (GitHub))
Report a problem with this lesson
Kept only in this browser. Your Learn progress