<?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: Technomantus Corvi</title>
    <description>The latest articles on DEV Community by Technomantus Corvi (@technomantus_corvi).</description>
    <link>https://dev.to/technomantus_corvi</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4010203%2F7242e9c9-82a1-45da-b7e8-3fa3591c93cd.png</url>
      <title>DEV Community: Technomantus Corvi</title>
      <link>https://dev.to/technomantus_corvi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/technomantus_corvi"/>
    <language>en</language>
    <item>
      <title>Tanuki Framework, a PHP framework for people who actually like writing PHP</title>
      <dc:creator>Technomantus Corvi</dc:creator>
      <pubDate>Sun, 06 Sep 2026 13:31:53 +0000</pubDate>
      <link>https://dev.to/technomantus_corvi/tanuki-framework-a-php-framework-for-people-who-actually-like-writing-php-2pli</link>
      <guid>https://dev.to/technomantus_corvi/tanuki-framework-a-php-framework-for-people-who-actually-like-writing-php-2pli</guid>
      <description>&lt;h2&gt;
  
  
  The problem I kept running into
&lt;/h2&gt;

&lt;p&gt;Every time I started a small PHP project, I had two bad options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option one:&lt;/strong&gt; grab a full framework. Get an ORM I have to learn a query language for, a template engine that's basically PHP-but-different, a service container, middleware pipelines, and a folder structure designed by a committee. Half my "learning" time goes to the framework's opinions, not my actual app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option two:&lt;/strong&gt; go fully raw PHP. Rebuild routing, rebuild a database wrapper, rebuild CSRF protection, rebuild sessions — for the fifth time, slightly worse than the last, because I'm rushing.&lt;/p&gt;

&lt;p&gt;I wanted something in between: &lt;strong&gt;the boring plumbing solved once, in code I can actually read&lt;/strong&gt;, with nothing hidden underneath.&lt;/p&gt;

&lt;p&gt;That's Tanuki.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Tanuki actually is
&lt;/h2&gt;

&lt;p&gt;A lightweight PHP MVC framework with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A router that maps &lt;code&gt;'GET /todo/{id}' =&amp;gt; 'TodoController@show'&lt;/code&gt; — no annotations, no attribute magic, just an array.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;Model&lt;/code&gt; base class that's a thin, honest wrapper over PDO — &lt;code&gt;TodoModel::where('completed', 0)&lt;/code&gt; builds a real prepared statement you could write yourself, it just saves you the typing.&lt;/li&gt;
&lt;li&gt;Views that are just... PHP files. &lt;code&gt;&amp;lt;?= e($todo['title']) ?&amp;gt;&lt;/code&gt;. No new syntax.&lt;/li&gt;
&lt;li&gt;Zero ORM. Zero template engine. Zero dependency injection container.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Having full freedom to work your own vanilla way, or to follow an already-designed, basic and simple MVC pattern, gives you freedom without any kind of magic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's the entire flow of a request, and you can read the actual source in about ten minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser → public/index.php → App::run()
│
├── loadEnv()
├── registerAutoloader()
├── configureErrors()
└── dispatch()
│
├── Matches an exact route or a {param} pattern
└── Instantiates the Controller → calls method()
│
└── $this-&amp;gt;view('name', $data)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. That's the whole request lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;p&gt;If you're the kind of developer who:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;would rather write raw SQL than learn an ORM's query builder,&lt;/li&gt;
&lt;li&gt;gets annoyed when a framework does something "for you" that you didn't ask for,&lt;/li&gt;
&lt;li&gt;deletes code more readily than you write new abstractions,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...you'll probably feel at home here. This isn't trying to compete with Laravel or Symfony on features — it's deliberately smaller in scope, aimed at people who want a real MVC skeleton without the weight.&lt;/p&gt;

&lt;h2&gt;
  
  
  A real example: the TODO CRUD
&lt;/h2&gt;

&lt;p&gt;The repo ships with a complete, working TODO list as a learning reference:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TodoController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&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;store&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;csrf_verify&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;request&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;'_token'&lt;/span&gt;&lt;span class="p"&gt;)))&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;flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Your session expired. Please try again.'&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;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/todo/create'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$title&lt;/span&gt; &lt;span class="o"&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;request&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;'title'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$title&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;keep_old&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$title&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;flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Title is required.'&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;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/todo/create'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nc"&gt;TodoModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'completed'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&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;flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Task created!'&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;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/todo'&lt;/span&gt;&lt;span class="p"&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;p&gt;CSRF check, form repopulation on validation failure, flash messages — all real patterns you'll reuse for your own resources, not hidden behind a form-request class you have to configure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's included, and what's optional
&lt;/h2&gt;

&lt;p&gt;The core is: routing, models, views, session handling, CSRF helpers, and a small i18n system (&lt;code&gt;t('nav.home')&lt;/code&gt; + JSON dictionaries, with an optional URL-prefixed language switcher — &lt;code&gt;/en/todo&lt;/code&gt;, &lt;code&gt;/es/todo&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Beyond that, two extensions ship in the repo but stay &lt;strong&gt;completely inert&lt;/strong&gt; until you wire them in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;tanuki_login&lt;/code&gt;&lt;/strong&gt; — session-based auth: login, registration, password recovery by email, profile editing. Zero routes registered by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;tanuki_admin&lt;/code&gt;&lt;/strong&gt; — a Django-inspired admin panel. Register a model in one array, get a full CRUD interface for it:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// admin/admin.php&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'todo'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'model'&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;TodoModel&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;'label'&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Tasks'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'list_fields'&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;'completed'&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="s1"&gt;'form_fields'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'title'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'text'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'label'&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;'completed'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'checkbox'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'label'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Completed'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&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;p&gt;Don't want either? Delete the folder and the two lines in &lt;code&gt;routes.php&lt;/code&gt; that reference it. Nothing else in the project depends on them — that isolation was a deliberate design constraint from day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security, taken seriously without being invisible
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Every &lt;code&gt;Model&lt;/code&gt; method uses prepared statements, and column/table names are validated against a strict identifier pattern (this is a real fix — I found and closed a column-name injection vector during development, and there's a regression test for it).&lt;/li&gt;
&lt;li&gt;CSRF protection exists as explicit helpers (&lt;code&gt;csrf_field()&lt;/code&gt;, &lt;code&gt;csrf_verify()&lt;/code&gt;) you add per form — not a blanket policy silently applied to every route, which would break webhook-style endpoints that don't use sessions.&lt;/li&gt;
&lt;li&gt;Passwords are &lt;code&gt;password_hash()&lt;/code&gt;/&lt;code&gt;password_verify()&lt;/code&gt;, reset tokens are single-use SHA-256 hashes with expiry, sessions regenerate on login/logout to prevent fixation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing here is exotic — it's the standard stuff done correctly and made visible, not buried in a security middleware you never read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Translations that don't get in your way
&lt;/h2&gt;

&lt;p&gt;i18n is one of those things frameworks either skip entirely or turn into an entire subsystem you have to learn. Tanuki does the minimum useful version: one JSON dictionary per language, one helper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;lang/en.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"nav"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"home"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Home"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"todo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"flash_created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Task created successfully!"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'nav.home'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                    &lt;span class="c1"&gt;// → "Home"&lt;/span&gt;
&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'todo.flash_created'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;// → "Task created successfully!"&lt;/span&gt;
&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'todo.count_summary'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'total'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="c1"&gt;// → replaces ":total" in the string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a key is missing in the active language, it falls back to English; if it's missing everywhere, you get the raw key back instead of a broken page — a missing translation is visible, not a silent failure.&lt;/p&gt;

&lt;p&gt;There's also a URL-based language switcher (&lt;code&gt;/en/todo&lt;/code&gt;, &lt;code&gt;/es/todo&lt;/code&gt;) wired in from the start, but it stays completely inactive unless you set &lt;code&gt;ACCEPTED_LANGUAGES&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;ACCEPTED_LANGUAGES&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;en,es&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit &lt;code&gt;/es/todo&lt;/code&gt; once, and the choice sticks in the session — every other link on the site works without a prefix. Don't set that variable at all, and the switcher never renders: the framework assumes your project is single-language by default, and only asks you to think about locales when you actually need more than one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer create-project forja-de-onix/tanuki-framework my-project
&lt;span class="nb"&gt;cd &lt;/span&gt;my-project
&lt;span class="nb"&gt;cp&lt;/span&gt; .env-example .env
nano .env   &lt;span class="c"&gt;# set DB_NAME, DB_USER, DB_PASS, APP_URL&lt;/span&gt;
php &lt;span class="nt"&gt;-S&lt;/span&gt; localhost:8050 &lt;span class="nt"&gt;-t&lt;/span&gt; public public/index.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit &lt;code&gt;/todo&lt;/code&gt; for the working example, or start from scratch with your own route → controller → model → view.&lt;/p&gt;

&lt;h2&gt;
  
  
  Long-term support, by design
&lt;/h2&gt;

&lt;p&gt;Tanuki isn't meant to chase trends. The goal is a framework stable enough that a project built on it today still works, unchanged, in five years — minor patches, no breaking rewrites. If that sounds appealing, or if you just want to poke around a small, readable PHP codebase, the repo and full wiki documentation are linked below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Packagist:&lt;/strong&gt; &lt;a href="https://packagist.org/packages/forja-de-onix/tanuki-framework" rel="noopener noreferrer"&gt;packagist.org/packages/forja-de-onix/tanuki-framework&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/Forja-de-Onix/tanuki-framework" rel="noopener noreferrer"&gt;github.com/Forja-de-Onix/tanuki-framework&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Wiki (full docs):&lt;/strong&gt; &lt;a href="https://github.com/Forja-de-Onix/tanuki-framework/wiki/Home" rel="noopener noreferrer"&gt;github.com/Forja-de-Onix/tanuki-framework/wiki/Home&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback, issues, and PRs welcome — especially from other vanilla-PHP fans who want to poke holes in the design.&lt;/p&gt;

</description>
      <category>php</category>
      <category>webdev</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Tanuki Framework, un framework PHP para gente a la que realmente le gusta escribir PHP Vanilla</title>
      <dc:creator>Technomantus Corvi</dc:creator>
      <pubDate>Sun, 06 Sep 2026 13:27:51 +0000</pubDate>
      <link>https://dev.to/technomantus_corvi/tanuki-framework-un-framework-php-para-gente-a-la-que-realmente-le-gusta-escribir-php-vanilla-1le1</link>
      <guid>https://dev.to/technomantus_corvi/tanuki-framework-un-framework-php-para-gente-a-la-que-realmente-le-gusta-escribir-php-vanilla-1le1</guid>
      <description>&lt;h2&gt;
  
  
  El problema con el que siempre me topaba
&lt;/h2&gt;

&lt;p&gt;Cada vez que empezaba un proyecto pequeño en PHP, tenía dos malas opciones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opción uno:&lt;/strong&gt; coger un framework completo. Conseguir un ORM cuyo lenguaje de queries tengo que aprender, un motor de plantillas que básicamente es PHP-pero-distinto, un contenedor de servicios, pipelines de middleware, y una estructura de carpetas diseñada por un comité. La mitad de mi tiempo de "aprendizaje" se va en las opiniones del framework, no en mi app de verdad.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opción dos:&lt;/strong&gt; ir a PHP crudo del todo. Reconstruir el enrutamiento, reconstruir un envoltorio de base de datos, reconstruir la protección CSRF, reconstruir sesiones — por quinta vez, un poco peor que la anterior, porque voy con prisa.&lt;/p&gt;

&lt;p&gt;Quería algo intermedio: &lt;strong&gt;la fontanería aburrida resuelta una sola vez, en código que de verdad pueda leer&lt;/strong&gt;, sin nada oculto debajo.&lt;/p&gt;

&lt;p&gt;Eso es Tanuki.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué es Tanuki realmente
&lt;/h2&gt;

&lt;p&gt;Un framework PHP MVC ligero con:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Un router que mapea &lt;code&gt;'GET /todo/{id}' =&amp;gt; 'TodoController@show'&lt;/code&gt; — sin anotaciones, sin magia de atributos, solo un array.&lt;/li&gt;
&lt;li&gt;Una clase base &lt;code&gt;Model&lt;/code&gt; que es un envoltorio fino y honesto sobre PDO — &lt;code&gt;TodoModel::where('completed', 0)&lt;/code&gt; construye un prepared statement real que podrías escribir tú mismo, solo te ahorra teclear.&lt;/li&gt;
&lt;li&gt;Vistas que son solo... archivos PHP. &lt;code&gt;&amp;lt;?= e($todo['title']) ?&amp;gt;&lt;/code&gt;. Sin sintaxis nueva.&lt;/li&gt;
&lt;li&gt;Cero ORM. Cero motor de plantillas. Cero contenedor de inyección de dependencias.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Tener libertad total de trabajar según tu forma de programar en vanilla o seguir un patrón MVC ya diseñado básico y sencillo te otorga libertad sin ningún tipo de magia.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Aquí está el flujo completo de una petición, y puedes leer el código fuente real en unos diez minutos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Navegador → public/index.php → App::run()
│
├── loadEnv()
├── registerAutoloader()
├── configureErrors()
└── dispatch()
│
├── Encuentra ruta exacta o patrón {param}
└── Instancia el Controller → llama método()
│
└── $this-&amp;gt;view('nombre', $data)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eso es todo. Ese es el ciclo de vida completo de una petición.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quién va dirigido
&lt;/h2&gt;

&lt;p&gt;Si eres del tipo de desarrollador que:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;prefiere escribir SQL directo antes que aprender el query builder de un ORM,&lt;/li&gt;
&lt;li&gt;se molesta cuando un framework hace algo "por ti" que no pediste,&lt;/li&gt;
&lt;li&gt;borra código con más facilidad de la que escribe abstracciones nuevas,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...probablemente te sientas como en casa aquí. Esto no intenta competir con Laravel o Symfony en funcionalidades — es deliberadamente más pequeño en alcance, pensado para gente que quiere un esqueleto MVC real sin el peso.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un ejemplo real: el CRUD de TODO
&lt;/h2&gt;

&lt;p&gt;El repositorio incluye una lista de tareas completa y funcional como referencia de aprendizaje:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TodoController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&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;store&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;csrf_verify&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;request&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;'_token'&lt;/span&gt;&lt;span class="p"&gt;)))&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;flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Tu sesión expiró. Inténtalo de nuevo.'&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;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/todo/create'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$title&lt;/span&gt; &lt;span class="o"&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;request&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;'title'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$title&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;keep_old&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$title&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;flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'El título es obligatorio.'&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;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/todo/create'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nc"&gt;TodoModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'completed'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&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;flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'¡Tarea creada!'&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;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/todo'&lt;/span&gt;&lt;span class="p"&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;p&gt;Verificación CSRF, repoblado de formulario si falla la validación, mensajes flash — todo patrones reales que reutilizarás para tus propios recursos, no escondidos detrás de una clase de form-request que tienes que configurar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué incluye, y qué es opcional
&lt;/h2&gt;

&lt;p&gt;El núcleo es: rutas, modelos, vistas, manejo de sesión, helpers CSRF, y un pequeño sistema de i18n (&lt;code&gt;t('nav.home')&lt;/code&gt; + diccionarios JSON, con un selector de idioma opcional por prefijo de URL — &lt;code&gt;/en/todo&lt;/code&gt;, &lt;code&gt;/es/todo&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Más allá de eso, dos extensiones vienen en el repo pero permanecen &lt;strong&gt;completamente inertes&lt;/strong&gt; hasta que las conectas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;tanuki_login&lt;/code&gt;&lt;/strong&gt; — autenticación basada en sesión: login, registro, recuperación de contraseña por email, edición de perfil. Cero rutas registradas por defecto.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;tanuki_admin&lt;/code&gt;&lt;/strong&gt; — un panel admin inspirado en Django. Registra un modelo en un array, obtén una interfaz CRUD completa para él:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// admin/admin.php&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'todo'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'model'&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;TodoModel&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;'label'&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Tareas'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'list_fields'&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;'completed'&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="s1"&gt;'form_fields'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'title'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'text'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'label'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Título'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'completed'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'checkbox'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'label'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Completada'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&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;p&gt;¿No quieres ninguna de las dos? Borra la carpeta y las dos líneas de &lt;code&gt;routes.php&lt;/code&gt; que la referencian. Nada más en el proyecto depende de ellas — ese aislamiento fue una restricción de diseño deliberada desde el primer día.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seguridad, tomada en serio sin ser invisible
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Todos los métodos de &lt;code&gt;Model&lt;/code&gt; usan prepared statements, y los nombres de columna/tabla se validan contra un patrón estricto de identificador (esto es un fix real — encontré y cerré un vector de inyección vía nombre de columna durante el desarrollo, y hay un test de regresión para ello).&lt;/li&gt;
&lt;li&gt;La protección CSRF existe como helpers explícitos (&lt;code&gt;csrf_field()&lt;/code&gt;, &lt;code&gt;csrf_verify()&lt;/code&gt;) que añades por formulario — no una política general aplicada en silencio a cada ruta, que rompería endpoints tipo webhook que no usan sesiones.&lt;/li&gt;
&lt;li&gt;Las contraseñas usan &lt;code&gt;password_hash()&lt;/code&gt;/&lt;code&gt;password_verify()&lt;/code&gt;, los tokens de recuperación son hashes SHA-256 de un solo uso con expiración, las sesiones se regeneran en login/logout para prevenir fijación.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nada de esto es exótico — es lo estándar hecho correctamente y hecho visible, no enterrado en un middleware de seguridad que nunca lees.&lt;/p&gt;

&lt;h2&gt;
  
  
  Traducciones que no se interponen en tu camino
&lt;/h2&gt;

&lt;p&gt;El i18n es una de esas cosas que los frameworks o se saltan por completo, o convierten en todo un subsistema que tienes que aprender. Tanuki hace la versión mínima útil: un diccionario JSON por idioma, un helper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;lang/es.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"nav"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"home"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Inicio"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"todo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"flash_created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"¡Tarea creada correctamente!"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'nav.home'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                    &lt;span class="c1"&gt;// → "Inicio"&lt;/span&gt;
&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'todo.flash_created'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;// → "¡Tarea creada correctamente!"&lt;/span&gt;
&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'todo.count_summary'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'total'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="c1"&gt;// → sustituye ":total" en el string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si una clave falta en el idioma activo, cae al inglés; si falta en todos lados, te devuelve la clave tal cual en vez de romper la página — una traducción faltante se ve, no falla en silencio.&lt;/p&gt;

&lt;p&gt;También hay un selector de idioma por URL (&lt;code&gt;/en/todo&lt;/code&gt;, &lt;code&gt;/es/todo&lt;/code&gt;) conectado desde el principio, pero permanece totalmente inactivo a menos que definas &lt;code&gt;ACCEPTED_LANGUAGES&lt;/code&gt; en el &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;ACCEPTED_LANGUAGES&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;en,es&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visita &lt;code&gt;/es/todo&lt;/code&gt; una vez, y la elección se queda en sesión — el resto de enlaces del sitio funcionan sin prefijo. No definas esa variable, y el selector nunca se renderiza: el framework asume que tu proyecto es de un solo idioma por defecto, y solo te pide pensar en idiomas cuando realmente necesitas más de uno.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo empezar
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer create-project tu-usuario/tanuki-base mi-proyecto
&lt;span class="nb"&gt;cd &lt;/span&gt;mi-proyecto
&lt;span class="nb"&gt;cp&lt;/span&gt; .env-example .env
nano .env   &lt;span class="c"&gt;# ajusta DB_NAME, DB_USER, DB_PASS, APP_URL&lt;/span&gt;
php &lt;span class="nt"&gt;-S&lt;/span&gt; localhost:8050 &lt;span class="nt"&gt;-t&lt;/span&gt; public public/index.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visita &lt;code&gt;/todo&lt;/code&gt; para el ejemplo funcional, o empieza desde cero con tu propia ruta → controlador → modelo → vista.&lt;/p&gt;

&lt;h2&gt;
  
  
  Soporte a largo plazo, por diseño
&lt;/h2&gt;

&lt;p&gt;Tanuki no está pensado para perseguir tendencias. El objetivo es un framework lo suficientemente estable como para que un proyecto construido hoy sobre él siga funcionando, sin cambios, dentro de cinco años — parches menores, sin reescrituras que rompan nada. Si eso te resulta atractivo, o si simplemente quieres curiosear un código PHP pequeño y legible, el repo y la wiki con documentación completa están abajo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Packagist:&lt;/strong&gt; &lt;a href="https://packagist.org/packages/forja-de-onix/tanuki-framework" rel="noopener noreferrer"&gt;https://packagist.org/packages/forja-de-onix/tanuki-framework&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/Forja-de-Onix/tanuki-framework" rel="noopener noreferrer"&gt;https://github.com/Forja-de-Onix/tanuki-framework&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Wiki (documentación completa):&lt;/strong&gt; &lt;a href="https://github.com/Forja-de-Onix/tanuki-framework/wiki/Inicio" rel="noopener noreferrer"&gt;https://github.com/Forja-de-Onix/tanuki-framework/wiki/Inicio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback, issues y PRs bienvenidos — especialmente de otros fans del PHP vanilla que quieran encontrarle agujeros al diseño.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>php</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Corvorum OS Server Edition</title>
      <dc:creator>Technomantus Corvi</dc:creator>
      <pubDate>Mon, 06 Jul 2026 22:33:44 +0000</pubDate>
      <link>https://dev.to/technomantus_corvi/corvorum-os-server-edition-2d94</link>
      <guid>https://dev.to/technomantus_corvi/corvorum-os-server-edition-2d94</guid>
      <description>&lt;h1&gt;
  
  
  Corvorum OS Server Edition 1.0: Un sistema operativo para servidores con IA local y entorno listo desde el primer arranque
&lt;/h1&gt;

&lt;p&gt;Hace poco os presenté &lt;strong&gt;&lt;a href="https://dev.to/technomantus_corvi/corvorum-os-10-sistema-operativo-tecnomantico-68h"&gt;Corvorum OS 1.0 Desktop Edition&lt;/a&gt;&lt;/strong&gt;, una distro Linux basada en Ubuntu minimal, diseñada para desarrolladores que quieren un entorno moderno, funcional y con identidad propia y usuarios de Windows que buscan un cambio.&lt;/p&gt;

&lt;p&gt;Ahora os presento su versión para servidores, que cuenta con la misma IA integrada con Ollama, python, docker, ansible y Kubernetes k3s listo para montar una infraestructura devops minima.&lt;/p&gt;

&lt;p&gt;Cuenta con herramientas como mc y btop para poder gestionar archivos cómodamente desde cli y ver los procesos. También puedes trabajar con ollama usando su comando nativo y diferentes IAs o tirar del comando IA que invoca al modelo Odin-assist basado en Qwen2.5:1.5B. &lt;/p&gt;




&lt;h2&gt;
  
  
  ¿Qué incluye la versión 1.0?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Terminal con identidad propia
&lt;/h3&gt;

&lt;p&gt;Al abrir la terminal verás el banner de &lt;strong&gt;Corvorum OS&lt;/strong&gt; con la información del sistema: versión del kernel, modelos de IA local disponibles, y los comandos principales listos para usar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CORVORUM OS
Desktop Edition 1.0 | 6.8.0-124-generic
IA Local: 3 modelo(s) disponible(s)
Escribe:
  - 'ai' para consultar al asistente Odín
  - 'btop' para administrador de tareas
  - 'ctop' para administración de contenedores docker
  - 'mc' para ejecutar el explorador de archivos

Corvorum OS en k3s: default
Transmutando ›
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Filosofía del proyecto
&lt;/h2&gt;

&lt;p&gt;Corvorum OS nace de una creencia simple: &lt;strong&gt;recuperar el control de lo que haces y no depender de terceros&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Vivimos una era de Inteligencia Artificial bajo suscripciones y sistemas que implementan IA desde la nube. Eso genera una dependencia que todavía podemos sostener pero que en el futuro se volverá mas cara y difícil de gestionar, para cuando llegue ese momento quiero ofrecer esta alternativa ya para entonces madura e independiente que pueda ahorrarte pagar elevados precios por computo en IA o estén haciendo telemetría del uso que tienes con tu ordenador.&lt;/p&gt;

&lt;p&gt;Por eso el sistema integra IA local, soporte Windows y entorno de desarrollo como parte del sistema.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;No sabemos con exactitud lo que nos depara el futuro, lo que si podemos y debemos hacer es construirlo a través del presente.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Cómo probarlo
&lt;/h2&gt;

&lt;p&gt;La imagen ISO de Corvorum OS 1.0 Server Edition está disponible para descarga:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 [&lt;a href="https://sourceforge.net/projects/corvorum-os/files/release1.0/corvorum-os-se_1.0_amd64.iso/download" rel="noopener noreferrer"&gt;https://sourceforge.net/projects/corvorum-os/files/release1.0/corvorum-os-se_1.0_amd64.iso/download&lt;/a&gt;]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Puedes probarlo en una máquina virtual (VirtualBox, VMware, QEMU) o instalarlo directamente en hardware compatible (arquitectura amd64).&lt;/p&gt;

&lt;p&gt;Si deseas instalarlo las credenciales son:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Usuario: scholaris&lt;/li&gt;
&lt;li&gt;Contraseña: frater&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Estas puedes cambiarlas durante el proceso de instalación cli que se hace con penguin's eggs.&lt;/p&gt;

&lt;p&gt;Requisitos mínimos recomendados:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;8 GB RAM (8 GB recomendado para modelos de IA local)&lt;/li&gt;
&lt;li&gt;20 GB de espacio en disco&lt;/li&gt;
&lt;li&gt;CPU de 64 bits&lt;/li&gt;
&lt;li&gt;GPU Intel Iris XE o superior&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Roadmap
&lt;/h2&gt;

&lt;p&gt;Corvorum OS 1.0 es el punto de partida. El proyecto tiene un ecosistema más amplio en desarrollo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Odin Agent&lt;/strong&gt; — Asistente Odin que permite búsqueda de archivos, busquedas en internet, creación de documentos y otro tipo de funcionalidades.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notificación de actualizaciones&lt;/strong&gt; — Sistema que comprueba los repositorios oficiales.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuración de agentes IA&lt;/strong&gt; - Herramienta para comprobar los requisitos del equipo y elegir por defecto cuales son los mejores agentes para cada equipo, también da la opción a cambiarlos manualmente sin tener que buscarlos en Ollama.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repositorio Community&lt;/strong&gt; — abierto próximamente para contribuciones externas, con sistema de revisión progresivo y la &lt;strong&gt;Edición Technomantus&lt;/strong&gt; como rama oficial curada.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Contribuir
&lt;/h2&gt;

&lt;p&gt;El proyecto vive en:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 [&lt;a href="https://github.com/Forja-de-Onix/corvorum-os" rel="noopener noreferrer"&gt;https://github.com/Forja-de-Onix/corvorum-os&lt;/a&gt;]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Si quieres contribuir, reportar un bug o simplemente probar el sistema y dar feedback, eres bienvenido. Las contribuciones se revisan antes de integrarse en la rama oficial, y los colaboradores activos obtendrán privilegios de code review con el tiempo.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Corvorum OS es un proyecto open source de la Forja de Onix. Hecho con Python, Bash, Linux y demasiada paciencia.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>opensource</category>
      <category>linux</category>
    </item>
    <item>
      <title>Corvorum OS 1.0 - Sistema Operativo Tecnomántico</title>
      <dc:creator>Technomantus Corvi</dc:creator>
      <pubDate>Wed, 01 Jul 2026 21:04:29 +0000</pubDate>
      <link>https://dev.to/technomantus_corvi/corvorum-os-10-sistema-operativo-tecnomantico-68h</link>
      <guid>https://dev.to/technomantus_corvi/corvorum-os-10-sistema-operativo-tecnomantico-68h</guid>
      <description>&lt;h1&gt;
  
  
  Corvorum OS 1.0: Un sistema operativo para desarrolladores con IA local, soporte Windows y entorno listo desde el primer arranque
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"¿Y si tu sistema operativo ya viniera preparado para trabajar desde el minuto cero?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Os presento &lt;strong&gt;Corvorum OS 1.0 Desktop Edition&lt;/strong&gt;, una distro Linux basada en Ubuntu minimal, diseñada para desarrolladores que quieren un entorno moderno, funcional y con identidad propia y usuarios de Windows que buscan un cambio.&lt;/p&gt;

&lt;p&gt;Este proyecto nace de la &lt;strong&gt;Forja de Onix&lt;/strong&gt;, mi espacio personal de desarrollo open source, es un espacio donde voy a dar vida a varios proyectos mas relacionados con el ecosistema Corvorum.&lt;/p&gt;




&lt;h2&gt;
  
  
  ¿Qué es Corvorum OS?
&lt;/h2&gt;

&lt;p&gt;Corvorum OS no es otra distro más con un fondo de pantalla diferente. Es un sistema pensado desde cero para el desarrollador moderno, combinando tres mundos que normalmente requieren configuración manual extensa:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IA local integrada&lt;/strong&gt; con modelos LLM corriendo directamente en tu máquina, sin depender de servicios externos ni conexión a internet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Soporte nativo para aplicaciones Windows&lt;/strong&gt; mediante umu-run y Proton, con detección automática de shortcuts y lanzadores integrados en el menú del sistema.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entorno de desarrollo completo&lt;/strong&gt; listo desde el primer arranque: terminal personalizada, editor de código (Codium), agente de código (OpenCode), gestor de tareas (btop) y explorador de archivos en modo consola (mc).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ¿Qué incluye la versión 1.0?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Terminal con identidad propia
&lt;/h3&gt;

&lt;p&gt;Al abrir la terminal verás el banner de &lt;strong&gt;Corvorum OS&lt;/strong&gt; con la información del sistema: versión del kernel, modelos de IA local disponibles, y los comandos principales listos para usar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CORVORUM OS
Desktop Edition 1.0 | 6.8.0-124-generic
IA Local: 3 modelo(s) disponible(s)
Escribe:
  - 'ai' para consultar al asistente Odín
  - 'codium' para el editor de código
  - 'opencode' para iniciar el agente de código
  - 'btop' para administrador de tareas
  - 'mc' para ejecutar el explorador de archivos

Corvorum OS en 📁 ~
Transmutando ›
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Asistente Odín — IA local sin internet
&lt;/h3&gt;

&lt;p&gt;Corvorum OS incluye integración con &lt;strong&gt;Ollama&lt;/strong&gt; para inferencia local de modelos LLM. Con solo escribir &lt;code&gt;ai&lt;/code&gt; en la terminal tienes acceso al asistente &lt;strong&gt;Odín&lt;/strong&gt;, que corre completamente en local, sin enviar ningún dato a servidores externos.&lt;/p&gt;

&lt;p&gt;La versión 1.0 viene con soporte para múltiples modelos simultáneos. Tú decides qué modelo usar según la tarea.&lt;/p&gt;

&lt;p&gt;También puedes ejecutar directamente los comandos de ollama para ver los modelos disponibles y descargar nuevos modelos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Soporte para aplicaciones Windows
&lt;/h3&gt;

&lt;p&gt;Una de las características más llamativas de Corvorum OS es su capacidad para ejecutar aplicaciones Windows de forma transparente gracias a &lt;strong&gt;umu-run&lt;/strong&gt; con Proton.&lt;/p&gt;

&lt;p&gt;El sistema incluye un servicio en segundo plano que:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detecta automáticamente las aplicaciones Windows instaladas vía umu-run.&lt;/li&gt;
&lt;li&gt;Corrige los lanzadores generados (que en Linux nativo no funcionan directamente).&lt;/li&gt;
&lt;li&gt;Los añade al menú de aplicaciones del sistema en la categoría "Otros" para que el usuario los encuentre como cualquier app nativa.&lt;/li&gt;
&lt;li&gt;Extrae el icono real del &lt;code&gt;.exe&lt;/code&gt; y lo usa en el acceso directo, sin iconos genéricos.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Aplicaciones como &lt;strong&gt;Notepad++&lt;/strong&gt; funcionan desde el menú con un solo clic, igual que en Windows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Herramientas para desarrolladores incluidas
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VSCodium&lt;/strong&gt; — editor de código open source (base de VS Code sin telemetría)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opencode&lt;/strong&gt; — agente de código con IA integrada en terminal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;btop&lt;/strong&gt; — monitor de sistema avanzado&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mc&lt;/strong&gt; — Midnight Commander, explorador de archivos en consola&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt; — listo para usar&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git&lt;/strong&gt; — configurado desde el primer arranque&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Filosofía del proyecto
&lt;/h2&gt;

&lt;p&gt;Corvorum OS nace de una creencia simple: &lt;strong&gt;recuperar el control de lo que haces y no depender de terceros&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Vivimos una era de Inteligencia Artificial bajo suscripciones y sistemas que implementan IA desde la nube. Eso genera una dependencia que todavía podemos sostener pero que en el futuro se volverá mas cara y difícil de gestionar, para cuando llegue ese momento quiero ofrecer esta alternativa ya para entonces madura e independiente que pueda ahorrarte pagar elevados precios por computo en IA o estén haciendo telemetría del uso que tienes con tu ordenador.&lt;/p&gt;

&lt;p&gt;Por eso el sistema integra IA local, soporte Windows y entorno de desarrollo como parte del sistema.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;No sabemos con exactitud lo que nos depara el futuro, lo que si podemos y debemos hacer es construirlo a través del presente.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Cómo probarlo
&lt;/h2&gt;

&lt;p&gt;La imagen ISO de Corvorum OS 1.0 Desktop Edition está disponible para descarga:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 [&lt;a href="https://sourceforge.net/projects/corvorum-os/files/release1.0/corvorum-os-1.0_amd64.iso/download" rel="noopener noreferrer"&gt;https://sourceforge.net/projects/corvorum-os/files/release1.0/corvorum-os-1.0_amd64.iso/download&lt;/a&gt;]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Puedes probarlo en una máquina virtual (VirtualBox, VMware, QEMU) o instalarlo directamente en hardware compatible (arquitectura amd64).&lt;/p&gt;

&lt;p&gt;Requisitos mínimos recomendados:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;8 GB RAM (8 GB recomendado para modelos de IA local)&lt;/li&gt;
&lt;li&gt;40 GB de espacio en disco&lt;/li&gt;
&lt;li&gt;CPU de 64 bits&lt;/li&gt;
&lt;li&gt;GPU Intel Iris XE o superior&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Roadmap
&lt;/h2&gt;

&lt;p&gt;Corvorum OS 1.0 es el punto de partida. El proyecto tiene un ecosistema más amplio en desarrollo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Odin Agent&lt;/strong&gt; — Asistente Odin que permite búsqueda de archivos, busquedas en internet, creación de documentos y otro tipo de funcionalidades.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notificación de actualizaciones&lt;/strong&gt; — Sistema que comprueba los repositorios oficiales.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuración de agentes IA&lt;/strong&gt; - Herramienta para comprobar los requisitos del equipo y elegir por defecto cuales son los mejores agentes para cada equipo, también da la opción a cambiarlos manualmente sin tener que buscarlos en Ollama.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repositorio Community&lt;/strong&gt; — abierto próximamente para contribuciones externas, con sistema de revisión progresivo y la &lt;strong&gt;Edición Technomantus&lt;/strong&gt; como rama oficial curada.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Contribuir
&lt;/h2&gt;

&lt;p&gt;El proyecto vive en:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 [&lt;a href="https://github.com/Forja-de-Onix/corvorum-os" rel="noopener noreferrer"&gt;https://github.com/Forja-de-Onix/corvorum-os&lt;/a&gt;]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Si quieres contribuir, reportar un bug o simplemente probar el sistema y dar feedback, eres bienvenido. Las contribuciones se revisan antes de integrarse en la rama oficial, y los colaboradores activos obtendrán privilegios de code review con el tiempo.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Corvorum OS es un proyecto open source de la Forja de Onix. Hecho con Python, Bash, Linux y demasiada paciencia.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
