<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Miguel Angel Delgado</title>
    <description>The latest articles on DEV Community by Miguel Angel Delgado (@miguelex).</description>
    <link>https://dev.to/miguelex</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3913693%2F63dceb4a-4c0e-4c0a-827b-98f8dc904617.jpeg</url>
      <title>DEV Community: Miguel Angel Delgado</title>
      <link>https://dev.to/miguelex</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/miguelex"/>
    <language>en</language>
    <item>
      <title>I built a modern PHP 8.2 MVC template with full tooling and AI agent support</title>
      <dc:creator>Miguel Angel Delgado</dc:creator>
      <pubDate>Tue, 05 May 2026 12:01:14 +0000</pubDate>
      <link>https://dev.to/miguelex/i-built-a-modern-php-82-mvc-template-with-full-tooling-and-ai-agent-support-51om</link>
      <guid>https://dev.to/miguelex/i-built-a-modern-php-82-mvc-template-with-full-tooling-and-ai-agent-support-51om</guid>
      <description>&lt;p&gt;A few months ago I started with a simple goal: have a solid, reusable base for my PHP projects without pulling in a full framework every time. What I ended up with is something I'm genuinely proud of, and today I'm making it public.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;php-template&lt;/strong&gt; is a PHP 8.2 MVC starter template with serious tooling, full testing stack, and something I haven't seen in other PHP templates: native support for AI agent development.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer create-project miguelex/php-template my-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why not just use Laravel or Symfony?
&lt;/h2&gt;

&lt;p&gt;Fair question. I use frameworks when projects need them. But a lot of the work I do involves specific business modules, integrations with legacy systems, or projects where the overhead of a full framework isn't justified.&lt;/p&gt;

&lt;p&gt;The goal here wasn't to replace Laravel. It was to have a clean, well-structured starting point for projects where you want full control over every layer — without starting from scratch every single time.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's inside
&lt;/h2&gt;

&lt;h3&gt;
  
  
  MVC Core
&lt;/h3&gt;

&lt;p&gt;The architecture is built from scratch on PHP 8.2 with strict types throughout.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Router&lt;/strong&gt; — supports GET, POST, PUT, PATCH, DELETE, &lt;code&gt;_method&lt;/code&gt; override for HTML forms, global middleware, and clean 404/500 handling via exceptions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$router&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;HomeController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'index'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nv"&gt;$router&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/users'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;UserController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'store'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="c1"&gt;// Protect routes with middleware&lt;/span&gt;
&lt;span class="nv"&gt;$router&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AuthMiddleware&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Two data access patterns&lt;/strong&gt; — because one size doesn't fit all:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ActiveRecord&lt;/em&gt; for simple/medium projects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ActiveRecord&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'posts'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'body'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Updated title'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Repository pattern&lt;/em&gt; for complex domains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'posts'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;findPublished&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;findWhere&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'published'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;'created_at DESC'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;hydrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="nv"&gt;$entity&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$repo&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PostRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Database&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$repo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;perPage&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both patterns can coexist in the same project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Migration system&lt;/strong&gt; — no Doctrine, no Eloquent. A clean &lt;code&gt;up()&lt;/code&gt;/&lt;code&gt;down()&lt;/code&gt; base class with state tracking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php bin/console migrate
php bin/console migrate:status
php bin/console migrate:rollback 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;AuthMiddleware&lt;/strong&gt; — session-based auth with session fixation protection, &lt;code&gt;check()&lt;/code&gt;, &lt;code&gt;require()&lt;/code&gt;, and &lt;code&gt;guest()&lt;/code&gt; helpers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Paginator, JsonResponse, Html helper&lt;/strong&gt; — common utilities that get reimplemented in every project, included and ready to use.&lt;/p&gt;




&lt;h3&gt;
  
  
  PHP Code Quality
&lt;/h3&gt;

&lt;p&gt;This is where I spent a lot of time getting the configuration right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PHPStan level 6&lt;/strong&gt; — not level 9 (too many false positives in real projects), not level 0 (pointless). Level 6 catches the important stuff without noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PHP-CS-Fixer + PHP_CodeSniffer&lt;/strong&gt; — PSR-12 enforced. One command to check, one to auto-fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer lint        &lt;span class="c"&gt;# detect issues&lt;/span&gt;
composer lint:fix    &lt;span class="c"&gt;# auto-correct&lt;/span&gt;
composer stan        &lt;span class="c"&gt;# static analysis&lt;/span&gt;
composer qa          &lt;span class="c"&gt;# everything at once&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;PHPUnit 11 + Pest 3&lt;/strong&gt; — both, because they serve different purposes. PHPUnit for unit and integration tests, Pest for feature tests with its more expressive syntax.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// PHPUnit style&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_throws_when_user_not_found&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;expectException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserNotFoundException&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Pest style&lt;/span&gt;
&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'throws when user not found'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserNotFoundException&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Frontend — two bundler options
&lt;/h3&gt;

&lt;p&gt;Not every PHP project needs React. Most of mine don't. So the template ships with two options:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gulp 5&lt;/strong&gt; — SCSS → CSS, JS bundle, image optimization to WebP and AVIF, BrowserSync. Simple pipeline, zero module bundling complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vite 6&lt;/strong&gt; — for when you start using &lt;code&gt;import/export&lt;/code&gt;, need instant HMR, or expect the frontend to grow. Proxy to PHP dev server included.&lt;/p&gt;

&lt;p&gt;You choose at project creation time. The &lt;code&gt;init-project.sh&lt;/code&gt; script sets everything up.&lt;/p&gt;

&lt;p&gt;Generated image formats: original (optimized) + WebP (quality 80) + AVIF (quality 50).&lt;/p&gt;




&lt;h3&gt;
  
  
  Full testing stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vitest&lt;/strong&gt; — JS unit tests, shared config with Vite&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Playwright&lt;/strong&gt; — E2E tests, auto-starts the PHP server before running
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/front/e2e/example.spec.js&lt;/span&gt;
&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loads successfully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/.+/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  CI/CD with GitHub Actions
&lt;/h3&gt;

&lt;p&gt;Two parallel jobs on every push:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PHP QA&lt;/strong&gt; — PHPCS + PHPStan + PHPUnit + Pest, on PHP 8.2 and 8.3&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Front QA&lt;/strong&gt; — ESLint + Stylelint + Vitest + Playwright&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Green on both before any merge.&lt;/p&gt;




&lt;h3&gt;
  
  
  AI Agent support
&lt;/h3&gt;

&lt;p&gt;This is the part I'm most excited about. The template ships with a &lt;code&gt;.agent/&lt;/code&gt; directory that AI coding agents (Claude Code, Cursor, GitHub Copilot in agent mode) read automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.agent/
├── AGENTS.md       ← entry point: permissions, commands, what NOT to do
├── WORKFLOW.md     ← how to act: plan first, surgical changes, verify before done
├── CONVENTIONS.md  ← coding standards: PHP, JS, SCSS, Git
├── PROJECT.md      ← business context (fill in per project)
└── TASKS.md        ← strategic backlog

tasks/
├── todo.md         ← active task: plan + checkboxes + review
└── lessons.md      ← past mistakes + rules to avoid repeating them
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;WORKFLOW.md&lt;/code&gt; in particular encodes the behaviors that make AI agents actually useful: plan before acting, touch only what's necessary, verify before marking done, and learn from corrections via &lt;code&gt;lessons.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;AGENTS.md&lt;/code&gt; explicitly lists what the agent &lt;strong&gt;cannot&lt;/strong&gt; do — no direct edits to generated assets, no lowering PHPStan below level 6, no raw SQL string interpolation, no committing &lt;code&gt;.env&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This turns the template into something that works well with both human developers and AI agents from day one.&lt;/p&gt;




&lt;h3&gt;
  
  
  CLI tool
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php bin/console &lt;span class="nb"&gt;help&lt;/span&gt;

&lt;span class="c"&gt;# Migrations&lt;/span&gt;
php bin/console migrate
php bin/console migrate:fresh

&lt;span class="c"&gt;# Code generators&lt;/span&gt;
php bin/console make:controller Post
php bin/console make:model Post
php bin/console make:repository Post
php bin/console make:migration create_posts_table

&lt;span class="c"&gt;# Other&lt;/span&gt;
php bin/console cache:clear
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Makefile
&lt;/h3&gt;

&lt;p&gt;Because nobody should have to remember all the commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make dev        &lt;span class="c"&gt;# PHP + Gulp (concurrently)&lt;/span&gt;
make dev-vite   &lt;span class="c"&gt;# PHP + Vite (concurrently)&lt;/span&gt;
make qa         &lt;span class="c"&gt;# full PHP quality check&lt;/span&gt;
make test-all   &lt;span class="c"&gt;# PHP + JS + E2E&lt;/span&gt;
make migrate    &lt;span class="c"&gt;# run pending migrations&lt;/span&gt;
make &lt;span class="nb"&gt;help&lt;/span&gt;       &lt;span class="c"&gt;# list everything&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Starting a new project
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone and run the init script&lt;/span&gt;
git clone https://github.com/miguelex/php-template.git
&lt;span class="nb"&gt;cd &lt;/span&gt;php-template
./init-project.sh

&lt;span class="c"&gt;# Or via Composer&lt;/span&gt;
composer create-project miguelex/php-template my-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script asks for a project name and mode (backend-only or fullstack), removes what isn't needed, personalizes the config files, and initializes a clean Git repo with a first commit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Design decisions worth explaining
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No framework dependency&lt;/strong&gt; — the core MVC is ~600 lines of PHP total. You can read it in an afternoon and understand every line. That's intentional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PHPStan level 6, not 9&lt;/strong&gt; — level 9 on a real project with external integrations generates noise. Level 6 catches the important stuff. It can be raised gradually as the codebase matures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Both Gulp and Vite&lt;/strong&gt; — not a hedge, a deliberate choice. Gulp is better for projects with simple JS. Vite is better when the frontend grows. Having both available means the template fits more projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;composer.lock&lt;/code&gt; committed&lt;/strong&gt; — the template has &lt;code&gt;"type": "project"&lt;/code&gt;, so yes, the lockfile belongs in the repo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🔗 GitHub: &lt;a href="https://github.com/miguelex/php-template" rel="noopener noreferrer"&gt;github.com/miguelex/php-template&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 Packagist: &lt;a href="https://packagist.org/packages/miguelex/php-template" rel="noopener noreferrer"&gt;packagist.org/packages/miguelex/php-template&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Contributions, PRs, issues and feedback are very welcome. If you work with PHP and want a solid base without the complexity of a full framework, give it a try.&lt;/p&gt;

&lt;p&gt;— Migue Delgado&lt;/p&gt;

</description>
      <category>php</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
