concrete5/core
The core of Concrete CMS (formerly concrete5), an open-source PHP content management system. Includes MVC architecture, blocks, themes, permissions, in-context editing, and extensibility for building and managing websites.
Architecture fit — unchanged: poor as a library, viable as a co-deployed application. concrete5/core is not a Laravel package in any meaningful sense. It is the extracted concrete/ directory of Concrete CMS: a full-stack PHP application framework that owns bootstrap, routing, its own DI container, its own service-provider lifecycle, Doctrine-based persistence, asset compilation, permissions, and a page/block content model. It cannot be composer required into a Laravel app and "used" the way a typical package is used. Any realistic leverage model is coexistence, not composition. 9.5.7 is a pure patch release — no architectural or API-breaking changes — so this conclusion does not move.
Recommendation up front — revised: target 9.5.7, and it is now the only version that is both installable via Composer with security auditing enabled and upgradable on MySQL 8. Do not take a direct dependency on this split repository. Consume the distribution package (concretecms/concretecms), which composes core with the required directory scaffolding, application/ config, and public/ front controller. This repo remains a read-only artifact of the release process — 20 stars, 0 dependents. Treat it as a source of truth for the concrete/ tree, not as an install target, and verify its 9.5.7 tag tracks the distribution package's tag before relying on it for anything.
Delta since the last assessment — four items that change the plan.
20260914000000 alters PageContainerInstances.containerID." This is a significant correction to the prior sequencing advice: laddering through 9.5.5 on MySQL 8 was a trap, not a safe step. Go directly to 9.5.7.enshrined/svg-sanitize moved to 1.0. The CIF path was already flagged as under active repair; it now has a security dimension. Re-test any CIF pipeline and any SVG upload/rendering flow.Revised position on headless/REST — unchanged in substance, mildly strengthened in confidence. Concrete ships a supported first-party REST API, and that remains the correct interop path. 9.5.7 contains no REST bug fixes and no transformer schema changes — the only API item is a doc-console fix preventing the browser from prompting for credentials (#13249). Against 9.5.5's "many bug fixes in the REST API" and an additive date_public transformer field, one quiet release is weak positive evidence of stabilization, not proof. Keep the contract versioned, pin the client, consume read paths before write paths, and do not interpret a single clean release as schema freeze.
Integration feasibility. Three patterns, in descending order of realism:
/cms or a content subdomain with its own front controller and its own deploy unit. No shared process, no shared container, no shared autoloader. This is the only pattern where the two frameworks' opinions do not have to be reconciled.bt* tables, per-attribute-type at* tables). Eloquent models against that schema would be a read-only, best-effort reporting layer at most. The REST API supersedes this approach entirely for interop.Technical risk — high, with the risk profile now reshaped rather than reduced.
enshrined/svg-sanitize to a new major. The 9.5.5 symfony/config / monolog-cascade conflict is still the canonical failure mode for a shared Composer root; the mitigation (separate Composer roots) stands.sql_mode (#13257), which is exactly the class of difference that produces "works in staging, fails on prod DB." Tested upstream is not the same as tested against your sql_mode; establish the target value early.Key questions to answer before any commitment:
20260914000000 and the PageContainerInstances schema before doing anything else — a failed migration may have left partial state.sql_mode, and does it match what the team can test against locally and in CI?Stack fit. Concrete is a self-contained PHP application, so "fit" reduces to infrastructure fit: same PHP runtime family, same web server, same database engine. MySQL/MariaDB is the well-trodden path, though 9.5.7's MySQL 8 migration defect is a reminder that "well-trodden" still means version-specific regressions — pin the MySQL minor and record it alongside the CMS version. PostgreSQL support exists but narrows the pool of people who have debugged it. Keep the two applications on separate virtual hosts or separate path prefixes with independent document roots. Do not attempt a single Composer root.
Migration path. Sequence it as a greenfield co-deployment rather than a merge:
0. If an upgrade was already attempted and failed, snapshot the database, check whether migration 20260914000000 is recorded as applied, and verify the PageContainerInstances schema before touching anything. 9.5.7 fixes the migration; it does not automatically repair a half-applied one.
If the direction is instead to exit Concrete, the migration is content-extraction work: walk Pages → CollectionVersions → CollectionVersionBlocks, then resolve each block type's bt* table and each attribute's at* table individually. Budget per block type, not per page. The 9.5.7 change to sanitize inline designs read from a CIF is welcome but confirms that the import/export path is still being actively repaired — validate round-trips, including sanitization behavior on styles, rather than trusting them. This remains the single most underestimated line item in any Concrete migration.
Compatibility. Pin the distribution package to an exact version (9.5.7 minimum) and mirror that pin in the split-repo reference. Track four axes now: PHP minor, Doctrine major, Symfony component majors, and MySQL minor/sql_mode. Assert in CI that composer install succeeds with security auditing enabled — that is the exact configuration 9.5.7 repairs, and the assertion that would have caught the 9.5.5/9.5.7 defect. Review the newly documented advisory-ignore entries each bump to confirm none apply to your surface. Confirm that Concrete's asset cache directory (application/files/cache) is writable and excluded from deploys, that the two apps share neither vendor/ nor composer.lock, and re-verify cache-flush behavior if Redis is in the stack (9.5.5 fixed a RedisStashDriver key-deletion bug). If you render or accept SVG anywhere, validate against svg-sanitize 1.0's changes.
Sequencing. Rehearsed upgrade to 9.5.7 first, then boundary and identity, then content via REST, and never shared-database. If a stakeholder asks for "one app," the honest answer is a rewrite decision, not an integration decision — and it should be scoped as such.
Maintenance. Two PHP applications means two dependency trees, two security-patch streams, and two upgrade rehearsals per cycle. Concrete major upgrades have historically been non-trivial for sites with custom blocks and themes; every custom block type is upgrade surface. The 9.5.7 cycle adds a hard requirement that prior cycles only implied: every bump is a migration event. Clone the database, run the upgrade, verify the migration table and affected schema, smoke test high-traffic templates, and keep a rollback path including a DB snapshot. Two consecutive point releases regressed rendering or schema — plan for that as the base rate. On the positive side, patch ergonomics keep improving modestly (9.5.5 removed the need for explicit task-controller registration; 9.5.7 sanitizes imported designs), which lowers the cost of carrying custom packages across upgrades. Record both the distribution version and the split-repo version in every incident ticket, and confirm the split repo has a 9.5.7 tag at all before depending on it.
Support. MIT license, active upstream project with responsive named maintainers and a high volume of community-contributed fixes. The split repo itself is still not a support channel — no dependents and negligible stars means issues filed there will go unanswered. Route support to the main Concrete CMS repository and community channels. Internally, expect a thin bench: Concrete skills are rarer than Laravel skills, so plan for at least two people who can debug a block type, not one. Security response should be assigned to a named owner with an explicit SLA — the new advisory-ignore and advisory-workflow tooling makes that owner's job tractable, but it does not make it self-executing.
Scaling. Concrete scales the way a PHP CMS scales: page cache, full-page caching, and a CDN in front. It is not the constraint. The constraints are (a) the reverse-proxy hop adding latency and a cache-invalidation coordination problem, and (b) editorial traffic patterns hitting uncached pages. Cache HTML at the edge with path-based rules so CMS and app caches invalidate independently. Keep the CMS database separate so its load never contends with transactional app queries. If Laravel reads content over REST, cache those responses on the Laravel side with a short TTL rather than calling through on every request — the CMS becomes a dependency of the app's p95 otherwise.
Failure modes to design against:
sql_mode / MySQL minor mismatch between environments — upstream now tests strict and empty modes; your environments may be neither.Ramp-up. For a mid-level PHP developer with Laravel experience but no Concrete exposure, expect three to six weeks to productivity on theming and page types, and materially longer for custom block types, permissions, and the attribute system. Doctrine fluency remains a real prerequisite for migrations and block work, and the 9.5.7 migration defect reinforces that whoever owns upgrades must be comfortable reading migration code. The first-party REST API still reduces how often a Laravel-side developer must touch Concrete's persistence directly — read-path interop can be owned by someone without Doctrine depth. Factor the organizational cost into the build-vs-buy comparison honestly: it is mostly people and process, and it does not show up in an estimate that only counts lines of code.
How can I help you explore Laravel packages today?