Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

8.1KB

MVC Framework Skill

Purpose

Use this skill for MindVisionCode PHP framework architecture, routing, dispatching, controllers, actions, ViewModels, templates, and HTTP request/response flow.


Project Identity

This project is a small PHP MVC framework called MindVisionCode PHP.

It is intentionally inspired by a Classic ASP MVC framework style:

  • Central dispatcher
  • Controllers and actions
  • ViewModels
  • Repository classes
  • Simple validation
  • Database migrations
  • Small, readable files
  • Minimal dependencies

Do not turn this into Laravel, Symfony, Slim, CakePHP, or another large framework.


Request Flow

Browser
  → public/index.php
  → Request
  → Dispatcher
  → Router
  → Route
  → Controller
  → ViewModel/Repository/Service
  → View
  → Response

Project Structure

Preferred structure:

project-root/
  public/
    index.php
  src/
    Controller/
    Service/
    Repository/
    Entity/
    ValueObject/
    ViewModel/
    Http/
    Routing/
    Validation/
    Database/
    Migration/
  templates/
  config/
  tests/
  var/
    cache/
    logs/
  vendor/
  composer.json

Rules:

  • public/ is the web root.
  • Do not expose src/, config/, tests/, vendor/, .ai/, or .env files through the web server.
  • Put application code under src/.
  • Put generated cache/log files under var/ or another ignored runtime directory.
  • Keep secrets outside the web root.

Development Commands

Install dependencies:

composer install

Regenerate autoload files:

composer dump-autoload

Run local server:

php -S localhost:8000 -t public

Run basic tests:

php tests/run.php

Framework Coding Rules

  • Keep code simple and readable.
  • Prefer small classes.
  • Use typed properties and return types where practical.
  • Avoid hidden magic.
  • Do not add dependencies without a clear reason.
  • Preserve the framework style.
  • Explain any architectural changes.

Service and Request Injection

Core\App::resolveArgs() injects constructor and action parameters by type. Any service registered via $app->bind(SomeClass::class, $instance) in public/index.php is automatically injected when an action declares a typed parameter of that class.

Core\Request is registered as a binding in public/index.php before dispatch, so controller actions can declare it as a parameter without calling Request::capture() manually:

public function index(Request $request): Response
{
    $search = $request->input('search', '');
    // ...
}

Route segment parameters (e.g. {id}) are still resolved by name before the binding lookup.

To make a service injectable, register it once at bootstrap:

// public/index.php
$app->bind(Database::class, database());

Then declare it as a typed parameter in any action:

public function index(Database $db): Response
{
    // $db is injected automatically
}

Binding concrete instances: Use $app->instance($name, $obj) to bind a specific object by key. Instances take precedence over bindings when resolving.

Auto-wiring: Use $app->make(SomeClass::class) to resolve a class with its constructor dependencies injected automatically:

$repo = $app->make(EmployeeRepository::class);
// $app checks bindings first, then instantiates the class and resolves its constructor

Test isolation: Call $app->clear() to reset all bindings and instances between test runs.

Do not call Request::capture() inside action bodies. Declare the parameter instead.


Controller Rules

  • Keep controllers thin.
  • Validate request method and request shape at the boundary.
  • Do not put database query details directly in controllers when a repository or service is more appropriate.
  • Do not put template rendering logic inside business services.
  • Return or produce a response through the framework’s response mechanism.
  • Use requirePost($request) to guard POST-only actions. It returns ?Response (null when the method is POST, a 405 Response otherwise). Always return it immediately if non-null:
if ($guard = $this->requirePost($request)) {
    return $guard;
}
  • Verify CSRF before field validation on any state-changing action. CSRF failure is a security gate, not a form validation error. See the Security skill for the helper pattern.
  • When a controller uses a repository across multiple methods, store it as a nullable property and lazy-initialize once — do not call new Repository(database()) on every method call:
private ?EmployeeRepository $employees = null;

private function employees(): EmployeeRepository
{
    if ($this->employees === null) {
        $this->employees = new EmployeeRepository(database());
    }

    return $this->employees;
}

ViewModel Rules

  • Use ViewModels to shape data for views.
  • Keep ViewModels simple and explicit.
  • Avoid passing raw database rows directly into complex templates when a ViewModel would make the template clearer.
  • Do not put database access inside ViewModels unless the existing project convention explicitly does that.

View Configuration

View paths are set in config/view.php:

return [
    'views_path' => __DIR__ . '/../app/Views',
    'layout_path' => __DIR__ . '/../app/Views/layouts/app.php',
];

core/View.php reads this file lazily on first use and caches the result. To change where views or the layout live, edit config/view.php — do not edit core/View.php. This follows the same pattern as config/database.php.


Templates and Views

Keep presentation separate from business logic.

Rules:

  • Do not query the database from templates.
  • Do not place business rules in templates.
  • Escape output by default.
  • Prefer simple view models or arrays passed into templates.
  • Use a template engine with automatic escaping only if it fits the project and does not make the framework unnecessarily large.

Plain PHP template example:

<h1><?= e($pageTitle) ?></h1>

<ul>
    <?php foreach ($users as $user): ?>
        <li><?= e($user->name()) ?></li>
    <?php endforeach; ?>
</ul>

Router Methods

Core\Router exposes one method per HTTP verb:

Method HTTP verb
$router->get($path, $handler) GET
$router->post($path, $handler) POST
$router->put($path, $handler) PUT
$router->patch($path, $handler) PATCH
$router->delete($path, $handler) DELETE
$router->add($method, $path, $handler) Any verb

Method Override for HTML Forms

HTML forms only support GET and POST. To route a form submission to a PUT, PATCH, or DELETE handler, add a hidden _method field:

<form method="POST" action="/employees/42">
    <?= csrf_field() ?>
    <input type="hidden" name="_method" value="PUT">
    <!-- fields -->
</form>

Core\Request::method() checks for this field (and the X-HTTP-Method-Override header from JavaScript clients) when the base method is POST, and returns the overridden verb. Only PUT, PATCH, and DELETE are accepted as override values — all others are ignored.


HTTP and Web Application Rules

Rules:

  • Use the front controller pattern where appropriate.
  • Keep routing separate from business logic.
  • Validate request methods.
  • Use CSRF protection for state-changing forms.
  • Use proper HTTP status codes.
  • Redirect after successful POST to avoid duplicate form submission.
  • Do not trust headers such as X-Forwarded-For unless configured behind a trusted proxy.

Example POST guard using the framework helper:

public function store(Request $request): Response
{
    if ($guard = $this->requirePost($request)) {
        return $guard;
    }

    // POST-only logic here
}

Architecture Guardrail

When adding features, preserve the small-framework character:

  • Prefer explicit code over hidden convention.
  • Prefer simple routing over complex annotation systems.
  • Prefer plain PHP views unless a project decision says otherwise.
  • Prefer focused services and repositories over large framework abstractions.
  • Do not introduce a large package just to solve a small problem.

Powered by TurnKey Linux.