Compare commits

...

53 commits

Author SHA1 Message Date
Jérémy Munsch
583984b157
fix workflow
Some checks are pending
PHP Composer / build (push) Waiting to run
2023-06-02 12:54:18 +02:00
Jérémy Munsch
8bd0bb17e4
update build status 2023-06-02 12:53:10 +02:00
Jérémy Munsch
385552f454
remove scrore cards 2023-06-02 12:44:49 +02:00
Jérémy Munsch
b671dd3e1c
revert and fix phpunit 2023-06-02 12:38:08 +02:00
Jérémy Munsch
beb64c6470
composer .lock ignore 2023-06-02 12:37:50 +02:00
Jérémy Munsch
0277f4d0bc
test for score cards 2023-06-02 12:19:55 +02:00
Jérémy Munsch
3687d37c2f
fix score cards php version 2023-06-02 12:17:10 +02:00
Jérémy Munsch
ff3f6336db
remove composer lock 2023-06-02 12:15:42 +02:00
Jérémy Munsch
7b2f3530e9
fix scorecards 2023-06-02 12:14:30 +02:00
Jérémy Munsch
606c380e97
Bumped for php 8.2 + fix unit test and phpcs 2023-06-02 12:12:37 +02:00
Jérémy Munsch
8b25893b99
phpcs fix 2023-05-30 09:40:09 +02:00
Jérémy Munsch
71f45fa5ed
Create scorecards.yml 2022-11-06 18:53:36 +01:00
Jérémy Munsch
cb2ec1bd82
Delete composer.lock 2022-11-06 15:10:43 +01:00
Jérémy Munsch
57a94ab316
Update php.yml
Test
2022-11-06 15:07:40 +01:00
Jérémy Munsch
cc695e6a07
Fixed #2 + allow usage on php8 for cross compatibility 2022-07-28 17:18:54 +02:00
Jérémy Munsch
bbd0872389
update change log 2021-11-18 17:52:08 +01:00
Jérémy Munsch
34965dee37
Fix #1
- Strict check for enum duplicates
- Added check for empty enum definition
- Added check for duplicated enum definition
2021-11-18 17:51:58 +01:00
Jérémy Munsch
1c342113b6
update changelog 2021-09-20 22:37:21 +02:00
Jérémy Munsch
1e2e9bdecf
Reintroduce getRouteKey to handle router with model + tests 2021-09-20 22:37:12 +02:00
4dfe0eb17c
fix uncorrect licence 2021-09-13 00:31:33 +02:00
5b9806fd79
Add Examples
Update Changelog
Update Readme
2021-09-12 20:31:52 +02:00
163eeabcd1
Add make:enum console command 2021-09-12 19:21:54 +02:00
03bac5ba89
Hanle Enum not routable exception, instead of just resolvinf enum with null variables 2021-09-12 02:23:40 +02:00
fdfb6af639
Full unit test with 100% coverage 2021-09-12 02:23:40 +02:00
a7617753b1
Fix BaseEnumRule to really check if enum passed to constructor extends BaseEnum 2021-09-12 02:23:40 +02:00
19108c833e
add some comments on exceptions 2021-09-12 02:23:40 +02:00
9c91bb3659
Change package translations to use php files 2021-09-12 02:23:40 +02:00
c54d3602f3
downgrade orchestra test bench to 5.x (laravel 7)
add php7.3 requirement
2021-09-12 02:23:40 +02:00
0bb9a6f9b8
BaseEnum: remove unused herited closure case 2021-09-12 02:23:40 +02:00
7bbbe28e27
BaseEnum : correction on code comments 2021-09-12 02:23:40 +02:00
e687db37e7
CastsEnums : refactorize code
correction on code coments
2021-09-12 02:23:40 +02:00
1a5be20be2
CastsEnums : remove breaking attributes 2021-09-12 02:23:40 +02:00
5e3f825dca
EnumIsValidRule : fix int casting 2021-09-12 02:23:40 +02:00
b3f54a65c5
BaseEnumRoutable : case to int only if is really an integer 2021-09-12 02:23:40 +02:00
4cc27e6769
BaseEnumRoutable : remove unused getEnumName
add NotImplementedException for all unsued methods
2021-09-12 02:23:40 +02:00
3e654f5d3c
BaseEnum : Remove unused __call 2021-09-12 02:23:39 +02:00
bb09844610
fix cast enum phpdoc 2021-09-12 02:23:39 +02:00
5793cf591f
Type hint __get for label and value on Base Enum 2021-09-12 02:23:39 +02:00
2fb55ed30d
update phpunit config 2021-09-12 02:23:39 +02:00
d8402c3397
Fixes from code analyzer 2021-09-12 02:23:39 +02:00
402609ad07
Add code coverage and update readme badges 2021-09-12 02:23:39 +02:00
c80000e2fd
remove StyleCI 2021-09-12 02:23:39 +02:00
8348fc06c5
Update php.yml
test CI
2021-09-12 02:23:39 +02:00
a9f6e0722a
composer.lock fix 2021-09-12 02:23:39 +02:00
7b2f8ab01a
vscode settings for phpcs 2021-09-12 02:23:39 +02:00
4382d1c6fe
Refactor code and cleanup 2021-09-12 02:23:39 +02:00
416c941c8d
Fix unit test extend classes 2021-09-12 02:23:39 +02:00
d9e7d57a24
remove version from composer.json 2021-09-12 02:23:39 +02:00
4a33926706
Create php.yml
Add continuous integration
2021-09-12 02:23:39 +02:00
6f25da234c
add phpunit specific to gitignore 2021-09-12 02:23:38 +02:00
ff11dcd3d1
add composer script test 2021-09-12 02:23:38 +02:00
a6821a62fa
Fix unit tests 2021-09-12 02:23:38 +02:00
5c34cc5228
composer upgrade and phpcs enhancement 2021-09-12 02:23:38 +02:00
48 changed files with 2106 additions and 7685 deletions

43
.github/workflows/php.yml vendored Normal file
View file

@ -0,0 +1,43 @@
name: PHP Composer
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
tools: phpcs
- name: Validate composer.json and composer.lock
run: composer validate --strict
- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v2
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-
- name: Install dependencies
run: composer install --no-progress
# Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit"
# Docs: https://getcomposer.org/doc/articles/scripts.md
- name: Run test suite
run: composer run-script test

5
.gitignore vendored
View file

@ -1,2 +1,7 @@
workspace.code-workspace
vendor
.phpunit.result.cache
.phpunit.cache
.vscode/launch.json
composer.lock
build

View file

@ -1,2 +0,0 @@
php:
preset: laravel

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"phpcs.executablePath": "vendor/bin/phpcs"
}

View file

@ -2,11 +2,98 @@
All notable changes to `Enum` will be documented in this file.
## Version 0.0.1
## **Version 3.0.0**
- Changed use of php8.2 and more only For laravel 10 usage
### Initial release
## **Version 2.1.3**
## Version 0.0.2
### Fixed
- Allow usage on php 7.4 and more as php 8 for cross compatibility
- Bumped orchestra to 6 to allow unit test for all php versions
## **Version 2.1.2**
### Fixed
- Fixed check for label duplicates or missing declaration
## **Version 2.1.1**
### Fixed
- Reintroduce getRouteKey to handle router with model + tests
## **Version 2.1.0**
### Added
- Full unit tests with 100% coverage
- A laravel command to generate an Enum ``php artisan make:enum`
- examples and a full, readme doc
### Changed
- Code refactoring for most of the code
- Removed unused code parts
- Set requirements for unit test to laravel 7
- translations are in php files
### Fixed
- An enum used as in a route will now provoque a 404 code instead of generating null enum
- CheckEnumRule nows works properly and has unit tests
## **Version 2.0.0**
### Changed
- Now if you are using enums in laravel, you have to extend from `BaseEnumRoutable` instead of `BaseEnum`. This will allow for enum to be used in routes as a parameter getting them as a controller parameter with implicit binding.
- All laravel dependencies have been removed, this allows enum to be used in another project type. The project is now dependency free.
## **Version 0.1.4**
### Added
- Validation rule EnumIsValidRule, the constructor needs a BaseEnum class
eg: new EnumIsValidRule(ArcadiaMailType::class)
### Fixed
- Unit tests with 0.1.2 changes
## **Version 0.1.3**
### Added
- Validation rule EnumIsValidRule, the constructor needs a BaseEnum class
eg: new EnumIsValidRule(ArcadiaMailType::class)
### Fixed
- Unit tests with 0.1.2 changes
## **Version 0.1.2**
### Changed
- Changed toJson to output array with label and value
## **Version 0.1.1**
### Fixed
- Fixed performances issue on toValues and toLabels
## **Version 0.1.0**
### - Changed
- toArray Method now returns an array of BaseEnums
this is because php numeric string array indexes to int, this would break
origin enum values
## **Version 0.0.2**
### - Add
@ -21,33 +108,6 @@ All notable changes to `Enum` will be documented in this file.
'status' => OrderStatus::class // This is a class that extends \Kwaadpepper\Enum\BaseEnum
];
## Version 0.1.0
## **Version 0.0.1**
### - Changed
- toArray Method now returns an array of BaseEnums
this is because php numeric string array indexes to int, this would break
origin enum values
## Version 0.1.1
### Fixed
- Fixed performances issue on toValues and toLabels
## Version 0.1.2
### Changed
- Changed toJson to output array with label and value
## Version 0.1.3
### Added
- Validation rule EnumIsValidRule, the constructor needs a BaseEnum class
eg: new EnumIsValidRule(ArcadiaMailType::class)
### Fixed
- Unit tests with 0.1.2 changes
### Initial release

View file

@ -1,8 +1,8 @@
{
"name": "kwaadpepper/enum",
"type": "library",
"description": "Enums to use for any project",
"license": "MIT",
"version": "2.0.0",
"authors": [
{
"name": "Jérémy Munsch",
@ -15,10 +15,16 @@
"Laravel",
"Enum"
],
"require": {
"php": ">=8.2"
},
"require-dev": {
"phpunit/phpunit": "~9.0",
"squizlabs/php_codesniffer": "^3.5",
"orchestra/testbench": "^6.13"
"phpunit/phpunit": "^9",
"squizlabs/php_codesniffer": "^3",
"orchestra/testbench": "^8",
"slevomat/coding-standard": "^7",
"phpunit/php-code-coverage": "^9",
"nunomaduro/collision": "^6.4"
},
"autoload": {
"psr-4": {
@ -36,5 +42,16 @@
"Kwaadpepper\\Enum\\EnumServiceProvider"
]
}
},
"scripts": {
"post-autoload-dump": [
"@php ./vendor/bin/testbench package:discover --ansi"
],
"test": "vendor/bin/phpunit"
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}

7187
composer.lock generated

File diff suppressed because it is too large Load diff

16
examples/BasicEnum.php Normal file
View file

@ -0,0 +1,16 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
*
* @method static self child()
* @method static self adult()
* @method static self senior()
*/
final class BasicEnum extends BaseEnumRoutable
{
//
}

66
examples/Days.php Normal file
View file

@ -0,0 +1,66 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* This defines a day type with all
* seven days of the week, include 'none'
* to allow setting an 'undefined' value.
*
* @method static self none() This is used to set 'null' day
* @method static self mon() The first day of the week
* @method static self tue() The second day of the week
* @method static self wed() The third day of the week
* @method static self thu() The fourth day of the week
* @method static self fri() The fifth day of the week
* @method static self sat() The sixth day of the week
* @method static self sun() The last day of the week
*/
final class Days extends BaseEnumRoutable
{
protected static function values(): array
{
return [
'none' => 1,
'mon' => 1 << 1,
'tue' => 1 << 2,
'wed' => 1 << 3,
'thu' => 1 << 4,
'fri' => 1 << 5,
'sat' => 1 << 6,
'sun' => 1 << 7,
];
}
protected static function labels(): array
{
return [
'none' => 'None',
'mon' => 'Monday',
'tue' => 'Tuesday',
'wed' => 'Wednesday',
'thu' => 'Thursday',
'fri' => 'Friday',
'sat' => 'Saturday',
'sun' => 'Sunday'
];
}
public function __toString(): string
{
return (string)$this->label;
}
/**
* Checks if days contain this day
*
* @param int $days
* @return boolean
*/
public function has(int $days)
{
return (bool)($this->value & $days);
}
}

View file

@ -1,4 +1,4 @@
MIT License (!! includes CC BY-NC 3.0 parts please read Readme.md)
MIT License
Copyright (c) 2021 Jérémy Munsch

127
phpcs.xml
View file

@ -1,11 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="RealWorld" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">
<arg name="colors"/>
<arg name="extensions" value="php"/>
<arg name="colors" />
<arg name="extensions" value="php" />
<file>src/</file>
<file>tests/</file>
<rule ref="PSR1"/>
<rule ref="PSR2"/>
<rule ref="PSR12"/>
<rule ref="PSR2"/>
<rule ref="PSR1" />
<rule ref="PSR12" />
<rule ref="Generic.Classes.DuplicateClassName"/>
<rule ref="Generic.CodeAnalysis.EmptyStatement"/>
<rule ref="Generic.CodeAnalysis.ForLoopShouldBeWhileLoop"/>
<rule ref="Generic.CodeAnalysis.ForLoopWithTestFunctionCall"/>
<rule ref="Generic.CodeAnalysis.JumbledIncrementer"/>
<rule ref="Generic.CodeAnalysis.UnconditionalIfStatement"/>
<rule ref="Generic.CodeAnalysis.UnnecessaryFinalModifier"/>
<rule ref="Generic.CodeAnalysis.UnusedFunctionParameter"/>
<rule ref="Generic.CodeAnalysis.UselessOverridingMethod"/>
<rule ref="Generic.Commenting.DocComment.SpacingBeforeTags"/>
<rule ref="Generic.Commenting.Todo"/>
<rule ref="Generic.Commenting.Fixme"/>
<rule ref="Generic.ControlStructures.InlineControlStructure"/>
<rule ref="Generic.Files.ByteOrderMark"/>
<rule ref="Generic.Files.LineEndings"/>
<rule ref="Generic.Files.LineLength"/>
<rule ref="Generic.Formatting.DisallowMultipleStatements"/>
<rule ref="Generic.Formatting.MultipleStatementAlignment"/>
<rule ref="Generic.Formatting.NoSpaceAfterCast"/>
<rule ref="Generic.Functions.CallTimePassByReference"/>
<rule ref="Generic.Functions.FunctionCallArgumentSpacing"/>
<rule ref="Generic.Metrics.CyclomaticComplexity"/>
<rule ref="Generic.Metrics.NestingLevel"/>
<rule ref="Generic.NamingConventions.ConstructorName"/>
<rule ref="Generic.NamingConventions.UpperCaseConstantName"/>
<rule ref="Generic.NamingConventions.CamelCapsFunctionName"/>
<rule ref="Generic.PHP.DeprecatedFunctions"/>
<rule ref="Generic.PHP.DisallowShortOpenTag"/>
<rule ref="Generic.PHP.ForbiddenFunctions"/>
<rule ref="Generic.PHP.NoSilencedErrors"/>
<rule ref="Generic.Strings.UnnecessaryStringConcat"/>
<rule ref="Generic.WhiteSpace.DisallowTabIndent"/>
<rule ref="Generic.WhiteSpace.ScopeIndent"/>
<rule ref="MySource.PHP.EvalObjectFactory"/>
<rule ref="MySource.PHP.GetRequestData"/>
<rule ref="MySource.PHP.AjaxNullComparison"/>
<rule ref="PEAR.Classes.ClassDeclaration"/>
<rule ref="PEAR.Commenting.InlineComment"/>
<rule ref="PSR1.Classes.ClassDeclaration"/>
<rule ref="PSR1.Files.SideEffects"/>
<rule ref="PSR2.Classes.ClassDeclaration"/>
<rule ref="PSR2.Classes.PropertyDeclaration"/>
<rule ref="PSR2.ControlStructures.ElseIfDeclaration"/>
<rule ref="PSR2.ControlStructures.SwitchDeclaration"/>
<rule ref="PSR2.Files.EndFileNewline"/>
<rule ref="PSR2.Methods.MethodDeclaration"/>
<rule ref="PSR2.Namespaces.NamespaceDeclaration"/>
<rule ref="PSR2.Namespaces.UseDeclaration"/>
<rule ref="Squiz.PHP.CommentedOutCode"/>
<rule ref="Squiz.PHP.DisallowMultipleAssignments">
<exclude name="Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure" />
</rule>
<rule ref="Squiz.PHP.DisallowSizeFunctionsInLoops"/>
<rule ref="Squiz.PHP.DiscouragedFunctions">
<properties>
<property name="error" value="true"/>
</properties>
</rule>
<rule ref="Squiz.PHP.Eval"/>
<rule ref="Squiz.PHP.GlobalKeyword"/>
<rule ref="Squiz.PHP.Heredoc"/>
<rule ref="Squiz.PHP.InnerFunctions"/>
<rule ref="Squiz.PHP.LowercasePHPFunctions"/>
<rule ref="Squiz.PHP.NonExecutableCode"/>
<rule ref="Squiz.Scope.MemberVarScope"/>
<rule ref="Squiz.Scope.MethodScope"/>
<rule ref="Squiz.Scope.StaticThisUsage"/>
<rule ref="Squiz.WhiteSpace.LanguageConstructSpacing"/>
<rule ref="Squiz.WhiteSpace.LogicalOperatorSpacing"/>
<rule ref="Squiz.WhiteSpace.MemberVarSpacing">
<properties>
<property name="spacingBeforeFirst " value="false"/>
</properties>
</rule>
<rule ref="Squiz.WhiteSpace.OperatorSpacing">
<properties>
<property name="ignoreNewlines" value="true"/>
</properties>
</rule>
<rule ref="Squiz.WhiteSpace.ObjectOperatorSpacing">
<properties>
<property name="ignoreNewlines" value="true"/>
</properties>
</rule>
<rule ref="Squiz.WhiteSpace.ScopeKeywordSpacing"/>
<rule ref="Squiz.WhiteSpace.SemicolonSpacing"/>
<rule ref="Squiz.Commenting.BlockComment">
<exclude name="Squiz.Commenting.BlockComment.WrongStart" />
</rule>
<rule ref="Squiz.Commenting.ClassComment">
<exclude name="Squiz.Commenting.ClassComment.Missing" />
<exclude name="Squiz.Commenting.ClassComment.TagNotAllowed" />
</rule>
<rule ref="Squiz.Commenting.DocCommentAlignment"/>
<rule ref="Squiz.Commenting.EmptyCatchComment"/>
<rule ref="Squiz.Commenting.FunctionComment">
<exclude name="Squiz.Commenting.FunctionComment.MissingParamComment" />
</rule>
<rule ref="Squiz.Commenting.FunctionCommentThrowTag"/>
<rule ref="Squiz.Commenting.InlineComment">
<exclude name="Squiz.Commenting.InlineComment.DocBlock" />
</rule>
<rule ref="Squiz.Commenting.LongConditionClosingComment">
<exclude name="Squiz.Commenting.LongConditionClosingComment.SpacingBefore" />
</rule>
<rule ref="Squiz.Commenting.PostStatementComment"/>
<rule ref="Squiz.Commenting.VariableComment"/>
<rule ref="SlevomatCodingStandard.Namespaces.UnusedUses.UnusedUse" />
<!-- For Laravel migrations -->
<rule ref="PSR1.Classes.ClassDeclaration.MissingNamespace">
<exclude-pattern>*/database/migrations/*</exclude-pattern>
</rule>
</ruleset>

View file

@ -1,13 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/autoload.php" backupGlobals="false" backupStaticAttributes="false" colors="true" verbose="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<coverage>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
bootstrap="vendor/autoload.php"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
verbose="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<coverage cacheDirectory=".phpunit.cache/code-coverage" processUncoveredFiles="true">
<include>
<directory>src/</directory>
<directory suffix=".php">src/</directory>
</include>
<exclude>
<file>src/EnumServiceProvider.php</file>
</exclude>
<report>
<html outputDirectory="build/coverage"/>
<text outputFile="build/coverage.txt"/>
<clover outputFile="build/coverage.xml" />
</report>
</coverage>
<testsuites>
<testsuite name="Package">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
<php>
<env name="APP_ENV" value="testing"/>
<env name="DB_CONNECTION" value="testing"/>
</php>
</phpunit>

View file

@ -1,11 +1,13 @@
# Enum
![GitHub](https://img.shields.io/github/license/Kwaadpepper/enum?style=flat-square)
[![Latest Version on Packagist][ico-version]][link-packagist]
[![Total Downloads][ico-downloads]][link-downloads]
[![Build Status][ico-travis]][link-travis]
[![StyleCI][ico-styleci]][link-styleci]
![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/Kwaadpepper/enum/php.yml?branch=master?style=flat-square)
This is where your description should go. Take a look at [contributing.md](contributing.md) to see a to do list.
This package goal is to provide a complete solution to lack a long time php weakness : it do not have any `enum` support.
This will change with [**php 8 enums proposition**](https://php.watch/versions/8.1/enums). In the mean time we still a support for this allowing us to generate generic and extendable applications (instead of have singletons sql tables..).
You will be able to use enums in any project using a simple definition, take a look at example [Basic](examples/BasicEnum.php) or a [more sophisticated Example](examples/Days.php)
## Installation
@ -15,26 +17,110 @@ Via Composer
$ composer require kwaadpepper/enum
```
---
## Usage
**All enum have two properties : `label` and `value`, only value has to be unique. Each enum can have multiple options all written on class comment as static methods, these have to be unique !**
- On Any project, take a look on [examples](examples) to create an enum class.
1. **Invoke an enum value**
``` php
Days::mon()
```
2. **Compare enums**
``` php
Days::mon()->equals(Days::tue()) // false
Days::mon()->equals(Days::mon()) // true
Days::mon()->value === Days::tue()->value // false
Days::mon()->value === Days::mon()->value // true
```
3. **Print an enum**
``` php
echo Days::mon(); // 2
echo Days::tue(); // 4
echo Days::mon()->label; // Monday
```
As you can see enum implements the __toString method which you can override to display the label instead of the value.
This default behavior is set like this for a better behavior in laravel.
4. **Serialise an enum**
``` php
echo json_encode(Days::mon()); // {"label":"Monday","value":2}
```
Enums implement the JsonSerializable interface
- On a Laravel project you can use enums in multiple ways
1. **As a property**
``` php
// Add theses to your model
use CastsEnums;
protected $enumCasts = [
'day' => Days::class
];
```
This will allow you model to store an enum in database using its value, and then cast the property to an enum when accessing it
2. **As a route parameter**
Define a route like the following
``` php
Route::get('/days/{day}', function (Days $day) {
return response()->json([$day]);
})->middleware('bindings');
// OR
Route::get('/days/{day}', [DayController::class, 'getDay']);
```
Then on your controller for the `DayController` example
``` php
public function getDay(Request $request, Day $day) {
return response()->json([$day]);
}
```
3. **As a request parameter using validation**
Take a look at [this request example](tests/Requests/PassDayRequest.php).
``` php
new EnumIsValidRule(Days::class)
```
It use EnumIsValidRule to validate the parameter as being a valid enum value.
**Note that you may still have to cast your parameter to int if your enum value is an int, as enum check is strict**
4. **As a model primary Key**
Take a look at [unit test](tests/BaseEnumRoutableTest.php) using [the Report enum class from tests](tests/Models/Report.php)
---
## Change log
Please see the [changelog](changelog.md) for more information on what has changed recently.
---
## Testing
``` bash
$ composer test
```
---
## Contributing
Please see [contributing.md](contributing.md) for details and a todolist.
---
## Security
If you discover any security related issues, please email github@jeremydev.ovh instead of using the issue tracker.
---
## Credits
- [Jérémy Munsch][link-author]

View file

@ -1,4 +0,0 @@
{
"enum::enum.isNotValid": "The field :attribute is not valid, it has to be an integer or a string.",
"enum::enum.notInList": "The field :attribute has to be one of the followings: :values"
}

View file

@ -0,0 +1,5 @@
<?php
return [
'notInList' => 'The field :attribute has to be one of the followings: :values'
];

View file

@ -1,4 +0,0 @@
{
"enum::enum.isNotValid": "Le champ :attribute n'est pas valide, il doit être un entier ou une chaine.",
"enum::enum.notInList": "Le champ :attribute doit être une des valeurs suivantes: :values"
}

View file

@ -0,0 +1,5 @@
<?php
return [
'notInList' => 'Le champ :attribute doit être une des valeurs suivantes: :values'
];

View file

@ -0,0 +1,16 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* This is an enumeration class,
* the following methods are its possible values
*
* ##VALUES
*/
class TemplateEnum extends BaseEnumRoutable
{
// ##OVERRIDE
}

View file

@ -2,32 +2,105 @@
namespace Kwaadpepper\Enum;
use BadMethodCallException;
use Closure;
use JsonSerializable;
use Kwaadpepper\Enum\Exceptions\DuplicateDefinitionException;
use Kwaadpepper\Enum\Exceptions\DuplicateLabelsException;
use Kwaadpepper\Enum\Exceptions\DuplicateValuesException;
use Kwaadpepper\Enum\Exceptions\EmptyDefinitionException;
use Kwaadpepper\Enum\Exceptions\EnumNotRoutableException;
use Kwaadpepper\Enum\Exceptions\UnknownEnumProperty;
use ReflectionClass;
use TypeError;
abstract class BaseEnum implements JsonSerializable
/**
* This Base Enum is the class which should
* be extended to create an enumeration
*
* @property-read int|string $value The enum value, can be string or int
* @property-read string $label The enum label, is a string
*/
abstract class BaseEnum implements \JsonSerializable
{
/**
* @var string|int
* @readonly
* @var string|integer
*/
protected $value;
/** @readonly */
/**
* @var string
*/
protected $label;
/** @var array<string, array<string, App\Enums\BaseEnumDefinition>> */
/** @var array<string, array<string, \Kwaadpepper\Enum\BaseEnumDefinition>> */
private static $definitionCache = [];
// -- ENUM MAGIC METHODS --
/**
* @return array[BaseEnum]
* @param integer|string $value
* @param string $label
* @throws \TypeError If anything else than string or int
* is used for value.
* @throws \Kwaadpepper\Enum\Exceptions\EnumNotRoutableException If provided value are null.
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
*/
public function __construct($value = null, string $label = null)
{
// phpcs:ignore
/**
* This cannot be allowed, happens with laravel resolver
* when using BaseEnum instead of BaseEnumRoutable
*/
if ($label === null and !is_subclass_of(static::class, BaseEnumRoutable::class)) {
throw new EnumNotRoutableException();
}
$this->label = $label;
$this->value = $value;
}
/**
* @param string $attribute
* @return integer|string
* @throws \Kwaadpepper\Enum\Exceptions\UnknownEnumProperty As it could not be found.
*/
public function __get(string $attribute)
{
if ($attribute === 'value') {
return $this->value;
}
if ($attribute === 'label') {
return $this->label;
}
throw new UnknownEnumProperty($attribute);
}
/**
* @param string $name
* @param array $arguments
* @return static
* @throws \TypeError If anything else than string or int is used.
* @throws \BadMethodCallException If a matching definition cannot be found.
* @throws \Kwaadpepper\Enum\Exceptions\EnumNotRoutableException If provided value are null.
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
*/
public static function __callStatic(string $name, array $arguments)
{
// phpcs:enable
return static::make($name);
}
/**
* Output the value as a string
*
* @return string
*/
public function __toString(): string
{
return (string)$this->value;
}
// -- ENUM EXPORTATION METHODS --
/** @return array<BaseEnum> */
public static function toArray(): array
{
$array = [];
@ -37,9 +110,7 @@ abstract class BaseEnum implements JsonSerializable
return $array;
}
/**
* @return string[]|int[]
*/
/** @return array<int|string> */
public static function toValues(): array
{
$array = [];
@ -49,9 +120,7 @@ abstract class BaseEnum implements JsonSerializable
return $array;
}
/**
* @return string[]
*/
/** @return array<string> */
public static function toLabels(): array
{
$array = [];
@ -62,11 +131,47 @@ abstract class BaseEnum implements JsonSerializable
}
/**
* Get the enum definition
* Json serialize implementation.
*
* @return mixed|null
* @return array
*/
public function getDefinition()
public function jsonSerialize(): array
{
return [
'label' => $this->label,
'value' => $this->value
];
}
// -- DEFINITON METHODS --
/**
* Get all values
*
* @return array<string, string|int> | Closure(string):(int|string)
*/
protected static function values()
{
return [];
}
/**
* Get all labels
*
* @return array<string, string> | Closure(string):string
*/
protected static function labels()
{
return [];
}
/**
* Get the enum definition name
*
* @return string|null
*/
public function getDefinition(): ?string
{
$className = static::class;
foreach (self::$definitionCache[$className] as $definition => $enum) {
@ -78,204 +183,54 @@ abstract class BaseEnum implements JsonSerializable
}
/**
* @param string|int $value
* @throws TypeError
* @throws BadMethodCallException
* Parse a value to create its corresponding enumeration value.
*
* @param string|integer $value
* @return static
* @throws TypeError If anything else than string or int is used.
* @throws \BadMethodCallException If a matching definition cannot be found.
* @throws \Kwaadpepper\Enum\Exceptions\EnumNotRoutableException If provided value are null.
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
*/
public static function make($value): BaseEnum
{
if (!(is_string($value) || is_int($value))) {
$enumClass = static::class;
throw new TypeError("Only string and integer are allowed values for enum {$enumClass}.");
}
// phpcs:enable
self::assertValidValue($value);
$definition = static::findDefinition($value);
if ($definition === null) {
$enumClass = static::class;
throw new BadMethodCallException(
"There's no value {$value} defined for enum {$enumClass}," .
"consider adding it in the docblock definition."
throw new \BadMethodCallException(
"There's no value $value defined for enum $enumClass, consider adding it in the docblock definition."
);
}
$obj = new static();
$obj->value = $definition->value;
$obj->label = $definition->label;
$obj = new static(
$definition->value,
$definition->label
);
return $obj;
}
/**
* @param string $name
* Test if an enum values is equal to another
*
* @return int|string
*
* @throws UnknownEnumProperty
* @param \Kwaadpepper\Enum\BaseEnum $other
* @return boolean
*/
public function __get(string $name)
public function equals(BaseEnum $other): bool
{
if ($name === 'label') {
return $this->label;
if (
get_class($this) === get_class($other)
&& $this->value === $other->value
) {
return true;
}
if ($name === 'value') {
return $this->value;
}
throw new UnknownEnumProperty($name);
}
/**
* @param string $name
* @param array $arguments
*
* @return static
*/
public static function __callStatic(string $name, array $arguments)
{
return static::make($name);
}
/**
* @param string $name
* @param array $arguments
*
* @return bool|mixed
*
* @throws UnknownEnumMethod
*/
public function __call(string $name, array $arguments)
{
if (strpos($name, 'is') === 0) {
$other = static::make(substr($name, 2));
return $this->equals($other);
}
return self::__callStatic($name, $arguments);
}
public function equals(BaseEnum ...$others): bool
{
foreach ($others as $other) {
// @phpcs:disable PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace
if (
get_class($this) === get_class($other)
&& $this->value === $other->value
) {
return true;
}
}
return false;
}
/**
* @return string[]|int[]|Closure
* @return array<string, string|int> | Closure(string):(int|string)
*/
protected static function values()
{
return [];
}
/**
* @return string[]|Closure
* @return array<string, string> | Closure(string):string
*/
protected static function labels()
{
return [];
}
/**
* @param string|int $input
*
* @return App\Enums\BaseEnumDefinition|null
*/
private static function findDefinition($input): ?BaseEnumDefinition
{
foreach (static::resolveDefinition() as $definition) {
if ($definition->equals($input)) {
return $definition;
}
}
return null;
}
/**
* @return App\Enums\BaseEnumDefinition[]
*/
private static function resolveDefinition(): array
{
$className = static::class;
if (self::$definitionCache[$className] ?? null) {
return self::$definitionCache[$className];
}
$reflectionClass = new ReflectionClass($className);
$docComment = $reflectionClass->getDocComment();
preg_match_all('/@method\s+static\s+self\s+([\w_]+)\(\)/', $docComment, $matches);
$definition = [];
$valueMap = static::values();
if ($valueMap instanceof Closure) {
$valueMap = array_map($valueMap, array_combine($matches[1], $matches[1]));
}
$labelMap = static::labels();
if ($labelMap instanceof Closure) {
$labelMap = array_map($labelMap, array_combine($matches[1], $matches[1]));
}
foreach ($matches[1] as $methodName) {
$value = $valueMap[$methodName] = $valueMap[$methodName] ?? $methodName;
$label = $labelMap[$methodName] = $labelMap[$methodName] ?? $methodName;
$definition[$methodName] = new BaseEnumDefinition($methodName, $value, $label);
}
if (self::arrayHasDuplicates($valueMap)) {
throw new DuplicateValuesException();
}
if (self::arrayHasDuplicates($labelMap)) {
throw new DuplicateLabelsException();
}
return self::$definitionCache[$className] = self::$definitionCache[$className] ?? $definition;
}
private static function arrayHasDuplicates(array $array): bool
{
return count($array) > count(array_unique($array));
}
/**
* @return int|string
*/
public function jsonSerialize()
{
return [
'label' => $this->label,
'value' => $this->value
];
}
public function __toString(): string
{
return (string) $this->value;
}
/**
* Get the Enum name from class name
*
@ -286,4 +241,108 @@ abstract class BaseEnum implements JsonSerializable
$cls = explode('\\', static::class);
return array_pop($cls);
}
// -- PRIVATE METHODS --
/**
* @param string|integer $input
* @return \Kwaadpepper\Enum\BaseEnumDefinition|null
*/
private static function findDefinition($input): ?BaseEnumDefinition
{
foreach (static::resolveDefinition() as $definition) {
if ($definition->equals($input)) {
return $definition;
}
}
return null;
}
/**
* Resolve enum defintion
*
* @return \Kwaadpepper\Enum\BaseEnumDefinition[]
* @throws \Kwaadpepper\Enum\Exceptions\EmptyDefinitionException If there is any.
* @throws \Kwaadpepper\Enum\Exceptions\DuplicateValuesException If there is any in labels map.
* @throws \Kwaadpepper\Enum\Exceptions\DuplicateValuesException If there is any in values map.
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
*/
private static function resolveDefinition(): array
{
// phpcs:enable
if (isset(self::$definitionCache[static::class])) {
return self::$definitionCache[static::class];
}
$reflectionClass = new \ReflectionClass(static::class);
$docComment = $reflectionClass->getDocComment();
preg_match_all('/@method\s+static\s+self\s+([\w_]+)\(\)/', $docComment, $matches);
$definition = [];
$valueMap = static::values();
$labelMap = static::labels();
if (!count($matches[1])) {
throw new EmptyDefinitionException('You are missing defining enum definition in comments.');
}
if (self::arrayHasDuplicates($matches[1])) {
throw new DuplicateDefinitionException('You have duplicate values in comments enum definition.');
}
foreach ($matches[1] as $methodName) {
$valueMap[$methodName] = $valueMap[$methodName] ?? $methodName;
$labelMap[$methodName] = $labelMap[$methodName] ?? $methodName;
$definition[$methodName] = new BaseEnumDefinition(
$methodName,
$valueMap[$methodName],
$labelMap[$methodName]
);
}
if (self::arrayHasDuplicates($valueMap)) {
throw new DuplicateValuesException('You have duplicates values in enum values method.');
}
if (self::arrayHasDuplicates($labelMap)) {
throw new DuplicateLabelsException('You have duplicates values in enum labels method.');
}
self::$definitionCache[static::class] = $definition;
return self::$definitionCache[static::class];
}
/**
* Check if an array has duplicated values
*
* @param array $array
* @return boolean True if it has duplicated value.
*/
private static function arrayHasDuplicates(array $array): bool
{
$uniqueVals = [];
foreach ($array as $value) {
if (!in_array($value, $uniqueVals, true)) {
$uniqueVals[] = $value;
}
}
return count($array) > count($uniqueVals);
}
/**
* Assert value is valid
*
* @param mixed $value
* @return void
* @throws \TypeError If anything else than string or int is used.
*/
private static function assertValidValue(mixed $value): void
{
if (!(is_string($value) || is_int($value))) {
$enumClass = static::class;
throw new \TypeError("Only string and integer are allowed values for enum $enumClass.");
}
}
}

View file

@ -2,9 +2,13 @@
namespace Kwaadpepper\Enum;
/**
* The definition of an enumeration
* This defines it's value, label and method name
*/
class BaseEnumDefinition
{
/** @var string|int */
/** @var string|integer */
public $value;
/** @var string */
@ -14,32 +18,29 @@ class BaseEnumDefinition
private $methodName;
/**
* @param string $methodName
* @param string|int $value
* @param string $label
* @param string $methodName
* @param string|integer $value
* @param string $label
*/
public function __construct(string $methodName, $value, string $label)
{
$this->methodName = strtolower($methodName);
$this->value = $value;
$this->label = $label;
$this->value = $value;
$this->label = $label;
}
/**
* @param string|int $input
*
* @return bool
* @param string|integer $input
* @return boolean
*/
public function equals($input): bool
{
if ($this->value === $input) {
return true;
}
if (is_string($input) && $this->methodName === strtolower($input)) {
if (is_string($input) and $this->methodName === strtolower($input)) {
return true;
}
return false;
}
}

View file

@ -4,8 +4,16 @@ namespace Kwaadpepper\Enum;
use BadMethodCallException;
use Illuminate\Contracts\Routing\UrlRoutable;
use Kwaadpepper\Enum\Exceptions\EnumNotRoutableException;
use Kwaadpepper\Enum\Exceptions\NotImplementedException;
use TypeError;
/**
* This Base Enum will be routable with laravel.
* So you can use it with service container passing an
* enumeration as any route parameter directly as
* a controller parameter.
*/
abstract class BaseEnumRoutable extends BaseEnum implements UrlRoutable
{
/**
@ -21,58 +29,67 @@ abstract class BaseEnumRoutable extends BaseEnum implements UrlRoutable
/**
* Get the route key for the model.
*
* @phpcs:disable Squiz.Commenting.FunctionComment.InvalidNoReturn
*
* @return string
* @throws \Kwaadpepper\Enum\Exceptions\NotImplementedException GetRouteKeyName not implemented,
* should not be used.
*/
public function getRouteKeyName()
{
throw new NotImplementedException('getRouteKeyName not implemented, should not be used');
// phpcs:enable
// @codeCoverageIgnoreStart
throw new NotImplementedException('GetRouteKeyName not implemented, should not be used.');
// @codeCoverageIgnoreEnd
}
/**
* Retrieve the model for a bound value.
*
* @param mixed $value
* @param string|null $field
* @return \Illuminate\Database\Eloquent\Model|null
* @param mixed $value
* @param string|null $field
* @return \Kwaadpepper\Enum\BaseEnum|null
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
*/
public function resolveRouteBinding($value, $field = null)
public function resolveRouteBinding(mixed $value, $field = null)
{
// try to cast numeric value first
// phpcs:enable
// Try to cast numeric value first.
try {
return static::make(is_numeric($value) ? intval($value) : $value);
} catch (BadMethodCallException $e) {
// Try cast to int.
$iValue = (is_numeric($value) and floatval(intval($value)) === floatval($value)) ?
(int)$value : $value;
return static::make($iValue);
} catch (BadMethodCallException | EnumNotRoutableException | TypeError $e) {
// Try string value after that.
try {
return static::make($value);
} catch (BadMethodCallException | EnumNotRoutableException | TypeError $e) {
// Could not find a suitable value.
return null;
}
}
// try string value after that
try {
return static::make($value);
} catch (BadMethodCallException $e) {
}
// could not find a suitable value
return null;
}
/**
* Retrieve the child model for a bound value.
*
* @param string $childType
* @param mixed $value
* @param string|null $field
* @phpcs:disable Squiz.Commenting.FunctionComment.InvalidNoReturn
*
* @param string $childType
* @param mixed $value
* @param string|null $field
* @return \Illuminate\Database\Eloquent\Model|null
* @throws NotImplementedException
* @throws \Kwaadpepper\Enum\Exceptions\NotImplementedException ResolveChildRouteBinding not implemented,
* should not be used.
* @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
*/
public function resolveChildRouteBinding($childType, $value, $field)
{
throw new NotImplementedException('resolveChildRouteBinding not implemented, should not be used');
}
/**
* Get the Enum name from class name
*
* @return string
*/
public static function getEnumName()
{
$cls = explode('\\', static::class);
return array_pop($cls);
// phpcs:enable
// @codeCoverageIgnoreStart
throw new NotImplementedException('ResolveChildRouteBinding not implemented, should not be used.');
// @codeCoverageIgnoreEnd
}
}

201
src/Console/CreateEnum.php Normal file
View file

@ -0,0 +1,201 @@
<?php
namespace Kwaadpepper\Enum\Console;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
class CreateEnum extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'make:enum';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new enum class';
/**
* Handle the command
*
* @return integer|void
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
*/
public function handle()
{
// phpcs:enable
$validate = false;
$info = [
'name' => false,
'definition' => [],
'labels' => [],
'values' => []
];
$this->info('Enumeration Generator.');
do {
do {
$name = ucfirst($this->ask('Which name should have the enum class ? (use PascalCase, ex: UserRole)'));
if (\strlen($name) <= 1 or \preg_match('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/', $name) !== 1) {
$this->question('You shall provide a valid class name.');
if (!$this->confirm('Do you want to continue ?')) {
return -1;
}
}
if (File::exists(\app_path("Enums/$name.php"))) {
$this->question("$name.php already exists.");
if (!$this->confirm('Do you want to continue ?')) {
return -1;
}
} else {
$info['name'] = $name;
}
} while ($info['name'] === false);
do {
// phpcs:ignore Generic.Files.LineLength.TooLong
$values = \trim($this->ask('Write down definitions separated with pipes (|), ex: admin|editor|user|guest'));
$info['definition'] = (\strlen($values) > 1 and \strpos($values, '|') !== 0) ?
explode('|', $values) : [];
if (empty($info['definition'])) {
$this->question('You have to provide a definition for your enum.');
if (!$this->confirm('Do you want to continue ?')) {
return -1;
}
}
} while (empty($info['definition']));
if ($this->confirm('Do you want to add custom values to your enum ?')) {
// phpcs:ignore Generic.Files.LineLength.TooLong
$values = $this->ask('Write down your values separated with pipes (|), ex: 0|1|2|3, or \'value1\'|\'value2\'|\'value3\'');
$info['values'] = explode('|', $values);
}
if ($this->confirm('Do you want to add custom labels to your enum ?')) {
// phpcs:ignore Generic.Files.LineLength.TooLong
$values = $this->ask('Write down your labels separated with pipes (|), ex: When off|When on|When blinking');
$info['labels'] = explode('|', str_replace('\'', '\\\'', $values));
}
$this->info('You have selected the followings :');
$print = [];
foreach ($info['definition'] as $k => $definition) {
$print[] = [
$definition => [
'label' => $info['labels'][$k] ?? $definition,
'value' => $info['values'][$k] ?? $definition,
]
];
}
$this->info(\json_encode($print, \JSON_PRETTY_PRINT | \JSON_NUMERIC_CHECK));
if ($this->confirm('Do you want to use these parameters ?', true)) {
$validate = true;
} elseif (!$this->confirm('Do you want to continue ?')) {
return -1;
}
} while (!$validate);
if (!File::exists(\app_path('Enums'))) {
$this->info('Creating Enums directory');
File::makeDirectory(\app_path('Enums'));
}
$this->info('Generating enum');
$templateEnum = File::get(__DIR__ . '/../../resources/templates/TemplateEnum.php');
$this->replaceClassName($templateEnum, $info);
$this->replaceDefinition($templateEnum, $info);
$this->insertLabelsAndValues($templateEnum, $info);
File::put(\app_path("Enums/$name.php"), $templateEnum);
$this->info(\sprintf('File generated %s', \app_path("Enums/$name.php")));
}
/**
* Replace the class name
*
* @param string $templateEnum
* @param array $info
* @return void
*/
private function replaceClassName(string &$templateEnum, array $info): void
{
$targetDefinition = "class TemplateEnum";
$replaceDefinition = "class {$info['name']}";
$templateEnum = str_replace($targetDefinition, $replaceDefinition, $templateEnum);
}
/**
* Replace definition
*
* @param string $templateEnum
* @param array $info
* @return void
*/
private function replaceDefinition(string &$templateEnum, array $info): void
{
$targetDefinition = " * ##VALUES\n";
$replaceDefinition = '';
foreach ($info['definition'] as $definition) {
$replaceDefinition .= " * @method static self $definition()\n";
}
$replaceDefinition .= " *\n";
$templateEnum = str_replace($targetDefinition, $replaceDefinition, $templateEnum);
}
/**
* Insert lables and values
*
* @param string $templateEnum
* @param array $info
* @return void
*/
private function insertLabelsAndValues(string &$templateEnum, array $info): void
{
$targetDefinition = " // ##OVERRIDE\n";
$replaceDefinition = '';
$templateValues = 'public static function values()';
$templateLabels = 'public static function labels()';
if (count($info['values'])) {
$replaceDefinition = " $templateValues\n {\n return [\n";
foreach ($info['definition'] as $k => $definition) {
$replaceDefinition .= sprintf(
" '%s' => %s,\n",
$definition,
$info['values'][$k] ?? $definition
);
}
$replaceDefinition = rtrim($replaceDefinition, ',');
$replaceDefinition .= " ];\n }\n";
}
if (count($info['labels'])) {
$replaceDefinition .= "\n $templateLabels\n {\n return [\n";
foreach ($info['definition'] as $k => $definition) {
$replaceDefinition .= sprintf(
" '%s' => '%s',\n",
$definition,
$info['labels'][$k] ?? $definition
);
}
$replaceDefinition = rtrim($replaceDefinition, ',');
$replaceDefinition .= " ];\n }\n";
}
$templateEnum = str_replace($targetDefinition, $replaceDefinition, $templateEnum);
}
}

View file

@ -3,6 +3,7 @@
namespace Kwaadpepper\Enum;
use Illuminate\Support\ServiceProvider;
use Kwaadpepper\Enum\Console\CreateEnum;
class EnumServiceProvider extends ServiceProvider
{
@ -13,10 +14,17 @@ class EnumServiceProvider extends ServiceProvider
*/
public function boot()
{
$this->loadJsonTranslationsFrom(__DIR__ . '/../resources/lang', 'enum');
$this->loadTranslationsFrom(__DIR__ . '/../resources/lang', 'enum');
$this->publishes([
__DIR__ . '/../resources/lang' => resource_path('lang/vendor/enum'),
]);
// Register the command if we are using the application via the CLI.
if ($this->app->runningInConsole()) {
$this->commands([
CreateEnum::class,
]);
}
}
}

View file

@ -0,0 +1,15 @@
<?php
namespace Kwaadpepper\Enum\Exceptions;
use Exception;
/**
* Is thrown when your enum definition
* contain multiple times the same label value
*
* This should happen if you have a duplication declaration
*/
class DuplicateDefinitionException extends Exception
{
}

View file

@ -4,7 +4,12 @@ namespace Kwaadpepper\Enum\Exceptions;
use Exception;
/**
* Is thrown when your enum definition
* contain multiple times the same label value
*
* This should only happen if you override the labels method
*/
class DuplicateLabelsException extends Exception
{
//
}

View file

@ -4,7 +4,12 @@ namespace Kwaadpepper\Enum\Exceptions;
use Exception;
/**
* Is thrown when your enum definition
* contain multiple times the same value
*
* This should only happen if you override the values method
*/
class DuplicateValuesException extends Exception
{
//
}

View file

@ -0,0 +1,15 @@
<?php
namespace Kwaadpepper\Enum\Exceptions;
use Exception;
/**
* Is thrown when your enum definition
* contain multiple times the same label value
*
* This should happen if you have a duplication declaration
*/
class EmptyDefinitionException extends Exception
{
}

View file

@ -0,0 +1,15 @@
<?php
namespace Kwaadpepper\Enum\Exceptions;
use Exception;
/**
* Is thrown on enum constructor
* as it happens with laravel resolver
* it is named like this event if it is only on
* BaseEnum which works on any application
*/
class EnumNotRoutableException extends Exception
{
}

View file

@ -4,7 +4,11 @@ namespace Kwaadpepper\Enum\Exceptions;
use Exception;
/**
* This exception is used only
* as a safe guard it should not be thrown
* at any moment
*/
class NotImplementedException extends Exception
{
//
}

View file

@ -6,5 +6,4 @@ use Exception;
class UnknownEnumClass extends Exception
{
//
}

View file

@ -4,7 +4,28 @@ namespace Kwaadpepper\Enum\Exceptions;
use Exception;
/**
* Is thrown when accessing other propety than
* label or value on a enumeration.
*/
class UnknownEnumProperty extends Exception
{
//
/**
* The error message
*
* @var string
*/
protected $message;
/**
* Construct the exception. Note: The message is NOT binary safe.
*
* @param string $attribute
* @return void
*/
public function __construct(string $attribute)
{
$this->message = "Unknown attribute $attribute, enumeration only have propeties `label` and `value`.";
parent::__construct();
}
}

View file

@ -3,6 +3,7 @@
namespace Kwaadpepper\Enum\Rules;
use Illuminate\Contracts\Validation\Rule;
use Kwaadpepper\Enum\BaseEnum;
use Kwaadpepper\Enum\Exceptions\UnknownEnumClass;
abstract class BaseEnumRule implements Rule
@ -16,10 +17,11 @@ abstract class BaseEnumRule implements Rule
*
* @param string $enumClass
* @return void
* @throws \Kwaadpepper\Enum\Exceptions\UnknownEnumClass If the class passed is not an enum.
*/
public function __construct(string $enumClass)
{
if (!class_exists($enumClass)) {
if (!class_exists($enumClass) or !is_subclass_of($enumClass, BaseEnum::class)) {
throw new UnknownEnumClass();
}
$this->enumClass = $enumClass;
@ -28,11 +30,15 @@ abstract class BaseEnumRule implements Rule
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param string $attribute
* @param mixed $value
* @return bool
* @return boolean
* @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
*/
abstract public function passes($attribute, $value): bool;
// phpcs:enable
/**
* Get the validation error message.
*

View file

@ -5,28 +5,30 @@ namespace Kwaadpepper\Enum\Rules;
use BadMethodCallException;
use TypeError;
/**
* Custom Rule to validate an enum
*/
class EnumIsValidRule extends BaseEnumRule
{
private $messageType = 0;
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param string $attribute
* @param mixed $value
* @return bool
* @return boolean
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
*/
public function passes($attribute, $value): bool
{
// phpcs:enable
try {
// try cast to int if is int
$value = is_numeric($value) && floatval(intval($value)) === floatval($value) ? (int)$value : $value;
// Try cast to int if is int.
$value = (is_numeric($value) and floatval(intval($value)) === floatval($value)) ?
(int)$value : $value;
forward_static_call([$this->enumClass, 'make'], $value);
} catch (TypeError $e) {
$this->messageType = 1;
return false;
} catch (BadMethodCallException $e) {
$this->messageType = 2;
} catch (TypeError | BadMethodCallException $e) {
return false;
}
return true;
@ -38,13 +40,8 @@ class EnumIsValidRule extends BaseEnumRule
*/
public function message(): string
{
switch ($this->messageType) {
case 1:
return trans('enum::enum.isNotValid');
case 2:
return trans('enum::enum.notInList', [
'values' => implode(',', forward_static_call([$this->enumClass, 'toValues']))
]);
}
return trans('enum::enum.notInList', [
'values' => implode(',', forward_static_call([$this->enumClass, 'toValues']))
]);
}
}

View file

@ -2,21 +2,27 @@
namespace Kwaadpepper\Enum\Traits;
use Illuminate\Support\Facades\Log;
use Kwaadpepper\Enum\BaseEnum;
/**
* @property array $attributes Laravel model attributes
* @property array $enumCasts Map attribute names to enum classes.
* @method bool hasCast(string $key, array|string|null $types = null)
* @method mixed castAttribute(string $key, mixed $value)
*/
trait CastsEnums
{
/**
* Get a plain attribute (not a relationship).
*
* @param string $key
* @param string $key
* @return mixed
* @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing
*/
public function getAttributeValue($key)
{
// phpcs:enable
$value = parent::getAttributeValue($key);
if ($this->hasEnumCast($key)) {
@ -29,66 +35,83 @@ trait CastsEnums
/**
* Set a given attribute on the model.
*
* @param string $key
* @param string $key
* @param mixed $value
* @return $this
* @throws \TypeError If cast fails.
* @throws \BadMethodCallException If cast fails.
* @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
*/
public function setAttribute($key, $value)
{
if ($value !== null && $this->hasEnumCast($key)) {
/** @var \App\Enums\BaseEnum $enum */
$enum = $this->enumCasts[$key];
if ($value instanceof $enum) {
$this->attributes[$key] = $value->value;
} else {
if ($this->hasCast($key)) {
$value = $this->castAttribute($key, $value);
}
// try cast to int if is int
$value = is_numeric($value) && floatval(intval($value)) === floatval($value) ? (int)$value : $value;
$this->attributes[$key] = $enum::make($value)->value;
// phpcs:enable
try {
if ($enum = $this->castToEnum($key, $value) and $enum instanceof BaseEnum) {
$this->attributes[$key] = $enum->value;
return $this;
}
} catch (\TypeError | \BadMethodCallException $e) {
Log::debug(\sprintf('%s : trait CastEnums error, %s', __CLASS__, $e->getMessage()));
if (in_array(config('app.env'), ['debug', 'testing'])) {
throw $e;
}
return $this;
}
// @codeCoverageIgnoreStart
return parent::setAttribute($key, $value);
// @codeCoverageIgnoreEnd
}
/**
* Determine whether an attribute should be cast to a enum.
*
* @param string $key
* @return bool
* @param string $key
* @return boolean
*/
public function hasEnumCast($key): bool
public function hasEnumCast(string $key): bool
{
// This can happen if this trait is added to the model
// but no enum casts have been added yet
/**
* This can happen if this trait is added to the model
* but no enum casts have been added yet.
*/
if ($this->enumCasts === null) {
return false;
}
return array_key_exists($key, $this->enumCasts);
return array_key_exists($key, $this->enumCasts) and
is_subclass_of($this->enumCasts[$key], BaseEnum::class);
}
/**
* Casts the given key to an enum instance
*
* @param string $key
* @param string $key
* @param mixed $value
* @return \App\Enums\BaseEnum|null
* @return \Kwaadpepper\Enum\BaseEnum|mixed|null
* @throws TypeError — If anything else than string or int is used.
* @throws BadMethodCallException — If a matching definition cannot be found.
*/
protected function castToEnum($key, $value): ?BaseEnum
protected function castToEnum(string $key, mixed $value)
{
/** @var \App\Enums\BaseEnum $enum */
if (
$value === null or
(\is_object($value) and \is_subclass_of($value, BaseEnum::class)) or
!isset($this->enumCasts[$key])
) {
return $value;
}
if ($this->hasCast($key)) {
$value = $this->castAttribute($key, $value);
}
// Try cast to int.
$value = (is_numeric($value) and floatval(intval($value)) === floatval($value)) ?
(int)$value : $value;
/** @var \Kwaadpepper\Enum\BaseEnum $enum */
$enum = $this->enumCasts[$key];
if ($value === null || $value instanceof BaseEnum) {
return $value;
} else {
return $enum::make($value);
}
return $enum::make($value);
}
}

View file

@ -0,0 +1,319 @@
<?php
namespace Kwaadpepper\Enum\Tests;
use App\Http\Requests\PassDayRequest;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Kwaadpepper\Enum\Exceptions\EnumNotRoutableException;
use Kwaadpepper\Enum\Exceptions\UnknownEnumClass;
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
use Kwaadpepper\Enum\Tests\Enums\ContactFormCivility;
use Kwaadpepper\Enum\Tests\Enums\Days;
use Kwaadpepper\Enum\Tests\Enums\ForceStringsFromInteger;
use Kwaadpepper\Enum\Tests\Enums\NotRoutableEnum;
use Kwaadpepper\Enum\Tests\Models\Alarm;
use Kwaadpepper\Enum\Tests\Models\Journal;
use Kwaadpepper\Enum\Tests\Models\Report;
class BaseEnumRoutableTest extends TestCase
{
/**
* Defines aliases
*
* @param \Illuminate\Foundation\Application $app
* @return void
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
*/
protected function getPackageAliases($app)
{
// phpcs:enable
$app->alias(Alarm::class, 'Alarm');
$app->alias(Journal::class, 'Journal');
$app->alias(Report::class, 'Report');
$app->alias(Days::class, 'Days');
$app->alias(ContactFormCivility::class, 'ContactFormCivility');
$app->alias(ForceStringsFromInteger::class, 'ForceStringsFromInteger');
$app->alias(NotRoutableEnum::class, 'NotRoutableEnum');
}
/**
* Get package providers.
*
* @param \Illuminate\Foundation\Application $app
*
* @return array
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
protected function getPackageProviders($app)
{
// phpcs:enable
return [
\Kwaadpepper\Enum\EnumServiceProvider::class,
];
}
/**
* Defines migrations
*
* @return void
*/
protected function defineDatabaseMigrations()
{
Schema::create('reports', function (Blueprint $table) {
$table->bigIncrements('id');
$table->smallInteger('day');
});
Schema::create('alarms', function (Blueprint $table) {
$table->bigIncrements('id');
$table->smallInteger('day');
});
Schema::create('journals', function (Blueprint $table) {
$table->bigIncrements('id');
$table->smallInteger('day');
});
}
/**
* Defines seeders
*
* @return void
*/
protected function defineDatabaseSeeders()
{
Report::create(['day' => Days::none()]);
Report::create(['day' => Days::mon()]);
Report::create(['day' => Days::tue()]);
Report::create(['day' => Days::wed()]);
Report::create(['day' => Days::thu()]);
Report::create(['day' => Days::fri()]);
Report::create(['day' => Days::sat()]);
Report::create(['day' => Days::sun()]);
Alarm::create(['day' => Days::none()]);
Alarm::create(['day' => Days::mon()]);
Alarm::create(['day' => Days::tue()]);
Alarm::create(['day' => Days::wed()]);
Alarm::create(['day' => Days::thu()]);
Alarm::create(['day' => Days::fri()]);
Alarm::create(['day' => Days::sat()]);
Alarm::create(['day' => Days::sun()]);
Journal::create(['day' => Days::none()]);
Journal::create(['day' => Days::mon()]);
Journal::create(['day' => Days::tue()]);
Journal::create(['day' => Days::wed()]);
Journal::create(['day' => Days::thu()]);
Journal::create(['day' => Days::fri()]);
Journal::create(['day' => Days::sat()]);
Journal::create(['day' => Days::sun()]);
}
/**
* Define routes setup.
*
* @param \Illuminate\Routing\Router $router
*
* @return void
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
*/
protected function defineRoutes($router)
{
// phpcs:enable
$router->get('/journals/{journal}', function (Journal $journal) {
return response()->json([$journal->day]);
})->middleware(\Illuminate\Routing\Middleware\SubstituteBindings::class)->name('journal.view');
$router->get('/reports/{report}', function (Report $report) {
return response()->json([$report->day]);
})->middleware(\Illuminate\Routing\Middleware\SubstituteBindings::class);
$router->get('/alarms/{alarm}', function (Alarm $alarm) {
return response()->json([$alarm->day]);
})->middleware(\Illuminate\Routing\Middleware\SubstituteBindings::class);
$router->get('/days/{day}', function (Days $day) {
return response()->json([$day]);
})->middleware(\Illuminate\Routing\Middleware\SubstituteBindings::class);
$router->get('/day-validation', function (PassDayRequest $request) {
return response()->json([Days::make((int)$request->day)]);
})->middleware(\Illuminate\Routing\Middleware\SubstituteBindings::class);
$router->get('/civilities/{civility}', function (ContactFormCivility $civility) {
return response()->json([$civility]);
})->middleware(\Illuminate\Routing\Middleware\SubstituteBindings::class);
$router->get('/force/{force}', function (ForceStringsFromInteger $force) {
return response()->json([$force]);
})->middleware(\Illuminate\Routing\Middleware\SubstituteBindings::class);
$router->get('/notroutable/{notroutable}', function (NotRoutableEnum $notroutable) {
return response()->json([$notroutable]);
})->middleware(\Illuminate\Routing\Middleware\SubstituteBindings::class);
}
/**
* Test route values
*
* @return void
*/
public function testRoutesDays()
{
$response = $this->json('GET', sprintf('/days/%s', Days::none()->value));
$response->assertOk();
$response->assertExactJson([Days::none()]);
$response = $this->json('GET', sprintf('/days/%s', Days::mon()->value));
$response->assertOk();
$response->assertExactJson([Days::mon()]);
}
/**
* Test route with example civilities
*
* @return void
*/
public function testRoutesCivilities()
{
$response = $this->json('GET', sprintf('/civilities/%s', ContactFormCivility::mme()->value));
$response->assertOk();
$response->assertExactJson([ContactFormCivility::mme()]);
}
/**
* Test route with enum as string
*
* @return void
*/
public function testRoutesWithEnumIntString()
{
$response = $this->json('GET', sprintf('/force/%s', ForceStringsFromInteger::valueB()->value));
$response->assertOk();
$response->assertExactJson([ForceStringsFromInteger::valueB()]);
}
/**
* Test route with enum not found
*
* @return void
*/
public function testRoutesWithEnumNotFound()
{
$response = $this->json('GET', '/force/6');
$response->assertNotFound();
}
/**
* Test routes with enums as pk
*
* @return void
*/
public function testRoutesWithEnumAsPrimaryKey()
{
$response = $this->json('GET', sprintf('/reports/%s', Days::none()->value));
$response->assertOk();
$response = $this->json('GET', sprintf('/reports/%s', Days::mon()->value));
$response->assertOk();
$response = $this->json('GET', sprintf('/reports/%s', Days::tue()->value));
$response->assertOk();
$response = $this->json('GET', sprintf('/reports/%s', Days::fri()->value));
$response->assertOk();
$response = $this->json('GET', sprintf('/reports/%s', 9999));
$response->assertNotFound();
}
/**
* Test routes with enum as pk without cast enums
*
* @return void
*/
public function testRoutesWithEnumAsPrimaryKeyWithoutCastEnums()
{
$response = $this->json('GET', sprintf('/alarms/%s', Days::none()->value));
$response->assertOk();
$response = $this->json('GET', sprintf('/alarms/%s', Days::mon()->value));
$response->assertOk();
$response = $this->json('GET', sprintf('/alarms/%s', Days::tue()->value));
$response->assertOk();
$response = $this->json('GET', sprintf('/alarms/%s', Days::fri()->value));
$response->assertOk();
$response = $this->json('GET', sprintf('/alarms/%s', 9999));
$response->assertNotFound();
}
/**
* Test routes wit henum as pk without cast enums
*
* ! This test includes getRouteKey method for coverage
*
* @return void
*/
public function testRoutesWithEnumAsPrimaryKeyWithoutCastEnumsAndCast()
{
$response = $this->json('GET', \route('journal.view', Days::none()));
$response->assertOk();
$response = $this->json('GET', \route('journal.view', Days::mon()));
$response->assertOk();
$response = $this->json('GET', \route('journal.view', Days::tue()));
$response->assertOk();
$response = $this->json('GET', \route('journal.view', Days::fri()));
$response->assertOk();
$response = $this->json('GET', \route('journal.view', 9999));
$response->assertNotFound();
}
/**
* Test enum is valid rule
*
* @return void
*/
public function testRules()
{
$rule = new EnumIsValidRule(Days::class);
$this->assertTrue($rule->passes('day', Days::mon()->value));
$this->assertFalse($rule->passes('day', 9999));
$this->assertFalse($rule->passes('day', Days::mon()));
}
/**
* Test route with enum
*
* @return void
*/
public function testRulesRequest()
{
$response = $this->json('GET', sprintf('/day-validation?day=%s', Days::fri()->value));
$response->assertOk();
}
/**
* Test route fails on non enum
*
* @return void
*/
public function testRulesRequestFail()
{
$response = $this->json('GET', sprintf('/day-validation?day=%s', '"!:'));
$response->assertJsonValidationErrors('day');
}
/**
* Test Enum validate enum class on non enum class
*
* @return void
*/
public function testRulesRequestInvalid()
{
$this->expectException(UnknownEnumClass::class);
new EnumIsValidRule(\SplFileObject::class);
}
/**
* Test enum is not routable
*
* @return void
*/
public function testNotRoutableEnum()
{
$response = $this->json('GET', sprintf('/notroutable/%s', '1'));
$response->assertStatus(500);
$this->assertEquals(
new EnumNotRoutableException(),
$response->exception
);
}
}

219
tests/BaseEnumTest.php Normal file
View file

@ -0,0 +1,219 @@
<?php
namespace Kwaadpepper\Enum\Tests;
use Kwaadpepper\Enum\Exceptions\DuplicateLabelsException;
use Kwaadpepper\Enum\Exceptions\DuplicateValuesException;
use Kwaadpepper\Enum\Exceptions\UnknownEnumProperty;
use Kwaadpepper\Enum\Tests\Enums\Days;
use Kwaadpepper\Enum\Tests\Enums\DuplicatedLabels;
use Kwaadpepper\Enum\Tests\Enums\DuplicatedValues;
use Kwaadpepper\Enum\Tests\Enums\ForceStringsFromInteger;
use Kwaadpepper\Enum\Tests\Models\Report;
class BaseEnumTest extends TestCase
{
/**
* Test enum
*
* @return void
*/
public function testEnum()
{
$mon = Days::mon();
$tue = Days::tue();
Days::toArray();
Days::toValues();
Days::toLabels();
json_encode($tue);
// Basic tests.
$this->assertEquals('Monday', $mon->label, 'Days::mon()->value should be equals to `Monday`');
$this->assertEquals(2, $mon->value, 'Days::mon()->value should be equals to `2`');
$this->assertTrue(\property_exists(Days::class, 'value'), 'Days should have a value property');
$this->assertTrue(\property_exists(Days::class, 'label'), 'Days should have a label property');
// Undef prop test.
$exception = null;
try {
$mon->propthatdontexists;
} catch (\Exception $e) {
$exception = $e;
} finally {
$this->assertEquals(
UnknownEnumProperty::class,
\is_object($exception) ? \get_class($exception) : $exception,
'Accessing non existent prop on base enum should throw an UnknownEnumProperty exception'
);
}
$this->assertEquals('Days', $mon->getEnumName(), 'getEnumName should return the enum class name');
$this->assertEquals('mon', $mon->getDefinition(), 'Days::mon() definition should be `mon`');
$this->assertFalse($mon->equals($tue), 'Days::mon() should not be equals to Days::tue()');
$this->assertTrue($mon->equals($mon), 'Days::mon() should be equals to Days::mon()');
$this->assertIsArray(Days::toArray(), 'toArray method should return an array');
$this->assertIsArray(Days::toValues(), 'toValues method should return an array');
$this->assertIsArray(Days::toLabels(), 'toLabels method should return an array');
$this->assertEquals([
0 => 1,
1 => 2,
2 => 4,
3 => 8,
4 => 16,
5 => 32,
6 => 64,
7 => 128
], Days::toValues(), 'Days::toValues method should return an integer array');
$this->assertEquals([
0 => 'None',
1 => 'Monday',
2 => 'Tuesday',
3 => 'Wednesday',
4 => 'Thursday',
5 => 'Friday',
6 => 'Saturday',
7 => 'Sunday'
], Days::toLabels(), 'Days::toLabels method should return a string array');
$this->assertEquals([
0 => Days::none(),
1 => Days::mon(),
2 => Days::tue(),
3 => Days::wed(),
4 => Days::thu(),
5 => Days::fri(),
6 => Days::sat(),
7 => Days::sun()
], Days::toArray(), 'Days::toLabels method should return an enum array');
$this->assertEquals(
(string)ForceStringsFromInteger::valueA()->value,
(string)ForceStringsFromInteger::valueA(),
'BaseEnum::__toString should return the value as a string'
);
}
/**
* Test cannot assign an enum value
*
* @return void
*/
public function testCannotAssignAnEnumValue()
{
$this->expectException(\Error::class);
$enum = Days::mon();
$enum->value = 'anything';
}
/**
* Test cannot assign an enum label
*
* @return void
*/
public function testCannotAssignAnEnumLabel()
{
$this->expectException(\Error::class);
$enum = Days::mon();
$enum->label = 'anything';
}
/**
* This method is here for code coverage
*
* @return void
*/
public function testHackingEnumValueCouldConfuseCache()
{
$enum = Days::mon();
$refClass = new \ReflectionClass($enum);
$property = $refClass->getProperty('value');
$property->setAccessible(true);
$property->setValue($enum, 'anything');
$this->assertEquals('anything', $enum->value);
$this->assertNull(
$enum->getDefinition(),
'BaseEnum::getDefinition should be null when hacking var consing cache'
);
}
/**
* Test setting enum value onto model prop
*
* @return void
*/
public function testValidEnumSet()
{
$this->expectNotToPerformAssertions();
$report = new Report();
$report->day = Days::mon();
$report->day = Days::mon()->value;
}
/**
* Test setting invalid enum value onto model prop
*
* @return void
*/
public function testInvalidEnumSet()
{
$this->expectException(\BadMethodCallException::class);
$report = new Report();
$report->day = Days::mon()->label;
}
/**
* Test setting prop enum with float (invalid)
*
* @return void
*/
public function testInvalidEnumSetWithFloat()
{
$this->expectException(\TypeError::class);
$report = new Report();
$report->day = 0.1;
}
/**
* Test setting invalid enum prop with object
*
* @return void
*/
public function testInvalidEnumSetNotAuthorizedType()
{
$this->expectException(\TypeError::class);
$report = new Report();
$report->day = new \stdClass();
}
/**
* Test setting enum prop with null as valid
*
* @return void
*/
public function testInvalidEnumCanSetNull()
{
$this->expectNotToPerformAssertions();
$report = new Report();
$report->day = null;
}
/**
* Test enum has duplicated values
*
* @return void
*/
public function testDuplicatedValues()
{
$this->expectException(DuplicateValuesException::class);
DuplicatedValues::one();
}
/**
* Test enm has duplicated labels
*
* @return void
*/
public function testDuplicatedLabels()
{
$this->expectException(DuplicateLabelsException::class);
DuplicatedLabels::one();
}
}

View file

@ -1,120 +0,0 @@
<?php
namespace Kwaadpepper\Enum\Tests;
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
use Kwaadpepper\Enum\Tests\Enums\ContactFormCivility;
use Kwaadpepper\Enum\Tests\Enums\Days;
use Kwaadpepper\Enum\Tests\Enums\ForceStringsFromInteger;
use Orchestra\Testbench\TestCase;
class EnumTest extends TestCase
{
protected function getPackageAliases($app)
{
$app->alias(Days::class, 'Days');
$app->alias(ContactFormCivility::class, 'ContactFormCivility');
}
/**
* Define routes setup.
*
* @param \Illuminate\Routing\Router $router
*
* @return void
*/
protected function defineRoutes($router)
{
$router->get('/days/{day}', function (Days $day) {
return response()->json([$day]);
})->middleware('bindings');
$router->get('/civilities/{civility}', function (ContactFormCivility $civility) {
return response()->json([$civility]);
})->middleware('bindings');
$router->get('/force/{force}', function (ForceStringsFromInteger $force) {
return response()->json([$force]);
})->middleware('bindings');
}
public function testEnum()
{
$mon = Days::mon();
$tue = Days::tue();
Days::toArray();
Days::toValues();
Days::toLabels();
json_encode($tue);
$mon->getDefinition();
$this->assertClassHasAttribute('value', Days::class);
$this->assertClassHasAttribute('label', Days::class);
$this->assertFalse($mon->equals($tue));
$this->assertTrue($mon->equals($mon));
$this->assertIsArray(Days::toArray());
$this->assertIsArray(Days::toValues());
$this->assertIsArray(Days::toLabels());
$this->assertEquals([
0 => 0,
1 => 1,
2 => 2,
3 => 4,
4 => 8,
5 => 16,
6 => 32,
7 => 64
], Days::toValues());
$this->assertEquals([
0 => 'None',
1 => 'Monday',
2 => 'Tuesday',
3 => 'Wednesday',
4 => 'Thursday',
5 => 'Friday',
6 => 'Saturday',
7 => 'Sunday'
], Days::toLabels());
$this->assertEquals([
0 => Days::none(),
1 => Days::mon(),
2 => Days::tue(),
3 => Days::wed(),
4 => Days::thu(),
5 => Days::fri(),
6 => Days::sat(),
7 => Days::sun()
], Days::toArray());
$this->assertEquals('Monday', (string)$mon);
}
public function testRoutesDays()
{
$response = $this->call('GET', '/days/1');
$response->assertOk();
$response->assertExactJson([Days::mon()]);
$response = $this->call('GET', '/days/2');
$response->assertOk();
$response->assertExactJson([Days::tue()]);
}
public function testRoutesCivilities()
{
$response = $this->call('GET', '/civilities/mme');
$response->assertOk();
$response->assertExactJson([ContactFormCivility::mme()]);
}
public function testRoutesWithEnumIntString()
{
$response = $this->call('GET', '/force/1');
$response->assertOk();
$response->assertExactJson([ForceStringsFromInteger::valueB()]);
}
public function testRules()
{
$rule = new EnumIsValidRule(Days::class);
$this->assertTrue($rule->passes('day', Days::mon()->value));
$this->assertFalse($rule->passes('day', 9999));
$this->assertFalse($rule->passes('day', Days::mon()));
}
}

View file

@ -2,18 +2,22 @@
namespace Kwaadpepper\Enum\Tests\Enums;
use BadMethodCallException;
use Kwaadpepper\Enum\BaseEnum;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self m()
* @method static self mme()
* @method static self mlle()
*/
final class ContactFormCivility extends BaseEnum
final class ContactFormCivility extends BaseEnumRoutable
{
/**
* Output the value as a string
*
* @return string
*/
public function __toString(): string
{
return (string) $this->label;
return (string)$this->label;
}
}

View file

@ -2,7 +2,7 @@
namespace Kwaadpepper\Enum\Tests\Enums;
use Kwaadpepper\Enum\BaseEnum;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self none()
@ -14,22 +14,32 @@ use Kwaadpepper\Enum\BaseEnum;
* @method static self sat()
* @method static self sun()
*/
final class Days extends BaseEnum
final class Days extends BaseEnumRoutable
{
/**
* All values
*
* @return array
*/
protected static function values(): array
{
return [
'none' => 0,
'mon' => 1,
'tue' => 1 << 1,
'wed' => 1 << 2,
'thu' => 1 << 3,
'fri' => 1 << 4,
'sat' => 1 << 5,
'sun' => 1 << 6,
'none' => 1,
'mon' => 1 << 1,
'tue' => 1 << 2,
'wed' => 1 << 3,
'thu' => 1 << 4,
'fri' => 1 << 5,
'sat' => 1 << 6,
'sun' => 1 << 7,
];
}
/**
* All labels
*
* @return array
*/
protected static function labels(): array
{
return [
@ -44,15 +54,20 @@ final class Days extends BaseEnum
];
}
/**
* Output the value as a string
*
* @return string
*/
public function __toString(): string
{
return (string) $this->label;
return (string)$this->label;
}
/**
* Checks if days contain this day
*
* @param int $days
* @param integer $days
* @return boolean
*/
public function has(int $days)

View file

@ -0,0 +1,27 @@
<?php
namespace Kwaadpepper\Enum\Tests\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self one()
* @method static self two()
* @method static self three()
*/
final class DuplicatedLabels extends BaseEnumRoutable
{
/**
* All labels
*
* @return array
*/
protected static function labels(): array
{
return [
'one' => 'one',
'two' => 'two',
'three' => 'two'
];
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace Kwaadpepper\Enum\Tests\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self one()
* @method static self two()
* @method static self three()
*/
final class DuplicatedValues extends BaseEnumRoutable
{
/**
* All values
*
* @return array
*/
protected static function values(): array
{
return [
'one' => '0',
'two' => '1',
'three' => '1'
];
}
}

View file

@ -2,15 +2,20 @@
namespace Kwaadpepper\Enum\Tests\Enums;
use Kwaadpepper\Enum\BaseEnum;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self valueA()
* @method static self valueB()
* @method static self valueC()
*/
final class ForceStringsFromInteger extends BaseEnum
final class ForceStringsFromInteger extends BaseEnumRoutable
{
/**
* All values
*
* @return array
*/
protected static function values(): array
{
return [

View file

@ -0,0 +1,27 @@
<?php
namespace Kwaadpepper\Enum\Tests\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self one()
* @method static self two()
* @method static self three()
*/
final class NotRoutableEnum extends BaseEnum
{
/**
* All values
*
* @return array
*/
protected static function values(): array
{
return [
'one' => '0',
'two' => '1',
'three' => '2'
];
}
}

48
tests/Models/Alarm.php Normal file
View file

@ -0,0 +1,48 @@
<?php
namespace Kwaadpepper\Enum\Tests\Models;
use Illuminate\Database\Eloquent\Model;
use Kwaadpepper\Enum\Traits\CastsEnums;
class Alarm extends Model
{
use CastsEnums;
/**
* Indicates if the model should be timestamped.
*
* @var boolean
*/
public $timestamps = false;
/**
* The primary key for the model.
*
* @var string
*/
protected $primaryKey = 'day';
/**
* The "type" of the primary key ID.
*
* @var string
*/
protected $keyType = 'int';
/**
* Indicates if the IDs are auto-incrementing.
*
* @var boolean
*/
public $incrementing = false;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'day'
];
}

67
tests/Models/Journal.php Normal file
View file

@ -0,0 +1,67 @@
<?php
namespace Kwaadpepper\Enum\Tests\Models;
use Illuminate\Database\Eloquent\Model;
use Kwaadpepper\Enum\Tests\Enums\Days;
use Kwaadpepper\Enum\Traits\CastsEnums;
class Journal extends Model
{
use CastsEnums;
/**
* The attributes that are mass assignable.
*
* @var boolean
*/
public $timestamps = false;
/**
* The primary key for the model.
*
* @var string
*/
protected $primaryKey = 'day';
/**
* The "type" of the primary key ID.
*
* @var string
*/
protected $keyType = 'int';
/**
* Indicates if the IDs are auto-incrementing.
*
* @var boolean
*/
public $incrementing = false;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'day'
];
/**
* The attributes that should be cast.
*
* @var string[]
*/
protected $casts = [
'day' => 'int'
];
/**
* The attributes that should be to enum.
*
* @var string[]
*/
protected $enumCasts = [
'day' => Days::class
];
}

58
tests/Models/Report.php Normal file
View file

@ -0,0 +1,58 @@
<?php
namespace Kwaadpepper\Enum\Tests\Models;
use Illuminate\Database\Eloquent\Model;
use Kwaadpepper\Enum\Tests\Enums\Days;
use Kwaadpepper\Enum\Traits\CastsEnums;
class Report extends Model
{
use CastsEnums;
/**
* Indicates if the model should be timestamped.
*
* @var boolean
*/
public $timestamps = false;
/**
* The primary key for the model.
*
* @var string
*/
protected $primaryKey = 'day';
/**
* The "type" of the primary key ID.
*
* @var string
*/
protected $keyType = 'int';
/**
* Indicates if the IDs are auto-incrementing.
*
* @var boolean
*/
public $incrementing = false;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'day'
];
/**
* The attributes that should be to enum.
*
* @var string[]
*/
protected $enumCasts = [
'day' => Days::class
];
}

View file

@ -0,0 +1,32 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
use Kwaadpepper\Enum\Tests\Enums\Days;
class PassDayRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'day' => ['required', new EnumIsValidRule(Days::class)],
];
}
}

40
tests/TestCase.php Normal file
View file

@ -0,0 +1,40 @@
<?php
namespace Kwaadpepper\Enum\Tests;
abstract class TestCase extends \Orchestra\Testbench\TestCase
{
/**
* Automatically loads environment file if available.
*
* @var boolean
*/
protected $loadEnvironmentVariables = false;
/**
* Setup custom phpunit catch for deprecated error cathing.
*
* @return void
*/
public function setUp(): void
{
set_error_handler(
static function ($errno, $errstr) {
throw new \Exception($errstr, $errno);
},
E_ALL
);
parent::setUp();
}
/**
* Restore phpunit handler
*
* @return void
*/
public function tearDown(): void
{
restore_error_handler();
parent::tearDown();
}
}