Open-source project
symfony/contracts avatar
symfony/contracts

Symfony Contracts is generated output, and its issues live in another repository

A set of abstractions extracted out of the Symfony components

3,929 stars19 forksPHPMIT

At a glance

What is it?
Six directories, one per domain, produced by a split from the main Symfony repository, holding PHP interfaces that may only enter if an implementation already exists and may never break the components they were pulled out of.
Who is it for?
Symfony Contracts suits a library author who wants to be interchangeable with the Symfony ecosystem, or a framework author who wants a semantic name to type against without depending on a component. It does not suit anyone expecting a stable API to design against quickly, because backward compatibility with the components is a stated entry condition and the release line moves in patch steps.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • symfony/contracts3,929 ★ · +0
Star history
Repository2026-09-242026-11-13
symfony/contracts3.9k3.9k

0 Stars in the last 50 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The tree is the first design principle, one directory per domain

The stated principle is that contracts are split by domain, each into their own sub-namespaces, and the repository is that split made literal. Six directories sit at the top level: Cache, Deprecation, EventDispatcher, HttpClient, Service and Translation. Five of those names are service domains, the kind of thing an application asks for and receives. The sixth is not a service at all. Deprecation is a lifecycle concern, a contract about signalling that something is going away rather than a contract about doing something, so the split is by domain in the ordinary sense and by concern in one case. Everything else at the root belongs to the mechanics rather than to the abstractions: a LICENSE, a README, a CHANGELOG, a composer.json, a phpunit.xml.dist, a Tests directory and a splitsh.json. No contract source file appears at the top level, because every one of them sits inside one of the six domain directories.

splitsh.json and the Resources section together explain where this code comes from

Two things in this repository say that it is not the place work happens. The first is the splitsh.json file at the root, which is the configuration for splitting a monorepo into per-package repositories, and the six domain directories are exactly the kind of unit that tool publishes separately. The second is the Resources section, which points at the documentation, at the contributing guide, and then says to report issues and send pull requests in the main Symfony repository, linking symfony/symfony for both. So the file you are reading is a projection, and the upstream it is projected from is a different repository with a different name. That has a practical consequence for anybody who finds a defect here: a CHANGELOG is present in this tree, which makes it look like the place where changes are recorded, but a change recorded here originates elsewhere. It also means the six packages can be installed one at a time, which is the point of the split, since an application that needs one contract should not install six.

No contract may enter without an implementation that already exists

The third design principle is an admission rule rather than a design rule: all contracts must have a proven implementation to enter this repository. A proposal cannot be a specification written in the abstract, it has to point at code that already satisfies it, and the reference implementations are the Symfony components the abstractions were extracted out of. The file pairs that with a convention for the other direction. Packages implementing specific contracts should list them in the provide section of their composer.json, using the symfony/*-implementation convention, and the one worked example given is "provide": { "symfony/cache-implementation": "1.0" }. That example is the cache domain, one of the six. The convention generalises by name to the other five, mapping each to a virtual package such as symfony/translation-implementation, though the file only spells out the cache one. So the contract and its implementations are wired together from both sides, and the advertisement is a version the implementer declares rather than something resolved at install time.

Backward compatibility with the components sets the release cadence

The fourth principle is the one that constrains everything else: contracts must be backward compatible with existing Symfony components. That is a strange requirement to read in a package whose whole purpose is to define interfaces, because the usual expectation is that a new major version may tighten an interface. Here the constraint runs the other way, because these abstractions were taken out of components that are already in use, so a change that would break a component is not available to this package. The release record is consistent with that. v3.7.1 and v3.7.2 were both published on 27 June 2026, the first at 11:02 and the second at 21:07 the same evening, and v3.7.3 followed on 30 August. Three patch releases in one line, two of them inside a single day, is what a package moves like when it cannot take a major version to fix something. The branch itself was last pushed on 3 October 2026.

Three ways to consume a contract, and the third needs no framework

The FAQ is the most useful text in the file because it names three distinct mechanisms rather than one. The first is type hints: by using the provided interfaces as type hints you can reuse any implementation that matches its contract, whether that is a Symfony component or something else from the wider PHP community. The second is autowiring, which the file says applies depending on their semantics, so that some interfaces can be used to have a service injected into your classes. The third is the one that makes the package framework independent. Others might be useful as labeling interfaces, to hint about a specific behaviour that could be enabled through autoconfiguration or manual service tagging, and the sentence closes with a parenthetical covering any other means your framework provides. So there is a category of contract here whose whole job is to be a marker that a discovery mechanism can look for, and that category is exactly the one that does not require a container at all.

A contract can be an interface, a normative docblock, or a test suite

The second design principle describes contracts as small and consistent sets of PHP interfaces, traits, normative docblocks and reference test suites when applicable. Four artefacts in one sentence, and the hedge at the end is doing real work. The hedge matters because the three named directories-level test infrastructure in the root, a Tests directory and a phpunit.xml.dist, implies that some contracts arrive with a suite that a third party implementation can run against. That is a much stronger claim than shipping an interface, because an interface constrains signatures while a test suite constrains behaviour, and a suite that passes is evidence the implementation matches the semantics rather than just the shape. A normative docblock is the third option, rules written into the documentation and enforced by convention rather than by the compiler. The file does not say which contracts have which of the three, and it does not say how a third party is meant to discover the difference, so the honest reading is that these are the three things a contract here can be and any given one may be only the first.

Built on the PSRs where applicable, with different processes that are never named

There is a section asking how this differs from the PHP-FIG PSRs, and the answer is careful. When applicable, the contracts here are built on top of the PSRs. But the group has different goals and different processes, and the difference in processes is asserted and then left unexpanded, so the file never says what the process is that differs or who runs it. The stated goal is providing abstractions that are useful on their own while still remaining compatible with the implementations Symfony provides, which is a different objective from a PSR, since a PSR is useful chiefly as a shared target. The last sentence is the most revealing. Although not the main target, the file says, the authors hope the declared contracts will directly or indirectly contribute to the PHP-FIG. Contribution upstream is described as a hope rather than a plan, and the field ends there with no issue number and no process to track it against.

There is no install command and no version requirement in 308 words

For a package that sits under a large framework's dependency graph, the documentation is thin in a specific way. There is no composer require line, no PHP version constraint, no list of what is inside any of the six domains, and no example of implementing a contract. What there is, in 308 words, is the design principles, the provide convention, the FAQ and four resource links. The route to installing is therefore external by design: the homepage field points at symfony.com/contracts rather than at a repository page, and the documentation link goes to a page under symfony.com/doc/current/components/contracts.html. One last observation about scope. The five service domains and the Deprecation directory are named but never described, so a reader looking for whether HttpClient contracts cover a particular need has to leave this file to find out, and the repository itself will not answer it. That is consistent with a repository whose job is to be split and mirrored rather than to be read.

Editorial conclusion

Symfony Contracts suits a library author who wants to be interchangeable with the Symfony ecosystem, or a framework author who wants a semantic name to type against without depending on a component. It does not suit anyone expecting a stable API to design against quickly, because backward compatibility with the components is a stated entry condition and the release line moves in patch steps. Check four things first. Where to file an issue, because this repository is not where they go. Which of the six domain packages you actually need, since each is separate. Whether a contract you depend on carries a reference test suite, since the file says those exist only when applicable. And what your version constraint should be, because the README states no PHP version and no install command at all.

Frequently asked questions

What is Symfony Contracts?

A set of PHP interfaces, traits, normative docblocks and reference test suites extracted out of the Symfony components, released under the MIT license. Its stated purpose is to let you build on semantics the components proved useful, with implementations that already exist behind them.

How do you use Symfony Contracts?

Three ways, by its own account. Use the interfaces as type hints so any matching implementation can be substituted, combine some of them with autowiring to have a service injected, or use others as labeling interfaces to mark a specific behaviour under autoconfiguration or manual service tagging.

How is Symfony Contracts different from the PHP-FIG PSRs?

The contracts are built on top of the PSRs when applicable, but the file says the group has different goals and different processes, without saying what those processes are. Contributing to the PHP-FIG is described as something the authors hope for rather than a main target.

Where do I report a problem with a Symfony Contract?

In the main Symfony repository, not in this one. The Resources section points both issue reports and pull requests at symfony/symfony, and splitsh.json at the root of this tree records the split that produces these six packages.

What are the requirements for adding a contract to Symfony Contracts?

All contracts must have a proven implementation to enter the repository, and they must be backward compatible with the existing Symfony components. They are also split by domain into their own sub-namespaces and kept as small consistent sets of interfaces, traits, docblocks and test suites.

Which contract domains does the Symfony Contracts repository contain?

Six directories at the top level: Cache, Deprecation, EventDispatcher, HttpClient, Service and Translation. Five are service domains, and Deprecation is the one that concerns signalling that something is going away rather than providing a service.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. symfony/contracts on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/symfony-contracts.svg)](https://hysenlabs.com/projects/symfony-contracts)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/symfony-contracts.svg)](https://hysenlabs.com/projects/symfony-contracts)

Was this useful?