About blackCurrant PHP

blackCurrant PHP - a minimal PHP 8.1+ framework. Barebones, fast, secure.

How a request flows

  1. index.php registers the autoloader and calls Boot::run().
  2. Boot loads config, sends security headers, starts the session, captures the request.
  3. Url::parse() splits the path into currentClass / currentFunction / param.
  4. Dispatcher maps /aboutApp\Controllers\About.
  5. The controller calls $this->view->render('index'), the View engine finds the template via cascade.
  6. Controller::renderPage() wraps the body in layouts/app with header + footer.
  7. Boot echoes the output. Add ?_speed=1 from a safe IP for a per-stage profile.

Template cascade

When rendering index from the About controller, View looks in order:

  1. app/Views/about/index.phpthis page (most specific)
  2. app/Views/index.php (project default)
  3. core/Views/index.php (framework fallback)

Override only what you need; the rest cascades up.

Where things live

your-site/
├── public/                          ← *** WEB DOCUMENT ROOT ***
│   ├── index.php                    front controller (BC_ROOT = parent)
│   ├── .htaccess                    short rewrite + headers
│   ├── css/                         site.css, bc-banner.css, bc-components.css, bc-home.css,
│   │                                bc-theme-editor.css, bc-debug-viewer.css, bc-update.css,
│   │                                theme.css (generated)
│   ├── js/                          bc-banner.js, bc-nav.js, bc-features-toc.js,
│   │                                bc-theme-editor.js, bc-debug-clear.js
│   ├── build/                       Tailwind output (manifest.json + hashed files)
│   ├── uploads/                     source images for the ImageProxy
│   └── images/                      brand assets, logos
├── storage/                         persistent state OUTSIDE webroot
│   ├── theme.json                   palette saved by /theme
│   └── uploads/                     private user uploads (only proxied)
├── cache/                           writable, denied from HTTP
│   └── images/                      auto-generated proxy variants
├── logs/                            writable, denied from HTTP
├── core/                            framework code - treat as vendor
│   ├── Mvc/         Controller, View, Model, ApiController, AdminController, Seo
│   ├── Http/        Request, Response, Csrf, Headers, Pipeline, RateLimiter
│   │   └── Middleware/  MaintenanceMode, RateLimit, VerifyCsrf, Compress, ImageProxy
│   ├── Routing/     Url (locale-aware), Dispatcher
│   ├── Db/          Connection, Database, Paginator
│   ├── Cache/       Redis / APCu / File / Null
│   ├── Security/    Sanitize, CleanInput, Session, Auth
│   ├── Debug/       Logger, Profiler, ErrorHandler, LogViewer
│   ├── Ab/          Bucket
│   ├── Cli/         CLI kernel + commands (bin/bc: serve, ai:call, core:push, …)
│   ├── Ai/          AI agent layer: Capability, Registry, Gateway · Mcp/ Http/ Identity/ Discovery/
│   ├── Update/      CoreSync - push core/ to other installs on this drive
│   └── Views/       layouts/app, partials/seo, partials/seo_body, system/*
├── app/                             ← YOUR code goes here
│   ├── Controllers/                 page controllers (Home, About, Contact, …)
│   ├── Models/                      data-access classes
│   ├── Views/                       templates
│   │   ├── layouts/                 page shells (app, admin)
│   │   ├── partials/                shared chunks (header, nav, footer)
│   │   └── <controller>/            per-controller views
│   ├── Api/V1/                      JSON API endpoints
│   ├── Admin/                       admin-gated pages
│   ├── Ai/                          agent capabilities (skills) - exposed via MCP / REST / CLI
│   └── Http/Middleware/             custom middleware (optional)
├── config/config.php                all configuration
├── .env                             secrets (gitignored)
├── index.php                        legacy entrypoint - kept for back-compat
│                                    injects "Insecure deployment" banner
│                                    if reached (i.e. docroot wasn't switched
│                                    to public/ yet)
└── .htaccess                        legacy hardened rewrites + denies

Decision table - where new files go

You're adding…Put it at
A new page app/Controllers/<Name>.php + app/Views/default/<name>/index.php
A new API endpoint app/Api/V1/<Name>.php
A new admin screen app/Admin/<Name>.php + app/Views/default/admin/<name>/index.php
An AI agent capability app/Ai/<Name>.php (implements Capability) + register in config.phpai.capabilities
A database model app/Models/<Name>.php
A shared partial app/Views/default/partials/<name>.php
A layout app/Views/default/layouts/<name>.php
Custom HTTP middleware app/Http/Middleware/<Name>.php + register in config.phpmiddleware
Hand-written CSS public/css/<name>.css<link href="/css/<name>.css">
Plain JS (CSP-safe) public/js/<name>.js<script src="/js/<name>.js" defer>
Source images for the proxy public/uploads/<…>.jpg<?= img('/uploads/<…>.jpg', width: 800) ?>
Brand SVG / logos public/images/<name>.svg - passed through unchanged
Private user uploads storage/uploads/<…> - outside webroot, only proxy can serve
Persistent state (non-DB) storage/<name>.json - gitignored
Tailwind input resources/css/app.css → builds to public/build/app.css
A framework feature STOP - touching core/ needs review

Where things must NOT go

Don'tWhy
Anything in core/ Treated as vendor. App-level changes go in app/.
New routes in the root index.php Legacy entrypoint - kept only for back-compat. Target public/index.php.
Secrets / source under public/ public/ is the webroot. .env, config, models, vendor must stay above it.
.env in any deployed webroot One misconfigured rule away from leaking. Fix: docroot = public/.