New in Symfony 8.2 (in development). A role_hierarchy reads role by role: each key is one specific role, each value what it grants. Two sections that hand out the same roles, and the same sentence is written twice; a third section, and you write it again. Symfony 8.2 accepts wildcard keys, which name a whole family.
The wildcard
The * only counts as a wildcard in two places: surrounded by underscores, or at the end of the role after an underscore. ROLE_BLOG* and ROLE_*BLOG remain ordinary role names.
security: role_hierarchy: ROLE_*: ROLE_USER ROLE_*_MODERATOR: ROLE_MODERATOR ROLE_BLOG_*: ROLE_BLOG_READER
A role then accumulates what every wildcard it matches gives it, and its own key if it has one. Above all, a wildcard also applies to roles you never declared: ROLE_BLOG_ARCHIVIST, invented tomorrow, already reads the blog.
The wildcard only exists on the keys side. is_granted('ROLE_BLOG_*') still looks for an exact role, and will find nothing.
Seeing what applied
Two overlapping wildcards, and you quickly doubt what a role really receives. Symfony 8.2 answers in the console:
php bin/console debug:roles ROLE_BLOG_MODERATOR --tree
ROLE_BLOG_MODERATOR ├── ROLE_* │ └── ROLE_USER ├── ROLE_*_MODERATOR │ └── ROLE_MODERATOR ├── ROLE_BLOG_* │ └── ROLE_BLOG_READER ├── ROLE_BLOG_DELETE_ARTICLE └── ROLE_BLOG_LOCK_ARTICLE
Each branch names the key that granted what it holds. Without --tree, the command gives a flat list; without an argument, the whole hierarchy.
Your task
The magazine has two sections, the blog and the forum, and their hierarchy repeats nine times what could fit in a few lines. In config/packages/security.yaml, rewrite role_hierarchy with wildcards, without changing what anyone receives — the /roles preview shows, for each role, its effective roles.
Once the wildcards are in place, ROLE_BLOG_ARCHIVIST, which the configuration mentions nowhere, must read the blog like the others.