<?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: William Arin</title>
    <description>The latest articles on DEV Community by William Arin (@williarin).</description>
    <link>https://dev.to/williarin</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%2F680682%2F5a153d1e-ad84-41f5-a6a5-2ddfebe0dd96.jpg</url>
      <title>DEV Community: William Arin</title>
      <link>https://dev.to/williarin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/williarin"/>
    <language>en</language>
    <item>
      <title>Stochastix: High-Performance Quantitative Backtesting Engine in PHP/Symfony</title>
      <dc:creator>William Arin</dc:creator>
      <pubDate>Thu, 19 Jun 2025 15:08:21 +0000</pubDate>
      <link>https://dev.to/williarin/stochastix-high-performance-quantitative-backtesting-engine-1mib</link>
      <guid>https://dev.to/williarin/stochastix-high-performance-quantitative-backtesting-engine-1mib</guid>
      <description>&lt;p&gt;Have you ever wanted to backtest your trading strategies in PHP, instead of Python or MQL5? Well, probably not, as PHP is not the first language that comes to mind for algotrading. However, I have been working on a project that might change that perception.&lt;/p&gt;

&lt;p&gt;Stochastix is a high-performance quantitative backtesting engine designed for PHP developers. It allows you to test your trading strategies against historical data, providing insights into their performance, visual analysis, and more. The engine is built with performance in mind, leveraging PHP's &lt;code&gt;ds&lt;/code&gt; and &lt;code&gt;bcmath&lt;/code&gt; extensions to handle large datasets efficiently, using a custom binary format.&lt;/p&gt;

&lt;p&gt;If you've worked with Freqtrade or MQL5, you should feel at home as most concepts are similar. The engine is architectured as a Symfony bundle, which facilitates the engine upgrades and allows you to build your own application on top of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;

&lt;p&gt;The engine is built with the following technologies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PHP 8.4&lt;/li&gt;
&lt;li&gt;Symfony 7.3&lt;/li&gt;
&lt;li&gt;Nuxt 3&lt;/li&gt;
&lt;li&gt;Mercure for real-time updates&lt;/li&gt;
&lt;li&gt;Docker for containerization&lt;/li&gt;
&lt;li&gt;SQLite for the message queue&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Features overview
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;bar-by-bar processing&lt;/li&gt;
&lt;li&gt;market, limit, stop orders&lt;/li&gt;
&lt;li&gt;multi-timeframe strategies&lt;/li&gt;
&lt;li&gt;custom indicators&lt;/li&gt;
&lt;li&gt;binary formats to speed up data loading&lt;/li&gt;
&lt;li&gt;automatic data download from lots of exchanges (ccxt lib)&lt;/li&gt;
&lt;li&gt;UI built with Nuxt with real-time updates with Mercure&lt;/li&gt;
&lt;li&gt;chart plotting showing indicators and executed trades&lt;/li&gt;
&lt;li&gt;number metrics and visual metrics (equity curve, drawdown, etc.)&lt;/li&gt;
&lt;li&gt;default docker install using FrankenPHP (one-liner installation)&lt;/li&gt;
&lt;li&gt;background jobs with Symfony Messenger&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Web interface
&lt;/h2&gt;

&lt;p&gt;Although the framework is usable from the terminal, I've also created a web interface to make it easier to visualize the results of your backtests. The interface is built with Nuxt 3 and provides real-time updates using Mercure. It's an easier way to interact with the engine, especially if you want to get visual analysis with interactive charts.&lt;/p&gt;

&lt;p&gt;Here are some screenshots:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe7szxt382pnnepav36b6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe7szxt382pnnepav36b6.jpg" alt="New Backtest"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg2qvkklnujdo62sraosd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg2qvkklnujdo62sraosd.jpg" alt="Quick Summary"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flqph70o0j5imm5xch9kf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flqph70o0j5imm5xch9kf.jpg" alt="Trades Chart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://phpquant.github.io/stochastix-docs/screenshots.html" rel="noopener noreferrer"&gt;And more...&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What does a strategy look like?
&lt;/h2&gt;

&lt;p&gt;Here's a simple example of a strategy that uses a moving average crossover to generate buy and sell signals.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;I recommend you to read the official documentation at &lt;a href="https://phpquant.github.io/stochastix-docs/" rel="noopener noreferrer"&gt;https://phpquant.github.io/stochastix-docs/&lt;/a&gt; to be sure to not miss anything, but here's how you can install a new project, using this one-liner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--pull&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;always &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;HOST_PWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;:/app &lt;span class="nt"&gt;-v&lt;/span&gt; /var/run/docker.sock:/var/run/docker.sock &lt;span class="se"&gt;\&lt;/span&gt;
  ghcr.io/phpquant/stochastix-installer your-project-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I'd be happy to get some feedback, suggestions or contributions. If you want to try it out, you can find the code on GitHub below. Leave a star if you like it!&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/phpquant" rel="noopener noreferrer"&gt;
        phpquant
      &lt;/a&gt; / &lt;a href="https://github.com/phpquant/stochastix-core" rel="noopener noreferrer"&gt;
        stochastix-core
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      High-Performance Quantitative Backtesting Engine
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Stochastix&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/phpquant/stochastix-core/actions/workflows/ci.yaml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/phpquant/stochastix-core/actions/workflows/ci.yaml/badge.svg" alt="Stochastix CI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stochastix is a high-performance, event-driven quantitative trading backtesting engine built with PHP and Symfony. It provides a modular and extensible framework for developing, testing, and analyzing algorithmic trading strategies.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Key Features&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Event-Driven Architecture:&lt;/strong&gt; Built for performance and realism.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced Order Types:&lt;/strong&gt; Supports Market, Limit, and Stop orders with a full pending order book.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Timeframe Engine:&lt;/strong&gt; Develop strategies that analyze data across multiple timeframes simultaneously.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extensible Metrics:&lt;/strong&gt; A full suite of performance metrics (Sharpe, Sortino, Calmar, etc.) with a dependency-aware calculation engine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-Performance Storage:&lt;/strong&gt; Custom binary file formats for efficient market data storage and retrieval.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Interface:&lt;/strong&gt; Modern web interface to download market data, launch backtest and analyze results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST API:&lt;/strong&gt; A complete API for building custom interfaces, including real-time progress updates via Mercure.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Documentation&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;For the latest official documentation, visit the &lt;a href="https://phpquant.github.io/stochastix-docs" rel="nofollow noopener noreferrer"&gt;Stochastix Documentation&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;This project is a Symfony bundle. To install it in your application:&lt;/p&gt;


&lt;ol&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Require the&lt;/strong&gt;…&lt;/p&gt;


&lt;/li&gt;

&lt;/ol&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/phpquant/stochastix-core" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


</description>
      <category>symfony</category>
      <category>backtesting</category>
      <category>algotrading</category>
      <category>php</category>
    </item>
    <item>
      <title>Cook: an alternative to Symfony Flex to execute package-embedded recipes for any PHP project</title>
      <dc:creator>William Arin</dc:creator>
      <pubDate>Sun, 19 Feb 2023 15:46:26 +0000</pubDate>
      <link>https://dev.to/williarin/cook-an-alternative-to-symfony-flex-to-execute-package-embedded-recipes-for-any-php-project-2m87</link>
      <guid>https://dev.to/williarin/cook-an-alternative-to-symfony-flex-to-execute-package-embedded-recipes-for-any-php-project-2m87</guid>
      <description>&lt;p&gt;For the devs out there not working with Symfony, Flex is a Composer plugin that allows to automate Symfony plugins configuration with a recipe, which is a list of things to configure.&lt;/p&gt;

&lt;p&gt;It works by fetching a separate repository containing the recipe, either hosted by Symfony in &lt;code&gt;symfony/recipes&lt;/code&gt; and &lt;code&gt;symfony/recipes-contrib&lt;/code&gt;, or self-hosted with some extra steps required. It's good and it works. But it could be better: embedded in repositories. Unfortunately the Symfony team doesn't plan on adding this feature. It's been rejected &lt;a href="https://symfony.com/blog/symfony-flex-is-going-serverless#comment-24672" rel="noopener noreferrer"&gt;here&lt;/a&gt;, &lt;a href="https://github.com/symfony/flex/pull/753#issuecomment-924109963" rel="noopener noreferrer"&gt;here&lt;/a&gt; and &lt;a href="https://github.com/symfony/flex/issues/745" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While I totally respect their decision, I don't really understand the reasoning. We're left with one option: separate recipe repository. It would be nice if we had all options available, and we're missing the package-embedded one. And so I tried to fill this gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Cook.
&lt;/h2&gt;

&lt;p&gt;Cook is a Composer plugin that executes recipes embedded in packages. It can be used alongside with Flex, or in any other PHP project not limited to Symfony, as long as Composer is installed.&lt;/p&gt;

&lt;p&gt;Features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add new entries to arrays or export new arrays, filter how you want to output it&lt;/li&gt;
&lt;li&gt;Add content to existing files or create them (.env, .gitignore, Makefile, or anything else)&lt;/li&gt;
&lt;li&gt;Copy entire directories from your repository to the project&lt;/li&gt;
&lt;li&gt;Keep existing data by default or overwrite it with a CLI command&lt;/li&gt;
&lt;li&gt;Supports PHP arrays, JSON, YAML, text files&lt;/li&gt;
&lt;li&gt;Output post install instructions&lt;/li&gt;
&lt;li&gt;Process only required packages in the root project&lt;/li&gt;
&lt;li&gt;Uninstall recipe when a package is removed&lt;/li&gt;
&lt;li&gt;CLI commands to install or uninstall recipes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically it's Flex without Flex.&lt;/p&gt;

&lt;p&gt;Here's the repository with full documentation:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/williarin" rel="noopener noreferrer"&gt;
        williarin
      &lt;/a&gt; / &lt;a href="https://github.com/williarin/cook" rel="noopener noreferrer"&gt;
        cook
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Composer plugin to execute recipes embedded in packages
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Cook&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Baking recipes for any PHP package.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/williarin/cook/actions" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/williarin/cook/workflows/Test/badge.svg" alt="Github Workflow"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/williarin/cook#cook" rel="noopener noreferrer"&gt;Cook&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/williarin/cook#introduction" rel="noopener noreferrer"&gt;Introduction&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/williarin/cook#features" rel="noopener noreferrer"&gt;Features&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/williarin/cook#installation" rel="noopener noreferrer"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/williarin/cook#documentation" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/williarin/cook#creating-a-recipe" rel="noopener noreferrer"&gt;Creating a recipe&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/williarin/cook#files" rel="noopener noreferrer"&gt;Files&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/williarin/cook#directories" rel="noopener noreferrer"&gt;Directories&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/williarin/cook#post-install-output" rel="noopener noreferrer"&gt;Post install output&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/williarin/cook#mergers" rel="noopener noreferrer"&gt;Mergers&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/williarin/cook#text" rel="noopener noreferrer"&gt;Text&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/williarin/cook#php-array" rel="noopener noreferrer"&gt;PHP array&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/williarin/cook#json" rel="noopener noreferrer"&gt;JSON&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/williarin/cook#yaml" rel="noopener noreferrer"&gt;YAML&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/williarin/cook#docker-compose" rel="noopener noreferrer"&gt;Docker Compose&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/williarin/cook#placeholders" rel="noopener noreferrer"&gt;Placeholders&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/williarin/cook#cli" rel="noopener noreferrer"&gt;CLI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/williarin/cook#license" rel="noopener noreferrer"&gt;License&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Introduction&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Cook is a Composer plugin that executes recipes embedded in packages, in a similar fashion to &lt;a href="https://github.com/symfony/flex" rel="noopener noreferrer"&gt;Symfony Flex&lt;/a&gt;.
It can be used alongside with Flex, or in any other PHP project, as long as Composer is installed.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Features&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Add new entries to arrays or export new arrays, filter how you want to output it&lt;/li&gt;
&lt;li&gt;Add content to existing files or create them (.env, Makefile, or anything else)&lt;/li&gt;
&lt;li&gt;Copy entire directories from your repository to the project&lt;/li&gt;
&lt;li&gt;Keep existing data by default or overwrite it with a CLI command&lt;/li&gt;
&lt;li&gt;Supports PHP arrays, JSON, YAML, text files&lt;/li&gt;
&lt;li&gt;Output post install instructions&lt;/li&gt;
&lt;li&gt;Process only required packages in the root project&lt;/li&gt;
&lt;li&gt;Uninstall recipe when a package is removed&lt;/li&gt;
&lt;li&gt;CLI commands to…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/williarin/cook" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Please give a star if you like it!&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>discuss</category>
      <category>programming</category>
    </item>
    <item>
      <title>SWORD: the merge of Symfony and WordPress</title>
      <dc:creator>William Arin</dc:creator>
      <pubDate>Thu, 15 Sep 2022 05:17:17 +0000</pubDate>
      <link>https://dev.to/williarin/sword-the-merge-of-symfony-and-wordpress-4i3c</link>
      <guid>https://dev.to/williarin/sword-the-merge-of-symfony-and-wordpress-4i3c</guid>
      <description>&lt;p&gt;It could be a new beginning in the WordPress era. Imagine WordPress being WordPress, but also being Symfony. Not a subset of WordPress and a subset of Symfony. Both WordPress and Symfony at their full potential, at the same time, on the same application.&lt;/p&gt;

&lt;p&gt;Sounds like a dream? It's now reality.&lt;/p&gt;

&lt;p&gt;Meet &lt;strong&gt;Sword&lt;/strong&gt;. The best of both worlds.&lt;/p&gt;

&lt;p&gt;So, what Sword really is and what problems does it solve? If you've heard of &lt;a href="https://roots.io/bedrock/"&gt;Bedrock&lt;/a&gt;, it's similar but with Symfony.&lt;br&gt;
Sword is Symfony running WordPress.&lt;/p&gt;

&lt;p&gt;It allows you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use a centralized composer for WordPress core and plugins as well as other libs&lt;/li&gt;
&lt;li&gt;extend a WordPress website well outside its bounds, into the modern framework territory&lt;/li&gt;
&lt;li&gt;give WordPress code standardized and modern style and patterns&lt;/li&gt;
&lt;li&gt;bring all the good things of Symfony into WordPress, be it dependency injection, translator, Symfony UX, etc...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's no limit to what you can do.&lt;br&gt;
You want Symfony routes bypassing WordPress? No problem, it's a Symfony app.&lt;br&gt;
You need Symfony UX into your WordPress theme? Sure.&lt;br&gt;
You need a specific page to be 100% Symfony with API calls to WordPress API? Yes you can.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full installation with Docker takes one command. Zero configuration. No Docker knowledge needed&lt;/strong&gt; (recommended for further usage though).&lt;br&gt;
Your Sword app should be ready in one or two minutes.&lt;/p&gt;

&lt;p&gt;Performance wise, it seems fast enough. Pure Symfony performance is not impacted, the WordPress side is maybe 90ms slower. No big deal given the superpowers you get in exchange.&lt;/p&gt;

&lt;p&gt;Here's the link: &lt;a href="https://getsword.com"&gt;https://getsword.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you like it please give a star on the Github repo and share it!&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/phpsword"&gt;
        phpsword
      &lt;/a&gt; / &lt;a href="https://github.com/phpsword/sword-bundle"&gt;
        sword-bundle
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Modern WordPress development with Symfony
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/phpsword/sword-bundle/assets/sword-logo-tagline-sm.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u4y1_6-t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/phpsword/sword-bundle/assets/sword-logo-tagline-sm.png" alt="Sword Logo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;a href="https://github.com/phpsword/sworld-bundle/actions"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Iw_MhqcG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/phpsword/sword-bundle/workflows/Test/badge.svg" alt="Github Workflow"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
Introduction&lt;/h1&gt;
&lt;p&gt;Modern WordPress development with Symfony.&lt;/p&gt;
&lt;h1&gt;
Documentation&lt;/h1&gt;
&lt;p&gt;Visit &lt;a href="https://getsword.com/" rel="nofollow"&gt;https://getsword.com/&lt;/a&gt; for official documentation.&lt;/p&gt;
&lt;h1&gt;
Contributions&lt;/h1&gt;
&lt;p&gt;Feel free to submit issues and pull requests.&lt;/p&gt;
&lt;h1&gt;
License&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://github.com/phpsword/sword-bundleLICENSE"&gt;MIT&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Copyright (c) 2022, William Arin&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/phpsword/sword-bundle"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;I previously posted on Reddit, it got some attention with interesting comments. Check it out eventually:&lt;/p&gt;


&lt;div class="ltag__reddit--container"&gt;
  &lt;div class="ltag__reddit--title-container"&gt;
    
      &lt;div class="ltag__reddit--title"&gt;
        &lt;h1&gt;
          &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bCqI7Yj---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/reddit-icon-c6851eed10026b5707e2e8c814b5bbcbb4823de68d5b611a6f4b99c8beed6f05.svg" alt="Reddit Logo"&gt;
          &lt;a href="https://www.reddit.com/r/PHP/comments/xbcxy8/symfony_meets_wordpress_i_call_it_sword/" rel="noopener noreferrer"&gt;
            Symfony meets WordPress. I call it Sword.
          &lt;/a&gt;
        &lt;/h1&gt;
        &lt;div class="ltag__reddit--post-metadata"&gt;
          &lt;span&gt;Sep 11 '22&lt;/span&gt;
          &lt;span&gt;Author: williarin&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    
  &lt;/div&gt;
  &lt;div class="ltag__reddit--body"&gt;
    
&lt;p&gt;Few months ago I came here to introduce a library to work with WordPress database from a third party app (WordpressInterop). I use it to work in a Symfony app that connects to some WordPress websites.&lt;/p&gt;
&lt;p&gt;And then I had a monolithic dream. While the world is falling apart into microservices, I had a vision of WordPress and Symfony merging…&lt;/p&gt;
    
  &lt;/div&gt;
  &lt;div class="ltag__reddit--btn--container"&gt;
    
      &lt;a href="https://www.reddit.com/r/PHP/comments/xbcxy8/symfony_meets_wordpress_i_call_it_sword/" rel="noopener noreferrer"&gt;See Full Post&lt;/a&gt;
    
  &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag__user ltag__user__id__680682"&gt;
    &lt;a href="/williarin" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y_QXVNkY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--eg7nv3KA--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/680682/5a153d1e-ad84-41f5-a6a5-2ddfebe0dd96.jpg" alt="williarin image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/williarin"&gt;William Arin&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/williarin"&gt;Developing Sword (getsword.com), NumberNine CMS (numberninecms.com) and other things.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>symfony</category>
      <category>wordpress</category>
      <category>php</category>
      <category>cms</category>
    </item>
    <item>
      <title>Secure your MySQL/MariaDB backups with GFS retention and encryption</title>
      <dc:creator>William Arin</dc:creator>
      <pubDate>Fri, 07 Jan 2022 15:35:18 +0000</pubDate>
      <link>https://dev.to/williarin/secure-your-mysqlmariadb-databases-with-gfs-retention-encrypted-backups-22lm</link>
      <guid>https://dev.to/williarin/secure-your-mysqlmariadb-databases-with-gfs-retention-encrypted-backups-22lm</guid>
      <description>&lt;p&gt;While there are plenty of existing Docker images to backup MySQL/MariaDB databases with a rotating scheme, I needed GFS retention scheme. GFS as in &lt;a href="https://en.wikipedia.org/wiki/Backup_rotation_scheme#Grandfather-father-son" rel="noopener noreferrer"&gt;Grandfather-father-son&lt;/a&gt;, with several backup cycles: daily, weekly, monthly, yearly.&lt;/p&gt;

&lt;p&gt;So I created this image for my own use at first, then I published it with MIT license.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/williarin" rel="noopener noreferrer"&gt;
        williarin
      &lt;/a&gt; / &lt;a href="https://github.com/williarin/secure-mysql-backups" rel="noopener noreferrer"&gt;
        secure-mysql-backups
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Long-term retention for secure MySQL backups
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Long-term retention for secure MySQL backups&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;This Docker image will backup your MySQL/MariaDB databases following the Grandfather-Father-Son (GFS) retention scheme.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;GFS backups retention scheme&lt;/li&gt;
&lt;li&gt;AES256 encryption/decryption&lt;/li&gt;
&lt;li&gt;Single or multiple databases backups&lt;/li&gt;
&lt;li&gt;Grouped or individual archives&lt;/li&gt;
&lt;li&gt;Parallelized compression&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;GFS retention scheme&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;This means that you'll always have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A backup for every day of the last week (6)&lt;/li&gt;
&lt;li&gt;A backup for every week of the last month (4)&lt;/li&gt;
&lt;li&gt;A backup for every month of the last year (12)&lt;/li&gt;
&lt;li&gt;A backup for every previous year (unlimited)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a 100MB backup, it will cost you around 2GB for the current year + 100MB for each previous year.&lt;/p&gt;
&lt;p&gt;In SuperSafe mode, you'll have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A backup for every day of the last month (28~31)&lt;/li&gt;
&lt;li&gt;A backup for every week of the last year (48)&lt;/li&gt;
&lt;li&gt;A backup for every previous year (unlimited)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a 100MB backup, backups will cost you around 8GB for the current year…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/williarin/secure-mysql-backups" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Swarm-ready
&lt;/h3&gt;

&lt;p&gt;All configuration variables can be sourced from a file. This is ideal when deploying with Docker Swarm, as you can source your passwords from Swarm secrets.&lt;/p&gt;

&lt;h3&gt;
  
  
  GFS backups with 2 retention schemes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Normal GFS mode&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this mode, you'll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A backup for every day of the last week (6)&lt;/li&gt;
&lt;li&gt;A backup for every week of the last month (4)&lt;/li&gt;
&lt;li&gt;A backup for every month of the last year (12)&lt;/li&gt;
&lt;li&gt;A backup for every previous year (unlimited)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;SuperSafe mode&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In SuperSafe mode, you'll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A backup for every day of the last month (28~31)&lt;/li&gt;
&lt;li&gt;A backup for every week of the last year (48)&lt;/li&gt;
&lt;li&gt;A backup for every previous year (unlimited)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AES256 encryption and decryption
&lt;/h3&gt;

&lt;p&gt;Archives can be encrypted and decrypted with AES-256-CBC algorithm.&lt;/p&gt;

&lt;h3&gt;
  
  
  Single or multiple databases backups
&lt;/h3&gt;

&lt;p&gt;You can either create an archive with all your databases inside, or one archive per database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parallelized compression
&lt;/h3&gt;

&lt;p&gt;If your server has multiple CPUs, compressing the archives will use them all. You can configure the number of CPUs to be used to avoid saturating your server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Manual backups
&lt;/h3&gt;

&lt;p&gt;You can also trigger a backup manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Future proof
&lt;/h3&gt;

&lt;p&gt;This image has been tested with multiple unit and functional tests that cover all the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Learn more about how to use this image on the &lt;a href="https://github.com/williarin/secure-mysql-backups" rel="noopener noreferrer"&gt;Github repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What other features would you like to see in this image?&lt;/p&gt;

</description>
      <category>mysql</category>
      <category>backup</category>
      <category>docker</category>
      <category>security</category>
    </item>
    <item>
      <title>Create dynamic Tailwind CSS color palettes</title>
      <dc:creator>William Arin</dc:creator>
      <pubDate>Sat, 21 Aug 2021 14:08:07 +0000</pubDate>
      <link>https://dev.to/williarin/create-dynamic-tailwind-css-color-palettes-13me</link>
      <guid>https://dev.to/williarin/create-dynamic-tailwind-css-color-palettes-13me</guid>
      <description>&lt;p&gt;Sure, there are tools out there that will generate for you a color palette for a given color. But will you want to manually generate it again and copy the hex values every time you update the color?&lt;/p&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;Tailwind CSS gives us the ability to generate dynamic palettes, although it requires a little JavaScript function to make it work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with defining a root color
&lt;/h2&gt;

&lt;p&gt;The trick is to work with HSL values.&lt;/p&gt;

&lt;p&gt;Let's say we want to have a palette from the color &lt;code&gt;#0c8a4b&lt;/code&gt; and call it &lt;code&gt;primary&lt;/code&gt;. The first step is to get the HSL values. To get them, we can use this color converter website &lt;a href="https://convertacolor.com" rel="noopener noreferrer"&gt;https://convertacolor.com/&lt;/a&gt; or any color picker like the one in the browser's DevTools.&lt;/p&gt;

&lt;p&gt;In the HEX field, we enter &lt;code&gt;#0c8a4b&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It will give us in return a HSL field containing &lt;code&gt;hsl(150,84%,29.4%)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now in our CSS file, we define our base color:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--color-primary-h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;150&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--color-primary-s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;84%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--color-primary-l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;29.4%&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;Be careful, the first value &lt;code&gt;H&lt;/code&gt; is not a percentage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extend Tailwind CSS config
&lt;/h2&gt;

&lt;p&gt;In our &lt;code&gt;tailwind.config.js&lt;/code&gt; file, we'll add a small function at the top:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;dynamicHsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;opacityVariable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opacityValue&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opacityValue&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`hsla(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;opacityValue&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&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="nx"&gt;opacityVariable&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`hsla(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, var(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;opacityVariable&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, 1))`&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`hsl(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;This function will convert 3 given HSL values into a CSS property. Thanks to Tailwind CSS 2.0 and above, our generated color palette will also be able to take opacity into account.&lt;/p&gt;

&lt;p&gt;And now we extend the theme using this new function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;DEFAULT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dynamicHsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-h)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-s)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-l)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dynamicHsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-h)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-s)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;calc(var(--color-primary-l) + 30%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dynamicHsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-h)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-s)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;calc(var(--color-primary-l) + 24%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dynamicHsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-h)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-s)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;calc(var(--color-primary-l) + 18%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dynamicHsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-h)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-s)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;calc(var(--color-primary-l) + 12%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dynamicHsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-h)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-s)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;calc(var(--color-primary-l) + 6%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dynamicHsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-h)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-s)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-l)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dynamicHsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-h)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-s)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;calc(var(--color-primary-l) - 6%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dynamicHsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-h)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-s)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;calc(var(--color-primary-l) - 12%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dynamicHsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-h)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-primary-s)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;calc(var(--color-primary-l) - 18%)&lt;/span&gt;&lt;span class="dl"&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;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;By varying the &lt;code&gt;L&lt;/code&gt; value alone, the palette will keep a monochromatic look, going from light to dark. Exactly what we want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enjoy
&lt;/h2&gt;

&lt;p&gt;This will give us these new utility classes to use where we want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;text-primary (same as text-primary-600)&lt;/li&gt;
&lt;li&gt;text-primary-100 to text-primary-900&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And following the same pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bg-primary&lt;/li&gt;
&lt;li&gt;border-primary&lt;/li&gt;
&lt;li&gt;ring-primary&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also combine these values with opacity, for instance &lt;code&gt;class="border border-primary border-opacity-25"&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go further
&lt;/h2&gt;

&lt;p&gt;This technique works best when the base color is not too clear and not too dark. Try other percentage values than +30% to -18% and experiment with what better fits your needs.&lt;/p&gt;

</description>
      <category>tailwindcss</category>
      <category>javascript</category>
      <category>color</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Split your Symfony views into multiple controllers with NumberNine CMS</title>
      <dc:creator>William Arin</dc:creator>
      <pubDate>Fri, 13 Aug 2021 16:43:42 +0000</pubDate>
      <link>https://dev.to/numberninecms/split-your-symfony-views-into-multiple-controllers-with-numbernine-cms-2nj0</link>
      <guid>https://dev.to/numberninecms/split-your-symfony-views-into-multiple-controllers-with-numbernine-cms-2nj0</guid>
      <description>&lt;p&gt;When building a simple page with Symfony, we usually end up having a controller and a view.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp8y4yujvl2cfw9jifqs8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp8y4yujvl2cfw9jifqs8.png" alt="Simple controller" width="274" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oftentimes, things get complicated and one controller is not a viable solution anymore, therefore we need to split the view like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fss1gg87kgzhtr5n3ss1m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fss1gg87kgzhtr5n3ss1m.png" alt="Multiple controllers" width="600" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Symfony offers several ways to split your views into multiple controllers. NumberNine CMS offers two more. We'll see which cons and pros each method provides.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;using Twig partials with &lt;code&gt;include&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;include&lt;/span&gt; &lt;span class="s1"&gt;'my_partial.html.twig'&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'hello'&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;using Twig partials with &lt;code&gt;embed&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;embed&lt;/span&gt; &lt;span class="s1"&gt;'my_partial.html.twig'&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
    &lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;hello&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endembed&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;using &lt;a href="https://symfony.com/doc/current/templates.html#embedding-controllers" rel="noopener noreferrer"&gt;embedded controllers&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MyController'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'hello'&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;using &lt;a href="https://github.com/symfony/ux-twig-component" rel="noopener noreferrer"&gt;Twig components&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;component&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'my_component'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'hello'&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;using NumberNine components
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;N9_component&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MyComponent'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'hello'&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;using NumberNine shortcodes
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;N9_shortcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'[my_shortcode message="hello"]'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Pros and cons of each technique
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Twig includes and embeds&lt;/strong&gt; are great and flexible but their data come from above, which makes the root controller bloated. Of course there are workarounds such as Twig extensions or global variables to access a service, but a view shouldn't retrieve data that the controller didn't send to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Embedded controllers&lt;/strong&gt; are a good solution if they're used sporadically, as they make a new Request and will negatively impact performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Twig Components&lt;/strong&gt; is a better approach than embedded controllers, although they have some limitations. Components are non shared services, which means a new instance is created at every call of &lt;code&gt;{{ component('my_component') }}&lt;/code&gt;, erasing previously stored data. This means that if your component registers an event listener, it won't work as the instance is created on the fly.&lt;/p&gt;
&lt;h2&gt;
  
  
  NumberNine components
&lt;/h2&gt;

&lt;p&gt;NumberNine Components solve the problem of Twig Component's separate instances by creating a new set of template parameters everytime the component is called, but the instance itself of the component stays the same.&lt;/p&gt;

&lt;p&gt;A NumberNine Component is a shared service, hence event listeners registered will catch events as they should. Parameters injection is done through setters.&lt;/p&gt;

&lt;p&gt;Here's a concrete example.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;# templates/page/show.html.twig

&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;N9_component&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MyComponent'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;example&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'string variable'&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&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="c1"&gt;// src/Component/MyComponent/MyComponent.php&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyComponent&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ComponentInterface&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$example&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;setExample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$example&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="n"&gt;example&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$example&lt;/span&gt;&lt;span class="p"&gt;;&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;getTemplateParameters&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="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'example'&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;example&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;// src/Component/MyComponent/template.html.twig

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Displaying custom variable: &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;example&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Moreover, just like every other view in NumberNine, component templates are overridable. The theme you use probably uses components, and you'll probably want to adapt the design.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://numberninecms.com/developers/howto/theming/create-a-component.html" rel="noopener noreferrer"&gt;Read more&lt;/a&gt; about components on the documentation page.&lt;/p&gt;
&lt;h2&gt;
  
  
  NumberNine shortcodes
&lt;/h2&gt;

&lt;p&gt;While components are developer-oriented, shortcodes are a user-oriented way to inject templates in the view.&lt;/p&gt;

&lt;p&gt;Also known as BBcodes, shortcodes and have been widely used in forums and CMS for years. They are represented by a string that the user can input in his editor.&lt;/p&gt;

&lt;p&gt;They have a short syntax:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[my_shortcode message="hello"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Or an extended syntax:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[my_shortcode]hello[/my_shortcode]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Extended syntax allows for nested shortcodes:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[my_shortcode]
    [my_nested_shortcode message="hello"]
[/my_shortcode]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;NumberNine handles shortcodes as services. Each shortcode has its own class which acts like a controller.&lt;/p&gt;

&lt;p&gt;As an example, we'll analyze this page header:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl82bfj06mjswwjq7cal2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl82bfj06mjswwjq7cal2.jpg" alt="Page header" width="472" height="223"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now look at how it's rendered behind the scene:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkoa2ps5hqi7a6rmhl1p9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkoa2ps5hqi7a6rmhl1p9.jpg" alt="Rendered shortcodes" width="800" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is not the result of a manual input in an editor, although it can. To know more about how it was rendered, see the last section of this article about the page builder.&lt;/p&gt;

&lt;p&gt;Back to our view splitting. To put it simply, this header is composed of 9 controllers and 9 views, with no negative performance impact.&lt;/p&gt;

&lt;p&gt;Let's take the &lt;code&gt;my_account_link&lt;/code&gt; shortcode and see how it's built, as it's a very basic shortcode. Its purpose is to automatically link to the user-defined "My account" page:&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="cd"&gt;/**
 * @Shortcode(name="my_account_link", label="My Account Link")
 */&lt;/span&gt;
&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyAccountLinkShortcode&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractShortcode&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;configureParameters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;OptionsResolver&lt;/span&gt; &lt;span class="nv"&gt;$resolver&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;$resolver&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setDefaults&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'loggedOutText'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Login / Register'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'loggedInText'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'My account'&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;processParameters&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;$parameters&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="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'loggedOutText'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$parameters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'loggedOutText'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'loggedInText'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$parameters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'loggedInText'&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;While its template looks like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;N9_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;PAGE_FOR_MY_ACCOUNT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;is_granted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'IS_AUTHENTICATED_REMEMBERED'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
        &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;loggedInText&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;trans&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
    &lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
        &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;loggedOutText&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;trans&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
    &lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now it can be used either by the developer with &lt;code&gt;{{ N9_shortcode('[my_account_link]') }}&lt;/code&gt;, or directly by the user in a text editor with &lt;code&gt;[my_account_link]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's all! Our views can be split in many ways and still keep a high performance. The main controller stays very light, and every partial controllers handle their own responsability. This makes the code highly reusable and easier to use for both developers and users.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://numberninecms.com/developers/howto/theming/create-a-shortcode.html" rel="noopener noreferrer"&gt;Read more&lt;/a&gt; about shortcodes on the documentation page.&lt;/p&gt;
&lt;h2&gt;
  
  
  Shortcodes and the page builder
&lt;/h2&gt;

&lt;p&gt;NumberNine's page builder relies on shortcodes to build the view. Each shortcode can be defined as an editable shortcode, which will appear in the page builder's list of available components to the user.&lt;/p&gt;

&lt;p&gt;This will be the topic of an other article. Meanwhile, &lt;a href="https://numberninecms.com/developers/howto/theming/create-a-shortcode.html" rel="noopener noreferrer"&gt;check out the documentation&lt;/a&gt; to see how to build a page builder component with a shortcode.&lt;/p&gt;
&lt;h2&gt;
  
  
  Read more
&lt;/h2&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/numberninecms" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F4530%2F2604e237-cf9a-4a2b-aae3-44bd2117b1a4.png" alt="NumberNine CMS" width="800" height="800"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F680682%2F5a153d1e-ad84-41f5-a6a5-2ddfebe0dd96.jpg" alt="" width="800" height="800"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/numberninecms/symfony-on-steroids-with-numbernine-cms-4no4" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Symfony on steroids with NumberNine CMS&lt;/h2&gt;
      &lt;h3&gt;William Arin for NumberNine CMS ・ Aug 8 '21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#symfony&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#php&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#cms&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;



</description>
      <category>symfony</category>
      <category>cms</category>
      <category>php</category>
    </item>
    <item>
      <title>Symfony on steroids with NumberNine CMS</title>
      <dc:creator>William Arin</dc:creator>
      <pubDate>Sun, 08 Aug 2021 05:33:17 +0000</pubDate>
      <link>https://dev.to/numberninecms/symfony-on-steroids-with-numbernine-cms-4no4</link>
      <guid>https://dev.to/numberninecms/symfony-on-steroids-with-numbernine-cms-4no4</guid>
      <description>&lt;p&gt;Web agencies and freelancers don't need to waste precious time on project setup and configuration anymore. With &lt;a href="https://numberninecms.com" rel="noopener noreferrer"&gt;NumberNine&lt;/a&gt;'s installer, you'll be able to start working on a Symfony app in seconds.&lt;/p&gt;

&lt;p&gt;Copy/paste this installation one-liner in your terminal, given that you have &lt;a href="https://www.docker.com/products/docker-desktop" rel="noopener noreferrer"&gt;Docker installed&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--pull&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;always &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;HOST_PWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;$PWD&lt;/span&gt;:/srv/app &lt;span class="nt"&gt;-v&lt;/span&gt; /var/run/docker.sock:/var/run/docker.sock &lt;span class="se"&gt;\&lt;/span&gt;
    numberninecms/installer myproject
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Boom! Done.&lt;/p&gt;

&lt;p&gt;One minute later, even without any prior knowledge of Docker, here's what you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The latest Symfony version (&lt;code&gt;symfony/web-skeleton&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The latest NumberNine version&lt;/li&gt;
&lt;li&gt;The latest PHP, MySQL, Nginx, Redis, Maildev and blackfire versions running as Docker containers&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://numberninecms.com/developers/docker.html" rel="noopener noreferrer"&gt;PHP Docker container&lt;/a&gt; with Cachetool,  Composer 2, APCu, Xdebug 3, make, and zsh&lt;/li&gt;
&lt;li&gt;A self-signed SSL certificate for &lt;code&gt;myproject.localhost&lt;/code&gt; and HTTPS access&lt;/li&gt;
&lt;li&gt;A ready-to-use website with all CMS features&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Simplicity in mind
&lt;/h2&gt;

&lt;p&gt;NumberNine is a CMS which focus on simplicity of use, both for the end-users and the developers. It automates most of the tedious steps of setting up a new Symfony project, while also easing the maintenance by the clients in charge of their content.&lt;/p&gt;

&lt;p&gt;For instance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have you ever used &lt;code&gt;MakerBundle&lt;/code&gt; to create user, registration form, etc.?&lt;/li&gt;
&lt;li&gt;Have you ever created some CRUD controllers for your entities?&lt;/li&gt;
&lt;li&gt;Have you ever installed &lt;code&gt;VichUploaderBundle&lt;/code&gt; or implemented file upload?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you answered yes to any of these questions, you'll most probably find benefits using NumberNine. All these are core features.&lt;/p&gt;

&lt;p&gt;What's more? It's still a Symfony app, which means that you can develop things that won't interfere with the CMS, the Symfony way. NumberNine acts like an additional CMS layer, so you get the best of both worlds.&lt;/p&gt;
&lt;h2&gt;
  
  
  Some features
&lt;/h2&gt;

&lt;p&gt;You'll find on &lt;a href="https://numberninecms.com/developers/features.html" rel="noopener noreferrer"&gt;this page&lt;/a&gt; an exhaustive list of features, but the most notable are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client-side image resizing with high quality downscaling algorithm&lt;/li&gt;
&lt;li&gt;Granular user management with capabilities assigned to roles&lt;/li&gt;
&lt;li&gt;WYSIWYG extensible page builder&lt;/li&gt;
&lt;li&gt;Shortcodes&lt;/li&gt;
&lt;li&gt;Relationships between entities&lt;/li&gt;
&lt;li&gt;Menu creation&lt;/li&gt;
&lt;li&gt;Easy Twig theming with inheritance (core -&amp;gt; theme -&amp;gt; app)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's MIT license, totally free to use.&lt;/p&gt;

&lt;p&gt;You can star it on Github if you like it.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/numberninecms" rel="noopener noreferrer"&gt;
        numberninecms
      &lt;/a&gt; / &lt;a href="https://github.com/numberninecms/cms" rel="noopener noreferrer"&gt;
        cms
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Core code of NumberNine CMS
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;The developer documentation is at &lt;a href="https://numberninecms.com" rel="noopener noreferrer"&gt;https://numberninecms.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus video trailer
&lt;/h2&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/GcDZgwmVSSM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Read more
&lt;/h2&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/numberninecms" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F4530%2F2604e237-cf9a-4a2b-aae3-44bd2117b1a4.png" alt="NumberNine CMS" width="800" height="800"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F680682%2F5a153d1e-ad84-41f5-a6a5-2ddfebe0dd96.jpg" alt="" width="800" height="800"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/numberninecms/split-your-symfony-views-into-multiple-controllers-with-numbernine-cms-2nj0" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Split your Symfony views into multiple controllers with NumberNine CMS&lt;/h2&gt;
      &lt;h3&gt;William Arin for NumberNine CMS ・ Aug 13 '21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#symfony&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#cms&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#php&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>symfony</category>
      <category>php</category>
      <category>cms</category>
    </item>
  </channel>
</rss>
