alister/reserved-names-bundle
Install via Composer:
composer require alister/reserved-names-bundle
(Note: Due to Symfony 4/5 compatibility, ensure your project meets the ^4.4 || ^5.0 requirement.)
Register the Bundle:
Add to config/bundles.php (Symfony 4+):
return [
// ...
Alister\ReservedNamesBundle\AlisterReservedNamesBundle::class => ['all' => true],
];
Configure Reserved Names:
Define reserved names in config/packages/alister_reserved_names.yaml:
alister_reserved_names:
names:
- admin
- root
- private
- test
First Use Case: Check a username in a controller or service:
use Symfony\Component\HttpFoundation\Request;
public function register(Request $request)
{
$username = $request->request->get('username');
$reservedChecker = $this->get('alister_reserved_names.check');
if ($reservedChecker->isReserved($username)) {
throw new \RuntimeException('Username is reserved.');
}
// Proceed with registration...
}
Username Validation Pipeline: Integrate into registration/login flows:
$cleaner = $this->get('alister_reserved_names.cleanusername');
$cleaned = $cleaner->clean($username); // "user_123" → "user"
$reservedChecker = $this->get('alister_reserved_names.check');
if ($reservedChecker->isReserved($username) || $reservedChecker->isReserved($cleaned)) {
// Reject username
}
Dynamic Reserved Names: Override defaults via environment variables or config:
# config/packages/alister_reserved_names.yaml
alister_reserved_names:
names:
- "%env(reserved_names_list)%" # e.g., "admin,root,private"
Service Integration: Use in form validators or DTOs:
public function validateUsername(string $username): void
{
$cleaner = $this->container->get('alister_reserved_names.cleanusername');
$checker = $this->container->get('alister_reserved_names.check');
if ($checker->isReserved($cleaner->clean($username))) {
throw new \InvalidArgumentException('Reserved username.');
}
}
Event-Driven Checks:
Attach to KernelEvents::REQUEST or custom events:
$eventDispatcher->addListener(KernelEvents::REQUEST, function (RequestEvent $event) {
$request = $event->getRequest();
if ($request->isMethod('POST') && $request->request->has('username')) {
$checker = $this->container->get('alister_reserved_names.check');
if ($checker->isReserved($request->request->get('username'))) {
$event->setResponse(new Response('Reserved username.', 400));
}
}
});
Custom Reserved Name Providers: Extend functionality by creating a custom provider:
use Alister\ReservedNamesBundle\Services\ReservedNamesInterface;
class CustomReservedNames implements ReservedNamesInterface
{
public function isReserved(string $name): bool
{
$reserved = ['admin', 'custom_reserved'];
return in_array(strtolower($name), $reserved);
}
}
Register as a service:
services:
alister_reserved_names.check:
class: App\Services\CustomReservedNames
Local Overrides: Merge local reserved names with bundle defaults:
alister_reserved_names:
names:
- "%kernel.project_dir%/config/reserved_names.yml" # Load from file
Testing Integration: Mock the service in unit tests:
$mockChecker = $this->createMock(ReservedNamesInterface::class);
$mockChecker->method('isReserved')->willReturn(true);
$this->container->set('alister_reserved_names.check', $mockChecker);
Case Sensitivity: The bundle lowers all names before comparison. Ensure reserved names are lowercase in config to avoid false negatives.
Noise Character Handling:
The cleanusername service aggressively strips trailing s, digits, -, and _. Test edge cases like:
"admin123" → "admin" (flagged as reserved)"admins" → "admin" (flagged)"admin_" → "admin" (flagged)Symfony 4+ Compatibility:
^4.4 in composer.json).config/bundles.php is used instead of AppKernel.php.Performance:
The isReserved() method uses in_array(), which is O(n). For large reserved lists (>1000 names), consider:
// Pre-compile reserved names into a hash set (e.g., SplFixedArray)
$reservedNames = new \SplFixedArray(count($this->names));
foreach ($this->names as $index => $name) {
$reservedNames[$index] = strtolower($name);
}
Archived Status: The package is archived (no updates since 2018). Fork or extend if critical for your project.
Verify Configuration: Dump the loaded reserved names to debug:
$checker = $this->container->get('alister_reserved_names.check');
var_dump($checker->getReservedNames()); // Check loaded list
Cleaning Behavior: Log cleaned usernames to understand transformations:
$cleaner = $this->container->get('alister_reserved_names.cleanusername');
$original = 'Admin_123!';
$cleaned = $cleaner->clean($original);
\Log::debug("Original: {$original}, Cleaned: {$cleaned}");
Override Services: Temporarily replace services for debugging:
services:
alister_reserved_names.check:
class: Alister\ReservedNamesBundle\Services\ReservedNames
arguments:
$reservedNames: ['debug', 'test'] # Override for testing
Custom Cleaning Logic:
Extend CleanUserNames to handle domain-specific noise:
class CustomCleaner extends \Alister\ReservedNamesBundle\Services\CleanUserNames
{
protected function getNoiseCharacters(): string
{
return parent::getNoiseCharacters() . '.@'; // Add custom chars
}
}
Register as a service:
services:
alister_reserved_names.cleanusername:
class: App\Services\CustomCleaner
Validator Integration: Create a Symfony validator constraint (as suggested in the TODO):
use Symfony\Component\Validator\Constraint;
class ReservedUsername extends Constraint
{
public $message = 'This username is reserved.';
public function validatedBy(): string { return static::class; }
}
class ReservedUsernameValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
$checker = $this->container->get('alister_reserved_names.check');
if ($checker->isReserved($value)) {
$this->context->buildViolation($constraint->message)
->addViolation();
}
}
}
Use in entities:
use App\Validator\Constraints\ReservedUsername;
class User
{
#[ReservedUsername]
private string $username;
}
Database-Backed Reserved Names: Fetch reserved names dynamically from a DB:
class DatabaseReservedNames implements ReservedNamesInterface
{
public function isReserved(string $name): bool
{
return $this->entityManager->getRepository(ReservedName::class)
->exists(['lower(name) = ?1'], [strtolower($name)]);
}
}
How can I help you explore Laravel packages today?