PHP
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Object-Oriented PHP
-
Section 5: Working with Data
-
Section 6: Modern PHP (PHP 8)
-
Section 7: Working with Files and Networking
-
Section 8: Common PHP Frameworks Overview
-
Section 9: Tooling and Ecosystem
-
Section 10: Practical Projects
-
Section 11: Interview Practice
-
Section 12: More Standard Library
-
Section 13: Data Structures and Algorithms in PHP
-
Section 14: More Practice Exercises
-
Section 15: More Security and Best Practices
-
Section 16: More Testing and Tooling
-
Section 17: WordPress-Style CMS Concepts
-
Section 18: Advanced OOP Practice
-
Section 19: More Web Fundamentals
-
Section 20: Database Practice
-
Section 21: PHP Manual: Array Functions
-
Section 22: PHP Manual: Date and Calendar Functions
-
Section 23: PHP Manual: Filesystem and Directory Functions
-
Section 24: PHP Manual: Filter and Var Handling
-
Section 25: PHP Manual: Math Functions
-
Section 26: PHP Manual: JSON and XML
-
Section 27: PHP Manual: Network and Stream Functions
-
Section 28: PHP Manual: Error and Exception Handling
-
Section 29: PHP Manual: Output Control and Misc
-
Section 30: PHP Manual: FTP, Zip, and Mail
-
Section 31: Modern PHP Frameworks Deep Dive
-
Section 32: PHP Design Patterns
-
Section 33: More Practice Exercises
-
Section 34: PHP Performance and Deployment
-
Section 35: More Interview Practice
-
Section 36: More PHP Standard Library
-
Section 37: PHP Concurrency and Async
-
Section 38: More Web Development Practice
-
Section 39: PHP Testing Deep Dive
-
Section 40: Composer and Package Development
-
Section 41: PHP Security Deep Dive
-
Section 42: More Practical Projects
-
Section 43: Legacy PHP Maintenance
-
Section 44: More Algorithm Practice
-
Section 45: Final Practice and Review
-
Section 46: PHP for E-Commerce Patterns
-
Section 47: PHP API Design Deep Dive
-
Section 48: PHP Caching Strategies
-
Section 49: PHP Queue and Background Jobs
-
Section 50: PHP Multi-Tenancy Patterns
-
Section 51: PHP Real-Time Features
-
Section 52: PHP CMS and Content Modeling
-
Section 53: PHP Internationalization
-
Section 54: More Framework-Specific Practice
-
Section 55: PHP Legacy Code Refactoring
-
Section 56: More Practice Projects Round 2
-
Section 57: PHP Command-Line Applications
-
Section 58: PHP and Microservices
-
Section 59: More Interview and Review Round 2
239: Building CLI Tools with Symfony Console
How do I actually structure a command so Symfony knows what to do?
You could technically write a raw PHP script and run it with php script.php, but that gets messy fast once you need help menus, validation, or complex arguments. With Symfony Console, everything revolves around a class that extends Command. I always tell people to think of it in two phases: the configuration (what the command is called and what it expects) and the execution (the actual logic).
Let's say we're building a tool to sync products from a CSV file into our database. Here is the skeletal structure you'll use:
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class SyncProductsCommand extends Command
{
protected static $defaultName = 'app:sync-products';
protected function configure(): void
{
$this
->setDescription('Imports products from a CSV file.')
->setHelp('This command allows you to bulk upload products to the catalog.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
// Your logic goes here
$output->writeln('Starting the sync process...');
return Command::SUCCESS;
}
}
Notice the return Command::SUCCESS;. Symfony expects an integer return value. If something goes south, return Command::FAILURE. This is huge for CI/CD pipelines because it tells the server whether the deployment script should stop or keep going.
What's the difference between an argument and an option, and how do I add them?
This is where a lot of people trip up. In my experience, the simplest way to remember it is: Arguments are the "what" (required or optional values), and Options are the "how" (flags that modify behavior).
For our product sync, we definitely need the path to the CSV file (Argument). But we might also want a --dry-run flag so we can see what would happen without actually touching the database (Option). You define these in the configure() method:
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
protected function configure(): void
{
$this
->addArgument('filename', InputArgument::REQUIRED, 'The path to the CSV file')
->addOption('dry-run', null, InputOption::VALUE_NONE, 'Check the file without importing');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$file = $input->getArgument('filename');
$isDryRun = $input->getOption('dry-run');
if ($isDryRun) {
$output->writeln("Dry run enabled. No data will be saved for $file.");
}
return Command::SUCCESS;
}
Using InputOption::VALUE_NONE means the user doesn't have to pass a value (like --dry-run=yes); just presence of the flag makes it true.
How do I stop the output from looking like a wall of plain text?
If you just use $output->writeln(), your tool looks like it was written in 1992. Symfony provides a wrapper called SymfonyStyle that makes your CLI tool look like a professional product. It gives you pre-styled blocks for success, errors, warnings, and even progress bars.
I almost always instantiate SymfonyStyle at the very top of my execute method. Here is how you'd use it to make a product import feel responsive:
use Symfony\Component\Console\Style\SymfonyStyle;
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->title('Product Catalog Importer');
$file = $input->getArgument('filename');
if (!file_exists($file)) {
$io->error("The file $file was not found!");
return Command::FAILURE;
}
$io->section('Processing items...');
$progressBar = $io->createProgressBar(100);
$progressBar->start();
// Simulate processing
for ($i = 0; $i < 100; $i++) {
usleep(10000);
$progressBar->advance();
}
$progressBar->finish();
$io->newLine(2);
$io->success('All products have been synced successfully!');
return Command::SUCCESS;
}
The $io->success() and $io->error() methods add background colors and icons automatically. It's a small touch, but it makes a massive difference in how other developers perceive your tools.
📋 Practical Task
Exercise: Build a "Database User Deactivator" Tool
Your task is to create a Symfony Console command called app:user:deactivate. This tool will be used by admins to disable user accounts in the system.
Requirements:
- Argument: The command must require a
userId(integer). - Option: Add a
--reasonoption that allows the admin to provide a reason for deactivation (this should be optional and accept a string value). - UX: Use
SymfonyStyleto:- Display a confirmation question (
confirm()) asking "Are you sure you want to deactivate user [ID]?" before proceeding. - If the user says "no", display a warning saying "Operation cancelled." and exit.
- If the user says "yes", display a success message stating the user has been deactivated, including the reason if one was provided.
- Display a confirmation question (
Note: You don't need to connect to a real database; just simulate the logic by printing the results to the console.
There are no comments for now.