Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Cms Core Laravel Package

typo3/cms-core

The core package of TYPO3, a free enterprise-grade open source CMS. It provides the framework, APIs, backend and frontend rendering, extension system, and shared libraries needed to build and run TYPO3 websites and custom extensions.

View on GitHub
Deep Wiki
Context7

Getting Started

Treat typo3/cms-core as the kernel of a competing PHP framework, not as a Composer package you drop into a Laravel app. It ships its own DI container, HTTP kernel, event dispatcher, console application, and ORM layer (Doctrine DBAL) — none of which coexist cleanly with Laravel's. There are exactly two realistic ways a Laravel developer touches this package:

  1. You inherited or were assigned a TYPO3 site and need to extend it.
  2. You are building a headless setup — TYPO3 as the editorial backend, Laravel as the delivery frontend.

Step 1 — get a running instance, not the package. Never composer require typo3/cms-core. Install a distribution that pins a coherent set of typo3/cms-* packages:

composer create-project typo3/cms-base-distribution my-site
cd my-site
php vendor/bin/typo3 setup        # or: ./vendor/bin/typo3 setup --no-interaction
php vendor/bin/typo3 serve        # dev server, or point your vhost at ./public

Step 2 — read the bootstrap before anything else. Open index.php in the distribution root. It is six lines that tell you the whole architecture:

// SystemEnvironmentBuilder::run()  → constants, env, error handling
// Bootstrap::init()                → package discovery, compiled DI container, failsafe fallback
// Core\Http\Application           → PSR-15 middleware stack, routing, response

That failsafe fallback is the key detail: when the compiled container cannot be built (fresh install, broken Services.yaml), TYPO3 falls back to a hand-wired minimal container assembled from Classes/ServiceProvider.php files. Half of the "why isn't my class autowired?" mysteries end here.

Step 3 — go to typo3/sysext/core/ and the docs. Every system extension is a Composer package with an identical internal shape (Classes/, Configuration/, Resources/Private, Tests/, ext_localconf.php, ext_tables.sql). First reading, in order:

  • https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ — Extension System, Dependency Injection, Events, Middleware.
  • typo3/sysext/core/Documentation/Changelog/ — the single best source for version-specific API changes; this is TYPO3's equivalent of upgrading Laravel between majors.
  • Core\Utility\GeneralUtility, Core\DataHandling\DataHandler, Core\Database\ConnectionPool — the three classes you will actually call.

First useful task for a Laravel dev: write a console command that exports a page tree and its content elements to JSON, then consume it from a Laravel importer. It is a bounded, low-risk task that teaches you package discovery, DI, Doctrine DBAL, and the CLI in one go — and it is the concrete first step of the headless pattern below.

Implementation Patterns

Mental model: Laravel ↔ TYPO3

Laravel TYPO3 core
config/*.php $GLOBALS['TYPO3_CONF_VARS'] + config/system/additional.php + per-site YAML
Service providers Configuration/Services.yaml (autowired) or Services.php (compiler passes)
app/Http/Middleware + Kernel.php Configuration/RequestMiddlewares.php, PSR-15 stack per request type
Events/Listeners PSR-14 events, #[AsEventListener] attribute
routes/web.php Site configuration YAML (frontend); Configuration/Backend/{Routes,AjaxRoutes,Modules}.php (backend)
php artisan vendor/bin/typo3 (Configuration/Commands.php or #[AsCommand])
Blade Fluid; ViewHelpers resolve through the DI container
Eloquent / migrations Doctrine DBAL, ext_tables.sql + schema migrator + Upgrade Wizards
Queue::dispatch() Scheduler extension (typo3/cms-scheduler) hitting CLI tasks; no native queue
.env .env exists, but only a subset is consumed; real config lives in config/system/

Pattern A — headless: TYPO3 as content API, Laravel as the frontend

This is the highest-value integration for a Laravel team, and the one where typo3/cms-core is genuinely load-bearing without you writing TYPO3 extension code.

  • Editors work in the TYPO3 backend; content is exposed over HTTP via a JSON API extension (EXT:headless, EXT:t3api, or the core JSON view of Extbase controllers) rather than the built-in Fluid renderer.
  • Define the API surface in site configuration (config/sites/<identifier>/), which is where TYPO3 keeps base variants, languages, routing enhancers, and error handling. Multiple sites share one core and one database — this is the feature that justifies TYPO3 in the first place.
  • On the Laravel side, wrap each API call in a cache layer keyed on a content-version signal. TYPO3 emits PSR-14 events on publish; register a small listener (#[AsEventListener]) that POSTs a purge webhook to your Laravel app. That single listener is often the only core-touching code you write.
  • Keep the content model in TYPO3 (TCA + Configuration/TCA/Overrides/), keep the presentation model in Laravel. Do not attempt to render Fluid templates inside a Laravel request cycle.

Pattern B — custom PSR-15 middleware

Register in Configuration/RequestMiddlewares.php, keyed by frontend or backend, with before/after ordering resolved by Http\MiddlewareStackResolver:

return [
    'frontend' => [
        'vendor/request-id' => [
            'target' => \Vendor\Ext\Middleware\RequestIdMiddleware::class,
            'before' => ['typo3/cms-frontend/site'],
            'after'  => ['typo3/cms-core/normalized-params-attribute'],
        ],
    ],
];

The middleware itself is ordinary PSR-15 — no TYPO3 base class. Use this for correlation IDs, security headers, or edge-cache hints. Do not use it for authentication; the backend authentication middleware has a strict ordering contract.

Pattern C — PSR-14 events instead of hooks

$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'] hooks are legacy. New integration points are events dispatched through the core's EventDispatcher, consumed with an attribute:

#[AsEventListener]
final class PurgeSiteCacheOnPublish
{
    public function __invoke(AfterRecordPublishedEvent $event): void
    {
        // push to Laravel, invalidate CDN, etc.
    }
}

The attribute registration works because Services.yaml in your extension uses autoconfigure: true over a resource: '../Classes/*' sweep. If you hand-write service definitions, add the event.listener tag yourself.

Pattern D — CLI commands as the integration seam

For ETL work (content out of TYPO3, into Laravel; or assets in), a command under Classes/Command/ is far safer than touching DataHandler directly:

$connection = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getConnectionForTable('tt_content');
$rows = $connection->fetchAllAssociative(
    'SELECT uid, pid, CType, header, bodytext FROM tt_content WHERE deleted = 0 AND hidden = 0'
);

Use ConnectionPool, never $GLOBALS['TYPO3_DBAL_CONNECTION'] (that name appears in older docs and is not a stable contract). Doctrine's fetchAllAssociative/executeStatement map cleanly onto Laravel's query builder habits; the difference is that table names are unquoted-by-default and uid/pid/t3ver_* columns carry semantics you must respect if writing.

Pattern E — extend, never fork

  • Content model changes → Configuration/TCA/Overrides/<table>.php.
  • DI changes → Configuration/Services.yaml with tags; consume extension points via !tagged_iterator (e.g. mfa.provider, fal.file_renderer, softreference.parser).
  • Anything else → a PSR-14 listener.

The core is a read-only subtree split regenerated by the release pipeline. A local patch will be silently overwritten on the next composer update.

Testing

Use the core's own harness rather than adapting PHPUnit yourself:

./Build/Scripts/runTests.sh -s unit
./Build/Scripts/runTests.sh -s functional -d sqlite      # default; fastest feedback
./Build/Scripts/runTests.sh -s cgl

Extensions boot TYPO3 via TYPO3\CMS\Core\Tests\FunctionalTestCase plus the Fixture trait, which loads XML/CSV into a real schema. There is no equivalent of Laravel's RefreshDatabase; the trait is the mechanism. Do not invoke phpunit directly in core work — the wrapper supplies the PHP version, DB service, and bootstrap.

Gotchas and Tips

Never add typo3/cms-core to require. It is a [READ-ONLY] subtree split. Depend on typo3/cms-base-distribution (new projects) or individual typo3/cms-* packages. A direct requirement produces version skew and missing transitive sysexts; the failure surfaces as a container compile error, not a Composer error, which makes it very hard to diagnose.

GitHub issues and PRs are ignored. The repository is a synchronized mirror; development happens on Gerrit (https://review.typo3.org/q/project:Packages/TYPO3.CMS) and issues live on Forge. If you need to query the tracker programmatically, use Redmine's JSON API with curl's default user agent — browser-like agents get walled by the Anubis bot check:

curl -H "Accept: application/json" "https://forge.typo3.org/issues/12345.json?include=journals"

Class aliasing hides legacy namespaces. typo3/class-alias-loader maps t3lib_div → TYPO3\CMS\Core\Utility\GeneralUtility and dozens more. This keeps old extensions alive but means autocomplete and grep lie to you. Check Build/ClassAliasMap.php before concluding a class does not exist, and expect alias maps to shift across majors.

Two containers, and autowiring only covers one. Your Services.yaml is compiled into the main container. Code that must run in the install tool or with broken configuration runs in the failsafe container, assembled by hand from Classes/ServiceProvider.php in core, backend, install, fluid, and dashboard. Symptom: a service injects perfectly on the frontend and is null in the installer. Fix: wire it manually in the corresponding ServiceProvider.

Constructor injection only; GeneralUtility::makeInstance() is a code smell. Prefer autowired constructor arguments. Never inject request-scoped services — ContentObjectRenderer, Extbase's UriBuilder, Context — into singletons or into Fluid ViewHelpers. The Fluid compiler pass makes every ViewHelperInterface public and non-shared so the resolver can build them per call; a stateful ViewHelper breaks silently under repeated rendering.

Config lives in three places and none of them is config/. $GLOBALS['TYPO3_CONF_VARS'] (ext_localconf.php, config/system/*.php), per-site YAML in config/sites/<id>/, and .env for a small allowlisted set. After editing DI config or Services.yaml, clear the compiled container (vendor/bin/typo3 cache:flush or remove var/cache/code/container) or your changes will appear not to apply.

No migrations in the Laravel sense. Schema comes from ext_tables.sql parsed by the core schema migrator; data changes go through Upgrade Wizards (Classes/Updates/). Never write DDL against tt_content by hand — TCA, the schema migrator, and the backend form engine will disagree about the table.

Routing is asymmetric. Backend routes are PHP arrays under Configuration/Backend/; frontend routing is generated entirely from site configuration and Core\Routing. Do not look for a routes.php. A "404 that works in dev" is almost always a site-configuration issue, not a routing bug.

XLIFF is strict. Translation files are locallang*.xlf and must match the extension key. Translate through LocalizationUtility::translate() in PHP or the f:translate ViewHelper in Fluid — raw LLL: references in PHP do not resolve. Run ./Build/Scripts/runTests.sh -s checkIntegrityXliff (with -s normalizeXliff to auto-fix formatting) before committing; CI gates on it.

Resources/Public/{JavaScript,Css} is build output. Sources live in Build/Sources/TypeScript/<ext-key>/ and Build/Sources/Sass/. Editing the compiled assets is silently lost on next build. Note the naming shift: TypeScript folders use dashes (rte-ckeditor), sysext keys use underscores (rte_ckeditor).

Debugging entry points that actually work:

  • vendor/bin/typo3 with no arguments lists all commands; database:upgrade, cache:flush, extension:setup are the workhorses.
  • Install Tool (/typo3/install.php) for environment and schema diagnostics — this is where the failsafe container surfaces.
  • listExceptionCodes in the test runner reports duplicate and missing exception codes; TYPO3 relies on numeric exception codes as stable documentation anchors, so a new exception without a unique code fails CI.
  • typo3/sysext/core/Documentation/Changelog/ for deprecation notices — read it before, not after, a major upgrade.

When you touch an extension point, add a changelog entry. Replacing a legacy hook with a PSR-14 event requires both the event class and a Feature-*.rst file. ./Build/Scripts/runTests.sh -s watchRst core interactive is the live-preview and changelog wizard.

PHP version floor is strict. Recent majors demand PHP 8.1 (v13 LTS) to 8.2+ (v14/main), with strong typing enforced. Add declare(strict_types=1); to your extensions; missing union or nullable hints that PHP tolerates will fail TYPO3's static analysis baseline (-s phpstan, config in Build/phpstan/).

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin