@Florian_FB
Magnifique photo corporate
Lead Développeur PHP/Symfony
@Florian_FB
Florian-B
GL events
@Florian_FB
@Florian_FB
@Florian_FB
Catégorie attribuée à l'utilisateur
Attribué de manière statique
Détermine les actions autorisées
@Florian_FB
# config/packages/security.yaml
security:
role_hierarchy:
ROLE_SUPER_ADMIN: [ROLE_ADMIN]
access_control:
- { path: ^/admin/login, roles: PUBLIC_ACCESS }
- { path: ^/admin/cfp, roles: ROLE_SUPER_ADMIN }
- { path: ^/admin, roles: ROLE_ADMIN }
@Florian_FB
use Symfony\Component\Security\Http\Attribute\IsGranted;
final class ListPropositionController
{
#[Route('/admin/cfp/{id}/list', name: 'admin_cfp_list')]
#[IsGranted('ROLE_SUPER_ADMIN')]
public function __invoke(Event $event): Response
{
// do amazing stuff here
}
}
@Florian_FB
{% if is_granted('ROLE_SUPER_ADMIN') %}
<a href="{{ path('my_path') }}">button title</a>
{% endif %}
use Symfony\Bundle\SecurityBundle\Security;
public function __construct(private readonly Security $security) {}
public function myAmazingFunction(): void
{
if ($this->security->isGranted('ROLE_SUPER_ADMIN')) {
// do something
}
}
@Florian_FB
Règle métier spécifique
Appelée de manière dynamique
Roles & Permissions sont complémentaires
Voter
@Florian_FB
Attribute : edit_document
subject : document
@Florian_FB
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
abstract class Voter implements VoterInterface
{
abstract protected function supports(
string $attribute,
mixed $subject
): bool;
abstract protected function voteOnAttribute(
string $attribute,
mixed $subject,
TokenInterface $token
): bool;
}
@Florian_FB
use Symfony\Component\Security\Http\Attribute\IsGranted;
final class ListPropositionController
{
#[Route('/admin/cfp/{id}/list', name: 'admin_cfp_list')]
#[IsGranted('ROLE_SUPER_ADMIN')]
#[IsGranted('list_cfp', 'event')]
public function __invoke(Event $event): Response
{
// do amazing stuff here
}
}
@Florian_FB
Affirmative (défaut)
Consensus
Unanimous
Priority
prio : 2
prio : 1
prio : 0
@Florian_FB
Â
@Florian_FB
Identité visuelle
Nom de domaine
Mire de login
Propres fonctionnalités
Cacher des fonctionnalités
Profils d'utilisateurs
Vendre l'application en externe 🚀
@Florian_FB
@Florian_FB
​
Identité visuelle
Nom de domaine
Mire de login
Â
Propres fonctionnalités
Cacher des fonctionnalités
Profils d'utilisateurs
Marque blanche
Feature flipping
@Florian_FB
@Florian_FB
Une seule application
Multi clients
Nom de domaine dédié
Charte graphique dédiée
Fonctionnalités dédiées
Impression d'un développement en interne
@Florian_FB
@Florian_FB
@Florian_FB
# my_white_label.conf
# Apache
SetEnv app_white_label symfony
# Nginx
set $app_white_label symfony;
@Florian_FB
# public/index.php
use App\Kernel;
use Symfony\Component\Dotenv\Dotenv;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
$whiteLabel = $context['APP_WHITE_LABEL'] ?? null;
$whiteLabelEnvFile = sprintf('%s/.env.wl.%s', dirname(__DIR__), $whiteLabel);
if (null !== $whiteLabel && file_exists($whiteLabelEnvFile)) {
(new Dotenv())->loadEnv($whiteLabelEnvFile);
}
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};
@Florian_FB
# .env.wl.symfony
APP_STYLE_ENTRY=symfony
APP_LOGO=logo-symfony.png
APP_FAVICON=favicon-symfony.png
APP_MY_VAR=foo
...
@Florian_FB
# config/services.yaml
App\Twig\WhiteLabelExtension:
arguments:
$appStyleEntry: '%env(string:APP_STYLE_ENTRY)%'
$appLogo: '%env(string:APP_LOGO)%'
$appFavicon: '%env(string:APP_FAVICON)%'
@Florian_FB
class WhiteLabelExtension extends AbstractExtension
{
public function __construct(private readonly string $appLogo) {}
public function getFunctions(): array
{
return [
new TwigFunction('get_logo', [$this, 'getLogo']),
];
}
public function getLogo(): string
{
return sprintf('path/to/logo/%s', $this->appLogo);
}
}
@Florian_FB
aussi appelé Feature Flag
@Florian_FB
Déploiement continu
Déploiement progressif
Déploiement sécurisé
En fonction de l'utilisateur
Fonctionnement dégradé
@Florian_FB
@Florian_FB
features:
my_feature_1: false
my_feature_2: true
if ($this->featureManager->isEnabled('my_feature_1')) {
// my_feature_1 is enabled
}
if ($this->featureManager->isDisabled('my_feature_2')) {
// my_feature_2 is not enabled
}
{% if isFeatureEnabled('my_feature_1') %}
{% include 'feature1_template.html.twig' %}
{% endif %}
@Florian_FB
Features et états dans Gitlab
Gitlab expose une API
Application consomme l'API
Le fonctionnement est "basique"
Bundle Symfony
@Florian_FB
En fonction de l'utilisateur
En fonction du contexte
En fonction de la marque blanche
@Florian_FB
John Doe
Admin
Facturation
Lecture seule
SymfonyLive
SymfonyCon
Forum PHP
Sponsor 1
Sponsor 2
WL Symfony
WL AFUP
@Florian_FB
WL AFUP
WL Symfony
AFUP Day
SF Live
@Florian_FB
Utiliser des rĂ´les
Utiliser des permissions
@Florian_FB
Voters & attributs
1 action = 1 attribut unique
1 feature = N attributs
1 attribut = 1 feature
Migration Doctrine
CRUD
@Florian_FB
Attribut
list_document
add_document
...
Feature
document
product
...
Profile
admin
facturation
...
@Florian_FB
public function whiteLabelHasAttribute(string $attribute): bool
{
// do check
}
@Florian_FB
public function whiteLabelHasAttribute(string $attribute): bool
{
// do check
}
public function exhibitionHasAttribute(Exhibition $exhibition, string $attribute): bool
{
// do check
}
@Florian_FB
public function whiteLabelHasAttribute(string $attribute): bool
{
// do check
}
public function exhibitionHasAttribute(Exhibition $exhibition, string $attribute): bool
{
// do check
}
public function userHasAttributeForExhibitor(Exhibitor $exhibitor, string $attribute): bool
{
// do check
}
@Florian_FB
abstract class CustomVoter extends Voter
{
abstract protected function getExhibitor(mixed $subj): Exhibitor;
abstract protected function checkAttribute(
string $attribute,
mixed $subject,
TokenInterface $token
): bool;
final protected function voteOnAttribute(
string $attribute,
mixed $subject,
TokenInterface $token
): bool {
// some check here
}
}
@Florian_FB
final protected function voteOnAttribute(
string $attribute,
mixed $subject,
TokenInterface $token
): bool {
$exhibitor = $this->getExhibitor($subject);
$exhibition = $exhibitor->getExhibition()
if (!$this->whiteLabelHasAttribute($attribute)) {
// write log
return false;
}
if (!$this->exhibitionHasAttribute($exhibition, $attribute)) {
// write log
return false;
}
if (!$this->userAttributeForExhibitor($exhibitor, $attribute)) {
// write log
return false;
}
return $this->checkAttribute($attribute, $subject, $token);
}
@Florian_FB
{% if user_has_attribute(exhibitor, 'add_product') %}
// todo
{% endif %}
Fonction twig
Reprends les check du voter
@Florian_FB
$frontendAppContext = new FrontendAppContext();
$attributes = $this->featureFlipping->getAttributesForApp($app);
foreach ($attributes as $attribute) {
if (!$this->checker->userHasAttribute($attribute, ...)) {
continue;
}
$frontendAppContext->permissions[] = $attribute;
}
return $frontendAppContext;
API pour le VueJS
Expose les attributs accessibles pour l'app
@Florian_FB
Requêtes SQL très simple
Cache Redis
Impact faible
@Florian_FB
public function __construct(private readonly CacheInterface $cacheRedis) {}
private function whiteLabelHasAttribute(string $attribute): bool
{
return $this->cacheRedis->get(
$cacheKey,
function (ItemInterface $item) use ($attribute) {
$item->expiresAfter(self::CACHE_TTL);
// do check
return ...;
}
);
}
@Florian_FB
Template Jira
Recette
Template Gitlab
Architectural Decision Records
Et bien-sur des outils !
@Florian_FB
Règles PHPStan
Maker
Scripts de validation
CI
Tests
CRUD en backoffice
Dashboard Kibana
@Florian_FB
@Florian_FB