LydiaCMS gradually grew into a set of separate Composer packages. A change in the core can therefore affect several modules and applications that use it. Checking every step manually would be slow and leave too much room for error.
My goal is to automate as much repetitive work as possible—from architecture checks, testing and large-scale refactoring to package releases and deployment. Decisions and review remain manual, but execution should be as repeatable as possible.
Rules that guard security and architecture
I run PHPStan at its maximum level. General static analysis does not know the security and architectural rules of a specific application, so I also use 12 custom rules for LydiaCMS.
For example, SignalHandlerRequiresCapabilityRule checks that every administration action includes the appropriate permission check. If I forget it when adding an action, static analysis catches the mistake instead of leaving an unprotected path in the administration.
For large-scale refactoring and enforcing architectural rules, I combine AI with 14 custom Rector rules. AI helps me design and develop the change, while Rector applies the defined transformation consistently across packages. I review the resulting diff before merging it. Automation does not replace review here; it reduces mechanical work and keeps the change consistent.
Source code: lydiacms-phpstan and lydiacms-rector.
Several layers of checks instead of one number
One testing layer cannot catch every kind of error. Unit tests quickly verify isolated logic, integration tests cover cooperation with the database, and E2E scenarios exercise the actual behaviour of the public website and administration in a browser.
- The core contains more than 800 test methods for unit and integration scenarios in Nette Tester.
- The demo application is verified by more than 250 E2E tests in Playwright.
- The tests use a separate database, protection against confusing it with the development database and isolated parallel workers. Database changes made by individual tests are rolled back using transactions.
The core currently has approximately 62% code coverage. My next goal is to approach 80%. I do not treat that value as a universal quality threshold or proof that the system is error-free. It is a practical target inspired by the Pareto principle: cover most important behaviour without spending a disproportionate amount of work on the last, low-value percentage points.
The priority is not the easiest lines to test, but security rules, data operations, permissions and parts of the system where a failure would have the greatest impact. I run the complete core suite from the lydiacms-demo application in Docker and generate an HTML coverage report in GitLab CI.
CI/CD for separate packages
Most repositories inherit a shared GitLab CI configuration. A mandatory check validates the structure of composer.json and the syntax of all PHP files. The pipeline also runs a shared code-style check; specialised packages add their own static analysis and tests.
After a change reaches the main branch, the pipeline creates the next version tag and publishes the package to a private Composer registry. When building a release, the application installs the exact versions locked in composer.lock, so the production build is not assembled from the accidentally latest state of individual repositories.
An automated release with a way back
Despite static analysis and tests, something can still go wrong during a build or deployment. The release process therefore has automated checks and a mechanism for returning to the previous version. The deployment script does not modify the running application; it prepares every version in a separate directory:
- it creates a new release and installs the locked production dependencies with
composer install, - it connects the shared configuration, logs and uploaded files,
- it starts the DI container as a smoke test and stops a broken build before traffic is switched,
- it puts the website into maintenance mode while backing up the database and running migrations,
- it atomically switches the symbolic link to the new release and runs a health check against the public website and administration.
If the health check fails, the script restores the previous file release. The database can be restored from the pre-migration backup, but this is not always automatic because it could discard data written after the backup. Rollback therefore distinguishes between safely returning the code and potentially destructive database restoration.
Fewer manual steps, less room for error
The purpose of automation in LydiaCMS is not only to make work faster. Every repeated manual step is an opportunity for it to be omitted or performed differently one day.
I therefore automate permission and architecture checks, refactoring across packages, testing, versioning, release builds, database backups, migrations and the subsequent health check. What remains manual is primarily decision-making, risk assessment and review of the resulting diff.
The transferable principle is not to automate everything indiscriminately. Automation delivers the most value for repeated steps whose incorrect or inconsistent execution could affect security, stability or the system’s future development.