popcorn4dinner / policies
easy to use, non-opinionated library to model complex rules for validations, access control, etc
0.2.0
2018-06-26 08:24 UTC
Requires
- php: >=7.1
- esky/enum: ^1.1
Requires (Dev)
- fzaninotto/faker: ^1.5.0
- phpunit/phpunit: ~7
This package is not auto-updated.
Last update: 2025-04-13 08:48:28 UTC
README
A PHP library to easily model complex rules or policies for validations, access control, etc
installation
composer require popcorn4dinner/policies
Example: Validating a user
- define actions or events as an enum, here CREATE, REGISTER, UPDATE, DELETE and ADMIN_UPDATE
- create one policy for each of your business rules
- create a Validator to use your policies and define exceptions
creating policies
email address must have the right format
<?php namespace Popcorn4dinner\Policies\Examples\Policies; use Popcorn4dinner\Policies\AbstractPolicy; use Popcorn4dinner\Policies\Examples\User; use Popcorn4dinner\Policies\Examples\UserAction; use Popcorn4dinner\Policies\Examples\UserRepositoryInterface; class EmailFormatPolicy extends AbstractPolicy { protected const ERROR_MESSAGE = 'Invalid email address'; /** * @param MvpUser $user * @return bool * @throws PolicyValidationException */ protected function isViolatedWith(MvpUser $user, UserAction $action): bool { return $this->isInvalidEmail($user->getEmail()); } /** * @param string $email * @return bool * */ private function isInvalidEmail(string $email): bool { return filter_var($email, FILTER_VALIDATE_EMAIL) === false; } }
email address must be unique
<?php namespace Popcorn4dinner\Policies\Examples\Policies; use Popcorn4dinner\Policies\AbstractPolicy; use Popcorn4dinner\Policies\Examples\User; use Popcorn4dinner\Policies\Examples\UserAction; use Popcorn4dinner\Policies\Examples\UserRepositoryInterface; class EmailUniquenessPolicy extends AbstractPolicy { protected const ERROR_MESSAGE = 'User with email already exists.'; /** * @var UserRepositoryInterface */ private $userRepository; /** * EmailPresencePolicy constructor. * @param UserRepositoryInterface $userRepository */ public function __construct(UserRepositoryInterface $userRepository, UserAction ...$excludedActions) { parent::__construct(...$excludedActions); $this->userRepository = $userRepository; } /** * @param MvpUser $user * @return bool * @throws PolicyValidationException */ protected function isViolatedWith(MvpUser $user, UserAction $action): bool { return !$this->isUniqueEmail($user->getEmail()); } private function isUniqueEmail(string $email): bool { return $this->userRepository->findByEmail($email) === null; } }
password must be longer then 8 characters
<?php namespace Popcorn4dinner\Policies\Examples\Policies; use Popcorn4dinner\Policies\AbstractPolicy; use Popcorn4dinner\Policies\Examples\User; use Popcorn4dinner\Policies\Examples\UserAction; use Popcorn4dinner\Policies\Examples\UserRepositoryInterface; class PasswordLengthPolicy extends AbstractPolicy { protected const ERROR_MESSAGE = 'Password not long enough.'; private const MIN_PASSWORD_LENGTH = 8; /** * @param MvpUser $user * @return bool * @throws PolicyValidationException */ protected function isViolatedWith(MvpUser $user, UserAction $action): bool { return $this->isInvalidPassword($user->getPassword()); } private function isInvalidPassword(string $password): bool { return strlen($password) < static::MIN_PASSWORD_LENGTH; } }
password must be secure enough
<?php namespace Popcorn4dinner\Policies\Examples\Policies; use Popcorn4dinner\Policies\AbstractPolicy; use Popcorn4dinner\Policies\Examples\User; use Popcorn4dinner\Policies\Examples\UserAction; use Popcorn4dinner\Policies\Examples\UserRepositoryInterface; class PasswordCharactersPolicy extends AbstractPolicy { protected const ERROR_MESSAGE = 'Password not strong enough.'; private const NUMBER_IN_PASSWORD = '#[0-9]+#'; private const LOWERCASE_CHARACTERS = '#[a-z]+#'; private const UPPERCASE_CHARACTERS = '#[A-Z]+#'; /** * @param MvpUser $user * @return bool * @throws PolicyValidationException */ protected function isViolatedWith(MvpUser $user, UserAction $action): bool { return $this->isInvalidPassword($user->getPassword()); private function isInvalidPassword(string $password): bool { $includesNumber = preg_match(static::NUMBER_IN_PASSWORD, $password); $includesUpperCaseCharacters = preg_match(static::UPPERCASE_CHARACTERS, $password); $includesLowerCaseCharacters = preg_match(static::LOWERCASE_CHARACTERS, $password); return !($includesNumber && $includesUpperCaseCharacters && $includesLowerCaseCharacters); } }
creating a validator
<?php namespace Popcorn4dinner\Policies\Examples; use Popcorn4dinner\Policies\Examples\Policies\EmailFormatPolicy; use Popcorn4dinner\Policies\Examples\Policies\EmailUniquenessPolicy; use Popcorn4dinner\Policies\Examples\Policies\PasswordLengthPolicy; use Popcorn4dinner\Policies\Examples\Policies\PasswordCharactersPolicy; use Popcorn4dinner\Policies\BasicValidator; class UserValidatorFactory { /** * @param UserRepositoryInterface $userRepository * @return UserValidator */ public function create(UserRepositoryInterface $userRepository) { return new BasicValidator( new EmailFormatPolicy(), new EmailUniquenessPolicy($userRepository), new PasswordLengthPolicy(UserAction::ADMIN_UPDATE()), new PasswordStrengthPolicy(UserAction::ADMIN_UPDATE()) ); } }
use PolicyCollections to model more complex rules
class UserValidatorFactory { /** * @param UserRepositoryInterface $userRepository * @return UserValidator */ public function create(UserRepositoryInterface $userRepository) { $userPolicies = new PolicyCollection( new PasswordLengthPolicy(UserAction::ADMIN_UPDATE()); new PasswordStrengthPolicy(UserAction::ADMIN_UPDATE()) ) $universalPolicies = new PolicyCollection( new EmailFormatPolicy(), new EmailUniquenessPolicy($userRepository) ) return new BasicValidator( $universalPolicies, $standardUserPolicies ); } }