How to Create a Bundle in Symfony

Bhavin Nakrani
Simform Engineering
4 min readOct 19, 2023

--

In this step-by-step guide, you’ll discover that creating a Bundle for a Symfony application may be easier than you think.

Create a bundle in Symfony

Bundles in Symfony are akin to plugins found in other software, but they offer even greater versatility. Symfony’s core features are realized through bundles, such as DoctrineBundle, LexikJWTAuthenticationBundle, WebpackEncoreBundle, and more.

Let’s begin by taking a specific example of how to create a Bundle.

PasswordStrengthBundle

We will create the PasswordStrengthBundle, which is introduced in Symfony 6.3 but does not exist in earlier versions. This bundle is dedicated to Symfony and is designed for handling constraints.

Basic features

This bundle encompasses a range of validations, including minimum length requirements, the inclusion of alphabetic and/or numeric characters, the usage of mixed-case characters, and the incorporation of ‘special’ characters.

Folder structure

It depends on the feature of the Bundle. In our case, it relates to the folder structure.

Folder Structure of PasswordStrengthBundle

Where should you create it?

You can either install the latest Symfony project or use your existing Symfony project.

Create the Bundle folder “PasswordStrengthBundle” and inside src folder, create a class file “SimformsolutionsPasswordStrengthBundle.php”

<?php

namespace Simformsolutions\PasswordStrengthBundle;

use Symfony\Component\HttpKernel\Bundle\AbstractBundle;

class SimformsolutionsPasswordStrengthBundle extends AbstractBundle
{
public function getPath(): string
{
return dirname(__DIR__);
}
}

Create a composer.json file in the folder PasswordStrengthBundle.

The most important is the “type”, which must be set to “symfony-bundle”

Package Type: symfony-bundle

{
"name": "simformsolutions/password-strength-bundle",
"type": "symfony-bundle",
"version": "1.0.1",
"require": {
"php": ">=8.1",
"symfony/validator": ">=6.2",
"symfony/http-kernel": ">=6.2"
},
"license": "MIT",
"autoload": {
"psr-4": {
"Simformsolutions\\PasswordStrengthBundle\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Simformsolutions\\PasswordStrengthBundle\\Tests\\": "tests/"
}
}
}

Logic

Create a Validator folder and add Validation logic.

Path: src/Validator/Constraints/PasswordStrength.php

<?php

namespace Simformsolutions\PasswordStrengthBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;

/**
* @Annotation
*
* @Target({"CLASS", "ANNOTATION"})
*/
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
class PasswordStrength extends Constraint
{
public string $tooShortMessage = 'Password must be at least {{length}} characters long.';
public string $missingLettersMessage = 'Password must include at least one letter.';
public string $requireCaseDiffMessage = 'Password must include both upper and lower case letters.';
public string $missingNumbersMessage = 'Password must include at least one number.';
public string $missingSpecialsMessage = 'Password must include at least one special character.';

public int $minLength = 5;
public bool $requireLetters = true;
public bool $requireCaseDiff = false;
public bool $requireNumbers = false;
public bool $requireSpecials = false;

public string $charset = 'UTF-8';
}

Path: src/Validator/Constraints/PasswordStrengthValidator.php

<?php

namespace Simformsolutions\PasswordStrengthBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

class PasswordStrengthValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint): void
{
if (null === $value) {
$value = '';
}

if (function_exists('grapheme_strlen') && 'UTF-8' === $constraint->charset) {
$length = grapheme_strlen($value);
} else {
$length = mb_strlen($value, $constraint->charset);
}

if ($constraint->minLength > 0 && (mb_strlen($value, $constraint->charset) < $constraint->minLength)) {
$this->context->addViolation($constraint->tooShortMessage, ['{{length}}' => $constraint->minLength]);
}

if ($constraint->requireLetters && !preg_match('/\pL/', $value)) {
$this->context->addViolation($constraint->missingLettersMessage);
}

if ($constraint->requireCaseDiff && !preg_match('/(\p{Ll}+.*\p{Lu})|(\p{Lu}+.*\p{Ll})/', $value)) {
$this->context->addViolation($constraint->requireCaseDiffMessage);
}

if ($constraint->requireNumbers && !preg_match('/\pN/', $value)) {
$this->context->addViolation($constraint->missingNumbersMessage);
}

// Cc: Control; M: Mark; P: Punctuation; S: Symbol; Z: Separator
// Not checked: L: Letter; N: Number; C{fosn}: format, private-use, surrogate, unassigned
if ($constraint->requireSpecials && !preg_match('/[\p{Cc}\pM\pP\pS\pZ]/', $value)) {
$this->context->addViolation($constraint->missingSpecialsMessage);
}
}
}

How to test it?

You can register the Bundle Folder (PasswordStrengthBundle) with your existing project by adding it to your project’s composer JSON file.

For instance, if you have the following directory structure in your repository:

...
├── apps
│ └── my-app
│ └── composer.json
├── packages
│ └── PasswordStrengthBundle
│ └── composer.json
...

Then, to add the package /packages as a dependency in your apps/my-app/composer.json file, you can use the following configuration:

{
"repositories": [
{
"type": "path",
"url": "../../packages/PasswordStrengthBundle"
}
],
"require": {
"my/package": "*"
}
}

For Versioning dependency and more options, you can read this Composer Doc.

Then run composer install command to install this bundle in your project.

How to configure it in your project?

After you’ve confirmed that it’s functioning properly, you can publish it on Packagist.

OR

You can use it locally if you don’t want to publish it publicly.

Initialize the bundle

// config/bundles.php
return [
// ...
Simformsolutions\PasswordStrengthBundle\SimformsolutionsPasswordStrengthBundle::class => ['all' => true],
];

Usage

If you are using annotations for validations, include the constraints namespace:

use Simformsolutions\PasswordStrengthBundle\Validator\Constraints as PasswordAssert;

and then add the PasswordStrength validator to the relevant entity field:

/**
* #[PasswordAssert\PasswordStrength(['minLength'=> 7, 'requireNumbers'=> true])]
*/
protected $password;

That’s it, we’re done! :)

Conclusion

The blog gave you a basic idea of how to create a bundle, verify it in local code, and use it in any project. For more information, please read this Symfony Bundle documentation.

What’s Next

The code for PasswordStrengthBundle is available on GitHub, and the community is encouraged to contribute to further enhance the capabilities of Password validation.

To stay up-to-date with the latest trends in the development ecosystem, follow Simform Engineering.

--

--