I won't go outside this week-end and that's fine.
I'm lucky. I share my life with my wife and my two sons in my apartment in Lille, France. Hopefully, we're not infected. Not yet. We're now following the rules very strictly. To protect us, but more importantly, to protect everyone else. Let's not be selfish.
In January and February, the weather was bad. It rained every single day. Or it seemed so. Since the beginning of the week, sun made a big come back. The sun taunts me. If you know me a little, you know that I would really like to go outside. Buy a book in my local bookstore, buy some groceries, find some new cheese to taste, bike to the butcher, jog in the biggest public park of the city. I would love to do all of these activities today. Like every "regular" Saturday. But for the next few weeks, my world will look different. I need to adapt. And that's fine. I can certainly wait.
I'm lucky. I can work from home. My sons are 12 and 15, so they can do their homework on their own. Wonderful kids. They know how to spend their time. They read books, they watch some episodes of their favorite series on TV. They draw and they are talented. Don't ask me to draw anything.
I'm lucky. I'm the CEO of two great companies. Symfony and Blackfire. Remote first companies. All employees are working remotely, from home. They live everywhere in the world. I'm proud of them. They are confined like everyone else. Some have kids at home. Some are anxious. We are all reading the news, trying to understand what's going on; trying to imagine the impact of this "tornado". We are also trying to keep working. As "usual". Let's be honest, maybe less than usual. That's fine. The business is impacted. Some customers have already canceled their subscription. Very little new customers this week. That makes sense. At Symfony, we're canceling or postponing all our worldwide conferences. At Blackfire, our marketing is mostly about sponsoring conferences. All our eggs in the same basket. How ironic. Everyone is on board to imagine our future. We have many ideas to reinvent ourselves. I'm blessed.
I'm lucky. I'm young-ish and I'm in good health. I want to help but I'm not a doctor. I'm trying to support my family and my employees the best I can. There is blood shortage in France, and in some other countries. I will donate my blood next week.
This is the week-end. I won't go outside. I will probably read fiction books and code. I will cook for sure. As usual. But I will also take some time to dream. For a change. Or perhaps thinking about what's next.
Be strong, stay safe.
]]>But one issue I hear sometimes is that it is more complex to start a new project. Most projects need many Symfony features. But new projects now have bare minimum dependencies when created via composer create-project symfony/skeleton
. You need to explicitly add all the dependencies you want to depend on. Being explicit is great, but UX suffers. Ever tried to add profiler
and didn't get timing info? Yup, symfony/stopwatch
is optional, so you need to add it explicitly.
You loved the simplicity of starting a project with the Symfony Standard Edition. Is it nostalgia? Perhaps. Would it be possible to get the best of both world? Certainly! Read on.
First, let me recap some current available features:
Any Composer package can have a related recipe that helps with
auto-configuration (recipes are stored at symfony/recipes
and
symfony/recipes-contrib
);
symfony/debug-pack
and get
everything you need to debug a Symfony application).Symfony Packs are time savers, but they have one limitation: they mask the real dependencies. Let me explain with an example. Using symfony/orm-pack
is a great way to get the most commonly needed Doctrine related packages and bundles. Run composer req symfony/orm-pack
(or simply composer req orm
) to get Doctrine core, the Doctrine bundle, and the Doctrine migrations bundle (everything configured thanks to some nice recipes).
But what if you want to remove one dependency that is included in the pack? Like the migrations bundle. You cannot remove it as the project's composer.json
file requires symfony/orm-pack
, not the individual dependencies:
diff --git a/composer.json b/composer.json
index e24f26b..5238f98 100644
--- a/composer.json
+++ b/composer.json
@@ -7,6 +7,7 @@
"symfony/flex": "^1.0",
"symfony/framework-bundle": "^4.0",
"symfony/lts": "^4@dev",
+ "symfony/orm-pack": "^1.0",
"symfony/yaml": "^4.0"
},
"require-dev": {
Another example would be when you want to change a dependency constraint for a package coming from a pack.
You can of course require the individual dependencies, but for packs like debug
or api
, you would have to have a look at their composer.json
on Github and do some copy/paste. Not ideal.
There is another way. Unpacking the pack. You can now unpack
an already installed pack via the unpack
command:
composer unpack orm
The command updates composer.json
to remove the pack and replace it with the individual dependencies defined in the pack:
diff --git a/composer.json b/composer.json
index 5238f98..b8c9794 100644
--- a/composer.json
+++ b/composer.json
@@ -3,11 +3,13 @@
"license": "proprietary",
"require": {
"php": "^7.1.3",
+ "doctrine/doctrine-bundle": "^1.6.10",
+ "doctrine/doctrine-migrations-bundle": "^1.3",
+ "doctrine/orm": "^2.5.11",
"symfony/console": "^4.0",
"symfony/flex": "^1.0",
"symfony/framework-bundle": "^4.0",
"symfony/lts": "^4@dev",
- "symfony/orm-pack": "^1.0",
"symfony/yaml": "^4.0"
},
"require-dev": {
Tweaking the pack dependencies is now possible. Don't want the migration bundles? Simple enough:
composer rem migrations
You can also unpack a pack at installation time via the --unpack
flag. That flag tells Composer to add the dependencies of the pack in your composer.json
instead of adding the pack package itself:
composer req orm-pack --unpack
Note that the unpack
command and the --unpack
flag only work for Symfony packs (the Composer package type must be symfony-pack
). Any other dependencies are simply ignored and follow the standard Composer installation process.
That's a great feature by itself and give you even more power when it comes to dependency management.
Now, you could create a "Symfony Standard Edition" pack and benefit from the new unpacking feature.
That would almost work. Except that the Symfony Standard Edition has dev dependencies that would not be installed by Composer. As you know, when adding a dependency, Composer will install any transitive dependencies from the require
section, but it does not care about the ones defined under the require-dev
section.
To get to the next level, and because you can now unpack a pack very easily, a Standard Edition can be re-created simply by having a new skeleton.
To demonstrate how that works, let's take the brand new "Symfony Website Skeleton" as an example.
This new skeleton almost replicates Symfony Standard Edition's dependencies:
{
"name": "symfony/website-skeleton",
"type": "project",
"license": "MIT",
"description": "A skeleton to start a new Symfony website",
"require": {
"php": "^7.1.3",
"ext-iconv": "*",
"sensio/framework-extra-bundle": "^5.1",
"symfony/asset": "^4.0",
"symfony/browser-kit": "^4.0",
"symfony/console": "^4.0",
"symfony/css-selector": "^4.0",
"symfony/debug-pack": "*",
"symfony/expression-language": "^4.0",
"symfony/flex": "^1.0",
"symfony/framework-bundle": "^4.0",
"symfony/form": "^4.0",
"symfony/lts": "^4@dev",
"symfony/orm-pack": "*",
"symfony/monolog-bundle": "^3.1",
"symfony/process": "^4.0",
"symfony/security-bundle": "^4.0",
"symfony/serializer-pack": "*",
"symfony/validator": "^4.0",
"symfony/swiftmailer-bundle": "^3.1",
"symfony/web-link": "^4.0",
"symfony/webpack-encore-pack": "*",
"symfony/yaml": "^4.0"
},
"require-dev": {
"symfony/dotenv": "^4.0",
"symfony/maker-bundle": "^1.0",
"symfony/phpunit-bridge": "^4.0",
"symfony/profiler-pack": "*"
},
...
Run composer create-project symfony/website-skeleton
to start a new project. You get everything you need to get started fast. Use the unpack
command if you need greater control over the added dependencies; unpack, remove, add as you see fit. You have full control. Note that if you unpack a dependency defined under require-dev
, all its dependencies are added to the require-dev
section as expected.
Like Silex ? Start with symfony/skeleton
. Like the full-stack Symfony framework better? Start with symfony/website-skeleton
. Then, be free to add or remove dependencies. Scale the way you want your project.
Packs makes it easy to create great skeletons. And unpack allows to keep the flexibility of being explicit and precise about the dependencies.
The new unpack feature opens a lot of possibilities and gives you even more flexibility and control on your project's dependency management. Mind blowing if you ask me.
Happy Symfony!
]]>What about Symfony 4? During its development cycle, we did some nice performance optimizations: mainly to improve the router and container speed. But several non-related changes in Symfony 4 also help with performance... Your mileage may vary depending on your specific project of course.
Synthetic benchmarks that were made to back pull requests that claimed to improve performance do not always convert to gain on real-world projects. Now that Symfony 4 is out, it's time to test a full project. An "Hello World!" application benchmark is a good start as it helps understand the base performance of a framework. It helps understand the overhead added on top of PHP by your framework of choice. We spent so much time doing optimizations that I wanted to compare the out-of-the-box performance of a default Symfony Standard Edition project (based on Symfony 3) with the performance of a Symfony 4 project.
Benchmarking is a science and takes a lot of time. Luckily enough, and while I was procrastinating, someone did the work for me. The http://www.phpbenchmarks.com/en
website has a great benchmark protocol and a great UI that let you browser and compare results easily.
According to those benchmarks, an "hello world" page on Symfony 4.0 is almost twice as fast as the same code using Symfony 3.4. You read that right. On PHP 7.2, Symfony 4.0 is twice as fast compared to Symfony 3.4.
There is also a benchmark for a more complex REST API application. Symfony 4 is again still much faster than 3.4.
Visit http://www.phpbenchmarks.com/en to get more numbers. Among other things, you will learn the following:
Symfony on PHP 7.2 makes your code quite a bit faster than 7.1;
Symfony 2.3 is the second fastest Symfony release since 2.0
Symfony 3.4 is the slowest release since 2.0 (deprecated features are probably one of the reasons);
I would love to get some numbers for your application. Share them if you upgraded recently.
Enjoy using Symfony 4.0, the fastest Symfony ever released!
]]>Many Flex early adopters asked for it. The Symfony Flex server now supports private recipes repositories as announced during my keynote at SymfonyCon Cluj.
Creating a repository for your private recipes is easy. Create a regular Github repository (probably a private one) to store the recipes. The directory structure is the same as for the official Flex recipes repositories. Then, register the repository as a recipes repository (not available anymore). Done.
After registration, the repository will behave in the exact same way as the official ones, with the same features like automatic validation of pull requests by the Flex bot, and a dedicated endpoint to test a pull request on a project.
As for the main recipes repository, but unlike the contrib one, private repositories can define aliases and override existing ones. Your aliases take precedence over the official ones. If you don't like our default choice for the admin
or orm
aliases, override them. You can also override a recipe to customize the package's default configuration.
There are some differences with the public repositories though: of course, you can define recipes for your private packages. Auto-merge of pull requests is not supported yet. And for Makefile lovers, I have some good news: private repositories support Makefile (as you have full control of your stack).
How do you configure the projects that can have access to your private repository? In a config.json
file located at the root directory of the repository. Under the projects
key, list the project's ULID (as found in composer.json
or via composer config extra.symfony.id
):
{
"projects": {
"01BWTQ6H4KJFHRZ240CXBN6S6A": "my super secret project",
"01BYV69F2S7ECNN3N76K82BV4W": "demo"
}
}
While in beta, private repositories are free.
Happy private Flex!
]]>Using recipes from the contrib repository has became easier. The first time you require a package for which a contrib recipe exists, Flex now asks you the permission to execute the recipe. It also lets you switch the "accept contrib recipes" flag for the project (so it does not ask the question again). Much better experience, and better discoverability.
One issue people have had a lot is recipes installed multiple-times. I thought Flex got you covered except under some rare circumstances, but apparently those rare conditions are a bit more frequent than I anticipated. So, instead of trying to make clever hooks into the Composer life-cycle to determine if a recipe should be applied or not, each project now has a symfony.lock
file where Flex's state is stored (this file must be committed in the project's code repository). Problem solved. And that lock file opens up some nice opportunities... but that's for another iteration.
Makefile support proves to be a nightmare. Despite several patches to the recipes having some Makefile support (support for Windows and whatnot), it was a never ending battle. At the end of the day, we realize that people had wrong expectations about the Makefile, and that make
is not so standard. Instead of fighting the tool, we decided to add symfony/console
as a requirement. Done. Simple. Efficient. And yes, Makefile support was introduced just as a facade to the optional Symfony Console component. You can still use a Makefile for your project of course. And I still think that this is a good idea.
The minimum version supported by Flex is now PHP 7.0 instead of PHP 7.1 previously to let more people use Flex. It's especially important for projects using Symfony 3.4, which is a long term support release. That will make the upgrade path to Symfony 4 smoother as well.
Having beta testers helped find bugs you would never have expected. When upgrading Flex, people had weird issues. That was because of how Composer plugins work. Some Flex classes are loaded early on by Composer. And if down the road, Flex is updated, you can have a situtation where a class from the newest version is mixed with classes from the old version. Not a good situation to be in. Now, Flex loads all its classes preemptively to be sure it is always in a consistent state.
In terms of features, Flex is now able to auto-register almost any bundle for which there are no recipes. And we added support for environment variables in PHPUnit configuration. Using Flex is also much faster as there are less roundtrips with the server (only one request per Composer session most of the time).
Regarding documentation, we have also started to document the new workflow with updates to the upgrade tutorial and the best practices document. They now take into account changes introduced by Flex. However, keep in mind that documentation updates are still on-going.
Last, but not least, the Symfony Flex Server has a new UI, which allows you to better discover available recipes, aliases, and packs.
Happy Flex!
]]>etc/
was renamed to config/
, and web/
to public/
. My blog posts about
Symfony 4 have just been updated to reflect these changes.
If you already have a project using Symfony Flex, upgrading is as simple as:
etc/
to config/
;web/
to public/
;public/
directory;symfony/flex
to the latest version;The first step is to create a project. Currently, this needs to be done via
composer create-project
. We might release a tool to bootstrap a project
faster.
Let's go:
composer create-project "symfony/skeleton:^3.3" demo
cd demo
3.3
is the only currently available version and uses the yet-to-be-released
Symfony 3.3. Versions like , 4.0
, lts
, or latest
will be available
later on (but not before the release of Symfony 4.0
of course).
The command downloads the Symfony
skeleton which consists of just one
composer.json
file.
Then, it extracts the file into the demo
directory and automatically runs
composer install
. symfony/flex
is the first package to be installed so that
it can hook into the Composer process. When Composer installs (or updates)
other dependencies, Symfony Flex looks for an associated recipe on the
Symfony Flex server, and executes it. You can see Symfony Flex in action via
the logs it adds to the Composer output.
When finished, you should see a "What's next?" section that explains the
possible next steps, like running make serve
to start the PHP built-in web
server. Before going further, go to the project's directory: cd demo
.
serve
is one of the tasks added to the local Makefile
, as described in the
symfony/framework-bundle
recipe.
Note that some commands were automatically installed and run at the end of the process:
Again, those scripts were added to the project's composer.json
file as
defined by the symfony/framework-bundle
recipe. The second script did not run
because the console tool (available via symfony/console
) is not installed by
default (we will see how to "fix" this issue later on).
Now is a good time to initialize Git:
git init
git add .
git commit -m "initial set of files"
Using git add .
works well as Symfony took care of creating a "good"
.gitignore
file.
Remember that the skeleton only has one file, composer.json
. Check the
demo/
directory now; some more files have been created. Those files were
added by Symfony Flex based on the installed packages, as described in recipes.
Let's examine the directory structure now. Most files has been added because of
the symfony/framework-bundle
dependency.
The .env
file defines the APP_ENV
and APP_DEBUG
environment variables:
###> symfony/framework-bundle ###
APP_ENV=dev
APP_DEBUG=1
APP_SECRET=ODtvoHW6pn3bE2rHgTa2MjkjwefzlsJH
###< symfony/framework-bundle ###
The comments allows Symfony Flex to "manage" this section. This is useful when
those variables needs to be removed when the package is removed. If you remove
the comments, Symfony Flex will not be able to automatically manage these
variables anymore. Have a look at .gitignore
for a similar example.
If you're curious, check public/index.php
, the new web front controller.
The most interesting files are under config/
. The main entry points are the
empty container.yaml
and routing.yaml
files; this is where you can add
services, parameters, and routes for your project. A default configuration has
been installed as well for symfony/framework-bundle
under config/packages/
.
Feel free to tweak installed configuration files or add more for your own needs.
Last, but not least, FrameworkBundle
is now registered in bundles.php
:
return [
'Symfony\Bundle\FrameworkBundle\FrameworkBundle' => ['all' => true],
];
Even if a bundle does not have a recipe, Symfony detects Composer packages with
the symfony-bundle
type and automatically enable them for all environments.
This avoids the creation of recipes when registration is just a matter of
enabling the bundle.
The src/
directory is where you can store your PHP classes. Under the App\
namespace as registered in composer.json
. Note that this is where
Kernel.php
was also installed as App\Kernel
.
Now, it is time to install some new dependencies via Composer. Let's start by adding a more powerful web server for your project:
composer req webserver
And install Symfony console support via:
composer req cli
Note: req
is a shortcut for require
(the Composer CLI supports any
shortcut that is not ambiguous; use rem
to remove a Package).
webserver
is an alias for symfony/web-server-bundle
, and cli
is an
alias for symfony/console
. That works because Symfony Flex knows how to
convert those aliases into full package names: cli
is equivalent to
console
, which is equivalent to symfony/console
. The symfony/
prefix is
always optional. Try composer req workflow
or composer req ldap
.
web-server-bundle
is too cumbersome, so use webserver
or just server
.
For Symfony dependencies, Symfony Flex also recognizes a few more versions than
the usual Composer ones, mainly next
, previous
, lts
, and stable
(they
don't all work yet though).
composer req cli:next
After executing composer req cli
, notice how the assets:install
command
automatically ran. The bin/console
file has also been added to your project.
Aliases work when removing dependency as well:
composer rem cli
... which also removes the bin/console
binary (for fun, try this: composer rem framework-bundle
).
Remember I wrote about the developer experience when installing a Symfony bundle? Let's go wild and install something really "complex" like an admin generator based on Doctrine. How many steps to make it work? It might be fewer than you expect, and definitely more fun.
First, let's install EasyAdminBundle:
composer req admin
Besides installing the admin generator bundle, it also installs all its
transitive dependencies and auto-configures them all: TwigBundle
,
SecurityBundle
, FrameworkExtraBundle
, and DoctrineBundle
.
admin
is a "generic" word. This is why I wrote about Symfony Flex recipes
being opinionated. There can only be one package aliased to admin
. orm
is
another generic word that is currently aliased to the Doctrine ORM.
Run the PHP built-in web-server via make serve
or bin/console server:start
and go to http://localhost:8000/admin/
. You should get an error as no
Doctrine entities exist yet. Let's create one in src/Entity/Product.php
:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="product")
*/
class Product
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* @ORM\Column(type="string", length=100)
*/
public $name;
/**
* @ORM\Column(type="decimal", scale=2)
*/
public $price;
/**
* @ORM\Column(type="text")
*/
public $description;
}
Tweak the database configuration in .env
:
###> doctrine/doctrine-bundle ###
DB_URL=mysql://root@127.0.0.1:3306/symfony?charset=utf8mb4
###< doctrine/doctrine-bundle ###
Run the following commands to initialize the database:
./bin/console doctrine:database:create
./bin/console doctrine:schema:update --force
Finally, add the Product
entity to the list of entities managed by the admin
generator (config/packages/easy_admin.yaml
):
easy_admin:
entities:
- App\Entity\Product
Try http://localhost:8000/admin/
again. If everything worked well, you should
be able to manage products.
Instead of installing an admin generator, have a look at this small screencast
where I'm using the api
alias to bootstrap an API project quickly and easily:
Here are some nice aliases you can try:
sec-checker
to install the SensioLabs Security Checker;req-checker
to install the Symfony requirements checks;log
to install MonologBundle and all its dependencies;template
for Twig;mailer
for Swiftmailer;profiler
for the profiler;As symfony/symfony
is not required anymore, you get more fine-grained
dependencies, but it might be cumbersome to install each Symfony component one
by one. To ease the pain, I'm experimenting with a new concept: "packs". A pack
is nothing more than a regular Git repository registered on Composer that
contains a composer.json
that references a set of related packages. As an
example, I have created a debug-pack
pack that can be installed via
composer req debug-pack
. Have a look at the ORM
pack or the API
pack. Imagine a web-pack
that
references nothing in composer.json
but is associated with a recipe that
installs a set of default files under public/
like favicons, a robots.txt
,
and so on. Nice? Imagine the same for Travis, Blackfire, or Docker. Or a pack
that installs the exact same dependencies as the current Symfony Standard
Edition? Your imagination is the limit. Compose your applications with
off-the-shelf packages, packs, and their associated recipes.
Symfony Flex enables distribution composition instead of inheritance. The new way is easier, more flexible, and more powerful at the same time.
The first version of the Symfony Flex server is quite simple, but over time, more features will be implemented.
Now, it is probably time for you to write some code. What about some controllers and templates? Hold on. Even if you can do what you are used to, Symfony 3.3 and Symfony 4.0 proposes a much smoother experience that you might like better. This is a great topic for my next post about Symfony 4.
Remember that the recipes repositories at https://github.com/symfony/recipes and https://github.com/symfony/recipes-contrib are also public now. Feel free to look around. And keep in mind that what you see is experimental. Current choices might change.
]]>Everybody knows that I like to tease. Trying to build the momentum on something you have been working on for months feels great. But the reality is a bit more complex: I am still working on the first MVP of the infrastructure needed to support Symfony Flex. To be honest, I'm probably working on something that is a bit more than just an MVP.
The good news is that I'm almost there. The bad news is that some feedback made me realize that some features I had on my todo list for later need to be part of the first version. You know, that "one more feature" that will make Symfony Flex the next killer application for the Symfony community.
People are worried about the opinionated recipe repository. Keep in mind that one of the main goals of Symfony Flex is to automate your day-to-day workflow for the happy path.
First, I want to reiterate that a package does not need to have a recipe to be installed. The good old way of registering bundles manually and copy/pasting some default configuration will still work fine... which is what everybody has been doing for years. In other words, everything will be at least as easy to install as it is now.
Then, for several reasons I explained in my previous post, I won't change my mind about the official repository being opinionated. I talk to a lot of developers and companies using Symfony, and many want "us" to make choices for them. They don't want to test dozens of bundles to make an informed choice.
Finally, I need to reveal one more recipe configuration option that I did not
mention yet: aliases. Instead of using the regular Composer package name, a
recipe can list alternative shorter names. For instance, use composer req cli
instead of composer req symfony/console
. You can imagine packages willing to
reserve the admin
, api
, or orm
aliases. And of course, each alias can
only be linked to one Composer package.
Choices are good.
But what about having another recipe repository along side the main one? Well, that feature was on my todo list for a future version. But I have decided to include it now. For the first version. And I like it a lot.
Let's dive into the Symfony Flex recipe repositories. Instead of having just one repository, Symfony Flex supports two.
Here is how it works:
The "main" official repository, hosted at
https://github.com/symfony/recipes
will be opinionated. Submissions will be reviewed carefully. They will need
approval from the Symfony core team (like pull requests on the main
symfony/symfony
repository). We want the utmost quality and the best
developer experience.
Other than the rules to accept submissions, the "main" and the "contrib" repositories work in the exact same way, except for the following differences:
Only the "main" repository can define aliases (that makes sense I suppose);
composer config extra.symfony.allow-contrib true
.And packages can be promoted from the "contrib" repository to the "main" one as well.
To maintain a high level of quality, I have also developed a set of validation rules for recipes. When you submit a pull request on both repositories, the Symfony Flex server runs a series of checks to ensure that your changes are valid. Basic checks like validating the existence of the package on Packagist. And more interesting ones like checking that two packages do not define the same alias.
But more interesting, a dedicated staging Flex Server is built for every pull request. That allows anyone to test the changes before they are merged into the master/production branch of a repository.
For instance, if you submit a pull request (number 42) on the "contrib"
repository that adds a recipe for package "foo/bar", install your new package
by defining the SYMFONY_ENDPOINT
environment variable:
SYMFONY_ENDPOINT=https://symfony.sh/r/contrib/42 composer req foo/bar
If changes are submitted for "core" packages (like in
symfony/framework-bundle
), using the environment variable even works with
composer create-project
:
SYMFONY_ENDPOINT=https://symfony.sh/r/main/42 composer create-project symfony/skeleton demo
That's all for now. I hope that these last minute changes make you happy.
]]>Symfony 4 is powered by Symfony Flex, a deceptively simple but powerful
Composer plugin. The default Symfony
skeleton only lists two main
dependencies: symfony/flex
and symfony/framework-bundle
. Everything else is
optional.
As you can imagine, most projects need more than FrameworkBundle
. And
installing and configuring all packages and bundles by hand is error-prone,
tedious, and just plain boring. Even for the few dependencies that Symfony
Standard Edition depends on.
This is where Symfony Flex shines.
When you install a Composer package, Flex hooks into the installation process to automate its integration into your project via pre-defined and community-driven recipes. It works for any Composer packages, not just Symfony bundles. If a recipe does not exist, you can still configure it the old way, like you currently do.
Take sensiolabs/security-checker
as an example. It works for any PHP
projects. But when installed on a project using Symfony Flex, it knows how to
run the checker whenever you run composer install
.
The automation is implemented in a recipe that has a JSON manifest that
describes how to integrate the package into a Symfony application. Here is the
one for sensiolabs/security-checker
:
{
"copy-from-recipe": {
"config/": "%CONFIG_DIR%/"
},
"composer-scripts": {
"vendor/bin/security-checker security:check": "php-script"
}
}
Flex currently implements 9 "configurators" that helps automate the integration
of any PHP package into a Symfony application: copy-from-recipe
,
copy-from-package
, bundles
, env
, container
, makefile
,
composer-scripts
, gitignore
, and post-install-output
.
The bundles
configurator enables bundles in your project by adding them
to the bundles.php
file (and it removes them when you uninstall the dependency):
{
"bundles": {
"Symfony\\Bundle\\DebugBundle\\DebugBundle": ["dev", "test"],
"Symfony\\Bundle\\MonologBundle\\MonologBundle": ["all"]
}
}
Configuration can be added via the copy-from-recipe
and copy-from-package
configurators: the former copies files from the recipe while the later copies
files from the Composer package itself.
{
"copy-from-package": {
"bin/check.php": "%BIN_DIR%/check.php"
},
"copy-from-recipe": {
"config/": "%CONFIG_DIR%/",
"src/": "%SRC_DIR%/"
}
}
The env
configurator knows how to add environment variables to the project's
.env
and .env.dist
files (and removes them when uninstalling the dependency):
{
"env": {
"APP_ENV": "dev",
"APP_DEBUG": "1"
}
}
The makefile
configurator adds new tasks to the project's Makefile
(and
removes them when uninstalling the dependency). Unlike other configurators,
there is no entry in the manifest.json
. Just define tasks in a Makefile
file:
cache-clear:
@test -f bin/console && bin/console cache:clear --no-warmup || rm -rf var/cache/*
.PHONY: cache-clear
cache-warmup: cache-clear
@test -f bin/console && bin/console cache:warmup || echo "cannot warmup the cache (needs symfony/console)"
.PHONY: cache-warmup
Note: A previous version of this blog post described a previous version where
the post install output was defined in the manifest.json
file.
The composer.json
file defined in symfony/skeleton
contains the following snippet:
{
"scripts": {
"auto-scripts": [
],
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
]
}
}
The composer-scripts
configurator manages the special @auto-scripts
section
by adding commands and scripts defined in recipes. Those are automatically run
on composer install
and composer update
:
{
"composer-scripts": {
"assets:install --symlink --relative %PUBLIC_DIR%": "symfony-cmd"
}
}
.gitignore
#The .gitignore
configurator adds patterns to the .gitignore
file. And like
for the other configurators, it knows how to clean up when you remove a package:
{
"gitignore": [
"/phpunit.xml"
]
}
post-install-output
#If you want to display some next step actions when a package is installed, list
them via the post-install-output
configurator. Like for the Makefile, the output
must be defined in a post-install.txt
:
Execute make serve> to run your application.
Note: A previous version of this blog post described a previous version where
the post install output was defined in the manifest.json
file.
The most "complex" manifest is currently the one for symfony/framework-bundle
and reads as follows:
{
"bundles": {
"Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle": ["all"]
},
"copy-from-recipe": {
"config/": "%CONFIG_DIR%/",
"public/": "%PUBLIC_DIR%/",
"src/": "%SRC_DIR%/"
},
"composer-scripts": {
"make cache-warmup": "script",
"assets:install --symlink --relative %PUBLIC_DIR%": "symfony-cmd"
},
"env": {
"APP_ENV": "dev",
"APP_DEBUG": "1",
"APP_SECRET": "%generate(secret)%"
},
"gitignore": [
".env",
"/public/bundles/",
"/var/",
"/vendor/"
]
}
That's all. I have yet to find a case not covered by this small set of
configurators. Note that I might remove the container
one as it is not used
anymore in the current set of recipes I have written so far.
The Symfony Flex recipe repository will be opinionated. The developer is free to use any combinations of packages, but to be able to provide a good sensible configuration, we need to make choices. We won't be able to support all bundles, only a curated set of them. Which is probably a good thing as well.
Symfony doesn't make choices for you. And that makes sense. You know what you need for your project. But with more than 3000 bundles to choose from, selecting the right bundle from the ecosystem is far from being easy for beginners. Want an admin generator? Currently, you need to choose between 26 different ones. And several are quite popular. Which one should I use?
Compare that to symfony1, where the admin generator was built-in. This is just one example. Have a look at the number of Blog bundles, or API ones. The great news is that the Symfony community is hard at work.
Along the years, Symfony became a very low-level framework, without batteries included. Now that Symfony gives us a very powerful platform to work with, I think we can work on being more opinionated again.
We should be giving more exposure to good bundles. In order to do that, we would first need to define what "good" means. I can imagine some simple rules like the license, or the number of opened bugs vs activity on the Git repository, the amount of documentation, the number of contributors, whether it is compatible with a large set of Symfony versions, and so on.
And of course, any bundle can still be installed the old way, without auto-configuration.
Besides being a Composer plugin, Symfony Flex also communicates with an HTTP server to get up-to-date information about package recipes, aliases, and more.
Try curl https://flex.symfony.com/recipes/sensiolabs/security-checker/4.0 | json_pp
.
Eager to try out Symfony Flex? Well, I will push everything to Github just in time for the next blog post.
]]>The Symfony 3 directory structure introduced a more standard Unix-like directory structure, with less sub-directories. Symfony 4 keeps going in that direction.
Using well-known directory names helps. Using bin/
, src/
, or var/
was a
great step forward. Symfony 4 adds etc/
in place of app/
. It was proposed
for Symfony 3 but rejected for reasons that don't hold anymore.
Update: The etc/
has been changed to config/
after a long discussion
with the community. The web/
directory has also been changed to public/
.
The blog posts about Symfony 4 have been updated to reflect these changes.
tests/
#Tests move to a top-level tests/
directory. It was proposed before, it just
makes sense now that we have bundle-less applications. It also allows to
declare a specific test namespace for autoloading:
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
}
}
templates/
#Templates become first-class citizens via a new top-level templates/
directory. Would tpl
make more sense compared to the other short 3-letter
directory names?
Why not views
? views
was the original name I chose very early on because it
was shorter than templates
. It was a mistake as View
is a concept by itself.
Fixed, finally.
Having templates at the root level also makes working with web designers easier. And this directory is only created when you install Twig.
Moving templates allows to reserve src/
for PHP classes only, no more
resources. Another consequence of being bundle-less.
config/
#The new config/
directory is the equivalent of the current app/config/
directory. But with a very different layout. parameters.yml
and
parameters.yml.dist
are gone.
The main entry point for the container is an empty container.yaml
that you
can use to define your own services and parameters. Configuration for bundles
that you install via Composer are stored as individual files. One file per
bundle and per environment. The same goes for the routing configuration. That's
a soft requirement for auto-configuration, but allows for a better file
organization.
Configuration files can be written in PHP, XML, or YAML as before. The only
difference being the usage of the standard .yaml
extension instead of the
current .yml
one.
One major change is the introduction of a bundles.php
file where installed
bundles are referenced.
One file per bundle and bundles.php
are the two core concepts that enables
Symfony to automatically manage your bundles and their configurations.
src/
#The Kernel
class has been moved to src/
, where it belongs. The content is
very different from the one in Symfony 2. First, it uses the MicroKernelTrait
trait. Then, it implements the logic to load the bundles from bundles.php
and
to read the various bundle configuration files. The logic works as of Symfony
3.3. For instance, the code that loads the container configuration files reads
as follow:
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
{
$confDir = dirname(__DIR__).'/config';
$loader->import($confDir.'/packages/*'.self::CONFIG_EXTS, 'glob');
if (is_dir($confDir.'/packages/'.$this->getEnvironment())) {
$loader->import($confDir.'/packages/'.$this->getEnvironment().'/**/*'.self::CONFIG_EXTS, 'glob');
}
$loader->import($confDir.'/container'.self::CONFIG_EXTS, 'glob');
}
var/
#var/
is very similar to Symfony 3, with some minor tweaks in the best
practices.
var/cache/
should now only be used to store long term cached contents like
compiled container files, compiled translations, or Doctrine proxies. No
temporary files. Basically, anything stored under var/cache
should have a
warmup class able to generate the cache files. Files that should never be
updated after deployment to allow for read-only filesystems.
What about temporary files then? Use another directory like var/tmp/
. Or just
the standard Unix /tmp
directory.
If you follow this best practice, we will be able to guarantee a read-only
/var/cache
directory at some point. Having read-only directories is a
requirement of some hosting platforms like Heroku or SensioCloud and helps
scale an application. Probably not for Symfony 4.0 though.
public/
#I have already mentioned the single web front controller under public/
.
Almost all the other files were removed. No config.php
. No .htaccess
. No
favicon.ico
or apple-touch-icon.png
. Not even robots.txt
. Not all
projects need those files. If you want skeletons for those files, I got you
covered of course.
One difference with Symfony 3 is that all first-level directories are optional.
Don't use templates for you API? No need to create a templates/
directory.
Directories are created on-demand anyway.
When using Composer to add a new bundle, Symfony 4 levers this new directory
structure to automatically register the bundle in bundles.php
, copy some
sensible configuration under config/
, and much more. What else you ask? Great
topic for the next post.