├── .codeclimate.yml
├── .styleci.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── composer.json
├── extension.neon
├── phpstan.neon
└── src
├── Composer
└── IdeHelper.php
├── Concerns
├── Attributes.php
├── Bitmasks.php
├── Comparison.php
├── Configure.php
├── ConfigureDefaults.php
├── ConfigureLabels.php
├── ConfigureMapper.php
├── ConfigureState.php
├── Constructor.php
├── Defaults.php
├── Dropdown.php
├── Enhancers.php
├── Extractor.php
├── From.php
├── Getters.php
├── Labels.php
├── Macros.php
├── MagicCalls.php
├── Mappers.php
├── Properties.php
├── Reporters.php
├── State.php
├── Subset.php
└── Value.php
├── Contracts
├── EnumSubset.php
├── Mapper.php
├── Reporter.php
└── TransitionHook.php
├── Enums
└── LogLevel.php
├── Exceptions
├── EnumException.php
├── IllegalEnumTransitionException.php
├── InvalidBitmaskEnum.php
├── NotAnEnumException.php
├── PropertyAlreadyStoredException.php
├── ReservedPropertyNameException.php
└── SyntaxException.php
├── Functions
└── Functions.php
├── Helpers
├── Bitmasks
│ ├── Bitmask.php
│ ├── Concerns
│ │ ├── BitmaskModifiers.php
│ │ └── BitmaskValidators.php
│ └── EnumBitmasks.php
├── EnumAttributes.php
├── EnumBlade.php
├── EnumCheck.php
├── EnumCompare.php
├── EnumDefaults.php
├── EnumExtractor.php
├── EnumGetters.php
├── EnumImplements.php
├── EnumLabels.php
├── EnumMacros.php
├── EnumMagicCalls.php
├── EnumProperties.php
├── EnumProxy.php
├── EnumReporter.php
├── EnumState.php
├── EnumValue.php
├── Enumhancer.php
├── Mappers
│ ├── EnumArrayMapper.php
│ └── EnumMapper.php
└── Subset
│ └── EnumSubsetMethods.php
├── Laravel
├── Concerns
│ ├── CastsBasicEnumerations.php
│ └── CastsStatefulEnumerations.php
├── Middleware
│ └── SubstituteEnums.php
├── Mixins
│ ├── FormRequestMixin.php
│ └── RulesMixin.php
├── Providers
│ └── EnumhancerServiceProvider.php
├── Reporters
│ └── LaravelLogReporter.php
└── Rules
│ ├── EnumBitmask.php
│ ├── EnumTransition.php
│ └── IsEnum.php
└── PHPStan
├── Constants
├── BitmaskConstantAlwaysUsed.php
├── BitmaskModifierConstantAlwaysUsed.php
├── DefaultConstantAlwaysUsed.php
├── MapperConstantAlwaysUsed.php
├── Rules
│ ├── DefaultConstantRule.php
│ ├── MapperConstantRule.php
│ └── StrictConstantRule.php
└── StrictConstantAlwaysUsed.php
├── Methods
├── EnumComparisonMethodsClassReflection.php
├── EnumConstructorMethodsClassReflection.php
├── EnumMacrosMethodsClassReflection.php
└── EnumStateMethodsClassReflection.php
└── Reflections
└── ClosureMethodReflection.php
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | plugins:
3 | markdownlint:
4 | exclude_patterns:
5 | - vendor
6 | - LICENSE.md
7 | enabled: true
8 | checks:
9 | MD029:
10 | enabled: false
11 | phpcodesniffer:
12 | channel: "beta"
13 | config:
14 | standard: "PSR1,PSR2"
15 | exclude_patterns:
16 | - "**/*"
17 | - "!src/*"
18 | - 'src/Enums/*'
19 | enabled: true
20 | phpmd:
21 | enabled: true
22 | exclude_patterns:
23 | - !**/*.php
24 | checks:
25 | CleanCode/StaticAccess:
26 | enabled: false
27 | CleanCode/BooleanArgumentFlag:
28 | enabled: false
29 | Naming/ShortMethodName:
30 | enabled: false
31 | duplication:
32 | enabled: true
33 | exclude_patterns:
34 | - config/
35 | - 'coverage'
36 | - .github
37 | - db/
38 | - dist/
39 | - features/
40 | - '**/node_modules/'
41 | - script/
42 | - '**/spec/'
43 | - '**/test/'
44 | - '**/tests/'
45 | - Tests/
46 | - '**/vendor/'
47 | - '**/*_test.go'
48 | - '**/*.d.ts'
49 |
--------------------------------------------------------------------------------
/.styleci.yml:
--------------------------------------------------------------------------------
1 | preset: laravel
2 |
3 | disabled:
4 | - single_class_element_per_statement
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to `Enumhancer` will be documented in this file
4 |
5 | ## 2.2.0 - 2023-06-09
6 |
7 | - fixed serious bug in [Getters](docs/getters.md) where
8 | getting by integer would not match value first.
9 | - added support for [BIT_MODIFIER](docs/bitmasks.md#modifiers)
10 |
11 | ## 2.1.0 - 2023-05-12
12 |
13 | - Support for [Attributes](docs/attributes.md)
14 |
15 | ## 2.0.0 - 2023-02-28
16 |
17 | - Now supports Laravel 10
18 | - dropped support for laravel 8
19 |
20 | ### Upgrade notes
21 |
22 | - Makers (make, tryMake etc.) are removed in favor
23 | of [Getters](docs/getters.md)
24 |
25 | ## 1.23.0 - 2023-02-03
26 |
27 | - added [PHPStan](docs/phpstan.md) support
28 | - added [IDE-helper](docs/ide-helper.md) (requires another package)
29 | - added global class for configuring Enumhancer
30 | - added global [Macro](docs/macros.md#global-macros) support
31 |
32 | ## 1.22.0 - 2023-01-07
33 |
34 | - added [asEnum](docs/formrequests.md) to laravel's FormRequests
35 | - tiny fix in [isEnum](docs/laravel.validation.md#isEnum)
36 | validation: When [Defaults](docs/defaults.md) are used, it
37 | should fail validation.
38 |
39 | ## 1.21.0 - 2023-01-06
40 |
41 | - added [(basic) enum binding](docs/binding.md) allowing you to bind
42 | basic enumerations to your routes and use Enumhancers secret sauce.
43 | - Fixed a lot of potential issues with PHPstan.
44 |
45 | ## 1.20.0 - 2023-01-04
46 |
47 | - bugfix in [Default](docs/defaults.md) where configured defaults would
48 | not override the by const defined value
49 | - bugfix in [Mappers](docs/mappers.md) where mapping to integers was
50 | not allowed
51 |
52 | ### Extended features
53 |
54 | - You can now set Mapper FQCN in constants starting with
55 | `map` and `map_flip`
56 | - [Mappers](docs/mappers.md) methods now are usable statically
57 | - All Laravel rules have now macro's set on `Rule`
58 |
59 | ### New features
60 |
61 | - added [Bitmask](docs/bitmasks.md)
62 | - added [Macros](docs/macros.md)
63 | - added `isEnum` and `enumBitmask` rules
64 |
65 | ## 1.19.0 - 2022-12-15
66 |
67 | - You can now use constants for [Mappers](docs/mappers.md)
68 | and [Defaults](docs/defaults.md)
69 | - you can now flag a unit enum as `strict`, so you don't
70 | have to worry about casing in [Values](docs/value.md).
71 |
72 | ## 1.18.0 - 2022-12-14
73 |
74 | - Added Magic method functionality to [State](docs/state.md)
75 | - Added `to` and `tryTo` methods to `State`
76 | - Added `is`, `isNot`, `isIn` and `isNotIn`
77 | to [Comparison](docs/comparison.md)
78 |
79 | ## 1.17.0 - 2022-12-13
80 |
81 | - Added [Flip](docs/mappers.md#flip), allowing to use
82 | a single mapper for mapping between enums
83 | - [From](docs/from.md)
84 | now allows `UnitEnum` objects for use with `Flip`
85 | - [Comparison](docs/comparison.md) now allows different enums
86 | when used with [Mappers](docs/mappers.md)
87 | - Deprecated [Makers](docs/makers.md), replaced by
88 | [Getters](docs/getters.md)
89 |
90 | ## 1.16.0 - 2022-12-11
91 |
92 | - Added [Configure](docs/configure.md)
93 | - Added [Dropdown](docs/dropdown.md)
94 | - [Comparison](docs/configure.md) now accepts null values
95 | - Fixed bug in [Casting](docs/casting.md) where in the latest Laravel versions
96 | the `Keep Enum Value Case` switch no longer worked.
97 |
98 | ## 1.15.0 - 2022-06-21
99 |
100 | - Made the Laravel [Reporter](docs/reporters.md#laravel) configurable
101 | - added `key` method to [Value](docs/value.md)
102 |
103 | ## 1.14.0 - 2022-06-19
104 |
105 | - Added transition hooks [State](docs/state.md)
106 | - [Makers](docs/makers.md) & [From](docs/from.md) now allow you to use integer
107 | keys on basic and string enums
108 |
109 | ## 1.12.0 - 2022-06-15
110 |
111 | - Added casting support for [State](docs/state.md)
112 |
113 | ## 1.11.0 - 2022-06-14
114 |
115 | - Added [State](docs/state.md) that allows you to have transitions with enums
116 |
117 | ## 1.10.0 - 2022-06-12
118 |
119 | - Added [Defaults](docs/defaults.md) that allows you to have default enums
120 |
121 | ## 1.9.0 - 2022-06-08
122 |
123 | - Added [Blade](docs/blade.md) support
124 |
125 | ## 1.8.0 - 2022-06-07
126 |
127 | - Added [Helper functions](docs/functions.md) to ease usage of basic enums
128 |
129 | ## 1.7.0 - 2022-06-06
130 |
131 | - When using [Comparison](docs/comparison.md), you can now assert with `is`
132 | or `isNot`
133 |
134 | ## 1.6.0 - 2022-06-04
135 |
136 | - Added Eloquent Casting support for basic enumerations
137 |
138 | ## 1.5.0 - 2022-05-31
139 |
140 | - Added [Extractor](docs/extractor.md) to extract enums from a string mentioned
141 | by value
142 | - Some documentation repairs
143 |
144 | ## 1.4.1 - 2022-03-04
145 |
146 | - Added `cases` method to `Subset`
147 |
148 | ## 1.4.0 - 2022-03-02
149 |
150 | - Renamed Multi to Subset
151 | - Added `names` method to `Subset`
152 | - Added `values` method to `Subset`
153 | - Added `do` method to `Subset`
154 |
155 | ## 1.3.0 - 2022-02-28
156 |
157 | - Added Multi. Currently allows you to compare against a subset of your enum
158 |
159 | ## 1.2.0 - 2022-02-26
160 |
161 | - Added Value (for use with basic enums)
162 |
163 | ## 1.1.0 - 2022-02-25
164 |
165 | - Added From. Useful for situations where you need them with basic enums
166 |
167 | ## 1.0.2 - 2022-02-16
168 |
169 | - Bugfix: Constructor did not use internal mapper
170 |
171 | ## 1.0.1 - 2022-02-16
172 |
173 | - You can now define a mapper in a method
174 | - When you use an empty string or null in mappable, it will return null now
175 |
176 | ## 1.0.0 - 2022-02-15
177 |
178 | - Initial release
179 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are **welcome** and will be fully **credited**.
4 |
5 | Please read and understand the contribution guide before creating an issue or
6 | pull request.
7 |
8 | ## Etiquette
9 |
10 | This project is open source, and as such, the maintainers give their free time
11 | to build and maintain the source code held within. They make the code freely
12 | available in the hope that it will be of use to other developers. It would be
13 | extremely unfair for them to suffer abuse or anger for their hard work.
14 |
15 | Please be considerate towards maintainers when raising issues or presenting pull
16 | requests. Let's show the world that developers are civilized and selfless
17 | people.
18 |
19 | It's the duty of the maintainer to ensure that all submissions to the project
20 | are of sufficient quality to benefit the project. Many developers have different
21 | skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do
22 | not be upset or abusive if your submission is not used.
23 |
24 | ## Viability
25 |
26 | When requesting or submitting new features, first consider whether it might be
27 | useful to others. Open source projects are used by many developers, who may have
28 | entirely different needs to your own. Think about whether or not your feature is
29 | likely to be used by other users of the project.
30 |
31 | ## Procedure
32 |
33 | Before filing an issue:
34 |
35 | - Attempt to replicate the problem, to ensure that it wasn't a coincidental
36 | incident.
37 | - Check to make sure your feature suggestion isn't already present within the
38 | project.
39 | - Check the pull requests tab to ensure that the bug doesn't have a fix in
40 | progress.
41 | - Check the pull requests tab to ensure that the feature isn't already in
42 | progress.
43 |
44 | Before submitting a pull request:
45 |
46 | - Check the codebase to ensure that your feature doesn't already exist.
47 | - Check the pull requests to ensure that another person hasn't already submitted
48 | the feature or fix.
49 |
50 | ## Requirements
51 |
52 | If the project maintainer has any additional requirements, you will find them
53 | listed here.
54 |
55 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)**
56 | - The easiest way to apply the conventions is to
57 | install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer).
58 |
59 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests.
60 |
61 | - **Document any change in behaviour** - Make sure the `README.md` and any other
62 | relevant documentation are kept up-to-date.
63 |
64 | - **Consider our release cycle** - We try to
65 | follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is
66 | not an option.
67 |
68 | - **One pull request per feature** - If you want to do more than one thing, send
69 | multiple pull requests.
70 |
71 | - **Send coherent history** - Make sure each individual commit in your pull
72 | request is meaningful. If you had to make multiple intermediate commits while
73 | developing,
74 | please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages)
75 | before submitting.
76 |
77 | **Happy coding**!
78 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies of this license
6 | document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU Affero General Public License is a free, copyleft license for software
11 | and other kinds of works, specifically designed to ensure cooperation with the
12 | community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed to take
15 | away your freedom to share and change the works. By contrast, our General Public
16 | Licenses are intended to guarantee your freedom to share and change all versions
17 | of a program--to make sure it remains free software for all its users.
18 |
19 | When we speak of free software, we are referring to freedom, not price. Our
20 | General Public Licenses are designed to make sure that you have the freedom to
21 | distribute copies of free software (and charge for them if you wish), that you
22 | receive source code or can get it if you want it, that you can change the
23 | software or use pieces of it in new free programs, and that you know you can do
24 | these things.
25 |
26 | Developers that use our General Public Licenses protect your rights with two
27 | steps: (1) assert copyright on the software, and (2) offer you this License
28 | which gives you legal permission to copy, distribute and/or modify the software.
29 |
30 | A secondary benefit of defending all users' freedom is that improvements made in
31 | alternate versions of the program, if they receive widespread use, become
32 | available for other developers to incorporate. Many developers of free software
33 | are heartened and encouraged by the resulting cooperation. However, in the case
34 | of software used on network servers, this result may fail to come about. The GNU
35 | General Public License permits making a modified version and letting the public
36 | access it on a server without ever releasing its source code to the public.
37 |
38 | The GNU Affero General Public License is designed specifically to ensure that,
39 | in such cases, the modified source code becomes available to the community. It
40 | requires the operator of a network server to provide the source code of the
41 | modified version running there to the users of that server. Therefore, public
42 | use of a modified version, on a publicly accessible server, gives the public
43 | access to the source code of the modified version.
44 |
45 | An older license, called the Affero General Public License and published by
46 | Affero, was designed to accomplish similar goals. This is a different license,
47 | not a version of the Affero GPL, but Affero has released a new version of the
48 | Affero GPL which permits relicensing under this license.
49 |
50 | The precise terms and conditions for copying, distribution and modification
51 | follow.
52 |
53 | TERMS AND CONDITIONS
54 |
55 | 0. Definitions.
56 |
57 | "This License" refers to version 3 of the GNU Affero General Public License.
58 |
59 | "Copyright" also means copyright-like laws that apply to other kinds of works,
60 | such as semiconductor masks.
61 |
62 | "The Program" refers to any copyrightable work licensed under this License. Each
63 | licensee is addressed as "you". "Licensees" and
64 | "recipients" may be individuals or organizations.
65 |
66 | To "modify" a work means to copy from or adapt all or part of the work in a
67 | fashion requiring copyright permission, other than the making of an exact copy.
68 | The resulting work is called a "modified version" of the earlier work or a
69 | work "based on" the earlier work.
70 |
71 | A "covered work" means either the unmodified Program or a work based on the
72 | Program.
73 |
74 | To "propagate" a work means to do anything with it that, without permission,
75 | would make you directly or secondarily liable for infringement under applicable
76 | copyright law, except executing it on a computer or modifying a private copy.
77 | Propagation includes copying, distribution (with or without modification),
78 | making available to the public, and in some countries other activities as well.
79 |
80 | To "convey" a work means any kind of propagation that enables other parties to
81 | make or receive copies. Mere interaction with a user through a computer network,
82 | with no transfer of a copy, is not conveying.
83 |
84 | An interactive user interface displays "Appropriate Legal Notices"
85 | to the extent that it includes a convenient and prominently visible feature
86 | that (1) displays an appropriate copyright notice, and (2)
87 | tells the user that there is no warranty for the work (except to the extent that
88 | warranties are provided), that licensees may convey the work under this License,
89 | and how to view a copy of this License. If the interface presents a list of user
90 | commands or options, such as a menu, a prominent item in the list meets this
91 | criterion.
92 |
93 | 1. Source Code.
94 |
95 | The "source code" for a work means the preferred form of the work for making
96 | modifications to it. "Object code" means any non-source form of a work.
97 |
98 | A "Standard Interface" means an interface that either is an official standard
99 | defined by a recognized standards body, or, in the case of interfaces specified
100 | for a particular programming language, one that is widely used among developers
101 | working in that language.
102 |
103 | The "System Libraries" of an executable work include anything, other than the
104 | work as a whole, that (a) is included in the normal form of packaging a Major
105 | Component, but which is not part of that Major Component, and (b) serves only to
106 | enable use of the work with that Major Component, or to implement a Standard
107 | Interface for which an implementation is available to the public in source code
108 | form. A
109 | "Major Component", in this context, means a major essential component
110 | (kernel, window system, and so on) of the specific operating system
111 | (if any) on which the executable work runs, or a compiler used to produce the
112 | work, or an object code interpreter used to run it.
113 |
114 | The "Corresponding Source" for a work in object code form means all the source
115 | code needed to generate, install, and (for an executable work) run the object
116 | code and to modify the work, including scripts to control those activities.
117 | However, it does not include the work's System Libraries, or general-purpose
118 | tools or generally available free programs which are used unmodified in
119 | performing those activities but which are not part of the work. For example,
120 | Corresponding Source includes interface definition files associated with source
121 | files for the work, and the source code for shared libraries and dynamically
122 | linked subprograms that the work is specifically designed to require, such as by
123 | intimate data communication or control flow between those subprograms and other
124 | parts of the work.
125 |
126 | The Corresponding Source need not include anything that users can regenerate
127 | automatically from other parts of the Corresponding Source.
128 |
129 | The Corresponding Source for a work in source code form is that same work.
130 |
131 | 2. Basic Permissions.
132 |
133 | All rights granted under this License are granted for the term of copyright on
134 | the Program, and are irrevocable provided the stated conditions are met. This
135 | License explicitly affirms your unlimited permission to run the unmodified
136 | Program. The output from running a covered work is covered by this License only
137 | if the output, given its content, constitutes a covered work. This License
138 | acknowledges your rights of fair use or other equivalent, as provided by
139 | copyright law.
140 |
141 | You may make, run and propagate covered works that you do not convey, without
142 | conditions so long as your license otherwise remains in force. You may convey
143 | covered works to others for the sole purpose of having them make modifications
144 | exclusively for you, or provide you with facilities for running those works,
145 | provided that you comply with the terms of this License in conveying all
146 | material for which you do not control copyright. Those thus making or running
147 | the covered works for you must do so exclusively on your behalf, under your
148 | direction and control, on terms that prohibit them from making any copies of
149 | your copyrighted material outside their relationship with you.
150 |
151 | Conveying under any other circumstances is permitted solely under the conditions
152 | stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
153 |
154 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
155 |
156 | No covered work shall be deemed part of an effective technological measure under
157 | any applicable law fulfilling obligations under article 11 of the WIPO copyright
158 | treaty adopted on 20 December 1996, or similar laws prohibiting or restricting
159 | circumvention of such measures.
160 |
161 | When you convey a covered work, you waive any legal power to forbid
162 | circumvention of technological measures to the extent such circumvention is
163 | effected by exercising rights under this License with respect to the covered
164 | work, and you disclaim any intention to limit operation or modification of the
165 | work as a means of enforcing, against the work's users, your or third parties'
166 | legal rights to forbid circumvention of technological measures.
167 |
168 | 4. Conveying Verbatim Copies.
169 |
170 | You may convey verbatim copies of the Program's source code as you receive it,
171 | in any medium, provided that you conspicuously and appropriately publish on each
172 | copy an appropriate copyright notice; keep intact all notices stating that this
173 | License and any non-permissive terms added in accord with section 7 apply to the
174 | code; keep intact all notices of the absence of any warranty; and give all
175 | recipients a copy of this License along with the Program.
176 |
177 | You may charge any price or no price for each copy that you convey, and you may
178 | offer support or warranty protection for a fee.
179 |
180 | 5. Conveying Modified Source Versions.
181 |
182 | You may convey a work based on the Program, or the modifications to produce it
183 | from the Program, in the form of source code under the terms of section 4,
184 | provided that you also meet all of these conditions:
185 |
186 | a) The work must carry prominent notices stating that you modified
187 | it, and giving a relevant date.
188 |
189 | b) The work must carry prominent notices stating that it is
190 | released under this License and any conditions added under section
191 | 7. This requirement modifies the requirement in section 4 to
192 | "keep intact all notices".
193 |
194 | c) You must license the entire work, as a whole, under this
195 | License to anyone who comes into possession of a copy. This
196 | License will therefore apply, along with any applicable section 7
197 | additional terms, to the whole of the work, and all its parts,
198 | regardless of how they are packaged. This License gives no
199 | permission to license the work in any other way, but it does not
200 | invalidate such permission if you have separately received it.
201 |
202 | d) If the work has interactive user interfaces, each must display
203 | Appropriate Legal Notices; however, if the Program has interactive
204 | interfaces that do not display Appropriate Legal Notices, your
205 | work need not make them do so.
206 |
207 | A compilation of a covered work with other separate and independent works, which
208 | are not by their nature extensions of the covered work, and which are not
209 | combined with it such as to form a larger program, in or on a volume of a
210 | storage or distribution medium, is called an
211 | "aggregate" if the compilation and its resulting copyright are not used to limit
212 | the access or legal rights of the compilation's users beyond what the individual
213 | works permit. Inclusion of a covered work in an aggregate does not cause this
214 | License to apply to the other parts of the aggregate.
215 |
216 | 6. Conveying Non-Source Forms.
217 |
218 | You may convey a covered work in object code form under the terms of sections 4
219 | and 5, provided that you also convey the machine-readable Corresponding Source
220 | under the terms of this License, in one of these ways:
221 |
222 | a) Convey the object code in, or embodied in, a physical product
223 | (including a physical distribution medium), accompanied by the
224 | Corresponding Source fixed on a durable physical medium
225 | customarily used for software interchange.
226 |
227 | b) Convey the object code in, or embodied in, a physical product
228 | (including a physical distribution medium), accompanied by a
229 | written offer, valid for at least three years and valid for as
230 | long as you offer spare parts or customer support for that product
231 | model, to give anyone who possesses the object code either (1) a
232 | copy of the Corresponding Source for all the software in the
233 | product that is covered by this License, on a durable physical
234 | medium customarily used for software interchange, for a price no
235 | more than your reasonable cost of physically performing this
236 | conveying of source, or (2) access to copy the
237 | Corresponding Source from a network server at no charge.
238 |
239 | c) Convey individual copies of the object code with a copy of the
240 | written offer to provide the Corresponding Source. This
241 | alternative is allowed only occasionally and noncommercially, and
242 | only if you received the object code with such an offer, in accord
243 | with subsection 6b.
244 |
245 | d) Convey the object code by offering access from a designated
246 | place (gratis or for a charge), and offer equivalent access to the
247 | Corresponding Source in the same way through the same place at no
248 | further charge. You need not require recipients to copy the
249 | Corresponding Source along with the object code. If the place to
250 | copy the object code is a network server, the Corresponding Source
251 | may be on a different server (operated by you or a third party)
252 | that supports equivalent copying facilities, provided you maintain
253 | clear directions next to the object code saying where to find the
254 | Corresponding Source. Regardless of what server hosts the
255 | Corresponding Source, you remain obligated to ensure that it is
256 | available for as long as needed to satisfy these requirements.
257 |
258 | e) Convey the object code using peer-to-peer transmission, provided
259 | you inform other peers where the object code and Corresponding
260 | Source of the work are being offered to the general public at no
261 | charge under subsection 6d.
262 |
263 | A separable portion of the object code, whose source code is excluded from the
264 | Corresponding Source as a System Library, need not be included in conveying the
265 | object code work.
266 |
267 | A "User Product" is either (1) a "consumer product", which means any tangible
268 | personal property which is normally used for personal, family, or household
269 | purposes, or (2) anything designed or sold for incorporation into a dwelling. In
270 | determining whether a product is a consumer product, doubtful cases shall be
271 | resolved in favor of coverage. For a particular product received by a particular
272 | user, "normally used" refers to a typical or common use of that class of
273 | product, regardless of the status of the particular user or of the way in which
274 | the particular user actually uses, or expects or is expected to use, the
275 | product. A product is a consumer product regardless of whether the product has
276 | substantial commercial, industrial or non-consumer uses, unless such uses
277 | represent the only significant mode of use of the product.
278 |
279 | "Installation Information" for a User Product means any methods, procedures,
280 | authorization keys, or other information required to install and execute
281 | modified versions of a covered work in that User Product from a modified version
282 | of its Corresponding Source. The information must suffice to ensure that the
283 | continued functioning of the modified object code is in no case prevented or
284 | interfered with solely because modification has been made.
285 |
286 | If you convey an object code work under this section in, or with, or
287 | specifically for use in, a User Product, and the conveying occurs as part of a
288 | transaction in which the right of possession and use of the User Product is
289 | transferred to the recipient in perpetuity or for a fixed term (regardless of
290 | how the transaction is characterized), the Corresponding Source conveyed under
291 | this section must be accompanied by the Installation Information. But this
292 | requirement does not apply if neither you nor any third party retains the
293 | ability to install modified object code on the User Product (for example, the
294 | work has been installed in ROM).
295 |
296 | The requirement to provide Installation Information does not include a
297 | requirement to continue to provide support service, warranty, or updates for a
298 | work that has been modified or installed by the recipient, or for the User
299 | Product in which it has been modified or installed. Access to a network may be
300 | denied when the modification itself materially and adversely affects the
301 | operation of the network or violates the rules and protocols for communication
302 | across the network.
303 |
304 | Corresponding Source conveyed, and Installation Information provided, in accord
305 | with this section must be in a format that is publicly documented (and with an
306 | implementation available to the public in source code form), and must require no
307 | special password or key for unpacking, reading or copying.
308 |
309 | 7. Additional Terms.
310 |
311 | "Additional permissions" are terms that supplement the terms of this License by
312 | making exceptions from one or more of its conditions. Additional permissions
313 | that are applicable to the entire Program shall be treated as though they were
314 | included in this License, to the extent that they are valid under applicable
315 | law. If additional permissions apply only to part of the Program, that part may
316 | be used separately under those permissions, but the entire Program remains
317 | governed by this License without regard to the additional permissions.
318 |
319 | When you convey a copy of a covered work, you may at your option remove any
320 | additional permissions from that copy, or from any part of it. (Additional
321 | permissions may be written to require their own removal in certain cases when
322 | you modify the work.) You may place additional permissions on material, added
323 | by you to a covered work, for which you have or can give appropriate copyright
324 | permission.
325 |
326 | Notwithstanding any other provision of this License, for material you add to a
327 | covered work, you may (if authorized by the copyright holders of that material)
328 | supplement the terms of this License with terms:
329 |
330 | a) Disclaiming warranty or limiting liability differently from the
331 | terms of sections 15 and 16 of this License; or
332 |
333 | b) Requiring preservation of specified reasonable legal notices or
334 | author attributions in that material or in the Appropriate Legal
335 | Notices displayed by works containing it; or
336 |
337 | c) Prohibiting misrepresentation of the origin of that material, or
338 | requiring that modified versions of such material be marked in
339 | reasonable ways as different from the original version; or
340 |
341 | d) Limiting the use for publicity purposes of names of licensors or
342 | authors of the material; or
343 |
344 | e) Declining to grant rights under trademark law for use of some
345 | trade names, trademarks, or service marks; or
346 |
347 | f) Requiring indemnification of licensors and authors of that
348 | material by anyone who conveys the material (or modified versions of
349 | it) with contractual assumptions of liability to the recipient, for
350 | any liability that these contractual assumptions directly impose on
351 | those licensors and authors.
352 |
353 | All other non-permissive additional terms are considered "further restrictions"
354 | within the meaning of section 10. If the Program as you received it, or any part
355 | of it, contains a notice stating that it is governed by this License along with
356 | a term that is a further restriction, you may remove that term. If a license
357 | document contains a further restriction but permits relicensing or conveying
358 | under this License, you may add to a covered work material governed by the terms
359 | of that license document, provided that the further restriction does not survive
360 | such relicensing or conveying.
361 |
362 | If you add terms to a covered work in accord with this section, you must place,
363 | in the relevant source files, a statement of the additional terms that apply to
364 | those files, or a notice indicating where to find the applicable terms.
365 |
366 | Additional terms, permissive or non-permissive, may be stated in the form of a
367 | separately written license, or stated as exceptions; the above requirements
368 | apply either way.
369 |
370 | 8. Termination.
371 |
372 | You may not propagate or modify a covered work except as expressly provided
373 | under this License. Any attempt otherwise to propagate or modify it is void, and
374 | will automatically terminate your rights under this License (including any
375 | patent licenses granted under the third paragraph of section 11).
376 |
377 | However, if you cease all violation of this License, then your license from a
378 | particular copyright holder is reinstated (a)
379 | provisionally, unless and until the copyright holder explicitly and finally
380 | terminates your license, and (b) permanently, if the copyright holder fails to
381 | notify you of the violation by some reasonable means prior to 60 days after the
382 | cessation.
383 |
384 | Moreover, your license from a particular copyright holder is reinstated
385 | permanently if the copyright holder notifies you of the violation by some
386 | reasonable means, this is the first time you have received notice of violation
387 | of this License (for any work) from that copyright holder, and you cure the
388 | violation prior to 30 days after your receipt of the notice.
389 |
390 | Termination of your rights under this section does not terminate the licenses of
391 | parties who have received copies or rights from you under this License. If your
392 | rights have been terminated and not permanently reinstated, you do not qualify
393 | to receive new licenses for the same material under section 10.
394 |
395 | 9. Acceptance Not Required for Having Copies.
396 |
397 | You are not required to accept this License in order to receive or run a copy of
398 | the Program. Ancillary propagation of a covered work occurring solely as a
399 | consequence of using peer-to-peer transmission to receive a copy likewise does
400 | not require acceptance. However, nothing other than this License grants you
401 | permission to propagate or modify any covered work. These actions infringe
402 | copyright if you do not accept this License. Therefore, by modifying or
403 | propagating a covered work, you indicate your acceptance of this License to do
404 | so.
405 |
406 | 10. Automatic Licensing of Downstream Recipients.
407 |
408 | Each time you convey a covered work, the recipient automatically receives a
409 | license from the original licensors, to run, modify and propagate that work,
410 | subject to this License. You are not responsible for enforcing compliance by
411 | third parties with this License.
412 |
413 | An "entity transaction" is a transaction transferring control of an
414 | organization, or substantially all assets of one, or subdividing an
415 | organization, or merging organizations. If propagation of a covered work results
416 | from an entity transaction, each party to that transaction who receives a copy
417 | of the work also receives whatever licenses to the work the party's predecessor
418 | in interest had or could give under the previous paragraph, plus a right to
419 | possession of the Corresponding Source of the work from the predecessor in
420 | interest, if the predecessor has it or can get it with reasonable efforts.
421 |
422 | You may not impose any further restrictions on the exercise of the rights
423 | granted or affirmed under this License. For example, you may not impose a
424 | license fee, royalty, or other charge for exercise of rights granted under this
425 | License, and you may not initiate litigation
426 | (including a cross-claim or counterclaim in a lawsuit) alleging that any patent
427 | claim is infringed by making, using, selling, offering for sale, or importing
428 | the Program or any portion of it.
429 |
430 | 11. Patents.
431 |
432 | A "contributor" is a copyright holder who authorizes use under this License of
433 | the Program or a work on which the Program is based. The work thus licensed is
434 | called the contributor's "contributor version".
435 |
436 | A contributor's "essential patent claims" are all patent claims owned or
437 | controlled by the contributor, whether already acquired or hereafter acquired,
438 | that would be infringed by some manner, permitted by this License, of making,
439 | using, or selling its contributor version, but do not include claims that would
440 | be infringed only as a consequence of further modification of the contributor
441 | version. For purposes of this definition, "control" includes the right to grant
442 | patent sublicenses in a manner consistent with the requirements of this License.
443 |
444 | Each contributor grants you a non-exclusive, worldwide, royalty-free patent
445 | license under the contributor's essential patent claims, to make, use, sell,
446 | offer for sale, import and otherwise run, modify and propagate the contents of
447 | its contributor version.
448 |
449 | In the following three paragraphs, a "patent license" is any express agreement
450 | or commitment, however denominated, not to enforce a patent
451 | (such as an express permission to practice a patent or covenant not to sue for
452 | patent infringement). To "grant" such a patent license to a party means to make
453 | such an agreement or commitment not to enforce a patent against the party.
454 |
455 | If you convey a covered work, knowingly relying on a patent license, and the
456 | Corresponding Source of the work is not available for anyone to copy, free of
457 | charge and under the terms of this License, through a publicly available network
458 | server or other readily accessible means, then you must either (1) cause the
459 | Corresponding Source to be so available, or (2) arrange to deprive yourself of
460 | the benefit of the patent license for this particular work, or (3) arrange, in a
461 | manner consistent with the requirements of this License, to extend the patent
462 | license to downstream recipients. "Knowingly relying" means you have actual
463 | knowledge that, but for the patent license, your conveying the covered work in a
464 | country, or your recipient's use of the covered work in a country, would
465 | infringe one or more identifiable patents in that country that you have reason
466 | to believe are valid.
467 |
468 | If, pursuant to or in connection with a single transaction or arrangement, you
469 | convey, or propagate by procuring conveyance of, a covered work, and grant a
470 | patent license to some of the parties receiving the covered work authorizing
471 | them to use, propagate, modify or convey a specific copy of the covered work,
472 | then the patent license you grant is automatically extended to all recipients of
473 | the covered work and works based on it.
474 |
475 | A patent license is "discriminatory" if it does not include within the scope of
476 | its coverage, prohibits the exercise of, or is conditioned on the non-exercise
477 | of one or more of the rights that are specifically granted under this License.
478 | You may not convey a covered work if you are a party to an arrangement with a
479 | third party that is in the business of distributing software, under which you
480 | make payment to the third party based on the extent of your activity of
481 | conveying the work, and under which the third party grants, to any of the
482 | parties who would receive the covered work from you, a discriminatory patent
483 | license (a) in connection with copies of the covered work conveyed by you (or
484 | copies made from those copies), or (b) primarily for and in connection with
485 | specific products or compilations that contain the covered work, unless you
486 | entered into that arrangement, or that patent license was granted, prior to 28
487 | March 2007.
488 |
489 | Nothing in this License shall be construed as excluding or limiting any implied
490 | license or other defenses to infringement that may otherwise be available to you
491 | under applicable patent law.
492 |
493 | 12. No Surrender of Others' Freedom.
494 |
495 | If conditions are imposed on you (whether by court order, agreement or
496 | otherwise) that contradict the conditions of this License, they do not excuse
497 | you from the conditions of this License. If you cannot convey a covered work so
498 | as to satisfy simultaneously your obligations under this License and any other
499 | pertinent obligations, then as a consequence you may not convey it at all. For
500 | example, if you agree to terms that obligate you to collect a royalty for
501 | further conveying from those to whom you convey the Program, the only way you
502 | could satisfy both those terms and this License would be to refrain entirely
503 | from conveying the Program.
504 |
505 | 13. Remote Network Interaction; Use with the GNU General Public License.
506 |
507 | Notwithstanding any other provision of this License, if you modify the Program,
508 | your modified version must prominently offer all users interacting with it
509 | remotely through a computer network (if your version supports such interaction)
510 | an opportunity to receive the Corresponding Source of your version by providing
511 | access to the Corresponding Source from a network server at no charge, through
512 | some standard or customary means of facilitating copying of software. This
513 | Corresponding Source shall include the Corresponding Source for any work covered
514 | by version 3 of the GNU General Public License that is incorporated pursuant to
515 | the following paragraph.
516 |
517 | Notwithstanding any other provision of this License, you have permission to link
518 | or combine any covered work with a work licensed under version 3 of the GNU
519 | General Public License into a single combined work, and to convey the resulting
520 | work. The terms of this License will continue to apply to the part which is the
521 | covered work, but the work with which it is combined will remain governed by
522 | version 3 of the GNU General Public License.
523 |
524 | 14. Revised Versions of this License.
525 |
526 | The Free Software Foundation may publish revised and/or new versions of the GNU
527 | Affero General Public License from time to time. Such new versions will be
528 | similar in spirit to the present version, but may differ in detail to address
529 | new problems or concerns.
530 |
531 | Each version is given a distinguishing version number. If the Program specifies
532 | that a certain numbered version of the GNU Affero General Public License "or any
533 | later version" applies to it, you have the option of following the terms and
534 | conditions either of that numbered version or of any later version published by
535 | the Free Software Foundation. If the Program does not specify a version number
536 | of the GNU Affero General Public License, you may choose any version ever
537 | published by the Free Software Foundation.
538 |
539 | If the Program specifies that a proxy can decide which future versions of the
540 | GNU Affero General Public License can be used, that proxy's public statement of
541 | acceptance of a version permanently authorizes you to choose that version for
542 | the Program.
543 |
544 | Later license versions may give you additional or different permissions.
545 | However, no additional obligations are imposed on any author or copyright holder
546 | as a result of your choosing to follow a later version.
547 |
548 | 15. Disclaimer of Warranty.
549 |
550 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
551 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER
552 | PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
553 | EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
554 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE
555 | QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
556 | DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
557 |
558 | 16. Limitation of Liability.
559 |
560 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY
561 | COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS
562 | PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
563 | INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
564 | THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
565 | INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE
566 | PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY
567 | HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
568 |
569 | 17. Interpretation of Sections 15 and 16.
570 |
571 | If the disclaimer of warranty and limitation of liability provided above cannot
572 | be given local legal effect according to their terms, reviewing courts shall
573 | apply local law that most closely approximates an absolute waiver of all civil
574 | liability in connection with the Program, unless a warranty or assumption of
575 | liability accompanies a copy of the Program in return for a fee.
576 |
577 | END OF TERMS AND CONDITIONS
578 |
579 | How to Apply These Terms to Your New Programs
580 |
581 | If you develop a new program, and you want it to be of the greatest possible use
582 | to the public, the best way to achieve this is to make it free software which
583 | everyone can redistribute and change under these terms.
584 |
585 | To do so, attach the following notices to the program. It is safest to attach
586 | them to the start of each source file to most effectively state the exclusion of
587 | warranty; and each file should have at least the "copyright" line and a pointer
588 | to where the full notice is found.
589 |
590 |
591 | Copyright (C)
592 |
593 | This program is free software: you can redistribute it and/or modify
594 | it under the terms of the GNU Affero General Public License as published
595 | by the Free Software Foundation, either version 3 of the License, or
596 | (at your option) any later version.
597 |
598 | This program is distributed in the hope that it will be useful,
599 | but WITHOUT ANY WARRANTY; without even the implied warranty of
600 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
601 | GNU Affero General Public License for more details.
602 |
603 | You should have received a copy of the GNU Affero General Public License
604 | along with this program. If not, see .
605 |
606 | Also add information on how to contact you by electronic and paper mail.
607 |
608 | If your software can interact with users remotely through a computer network,
609 | you should also make sure that it provides a way for users to get its source.
610 | For example, if your program is a web application, its interface could display
611 | a "Source" link that leads users to an archive of the code. There are many ways
612 | you could offer source, and different solutions will be better for different
613 | programs; see section 13 for the specific requirements.
614 |
615 | You should also get your employer (if you work as a programmer) or school, if
616 | any, to sign a "copyright disclaimer" for the program, if necessary. For more
617 | information on this, and how to apply and follow the GNU AGPL, see
618 | .
619 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Enumhancer
2 |
3 | [](https://github.com/henzeb/enumhancer/actions)
4 | [](https://codeclimate.com/github/henzeb/enumhancer/test_coverage)
5 | [](https://packagist.org/packages/henzeb/enumhancer)
6 | [](https://packagist.org/packages/henzeb/enumhancer)
7 | [](https://packagist.org/packages/henzeb/enumhancer)
8 |
9 | This package is your framework-agnostic Swiss Army knife when it comes to
10 | PHP 8.1's native enums. In this package you will find a lot of tools for
11 | the most common use cases, and more will be added in the future.
12 |
13 | If you have an idea, or you miss something that needs to be added, just let me
14 | know.
15 |
16 | Enumhancer is case-agnostic, which means `Enum` equals `ENUM` equals `enum`.
17 | This is done with the idea that it is useless to have two different enums
18 | having the same name and different casing.
19 |
20 | It is also type-agnostic. This way you can steer clear as much as possible
21 | from the extra work that comes with backed enums.
22 |
23 | Note: While most functionality that also exists in Spatie's PHP Enum is made
24 | backwards compatible to allow for an easy migration to PHP native enums,
25 | currently this is not the case for their laravel package, PHPUnit assertions or
26 | Faker Provider.
27 |
28 | ## Installation
29 |
30 | You can install the package via composer:
31 |
32 | ```bash
33 | composer require henzeb/enumhancer
34 | ```
35 |
36 | ## Usage
37 |
38 | You can simply add the `Enhancers` trait to your `enum` in order to use almost
39 | all functionality of this package. All features should work with `basic` enums as
40 | well as `backed` enums' unless stated otherwise.
41 |
42 | ```php
43 | use Henzeb\Enumhancer\Concerns\Enhancers;
44 |
45 | enum YourEnum {
46 | use Enhancers;
47 |
48 | // ...
49 | }
50 | ```
51 |
52 | You can also just use one of the features by using the specific trait for that
53 | feature.
54 |
55 | Note: all traits can be used next to each other, except for `Mappers`, which has
56 | implemented the methods of `Getters`, `Extractor` and `Reporters`.
57 |
58 | ### Features
59 |
60 | - [Attributes](docs/attributes.md)
61 | - [Bitmasks](docs/bitmasks.md)
62 | - [Constructor](docs/constructor.md)
63 | - [Comparison](docs/comparison.md)
64 | - [Configure](docs/configure.md)
65 | - [Defaults](docs/defaults.md)
66 | - [Dropdown](docs/dropdown.md)
67 | - [Extractor](docs/extractor.md)
68 | - [From](docs/from.md)
69 | - [Getters](docs/getters.md)
70 | - [Labels](docs/labels.md)
71 | - [Macros](docs/macros.md)
72 | - [Mappers](docs/mappers.md)
73 | - [Properties](docs/properties.md)
74 | - [Reporters](docs/reporters.md)
75 | - [State](docs/state.md)
76 | - [Subset](docs/subset.md)
77 | - [Value](docs/value.md)
78 |
79 | ### Helper functions
80 |
81 | - [Backing](docs/functions.md#backing)
82 | - [Name](docs/functions.md#name)
83 | - [Value](docs/functions.md#value)
84 |
85 | ### Development
86 |
87 | - [IDE-Helper](docs/ide-helper.md)
88 | - [PHPstan](docs/phpstan.md)
89 |
90 | ### Laravel specific Features
91 |
92 | - [Blade](docs/blade.md)
93 | - [Casting](docs/casting.md)
94 | - [FormRequest](docs/formrequests.md)
95 | - [Implicit (basic) enum binding](docs/binding.md)
96 | - [Validation](docs/laravel.validation.md)
97 |
98 | ### Laravel's auto-discovery
99 |
100 | When you are installing this package into a laravel project, Enumhancer will
101 | automatically set macro's for the `validation rules` and sets the global
102 | `Reporter` for the `getOrReport` methods, so that it will use Laravel's
103 | `Log` facade.
104 |
105 | If you don't want that to happen, you can tell Laravel not to discover the
106 | package.
107 |
108 | ```composer
109 | "extra": {
110 | "laravel": {
111 | "dont-discover": [
112 | "henzeb/enumhancer"
113 | ]
114 | }
115 | }
116 | ```
117 |
118 | ### Testing
119 |
120 | ```bash
121 | composer test
122 | ```
123 |
124 | #### PHPStan integration
125 |
126 | If you are using PHPStan for static analysis, you can enable the extension.
127 |
128 | Add the following to your projects phpstan.neon:
129 |
130 | ````
131 | includes:
132 | - vendor/henzeb/enumhancer/extension.neon
133 | ````
134 |
135 | ## Changelog
136 |
137 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed
138 | recently.
139 |
140 | ## Contributing
141 |
142 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
143 |
144 | ### Security
145 |
146 | If you discover any security related issues, please email
147 | henzeberkheij@gmail.com instead of using the issue tracker.
148 |
149 | ## Credits
150 |
151 | - [Henze Berkheij](https://github.com/henzeb)
152 |
153 | ## License
154 |
155 | The GNU AGPLv. Please see [License File](LICENSE.md) for more information.
156 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "henzeb/enumhancer",
3 | "description": "Your framework-agnostic Swiss Army knife for PHP 8.1+ native enums",
4 | "keywords": [
5 | "henzeb",
6 | "enumhancer",
7 | "enums",
8 | "enum",
9 | "dropdown",
10 | "select",
11 | "list",
12 | "enumerators",
13 | "enumerator",
14 | "mappers",
15 | "labels",
16 | "reporting",
17 | "report",
18 | "logging",
19 | "stringable",
20 | "string",
21 | "comparison",
22 | "blade",
23 | "native",
24 | "backed",
25 | "basic",
26 | "unit",
27 | "unitEnum",
28 | "backedEnum",
29 | "from",
30 | "tryFrom",
31 | "extract",
32 | "extractor",
33 | "properties",
34 | "subset",
35 | "value",
36 | "casting",
37 | "cast",
38 | "eloquent",
39 | "laravel",
40 | "spatie",
41 | "default",
42 | "php 8.1",
43 | "8.1",
44 | "php 8.2",
45 | "8.2",
46 | "state",
47 | "machine",
48 | "transition",
49 | "validation",
50 | "rules",
51 | "bitmask",
52 | "bitmasks",
53 | "macros",
54 | "macroable",
55 | "binding",
56 | "ide-helper",
57 | "implicit"
58 | ],
59 | "homepage": "https://github.com/henzeb/enumhancer",
60 | "license": "AGPL-3.0-only",
61 | "type": "library",
62 | "authors": [
63 | {
64 | "name": "Henze Berkheij",
65 | "email": "henzeberkheij@gmail.com",
66 | "role": "Developer"
67 | }
68 | ],
69 | "require": {
70 | "php": "^8.1"
71 | },
72 | "require-dev": {
73 | "composer/composer": "2.8.9",
74 | "henzeb/enumhancer-ide-helper": "main-dev",
75 | "mockery/mockery": "^1.5",
76 | "orchestra/testbench": "^8|^9|^10",
77 | "pestphp/pest": "^2.0|^3.0",
78 | "phpstan/phpstan": "^2.0"
79 | },
80 | "autoload": {
81 | "files": [
82 | "src/Functions/Functions.php"
83 | ],
84 | "psr-4": {
85 | "Henzeb\\Enumhancer\\": "src/"
86 | }
87 | },
88 | "autoload-dev": {
89 | "psr-4": {
90 | "Henzeb\\Enumhancer\\Tests\\": "tests/"
91 | }
92 | },
93 | "scripts": {
94 | "test": "vendor/bin/pest",
95 | "test-coverage-txt": "XDEBUG_MODE=coverage vendor/bin/pest --coverage --coverage-text",
96 | "test-coverage": "XDEBUG_MODE=coverage vendor/bin/pest --coverage --coverage-html coverage",
97 | "test-dox": "vendor/bin/pest --testdox"
98 | },
99 | "config": {
100 | "sort-packages": true,
101 | "allow-plugins": {
102 | "infection/extension-installer": true,
103 | "pestphp/pest-plugin": true
104 | }
105 | },
106 | "extra": {
107 | "laravel": {
108 | "providers": [
109 | "Henzeb\\Enumhancer\\Laravel\\Providers\\EnumhancerServiceProvider"
110 | ]
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/extension.neon:
--------------------------------------------------------------------------------
1 | rules:
2 | - Henzeb\Enumhancer\PHPStan\Constants\Rules\DefaultConstantRule
3 | - Henzeb\Enumhancer\PHPStan\Constants\Rules\MapperConstantRule
4 | - Henzeb\Enumhancer\PHPStan\Constants\Rules\StrictConstantRule
5 |
6 | services:
7 | -
8 | class: Henzeb\Enumhancer\PHPStan\Constants\DefaultConstantAlwaysUsed
9 | tags:
10 | - phpstan.constants.alwaysUsedClassConstantsExtension
11 |
12 | -
13 | class: Henzeb\Enumhancer\PHPStan\Constants\BitmaskConstantAlwaysUsed
14 | tags:
15 | - phpstan.constants.alwaysUsedClassConstantsExtension
16 |
17 | -
18 | class: Henzeb\Enumhancer\PHPStan\Constants\StrictConstantAlwaysUsed
19 | tags:
20 | - phpstan.constants.alwaysUsedClassConstantsExtension
21 |
22 | -
23 | class: Henzeb\Enumhancer\PHPStan\Constants\MapperConstantAlwaysUsed
24 | tags:
25 | - phpstan.constants.alwaysUsedClassConstantsExtension
26 |
27 | -
28 | class: Henzeb\Enumhancer\PHPStan\Methods\EnumMacrosMethodsClassReflection
29 | tags:
30 | - phpstan.broker.methodsClassReflectionExtension
31 |
32 | -
33 | class: Henzeb\Enumhancer\PHPStan\Methods\EnumComparisonMethodsClassReflection
34 | tags:
35 | - phpstan.broker.methodsClassReflectionExtension
36 |
37 | -
38 | class: Henzeb\Enumhancer\PHPStan\Methods\EnumConstructorMethodsClassReflection
39 | tags:
40 | - phpstan.broker.methodsClassReflectionExtension
41 |
42 | -
43 | class: Henzeb\Enumhancer\PHPStan\Methods\EnumStateMethodsClassReflection
44 | tags:
45 | - phpstan.broker.methodsClassReflectionExtension
46 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - phpstan-baseline.neon
3 | - vendor/nunomaduro/larastan/extension.neon
4 | - vendor/phpstan/phpstan/conf/bleedingEdge.neon
5 | parameters:
6 | scanDirectories:
7 | - src
8 | paths:
9 | - src
10 | level: 6
11 |
--------------------------------------------------------------------------------
/src/Composer/IdeHelper.php:
--------------------------------------------------------------------------------
1 | isDevMode()) {
18 | return;
19 | }
20 |
21 | $composer = $event->getComposer();
22 | $config = $composer->getConfig();
23 | $package = $composer->getPackage();
24 | $vendorDir = $config->get('vendor-dir');
25 |
26 | self::requireAutoloader($vendorDir);
27 |
28 | $alreadyBootstrapped = self::requireUserDefinedBootstrap($package);
29 |
30 | if (!$alreadyBootstrapped) {
31 | self::requireLaravel($package);
32 | }
33 |
34 | if (self::hasIdeHelperInstalled($package)) {
35 | EnumIdeHelper::postAutoloadDump($event);
36 | }
37 | }
38 |
39 | private static function hasIdeHelperInstalled(RootPackageInterface $package): bool
40 | {
41 | $filtered = array_filter(
42 | $package->getDevRequires(),
43 | fn($name) => $name === 'henzeb/enumhancer-ide-helper',
44 | ARRAY_FILTER_USE_KEY
45 | );
46 |
47 | return count($filtered) > 0;
48 | }
49 |
50 | private static function requireAutoloader(string $vendorDir): void
51 | {
52 | $file = $vendorDir . '/autoload.php';
53 |
54 | if (file_exists($vendorDir . '/autoload_runtime.php')) {
55 | $file = $vendorDir . '/autoload_runtime.php';
56 | }
57 |
58 | require_once $file;
59 | }
60 |
61 | private static function requireUserDefinedBootstrap(RootPackageInterface $package): bool
62 | {
63 | $file = $package->getExtra()['enumhancer']['ide-helper'] ?? null;
64 |
65 | if ($file) {
66 | if (!file_exists($file)) {
67 | throw new RuntimeException(
68 | sprintf(
69 | 'require_once(%s): Failed to open stream: No such file or directory',
70 | $file
71 | )
72 | );
73 | }
74 | require_once $file;
75 | return true;
76 | }
77 | return false;
78 | }
79 |
80 | private static function requireLaravel(RootPackageInterface $package): void
81 | {
82 | $filtered = array_filter(
83 | $package->getRequires(),
84 | fn(string $name) => $name === 'laravel/framework',
85 | ARRAY_FILTER_USE_KEY
86 | );
87 | $realPath = realpath('./bootstrap/app.php');
88 | $hasLaravel = count($filtered) > 0
89 | && is_string($realPath)
90 | && file_exists($realPath);
91 |
92 | if ($hasLaravel) {
93 | $app = require_once realpath('./bootstrap/app.php');
94 |
95 | /**
96 | * already bootstrapped
97 | */
98 | if (is_bool($app)) {
99 | return;
100 | }
101 |
102 | /**
103 | * @var Kernel $kernel
104 | */
105 | $kernel = $app->make(Kernel::class);
106 |
107 | $kernel->bootstrap();
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/Concerns/Attributes.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | public static function bits(): array
19 | {
20 | return EnumBitmasks::getCaseBits(self::class);
21 | }
22 |
23 | public static function mask(self|string|int ...$enums): Bitmask
24 | {
25 | return EnumBitmasks::getMask(self::class, ...$enums);
26 | }
27 |
28 | public static function fromMask(int $mask): Bitmask
29 | {
30 | return EnumBitmasks::fromMask(self::class, $mask);
31 | }
32 |
33 | public static function tryMask(
34 | ?int $mask,
35 | BitMask|self|string|int|null ...$enums
36 | ): Bitmask {
37 | return EnumBitmasks::tryMask(
38 | self::class,
39 | $mask,
40 | ...$enums
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Concerns/Comparison.php:
--------------------------------------------------------------------------------
1 | equals($equals);
20 | }
21 |
22 | public function isNot(UnitEnum|string|int|null $equals): bool
23 | {
24 | return !$this->is($equals);
25 | }
26 |
27 | public function isIn(UnitEnum|string|int|null ...$equals): bool
28 | {
29 | return $this->equals(...$equals);
30 | }
31 |
32 | public function isNotIn(UnitEnum|string|int|null ...$equals): bool
33 | {
34 | return !$this->equals(...$equals);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Concerns/Configure.php:
--------------------------------------------------------------------------------
1 | isDefault();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Concerns/Dropdown.php:
--------------------------------------------------------------------------------
1 | dropdown($keepEnumCase);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Concerns/Enhancers.php:
--------------------------------------------------------------------------------
1 | isTransitionAllowed($state, $hook)) {
28 | $hook?->execute($this, $state);
29 | self::transitionHook()?->execute($this, $state);
30 |
31 | return $state;
32 | }
33 |
34 | throw new IllegalEnumTransitionException($this, $state);
35 | }
36 |
37 | /**
38 | * @param string|int|static $state
39 | * @param TransitionHook|null $hook
40 | * @return static
41 | * @throws IllegalEnumTransitionException
42 | */
43 | public function to(self|string|int $state, TransitionHook|null $hook = null): static
44 | {
45 | return $this->transitionTo($state, $hook);
46 | }
47 |
48 | public function tryTo(self|string|int $state, TransitionHook|null $hook = null): static
49 | {
50 | if ($this->isTransitionAllowed($state, $hook)) {
51 | return $this->transitionTo($state, $hook);
52 | }
53 | return $this;
54 | }
55 |
56 | /**
57 | * @param self|string|int $state
58 | * @param TransitionHook|null $hook
59 | * @return bool
60 | */
61 | public function isTransitionAllowed(self|string|int $state, TransitionHook|null $hook = null): bool
62 | {
63 | /**
64 | * @var $this UnitEnum
65 | */
66 | $state = EnumGetters::tryCast(self::class, $state);
67 |
68 | return $state !== null && in_array($state, $this->allowedTransitions($hook));
69 | }
70 |
71 | /**
72 | * @param TransitionHook|null $hook
73 | * @return static[]
74 | */
75 | public function allowedTransitions(TransitionHook|null $hook = null): array
76 | {
77 | return EnumState::allowedTransitions(
78 | $this,
79 | ...array_filter([$hook, self::transitionHook()])
80 | );
81 | }
82 |
83 | /**
84 | * @return static[]
85 | */
86 | public static function transitions(): array
87 | {
88 | return EnumState::transitions(self::class, self::customTransitions());
89 | }
90 |
91 | /**
92 | * @return static[]
93 | */
94 | protected static function customTransitions(): array
95 | {
96 | return EnumProperties::get(self::class, EnumProperties::reservedWord('state')) ?? [];
97 | }
98 |
99 | protected static function transitionHook(): ?TransitionHook
100 | {
101 | return EnumProperties::get(
102 | self::class,
103 | EnumProperties::reservedWord('hooks')
104 | );
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/Concerns/Subset.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | public static function without(self ...$enums): EnumSubsetMethods
15 | {
16 | return new EnumSubsetMethods(
17 | self::class,
18 | ...array_filter(
19 | self::cases(),
20 | function (UnitEnum $case) use ($enums) {
21 | return !in_array($case, $enums);
22 | }
23 | )
24 | );
25 | }
26 |
27 | /**
28 | * @param static[] $enums
29 | * @return EnumSubsetMethods
30 | */
31 | public static function of(self ...$enums): EnumSubsetMethods
32 | {
33 | return new EnumSubsetMethods(
34 | self::class,
35 | ...($enums ?: self::cases())
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Concerns/Value.php:
--------------------------------------------------------------------------------
1 | >
24 | */
25 | abstract protected function mappable(): array;
26 |
27 | public function makeFlipped(string|null $prefix = null): self
28 | {
29 | $this->flip = true;
30 | $this->flipped = null;
31 | $this->flipPrefix = $prefix;
32 |
33 | return $this;
34 | }
35 |
36 | private function flipMethod(string|null $prefix = null): self
37 | {
38 | return (clone $this)->makeFlipped($prefix);
39 | }
40 |
41 | private function parseValue(mixed $value): string|int|null
42 | {
43 | if (null === $value) {
44 | $value = null;
45 | }
46 |
47 | if (empty($value)) {
48 | $value = null;
49 | }
50 |
51 | if ($value instanceof UnitEnum) {
52 | $value = $value->name;
53 | }
54 |
55 | if (!is_string($value) && !is_int($value)) {
56 | $value = null;
57 | }
58 |
59 | return $value;
60 | }
61 |
62 | private function getMapWithPrefix(string|null $prefix = null): array
63 | {
64 | /**
65 | * @var array $mappable
66 | */
67 | $mappable = $this->mappable();
68 | return array_change_key_case($mappable[$prefix] ?? []);
69 | }
70 |
71 | private function getMap(string|null $prefix = null): array
72 | {
73 | if ($this->flip) {
74 | return $this->flipped ?? $this->flipped = $this->flipMappable($prefix);
75 | }
76 |
77 | return array_change_key_case($this->mappable());
78 | }
79 |
80 | private function mapMethod(string|UnitEnum $key, string|null $prefix = null): string|int|null
81 | {
82 | $key = $this->parseValue($key);
83 |
84 | if (is_string($key)) {
85 | $key = strtolower($key);
86 | }
87 |
88 | return $this->parseValue(
89 | ($this->flip ? null : $this->getMapWithPrefix($prefix)[$key] ?? null)
90 | ??
91 | $this->getMap($prefix)[$key]
92 | ?? null
93 | );
94 | }
95 |
96 | private function definedMethod(string|UnitEnum $key, string|null $prefix = null): bool
97 | {
98 | return (bool)$this->map($key, $prefix);
99 | }
100 |
101 | private function keysMethod(string|null $prefix = null): array
102 | {
103 | if (!$prefix || $this->flip) {
104 | $mappable = $this->getMap($prefix);
105 | }
106 |
107 | if (!isset($mappable)) {
108 | $mappable = [...$this->getMap(), ...$this->getMapWithPrefix($prefix)];
109 | }
110 |
111 | return array_unique(
112 | array_merge(
113 | array_keys(
114 | array_filter(
115 | $mappable,
116 | function ($value) {
117 | return !is_array($value);
118 | }
119 | ),
120 | ),
121 | is_array($mappable[$prefix] ?? null) ? array_keys($mappable[$prefix]) : []
122 | )
123 | );
124 | }
125 |
126 | private function flipMappable(string|null $prefix = null): array
127 | {
128 | return array_change_key_case(
129 | array_flip(
130 | array_filter(
131 | array_map(
132 | fn($value) => is_array($value) ? null : $this->parseValue($value),
133 | $this->getMapWithPrefix($prefix ?? $this->flipPrefix) ?: $this->mappable()
134 | )
135 | )
136 | )
137 | );
138 | }
139 |
140 | public function __call(string $name, array $arguments): mixed
141 | {
142 | return match ($name) {
143 | 'map' => $this->mapMethod(...$arguments),
144 | 'defined' => $this->definedMethod(...$arguments),
145 | 'keys' => $this->keysMethod(...$arguments),
146 | 'flip' => $this->flipMethod(...$arguments),
147 | default => $this->triggerError($name)
148 | };
149 | }
150 |
151 | public static function __callStatic(string $name, array $arguments): mixed
152 | {
153 | return self::newInstance()->$name(...$arguments);
154 | }
155 |
156 | private function triggerError(string $name): bool
157 | {
158 | return throw new ErrorException(
159 | sprintf(
160 | 'Uncaught Error: Call to undefined method %s::%s()',
161 | static::class,
162 | $name
163 | ),
164 | E_USER_ERROR
165 | );
166 | }
167 |
168 | public static function newInstance(mixed ...$parameters): static
169 | {
170 | return new static(...$parameters);
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/src/Contracts/Reporter.php:
--------------------------------------------------------------------------------
1 | getMethodName($from, $transitionTo);
13 |
14 | if (method_exists($this, $method)) {
15 | $this->$method();
16 | }
17 | }
18 |
19 | /**
20 | * @throws SyntaxException
21 | */
22 | final public function isAllowed(UnitEnum $from, UnitEnum $transitionTo): bool
23 | {
24 | $method = $this->getMethodName($from, $transitionTo, 'allows');
25 |
26 | if (method_exists($this, $method)) {
27 | $value = $this->$method();
28 | if (is_bool($value)) {
29 | return $value;
30 | }
31 |
32 | if (!is_null($value)) {
33 | throw new SyntaxException('true', $value);
34 | }
35 | }
36 |
37 | return true;
38 | }
39 |
40 | private function getMethodName(UnitEnum $from, UnitEnum $transitionTo, string $prefix = ''): string
41 | {
42 | return $prefix . $from->name . $transitionTo->name;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Enums/LogLevel.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | enum LogLevel
13 | {
14 | use Enhancers, Configure, Macros;
15 |
16 | case Debug;
17 | case Info;
18 | case Notice;
19 | case Warning;
20 | case Error;
21 | case Critical;
22 | case Alert;
23 | case Emergency;
24 | }
25 |
--------------------------------------------------------------------------------
/src/Exceptions/EnumException.php:
--------------------------------------------------------------------------------
1 | name, $transitionTo->name)
13 | );
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Exceptions/InvalidBitmaskEnum.php:
--------------------------------------------------------------------------------
1 | name;
54 | }
55 |
56 | function name(?UnitEnum $enum): ?string
57 | {
58 | return n($enum);
59 | }
60 |
61 | /**
62 | * Returns a value equal to it's name when given enum is not backed by default
63 | *
64 | * @param UnitEnum|null $enum
65 | * @param bool $keepValueCase returns a lower cased enum name when it's a UnitEnum
66 | * @return string|int|null
67 | */
68 | function v(?UnitEnum $enum, bool $keepValueCase = true): string|int|null
69 | {
70 | if (!$enum) {
71 | return null;
72 | }
73 | return EnumValue::value($enum, $keepValueCase);
74 | }
75 |
76 |
77 | function value(?UnitEnum $enum, bool $keepValueCase = true): string|int|null
78 | {
79 | return v($enum, $keepValueCase);
80 | }
81 |
82 | /**
83 | * Returns a lowercase value equal to it's name when given enum is not backed.
84 | *
85 | * @param UnitEnum|null $enum
86 | * @return string|int|null
87 | */
88 | function vl(?UnitEnum $enum): string|int|null
89 | {
90 | return v($enum, false);
91 | }
92 |
93 | function valueLowercase(?UnitEnum $enum): string|int|null
94 | {
95 | return vl($enum);
96 | }
97 |
--------------------------------------------------------------------------------
/src/Helpers/Bitmasks/Bitmask.php:
--------------------------------------------------------------------------------
1 | bitmask);
18 | }
19 |
20 | public function has(UnitEnum|string|int $enum): bool
21 | {
22 | return $this->all($enum);
23 | }
24 |
25 | public function all(self|UnitEnum|string|int ...$bits): bool
26 | {
27 | $mask = EnumBitmasks::getBits($this->enumFQCN, ...$bits);
28 |
29 | if ($mask === 0) {
30 | return true;
31 | }
32 |
33 | return ($this->value() & $mask) === $mask;
34 | }
35 |
36 | public function any(self|UnitEnum|string|int ...$bits): bool
37 | {
38 | $mask = EnumBitmasks::getBits($this->enumFQCN, ...$bits);
39 |
40 | if ($mask === 0) {
41 | return true;
42 | }
43 |
44 | foreach ($bits as $bit) {
45 | if ($this->has($bit)) {
46 | return true;
47 | }
48 | }
49 |
50 | return false;
51 | }
52 |
53 | public function xor(self|UnitEnum|string|int ...$bits): bool
54 | {
55 | if (count($bits) === 0) {
56 | return false;
57 | }
58 |
59 | $result = false;
60 |
61 | foreach ($bits as $bit) {
62 | $hasBit = $this->has($bit);
63 |
64 | if ($hasBit && $result) {
65 | return false;
66 | }
67 |
68 | if ($hasBit) {
69 | $result = true;
70 | }
71 | }
72 |
73 | return $result;
74 | }
75 |
76 | public function none(self|UnitEnum|string|int ...$bits): bool
77 | {
78 | $mask = EnumBitmasks::getBits($this->enumFQCN, ...$bits);
79 |
80 | if ($mask === 0) {
81 | return true;
82 | }
83 |
84 | return !$this->any(...$bits);
85 | }
86 |
87 | public function value(): int
88 | {
89 | return $this->bitmask;
90 | }
91 |
92 | public function cases(): array
93 | {
94 | $matchingCases = [];
95 |
96 | foreach ($this->enumFQCN::cases() as $case) {
97 | $value = EnumBitmasks::getBit($case);
98 | if ($this->bitmask === $value) {
99 | return [$case];
100 | }
101 | if ($this->bitmask & $value) {
102 | $matchingCases[] = $case;
103 | }
104 | }
105 |
106 | return $matchingCases;
107 | }
108 |
109 | public function __toString(): string
110 | {
111 | return (string)$this->value();
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/Helpers/Bitmasks/Concerns/BitmaskModifiers.php:
--------------------------------------------------------------------------------
1 | bitmask |= EnumBitmasks::getBits($this->enumFQCN, ...$enums);
13 |
14 | return $this;
15 | }
16 |
17 | public function unset(self|UnitEnum|string|int ...$enums): self
18 | {
19 | $this->bitmask &= ~EnumBitmasks::getBits($this->enumFQCN, ...$enums);
20 |
21 | return $this;
22 | }
23 |
24 | public function toggle(self|UnitEnum|string|int ...$enums): self
25 | {
26 | foreach ($enums as $enum) {
27 | $this->has($enum) ? $this->unset($enum) : $this->set($enum);
28 | }
29 |
30 | return $this;
31 | }
32 |
33 | public function clear(): self
34 | {
35 | $this->bitmask = 0;
36 |
37 | return $this;
38 | }
39 |
40 | public function copy(): self
41 | {
42 | return new self(
43 | $this->forEnum(),
44 | $this->value()
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Helpers/Bitmasks/Concerns/BitmaskValidators.php:
--------------------------------------------------------------------------------
1 | enumFQCN === $enumclass;
15 | }
16 |
17 | public function forOrFail(string $enumClass): bool
18 | {
19 | if ($this->for($enumClass)) {
20 | return true;
21 | }
22 |
23 | EnumBitmasks::throwMismatch(
24 | $this->forEnum(),
25 | $enumClass
26 | );
27 | }
28 |
29 | public function forEnum(): string
30 | {
31 | return $this->enumFQCN;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Helpers/Bitmasks/EnumBitmasks.php:
--------------------------------------------------------------------------------
1 | > 1);
31 | }
32 |
33 | public static function isBit(mixed $bit): bool
34 | {
35 | return self::isInt($bit) && (self::countSetBits($bit) === 1 || $bit === 0);
36 | }
37 |
38 | public static function validateBitmaskCases(string $enum): void
39 | {
40 | EnumCheck::check($enum);
41 |
42 | if (in_array($enum, self::$isValid)
43 | || !is_a($enum, BackedEnum::class, true)
44 | || self::ignoreIntValues($enum)
45 | ) {
46 | self::$isValid[] = $enum;
47 | self::$isValid = array_unique(self::$isValid);
48 | return;
49 | }
50 |
51 | self::validateBitCases($enum);
52 |
53 | self::$isValid[] = $enum;
54 | }
55 |
56 | public static function ignoreIntValues(string $enum): bool
57 | {
58 | /**
59 | * @var UnitEnum $enum
60 | */
61 |
62 | EnumCheck::check($enum);
63 |
64 | foreach ((new ReflectionClass($enum))->getConstants() as $constant => $value) {
65 | if (strtolower($constant) === 'bit_values' and is_bool($value)) {
66 | return !$value;
67 | }
68 | }
69 | return true;
70 | }
71 |
72 | public static function isModifier(BackedEnum|string $enum): bool
73 | {
74 | /**
75 | * @var UnitEnum $enum
76 | */
77 |
78 | EnumCheck::check($enum);
79 |
80 | if (self::ignoreIntValues($enum)) {
81 | return false;
82 | }
83 |
84 | foreach ((new ReflectionClass($enum))->getConstants() as $constant => $value) {
85 | if (strtolower($constant) === 'bit_modifier' and is_bool($value)) {
86 | return $value;
87 | }
88 | }
89 | return false;
90 | }
91 |
92 | private static function validateBitCases(BackedEnum|string $enum): void
93 | {
94 | if (self::isModifier($enum)) {
95 | return;
96 | }
97 |
98 | foreach ($enum::cases() as $case) {
99 | if (self::validateBitCase($case)) {
100 | self::triggerInvalidBitCase($case::class, $case);
101 | }
102 | }
103 | }
104 |
105 | private static function validateBitCase(BackedEnum $case): bool
106 | {
107 | return self::isInt($case->value) && !self::isBit($case->value);
108 | }
109 |
110 | public static function getBit(UnitEnum $enum): int
111 | {
112 | self::validateBitmaskCases($enum::class);
113 |
114 | $value = EnumValue::value($enum);
115 |
116 | if (self::ignoreIntValues($enum::class)
117 | || !is_int($value)
118 | ) {
119 | return pow(
120 | 2,
121 | (int)array_search($enum, $enum::cases())
122 | );
123 | }
124 |
125 | return $value;
126 | }
127 |
128 | public static function getMask(string $class, UnitEnum|string|int ...$enums): Bitmask
129 | {
130 | return new Bitmask(
131 | $class,
132 | self::getBits($class, ...$enums)
133 | );
134 | }
135 |
136 | public static function getBits(string|UnitEnum $class, Bitmask|UnitEnum|string|int ...$values): int
137 | {
138 | $bits = 0;
139 |
140 | foreach ($values as $value) {
141 | $bits |= self::castToBits($value, $class);
142 | }
143 |
144 | return $bits;
145 | }
146 |
147 | private static function castToBits(Bitmask|UnitEnum|string|int $value, string|UnitEnum $class): int
148 | {
149 | $class = is_object($class) ? $class::class : $class;
150 |
151 | if ($value instanceof Bitmask) {
152 | self::forOrFail($class, $value);
153 | return $value->value();
154 | }
155 |
156 | $enum = EnumGetters::tryGet($class, $value);
157 |
158 | if ($enum) {
159 | return self::getBit($enum);
160 | }
161 |
162 | if (self::isInt($value) && self::isValidBitmask($class, $value)) {
163 | /**
164 | * @var int $value
165 | */
166 | return $value;
167 | }
168 |
169 | self::throwMismatch($class, gettype($value));
170 | }
171 |
172 | /**
173 | * @param string $class
174 | * @return array
175 | */
176 | public static function getCaseBits(string $class): array
177 | {
178 | /**
179 | * @var UnitEnum|string $class
180 | */
181 | $bits = [];
182 |
183 | foreach ($class::cases() as $bit) {
184 | $bits[self::getBit($bit)] = EnumLabels::getLabelOrName($bit);
185 | }
186 |
187 | return $bits;
188 | }
189 |
190 | public static function fromMask(string $enum, int $mask): Bitmask
191 | {
192 | /**
193 | * @var $enum UnitEnum|string
194 | */
195 |
196 | return new Bitmask(
197 | $enum,
198 | $mask
199 | );
200 | }
201 |
202 | public static function tryMask(string $enum, ?int $mask, Bitmask|UnitEnum|string|int|null ...$enums): Bitmask
203 | {
204 | /**
205 | * @var $enum UnitEnum|string
206 | */
207 |
208 | if (!is_null($mask) && self::isValidBitmask($enum, $mask)) {
209 | return new Bitmask($enum, $mask);
210 | }
211 |
212 | return new Bitmask(
213 | $enum,
214 | self::getBits(
215 | $enum,
216 | ...array_filter(
217 | $enums ?: [EnumDefaults::default($enum)]
218 | )
219 | ),
220 | );
221 | }
222 |
223 | public static function validateBitmaskOrThrowException(UnitEnum|string $enum, int $bitmask): void
224 | {
225 | if (!self::isValidBitmask($enum, $bitmask)) {
226 | throw new InvalidBitmaskEnum(
227 | is_object($enum) ? $enum::class : $enum,
228 | $bitmask
229 | );
230 | }
231 | }
232 |
233 | public static function isValidBitmask(UnitEnum|string $enum, mixed $bitmask): bool
234 | {
235 | if (!self::isInt($bitmask)) {
236 | return false;
237 | }
238 |
239 | $maxbits = self::getBits($enum, ...$enum::cases());
240 |
241 | if ($maxbits < $bitmask) {
242 | return false;
243 | }
244 |
245 | return (int)$bitmask === ($maxbits & $bitmask);
246 | }
247 |
248 | private static function forOrFail(string $class, Bitmask $enum): void
249 | {
250 | if (!$enum->for($class)) {
251 | self::throwMismatch(
252 | $class,
253 | $enum->forEnum()
254 | );
255 | }
256 | }
257 |
258 | public static function throwMismatch(string $expected, string $given): never
259 | {
260 | EnumCheck::check($expected);
261 | EnumCheck::check($given);
262 |
263 | throw new InvalidBitmaskEnum(
264 | $expected,
265 | $given
266 | );
267 | }
268 |
269 | protected static function triggerInvalidBitCase(UnitEnum|string $enum, UnitEnum $case): never
270 | {
271 | $enum = is_string($enum) ? $enum : $enum::class;
272 | throw new TypeError(
273 | sprintf('%s::%s is not a valid bit value', $enum, $case->name),
274 | E_USER_ERROR
275 | );
276 | }
277 |
278 | protected static function isInt(mixed $value): bool
279 | {
280 | return is_scalar($value) && filter_var($value, FILTER_VALIDATE_INT) !== false;
281 | }
282 |
283 | public static function triggerNotImplementingBitmasks(string $enum): never
284 | {
285 | throw new ErrorException(
286 | sprintf('`%s` is not implementing `Bitmasks`', $enum),
287 | E_USER_ERROR
288 | );
289 | }
290 | }
291 |
--------------------------------------------------------------------------------
/src/Helpers/EnumAttributes.php:
--------------------------------------------------------------------------------
1 | name))
17 | ->getAttributes($attributeClass, ReflectionAttribute::IS_INSTANCEOF);
18 |
19 | if (count($enumAttributes) > 0) {
20 | return $enumAttributes[0]->newInstance();
21 | }
22 |
23 | return null;
24 | }
25 |
26 | public static function fromCaseArray(string $enumClass, UnitEnum $case, string|null $attributeClass = null): array
27 | {
28 | EnumCheck::check($case, $enumClass);
29 |
30 | $enumAttributes = (new ReflectionClassConstant($enumClass, $case->name))
31 | ->getAttributes($attributeClass, ReflectionAttribute::IS_INSTANCEOF);
32 |
33 | return array_map(
34 | fn($enumAttribute) => $enumAttribute->newInstance(),
35 | $enumAttributes
36 | );
37 | }
38 |
39 | public static function fromEnum(string $enumClass, string $attributeClass): mixed
40 | {
41 | EnumCheck::check($enumClass);
42 |
43 | $enumAttributes = (new ReflectionClass($enumClass))
44 | ->getAttributes($attributeClass, ReflectionAttribute::IS_INSTANCEOF);
45 |
46 | if (count($enumAttributes) > 0) {
47 | return $enumAttributes[0]->newInstance();
48 | }
49 |
50 | return null;
51 | }
52 |
53 | public static function fromEnumArray(string $enumClass, string|null $attributeClass = null): array
54 | {
55 | EnumCheck::check($enumClass);
56 |
57 | $enumAttributes = (new ReflectionClass($enumClass))
58 | ->getAttributes($attributeClass, ReflectionAttribute::IS_INSTANCEOF);
59 |
60 | return array_map(
61 | fn($enumAttribute) => $enumAttribute->newInstance(),
62 | $enumAttributes
63 | );
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Helpers/EnumBlade.php:
--------------------------------------------------------------------------------
1 | equals(...$with);
17 | }
18 |
19 | public static function isValidCall(string $class, string $name, array $arguments): bool
20 | {
21 | EnumCheck::check($class);
22 |
23 | return EnumImplements::comparison($class)
24 | && !count($arguments) && str_starts_with(strtolower($name), 'is')
25 | && self::getValueFromString($class, $name);
26 | }
27 |
28 | private static function getValueFromString(string $class, string $name): ?UnitEnum
29 | {
30 | return EnumGetters::tryGet(
31 | $class,
32 | substr($name, str_starts_with(strtolower($name), 'isnot') ? 5 : 2),
33 | true,
34 | false
35 | );
36 | }
37 |
38 | public static function call(UnitEnum $enum, string $name): bool
39 | {
40 | $value = self::getValueFromString($enum::class, $name);
41 |
42 | return str_starts_with($name, 'isNot') !== self::equals($enum, $value);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Helpers/EnumDefaults.php:
--------------------------------------------------------------------------------
1 | getFileName() ?: '';
35 |
36 | return !str_contains($fileName, 'Henzeb/Enumhancer')
37 | && !str_ends_with($fileName, 'Defaults.php');
38 | }
39 |
40 | private static function getConfiguredOrCustomDefault(string $class): ?UnitEnum
41 | {
42 | $configured = EnumProperties::get($class, EnumProperties::reservedWord('defaults'));
43 |
44 | $hasCustomMethod = self::hasCustomDefaultMethod($class);
45 |
46 | if ($configured && !$hasCustomMethod) {
47 | return $configured;
48 | }
49 |
50 | if ($hasCustomMethod) {
51 | /**
52 | * @var $class UnitEnum|Defaults
53 | */
54 | return $class::default();
55 | }
56 | return null;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Helpers/EnumExtractor.php:
--------------------------------------------------------------------------------
1 | $map->keys($class), $mappers)
29 | )
30 | );
31 |
32 | preg_match_all(sprintf('/\b%s\b/i', $match), $text, $matches);
33 |
34 | $matches = array_map(fn($value) => EnumMapper::map($class, $value, ...$mappers), $matches[0] ?? []);
35 |
36 | return EnumGetters::getArray($class, $matches);
37 | }
38 |
39 | /**
40 | * @param UnitEnum|string $class
41 | * @return array|string[]
42 | */
43 | protected static function getMatchArray(UnitEnum|string $class): array
44 | {
45 | /**
46 | * @var UnitEnum|string $class
47 | */
48 | $match = array_map(
49 | function ($enum) {
50 |
51 | if (property_exists($enum, 'value')) {
52 | return $enum->value;
53 | }
54 |
55 | return $enum->name;
56 | },
57 | $class::cases()
58 | );
59 | return $match;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Helpers/EnumGetters.php:
--------------------------------------------------------------------------------
1 | name ?? $value;
29 |
30 | if (($useDefault)
31 | && is_string($value)
32 | && strtolower($value) === 'default'
33 | ) {
34 | return EnumDefaults::default($class) ?? self::throwError($value, $class);
35 | }
36 |
37 | if ($useMapper && EnumImplements::mappers($class)) {
38 | /**
39 | * @var $class Mappers|UnitEnum;
40 | */
41 | return $class::get($value);
42 | }
43 |
44 | $value = is_object($value) ? $value->name : $value;
45 |
46 | $match = !is_null($value) ? self::match($class, $value) : null;
47 |
48 | if ($match) {
49 | return $match;
50 | }
51 |
52 | self::throwError($value, $class);
53 | }
54 |
55 | public static function tryGet(
56 | string $class,
57 | int|string|UnitEnum|null $value,
58 | bool $useMapper = false,
59 | bool $useDefault = true
60 | ): mixed {
61 | EnumCheck::check($class);
62 |
63 | try {
64 | return self::get($class, $value, $useMapper, $useDefault);
65 | } catch (ValueError) {
66 | return $useDefault ? EnumDefaults::default($class) : null;
67 | }
68 | }
69 |
70 | public static function getArray(
71 | string $class,
72 | iterable $values,
73 | bool $useMapper = false
74 | ): array {
75 | EnumCheck::check($class);
76 | $return = [];
77 |
78 | foreach ($values as $value) {
79 | $return[] = self::get($class, $value, $useMapper);
80 | }
81 |
82 | return $return;
83 | }
84 |
85 | public static function tryArray(
86 | string $class,
87 | iterable $values,
88 | bool $useMapper = false,
89 | bool $useDefault = true
90 | ): array {
91 | EnumCheck::check($class);
92 |
93 | $return = [];
94 |
95 | foreach ($values as $value) {
96 | $return[] = self::tryGet($class, $value, $useMapper, $useDefault);
97 | }
98 |
99 | return array_filter($return);
100 | }
101 |
102 | public static function cast(string|UnitEnum $class, UnitEnum|string|int $enum): mixed
103 | {
104 | EnumCheck::check($class);
105 |
106 | if ($enum instanceof $class) {
107 | return $enum;
108 | }
109 |
110 | return self::get(is_object($class) ? $class::class : $class, $enum, true);
111 | }
112 |
113 | public static function tryCast(string|UnitEnum $class, UnitEnum|int|string $key): mixed
114 | {
115 | try {
116 | return self::cast($class, $key);
117 | } catch (ValueError) {
118 | return null;
119 | }
120 | }
121 |
122 | private static function match(UnitEnum|string $class, int|string $value): ?UnitEnum
123 | {
124 | $constants = self::cases($class);
125 |
126 | $case = self::findCase($constants, $value);
127 |
128 | if (!$case && is_a($class, BackedEnum::class, true)) {
129 | foreach ($constants as $constant) {
130 | if ($constant->value == $value) {
131 | $case = $constant;
132 | break;
133 | }
134 | }
135 | }
136 |
137 | if ($case) {
138 | return $case;
139 | }
140 |
141 | if (array_key_exists($value, array_keys($constants))) {
142 | return $constants[array_keys($constants)[$value]];
143 | }
144 |
145 | return null;
146 | }
147 |
148 | protected static function throwError(
149 | UnitEnum|int|string|null $value,
150 | string $class
151 | ): never {
152 | throw new ValueError(
153 | sprintf(
154 | '"%s" is not a valid backing value for enum "%s"',
155 | is_object($value) ? $value->name : $value,
156 | $class
157 | )
158 | );
159 | }
160 |
161 | public static function cases(
162 | UnitEnum|string $class
163 | ): array {
164 |
165 | /**
166 | * @var class-string $class
167 | */
168 |
169 | return array_filter(
170 | (new ReflectionClass($class))->getConstants(),
171 | fn($constant) => $constant instanceof $class
172 | );
173 | }
174 |
175 | protected static function findCase(array $constants, int|string $value): ?UnitEnum
176 | {
177 | if (is_string($value)) {
178 | $value = strtolower($value);
179 | }
180 |
181 | foreach ($constants as $name => $case) {
182 | if (self::isCase($case, $name, $value)) {
183 | return $case;
184 | }
185 | }
186 |
187 | return null;
188 | }
189 |
190 | private static function isCase(mixed $case, string $name, int|string $value): bool
191 | {
192 | return $value === strtolower($name)
193 | || strtolower(backing($case) ?? '') === $value;
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/Helpers/EnumImplements.php:
--------------------------------------------------------------------------------
1 | [strtolower(basename(str_replace('\\', '/', $class))) => $class],
64 | $available
65 | );
66 |
67 | return self::$available = array_merge(...$available);
68 | }
69 |
70 | public static function enumhancer(string $class): bool
71 | {
72 | if (self::implements($class, Enhancers::class)) {
73 | return true;
74 | }
75 |
76 | foreach (self::available() as $trait) {
77 | if (self::implements($class, $trait)) {
78 | return true;
79 | }
80 | }
81 | return false;
82 | }
83 |
84 | public static function __callStatic(string $name, array $arguments): bool
85 | {
86 | $implements = self::available()[strtolower($name)] ?? null;
87 |
88 | if ($implements && $arguments[0] && is_string($arguments[0])) {
89 | return self::implements($arguments[0], $implements);
90 | }
91 |
92 | throw new ErrorException(
93 | sprintf('Call to undefined method %s::%s()', self::class, $name),
94 | E_USER_ERROR
95 | );
96 | }
97 |
98 | public static function implements(string $class, string $implements): bool
99 | {
100 | EnumCheck::check($class);
101 |
102 | return in_array($implements, self::classUsesTrait($class));
103 | }
104 |
105 | private static function classUsesTrait(string $class): array
106 | {
107 | $results = [];
108 |
109 | foreach (array_reverse(class_parents($class) ?: []) + [$class => $class] as $class) {
110 | $results += self::getTraitsFrom($class);
111 | }
112 |
113 | return array_unique($results);
114 | }
115 |
116 | private static function getTraitsFrom(string $class): array
117 | {
118 | $traits = class_uses($class) ?: [];
119 |
120 | foreach ($traits as $class) {
121 | $traits += self::getTraitsFrom($class);
122 | }
123 |
124 | return $traits;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/Helpers/EnumLabels.php:
--------------------------------------------------------------------------------
1 | name]
41 | ?? self::getLabels($enum::class)[EnumValue::key($enum)]
42 | ?? (method_exists($enum, 'value') ? $enum->value() : null)
43 | ?? $enum->value
44 | ?? $enum->name;
45 | }
46 |
47 | public static function getLabelOrName(UnitEnum $enum): string
48 | {
49 | return self::getLabels($enum::class)[$enum->name] ?? $enum->name;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Helpers/EnumMacros.php:
--------------------------------------------------------------------------------
1 | getMethods(
41 | ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
42 | );
43 | }
44 |
45 | /**
46 | * @throws ReflectionException
47 | */
48 | public static function mixin(?string $enum, string|object $mixin): void
49 | {
50 | if (!is_null($enum)) {
51 | EnumCheck::check($enum);
52 | }
53 |
54 | if (is_string($mixin)) {
55 | $mixin = new $mixin();
56 | }
57 |
58 |
59 | foreach (self::getMethodsFromMixin($mixin) as $method) {
60 | if ($enum) {
61 | self::macro($enum, $method->name, $method->invoke($mixin));
62 | continue;
63 | }
64 |
65 | self::globalMacro($method->name, $method->invoke($mixin));
66 | }
67 |
68 | unset($mixin);
69 | }
70 |
71 | public static function globalMixin(string|object $mixin): void
72 | {
73 | self::mixin(null, $mixin);
74 | }
75 |
76 | public static function flush(string $enum): void
77 | {
78 | EnumCheck::check($enum);
79 |
80 | if (isset(self::$macros[$enum])) {
81 | unset(self::$macros[$enum]);
82 | }
83 | }
84 |
85 | public static function flushGlobal(): void
86 | {
87 | self::$globalMacros = [];
88 | }
89 |
90 | public static function hasMacro(string $enum, string $name): bool
91 | {
92 | return self::getMacro($enum, $name) !== null;
93 | }
94 |
95 | private static function getMacro(string $enum, string $name): ?callable
96 | {
97 | EnumCheck::check($enum);
98 |
99 | $name = strtolower($name);
100 |
101 | return array_change_key_case(self::getMacros($enum))[$name] ?? null;
102 | }
103 |
104 | private static function getMacros(string $enum): array
105 | {
106 | return array_merge(
107 | self::$globalMacros ?? [],
108 | self::$macros[$enum] ?? []
109 | );
110 | }
111 |
112 | /**
113 | * @throws ReflectionException
114 | */
115 | private static function isStaticMacro(callable $callable): bool
116 | {
117 | return (new ReflectionFunction($callable(...)))->isStatic();
118 | }
119 |
120 | /**
121 | * @throws ReflectionException
122 | */
123 | public static function call(UnitEnum $enum, string $name, array $arguments): mixed
124 | {
125 | EnumCheck::check($enum);
126 |
127 | $macro = self::getMacro($enum::class, $name);
128 |
129 | if ($macro && self::isStaticMacro($macro)) {
130 | return self::callStatic($enum::class, $name, $arguments);
131 | }
132 |
133 | $macro = ($macro ?? fn() => true)(...)->bindTo($enum, $enum::class);
134 |
135 | return $macro(...$arguments);
136 | }
137 |
138 | /**
139 | * @throws ReflectionException
140 | */
141 | public static function callStatic(string $enum, string $name, array $arguments): mixed
142 | {
143 | EnumCheck::check($enum);
144 |
145 | $macro = self::getMacro($enum, $name);
146 |
147 | if (!$macro || false === self::isStaticMacro($macro)) {
148 | self::triggerError($enum, $name);
149 | }
150 |
151 | $macro = $macro(...)->bindTo(null, $enum);
152 |
153 | return $macro(...$arguments);
154 | }
155 |
156 | private static function triggerError(string $enum, string $name): never
157 | {
158 | throw new ErrorException(
159 | sprintf(
160 | 'Uncaught Error: Non-static method %s::%s() cannot be called statically',
161 | $enum,
162 | $name
163 | ),
164 | E_USER_ERROR
165 | );
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/Helpers/EnumMagicCalls.php:
--------------------------------------------------------------------------------
1 | '@default_configure',
15 | 'labels' => '@labels_configure',
16 | 'mapper' => '@mapper_configure',
17 | 'with_mapper' => '@with_mapper',
18 | 'state' => '@state_configure',
19 | 'hooks' => '@state_hook_configure'
20 | ];
21 |
22 | private static array $global = [];
23 | protected static array $properties = [];
24 | protected static array $once = [];
25 |
26 | /**
27 | * @throws ReservedPropertyNameException|PropertyAlreadyStoredException
28 | */
29 | public static function store(string $class, string $property, mixed $value, bool $allowReservedWord = false): void
30 | {
31 | EnumCheck::check($class);
32 |
33 | self::reservedWordCheck($property, $allowReservedWord);
34 | self::storedOnceCheck($class, $property);
35 |
36 | self::$properties[$class][$property] = $value;
37 | }
38 |
39 | /**
40 | * @throws ReservedPropertyNameException|PropertyAlreadyStoredException
41 | */
42 | public static function storeOnce(
43 | string $class,
44 | string $property,
45 | mixed $value,
46 | bool $allowReservedWord = false
47 | ): void {
48 | EnumCheck::check($class);
49 |
50 | self::reservedWordCheck($property, $allowReservedWord);
51 | self::storedOnceCheck($class, $property);
52 |
53 | self::$once[$class][$property] = $value;
54 | unset(self::$properties[$class][$property]);
55 | }
56 |
57 | private static function reservedWordCheck(string $property, bool $allowReservedWord): void
58 | {
59 | if (!$allowReservedWord && in_array($property, self::$reserved)) {
60 | throw new ReservedPropertyNameException($property);
61 | }
62 | }
63 |
64 | private static function storedOnceCheck(string $class, string $property): void
65 | {
66 | if (isset(self::$once[$class][$property])) {
67 | throw new PropertyAlreadyStoredException($class, $property);
68 | }
69 | }
70 |
71 | public static function reservedWord(string $name): string
72 | {
73 | return self::$reserved[$name] ?? $name;
74 | }
75 |
76 | public static function get(string $class, string $property): mixed
77 | {
78 | EnumCheck::check($class);
79 |
80 | return self::$once[$class][$property]
81 | ?? self::$properties[$class][$property]
82 | ?? self::$global[$property] ?? null;
83 | }
84 |
85 | public static function getGlobal(string $property): mixed
86 | {
87 | return self::$global[$property] ?? null;
88 | }
89 |
90 | public static function global(string $property, mixed $value): mixed
91 | {
92 | return self::$global[$property] = $value;
93 | }
94 |
95 | public static function clear(string $class, string|null $property = null): void
96 | {
97 | EnumCheck::check($class);
98 |
99 | if (!empty($property)) {
100 | unset(self::$properties[$class][$property]);
101 | }
102 |
103 | if (empty($property)) {
104 | unset(self::$properties[$class]);
105 | }
106 | }
107 |
108 | public static function clearGlobal(string|null $property = null): void
109 | {
110 | if (!empty($property)) {
111 | unset(self::$global[$property]);
112 | }
113 |
114 | if (empty($property)) {
115 | self::$global = [];
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/Helpers/EnumProxy.php:
--------------------------------------------------------------------------------
1 | name = $enum->name;
16 | $this->value = (string)EnumValue::value($enum, $keepValueCase);
17 | }
18 |
19 | public function __toString(): string
20 | {
21 | return $this->value;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Helpers/EnumReporter.php:
--------------------------------------------------------------------------------
1 | name;
70 | }
71 |
72 | if (!is_null($key)) {
73 | $key = (string)$key;
74 | }
75 |
76 | $reporter?->report($class, $key, $context);
77 | }
78 |
79 | return $enum;
80 | }
81 |
82 | /**
83 | * @param string $class
84 | * @param iterable $keys
85 | * @param BackedEnum|null $context
86 | * @param Reporter|null $reporter
87 | * @return UnitEnum[]
88 | */
89 | public static function getOrReportArray(
90 | string $class,
91 | iterable $keys,
92 | ?BackedEnum $context,
93 | ?Reporter $reporter
94 | ): array {
95 | EnumCheck::check($class);
96 |
97 | $result = [];
98 |
99 | foreach ($keys as $key) {
100 | $result[] = self::getOrReport($class, $key, $context, $reporter);
101 | }
102 |
103 | return array_filter($result);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/Helpers/EnumState.php:
--------------------------------------------------------------------------------
1 | name] = $case;
29 | }
30 |
31 | $current = $case;
32 | }
33 | unset($current);
34 |
35 | return array_merge(
36 | $transitions,
37 | self::castTransitions($class, $userTransitions)
38 | );
39 | }
40 |
41 | public static function allowedTransitions(UnitEnum $currentTransition, ?TransitionHook ...$hooks): array
42 | {
43 | return self::filterAllowedTransitions($currentTransition, self::getTransitions($currentTransition), $hooks);
44 | }
45 |
46 | private static function getTransitions(UnitEnum $currentTransition): array
47 | {
48 | $transitions = array_change_key_case(
49 | EnumState::transitions($currentTransition::class, $currentTransition::class::transitions())
50 | );
51 |
52 | $transitions = $transitions[$currentTransition->name]
53 | ?? $transitions[strtolower($currentTransition->name)]
54 | ?? $transitions[EnumValue::value($currentTransition)]
55 | ?? [];
56 |
57 | return array_filter(is_array($transitions) ? $transitions : [$transitions]);
58 | }
59 |
60 | private static function castTransitions(string|UnitEnum $class, array $transitions): array
61 | {
62 | foreach ($transitions as $key => $value) {
63 | unset($transitions[$key]);
64 |
65 | $key = EnumGetters::tryCast($class, $key)->name ?? $key;
66 |
67 | if (is_array($value)) {
68 | $transitions[$key] = self::castTransitions($class, $value);
69 | continue;
70 | }
71 |
72 | $transitions[$key] = $value ? EnumGetters::cast($class, $value) : null;
73 | }
74 |
75 | return $transitions;
76 | }
77 |
78 | private static function filterAllowedTransitions(
79 | UnitEnum $currentTransition,
80 | array $transitions,
81 | array $hooks
82 | ): array {
83 | return array_filter(
84 | $transitions,
85 | function (UnitEnum $transitionTo) use ($hooks, $currentTransition) {
86 | foreach ($hooks as $hook) {
87 | if (!$hook->isAllowed($currentTransition, $transitionTo)) {
88 | return false;
89 | }
90 | }
91 | return true;
92 | }
93 | );
94 | }
95 |
96 | public static function isValidCall(string $class, string $name): bool
97 | {
98 | EnumCheck::check($class);
99 |
100 | return (str_starts_with($name, 'to') || str_starts_with($name, 'tryTo'))
101 | && self::getToState($class, $name) !== null;
102 | }
103 |
104 | private static function getToState(string $class, string $name): ?UnitEnum
105 | {
106 | return EnumGetters::tryGet(
107 | $class,
108 | substr($name, str_starts_with($name, 'tryTo') ? 5 : 2),
109 | true,
110 | false
111 | );
112 | }
113 |
114 | public static function call(UnitEnum $currentState, string $name, array $arguments): mixed
115 | {
116 | $toState = self::getToState($currentState::class, $name);
117 |
118 | /**
119 | * @var State|UnitEnum $currentState
120 | */
121 | if (str_starts_with($name, 'tryTo')) {
122 | return $currentState->tryTo($toState, ...$arguments);
123 | }
124 |
125 | return $currentState->to($toState, ...$arguments);
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/Helpers/EnumValue.php:
--------------------------------------------------------------------------------
1 | value ?? ($keepCase ? $enum->name : strtolower($enum->name));
20 | }
21 |
22 | public static function key(UnitEnum $enum): int
23 | {
24 | if (property_exists($enum, 'value') && is_numeric($enum->value)) {
25 | return (int)$enum->value;
26 | }
27 |
28 | return (int)array_search($enum, $enum::cases());
29 | }
30 |
31 | private static function isStrict(UnitEnum $enum): bool
32 | {
33 | $constants = (new ReflectionClass($enum))->getConstants();
34 |
35 | foreach ($constants as $name => $constant) {
36 | if ('strict' === strtolower($name) && is_bool($constant)) {
37 | return $constant;
38 | }
39 | }
40 |
41 | return false;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Helpers/Enumhancer.php:
--------------------------------------------------------------------------------
1 | > $mappable
12 | */
13 | public function __construct(private readonly array $mappable)
14 | {
15 | }
16 |
17 |
18 | protected function mappable(): array
19 | {
20 | return $this->mappable;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Helpers/Mappers/EnumMapper.php:
--------------------------------------------------------------------------------
1 | |Mapper|string|null ...$mappers
27 | * @return string|null
28 | */
29 | public static function map(
30 | string $enum,
31 | UnitEnum|int|string|null $value,
32 | array|string|Mapper|null ...$mappers
33 | ): ?string {
34 | EnumCheck::check($enum);
35 |
36 | if (null === $value) {
37 | return null;
38 | }
39 |
40 | foreach (self::getMappers($enum, ...$mappers) as $mapper) {
41 | $value = $mapper->map($value, $enum) ?? $value;
42 | }
43 |
44 | return $value instanceof UnitEnum ? $value->name : $value;
45 | }
46 |
47 | /**
48 | * @param string $enum
49 | * @param iterable $values
50 | * @param Mapper|string|array>|null ...$mappers
51 | * @return string[]
52 | */
53 | public static function mapArray(string $enum, iterable $values, Mapper|string|array|null ...$mappers): array
54 | {
55 | $mapped = [];
56 |
57 | foreach ($values as $value) {
58 | $mapped[] = EnumMapper::map($enum, $value, ...$mappers);
59 | }
60 |
61 | return $mapped;
62 | }
63 |
64 | /**
65 | * @return Mapper[]
66 | */
67 | private static function sanitizeMapperArray(array $mappers): array
68 | {
69 | return array_map(
70 | function (Mapper|array|string $mapper) {
71 |
72 | if (is_string($mapper)) {
73 | $mapper = new $mapper;
74 | }
75 |
76 | if (is_array($mapper)) {
77 | $mapper = self::wrapInMapper($mapper);
78 | }
79 |
80 | if (!$mapper instanceof Mapper) {
81 | throw new RuntimeException(
82 | sprintf(
83 | 'object of type \'%s\' expected, got \'%s\'',
84 | Mapper::class,
85 | $mapper::class
86 | )
87 | );
88 | }
89 | return $mapper;
90 | },
91 | array_filter($mappers)
92 | );
93 | }
94 |
95 | /**
96 | * @param string $enum
97 | * @return Mapper[]
98 | * @throws ReflectionException
99 | */
100 | private static function getConstantMappers(string $enum): array
101 | {
102 | /**
103 | * @var UnitEnum $enum
104 | */
105 |
106 | $mappers = [];
107 | $constants = (new ReflectionClass($enum))->getConstants();
108 |
109 | foreach ($constants as $name => $constant) {
110 | if (!str_starts_with(strtolower($name), 'map')) {
111 | continue;
112 | }
113 |
114 | $mappers[] = self::parseConstantAsMapper($enum, $name, $constant);
115 | }
116 |
117 | return $mappers;
118 | }
119 |
120 | protected static function parseConstantAsMapper(UnitEnum|string $enum, string $name, mixed $constant): ?Mapper
121 | {
122 | if (is_array($constant)) {
123 | return self::wrapInMapper($constant);
124 | }
125 |
126 | if (!is_string($constant) || !class_exists($constant)) {
127 | return null;
128 | }
129 |
130 | self::checkMappers(is_object($enum) ? $enum::class : $enum, $constant);
131 |
132 | return self::instantiateMapper(
133 | $constant,
134 | str_starts_with(
135 | strtolower($name),
136 | 'map_flip'
137 | )
138 | );
139 | }
140 |
141 | public static function isValidMapper(string $enum, mixed $value): bool
142 | {
143 | return self::isMapperClass($value)
144 | || is_array($value)
145 | || is_a($value, $enum);
146 | }
147 |
148 | public static function isMapperClass(mixed $mapper): bool
149 | {
150 | return is_subclass_of($mapper, Mapper::class);
151 | }
152 |
153 | protected static function instantiateMapper(string $class, bool $flip = false): Mapper
154 | {
155 | /**
156 | * @var $class Mapper
157 | */
158 | if ($flip) {
159 | return $class::flip();
160 | }
161 |
162 | return $class::newInstance();
163 | }
164 |
165 | /**
166 | * @param array> $map
167 | * @return Mapper
168 | */
169 | protected static function wrapInMapper(array $map): Mapper
170 | {
171 | return EnumArrayMapper::newInstance($map);
172 | }
173 |
174 | /**
175 | * @param string $enum
176 | * @return array>
177 | */
178 | private static function getConfiguredMapper(string $enum): array
179 | {
180 | return EnumProperties::get(
181 | $enum,
182 | EnumProperties::reservedWord('mapper')
183 | ) ?? [];
184 | }
185 |
186 | /**
187 | * @param string $enum
188 | * @param Mapper|string|array|null ...$mappers
189 | * @return Mapper
190 | * @throws ReflectionException
191 | */
192 | public static function getMappers(string $enum, Mapper|string|array|null ...$mappers): array
193 | {
194 | /**
195 | * @var $enum Mappers|UnitEnum|String
196 | */
197 | return self::sanitizeMapperArray(
198 | [
199 | ...$mappers,
200 | ...self::getConfiguredMapper($enum),
201 | ...self::getConstantMappers($enum)
202 | ]
203 | );
204 | }
205 |
206 | public static function checkMappers(string $enum, mixed ...$mappers): void
207 | {
208 | EnumCheck::check($enum);
209 |
210 | array_walk(
211 | $mappers,
212 | function ($mapper) {
213 | if (is_string($mapper) && !EnumMapper::isMapperClass($mapper)) {
214 | throw new ValueError(
215 | sprintf('Invalid class. expected Mapper, %s given', $mapper)
216 | );
217 | }
218 | }
219 | );
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/src/Helpers/Subset/EnumSubsetMethods.php:
--------------------------------------------------------------------------------
1 | $enumType
24 | * @param T ...$enums
25 | */
26 | public function __construct(private readonly string $enumType, UnitEnum ...$enums)
27 | {
28 | EnumCheck::matches($enumType, ...$enums);
29 |
30 | $this->enums = $enums;
31 | }
32 |
33 | public function do(Closure $callable): void
34 | {
35 | foreach ($this->enums as $enum) {
36 | $callable($enum);
37 | }
38 | }
39 |
40 | public function equals(UnitEnum|string|int|null ...$equals): bool
41 | {
42 | foreach ($this->enums as $enum) {
43 | if ($this->compare($enum, ...$equals)) {
44 | return true;
45 | }
46 | }
47 |
48 | return false;
49 | }
50 |
51 | private function compare(UnitEnum $enum, UnitEnum|string|int|null ...$equals): bool
52 | {
53 | $result = false;
54 | foreach ($equals as $equal) {
55 | $equal = $this->asEnumObject($equal);
56 |
57 | EnumCheck::matches($this->enumType, $equal);
58 |
59 | if ($enum === $equal) {
60 | $result = true;
61 | }
62 | }
63 | return $result;
64 | }
65 |
66 | private function asEnumObject(mixed $value): ?UnitEnum
67 | {
68 | if (!$value instanceof UnitEnum || $this->enumType !== $value::class) {
69 | return EnumGetters::tryGet($this->enumType, $value, true);
70 | }
71 |
72 | return $value;
73 | }
74 |
75 |
76 | /**
77 | * @return string[]
78 | */
79 | public function names(): array
80 | {
81 | return array_map(fn(UnitEnum $enum) => $enum->name, $this->enums);
82 | }
83 |
84 | /**
85 | * @return string[]|int[]
86 | */
87 | public function values(bool|null $keepEnumCase = null): array
88 | {
89 | return array_map(
90 | function (mixed $enum) use ($keepEnumCase) {
91 | return EnumValue::value($enum, $keepEnumCase);
92 | },
93 | $this->enums
94 | );
95 | }
96 |
97 | /**
98 | * @param bool|null $keepEnumCase
99 | * @return array
100 | */
101 | public function dropdown(bool|null $keepEnumCase = null): array
102 | {
103 | return array_replace(
104 | [],
105 | ...array_map(
106 | function (UnitEnum $case) use ($keepEnumCase) {
107 |
108 | return [
109 | EnumValue::value($case, $keepEnumCase)
110 | =>
111 | EnumLabels::getLabelOrName($case)
112 | ];
113 | },
114 | $this->enums
115 | )
116 | );
117 | }
118 |
119 | /**
120 | * @return T[]
121 | */
122 | public function cases(): array
123 | {
124 | return $this->enums;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/Laravel/Concerns/CastsBasicEnumerations.php:
--------------------------------------------------------------------------------
1 | getCasts()[$key];
26 |
27 | if (!$value instanceof $castType) {
28 | $value = EnumGetters::get($castType, $value);
29 | }
30 |
31 | if ($this->shouldUseBasicEnumWorkaround($castType)) {
32 | $keepEnumCase = property_exists($this, 'keepEnumCase') ? $this->keepEnumCase : true;
33 |
34 | return backing($value, $keepEnumCase);
35 | }
36 |
37 | return $value;
38 | }
39 |
40 | protected function setEnumCastableAttribute($key, $value)
41 | {
42 | $enumClass = $this->getCasts()[$key];
43 |
44 | $keepEnumCase = property_exists($this, 'keepEnumCase') ? $this->keepEnumCase : true;
45 |
46 | if (!isset($value)) {
47 | $this->attributes[$key] = null;
48 | return;
49 | }
50 |
51 | if ($value instanceof $enumClass) {
52 | $value = EnumValue::value($value, $keepEnumCase);
53 | }
54 |
55 | if ($value instanceof UnitEnum && !$value instanceof $enumClass) {
56 | throw new ValueError(
57 | sprintf('Enum of `%s` expected, got `%s`', $enumClass, $value::class)
58 | );
59 | }
60 |
61 | $this->attributes[$key] = EnumValue::value(EnumGetters::get($enumClass, $value), $keepEnumCase);
62 | }
63 |
64 | private function shouldUseBasicEnumWorkaround(string $enumClass): bool
65 | {
66 | return (!is_subclass_of($enumClass, BackedEnum::class, true))
67 | && 'toArray' === (debug_backtrace(2)[5]['function'] ?? null);
68 | }
69 |
70 | protected function getStorableEnumValue($expectedEnumValue, $value = null)
71 | {
72 | $value ??= $expectedEnumValue;
73 | if ($value instanceof UnitEnum) {
74 | $keepEnumCase = property_exists($this, 'keepEnumCase') ? $this->keepEnumCase : true;
75 |
76 | return EnumValue::value($value, $keepEnumCase);
77 | }
78 |
79 | return $value;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Laravel/Concerns/CastsStatefulEnumerations.php:
--------------------------------------------------------------------------------
1 | getAttribute($key);
31 |
32 | if (!isset($value) || !$currentAttribute || $this->shouldNotCastByTransition($key)) {
33 | $this->setEnumCastableAttributeAnyway($key, $value);
34 | return;
35 | }
36 |
37 | $this->setEnumCastableAttributeAnyway(
38 | $key,
39 | $currentAttribute->transitionTo($value, $this->getTransactionHooks($key))
40 | );
41 | }
42 |
43 | private function shouldNotCastByTransition($key): bool
44 | {
45 | $cast = $this->getCasts()[$key];
46 |
47 | $ignore = property_exists($this, 'castsIgnoreEnumState') ? $this->castsIgnoreEnumState : [];
48 |
49 | return in_array($key, $ignore) || !EnumImplements::state($cast);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Laravel/Middleware/SubstituteEnums.php:
--------------------------------------------------------------------------------
1 | getParameters($request->route()) as $key => $parameter) {
23 | if ($request->route($key) === null) {
24 | $this->setDefaultIfAvailable($request, $key, $parameter);
25 | continue;
26 | }
27 |
28 | $this->processParameter($parameter, $request, $key);
29 | }
30 |
31 | return $next($request);
32 | }
33 |
34 | protected function processParameter(ReflectionEnum $parameter, Request $request, string $key): void
35 | {
36 | /**
37 | * @var string $givenValue
38 | */
39 | $givenValue = $request->route($key);
40 |
41 | $value = EnumGetters::tryGet($parameter->getName(), $givenValue, useDefault: false);
42 |
43 | if (!$value) {
44 | throw new NotFoundHttpException();
45 | }
46 |
47 | /**
48 | * Laravel's middleware SubstituteBindings is still being processed. Returning the value allows
49 | * that middleware to complete the request properly.
50 | */
51 | if ($this->isStringBacked($parameter)) {
52 | /**
53 | * @var BackedEnum $value
54 | */
55 | $value = $value->value;
56 | }
57 |
58 | $request->route()?->setParameter($key, $value);
59 | }
60 |
61 | /**
62 | * @param Route|null $route
63 | * @return array
64 | * @throws ReflectionException
65 | */
66 | private function getParameters(Route|null $route): array
67 | {
68 | return collect(
69 | $route?->signatureParameters(['subClass' => UnitEnum::class])
70 | )->mapWithKeys(
71 | function (ReflectionParameter $parameter) {
72 | $backedEnumClass = ltrim((string)$parameter->getType(), '?');
73 |
74 | return [$parameter->getName() => new ReflectionEnum($backedEnumClass)];
75 | }
76 | )->filter()->toArray();
77 | }
78 |
79 | private function isStringBacked(ReflectionEnum $parameter): bool
80 | {
81 | return ((string)$parameter->getBackingType()) === 'string';
82 | }
83 |
84 | private function hasDefault(ReflectionEnum $parameter): bool
85 | {
86 | return EnumImplements::defaults($parameter->getName());
87 | }
88 |
89 | private function setDefaultIfAvailable(
90 | Request $request,
91 | string $key,
92 | ReflectionEnum $parameter
93 | ): void {
94 | if ($this->hasDefault($parameter)) {
95 | $request->route()?->setParameter(
96 | $key,
97 | EnumDefaults::default($parameter->getName())
98 | );
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/Laravel/Mixins/FormRequestMixin.php:
--------------------------------------------------------------------------------
1 | get($key), ...$mappers)
28 | );
29 | };
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Laravel/Mixins/RulesMixin.php:
--------------------------------------------------------------------------------
1 | setupReporter();
20 |
21 | $this->setupMacroMixins();
22 |
23 | $this->setupEnumBindingMiddleware($kernel);
24 | }
25 |
26 | protected function setupReporter(): void
27 | {
28 | LogLevel::setDefault(LogLevel::Notice);
29 |
30 | EnumReporter::laravel();
31 | }
32 |
33 | private function setupMacroMixins(): void
34 | {
35 | Rule::mixin(new RulesMixin());
36 | FormRequest::mixin(new FormRequestMixin());
37 | }
38 |
39 | protected function setupEnumBindingMiddleware(Kernel $kernel): void
40 | {
41 | /**
42 | * @var \Illuminate\Foundation\Http\Kernel $kernel
43 | */
44 | $kernel->prependToMiddlewarePriority(SubstituteEnums::class);
45 | $kernel->appendMiddlewareToGroup('web', SubstituteEnums::class);
46 | $kernel->appendMiddlewareToGroup('api', SubstituteEnums::class);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Laravel/Reporters/LaravelLogReporter.php:
--------------------------------------------------------------------------------
1 | level = $level;
25 | $this->channels = $channels;
26 | }
27 |
28 | private function getLevel(): string
29 | {
30 | return (string)($this->level ?? LogLevel::default() ?? LogLevel::Notice)->value();
31 | }
32 |
33 | /**
34 | * @return string[]
35 | */
36 | private function getChannels(): array
37 | {
38 | if (empty($this->channels)) {
39 | return [config('logging.default')];
40 | }
41 |
42 | return $this->channels;
43 | }
44 |
45 | public function report(string $enum, ?string $key, ?BackedEnum $context): void
46 | {
47 | Log::stack(
48 | $this->getChannels()
49 | )->log(
50 | $this->getLevel(),
51 | class_basename($enum)
52 | . ($key ? sprintf(' does not have \'%s\'', $key) : ': A null value was passed'),
53 | array_filter(
54 | [
55 | 'class' => $enum,
56 | 'key' => $key,
57 | 'context' => $context?->value
58 | ]
59 | )
60 | );
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Laravel/Rules/EnumBitmask.php:
--------------------------------------------------------------------------------
1 | value = $value;
32 |
33 | if ($this->singleBit) {
34 | return $value == 0
35 | || (EnumBitmasks::isBit($value)
36 | && array_key_exists($value, EnumBitmasks::getCaseBits($this->type))
37 | );
38 | }
39 |
40 | return EnumBitmasks::isValidBitmask($this->type, $value);
41 | }
42 |
43 | /**
44 | * @return string|string[]
45 | */
46 | public function message(): string|array
47 | {
48 | $message = trans(
49 | 'validation.enumhancer.bitmask',
50 | [
51 | 'enum' => $this->type,
52 | 'value' => $this->value,
53 | ]
54 | );
55 |
56 | return $message === 'validation.enumhancer.bitmask'
57 | ? ['The selected :attribute is invalid.']
58 | : $message;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Laravel/Rules/EnumTransition.php:
--------------------------------------------------------------------------------
1 | currentState::class)) {
23 | throw new ErrorException(
24 | sprintf('%s does not implement `State`', $this->currentState::class),
25 | E_USER_ERROR
26 | );
27 | }
28 | }
29 |
30 | /**
31 | * @SuppressWarnings(PHPMD.UnusedFormalParameter)
32 | */
33 | public function passes($attribute, $value)
34 | {
35 | $this->transitionTo = $value;
36 |
37 | /**
38 | * @var State $currentState
39 | */
40 | $currentState = $this->currentState;
41 |
42 | return $currentState->isTransitionAllowed($value, $this->hook);
43 | }
44 |
45 | /**
46 | * Get the validation error message.
47 | *
48 | * @return string[]
49 | */
50 | public function message()
51 | {
52 | $message = trans('validation.enumhancer.transition', [
53 | 'from' => $this->currentState->name,
54 | 'to' => $this->transitionTo ?? 'unknown',
55 | ]);
56 |
57 | return $message === 'validation.enumhancer.transition'
58 | ? ['The transition for :attribute is invalid.']
59 | : $message;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Laravel/Rules/IsEnum.php:
--------------------------------------------------------------------------------
1 | |Mapper[]|null[]|string[]
18 | */
19 | private array $mappers;
20 |
21 | /**
22 | * @param string $type
23 | * @param array|array|Mapper[]|null[]|string[] $mappers
24 | */
25 | public function __construct(private readonly string $type, Mapper|string|array|null ...$mappers)
26 | {
27 | EnumCheck::check($type);
28 | $this->mappers = $mappers;
29 | }
30 |
31 | /**
32 | * @SuppressWarnings(PHPMD.UnusedFormalParameter)
33 | */
34 | public function passes($attribute, $value): bool
35 | {
36 | $this->value = EnumMapper::map($this->type, $value, ...$this->mappers);
37 |
38 | return (bool)EnumGetters::tryGet($this->type, $this->value, useDefault: false);
39 | }
40 |
41 | /**
42 | * @return string|string[]
43 | */
44 | public function message(): string|array
45 | {
46 | $message = trans(
47 | 'validation.enumhancer.enum',
48 | [
49 | 'enum' => $this->type,
50 | 'value' => $this->value,
51 | ]
52 | );
53 |
54 | return $message === 'validation.enumhancer.enum'
55 | ? ['The selected :attribute is invalid.']
56 | : $message;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/PHPStan/Constants/BitmaskConstantAlwaysUsed.php:
--------------------------------------------------------------------------------
1 | getName()) !== 'bit_values') {
15 | return false;
16 | }
17 |
18 | $class = $constant->getDeclaringClass();
19 |
20 | if (!$class->isEnum()) {
21 | return false;
22 | }
23 |
24 | return EnumImplements::bitmasks($class->getName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/PHPStan/Constants/BitmaskModifierConstantAlwaysUsed.php:
--------------------------------------------------------------------------------
1 | getName()) !== 'bit_modifier') {
16 | return false;
17 | }
18 |
19 | $class = $constant->getDeclaringClass();
20 |
21 | if (!$class->hasConstant('BIT_VALUES')) {
22 | return false;
23 | }
24 |
25 | if (!$class->getBackedEnumType()?->isInteger()) {
26 | return false;
27 | }
28 |
29 |
30 | return EnumImplements::bitmasks($class->getName());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/PHPStan/Constants/DefaultConstantAlwaysUsed.php:
--------------------------------------------------------------------------------
1 | getName();
14 |
15 | if (\strtolower($constantName) === 'default') {
16 | $class = $constant->getDeclaringClass();
17 | return $class->isEnum() && EnumImplements::defaults($class->getName());
18 | }
19 |
20 | return false;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/PHPStan/Constants/MapperConstantAlwaysUsed.php:
--------------------------------------------------------------------------------
1 | getDeclaringClass();
16 |
17 | if (!$class->isEnum()) {
18 | return false;
19 | }
20 |
21 | $className = $class->getName();
22 | $constantName = $constant->getName();
23 |
24 | if (str_starts_with(strtolower($constantName), 'map')) {
25 | return EnumImplements::mappers($className);
26 | }
27 |
28 | return false;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/PHPStan/Constants/Rules/DefaultConstantRule.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | class DefaultConstantRule implements Rule
19 | {
20 | public function getNodeType(): string
21 | {
22 | return ClassConst::class;
23 | }
24 |
25 | public function processNode(Node $node, Scope $scope): array
26 | {
27 | $constantName = $node->consts[0]->name->name;
28 |
29 | if ($this->isDefault($constantName)) {
30 | return [];
31 | }
32 |
33 | $reflectedClass = $scope->getClassReflection();
34 |
35 | if (!$reflectedClass->isEnum()) {
36 | return [];
37 | }
38 |
39 | return $this->validate($reflectedClass, $constantName);
40 | }
41 |
42 | /**
43 | * @param ClassReflection|null $reflectedClass
44 | * @param string $constantName
45 | * @return string[]
46 | * @throws ReflectionException
47 | */
48 | protected function validate(?ClassReflection $reflectedClass, string $constantName): array
49 | {
50 | $implementsDefault = EnumImplements::defaults($reflectedClass->getName());
51 |
52 | $value = $reflectedClass->getConstant($constantName)->getValueType();
53 |
54 | $valueFromEnum = $value instanceof EnumCaseObjectType && $value->getClassName() === $reflectedClass->getName();
55 |
56 | $return = [];
57 |
58 | if ($implementsDefault && !$valueFromEnum) {
59 | $return = [
60 | RuleErrorBuilder::message(
61 | sprintf(
62 | 'Enumhancer: enum is implementing `Defaults`, '
63 | . 'but constant `%s` is not referencing to one of its own cases.',
64 | $constantName
65 | )
66 | )->build()
67 | ];
68 | }
69 |
70 | if (!$implementsDefault && $valueFromEnum) {
71 | $return = [
72 | RuleErrorBuilder::message(
73 | sprintf(
74 | 'Enumhancer: Constant `%s` is not going to be used, '
75 | . 'because enum is not implementing `Defaults`',
76 | $constantName
77 | )
78 | )->build()
79 | ];
80 | }
81 |
82 | return $return;
83 | }
84 |
85 | protected function isDefault(string $constantName): bool
86 | {
87 | return strtolower($constantName) !== 'default';
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/PHPStan/Constants/Rules/MapperConstantRule.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | class MapperConstantRule implements Rule
23 | {
24 | public function __construct(
25 | private ReflectionProvider $reflectionProvider
26 | ) {
27 | }
28 |
29 | public function getNodeType(): string
30 | {
31 | return ClassConst::class;
32 | }
33 |
34 | public function processNode(Node $node, Scope $scope): array
35 | {
36 | $constantName = $node->consts[0]->name->name;
37 |
38 | if (!$this->isMapperConstant($constantName)) {
39 | return [];
40 | }
41 |
42 | $classReflection = $scope->getClassReflection();
43 |
44 | if (!$classReflection->isEnum()) {
45 | return [];
46 | }
47 |
48 | return $this->validate($classReflection, $constantName);
49 | }
50 |
51 | private function isMapperConstant(string $name): bool
52 | {
53 | return str_starts_with(strtolower($name), 'map');
54 | }
55 |
56 | /**
57 | * @param ClassReflection|null $class
58 | * @param string $constantName
59 | * @return array
60 | * @throws MissingConstantFromReflectionException
61 | */
62 | protected function validate(?ClassReflection $class, string $constantName): array
63 | {
64 | $implementsMappers = EnumImplements::mappers($class->getName());
65 | $return = [];
66 |
67 | $isValid = $this->isValidValue($class, $constantName);
68 |
69 | if (!$implementsMappers && $isValid) {
70 | $return = [
71 | RuleErrorBuilder::message(
72 | sprintf(
73 | 'Enumhancer: `%s` is not going to be used because enum is not implementing `Mappers`',
74 | $constantName,
75 | )
76 | )->build()
77 | ];
78 | }
79 |
80 | if ($implementsMappers && !$isValid) {
81 | $return = [
82 | RuleErrorBuilder::message(
83 | sprintf(
84 | 'Enumhancer: The specified `%s` map is invalid',
85 | $constantName,
86 | )
87 | )->build()
88 | ];
89 | }
90 |
91 | return $return;
92 | }
93 |
94 | /**
95 | * @throws MissingConstantFromReflectionException
96 | */
97 | protected function isValidValue(
98 | ?ClassReflection $class,
99 | string $constantName
100 | ): bool {
101 | $valueType = $class->getConstant($constantName)->getValueType();
102 |
103 | $isValid = $valueType instanceof ConstantArrayType;
104 |
105 | if ($valueType instanceof ConstantStringType) {
106 | $class = $valueType->getValue();
107 | try {
108 | $classReflection = $this->reflectionProvider->getClass($class);
109 | $isValid = $classReflection->isSubclassOf(Mapper::class);
110 | } catch (\PHPStan\Broker\ClassNotFoundException) {
111 | $isValid = false;
112 | }
113 | }
114 |
115 | return $isValid;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/PHPStan/Constants/Rules/StrictConstantRule.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | class StrictConstantRule implements Rule
17 | {
18 | public function getNodeType(): string
19 | {
20 | return ClassConstantsNode::class;
21 | }
22 |
23 | public function processNode(Node $node, Scope $scope): array
24 | {
25 | $constantName = $node->getConstants()[0]->consts[0]->name->name;
26 |
27 | if (strtolower($constantName) !== 'strict') {
28 | return [];
29 | }
30 |
31 | $class = $scope->getClassReflection();
32 |
33 | if ($this->shouldProcessEnum($class)) {
34 | return [];
35 | }
36 |
37 | if ($class->getConstant($constantName)->getValueType()->isBoolean()->no()) {
38 | return [
39 | RuleErrorBuilder::message(
40 | sprintf('Enumhancer: constant `%s` should be a boolean.', $constantName)
41 | )->build()
42 | ];
43 | }
44 |
45 | return [];
46 | }
47 |
48 | /**
49 | * @param ClassReflection|null $class
50 | * @return bool
51 | */
52 | protected function shouldProcessEnum(?ClassReflection $class): bool
53 | {
54 | return !$class->isEnum() || !EnumImplements::enumhancer($class->getName());
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/PHPStan/Constants/StrictConstantAlwaysUsed.php:
--------------------------------------------------------------------------------
1 | getName()) !== 'strict') {
15 | return false;
16 | }
17 |
18 | $class = $constant->getDeclaringClass();
19 |
20 | if (!$class->isEnum()) {
21 | return false;
22 | }
23 |
24 | return EnumImplements::enumhancer($class->getName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/PHPStan/Methods/EnumComparisonMethodsClassReflection.php:
--------------------------------------------------------------------------------
1 | isEnum()) {
18 | return false;
19 | }
20 |
21 | return EnumCompare::isValidCall(
22 | $classReflection->getName(),
23 | $methodName,
24 | []
25 | );
26 | }
27 |
28 | public function getMethod(
29 | ClassReflection $classReflection,
30 | string $methodName
31 | ): MethodReflection {
32 | return new ClosureMethodReflection(
33 | $classReflection,
34 | $methodName,
35 | new ClosureType([], new BooleanType(), false)
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/PHPStan/Methods/EnumConstructorMethodsClassReflection.php:
--------------------------------------------------------------------------------
1 | isEnum()) {
19 | return false;
20 | }
21 |
22 | $className = $classReflection->getName();
23 |
24 | if (!EnumImplements::constructor($className)) {
25 | return false;
26 | }
27 |
28 | return EnumGetters::tryGet($className, $methodName, useDefault: false) !== null;
29 | }
30 |
31 | public function getMethod(
32 | ClassReflection $classReflection,
33 | string $methodName
34 | ): MethodReflection {
35 |
36 | return new ClosureMethodReflection(
37 | $classReflection,
38 | $methodName,
39 | new ClosureType(
40 | [],
41 | new ObjectType($classReflection->getName()),
42 | false
43 | ),
44 | true
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/PHPStan/Methods/EnumMacrosMethodsClassReflection.php:
--------------------------------------------------------------------------------
1 | isEnum()) {
26 | return false;
27 | }
28 |
29 | $className = $classReflection->getName();
30 |
31 | if (EnumImplements::macros($className)) {
32 | return $this->getMacro($className, $methodName) !== null;
33 | }
34 |
35 | return false;
36 | }
37 |
38 | public function getMethod(
39 | ClassReflection $classReflection,
40 | string $methodName
41 | ): MethodReflection {
42 | $className = $classReflection->getName();
43 | $macro = $this->getMacro($className, $methodName);
44 | /**
45 | * PHPStan does not support isStatic on closureType
46 | * @phpstan-ignore-next-line
47 | */
48 | $nativeReflection = new ReflectionFunction($macro);
49 |
50 | try {
51 | return (new ClosureMethodReflection(
52 | $classReflection,
53 | $methodName,
54 | $this->closureTypeFactory->fromClosureObject(
55 | $macro
56 | ),
57 | $nativeReflection->isStatic(),
58 | ))->setDocDocument(
59 | $nativeReflection->getDocComment()
60 | );
61 | } catch (LogicException) {
62 | /**
63 | * Transforming Illogic Exception into an explanatory Logically clear exception.
64 | */
65 | throw new LogicException(
66 | sprintf(
67 | 'PHPStan Could not properly parse closure `%s` for `%s`, '
68 | . 'Check if a default value\'s code is not trying to execute this macro.',
69 | $methodName,
70 | $className
71 | )
72 | );
73 | }
74 | }
75 |
76 | public function getMacro(string $class, string $macro): ?Closure
77 | {
78 | return Closure::bind(
79 | function (string $class, string $macro) {
80 | return EnumMacros::getMacro($class, $macro);
81 | },
82 | null,
83 | EnumMacros::class
84 | )($class, $macro);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/PHPStan/Methods/EnumStateMethodsClassReflection.php:
--------------------------------------------------------------------------------
1 | isEnum()) {
18 | return false;
19 | }
20 |
21 | return EnumState::isValidCall(
22 | $classReflection->getName(),
23 | $methodName
24 | );
25 | }
26 |
27 | public function getMethod(
28 | ClassReflection $classReflection,
29 | string $methodName
30 | ): MethodReflection {
31 |
32 | return new ClosureMethodReflection(
33 | $classReflection,
34 | $methodName,
35 | new ClosureType(
36 | [],
37 | new ObjectType($classReflection->getName()),
38 | false
39 | )
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/PHPStan/Reflections/ClosureMethodReflection.php:
--------------------------------------------------------------------------------
1 | docComment = $docComment;
30 | return $this;
31 | }
32 |
33 |
34 | public function getDeclaringClass(): ClassReflection
35 | {
36 | return $this->classReflection;
37 | }
38 |
39 | public function isPrivate(): bool
40 | {
41 | return false;
42 | }
43 |
44 | public function isPublic(): bool
45 | {
46 | return true;
47 | }
48 |
49 | public function isFinal(): TrinaryLogic
50 | {
51 | return TrinaryLogic::createNo();
52 | }
53 |
54 | public function isInternal(): TrinaryLogic
55 | {
56 | return TrinaryLogic::createNo();
57 | }
58 |
59 | public function isStatic(): bool
60 | {
61 | return $this->isStatic;
62 | }
63 |
64 | public function getDocComment(): ?string
65 | {
66 | if (!$this->docComment || is_bool($this->docComment)) {
67 | return null;
68 | }
69 |
70 | return $this->docComment;
71 | }
72 |
73 | public function getName(): string
74 | {
75 | return $this->methodName;
76 | }
77 |
78 | public function isDeprecated(): TrinaryLogic
79 | {
80 | return TrinaryLogic::createNo();
81 | }
82 |
83 | public function getPrototype(): ClassMemberReflection
84 | {
85 | return $this;
86 | }
87 |
88 | public function getVariants(): array
89 | {
90 | return [
91 | new FunctionVariant(
92 | TemplateTypeMap::createEmpty(),
93 | null,
94 | $this->closureType->getParameters(),
95 | $this->closureType->isVariadic(),
96 | $this->closureType->getReturnType()
97 | ),
98 | ];
99 | }
100 |
101 | public function getDeprecatedDescription(): ?string
102 | {
103 | return null;
104 | }
105 |
106 | public function getThrowType(): ?Type
107 | {
108 | return null;
109 | }
110 |
111 | public function hasSideEffects(): TrinaryLogic
112 | {
113 | return TrinaryLogic::createMaybe();
114 | }
115 | }
116 |
--------------------------------------------------------------------------------