[Kocal/SymfonyAppPack] Add recipe (#1936)

* [Kocal/SymfonyAppPack] Add recipe

* Remove Makefile from copy-from-recipe
This commit is contained in:
Hugo Alliaume
2026-02-11 00:01:04 +01:00
committed by GitHub
parent f2283e952e
commit cdc5b03530
18 changed files with 652 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "composer" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

View File

@@ -0,0 +1,41 @@
name: CI
defaults:
run:
shell: bash
on:
pull_request:
types: [ opened, synchronize, reopened, ready_for_review ]
push:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
qa:
if: ${{ ! github.event.pull_request.draft }}
name: Quality Assurance
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
extensions: ctype, iconv, mbstring
tools: symfony
- name: Start Docker containers
run: docker compose up -d --wait
- name: Install project
run: make app.install
- name: QA
run: make qa

View File

@@ -0,0 +1,10 @@
<?php
$ruleset = new TwigCsFixer\Ruleset\Ruleset();
$ruleset->addStandard(new TwigCsFixer\Standard\Symfony());
$config = new TwigCsFixer\Config\Config();
$config->setCacheFile(__DIR__.'/tools/.cache/twig-cs-fixer');
$config->setRuleset($ruleset);
return $config;

View File

@@ -0,0 +1,180 @@
ROOT_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
include $(ROOT_DIR)/tools/make/text.mk
include $(ROOT_DIR)/tools/make/help.mk
include $(ROOT_DIR)/tools/make/os.mk
include $(ROOT_DIR)/tools/make/git.mk
.DEFAULT_GOAL := help
# Executables
SF := symfony
SF_PROXY = $(shell $(SF) local:proxy:url)
SF_CONSOLE := $(SF) console
PHP := $(SF) php
COMPOSER := $(SF) composer
UID := $(shell id -u)
GID := $(shell id -g)
DC := USER_ID=$(UID) GROUP_ID=$(GID) docker compose --env-file /dev/null
## Install everything needed to start the project
install:
$(SF) local:server:ca:install
$(MAKE) start
$(MAKE) app.install
## Start the environment
start:
$(DC) up -d
$(SF) proxy:start
$(SF) serve
## Stop the environment
stop:
$(DC) kill
## Stop and delete the environment and project data (database, logs, etc.)
delete:
$(DC) down -v --remove-orphans
## App - Install the application
app.install:
@$(call action, Installing PHP dependencies...)
$(COMPOSER) install --prefer-dist
@$(call action, Running DB migrations...)
$(SF_CONSOLE) doctrine:migrations:migrate --no-interaction --all-or-nothing
## App - Install the application (alias to "make app.install")
app.update: app.install
######
# QA #
######
## QA - Run all QA checks
qa: archi refactor cs lint phpstan test
## QA - Run all QA checks and fix issues
qa.fix: archi refactor.fix cs.fix lint.fix phpstan test
#########
# Archi #
#########
## Architecture - Run architecture checks
archi: archi.back
## Architecture - Run architecture checks for backend
archi.back:
$(PHP) vendor/bin/deptrac --report-uncovered --fail-on-uncovered
############
# Refactor #
############
## Refactor - Run all refactor checks
refactor: refactor.back
## Refactor - Run all refactor checks and fix issues
refactor.fix: refactor.back.fix
## Refactor - Run refactor checks for backend
refactor.back:
$(PHP) vendor/bin/rector process --dry-run
## Refactor - Run refactor checks for backend and fix issues
refactor.back.fix:
$(PHP) vendor/bin/rector process
################
# Coding style #
################
## Coding style - Run all coding style checks
cs: cs.back cs.front
## Coding style - Run all coding style checks and fix issues
cs.fix: cs.back.fix cs.front.fix
## Coding style - Check backend coding style
cs.back:
$(PHP) vendor/bin/ecs check
$(PHP) vendor/bin/twig-cs-fixer
## Coding style - Check backend coding style and fix issues
cs.back.fix:
$(PHP) vendor/bin/ecs check --fix
$(PHP) vendor/bin/twig-cs-fixer --fix
## Coding style - Check frontend coding style
cs.front: bin/oxfmt
bin/oxfmt --check
## Coding style - Check frontend coding style and fix issues
cs.front.fix: bin/oxfmt
bin/oxfmt
bin/oxfmt:
@$(call action, Downloading oxfmt...)
$(SF_CONSOLE) oxc:download:oxfmt
@$(call success, oxfmt downloaded successfully)
##########
# Linter #
##########
## Linter - Run all linters
lint: lint.back lint.front
## Linter - Run all linters and fix issues
lint.fix: lint.back lint.front.fix
## Linter - Run linters for backend
lint.back:
$(SF_CONSOLE) lint:container
$(SF_CONSOLE) lint:xliff translations
$(SF_CONSOLE) lint:yaml --parse-tags config
$(SF_CONSOLE) lint:twig templates
#$(SF_CONSOLE) doctrine:schema:validate
## Linter - Lint front files
lint.front: bin/oxlint
bin/oxlint
## Linter - Lint front files and fix issues
lint.front.fix: bin/oxlint
bin/oxlint --fix
bin/oxlint:
@$(call action, Downloading oxlint...)
$(SF_CONSOLE) oxc:download:oxlint
###########
# PHPStan #
###########
## PHPStan - Run PHPStan
phpstan:
$(PHP) vendor/bin/phpstan analyse
## PHPStan - Run PHPStan and update the baseline
phpstan.generate-baseline:
$(PHP) vendor/bin/phpstan analyse --generate-baseline
#########
# Tests #
#########
## Tests - Run all tests
test: test.back
## Tests - Run backend tests
test.back:
$(PHP) vendor/bin/phpunit
## Tests - Run backend tests with coverage
test.back.coverage:
$(PHP) vendor/bin/phpunit --coverage-html .cache/phpunit/coverage-html
-include $(ROOT_DIR)/Makefile.local

View File

@@ -0,0 +1 @@
# Local targets that should not be committed to git

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
use Symplify\EasyCodingStandard\Config\ECSConfig;
return ECSConfig::configure()
->withParallel()
->withCache(__DIR__ . '/tools/.cache/ecs')
->withPaths([
__DIR__ . '/assets',
__DIR__ . '/config',
__DIR__ . '/public',
__DIR__ . '/src',
__DIR__ . '/tests',
__DIR__ . '/tools',
])
->withSkip([
__DIR__ . '/tools/.cache',
__DIR__ . '/config/bundles.php',
__DIR__ . '/config/reference.php',
])
->withPreparedSets(
psr12: true,
common: true,
strict: true,
cleanCode: true,
)
;

View File

@@ -0,0 +1,15 @@
{
"copy-from-recipe": {
".github/": ".github/",
"tools/": "tools/",
".twig-cs-fixer.php": ".twig-cs-fixer.php",
"ecs.php": "ecs.php",
"Makefile.local": "Makefile.local",
"php.ini": "php.ini",
"phpstan.dist.neon": "phpstan.dist.neon",
"rector.php": "rector.php"
},
"gitignore": [
"/Makefile.local"
]
}

View File

@@ -0,0 +1 @@
memory_limit = -1

View File

@@ -0,0 +1,36 @@
includes:
- vendor/kocal/phpstan-symfony-ux/extension.neon
- vendor/phpstan/phpstan-symfony/extension.neon
- vendor/phpstan/phpstan-doctrine/extension.neon
- tools/phpstan/symfony-configuration.php
parameters:
level: 9
paths:
- bin/
- config/
- public/
- src/
- tools/
- tests/
excludePaths:
- config/reference.php
- tools/.cache
tmpDir: tools/.cache/phpstan
symfony:
consoleApplicationLoader: tools/phpstan/console-application.php
doctrine:
objectManagerLoader: tools/phpstan/object-manager.php
rules:
- Kocal\PHPStanSymfonyUX\Rules\TwigComponent\ClassMustBeFinalRule
- Kocal\PHPStanSymfonyUX\Rules\TwigComponent\ClassNameMustNotEndWithComponentRule
- Kocal\PHPStanSymfonyUX\Rules\TwigComponent\ForbiddenAttributesPropertyRule
- Kocal\PHPStanSymfonyUX\Rules\TwigComponent\ForbiddenClassPropertyRule
- Kocal\PHPStanSymfonyUX\Rules\TwigComponent\MethodsVisibilityRule
- Kocal\PHPStanSymfonyUX\Rules\TwigComponent\PostMountMethodSignatureRule
- Kocal\PHPStanSymfonyUX\Rules\TwigComponent\PreMountMethodSignatureRule
- Kocal\PHPStanSymfonyUX\Rules\TwigComponent\PublicPropertiesMustBeCamelCaseRule

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\Symfony\CodeQuality\Rector\Class_\ControllerMethodInjectionToConstructorRector;
return RectorConfig::configure()
->withParallel()
->withCache(__DIR__ . '/tools/.cache/rector')
->withPaths([
__DIR__ . '/config',
__DIR__ . '/public',
__DIR__ . '/src',
__DIR__ . '/tests',
__DIR__ . '/tools',
])
->withPhpSets()
->withPreparedSets(
deadCode: true,
codeQuality: true,
codingStyle: true,
typeDeclarations: true,
privatization: true,
instanceOf: true,
earlyReturn: true,
phpunitCodeQuality: true,
doctrineCodeQuality: true,
symfonyCodeQuality: true,
)
->withSkip([
__DIR__ . '/config/bundles.php',
__DIR__ . '/config/reference.php',
__DIR__ . '/tools/.cache',
ControllerMethodInjectionToConstructorRector::class,
])
;

View File

@@ -0,0 +1 @@
.cache/

View File

@@ -0,0 +1,30 @@
########
# Diff #
########
# Returns the list of changed files for some given extensions and some given folders.
#
# @param $1 The file extensions of changed files
# @param $2 The relative folders to parse for changed files
#
# Examples:
#
# Example #1: list PHP and Javascript files changed in the src and test folders
#
# $(call git_diff, php js, src test)
define git_diff
$(shell \
for ext in $(if $(strip $(1)),$(strip $(1)),"") ; \
do \
for dir in $(if $(strip $(2)),$(strip $(2)),"") ; \
do \
git --no-pager diff --name-status "$$(git merge-base HEAD origin/master)" \
| grep "$${ext}\$$" \
| grep "\\s$${dir}" \
| grep -v '^D' \
| awk '{ print $$NF }' || true ; \
done ; \
done \
)
endef

View File

@@ -0,0 +1,70 @@
########
# Help #
########
.DEFAULT_GOAL := help
HELP = \
Usage: make [$(COLOR_INFO)command$(COLOR_RESET)] \
$(call help_section, Help) \
$(call help,help,This help)
define help_section
\n\n$(COLOR_COMMENT)$(strip $(1)):$(COLOR_RESET)
endef
define help
\n $(COLOR_INFO)$(1)$(COLOR_RESET) $(2)
endef
help:
@printf "\n$(HELP)"
@awk ' \
BEGIN { \
sectionsName[1] = "Commands" ; \
sectionsCount = 1 ; \
} \
/^[-a-zA-Z0-9_.@%\/+]+:/ { \
if (match(lastLine, /^## (.*)/)) { \
command = substr($$1, 1, index($$1, ":") - 1) ; \
section = substr(lastLine, RSTART + 3, index(lastLine, " - ") - 4) ; \
if (section) { \
message = substr(lastLine, index(lastLine, " - ") + 3, RLENGTH) ; \
sectionIndex = 0 ; \
for (i = 1; i <= sectionsCount; i++) { \
if (sectionsName[i] == section) { \
sectionIndex = i ; \
} \
} \
if (!sectionIndex) { \
sectionIndex = sectionsCount++ + 1 ; \
sectionsName[sectionIndex] = section ; \
} \
} else { \
message = substr(lastLine, RSTART + 3, RLENGTH) ; \
sectionIndex = 1 ; \
} \
if (length(command) > sectionsCommandLength[sectionIndex]) { \
sectionsCommandLength[sectionIndex] = length(command) ; \
} \
sectionCommandIndex = sectionsCommandCount[sectionIndex]++ + 1; \
helpsCommand[sectionIndex, sectionCommandIndex] = command ; \
helpsMessage[sectionIndex, sectionCommandIndex] = message ; \
} \
} \
{ lastLine = $$0 } \
END { \
for (i = 1; i <= sectionsCount; i++) { \
if (sectionsCommandCount[i]) { \
printf "\n\n$(COLOR_COMMENT)%s:$(COLOR_RESET)", sectionsName[i] ; \
for (j = 1; j <= sectionsCommandCount[i]; j++) { \
printf "\n $(COLOR_INFO)%-" sectionsCommandLength[i] "s$(COLOR_RESET) %s", helpsCommand[i, j], helpsMessage[i, j] ; \
} \
} \
} \
} \
' $(MAKEFILE_LIST)
@printf "\n\n"
.PHONY: help

View File

@@ -0,0 +1,21 @@
######
# Os #
######
# Os detection helpers.
#
# Examples:
#
# Example #1: conditions on linux
#
# echo $(if $(OS_LINUX),Running on Linux,*NOT* running on Linux)
ifeq ($(OS),Windows_NT)
OS = windows
OS_WINDOWS = 1
else
# See: https://make.mad-scientist.net/deferred-simple-variable-expansion/
OS = $(eval OS := $$(shell uname -s | tr '[:upper:]' '[:lower:]'))$(OS)
OS_LINUX = $(if $(findstring linux,$(OS)),1)
OS_DARWIN = $(if $(findstring darwin,$(OS)),1)
endif

View File

@@ -0,0 +1,103 @@
##########
# Colors #
##########
COLOR_RESET := \033[0m
COLOR_BOLD_ITALIC := \033[1;4m
COLOR_ERROR := \033[31m
COLOR_INFO := \033[32m
COLOR_WARNING := \033[33m
COLOR_COMMENT := \033[36m
######################
# Special Characters #
######################
# Usage:
# $(call message, Foo$(,) bar) = Foo, bar
, := ,
########
# Time #
########
# Usage:
# $(call time) = 11:06:20
define time
`date -u +%T`
endef
###########
# Message #
###########
# Usage:
# $(call message, Foo bar) = Foo bar
# $(call message_success, Foo bar) = (っ◕‿◕)っ Foo bar
# $(call message_warning, Foo bar) = ¯\_(ツ)_/¯ Foo bar
# $(call message_error, Foo bar) = (╯°□°)╯︵ ┻━┻ Foo bar
define message
printf "$(COLOR_INFO)$(strip $(1))$(COLOR_RESET)\n"
endef
define message_success
printf "$(COLOR_INFO)(っ◕‿◕)っ $(strip $(1))$(COLOR_RESET)\n"
endef
define message_warning
printf "$(COLOR_WARNING)$(strip $(1))$(COLOR_RESET)\n"
endef
define message_error
printf "$(COLOR_ERROR)(╯°□°)╯︵ ┻━┻ $(strip $(1))$(COLOR_RESET)\n"
endef
###########
# Confirm #
###########
# Usage:
# $(call confirm, Foo bar) = ༼ つ ◕_◕ ༽つ Foo bar (y/N):
define confirm
$(if $(CONFIRM),, \
printf "$(COLOR_INFO) ༼ つ ◕_◕ ༽つ $(COLOR_WARNING)$(strip $(1)) $(COLOR_RESET)$(COLOR_WARNING)(y/N)$(COLOR_RESET): "; \
read CONFIRM ; if [ "$$CONFIRM" != "y" ]; then printf "\n"; exit 1; fi; \
)
endef
#######
# Log #
#######
# Usage:
# $(call log, Foo bar) = [11:06:20] [target] Foo bar
# $(call log_warning, Foo bar) = [11:06:20] [target] ¯\_(ツ)_/¯ Foo bar
# $(call log_error, Foo bar) = [11:06:20] [target] (╯°□°)╯︵ ┻━┻ Foo bar
# $(call log_and_call, echo 'Message') = [11:06:20] [target] echo 'Message' then execute the command
define log
printf "[$(COLOR_COMMENT)$(call time)$(COLOR_RESET)] [$(COLOR_COMMENT)$(@)$(COLOR_RESET)] " ; $(call message, $(1))
endef
define log_warning
printf "[$(COLOR_COMMENT)$(call time)$(COLOR_RESET)] [$(COLOR_COMMENT)$(@)$(COLOR_RESET)] " ; $(call message_warning, $(1))
endef
define log_error
printf "[$(COLOR_COMMENT)$(call time)$(COLOR_RESET)] [$(COLOR_COMMENT)$(@)$(COLOR_RESET)] " ; $(call message_error, $(1))
endef
##########
# Action #
##########
# Usage:
# $(call action, Foo bar) = > Foo bar (in bold)
define action
printf "\n$(COLOR_BOLD_ITALIC)>>> $(strip $(1))$(COLOR_RESET)\n\n"
endef

View File

@@ -0,0 +1,18 @@
<?php
/**
* This file is used by PHPStan, see https://github.com/phpstan/phpstan-symfony#console-command-analysis.
*/
declare(strict_types=1);
use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Dotenv\Dotenv;
new Dotenv()->bootEnv(__DIR__ . '/../../.env');
// @phpstan-ignore-next-line argument.type
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
return new Application($kernel);

View File

@@ -0,0 +1,22 @@
<?php
/**
* This file is used by PHPStan, see https://github.com/phpstan/phpstan-symfony#console-command-analysis.
*/
declare(strict_types=1);
use App\Kernel;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Dotenv\Dotenv;
new Dotenv()->bootEnv(__DIR__ . '/../../.env');
// @phpstan-ignore-next-line argument.type
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$kernel->boot();
/** @var EntityManager $entityManager */
$entityManager = $kernel->getContainer()->get('doctrine')->getManager();
return $entityManager;

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
require __DIR__ . '/../../vendor/autoload.php';
$env = getenv('APP_ENV') ?: 'test';
$xmlContainerFile = __DIR__ . sprintf('/../../var/cache/%s/App_Kernel%sDebugContainer.xml', $env, ucfirst($env));
if (! file_exists($xmlContainerFile)) {
throw new RuntimeException(sprintf(<<<ERROR
PHPStan depends on the meta information the Symfony Dependency Injection that the compiler pass writes.
The meta xml file could not be found: %s.
To compile the Symfony container do a cache:clear in the current env (%s) with debug: true!
ERROR, $xmlContainerFile, $env));
}
return [
'parameters' => [
'symfony' => [
'containerXmlPath' => $xmlContainerFile,
],
],
];