Download the PHP package cachewraith/laravel-template-structure without Composer

On this page you can find all versions of the php package cachewraith/laravel-template-structure. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package laravel-template-structure

cachewraith/laravel-template-structure

An installer-style Laravel package — in the spirit of Laravel Breeze — that scaffolds a clean, scalable, OOAD-based, OWASP-hardened architecture into any Laravel 10, 11, 12 or 13 application, behind a versioned JSON API, a server-rendered Blade UI, or both.

It is not a runtime framework. One command copies real, working code into your application and wires it up; after that the package can be removed and your application keeps working.

Not writing an API? --stack=web scaffolds the Blade half on its own — session authentication, forms, views, no build step — on the same services, repositories and policies. Installing both front doors costs a controller, two form requests and some templates, and no business logic at all. That is the whole argument for the layering.


Installation

Or name the stack outright and skip the question:

The service provider is auto-discovered, so there is nothing to register by hand.

Choosing a stack

--stack=api --stack=web --stack=both
Front door routes/api_v{n}.php routes/web_ui.php both
Controllers Http/Controllers/Api/V{n} Http/Controllers/Web both
Auth Sanctum tokens sessions + CSRF both
Output Http/Resources + JSON envelope Blade views both
CSP profile default-src 'none' default-src 'self' picked per request
Needs laravel/sanctum yes no yes
Services · repositories · policies · models shared — identical in all three

The stack only ever changes the transport. Everything that decides what may happen is installed either way, because a rule enforced in two places is a rule that will diverge.

Re-running with a different --stack adds the missing half; it never removes the one you already have. Where the wiring cannot be extended safely — an existing then: closure in bootstrap/app.php, say — the installer prints the snippet instead of guessing.

The installer reads its stub tree straight out of the package: your project gets the generated application code and nothing else — no stubs/ directory left behind.

Testing it locally before publishing

Point a throwaway Laravel application at your working copy:

Command options

Option Effect
--stack=api\|web\|both Which front door to scaffold. Asked interactively when omitted; defaults to api under --no-interaction, so existing scripts keep their behaviour.
--force Overwrite scaffolded files that already exist. Without it, existing files are left untouched and reported as skipped.
--publish-stubs Also copy the raw stub tree to stubs/cachewraith-template/ so you can customise the generated code. Off by default.

The installer is idempotent: running it twice creates nothing new and patches nothing twice. Anything it cannot patch safely — because your application already customised the file — is printed as a snippet to paste in yourself, rather than being forced.

Customising the generated code

Once stubs/cachewraith-template/app exists, the installer prefers it over the packaged originals, so your edits survive a re-run. Delete the directory to go back to the packaged stubs.

Publishing tag Publishes
cachewraith-template-stubs The whole stub tree to stubs/cachewraith-template/
cachewraith-template-config config/cachewraith-template.php

What gets scaffolded

Shared by every stack

The part that decides what may happen. Installed identically whichever front door you chose, because duplicating it per transport is how the two halves start disagreeing.

--stack=api adds

--stack=web adds

Files your application already owns — app/Models/User.php, the users migration, UserFactory, DatabaseSeeder, routes/web.php, config/app.php — are never overwritten. Existing files are reported as skipped unless you pass --force.

If you already run Breeze, Jetstream, Fortify or laravel/ui, the installer detects it and generates no sign-in code: routes/web_ui.php guards its credential routes on class_exists, so your flow keeps the login route name and the scaffolded pages sit behind it. It also checks your resources/views/layouts/app.blade.php: those packages ship a component layout with no @yield('content'), which would render the scaffolded pages empty, so the installer says so rather than letting you find out in the browser.

The installer also patches, when it can do so safely:

Laravel 11+ Laravel 10
bootstrap/app.php → withRouting(then: …) loads routes/api_{version}.php app/Providers/RouteServiceProvider.php loads the same files
bootstrap/app.php → withMiddleware() registers the middleware and the ratelimit.api alias app/Http/Kernel.php registers them
bootstrap/app.php → withExceptions() registers App\Exceptions\Handler app/Exceptions/Handler.php delegates to a generated ApiExceptionHandler
bootstrap/providers.php registers RepositoryServiceProvider config/app.php registers it
app/Providers/AppServiceProvider::boot() gets the global security defaults same
.env.example gets the production hardening notes same

Laravel 10 note. Laravel 10 already owns app/Exceptions/Handler.php, so the mapper is installed beside it as ApiExceptionHandler and your handler delegates to it. On Laravel 11+ the path is free, so it lands there under its own name.


Architecture

The request path

The middle five rows are literally the same objects. That is not a coincidence to admire — it is the constraint the layering exists to enforce, and the reason a rule cannot hold over JSON while quietly failing over HTML.

OOAD principles this enforces

Single Responsibility. A service sequences a use case and owns the audit trail. A repository touches the database and nothing else — no authorisation, no logging, no business rules. If a class needs the word "and" to describe it, it is doing too much.

Dependency Inversion. Controllers and services depend on App\Repositories\Contracts\*Interface, never on Eloquent. RepositoryServiceProvider is the one file that names a concrete implementation, which is what makes the persistence layer swappable and trivially fakeable in tests — tests/Unit/ItemServiceTest.php exercises the status-transition rules against a hand-written in-memory repository, with no database:

Interface Segregation. One interface per aggregate root. Resist growing a shared CrudRepositoryInterface — every consumer would then depend on methods it never calls.

Open/Closed. A released API version is frozen. New behaviour is a new version: a new route file and new controllers, never an edit to v1.

Choosing a layer

Where does this code go? Layer
"When X happens, do A then B, and record it" Service — shared by both front doors
"Fetch/store this aggregate" Repository — shared
"May this caller do this?" Policy — shared, asked by FormRequest/Controller
"What does the client receive?" Resource (API) or a Blade view (web)
"What is a valid payload?" FormRequest — one per transport
"What shape is every JSON response?" App\Traits\ApiResponse
"How does a decimal become minor units?" App\Support\Money

The Item reference slice

Item is a complete vertical — model, migration, factory, policy, requests, resources in two API versions, service, repository, feature test, unit test, and on a web or both install a Blade controller, templates and their own feature test. It exists to be read and then copied for your own aggregate. Delete it when you have your own; nothing else depends on it.

On --stack=both, read Api/V1/ItemController and Web/ItemController side by side. They are the same seven decisions; everything that differs is transport, and everything that does not is shared on purpose.


API versioning

config/cachewraith-template.php drives it:

Each entry loads routes/api_{version}.php under {prefix}/{version}, with the api middleware group and the route-name prefix {version}. — so routes/api_v1.php is served at /api/v1/..., its routes are named v1.items.index, and the file itself contains no prefix(), no middleware('api') and no name('v1.').

Adding v2 without breaking v1

routes/api_v2.php and App\Http\Controllers\Api\V2\ItemController ship as the worked example. Enable them with one line:

What the example demonstrates: only items changed shape, so only ItemController and ItemResource were copied into V2. users, login and logout still point at the V1 controllers, and the V2 controller reuses the V1 FormRequests because the accepted input did not change. A V2 directory that mirrors V1 file-for-file is duplication, not versioning.

v1 v2
price "19.99" — decimal string, currency unstated { "amount_minor": 1999, "currency": "USD" }
timestamps top level nested under timestamps
owner absent { "id": 1, "name": "…" }

/api/v1/* keeps serving its existing clients, untouched. Never edit a released version in place — that is the Open/Closed principle with a deprecation policy attached. Versioning is a property of the HTTP boundary; the service, repository, model and policy underneath have one version.

The web front door is not versioned at all, and that is deliberate: a version number is a contract with clients you cannot redeploy, and the only consumer of a Blade page is a browser you ship the HTML to. Change the page and ship it. For the same reason app/Http/Requests/Web/ duplicates the rules in V1/ rather than importing them — the day the form gains a field, borrowing V1's class would force a change into a frozen version.


OWASP Top Ten defaults

Numbering follows the OWASP Top 10:2025 categories.

ID Category What is baked in
A01 Broken Access Control Every FormRequest::authorize() is a real policy call, never return true. Policies read the record's own columns, never a request parameter. Update/delete resolve the target from the route binding, never from a body-supplied id (IDOR). Read endpoints call Gate::authorize explicitly. ItemRepositoryInterface has no unscoped listing method, so there is none for a controller to reach for by accident. per_page is clamped.
A02 Security Misconfiguration SecurityHeaders is registered globally, so endpoints added later inherit it: CSP default-src 'none', X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer, Permissions-Policy, COOP/CORP, Cache-Control: no-store. X-Powered-By is stripped. config/cors.php is an explicit allow-list, empty by default. php.ini sets expose_php=Off and display_errors=Off; nginx sets server_tokens off. .env.example documents APP_DEBUG=false.
A03 Software Supply Chain Failures composer audit --locked runs during the production image build; wire it into CI too — see Dependency auditing.
A04 Cryptographic Failures Passwords are hashed at the single write path in UserService via Hash::make (bcrypt/argon2id — never a fast hash). URL::forceScheme('https') outside local/testing. HSTS on secure connections. .env is in .dockerignore, so it never reaches an image layer. Resources never emit a password, hash or token.
A05 Injection Eloquent and the query builder only — no DB::raw, whereRaw or string-concatenated SQL anywhere in the stubs. Validation rules are allowlists, so unknown keys never reach persistence. The nginx config refuses to execute any path but public/index.php.
A06 Insecure Design RateLimitApi gives every route a ceiling and fails closed on an unknown bucket name. Pagination is bounded. Item status transitions are enforced as a domain rule, not merely a validation rule. forceDelete is closed by default. Models run in strict mode outside production.
A07 Authentication Failures Sanctum tokens, named per device, revoked individually on logout. Login is limited per email + IP, returns an identical response for unknown-email and wrong-password, and equalises response timing so the endpoint cannot be used to enumerate accounts. Password strength (12 chars, mixed case, numbers, symbols, HIBP-checked) is enforced on write, not on login. UserSeeder refuses to run in production and reads its demo password from the environment.
A08 Software & Data Integrity Failures No unserialize(), no eval(), no dynamic class instantiation from request input anywhere in the stubs. The production image ships immutable code with only storage/ and bootstrap/cache writable, and runs as www-data, never root.
A09 Logging & Alerting Failures Services log create/update/delete and AuthController logs login success, failure and misconfiguration — each with actor id and source IP, and never a password, hash, token or full payload.
A10 Mishandling of Exceptional Conditions App\Exceptions\Handler maps every failure to one envelope and fails closed: an unrecognised exception is a generic 500, with class/file/line attached only when APP_DEBUG is on. ForceJsonResponse stops error paths from rendering HTML.

XXE is not addressed because the scaffolding parses no XML.

What the Blade front door adds

ID What is baked in
A01 Every state-changing action is a POST or DELETE carrying @csrf — no GET /logout, no delete link, because a GET that changes state is reachable from an <img> tag on any other site and never sees CSRF protection. @can in a template hides a control and is never the only check: the policy is asked again in the controller or the form request.
A02 SecurityHeaders serves a second profile for HTML — default-src 'self', form-action 'self', no 'unsafe-inline', no 'unsafe-eval'. The API keeps default-src 'none', which would block a page's own stylesheet and every form on it. Neither profile is loosened to accommodate the other; nginx sets nosniff on the static assets it serves without touching PHP.
A03 The stylesheet is self-hosted. No npm, no Vite, no CDN — nothing at runtime from an origin you do not control.
A04 Cache-Control: no-store, private on any page rendered for a signed-in visitor: a cached page is still on disk after they sign out, and still reachable with the back button on a shared machine. Session cookie hardening (SESSION_SECURE_COOKIE, HTTP_ONLY, SAME_SITE) is written into .env.example.
A05 Output escaping — the reason it now applies. Every interpolation in every scaffolded template is {{ }}; there is not one {!! !!} in the scaffold, and a feature test asserts that a stored <script> arrives as text. App\Support\Money parses prices from the string, never (int) round($v * 100).
A07 The session id is regenerated on sign-in (fixation) and the session invalidated and the CSRF token rotated on sign-out. Sign-in is throttled on email + IP through the same bucket the API uses; unknown-email and wrong-password give an identical, timing-equalised answer; the submitted password is never repopulated into the form.
A10 resources/views/errors/* are standalone pages that read no session and no data — a 404 for an unmatched URL never reaches StartSession, and a layout that touched the session would turn a clean 404 into a 500.

The scaffolded web tests assert these too: that a stranger gets a 403 on someone else's item, that a form-supplied user_id is ignored, that the session id actually changes on sign-in, that 1.15 is stored as 115 and 1,999.00 is rejected rather than silently becoming 0, and that a stored <script> renders as text.

The scaffolded feature tests assert these properties rather than just the happy path — that an unknown email and a wrong password are byte-identical, that a stranger gets a 403 on someone else's item, that a client-supplied user_id is ignored, and that every response carries the hardening headers.

Uniform response envelope

Success and failure share a shape, so a client parses one thing:

Dependency auditing

composer audit checks your installed dependency tree against the PHP Security Advisories Database. Run it in CI so a newly disclosed CVE fails the build:

Commit composer.lock, keep dependency updates small and frequent, and prefer composer update <package> over a blanket update you cannot review.


Docker

Production is docker compose up -d on its own. The development overlay is opt-in and must stay that way: it bind-mounts the source, turns opcache off, enables Xdebug and publishes the database port.

docker-compose.yml deliberately publishes no database or cache port — they are reachable only from the internal network. Both passwords are required (${DB_PASSWORD:?…}), so the stack refuses to start with a blank credential rather than starting insecurely.


After installing

  1. Install Sanctum (Laravel 11+): php artisan install:api
  2. Add the trait to App\Models\User:

  3. Run the migrations: php artisan migrate (creates the items table)
  4. Verify the routes: php artisan route:list --path=api

  5. Run the tests: php artisan test
  6. Review the policies. The generated rules are ownership-based, which is all a stock User model can express. Tighten them to your roles before shipping — and tighten rather than loosen: UserPolicy::create currently allows any authenticated user, which is right for an admin-only console and wrong for a public API.
  7. Review config/cors.php. It is empty by default; name your browser origins in CORS_ALLOWED_ORIGINS.
  8. Read docs/ARCHITECTURE.md, then copy the Item slice for your own aggregate and delete it.

If you installed the Blade stack

  1. Seed an account to sign in with: php artisan db:seed --class=UserSeeder (it refuses to run in production and reads its password from SEED_USER_PASSWORD).
  2. Visit /login, then /dashboard and /items. There is no build step — the stylesheet is a static file at public/vendor/cachewraith/app.css.
  3. Harden the session before production. Laravel's defaults are already http_only and same_site=lax; set them explicitly anyway, because a default you never wrote down is one a future config edit can quietly change:

  4. Replace the stylesheet whenever you like: change the <link> in resources/views/layouts/app.blade.php and delete the file. Nothing in the PHP depends on its class names. If your replacement loads from another origin, add that origin to web_security_headers — and do not reach for 'unsafe-inline'.
  5. Read docs/WEB.md for the page-level rules: no {!! !!}, no inline JavaScript, every @can backed by a real policy call.

Where did the global security defaults go?

URL::forceScheme, Eloquent strict mode and the production destructive-command guard are patched into your own app/Providers/AppServiceProvider::boot(), marked with a cachewraith-template comment block. That is where a reviewer looks for them, and it is one fewer generated provider to explain. If the installer cannot patch the file safely it prints the block for you to paste.

Rate limiting is not a provider concern here: RateLimitApi is a route middleware, so each route states its own budget (->middleware('ratelimit.api:login')) instead of depending on a limiter registered invisibly elsewhere.

Policies are found by Laravel's naming convention (App\Models\Item → App\Policies\ItemPolicy). If you move or rename either side, pair them explicitly with Gate::policy() — a policy that silently stops being found fails open.

Requirements

Laravel 10 is end-of-life. The fallback wiring for it is implemented and tested, but Composer now blocks every 10.x release because of published security advisories (OWASP A03). Treat Laravel 10 support here as a migration aid, not a destination.

Optional: laravel/sanctum — required by the API stack's auth routes, which register only when it is installed. A --stack=web install does not need it; it authenticates with sessions.

No Node, npm or Vite is required for the Blade stack. If you already run Breeze, Jetstream, Fortify or laravel/ui, the installer defers to your sign-in flow rather than competing with it.

License

MIT. See LICENSE.


All versions of laravel-template-structure with dependencies

PHP Build Version
Package Version
Requires php Version ^8.2
illuminate/console Version ^10.0|^11.0|^12.0|^13.0
illuminate/filesystem Version ^10.0|^11.0|^12.0|^13.0
illuminate/support Version ^10.0|^11.0|^12.0|^13.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package cachewraith/laravel-template-structure contains the following files

Loading the files please wait ...