In a Laravel 13 school-timetable app, where should a stray helper file of timetable functions live, and what does Laravel need to find it?
answer
- App\ maps to app/ under PSR-4
- a class beats a bag of functions
- app/Support is a convention, not a rule
- config/ files are all loaded as config
- a few folder names the framework scans
basics
~20 sTurn it into a class under app/, for example app/Support/TimetableFormatter.php in the App\Support namespace; PSR-4 autoloading of App\ finds it with no registration. Global functions need Composer files autoloading, and config/, routes/, bootstrap/ and public/ are the wrong places.
solid answer
~40 s`composer.json` maps the `App\` namespace to `app/` with PSR-4, so any class whose namespace matches its path is found automatically. A timetable helper is best written as a class, say `App\Support\TimetableFormatter` in `app/Support/TimetableFormatter.php`; `Support`, `Services` or `Actions` are team conventions, not folders Laravel requires. If you really want global functions, the file needs Composer's `files` autoloading and a regenerated autoloader, because PSR-4 only loads classes. Do not drop it in `config/`, where every `.php` file is loaded as a configuration array; `routes/`, which is for route definitions; `bootstrap/`, which boots the framework; or `public/`, which the web server exposes. Keep in mind that a few names under `app/` do carry meaning: `Console/Commands` is scanned for commands, `Listeners` for event discovery, and policies are guessed from `Models` to `Policies`.
code
php · 17 lines<?php
namespace App\Support;
use App\Models\Lesson;
use Illuminate\Support\Collection;
class TimetableFormatter
{
/** @param Collection<int, Lesson> $lessons */
public function clashes(Collection $lessons): Collection
{
return $lessons
->groupBy(fn (Lesson $l) => $l->room_id.'@'.$l->starts_at)
->filter(fn (Collection $group) => $group->count() > 1);
}
}go deeper
Remember that classes under app/ with a matching App namespace are found automatically, and that config/, routes/ and public/ are not for helpers.
Explain PSR-4 mapping from composer.json, why functions need Composer files autoloading, and why a class is the better home.
Spot the folders whose names the framework reads (Console/Commands, Listeners, Models to Policies) before reorganising app/ into modules.
Set the team's convention for app/ subfolders such as Support, Actions or domain modules, balancing discoverability against framework conventions.
## The scenario A developer on a **school-timetable app** has written `timetable_helpers.php`: a handful of functions that format periods, detect clashes and build week grids. It sits in the project root and is `require`d from a controller. The team asks where it should really live. ## How Laravel finds your code Laravel does not scan folders for arbitrary classes. It relies on **Composer's autoloader**, and the skeleton's `composer.json` maps namespaces to folders with **PSR-4**: | Namespace | Folder | |---|---| | `App\` | `app/` | | `Database\Factories\` | `database/factories/` | | `Database\Seeders\` | `database/seeders/` | | `Tests\` (dev) | `tests/` | So a class named `App\Support\TimetableFormatter` is loaded from `app/Support/TimetableFormatter.php` the first time it is used, with nothing to register. The docs say it plainly: Laravel imposes almost no restrictions on where a class lives, as long as Composer can autoload it. ## Option 1: make it a class (preferred) 1. Create `app/Support/TimetableFormatter.php` with `namespace App\Support;`. 2. Turn the functions into methods, static or instance. 3. Import it where needed, or type-hint it in a controller or job so the container injects it. Benefits: it is autoloaded on demand, it can be injected and swapped in tests, and names cannot collide with functions from packages. `app/Support`, `app/Services` and `app/Actions` are common, but they are **team conventions**; Laravel creates none of them. ## Option 2: keep global functions PSR-4 loads classes only, so a file of plain functions is never loaded by it. To keep functions global, list the file under `autoload.files` in `composer.json` and regenerate the autoloader; Composer then includes it on every request. Wrap each function in `if (! function_exists(...))` to avoid clashes. This is Composer's mechanism, not a Laravel feature. ## Folders that are the wrong place - **`config/`**: every `.php` file there is required at boot and its return value stored as configuration under the file name, so `config/timetable_helpers.php` would become the config key `timetable_helpers`, and the functions would be defined only when configuration is loaded from files, not once it is served from a cache. - **`routes/`**: route files are loaded by the routing layer; the helper would be tied to route loading. - **`bootstrap/`**: holds `app.php`, `providers.php` and the framework's cache; application code does not belong there. - **`public/`**: the web root. A PHP file there can be requested directly by URL. - **The project root**: not autoloaded; it only works through a manual `require`. ## A worked refactor For the timetable helper file, the move takes a few minutes: 1. Create `app/Support/TimetableFormatter.php` and paste the functions in as public methods. 2. Replace `require base_path('timetable_helpers.php');` in the controller with a constructor parameter `TimetableFormatter $formatter`, which the container resolves automatically. 3. Search the codebase for the old function names and call the methods instead. 4. Delete the root file and any `require` lines. 5. Add a unit test in `tests/Unit` that calls `clashes()` on a small collection of lessons. The result is a class that shows up in IDE navigation, can be replaced with a fake in a feature test, and no longer depends on a file being included first. ## Folder names that do mean something Most of `app/` is free-form, but a few names are read by the framework by convention: - `app/Console/Commands`: command classes here are registered automatically. - `app/Listeners`: scanned by event discovery. - `app/Models` and `app/Policies`: a policy for `App\Models\Lesson` is guessed as `App\Policies\LessonPolicy`. - `app/Providers`: where generated providers go before being listed in `bootstrap/providers.php`. Moving classes out of those folders works for autoloading but can switch off the convention that found them. ## Common misreadings - "Every folder under `app/` must be registered": PSR-4 handles it. - "`app/Helpers` is a Laravel folder": it is only a name people choose. - "Composer files autoloading is lazy": it includes the file on every request.
- What happens if you save the helper as config/timetable_helpers.php?Laravel requires every `.php` file in `config/` while loading configuration and stores its return value under the file name, so `config('timetable_helpers')` becomes whatever the file returns. The functions get defined as a side effect of that load, which stops happening once configuration is served from a cache, so code that relied on them breaks in production.
- When does the folder a class sits in under app/ actually matter to Laravel?When a framework convention reads it: command classes in `app/Console/Commands` are registered automatically, event discovery scans `app/Listeners`, and policy guessing maps `App\Models\Lesson` to `App\Policies\LessonPolicy`. Elsewhere only the namespace-to-path match matters, so `app/Support` or `app/Domain/Timetable` behave identically.
saying these in an interview costs you the question
- New folders under app/ must be registered in a service provider
- app/Helpers is a directory Laravel scans for helper functions
- PSR-4 autoloading also loads files of plain functions
- config/ is a fine place for shared helper functions
- Putting helpers in public/ keeps them away from the router