The perfect MakeFile for Symfony (at least for me)

Published on 2018-12-09 • Modified on 2019-11-30

I have always used shell (.sh) scripts to streamline all my dev tasks on Symfony projects. But I discovered (here and here) that we could use a Makefile for this. I must say It works very well because:

  • It allows documenting all your dev tasks in a single file.
  • You've got autocompletion of the tasks name as Make is a standard Unix tool.
  • The task will stop as soon as an error is returned by one of the executed commands.

The file below is the one used by the Strangebuzz.com project and you can test it live to see what is the default output. Which is, in fact, the "help" command that lists all that can be done. Don't forget to change the PROJECT parameter at the top of the file. 😉

[Edit 2019-11-30] Deployment with EasyDeploy.
[Edit 2019-11-26] Added entries for the CLI Tool.
[BONUS] The PHP code to get the output of the system call thanks to the Symfony Process component.


# —— Inspired by ———————————————————————————————————————————————————————————————
# http://fabien.potencier.org/symfony4-best-practices.html
# https://speakerdeck.com/mykiwi/outils-pour-ameliorer-la-vie-des-developpeurs-symfony?slide=47
# https://blog.theodo.fr/2018/05/why-you-need-a-makefile-on-your-project/

# Setup —————————————————————————————————————————————————————————————————————————
PROJECT     = strangebuzz
EXEC_PHP    = php
REDIS       = redis-cli
GIT         = git
GIT_AUTHOR  = COil
SYMFONY     = $(EXEC_PHP) bin/console
SYMFONY_CLI = ./symfony
COMPOSER    = $(EXEC_PHP) composer.phar
DOCKER      = docker-compose
.DEFAULT_GOAL := help

## —— 🐝 The Strangebuzz Symfony Make file 🐝 ——————————————————————————————————
help: ## Outputs this help screen
	@grep -E '(^[a-zA-Z_-]+:.*?##.*$$)|(^##)' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}{printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}' | sed -e 's/\[32m##/[33m/'

wait: ## Sleep 5 seconds
	sleep 5

## —— Composer 🐘 ——————————————————————————————————————————————————————————————
install: composer.lock ## Install vendors according to the current composer.lock file
	$(COMPOSER) install

update: composer.json ## Update vendors according to the current composer.json file
	$(COMPOSER) update

## —— Symfony 🎵 ———————————————————————————————————————————————————————————————
sf: ## List all Symfony commands
	$(SYMFONY)

cc: ## Clear the cache
	$(SYMFONY) c:c

warmup: ## Warmump the cache
	$(SYMFONY) cache:warmup

fix-perms: ## Fix permissions of all var files
	chmod -R 777 var/*

assets: purge ## Install the assets with symlinks in the public folder
	$(SYMFONY) assets:install public/ --symlink --relative

purge: ## Purge cache and logs
	rm -rf var/cache/* var/logs/*

## —— Symfony CLI Tool  💻 —————————————————————————————————————————————————————
serve: symfony ## Serve the application with the CLI Tool and https support
	$(SYMFONY_CLI) serve --daemon

unserve: symfony ## Stop the CLI Tool server
	$(SYMFONY_CLI) server:stop

cert-install: symfony ## Install the local https certificates
	$(SYMFONY_CLI) server:ca:install

cli-install: ## Download and install the CLI tools for the project
	curl -sS https://get.symfony.com/cli/installer | bash
	mv ~/.symfony/bin/symfony .

## —— elasticsearch 🔎 —————————————————————————————————————————————————————————
populate: ## Reset and populate the elasticsearch index
	$(SYMFONY) fos:elastica:reset
	$(SYMFONY) fos:elastica:populate --index=app
	$(SYMFONY) strangebuzz:populate

list-index: ## List all indexes on the cluster
	curl http://localhost:9209/_cat/indices?v

delete-index: ## Delete a given index
	curl -X DELETE "localhost:9209/index?pretty"

## —— Docker 🐳 ————————————————————————————————————————————————————————————————
up: docker-compose.yaml ## Start the docker hub (MySQL,redis,adminer,elasticsearch,head,Kibana)
	$(DOCKER) -f docker-compose.yaml up -d

down: docker-compose.yaml ## Stop the docker hub
	$(DOCKER) down --remove-orphans

## —— Project 🐝 ———————————————————————————————————————————————————————————————
run: up wait load-fixtures populate serve ## Start docker, load fixtures, populate the elasticsearch index and start the web server

abort: down unserve ## Stop docker and the Symfony CLI server

cc-redis: ## Flush all Redis cache
	$(REDIS) flushall

commands: ## Display all commands in the project namespace
	$(SYMFONY) list $(PROJECT)

load-fixtures: ## Build the db, control the schema validity, load fixtures and check the migration status
	$(SYMFONY) doctrine:cache:clear-metadata
	$(SYMFONY) doctrine:database:create --if-not-exists
	$(SYMFONY) doctrine:schema:drop --force
	$(SYMFONY) doctrine:schema:create
	$(SYMFONY) doctrine:schema:validate
	$(SYMFONY) doctrine:fixtures:load -n
	$(SYMFONY) doctrine:schema:validate

test: phpunit.xml ## Launch all functionnal and unit tests
	./bin/phpunit --stop-on-failure

init-snippet: ## Initialize a new snippet
	$(SYMFONY) $(PROJECT):init-snippet

## —— Coding standards 👨‍💻 ————————————————————————————————————————————————————————

cs: codesniffer stan psalm ## Launch check style and static analysis

codesniffer: ## Run php_codesniffer only
	./vendor/squizlabs/php_codesniffer/bin/phpcs --standard=phpcs.xml -n -p src/

stan: ## Run Stan only
	./vendor/bin/phpstan analyse -l max -c phpstan.neon src/

psalm: ## Run psalm only
	./vendor/bin/psalm --show-info=false

init-psalm: ## Init a new psalm config file for a given level, it must be decremented to have stricter rules
	rm ./psalm.xml
	./vendor/bin/psalm --init src/ 3

cs-fix: ## Run php-cs-fixer and fix the code.
	./vendor/bin/php-cs-fixer fix src/

## —— Deploy ⏩ ————————————————————————————————————————————————————————————————
deploy: ## Full no-downtime deployment with EasyDeploy
	$(SYMFONY) deploy -v

env-check: ## Check the main ENV vars of the project
	printenv | grep -i app_

## —— Stats 📜 —————————————————————————————————————————————————————————————————
stats: ## Commits by hour for the main author of this project
	$(GIT) log --author="$(GIT_AUTHOR)" --date=iso | perl -nalE 'if (/^Date:\s+[\d-]{10}\s(\d{2})/) { say $$1+0 }' | sort | uniq -c|perl -MList::Util=max -nalE '$$h{$$F[1]} = $$F[0]; }{ $$m = max values %h; foreach (0..23) { $$h{$$_} = 0 if not exists $$h{$$_} } foreach (sort {$$a <=> $$b } keys %h) { say sprintf "%02d - %4d %s", $$_, $$h{$$_}, "*"x ($$h{$$_} / $$m * 50); }'
Bonus, the snippet to run this code: 🎉
<?php declare(strict_types=1);

namespace App\Controller\Snippet;

use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Process\Process;

/**
 * I am using a PHP trait in order to isolate each snippet in a file.
 * This code should be called from a Symfony controller extending AbstractController (as of Symfony 4.2)
 * or Symfony\Bundle\FrameworkBundle\Controller\Controller (Symfony <= 4.1).
 * Services are injected in the main controller constructor.
 *
 * @property KernelInterface $kernel
 */
trait Snippet8Trait
{
    public function snippet8(): void
    {
        $process = new Process([
            'make',
            '-f',
            $this->kernel->getProjectDir().'/Makefile',
        ]);
        $process->run();

        $output = str_replace(
            ["\e[33m", "\e[32m", "\e[0m"],
            '',
            $process->getOutput()
        );

        echo $output; // That's it! 😁
    }
}

 Run this snippet  More on Stackoverflow