Introduction to Winter CMS

Meet Winter CMS

Winter is a Content Management System (CMS) whose sole purpose is to make your development workflow simple again. Winter is built on the Laravel framework, providing a robust base to springboard any type of project, from small brochure websites to large commercial and enterprise applications.

Everything should be made as simple as possible, but not simpler
(Albert Einstein, paraphrased)

Project Structure

The code for your Winter CMS projects can generally exist as one of three different types of extension; as a Theme, Plugin, or Module. Code can also be included in the form of external dependencies managed by Composer.

Directory Structure

MyWinterProject/
|-- bootstrap/         # Code used to bootstrap the application
|-- config/            # Configuration files for the application
|-- modules/           # Modules
|   |-- backend/         # The Backend module
|   |-- cms/             # The CMS module
|   `-- system/          # The System module
|-- node_modules/      # (optional) Frontend vendor files managed by NPM / Yarn
|-- plugins/           # Plugins
|-- storage/           # Local storage directory
|   |-- app/             # Application storage, such as media and uploads
|   |-- cms/             # CMS auto-generated system files
|   |-- framework/       # System and cache files generated by the Laravel framework
|   |-- logs/            # Error and message log storage
|   `-- temp/            # Temporary file storage
|-- themes/            # Themes
|-- vendor/            # PHP vendor files managed by Composer
|-- artisan            # CLI entrypoint
|-- composer.json      # Composer project file (managing PHP dependencies)
|-- composer.lock      # Composer lock file (info on currently installed packages)
|-- index.php          # Web request entrypoint
|-- package.json       # (optional) Node project file (managing frontend dependencies)
|-- package-lock.json  # (optional) Node lock file (info on currently installed frontend packages)
`-- phpunit.xml        # (optional) PHPUnit configuration for running automated testing suites

Themes

Themes contain the frontend code, assets, and functionality as well as static site content.

Check out the Themes page to see an example of the directory structure.

Themes are flat file based, but can also exist in the database through the database templates feature as well as the theme logging feature.

Themes are managed by the CMS module, the default frontend experience in Winter CMS. It is not a required module however, so it is entirely possible to not include the CMS module in your projects if desired and instead use custom plugins to have Winter act as a headless CMS, or not provide a frontend at all and use Winter solely for its backend functionality (for more complex, data focused applications like internal tools or SaaS offerings).

Languages Used

Plugins

Plugins are the foundation for adding new features to Winter CMS by extending it. The registration process allows plugins to declare their features such as components or backend menus and pages. Some examples of what a plugin can do:

  1. Define components.
  2. Define user permissions.
  3. Add settings pages, menu items, lists and forms.
  4. Create database table structures and seed data.
  5. Alter functionality of the core or other plugins.
  6. Provide classes, backend controllers, views, assets, and other files.

Check out the Plugins page to see an example of the directory structure.

Modules

Modules in Winter CMS can be thought of as "core plugins". Winter CMS itself consists of the following three modules:

  • System, used to bootstrap Winter and provide common services used by all other modules and plugins, required for Winter to function.
  • Backend, provides the backend for Winter and all of its associated services, controllers, and other features used by plugins.
  • CMS, provides the default frontend for Winter and all of its associated services used by Themes.

Modules have a ServiceProvider class that handles booting and registration and then can contain any number of other features that would be present in a plugin. While it is possible to develop and use your own custom modules, there really isn't a need to most of the time since plugins are capable of doing anything that a custom module would be able to do while providing a better experience with a version file and plugin management features builtin to the System and Backend modules.

Of the three modules present in Winter CMS, only the System module is required for Winter to function at a basic level. It is entirely possible (and actively done in many Winter projects) to operate with only the Backend module; usually for complex, data-heavy use cases, "intranet" type internal tools, and SaaS type offerings. While it is also possible to operate Winter with only the CMS module, that is less useful as the Backend module is where Winter really provides a lot of value.

Development Features

Winter CMS provides a number of features out of the box to make development easier. A few of them are listed below for easier discoverability.

AJAX Framework

The AJAX Framework is available in both the CMS frontend as well as the backend. Requests are processed by server-side PHP code and return a response. Responses can include rendered partials (providing dynamic content), validation information, or any custom data. There is a data-attributes API available for using it as well as a JavaScript API using jQuery.

The AJAX framework is used within Components, Controllers and Widgets.

Dynamic Content Parser

The Dynamic Content Parser is a templating engine unique to Winter CMS that's used to specify dynamic content fields right within the templates themselves. It has two modes, editor (which generates the fields necessary for the user to provide the custom content for the template) and view (which populates the template with the user provided content).

This template engine can be used on top of other templating engines but is mainly used with Twig. It can be used to generate any type of field that the backend currently supports. The dynamic content parser provides the capability of making fully customisable and intricate content pages that can be client-controlled.

Frontend Components

Components are the main conduit between backend functionality and Frontend content, being used in layouts, pages, and partials. They handle interactions and dynamic content generation as structured “objects”, including a PHP file handling all functionality, default partials for the frontend content, and any additional JS or CSS assets. They can provide configurable properties to set up aspects of the component, controlled through the backend.

Themes can override a component’s partial to tailor the component output to their own specifications.

Asset Compiler

Winter CMS includes two types of asset compilation to assist developers with quickly implementing frontend styling and functionality.

The server-side Asset Compiler makes use of the Assetic Framework to compile and combine assets like CSS and JavaScript serverside, through PHP, negating the need for complex build workflows. The Asset Compiler provides on-the-fly server-side compilation of SASS and LESS stylesheets as well as run-once manual compilation of assets without requiring additional workflow tools like Node or NPM. It is also able to combine and minify CSS and JS files.

Additionally, you can define variables in the theme.yaml file that can be modified in the Theme Settings area of the backend which are then injected into the compiled files, creating flexibility for theming and branding.

For more advanced workflows, or for taking advantage of the vast Node ecosystem, we also include command-line based compilation of assets through Mix commands, which is powered by the Laravel Mix library. These commands take a lot of the headache out of managing, using and compiling Node-based systems and give you the full flexibility to build the frontend the way you want to.

Image Resizing

The Image Resizing service can be used for resizing any image resources accessible to the application.

It works by accepting a variety of image sources and normalizing the pipeline for storing the desired resizing configuration and then deferring the actual resizing of the images until requested by the browser. When the resizer route is hit, the configuration is retrieved from the cache and used to generate the desired image and then redirect to the generated images static path to minimize the load on the server.

Future loads of the image are automatically pointed to the static URL of the resized image without even hitting the resizer route.

Behaviors & Dynamic Class Extension

In Winter CMS, it is possible to dynamically extend the constructor of most classes to add new properties and methods. This also allows binding to local events only present on specific object instances instead of globaly.

Dynamic class extension provides the following benefits:

  1. Add new properties and methods dynamically.
  2. Bind to local events fired in models, widgets, and other locations, even those in the core modules or other plugins.
  3. Add additional behaviours (private traits, see below) to classes.
  4. Must extend the Winter\Storm\Extension\Extendable class or implement the Winter\Storm\Extension\ExtendableTrait.
  5. Already-defined methods and properties cannot be overridden.
  6. Extendable classes are protected against having undefined properties set on first use and must use $object->addDynamicProperty(); instead.
  7. Dynamically added methods cannot override methods defined in code.

See below for a sample of what is possible with dynamic class extension:

// Dynamically extend a model that belongs to a third party plugin
Post::extend(function ($model) {
    // Bind to an event that's only fired locally
    $model->bindEvent('model.afterSave', function () use ($model) {
        if (!$model->isValid()) {
            throw new \Exception("Invalid Model!");
        }
    });

    // Add a new property to the class
    $model->addDynamicProperty('tagsCache', null);

    // Add a new method to the class
    $model->addDynamicMethod('getTagsAttribute', function () use ($model) {
        if ($model->tagsCache) {
            return $model->tagsCache;
        } else {
            return $model->tagsCache = $model->tags()->lists('name');
        }
    });
});

Dynamic Class Extension also adds the ability for classes to have private traits, also known as Behaviors. These are similar to native PHP Traits except they have some distinct benefits:

  1. Behaviors have their own constructor.
  2. Behaviors can have private or protected methods.
  3. Methods and property names can conflict safely.
  4. Provide a safe mechanism for shared functionality across controllers whilst still maintaining their own state.
  5. Classes can be extended with behaviors dynamically.

The best of example of the power of behaviors would be the backend form, list, and relation ControllerBehaviors that provide the majority of CRUD requirements in Winter CMS for any controllers that implement them.

Common Questions

As Winter is a project designed to serve the needs of developers all over the world working on all sorts of different projects we sometimes make decisions that are not obvious from the point of view of individual projects looking to build on Winter. This section should provide some background information on some of the most common questions that are asked about decisions made over the life of the project.

Why Laravel?

The Laravel documentation has an excellent section on why it should be used, but basically it all boils down to the fact that it is the most developer-friendly PHP framework available, not to mention the largest PHP framework with the largest collection of third party packages available for it.

Why Laravel LTS?

Traditionally, Winter has been based off the Laravel LTS (long-term support) releases to ensure the stability of the platform as a whole for our users.

Previously, Laravel had sometimes introduced breaking changes during their intermediary major releases between LTS versions which, while great for the development of the framework, hampered Winter's main goals of stability and reliability. As the Winter CMS development team invests a significant amount of time smoothing over the breaking changes as much as possible as well as putting together detailed, highly targeted migration guides that make the update process a breeze, this meant that it was only feasible for us to follow LTS releases.

Starting with Laravel 8 and 9, the Laravel framework now follows a yearly schedule for releasing major versions and no longer ship LTS releases, with their goal to make drive innovation forward for the framework whilst challenging their core maintainers to ship amazing features without introducing breaking changes. The hope is that upgrading between major versions of Laravel will cause less friction.

As such, the Winter team will investigate this further and determine our future course with following versions. We will update this answer once we have a plan forward.

Why Twig & not Blade?

Winter CMS uses Twig as the default templating engine, despite Blade being the templating engine provided by Laravel.

At a basic level, all templating engines are the same. However, Winter made the decision to go with Twig as it is a robust and mature templating language that is easy to learn, widely used in other project, and very similar to Liquid, the templating engine used by Shopify. These factors combined mean that Twig is easier to pick up and start using for most developers without requiring any existing knowledge of PHP or Blade specifically which means that a wider developer base can have an easier time working with Winter CMS.

Additionally, Blade is subject to the same breaking changes that Laravel itself introduces between major versions, and while breaking changes in a template engine may be fine for a web application framework designed for single use projects that can easily update their view files as well as their logic files during upgrades of the base framework version; it does not align at all with Winter's requirement to provide a stable base to build long term projects on. Having an update to your CMS require that you make changes to your frontend is simply not the developer experience that we wish to provide.

While it is theoretically possible to have your Winter CMS projects use Blade to render templates, it isn't a recommended approach as it is important to have a single templating engine used for the frontend to ensure maximum compatibility between all themes & plugins.

For historical context as well, when the decision was made Blade did not have many of the more advanced features it does today, and those features (such as Components) have existed in Winter CMS since it was created in 2014.

Copyright © 2024 Winter CMS