Using Symfony Components
Stand-alone in any project
Confoo - February 23rd, 2022
© David Buchmann
David Buchmann - david@liip.ch
PHP Engineer, Liip AG, Switzerland
OH:
Symfony is this massive
PHP framework to build applications with.
Massive?
- Yes, Symfony has a lot of components and can do many things
- But it is quite fast and not "heavy"
- It is quite modular, only look into the aspects you need
- Not only a framework - you can also use Symfony components stand alone
To full stack or not?
- Depending on the use case, it makes sense to use the full stack
- Things are set up already
- Standard application architecture
- With the MicroKernelTrait, you can build a very small Symfony full stack application
When to use components stand alone
- Reusable component to be used in any PHP application
- Really tiny application
- Application with a different framework but you need something the other framework does not provide
Large systems like Drupal and Laravel use Symfony components
Overview
- Symfony component ecosystem
- The OptionsResolver
- The Console
- Wrap up
Component Ecosystem
- Currently at 49 separate components!
- Some are tiny, just a single PHP file, others are large and solve complex tasks
- Everything is developped in a single repository but released as separately installable components
- Most are well designed
- Good documentation provided
Why bother to use any 3rd party component?
- Reasonable level of abstraction
- Errors and edge cases have been weeded out
- Good automated tests
Not convenient to use stand alone
- DependencyInjection
- HttpFoundation
- HttpKernel
Reinvented Wheels
My personal opinion, you are free to disagree :-)
- Filesystem : Flysystem allows you to transparently access every kind of Filesystem
- HttpClient: Guzzle is an established client, implements PSR-18
PSR-18 HTTP Client completely independent from the client you use
- Serializer: JMSSerializer provides advanced functionality like versioning and serialization groups
Utilities and components that solve a specific problem
Utilities: Do something small
- Dotenv: Populate $_SERVER from .env file
- ErrorHandler: Manage errors and help debugging
- ExpressionLanguage: Evaluated expressions in controlled context
- Finder: Conveniently search files in a filesystem
- HtmlSanitizer: Handle untrusted HTML
- Mime: Implements the email standard
- ... and many more
Solving Specific Problems
- DomCrawler: Navigate a HTML document
- EventDispatcher: Send events and register listeners
- Form: Object-oriented representation of HTML forms, integrated with the Validation component
- Mailer: Send emails
- Process: Manage binary processes from PHP
- Workflow: State machines and transitions
- Routing: Match requests to decide what to call
- ... and many more
OptionsResolver
- Flexible class, configurator
- Pass an array with key-value pairs with options, flags, callbacks
- There are valid use cases for this
- But don't use this pattern everywhere
Now how to implement this in your class?
Naive Approach
__construct(array $options = [])
{
if (!\array_key_exists('ttl', $options)) {
$options['ttl'] = 42;
}
...
}
Drawbacks of the naive approach
- Hard to see what options exist
- Hard to see the defaults
- Validation?   ¯\_(ツ)_/¯
- Typos in option keys are not noticed
Enter the OptionsResolver
$resolver = new OptionsResolver();
$resolver->setDefaults([
'default_ttl' => 0,
]);
$resolver->setAllowedTypes(
'default_ttl', ['int']
);
$this->config = $resolver->resolve($config);
Example on this and the next slides are from php-http/cache-plugin
OptionsResolver
- Does not depend on other Symfony tools.
- Consists of a total of 3 PHP classes.
=> There is no large cost in adding it to your dependencies.
OptionsResolver functionality
Custom normalizers
- Normalizer are applied to raw value before checks
- Convenience shortcuts
- Backwards compatibility when changing options
$resolver->setNormalizer(
'respect_response_cache_directives',
function (Options $options, $value) {
if (false === $options['respect_cache_headers']) {
return [];
}
return $value;
}
);
Console
- Command line interfaces
- Symfony: Used for bin/console
- Can be used to build a CLI for any application
PHP and the command line
- You can run files with php myscript.php
- You can add #!/usr/bin/env php and
chmod a+x myscript.php to directly execute it
- Symfony console
- Central entry point for all commands
- Tools for command arguments and options
- Easily provide self-documenting commands
- Wrappers for input and output streams
Symfony Console gives you control over the console cursor:
https://github.com/dbu/php-snake
Lets look at some examples from the Doctrine ORM database abstraction layer.
Set the name, description, arguments and options.
protected function configure()
{
$this->setName('orm:run-dql')
->setDescription('Executes queries from the cli')
->addArgument('dql', InputArgument::REQUIRED, 'DQL...')
->addOption('em', null, InputOption::VALUE_REQUIRED,
'Name of the entity manager to operate on')
->addOption('show-sql', null, InputOption::VALUE_NONE,
'Dump generated SQL instead of executing query')
->setHelp('...');
}
bin/console orm:run-dql "select * from Model\User" --em=userdata
Arguments
- Separated from the command name by whitespace
- Identified by order
Options
- Options are named
- "--" and then the option name
- You can define a short alias that is used with a single "-"
- Specify whether option is flag, may or must have a value
Command::execute
- Do what the command needs to do
- Larger applications: Have business logic in separate classes
- The output is used to print information
- Supports formatting: $output->writeln(
sprintf('Found %d results:', count($rows)));
Read input
Input is the place to get options:
$em = $input->getOption('em') === null
? $this->emProvider->getDefaultManager()
: $this->emProvider->getManager($input->getOption('em'))
;
And arguments:
$dql = $input->getArgument('dql');
Bootstrap the Console
Only needed when using console stand-alone. Minimal example from https://github.com/liip/varnish-plus-cli
#!/usr/bin/env php
<?php
require __DIR__.'/../vendor/autoload.php';
use App\Command\VclDeployCommand;
use App\Command\VclTwigCompileCommand;
use Symfony\Component\Console\Application;
$application = new Application('Varnish Plus CLI');
$application->add(new VclTwigCompileCommand());
$application->add(new VclDeployCommand());
$application->run();
Configuration Management
- You are free to do what you want
- In doctrine, most commands need access to the database
- You don't want to pass the credentials in each call
- The console checks for a configuration php script
- If it does not exist, it tells you how to write that file
- Each command gets the entity manager injected
Interactive Commands
Use the QuestionHelper to interact with the user
if ($input->isInteractive()) {
$question = 'Are you sure you wish to continue? (y/n)';
$confirmation = $this->getHelper('question')->ask(
$input,
$output,
new ConfirmationQuestion($question)
);
if ($confirmation) {
$this->markVersions($input, $output);
} else {
$output->writeln('<error>Cancelled!</error>');
}
}
Question Helper
- Ask for confirmation with ConfirmationQuestion
- Let the user pick from a list of options with ChoiceQuestion (with or without multiselect)
- Get text input with Question
- Allows fixed list of autocomplete options
- Callback to generate autocomplete suggestions on the fly
ProgressBar Helper
- Visual indication of progress when doing a long task
- E.g. when processing a large number of items
- Reassures the user that the command is still running
- Gives an indication of how long it might still take
- Lots of components available
- Generally of good quality
- Well maintained and secure
- Excellent documentation
For your next workflow, state machine, semaphores and locks in parallel processing and a bunch more standard concepts, check if you find a suitable Symfony component.
Thank you!
@dbu