How can I combine contextual binding and service location? - php

Disclaimer: I would have preferred a more generic question like "How do I keep track of the state of a recursive method?" on the code review stack exchange site, as that better describes where the problem is currently at. But the constraint on that board is that code must first be working
Background: I have a container that
can recursively hydrate and inject constructor arguments
concretes can be provided to it for use to circumvent hydration
provision context exist in two states: for classes being auto-wired, and for classes using the container as a service locator
Three of these target behaviours function as expected, save when the last 2 are combined.
Main problem: when 3a is followed by 3b, container uses an incorrect context, and I don't know how to inspect that particular state since the hydrator/service locator is recursive. Unit testing the individual methods all work correctly. Integration test of either of the two works. But at the level of multi layer hydration, I can't mock out any of the involved parts, thus, I have no way of determining what context is used there
I feel the problem is more of a philosophical one, where more than one answer is applicable. But instead of down-voting, kindly migrate to the appropriate stack exchange site
The code is in PHP, but if you aren't conversant with it, pseudo-code or a verbal solution is welcome. An acceptable solution may even be a test that demonstrates how to simulate and observe container state after hydration and service location. I already have this, but it fails
public function test_hydrated_class_with_getClass_correctly_uses_needs () {
$ourB = new BCounter; // given
$this->container->provideSelf();
$this->container->whenTypeAny()->needsAny([
BCounter::class => $ourB
]);
$this->assertSame( // then
$this->container->getClass($this->aRequires)
->getInternalB(), // when
$this->ourB
);
}
Relevant parts of the container below
<?php
use ReflectionMethod, ReflectionClass, ReflectionFunction, ReflectionType, ReflectionFunctionAbstract, ReflectionException;
class Container {
const UNIVERSAL_SELECTOR = "*";
private $provisionedNamespaces = [], // NamespaceUnit[]
$hydratingForStack = [], // String[]. Doubles as a dependency chain. #see [lastHydratedFor] for main usage
$internalMethodHydrate = false, // Used when [getMethodParameters] is called directly without going through instance methods such as [instantiateConcrete]
$hydratingArguments = false,
$constructor = "__construct",
$externalHydrators = [], $externalContainerManager,
$interfaceHydrator,
$provisionContext, // the active Type before calling `needs`
$provisionSpace; // same as above, but for namespaces
protected $provisionedClasses = []; // ProvisionUnit[]
public function __construct () {
$this->initializeUniversalProvision();
}
public function initializeUniversalProvision ():void {
$this->provisionedClasses[self::UNIVERSAL_SELECTOR] = new ProvisionUnit;
}
public function getInterfaceHydrator ():InterfaceHydrator {
return $this->interfaceHydrator;
}
/**
* Looks for the given class in this order
* 1) pre-provisioned caller list
* 2) Provisions it afresh if an interface or recursively wires in its constructor dependencies
*
* #param {includeSub} Regular provision: A wants B, but we give C sub-class of B. Sub-classes of A can't obtain B unless this parameter is used
*
* #return A class instance, if found
*/
public function getClass (string $fullName, bool $includeSub = false) {
$concrete = $this->decorateProvidedConcrete($fullName);
if (!is_null($concrete)) return $concrete;
if ($includeSub && $parent = $this->hydrateChildsParent($fullName))
return $parent;
$externalManager = $this->externalContainerManager;
if (
!is_null($externalManager) &&
$concrete = $externalManager->findInManagers($fullName)
) {
$this->saveWhenImplements($fullName, $concrete);
return $concrete;
}
return $this->initializeHydratingForAction($fullName, function ($className) {
if ($this->getReflectedClass($className)->isInterface())
return $this->provideInterface($className);
return $this->instantiateConcrete($className);
});
}
public function decorateProvidedConcrete (string $fullName) {
$freshlyCreated = $this->initializeHydratingForAction($fullName, function ($className) {
return new HydratedConcrete(
$this->getProvidedConcrete($className),
$this->lastHydratedFor()
);
});
if (!is_null($freshlyCreated->getConcrete()))
return $this->getDecorator()->scopeInjecting(
$freshlyCreated->getConcrete(),
$freshlyCreated->getCreatedFor()
); // decorator runs on each fetch (rather than only once), since different callers result in different behavior
}
public function getProvidedConcrete (string $fullName) {
$context = $this->getRecursionContext();
if ($context->hasConcrete($fullName))
return $context->getConcrete($fullName);
$globalContext = $this->provisionedClasses[self::UNIVERSAL_SELECTOR];
if ($globalContext->hasConcrete($fullName)) // current provision doesn't include this class. check in global
return $globalContext->getConcrete($fullName);
}
/**
* Switches unit being provided to universal if it doesn't exist
* #return currently available provision unit
*/
public function getRecursionContext ():ProvisionUnit {
$hydrateFor = $this->lastHydratedFor();
if (!array_key_exists($hydrateFor, $this->provisionedClasses))
$hydrateFor = self::UNIVERSAL_SELECTOR;
return $this->provisionedClasses[$hydrateFor];
}
/**
* This tells us the class we are hydrating arguments for
*/
public function lastHydratedFor ():?string {
$stack = $this->hydratingForStack;
if(empty($stack) ) return null;
$index = $this->hydratingArguments ? 2: 1; // If we're hydrating class A -> B -> C, we want to get provisions for B (who, at this point, is indexed -2 while C is -1). otherwise, we'll be looking through C's provisions instead of B
$length = count($stack);
return $stack[$length - $index];
}
/**
* Not explicitly decorating objects from here since it calls [getClass]
*/
private function hydrateChildsParent (string $fullName) {
$providedParent = $this->getProvidedParent($fullName);
if (!is_null($providedParent))
return $this->getClass($providedParent);
}
/**
* #return the first provided parent of the given class
*/
private function getProvidedParent (string $class):?string {
$allSuperiors = array_keys($this->provisionedClasses);
$classSuperiors = array_merge(
class_parents($class, true),
class_implements($class, true)
);
return current(
array_intersect($classSuperiors, $allSuperiors)
);
}
private function saveWhenImplements (string $interface, $concrete):void {
if (!($concrete instanceof $interface))
throw new InvalidImplementor($interface, get_class($concrete));
$this->storeConcrete( $interface, $concrete);
}
private function storeConcrete (string $fullName, $concrete):ProvisionUnit {
return $this->getRecursionContext()->addConcrete($fullName, $concrete);
}
private function getReflectedClass (string $className):ReflectionClass {
try {
return new ReflectionClass($className);
}
catch (ReflectionException $re) {
$message = "Unable to hydrate ". $this->lastHydratedFor() . ": ". $re->getMessage();
$hint = "Hint: Cross-check its dependencies";
throw new HydrationException("$message. $hint");
}
}
/**
* Wrap any call that internally attempts to read from [lastHydratedFor] in this i.e. calls that do some hydration and need to know what context/provision they're being hydrated for
*/
public function initializeHydratingForAction (string $fullName, callable $action) {
$this->initializeHydratingFor($fullName);
$result = $action($fullName);
$this->popHydratingFor($fullName);
return $result;
}
/**
* Tells us who to hydrate arguments for
*/
protected function initializeHydratingFor (string $fullName):void {
$isFirstCall = is_null($this->lastHydratedFor());
$hydrateFor = $isFirstCall ? $this->lastCaller(): $fullName;
$this->pushHydratingFor($hydrateFor);
}
private function lastCaller ():string {
$stack = debug_backtrace (2 ); // 2=> ignore concrete objects and their args
$caller = "class";
foreach ($stack as $execution)
if (array_key_exists($caller, $execution) && $execution[$caller] != get_class()) {
return $execution[$caller];
}
}
/**
* Updates the last element in the context hydrating stack, to that whose provision dependencies should be hydrated for
*/
protected function pushHydratingFor (string $fullName):void {
$this->hydratingForStack[] = $fullName;
}
/**
* #param {completedHydration} To guarantee push-pop consistency. When the name of what is expected to be removed doesn't match the last item in stack, it indicates we're currently hydrating an interface (where its name differs from concretes involved). When this happens, we simply ignore popping our list since those concretes were not the ones that originally got pushed
*/
private function popHydratingFor (string $completedHydration):void {
if (end($this->hydratingForStack) == $completedHydration)
array_pop($this->hydratingForStack);
}
/**
* #throws InvalidImplementor
*
* #return Concrete of the given [Interface] if it was bound
*/
protected function provideInterface (string $interface) {
$caller = $this->lastHydratedFor();
if ($this->hasRenamedSpace($caller)) {
$newIdentity = $this->relocateSpace($interface, $caller);
$concrete = $this->instantiateConcrete($newIdentity);
}
else {
$concrete = $this->getInterfaceHydrator()->deriveConcrete($interface);
if (!is_null($concrete))
$this->saveWhenImplements($interface, $concrete);
}
if (is_null($concrete))
throw new InvalidImplementor($interface, "No matching concrete" );
return $concrete;
}
/**
* A shorter version of [getClass], but neither checks in cache or contextual provisions. This means they're useful to:
* 1) To hydrate classes we're sure doesn't exist in the cache
* 2) In methods that won't be called more than once in the request cycle
* 3) To create objects that are more or less static, or can't be overidden by an extension
*
* All objects internally derived from this trigger decorators if any are applied
*/
public function instantiateConcrete (string $fullName) {
$freshlyCreated = $this->initializeHydratingForAction ($fullName, function ($className) {
if (!method_exists($className, $this->constructor))
return new HydratedConcrete(new $className, $this->lastHydratedFor() );
return $this->hydrateConcreteForCaller($className);
});
$this->storeConcrete($fullName, $freshlyCreated->getConcrete());
return $this->getDecorator()->scopeInjecting(
$freshlyCreated->getConcrete(),
$freshlyCreated->getCreatedFor()
);
}
public function hydrateConcreteForCaller (string $className):HydratedConcrete {
$dependencies = $this->internalMethodGetParameters(function () use ($className) {
return array_values($this->getMethodParameters($this->constructor, $className));
});
return new HydratedConcrete(
new $className (...$dependencies),
$this->lastHydratedFor()
);
}
public function internalMethodGetParameters (callable $action) {
$this->internalMethodHydrate = true;
$result = $action();
$this->internalMethodHydrate = false;
return $result;
}
/**
* Fetch appropriate dependencies for a callable's arguments
*
* #param {callable}:string|Closure
* #param {anchorClass} the class the given method belongs to
*
* #return {Array} associative. Contains hydrated parameters to invoke given callable with
*/
public function getMethodParameters ( $callable, string $anchorClass = null):array {
$context = null;
if (is_null($anchorClass))
$reflectedCallable = new ReflectionFunction($callable);
else {
$reflectedCallable = new ReflectionMethod($anchorClass, $callable);
if (!$this->internalMethodHydrate)
$this->initializeHydratingFor($anchorClass);
$context = $this->getRecursionContext();
}
$dependencies = $this->populateDependencies($reflectedCallable, $context);
if (is_null($anchorClass)) return $dependencies;
elseif (!$this->internalMethodHydrate)
$this->popHydratingFor($anchorClass);
return $this->getDecorator()->scopeArguments( $anchorClass, $dependencies, $callable);
}
public function populateDependencies (ReflectionFunctionAbstract $reflectedCallable, ?ProvisionUnit $callerProvision):array {
$dependencies = [];
foreach ($reflectedCallable->getParameters() as $parameter) {
$parameterName = $parameter->getName();
$parameterType = $parameter->getType();
if (!is_null($callerProvision) )
$dependencies[$parameterName] = $this->hydrateProvidedParameter($callerProvision, $parameterType, $parameterName);
elseif (!is_null($parameterType))
$dependencies[$parameterName] = $this->hydrateUnprovidedParameter($parameterType);
elseif ($parameter->isOptional() )
$dependencies[$parameterName] = $parameter->getDefaultValue();
else $dependencies[$parameterName] = null;
}
return $dependencies;
}
/**
* Pulls out a provided instance of a dependency when present, or creates a fresh one
*
* #return object matching type at given parameter
*/
private function hydrateProvidedParameter (ProvisionUnit $callerProvision, ReflectionType $parameterType, string $parameterName) {
if ($callerProvision->hasArgument($parameterName))
return $callerProvision->getArgument($parameterName);
$typeName = $parameterType->getName();
if ($callerProvision->hasArgument($typeName))
return $callerProvision->getArgument($typeName);
return $this->hydrateUnprovidedParameter($parameterType);
}
private function hydrateUnprovidedParameter (ReflectionType $parameterType) {
$typeName = $parameterType->getName();
if ( $parameterType->isBuiltin()) {
$defaultValue = null;
settype($defaultValue, $typeName);
return $defaultValue;
}
if (!in_array($typeName, $this->hydratingForStack)) {
$this->hydratingArguments = true;
$concrete = $this->getClass($typeName);
$this->hydratingArguments = false;
return $concrete;
}
if ($this->getReflectedClass($typeName)->isInterface())
throw new HydrationException ("$typeName's concrete cannot depend on its dependency's concrete");
trigger_error("Circular dependency detected while hydrating $typeName", E_USER_WARNING);
}
public function whenType (string $toProvision):self {
if (!array_key_exists($toProvision, $this->provisionedClasses))
$this->provisionedClasses[$toProvision] = new ProvisionUnit;
$this->provisionContext = $toProvision;
return $this;
}
public function whenTypeAny ():self {
return $this->whenType(self::UNIVERSAL_SELECTOR);
}
public function needs (array $dependencyList):self {
if (is_null ($this->provisionContext))
throw new HydrationException("Undefined provisionContext");
$this->provisionedClasses[$this->provisionContext]->updateConcretes($dependencyList);
return $this;
}
public function needsAny (array $dependencyList):self {
$this->needs($dependencyList)
->needsArguments($dependencyList);
$this->provisionContext = null;
return $this;
}
public function needsArguments (array $argumentList):self {
if (is_null ($this->provisionContext))
throw new HydrationException("Undefined provisionContext");
$this->provisionedClasses[$this->provisionContext]->updateArguments($argumentList);
return $this;
}
public function provideSelf ():void {
$this->whenTypeAny()->needsAny([get_class() => $this]);
}
protected function getDecorator ():DecoratorHydrator {
return $this->decorator;
}
}
?>
The relevant parts are:
decorateProvidedConcrete (who I want to assert was called twice, but by mocking, getClass can no longer function)
public function test_hydrated_class_with_getClass_reads_provision () {
// given
$container = $this->positiveDouble(Container::class, [
"getDecorator" => $this->stubDecorator(),
"getProvidedConcrete" => $this->returnCallback(function ($subject) {
return $this->positiveDouble($subject, []); // return a stub
})
], [
"getProvidedConcrete" => [2, [
$this->callback(function ($subject) {
return BCounter::class == $subject; // this obviously won't work, since method attempts to hydrate other classes, as well
})
]] // then 1
]);
$this->bootContainer($container);
$this->entityBindings();
// when
$container->getClass($this->aRequires)->getInternalB();
}
getRecursionContext, who I have no way of observing until getClass returns. But I would like to know what context it's working with by the time we're doing the service location. And, that's difficult to figure out since getClass is recursive
Finally, ARequiresBCounter. I want to DI this class with provided BCounter. Then when getInternalB runs, it equally uses the provided BCounter
class ARequiresBCounter {
private $b1, $container, $primitive;
public function __construct (BCounter $b1, Container $container, string $primitive) {
$this->b1 = $b1;
$this->container = $container;
$this->primitive = $primitive;
}
public function getConstructorB ():BCounter {
return $this->b1;
}
public function getInternalB ():BCounter {
return $this->container->getClass(BCounter::class);
}
}

Related

PHP Nesting functions OOP

I am using a library, and it has the following process to attach many operations to one event:
$action = (new EventBuilder($target))->addOperation($Operation1)->addOperation($Operation2)->addOperation($Operation3)->compile();
I am not sure how to dynamically add operations depending on what I need done.
Something like this
$action = (new EventBuilder($target));
while (some event) {
$action = $action->addOperation($OperationX);
}
$action->compile();
I need to be able to dynamically add operations in while loop and when all have been added run it.
Your proposed solution will work. The EventBuilder provides what is known as a Fluent Interface, which means that there are methods that return an instance of the builder itself, allowing you to chain calls to addOperation as many times as you want, then call the compile method to yield a result. However you are free to ignore the return value of addOperation as long as you have a variable containing an instance of the builder that you can eventually call compile on.
Take a walk with me...
// Some boilerplate classes to work with
class Target
{
private ?string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}
class Operation
{
private ?string $verb;
public function __construct(string $verb)
{
$this->verb = $verb;
}
public function getVerb(): string
{
return $this->verb;
}
}
class Action
{
private ?Target $target;
private array $operations = [];
public function __construct(Target $target, array $operations)
{
$this->target = $target;
$this->operations = $operations;
}
/**
* Do the things
* #return array
*/
public function run(): array
{
$output = [];
foreach ($this->operations as $currOperation)
{
$output[] = $currOperation->getVerb() . ' the ' . $this->target->getName();
}
return $output;
}
}
Here is a basic explanation of what your EventBuilder is doing under the covers:
class EventBuilder
{
private ?Target $target;
private array $operations = [];
public function __construct(Target $target)
{
$this->target = $target;
}
/**
* #param Operation $operation
* #return $this
*/
public function addOperation(Operation $operation): EventBuilder
{
$this->operations[] = $operation;
// Fluent interface - return a reference to the instance
return $this;
}
public function compile(): Action
{
return new Action($this->target, $this->operations);
}
}
Let's try both techniques and prove they will produce the same result:
// Mock some operations
$myOperations = [
new Operation('Repair'),
new Operation('Clean'),
new Operation('Drive')
];
// Create a target
$target = new Target('Car');
/*
* Since the EventBuilder implements a fluent interface (returns an instance of itself from addOperation),
* we can chain the method calls together and just put a call to compile() at the end, which will return
* an Action instance
*/
$fluentAction = (new EventBuilder($target))
->addOperation($myOperations[0])
->addOperation($myOperations[1])
->addOperation($myOperations[2])
->compile();
// Run the action
$fluentResult = $fluentAction->run();
// Traditional approach, create an instance and call the addOperation method as needed
$builder = new EventBuilder($target);
// Pass our mocked operations
while (($currAction = array_shift($myOperations)))
{
/*
* We can ignore the result from addOperation here, just keep calling the method
* on the builder variable
*/
$builder->addOperation($currAction);
}
/*
* After we've added all of our operations, we can call compile on the builder instance to
* generate our Action.
*/
$traditionalAction = $builder->compile();
// Run the action
$traditionalResult = $traditionalAction->run();
// Verify that the results from both techniques are identical
assert($fluentResult == $traditionalResult, 'Results from both techniques should be identical');
// Enjoy the fruits of our labor
echo json_encode($traditionalResult, JSON_PRETTY_PRINT).PHP_EOL;
Output:
[
"Repair the Car",
"Clean the Car",
"Drive the Car"
]
Rob Ruchte thank you for detailed explanation, one thing I did not include was that each operation itself had ->build() call and I needed to move that to each $builder for it to work.

Returning a child of class that is returned from a Trait

I then have a Base DTO class
class BaseDto
{
public function __construct(array $dtoValues)
{
$this->properties = array_map(static function (ReflectionProperty $q) {
return trim($q->name);
}, (new ReflectionClass(get_class($this)))->getProperties(ReflectionProperty::IS_PUBLIC));
foreach ($dtoValues as $propertyName => $value) {
$propertyName = Str::camel($propertyName);
if (in_array($propertyName, $this->properties, false)) {
$this->{$propertyName} = $value;
}
}
}
}
I also have an actual DTO class
class ModelDTO extends BaseDto
{
public int $id
public string $name;
}
I have the following Trait in PHP
trait ToDtoTrait
{
/**
* #param string $dtoClassName
* #return BaseDto
* #throws InvalidArgumentException
*/
public function toDto(string $dtoClassName): BaseDto;
{
$this->validateDtoClass($dtoClassName, BaseDto::class);
return new $dtoClassName($this->toArray());
}
/**
* #param string $dtoClassName
* #param string $baseClassName
* #return void
*/
private function validateDtoClass(string $dtoClassName, string $baseClassName)
{
if (!class_exists($dtoClassName)) {
throw new InvalidArgumentException("Trying to create a DTO for a class that doesn't exist: {$dtoClassName}");
}
if (!is_subclass_of($dtoClassName, $baseClassName)) {
throw new InvalidArgumentException("Can not convert current object to '{$dtoClassName}' as it is not a valid DTO class: " . self::class);
}
}
}
That trait is then used inside of my Model classes
class MyDbModel
{
use ToDtoTrait;
}
So this allows me to get an entry from the DB via the Model and then call toDto to receive an instance of the DTO class. Simple enough.
Now I have a service and this service will basically find the entry and return the DTO.
class MyService
{
public function find(int $id): ?ModelDTO
{
$model = MyModel::find($id);
if (empty($model)) {
return null;
}
return $model->toDto();
}
}
When I do it this way, I get a warning in the IDE:
Return value is expected to be '\App\Dtos\ModelDto|null', '\App\Dtos\BaseDto' returned
How do I declare this so that people can see that MyService::find() returns an instance of ModelDto so they will have autocomplete for the attributes of the DTO and any other base functions that come with it (not shown here).
The warning is raised because the return type of ToDtoTrait::toDto isBaseDto while the return type of MyService::find is ?ModelDTO, which are polymorphically incompatible (a BaseDto is not necessarily a ModelDTO).
An easy solution is to narrow down the DTO type using instanceof:
// MyService::find
$model = MyModel::find($id);
if (empty($model)) {
return null;
}
$dto = $model->toDto();
if (!$dto instanceof ModelDTO) {
return null;
}
return $dto;
Sidenote: Why is ToDtoTrait::toDto called without arguments in MyService (return $model->toDto();)? Looks like you want to pass ModelDTO::class to it: return $model->toDto(ModelDTO::class);.

Rewrite PHPUnit test after the at() matcher deprecation

I used to have a PHPUnit code like that (probably not a good one as I am not able to rewrite it):
$authorizator->expects($this->at(0))
->method('isAllowed')
->willReturn($hasSuperPrivilege);
if (!$hasSuperPrivilege) {
$authorizator->expects($this->at(1))
->method('isAllowed')
->willReturn($hasStandardPrivilege);
}
How to achieve the same behavior after the deprecation of at() method?
I don't know how many times the method will be called, so probably the willReturnOnConsecutiveCalls way is not the right one?
If the first call returns false, then the isAllowed() method must be called twice, if it returned true, then only once.
Related issue: https://github.com/sebastianbergmann/phpunit/issues/4297
Tested method:
public function canUserdoSomething(Foo $resource, Identity $identity): bool
{
if (
$this->authorizator->isAllowed(
$identity,
$resource,
'superPrivilege'
)
) {
return true;
}
if (
$this->authorizator->isAllowed(
$identity,
$resource,
'standardPrivilege'
)
) {
return $resource->hasSomeProperty();
}
return false;
}
I'd go with a handwritten fake so you don't have to worry about the number of calls. I did it inline in my example for brevity but if you're using the authorizator in multiple test, it makes sense to extract it into a separate class.
/**
* #covers \MyClass
*/
class MyClassTest extends TestCase
{
private $subject;
private $authorizator;
/**
* #testWith ["superPrivilege", false, true]
* ["standardPrivilege", true, true]
* ["standardPrivilege", false, false]
* ["invalidPrivilege", true, false]
*/
public function testCanUserdoSomething($privelege, $resourceProperty, $expected)
{
$identity = new Identity();
$resource = new Foo($resourceProperty);
$this->authorizator->addPrivilege($identity, $resource, $privelege);
$result = $this->subject->canUserdoSomething($resource, $identity);
self::assertEquals($expected, $result);
}
protected function setUp(): void
{
parent::setUp();
$this->authorizator = new class implements Authorizator {
private $privileges = [];
public function addPrivilege(Identity $identity, Foo $resource, string $privilege): void
{
$this->privileges[] = spl_object_id($identity) . spl_object_id($resource) . $privilege;
}
public function isAllowed(Identity $identity, Foo $resource, string $privilege): bool
{
return in_array(spl_object_id($identity) . spl_object_id($resource) . $privilege, $this->privileges);
}
};
$this->subject = new MyClass($this->authorizator);
}
}

Dependency Injection Container PHP

I've recently learned about the advantages of using Dependency Injection (DI) in my PHP application.
However, I'm still unsure how to create my container for the dependencies. Before, I use a container from a framework and I want to understand how is he doing things in back and reproduce it.
For example:
The container from Zend 2. I understand that the container make class dynamic, he does not have to know about them from the beginning, he checks if he already has that class in his registry and if he has not he check if that class exist and what parameters has inside constructor and put it in his own registry so next time could take it from there, practical is doing everything dynamic and it is completing his own registry, so we do not have to take care of nothing once we implement the container as he can give as any class we want even if we just make that class.
Also if I want to getInstance for A which needs B and B needs C I understand that he doing this recursive and he goes and instantiate C then B and finally A.
So I understand the big picture and what is he suppose to do but I am not so sure about how to implement it.
You may be better off using one of the existing Dependency Containers out there, such as PHP-DI or Pimple. However, if you are looking for a simpler solution, then I've implemented a Dependency Container as part of an article that I wrote here: http://software-architecture-php.blogspot.com/
Here is the code for the container
class Container implements \DecoupledApp\Interfaces\Container\ContainerInterface
{
/**
* This function resolves the constructor arguments and creates an object
* #param string $dataType
* #return mixed An object
*/
private function createObject($dataType)
{
if(!class_exists($dataType)) {
throw new \Exception("$dataType class does not exist");
}
$reflectionClass = new \ReflectionClass($dataType);
$constructor = $reflectionClass->getConstructor();
$args = null;
$obj = null;
if($constructor !== null)
{
$block = new \phpDocumentor\Reflection\DocBlock($constructor);
$tags = $block->getTagsByName("param");
if(count($tags) > 0)
{
$args = array();
}
foreach($tags as $tag)
{
//resolve constructor parameters
$args[] = $this->resolve($tag->getType());
}
}
if($args !== null)
{
$obj = $reflectionClass->newInstanceArgs($args);
}
else
{
$obj = $reflectionClass->newInstanceArgs();
}
return $obj;
}
/**
* Resolves the properities that have a type that is registered with the Container.
* #param mixed $obj
*/
private function resolveProperties(&$obj)
{
$reflectionClass = new \ReflectionClass(get_class($obj));
$props = $reflectionClass->getProperties();
foreach($props as $prop)
{
$block = new \phpDocumentor\Reflection\DocBlock($prop);
//This assumes that there is only one "var" tag.
//If there are more than one, then only the first one will be considered.
$tags = $block->getTagsByName("var");
if(isset($tags[0]))
{
$value = $this->resolve($tags[0]->getType());
if($value !== null)
{
if($prop->isPublic()) {
$prop->setValue($obj, $value);
} else {
$setter = "set".ucfirst($prop->name);
if($reflectionClass->hasMethod($setter)) {
$rmeth = $reflectionClass->getMethod($setter);
if($rmeth->isPublic()){
$rmeth->invoke($obj, $value);
}
}
}
}
}
}
}
/**
*
* #param string $dataType
* #return object|NULL If the $dataType is registered, the this function creates the corresponding object and returns it;
* otherwise, this function returns null
*/
public function resolve($dataType)
{
$dataType = trim($dataType, "\\");
$obj = null;
if(isset($this->singletonRegistry[$dataType]))
{
//TODO: check if the class exists
$className = $this->singletonRegistry[$dataType];
$obj = $className::getInstance();
}
else if(isset($this->closureRegistry[$dataType]))
{
$obj = $this->closureRegistry[$dataType]();
}
else if(isset($this->typeRegistry[$dataType]))
{
$obj = $this->createObject($this->typeRegistry[$dataType]);
}
if($obj !== null)
{
//Now we need to resolve the object properties
$this->resolveProperties($obj);
}
return $obj;
}
/**
* #see \DecoupledApp\Interfaces\Container\ContainerInterface::make()
*/
public function make($dataType)
{
$obj = $this->createObject($dataType);
$this->resolveProperties($obj);
return $obj;
}
/**
*
* #param Array $singletonRegistry
* #param Array $typeRegistry
* #param Array $closureRegistry
*/
public function __construct($singletonRegistry, $typeRegistry, $closureRegistry)
{
$this->singletonRegistry = $singletonRegistry;
$this->typeRegistry = $typeRegistry;
$this->closureRegistry = $closureRegistry;
}
/**
* An array that stores the mappings of an interface to a concrete singleton class.
* The key/value pair corresond to the interface name/class name pair.
* The interface and class names are all fully qualified (i.e., include the namespaces).
* #var Array
*/
private $singletonRegistry;
/**
* An array that stores the mappings of an interface to a concrete class.
* The key/value pair corresond to the interface name/class name pair.
* The interface and class names are all fully qualified (i.e., include the namespaces).
* #var Array
*/
private $typeRegistry;
/**
* An array that stores the mappings of an interface to a closure that is used to create and return the concrete object.
* The key/value pair corresond to the interface name/class name pair.
* The interface and class names are all fully qualified (i.e., include the namespaces).
* #var Array
*/
private $closureRegistry;
}
The above code can be found here: https://github.com/abdulla16/decoupled-app (under the /Container folder)
You can register your dependencies as a singleton, as a type (every time a new object will be instantiated), or as a closure (the container will call the function that you register and that function is expected to return the instance).
For example,
$singletonRegistry = array();
$singletonRegistry["DecoupledApp\\Interfaces\\UnitOfWork\\UnitOfWorkInterface"] =
"\\DecoupledApp\\UnitOfWork\\UnitOfWork";
$typeRegistry = array();
$typeRegistry["DecoupledApp\\Interfaces\\DataModel\\Entities\\UserInterface"] =
"\\DecoupledApp\\DataModel\\Entities\\User";
$closureRegistry = array();
$closureRegistry["DecoupledApp\\Interfaces\\DataModel\\Repositories\\UserRepositoryInterface"] =
function() {
global $entityManager;
return $entityManager->getRepository("\\DecoupledApp\\DataModel\\Entities\\User");
};
$container = new \DecoupledApp\Container\Container($singletonRegistry, $typeRegistry, $closureRegistry);
This Container resolves properties of a class as well as the constructor parameters.
I have done a very simple IoC class which works as intended. I've investigated the IoC and DI pattern and especially after reading this answer. Let me know if something is not right or you have any questions .
<?php
class Dependency {
protected $object = null;
protected $blueprint = null;
/**
* #param $instance callable The callable passed to the IoC object.
*/
public function __construct($instance) {
if (!is_object($instance)) {
throw new InvalidArgumentException("Received argument should be object.");
}
$this->blueprint = $instance;
}
/**
* (Magic function)
*
* This function serves as man-in-the-middle for method calls,
* the if statement there serves for lazy loading the objects
* (They get created whenever you call the first method and
* all later calls use the same instance).
*
* This could allow laziest possible object definitions, like
* adding annotation parsing functionality which can extract everything during
* the call to the method. once the object is created it can get the annotations
* for the method, automatically resolve its dependencies and satisfy them,
* if possible or throw an error.
*
* all arguments passed to the method get passed to the method
* of the actual code dependency.
*
* #param $name string The method name to invoke
* #param $args array The array of arguments which will be passed
* to the call of the method
*
* #return mixed the result of the called method.
*/
public function __call($name, $args = array())
{
if (is_null($this->object)) {
$this->object = call_user_func($this->blueprint);
}
return call_user_func_array(array($this->object, $name), $args);
}
}
/*
* If the object implements \ArrayAccess you could
* have easier access to the dependencies.
*
*/
class IoC {
protected $immutable = array(); // Holds aliases for write-protected definitions
protected $container = array(); // Holds all the definitions
/**
* #param $alias string Alias to access the definition
* #param $callback callable The calback which constructs the dependency
* #param $immutable boolean Can the definition be overriden?
*/
public function register ($alias, $callback, $immutable = false) {
if (in_array($alias, $this->immutable)) {
return false;
}
if ($immutable) {
$this->immutable[] = $alias;
}
$this->container[$alias] = new Dependency($callback);
return $this;
}
public function get ($alias) {
if (!array_key_exists($alias, $this->container)) {
return null;
}
return $this->container[$alias];
}
}
class FooBar {
public function say()
{
return 'I say: ';
}
public function hello()
{
return 'Hello';
}
public function world()
{
return ', World!';
}
}
class Baz {
protected $argument;
public function __construct($argument)
{
$this->argument = $argument;
}
public function working()
{
return $this->argument->say() . 'Yep!';
}
}
/**
* Define dependencies
*/
$dic = new IoC;
$dic->register('greeter', function () {
return new FooBar();
});
$dic->register('status', function () use ($dic) {
return new Baz($dic->get('greeter'));
});
/**
* Real Usage
*/
$greeter = $dic->get('greeter');
print $greeter->say() . ' ' . $greeter->hello() . ' ' . $greeter->world() . PHP_EOL . '<br />';
$status = $dic->get('status');
print $status->working();
?>
I think the code is pretty self-explanatory, but let me know if something is not clear
Because I haven't find anything near what I wanted,I tried to implement on my own a container and I want to hear some opinion about how is looking,because I've start to learn php and oop a month ago a feedback is very important for me because I know I have many things to learn,so please feel free to bully my code :))
<!DOCTYPE html>
<!--
To change this license header, choose License Headers in Project Properties.
To change this template file, choose Tools | Templates
and open the template in the editor.
-->
<?php
class ioc
{
private $defs;
static $instance;
private $reflection;
private function __construct()
{
$defs = array();
$reflection = array();
}
private function __clone()
{
;
}
public static function getInstance()
{
if (!self::$instance) {
self::$instance = new ioc();
}
return self::$instance;
}
public function getInstanceOf($class)
{
if (is_array($this->defs) && key_exists($class, $this->defs)) {
if (is_object($this->defs[$class])) {
return $this->defs[$class];
}
} else {
if (class_exists($class)) {
if (is_array($this->reflection) && key_exists($class, $this->reflection)) {
$reflection = $this->reflection[$class];
} else {
$reflection = new ReflectionClass($class);
$this->reflection[$class] = $reflection;
}
$constructor = $reflection->getConstructor();
if ($constructor) {
$params = $constructor->getParameters();
if ($params) {
foreach ($params as $param) {
$obj[] = $this->getInstanceOf($param->getName());
}
$class_instance = $reflection->newInstanceArgs($obj);
$this->register($class, $class_instance);
return $class_instance;
}
}
if (!$constructor || !$params) {
$class_instance = new $class;
$this->register($class, $class_instance);
return $class_instance;
}
}
}
}
public function register($key, $class)
{
$this->defs[$key] = $class;
}
}
?>

Enumerations on PHP

I know that PHP doesn't yet have native Enumerations. But I have become accustomed to them from the Java world. I would love to use enums as a way to give predefined values which IDEs' auto-completion features could understand.
Constants do the trick, but there's the namespace collision problem and (or actually because) they're global. Arrays don't have the namespace problem, but they're too vague, they can be overwritten at runtime and IDEs rarely know how to autofill their keys without additional static analysis annotations or attributes.
Are there any solutions/workarounds you commonly use? Does anyone recall whether the PHP guys have had any thoughts or decisions around enumerations?
Edit: Since PHP 8.1, Enums are supported: https://www.php.net/manual/en/language.types.enumerations.php
—
Depending upon use case, I would normally use something simple like the following:
abstract class DaysOfWeek
{
const Sunday = 0;
const Monday = 1;
// etc.
}
$today = DaysOfWeek::Sunday;
However, other use cases may require more validation of constants and values. Based on the comments below about reflection, and a few other notes, here's an expanded example which may better serve a much wider range of cases:
abstract class BasicEnum {
private static $constCacheArray = NULL;
private static function getConstants() {
if (self::$constCacheArray == NULL) {
self::$constCacheArray = [];
}
$calledClass = get_called_class();
if (!array_key_exists($calledClass, self::$constCacheArray)) {
$reflect = new ReflectionClass($calledClass);
self::$constCacheArray[$calledClass] = $reflect->getConstants();
}
return self::$constCacheArray[$calledClass];
}
public static function isValidName($name, $strict = false) {
$constants = self::getConstants();
if ($strict) {
return array_key_exists($name, $constants);
}
$keys = array_map('strtolower', array_keys($constants));
return in_array(strtolower($name), $keys);
}
public static function isValidValue($value, $strict = true) {
$values = array_values(self::getConstants());
return in_array($value, $values, $strict);
}
}
By creating a simple enum class that extends BasicEnum, you now have the ability to use methods thusly for simple input validation:
abstract class DaysOfWeek extends BasicEnum {
const Sunday = 0;
const Monday = 1;
const Tuesday = 2;
const Wednesday = 3;
const Thursday = 4;
const Friday = 5;
const Saturday = 6;
}
DaysOfWeek::isValidName('Humpday'); // false
DaysOfWeek::isValidName('Monday'); // true
DaysOfWeek::isValidName('monday'); // true
DaysOfWeek::isValidName('monday', $strict = true); // false
DaysOfWeek::isValidName(0); // false
DaysOfWeek::isValidValue(0); // true
DaysOfWeek::isValidValue(5); // true
DaysOfWeek::isValidValue(7); // false
DaysOfWeek::isValidValue('Friday'); // false
As a side note, any time I use reflection at least once on a static/const class where the data won't change (such as in an enum), I cache the results of those reflection calls, since using fresh reflection objects each time will eventually have a noticeable performance impact (Stored in an assocciative array for multiple enums).
Now that most people have finally upgraded to at least 5.3, and SplEnum is available, that is certainly a viable option as well--as long as you don't mind the traditionally unintuitive notion of having actual enum instantiations throughout your codebase. In the above example, BasicEnum and DaysOfWeek cannot be instantiated at all, nor should they be.
There is a native extension, too. The SplEnum
SplEnum gives the ability to emulate and create enumeration objects
natively in PHP.
http://www.php.net/manual/en/class.splenum.php
Attention:
https://www.php.net/manual/en/spl-types.installation.php
The PECL extension is not bundled with PHP.
A DLL for this PECL extension is currently unavailable.
From PHP 8.1, you can use native enumerations.
The basic syntax looks like this:
enum TransportMode {
case Bicycle;
case Car;
case Ship;
case Plane;
case Feet;
}
function travelCost(TransportMode $mode, int $distance): int
{ /* implementation */ }
$mode = TransportMode::Boat;
$bikeCost = travelCost(TransportMode::Bicycle, 90);
$boatCost = travelCost($mode, 90);
// this one would fail: (Enums are singletons, not scalars)
$failCost = travelCost('Car', 90);
Values
By default, enumerations are not backed by any kind of scalar. So TransportMode::Bicycle is not 0, and you cannot compare using > or < between enumerations.
But the following works:
$foo = TransportMode::Car;
$bar = TransportMode::Car;
$baz = TransportMode::Bicycle;
$foo === $bar; // true
$bar === $baz; // false
$foo instanceof TransportMode; // true
$foo > $bar || $foo < $bar; // false either way
Backed Enumerations
You can also have "backed" enums, where each enumeration case is "backed" by either an int or a string.
enum Metal: int {
case Gold = 1932;
case Silver = 1049;
case Lead = 1134;
case Uranium = 1905;
case Copper = 894;
}
If one case has a backed value, all cases need to have a backed value, there are no auto-generated values.
Notice the type of the backed value is declared right after the enumeration name
Backed values are read only
Scalar values need to be unique
Values need to be literals or literal expressions
To read the backed value you access the value property: Metal::Gold->value.
Finally, backed enumerations implement a BackedEnum interface internally, which exposes two methods:
from(int|string): self
tryFrom(int|string): ?self
They are almost equivalent, with the important distinction that the first one will throw an exception if the value is not found, and the second will simply return null.
// usage example:
$metal_1 = Metal::tryFrom(1932); // $metal_1 === Metal::Gold;
$metal_2 = Metal::tryFrom(1000); // $metal_2 === null;
$metal_3 = Metal::from(9999); // throws Exception
Methods
Enumerations may have methods, and thus implement interfaces.
interface TravelCapable
{
public function travelCost(int $distance): int;
public function requiresFuel(): bool;
}
enum TransportMode: int implements TravelCapable{
case Bicycle = 10;
case Car = 1000 ;
case Ship = 800 ;
case Plane = 2000;
case Feet = 5;
public function travelCost(int $distance): int
{
return $this->value * $distance;
}
public function requiresFuel(): bool {
return match($this) {
TransportMode::Car, TransportMode::Ship, TransportMode::Plane => true,
TransportMode::Bicycle, TransportMode::Feet => false
}
}
}
$mode = TransportMode::Car;
$carConsumesFuel = $mode->requiresFuel(); // true
$carTravelCost = $mode->travelCost(800); // 800000
Value listing
Both Pure Enums and Backed Enums internally implement the interface UnitEnum, which includes the (static) method UnitEnum::cases(), and allows to retrieve an array of the cases defined in the enumeration:
$modes = TransportMode::cases();
And now $modes is:
[
TransportMode::Bicycle,
TransportMode::Car,
TransportMode::Ship,
TransportMode::Plane
TransportMode::Feet
]
Static methods
Enumerations can implement their own static methods, which would generally be used for specialized constructors.
This covers the basics. To get the whole thing, head on to the relevant RFC until the feature is released and published in PHP's documentation.
What about class constants?
<?php
class YourClass
{
const SOME_CONSTANT = 1;
public function echoConstant()
{
echo self::SOME_CONSTANT;
}
}
echo YourClass::SOME_CONSTANT;
$c = new YourClass;
$c->echoConstant();
I used classes with constants:
class Enum {
const NAME = 'aaaa';
const SOME_VALUE = 'bbbb';
}
print Enum::NAME;
The top answer above is fantastic. However, if you extend it in two different ways, then whichever extension is done first results in a call to the functions will create the cache. This cache will then be used by all subsequent calls, no matter whichever extension the calls are initiated by ...
To solve this, replace the variable and first function with:
private static $constCacheArray = null;
private static function getConstants() {
if (self::$constCacheArray === null) self::$constCacheArray = array();
$calledClass = get_called_class();
if (!array_key_exists($calledClass, self::$constCacheArray)) {
$reflect = new \ReflectionClass($calledClass);
self::$constCacheArray[$calledClass] = $reflect->getConstants();
}
return self::$constCacheArray[$calledClass];
}
I've commented on some of the other answers here, so I figured I would weigh in too.
At the end of the day, since PHP doesn't support typed enumerations, you can go one of two ways: hack out typed enumerations, or live with the fact that they're extremely difficult to hack out effectively.
I prefer to live with the fact, and instead use the const method that other answers here have used in some way or another:
abstract class Enum
{
const NONE = null;
final private function __construct()
{
throw new NotSupportedException(); //
}
final private function __clone()
{
throw new NotSupportedException();
}
final public static function toArray()
{
return (new ReflectionClass(static::class))->getConstants();
}
final public static function isValid($value)
{
return in_array($value, static::toArray());
}
}
An example enumeration:
final class ResponseStatusCode extends Enum
{
const OK = 200;
const CREATED = 201;
const ACCEPTED = 202;
// ...
const SERVICE_UNAVAILABLE = 503;
const GATEWAY_TIME_OUT = 504;
const HTTP_VERSION_NOT_SUPPORTED = 505;
}
Using Enum as a base class from which all other enumerations extend allows for helper methods, such as toArray, isValid, and so on. To me, typed enumerations (and managing their instances) just end up too messy.
Hypothetical
If, there existed a __getStatic magic method (and preferably an __equals magic method too) much of this could be mitigated with a sort of multiton pattern.
(The following is hypothetical; it won't work, though perhaps one day it will)
final class TestEnum
{
private static $_values = [
'FOO' => 1,
'BAR' => 2,
'QUX' => 3,
];
private static $_instances = [];
public static function __getStatic($name)
{
if (isset(static::$_values[$name]))
{
if (empty(static::$_instances[$name]))
{
static::$_instances[$name] = new static($name);
}
return static::$_instances[$name];
}
throw new Exception(sprintf('Invalid enumeration value, "%s"', $name));
}
private $_value;
public function __construct($name)
{
$this->_value = static::$_values[$name];
}
public function __equals($object)
{
if ($object instanceof static)
{
return $object->_value === $this->_value;
}
return $object === $this->_value;
}
}
$foo = TestEnum::$FOO; // object(TestEnum)#1 (1) {
// ["_value":"TestEnum":private]=>
// int(1)
// }
$zap = TestEnum::$ZAP; // Uncaught exception 'Exception' with message
// 'Invalid enumeration member, "ZAP"'
$qux = TestEnum::$QUX;
TestEnum::$QUX == $qux; // true
'hello world!' == $qux; // false
I use interface instead of class:
interface DaysOfWeek
{
const Sunday = 0;
const Monday = 1;
// etc.
}
var $today = DaysOfWeek::Sunday;
Well, for a simple java like enum in php, I use:
class SomeTypeName {
private static $enum = array(1 => "Read", 2 => "Write");
public function toOrdinal($name) {
return array_search($name, self::$enum);
}
public function toString($ordinal) {
return self::$enum[$ordinal];
}
}
And to call it:
SomeTypeName::toOrdinal("Read");
SomeTypeName::toString(1);
But I'm a PHP beginner, struggling with the syntax so this might not be the best way. I experimented some with Class Constants, using Reflection to get the constant name from it's value, might be neater.
Four years later I came across this again. My current approach is this as it allows for code completion in the IDE as well as type safety:
Base class:
abstract class TypedEnum
{
private static $_instancedValues;
private $_value;
private $_name;
private function __construct($value, $name)
{
$this->_value = $value;
$this->_name = $name;
}
private static function _fromGetter($getter, $value)
{
$reflectionClass = new ReflectionClass(get_called_class());
$methods = $reflectionClass->getMethods(ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC);
$className = get_called_class();
foreach($methods as $method)
{
if ($method->class === $className)
{
$enumItem = $method->invoke(null);
if ($enumItem instanceof $className && $enumItem->$getter() === $value)
{
return $enumItem;
}
}
}
throw new OutOfRangeException();
}
protected static function _create($value)
{
if (self::$_instancedValues === null)
{
self::$_instancedValues = array();
}
$className = get_called_class();
if (!isset(self::$_instancedValues[$className]))
{
self::$_instancedValues[$className] = array();
}
if (!isset(self::$_instancedValues[$className][$value]))
{
$debugTrace = debug_backtrace();
$lastCaller = array_shift($debugTrace);
while ($lastCaller['class'] !== $className && count($debugTrace) > 0)
{
$lastCaller = array_shift($debugTrace);
}
self::$_instancedValues[$className][$value] = new static($value, $lastCaller['function']);
}
return self::$_instancedValues[$className][$value];
}
public static function fromValue($value)
{
return self::_fromGetter('getValue', $value);
}
public static function fromName($value)
{
return self::_fromGetter('getName', $value);
}
public function getValue()
{
return $this->_value;
}
public function getName()
{
return $this->_name;
}
}
Example Enum:
final class DaysOfWeek extends TypedEnum
{
public static function Sunday() { return self::_create(0); }
public static function Monday() { return self::_create(1); }
public static function Tuesday() { return self::_create(2); }
public static function Wednesday() { return self::_create(3); }
public static function Thursday() { return self::_create(4); }
public static function Friday() { return self::_create(5); }
public static function Saturday() { return self::_create(6); }
}
Example usage:
function saveEvent(DaysOfWeek $weekDay, $comment)
{
// store week day numeric value and comment:
$myDatabase->save('myeventtable',
array('weekday_id' => $weekDay->getValue()),
array('comment' => $comment));
}
// call the function, note: DaysOfWeek::Monday() returns an object of type DaysOfWeek
saveEvent(DaysOfWeek::Monday(), 'some comment');
Note that all instances of the same enum entry are the same:
$monday1 = DaysOfWeek::Monday();
$monday2 = DaysOfWeek::Monday();
$monday1 === $monday2; // true
You can also use it inside of a switch statement:
function getGermanWeekDayName(DaysOfWeek $weekDay)
{
switch ($weekDay)
{
case DaysOfWeek::Monday(): return 'Montag';
case DaysOfWeek::Tuesday(): return 'Dienstag';
// ...
}
You can also create an enum entry by name or value:
$monday = DaysOfWeek::fromValue(2);
$tuesday = DaysOfWeek::fromName('Tuesday');
Or you can just get the name (i.e. the function name) from an existing enum entry:
$wednesday = DaysOfWeek::Wednesday()
echo $wednesDay->getName(); // Wednesday
I found this library on github and I think it provides a very decent alternative to the answers here.
PHP Enum implementation inspired from SplEnum
You can type-hint: function setAction(Action $action) {
You can enrich the enum with methods (e.g. format, parse, …)
You can extend the enum to add new values (make your enum final to prevent it)
You can get a list of all the possible values (see below)
Declaration
<?php
use MyCLabs\Enum\Enum;
/**
* Action enum
*/
class Action extends Enum
{
const VIEW = 'view';
const EDIT = 'edit';
}
Usage
<?php
$action = new Action(Action::VIEW);
// or
$action = Action::VIEW();
type-hint enum values:
<?php
function setAction(Action $action) {
// ...
}
If you need to use enums that are globally unique (i.e. even when comparing elements between different Enums) and are easy to use, feel free to use the following code. I also added some methods that I find useful. You will find examples in the comments at the very top of the code.
<?php
/**
* Class Enum
*
* #author Christopher Fox <christopher.fox#gmx.de>
*
* #version 1.0
*
* This class provides the function of an enumeration.
* The values of Enum elements are unique (even between different Enums)
* as you would expect them to be.
*
* Constructing a new Enum:
* ========================
*
* In the following example we construct an enum called "UserState"
* with the elements "inactive", "active", "banned" and "deleted".
*
* <code>
* Enum::Create('UserState', 'inactive', 'active', 'banned', 'deleted');
* </code>
*
* Using Enums:
* ============
*
* The following example demonstrates how to compare two Enum elements
*
* <code>
* var_dump(UserState::inactive == UserState::banned); // result: false
* var_dump(UserState::active == UserState::active); // result: true
* </code>
*
* Special Enum methods:
* =====================
*
* Get the number of elements in an Enum:
*
* <code>
* echo UserState::CountEntries(); // result: 4
* </code>
*
* Get a list with all elements of the Enum:
*
* <code>
* $allUserStates = UserState::GetEntries();
* </code>
*
* Get a name of an element:
*
* <code>
* echo UserState::GetName(UserState::deleted); // result: deleted
* </code>
*
* Get an integer ID for an element (e.g. to store as a value in a database table):
* This is simply the index of the element (beginning with 1).
* Note that this ID is only unique for this Enum but now between different Enums.
*
* <code>
* echo UserState::GetDatabaseID(UserState::active); // result: 2
* </code>
*/
class Enum
{
/**
* #var Enum $instance The only instance of Enum (Singleton)
*/
private static $instance;
/**
* #var array $enums An array of all enums with Enum names as keys
* and arrays of element names as values
*/
private $enums;
/**
* Constructs (the only) Enum instance
*/
private function __construct()
{
$this->enums = array();
}
/**
* Constructs a new enum
*
* #param string $name The class name for the enum
* #param mixed $_ A list of strings to use as names for enum entries
*/
public static function Create($name, $_)
{
// Create (the only) Enum instance if this hasn't happened yet
if (self::$instance===null)
{
self::$instance = new Enum();
}
// Fetch the arguments of the function
$args = func_get_args();
// Exclude the "name" argument from the array of function arguments,
// so only the enum element names remain in the array
array_shift($args);
self::$instance->add($name, $args);
}
/**
* Creates an enumeration if this hasn't happened yet
*
* #param string $name The class name for the enum
* #param array $fields The names of the enum elements
*/
private function add($name, $fields)
{
if (!array_key_exists($name, $this->enums))
{
$this->enums[$name] = array();
// Generate the code of the class for this enumeration
$classDeclaration = "class " . $name . " {\n"
. "private static \$name = '" . $name . "';\n"
. $this->getClassConstants($name, $fields)
. $this->getFunctionGetEntries($name)
. $this->getFunctionCountEntries($name)
. $this->getFunctionGetDatabaseID()
. $this->getFunctionGetName()
. "}";
// Create the class for this enumeration
eval($classDeclaration);
}
}
/**
* Returns the code of the class constants
* for an enumeration. These are the representations
* of the elements.
*
* #param string $name The class name for the enum
* #param array $fields The names of the enum elements
*
* #return string The code of the class constants
*/
private function getClassConstants($name, $fields)
{
$constants = '';
foreach ($fields as $field)
{
// Create a unique ID for the Enum element
// This ID is unique because class and variables
// names can't contain a semicolon. Therefore we
// can use the semicolon as a separator here.
$uniqueID = $name . ";" . $field;
$constants .= "const " . $field . " = '". $uniqueID . "';\n";
// Store the unique ID
array_push($this->enums[$name], $uniqueID);
}
return $constants;
}
/**
* Returns the code of the function "GetEntries()"
* for an enumeration
*
* #param string $name The class name for the enum
*
* #return string The code of the function "GetEntries()"
*/
private function getFunctionGetEntries($name)
{
$entryList = '';
// Put the unique element IDs in single quotes and
// separate them with commas
foreach ($this->enums[$name] as $key => $entry)
{
if ($key > 0) $entryList .= ',';
$entryList .= "'" . $entry . "'";
}
return "public static function GetEntries() { \n"
. " return array(" . $entryList . ");\n"
. "}\n";
}
/**
* Returns the code of the function "CountEntries()"
* for an enumeration
*
* #param string $name The class name for the enum
*
* #return string The code of the function "CountEntries()"
*/
private function getFunctionCountEntries($name)
{
// This function will simply return a constant number (e.g. return 5;)
return "public static function CountEntries() { \n"
. " return " . count($this->enums[$name]) . ";\n"
. "}\n";
}
/**
* Returns the code of the function "GetDatabaseID()"
* for an enumeration
*
* #return string The code of the function "GetDatabaseID()"
*/
private function getFunctionGetDatabaseID()
{
// Check for the index of this element inside of the array
// of elements and add +1
return "public static function GetDatabaseID(\$entry) { \n"
. "\$key = array_search(\$entry, self::GetEntries());\n"
. " return \$key + 1;\n"
. "}\n";
}
/**
* Returns the code of the function "GetName()"
* for an enumeration
*
* #return string The code of the function "GetName()"
*/
private function getFunctionGetName()
{
// Remove the class name from the unique ID
// and return this value (which is the element name)
return "public static function GetName(\$entry) { \n"
. "return substr(\$entry, strlen(self::\$name) + 1 , strlen(\$entry));\n"
. "}\n";
}
}
?>
I like enums from java too and for this reason I write my enums in this way, I think this is the most similiar behawior like in Java enums, of course, if some want to use more methods from java should write it here, or in abstract class but core idea is embedded in code below
class FruitsEnum {
static $APPLE = null;
static $ORANGE = null;
private $value = null;
public static $map;
public function __construct($value) {
$this->value = $value;
}
public static function init () {
self::$APPLE = new FruitsEnum("Apple");
self::$ORANGE = new FruitsEnum("Orange");
//static map to get object by name - example Enum::get("INIT") - returns Enum::$INIT object;
self::$map = array (
"Apple" => self::$APPLE,
"Orange" => self::$ORANGE
);
}
public static function get($element) {
if($element == null)
return null;
return self::$map[$element];
}
public function getValue() {
return $this->value;
}
public function equals(FruitsEnum $element) {
return $element->getValue() == $this->getValue();
}
public function __toString () {
return $this->value;
}
}
FruitsEnum::init();
var_dump(FruitsEnum::$APPLE->equals(FruitsEnum::$APPLE)); //true
var_dump(FruitsEnum::$APPLE->equals(FruitsEnum::$ORANGE)); //false
var_dump(FruitsEnum::$APPLE instanceof FruitsEnum); //true
var_dump(FruitsEnum::get("Apple")->equals(FruitsEnum::$APPLE)); //true - enum from string
var_dump(FruitsEnum::get("Apple")->equals(FruitsEnum::get("Orange"))); //false
abstract class Enumeration
{
public static function enum()
{
$reflect = new ReflectionClass( get_called_class() );
return $reflect->getConstants();
}
}
class Test extends Enumeration
{
const A = 'a';
const B = 'b';
}
foreach (Test::enum() as $key => $value) {
echo "$key -> $value<br>";
}
It might be as simple as
enum DaysOfWeek {
Sunday,
Monday,
// ...
}
in the future.
PHP RFC: Enumerated Types
Finally, a PHP 7.1+ answer with constants that cannot be overridden.
/**
* An interface that groups HTTP Accept: header Media Types in one place.
*/
interface MediaTypes
{
/**
* Now, if you have to use these same constants with another class, you can
* without creating funky inheritance / is-a relationships.
* Also, this gets around the single inheritance limitation.
*/
public const HTML = 'text/html';
public const JSON = 'application/json';
public const XML = 'application/xml';
public const TEXT = 'text/plain';
}
/**
* An generic request class.
*/
abstract class Request
{
// Why not put the constants here?
// 1) The logical reuse issue.
// 2) Single Inheritance.
// 3) Overriding is possible.
// Why put class constants here?
// 1) The constant value will not be necessary in other class families.
}
/**
* An incoming / server-side HTTP request class.
*/
class HttpRequest extends Request implements MediaTypes
{
// This class can implement groups of constants as necessary.
}
If you are using namespaces, code completion should work.
However, in doing this, you lose the ability to hide the constants within the class family (protected) or class alone (private). By definition, everything in an Interface is public.
PHP Manual: Interfaces
Update:
PHP 8.1 now has enumerations.
The most common solution that I have seen to enum's in PHP has been to create a generic enum class and then extend it. You might take a look at this.
UPDATE: Alternatively, I found this from phpclasses.org.
Here is a github library for handling type-safe enumerations in php:
This library handle classes generation, classes caching and it implements the Type Safe Enumeration design pattern, with several helper methods for dealing with enums, like retrieving an ordinal for enums sorting, or retrieving a binary value, for enums combinations.
The generated code use a plain old php template file, which is also configurable, so you can provide your own template.
It is full test covered with phpunit.
php-enums on github (feel free to fork)
Usage: (#see usage.php, or unit tests for more details)
<?php
//require the library
require_once __DIR__ . '/src/Enum.func.php';
//if you don't have a cache directory, create one
#mkdir(__DIR__ . '/cache');
EnumGenerator::setDefaultCachedClassesDir(__DIR__ . '/cache');
//Class definition is evaluated on the fly:
Enum('FruitsEnum', array('apple' , 'orange' , 'rasberry' , 'bannana'));
//Class definition is cached in the cache directory for later usage:
Enum('CachedFruitsEnum', array('apple' , 'orange' , 'rasberry' , 'bannana'), '\my\company\name\space', true);
echo 'FruitsEnum::APPLE() == FruitsEnum::APPLE(): ';
var_dump(FruitsEnum::APPLE() == FruitsEnum::APPLE()) . "\n";
echo 'FruitsEnum::APPLE() == FruitsEnum::ORANGE(): ';
var_dump(FruitsEnum::APPLE() == FruitsEnum::ORANGE()) . "\n";
echo 'FruitsEnum::APPLE() instanceof Enum: ';
var_dump(FruitsEnum::APPLE() instanceof Enum) . "\n";
echo 'FruitsEnum::APPLE() instanceof FruitsEnum: ';
var_dump(FruitsEnum::APPLE() instanceof FruitsEnum) . "\n";
echo "->getName()\n";
foreach (FruitsEnum::iterator() as $enum)
{
echo " " . $enum->getName() . "\n";
}
echo "->getValue()\n";
foreach (FruitsEnum::iterator() as $enum)
{
echo " " . $enum->getValue() . "\n";
}
echo "->getOrdinal()\n";
foreach (CachedFruitsEnum::iterator() as $enum)
{
echo " " . $enum->getOrdinal() . "\n";
}
echo "->getBinary()\n";
foreach (CachedFruitsEnum::iterator() as $enum)
{
echo " " . $enum->getBinary() . "\n";
}
Output:
FruitsEnum::APPLE() == FruitsEnum::APPLE(): bool(true)
FruitsEnum::APPLE() == FruitsEnum::ORANGE(): bool(false)
FruitsEnum::APPLE() instanceof Enum: bool(true)
FruitsEnum::APPLE() instanceof FruitsEnum: bool(true)
->getName()
APPLE
ORANGE
RASBERRY
BANNANA
->getValue()
apple
orange
rasberry
bannana
->getValue() when values have been specified
pig
dog
cat
bird
->getOrdinal()
1
2
3
4
->getBinary()
1
2
4
8
My Enum class definition below is Strongly typed, and very natural to use and define.
Definition:
class Fruit extends Enum {
static public $APPLE = 1;
static public $ORANGE = 2;
}
Fruit::initialize(); //Can also be called in autoloader
Switch over Enum
$myFruit = Fruit::$APPLE;
switch ($myFruit) {
case Fruit::$APPLE : echo "I like apples\n"; break;
case Fruit::$ORANGE : echo "I hate oranges\n"; break;
}
>> I like apples
Pass Enum as parameter (Strongly typed)
/** Function only accepts Fruit enums as input**/
function echoFruit(Fruit $fruit) {
echo $fruit->getName().": ".$fruit->getValue()."\n";
}
/** Call function with each Enum value that Fruit has */
foreach (Fruit::getList() as $fruit) {
echoFruit($fruit);
}
//Call function with Apple enum
echoFruit(Fruit::$APPLE)
//Will produce an error. This solution is strongly typed
echoFruit(2);
>> APPLE: 1
>> ORANGE: 2
>> APPLE: 1
>> Argument 1 passed to echoFruit() must be an instance of Fruit, integer given
Echo Enum as string
echo "I have an $myFruit\n";
>> I have an APPLE
Get Enum by integer
$myFruit = Fruit::getByValue(2);
echo "Now I have an $myFruit\n";
>> Now I have an ORANGE
Get Enum by Name
$myFruit = Fruit::getByName("APPLE");
echo "But I definitely prefer an $myFruit\n\n";
>> But I definitely prefer an APPLE
The Enum Class:
/**
* #author Torge Kummerow
*/
class Enum {
/**
* Holds the values for each type of Enum
*/
static private $list = array();
/**
* Initializes the enum values by replacing the number with an instance of itself
* using reflection
*/
static public function initialize() {
$className = get_called_class();
$class = new ReflectionClass($className);
$staticProperties = $class->getStaticProperties();
self::$list[$className] = array();
foreach ($staticProperties as $propertyName => &$value) {
if ($propertyName == 'list')
continue;
$enum = new $className($propertyName, $value);
$class->setStaticPropertyValue($propertyName, $enum);
self::$list[$className][$propertyName] = $enum;
} unset($value);
}
/**
* Gets the enum for the given value
*
* #param integer $value
* #throws Exception
*
* #return Enum
*/
static public function getByValue($value) {
$className = get_called_class();
foreach (self::$list[$className] as $propertyName=>&$enum) {
/* #var $enum Enum */
if ($enum->value == $value)
return $enum;
} unset($enum);
throw new Exception("No such enum with value=$value of type ".get_called_class());
}
/**
* Gets the enum for the given name
*
* #param string $name
* #throws Exception
*
* #return Enum
*/
static public function getByName($name) {
$className = get_called_class();
if (array_key_exists($name, static::$list[$className]))
return self::$list[$className][$name];
throw new Exception("No such enum ".get_called_class()."::\$$name");
}
/**
* Returns the list of all enum variants
* #return Array of Enum
*/
static public function getList() {
$className = get_called_class();
return self::$list[$className];
}
private $name;
private $value;
public function __construct($name, $value) {
$this->name = $name;
$this->value = $value;
}
public function __toString() {
return $this->name;
}
public function getValue() {
return $this->value;
}
public function getName() {
return $this->name;
}
}
Addition
You can ofcourse also add comments for IDEs
class Fruit extends Enum {
/**
* This comment is for autocomplete support in common IDEs
* #var Fruit A yummy apple
*/
static public $APPLE = 1;
/**
* This comment is for autocomplete support in common IDEs
* #var Fruit A sour orange
*/
static public $ORANGE = 2;
}
//This can also go to the autoloader if available.
Fruit::initialize();
I have taken to using the approach below as it gives me the ability to have type safety for function parameters, auto complete in NetBeans and good performance. The one thing I don't like too much is that you have to call [extended class name]::enumerate(); after defining the class.
abstract class Enum {
private $_value;
protected function __construct($value) {
$this->_value = $value;
}
public function __toString() {
return (string) $this->_value;
}
public static function enumerate() {
$class = get_called_class();
$ref = new ReflectionClass($class);
$statics = $ref->getStaticProperties();
foreach ($statics as $name => $value) {
$ref->setStaticPropertyValue($name, new $class($value));
}
}
}
class DaysOfWeek extends Enum {
public static $MONDAY = 0;
public static $SUNDAY = 1;
// etc.
}
DaysOfWeek::enumerate();
function isMonday(DaysOfWeek $d) {
if ($d == DaysOfWeek::$MONDAY) {
return true;
} else {
return false;
}
}
$day = DaysOfWeek::$MONDAY;
echo (isMonday($day) ? "bummer it's monday" : "Yay! it's not monday");
I realize this is a very-very-very old thread but I had a thought about this and wanted to know what people thought.
Notes: I was playing around with this and realized that if I just modified the __call() function that you can get even closer to actual enums. The __call() function handles all unknown function calls. So let's say you want to make three enums RED_LIGHT, YELLOW_LIGHT, and GREEN_LIGHT. You can do so now by just doing the following:
$c->RED_LIGHT();
$c->YELLOW_LIGHT();
$c->GREEN_LIGHT();
Once defined all you have to do is to call them again to get the values:
echo $c->RED_LIGHT();
echo $c->YELLOW_LIGHT();
echo $c->GREEN_LIGHT();
and you should get 0, 1, and 2. Have fun! This is also now up on GitHub.
Update: I've made it so both the __get() and __set() functions are now used. These allow you to not have to call a function unless you want to. Instead, now you can just say:
$c->RED_LIGHT;
$c->YELLOW_LIGHT;
$c->GREEN_LIGHT;
For both the creation and getting of the values. Because the variables haven't been defined initially, the __get() function is called (because there isn't a value specified) which sees that the entry in the array hasn't been made. So it makes the entry, assigns it the last value given plus one(+1), increments the last value variable, and returns TRUE. If you set the value:
$c->RED_LIGHT = 85;
Then the __set() function is called and the last value is then set to the new value plus one (+1). So now we have a fairly good way to do enums and they can be created on the fly.
<?php
################################################################################
# Class ENUMS
#
# Original code by Mark Manning.
# Copyrighted (c) 2015 by Mark Manning.
# All rights reserved.
#
# This set of code is hereby placed into the free software universe
# via the GNU greater license thus placing it under the Copyleft
# rules and regulations with the following modifications:
#
# 1. You may use this work in any other work. Commercial or otherwise.
# 2. You may make as much money as you can with it.
# 3. You owe me nothing except to give me a small blurb somewhere in
# your program or maybe have pity on me and donate a dollar to
# sim_sales#paypal.com. :-)
#
# Blurb:
#
# PHP Class Enums by Mark Manning (markem-AT-sim1-DOT-us).
# Used with permission.
#
# Notes:
#
# VIM formatting. Set tabs to four(4) spaces.
#
################################################################################
class enums
{
private $enums;
private $clear_flag;
private $last_value;
################################################################################
# __construct(). Construction function. Optionally pass in your enums.
################################################################################
function __construct()
{
$this->enums = array();
$this->clear_flag = false;
$this->last_value = 0;
if( func_num_args() > 0 ){
return $this->put( func_get_args() );
}
return true;
}
################################################################################
# put(). Insert one or more enums.
################################################################################
function put()
{
$args = func_get_args();
#
# Did they send us an array of enums?
# Ex: $c->put( array( "a"=>0, "b"=>1,...) );
# OR $c->put( array( "a", "b", "c",... ) );
#
if( is_array($args[0]) ){
#
# Add them all in
#
foreach( $args[0] as $k=>$v ){
#
# Don't let them change it once it is set.
# Remove the IF statement if you want to be able to modify the enums.
#
if( !isset($this->enums[$k]) ){
#
# If they sent an array of enums like this: "a","b","c",... then we have to
# change that to be "A"=>#. Where "#" is the current count of the enums.
#
if( is_numeric($k) ){
$this->enums[$v] = $this->last_value++;
}
#
# Else - they sent "a"=>"A", "b"=>"B", "c"=>"C"...
#
else {
$this->last_value = $v + 1;
$this->enums[$k] = $v;
}
}
}
}
#
# Nope! Did they just sent us one enum?
#
else {
#
# Is this just a default declaration?
# Ex: $c->put( "a" );
#
if( count($args) < 2 ){
#
# Again - remove the IF statement if you want to be able to change the enums.
#
if( !isset($this->enums[$args[0]]) ){
$this->enums[$args[0]] = $this->last_value++;
}
#
# No - they sent us a regular enum
# Ex: $c->put( "a", "This is the first enum" );
#
else {
#
# Again - remove the IF statement if you want to be able to change the enums.
#
if( !isset($this->enums[$args[0]]) ){
$this->last_value = $args[1] + 1;
$this->enums[$args[0]] = $args[1];
}
}
}
}
return true;
}
################################################################################
# get(). Get one or more enums.
################################################################################
function get()
{
$num = func_num_args();
$args = func_get_args();
#
# Is this an array of enums request? (ie: $c->get(array("a","b","c"...)) )
#
if( is_array($args[0]) ){
$ary = array();
foreach( $args[0] as $k=>$v ){
$ary[$v] = $this->enums[$v];
}
return $ary;
}
#
# Is it just ONE enum they want? (ie: $c->get("a") )
#
else if( ($num > 0) && ($num < 2) ){
return $this->enums[$args[0]];
}
#
# Is it a list of enums they want? (ie: $c->get( "a", "b", "c"...) )
#
else if( $num > 1 ){
$ary = array();
foreach( $args as $k=>$v ){
$ary[$v] = $this->enums[$v];
}
return $ary;
}
#
# They either sent something funky or nothing at all.
#
return false;
}
################################################################################
# clear(). Clear out the enum array.
# Optional. Set the flag in the __construct function.
# After all, ENUMS are supposed to be constant.
################################################################################
function clear()
{
if( $clear_flag ){
unset( $this->enums );
$this->enums = array();
}
return true;
}
################################################################################
# __call(). In case someone tries to blow up the class.
################################################################################
function __call( $name, $arguments )
{
if( isset($this->enums[$name]) ){ return $this->enums[$name]; }
else if( !isset($this->enums[$name]) && (count($arguments) > 0) ){
$this->last_value = $arguments[0] + 1;
$this->enums[$name] = $arguments[0];
return true;
}
else if( !isset($this->enums[$name]) && (count($arguments) < 1) ){
$this->enums[$name] = $this->last_value++;
return true;
}
return false;
}
################################################################################
# __get(). Gets the value.
################################################################################
function __get($name)
{
if( isset($this->enums[$name]) ){ return $this->enums[$name]; }
else if( !isset($this->enums[$name]) ){
$this->enums[$name] = $this->last_value++;
return true;
}
return false;
}
################################################################################
# __set(). Sets the value.
################################################################################
function __set( $name, $value=null )
{
if( isset($this->enums[$name]) ){ return false; }
else if( !isset($this->enums[$name]) && !is_null($value) ){
$this->last_value = $value + 1;
$this->enums[$name] = $value;
return true;
}
else if( !isset($this->enums[$name]) && is_null($value) ){
$this->enums[$name] = $this->last_value++;
return true;
}
return false;
}
################################################################################
# __destruct(). Deconstruct the class. Remove the list of enums.
################################################################################
function __destruct()
{
unset( $this->enums );
$this->enums = null;
return true;
}
}
#
# Test code
#
# $c = new enums();
# $c->RED_LIGHT(85);
# $c->YELLOW_LIGHT = 23;
# $c->GREEN_LIGHT;
#
# echo $c->RED_LIGHT . "\n";
# echo $c->YELLOW_LIGHT . "\n";
# echo $c->GREEN_LIGHT . "\n";
?>
I know this is an old thread, however none of the workarounds I've seen really looked like enums, since almost all workarounds requires you to manually assign values to the enum items, or it requires you to pass an array of enum keys to a function. So I created my own solution for this.
To create an enum class using my solution one can simply extend this Enum class below, create a bunch of static variables (no need to initialize them), and make a call to yourEnumClass::init() just below the definition of your enum class.
edit: This only works in php >= 5.3, but it can probably be modified to work in older versions as well
/**
* A base class for enums.
*
* This class can be used as a base class for enums.
* It can be used to create regular enums (incremental indices), but it can also be used to create binary flag values.
* To create an enum class you can simply extend this class, and make a call to <yourEnumClass>::init() before you use the enum.
* Preferably this call is made directly after the class declaration.
* Example usages:
* DaysOfTheWeek.class.php
* abstract class DaysOfTheWeek extends Enum{
* static $MONDAY = 1;
* static $TUESDAY;
* static $WEDNESDAY;
* static $THURSDAY;
* static $FRIDAY;
* static $SATURDAY;
* static $SUNDAY;
* }
* DaysOfTheWeek::init();
*
* example.php
* require_once("DaysOfTheWeek.class.php");
* $today = date('N');
* if ($today == DaysOfTheWeek::$SUNDAY || $today == DaysOfTheWeek::$SATURDAY)
* echo "It's weekend!";
*
* Flags.class.php
* abstract class Flags extends Enum{
* static $FLAG_1;
* static $FLAG_2;
* static $FLAG_3;
* }
* Flags::init(Enum::$BINARY_FLAG);
*
* example2.php
* require_once("Flags.class.php");
* $flags = Flags::$FLAG_1 | Flags::$FLAG_2;
* if ($flags & Flags::$FLAG_1)
* echo "Flag_1 is set";
*
* #author Tiddo Langerak
*/
abstract class Enum{
static $BINARY_FLAG = 1;
/**
* This function must be called to initialize the enumeration!
*
* #param bool $flags If the USE_BINARY flag is provided, the enum values will be binary flag values. Default: no flags set.
*/
public static function init($flags = 0){
//First, we want to get a list of all static properties of the enum class. We'll use the ReflectionClass for this.
$enum = get_called_class();
$ref = new ReflectionClass($enum);
$items = $ref->getStaticProperties();
//Now we can start assigning values to the items.
if ($flags & self::$BINARY_FLAG){
//If we want binary flag values, our first value should be 1.
$value = 1;
//Now we can set the values for all items.
foreach ($items as $key=>$item){
if (!isset($item)){
//If no value is set manually, we should set it.
$enum::$$key = $value;
//And we need to calculate the new value
$value *= 2;
} else {
//If there was already a value set, we will continue starting from that value, but only if that was a valid binary flag value.
//Otherwise, we will just skip this item.
if ($key != 0 && ($key & ($key - 1) == 0))
$value = 2 * $item;
}
}
} else {
//If we want to use regular indices, we'll start with index 0.
$value = 0;
//Now we can set the values for all items.
foreach ($items as $key=>$item){
if (!isset($item)){
//If no value is set manually, we should set it, and increment the value for the next item.
$enum::$$key = $value;
$value++;
} else {
//If a value was already set, we'll continue from that value.
$value = $item+1;
}
}
}
}
}
class DayOfWeek {
static $values = array(
self::MONDAY,
self::TUESDAY,
// ...
);
const MONDAY = 0;
const TUESDAY = 1;
// ...
}
$today = DayOfWeek::MONDAY;
// If you want to check if a value is valid
assert( in_array( $today, DayOfWeek::$values ) );
Don't use reflection. It makes it extremely difficult to reason about your code and track down where something is being used, and tends to break static analysis tools (eg what's built into your IDE).
Now you can use The SplEnum class to build it natively. As per the official documentation.
SplEnum gives the ability to emulate and create enumeration objects
natively in PHP.
<?php
class Month extends SplEnum {
const __default = self::January;
const January = 1;
const February = 2;
const March = 3;
const April = 4;
const May = 5;
const June = 6;
const July = 7;
const August = 8;
const September = 9;
const October = 10;
const November = 11;
const December = 12;
}
echo new Month(Month::June) . PHP_EOL;
try {
new Month(13);
} catch (UnexpectedValueException $uve) {
echo $uve->getMessage() . PHP_EOL;
}
?>
Please note it's an extension which has to be installed, but it is not available by default. Which comes under Special Types described on the PHP website itself. The above example is taken from the PHP site.
The accepted answer is the way to go and is actually what I am doing for simplicity. Most advantages of enumeration are offered (readable, fast, etc.). One concept is missing, however: type safety. In most languages, enumerations are also used to restrict allowed values. Below is an example of how type safety can also be obtained by using private constructors, static instantiation methods and type checking:
class DaysOfWeek{
const Sunday = 0;
const Monday = 1;
// etc.
private $intVal;
private function __construct($intVal){
$this->intVal = $intVal;
}
//static instantiation methods
public static function MONDAY(){
return new self(self::Monday);
}
//etc.
}
//function using type checking
function printDayOfWeek(DaysOfWeek $d){ //compiler can now use type checking
// to something with $d...
}
//calling the function is safe!
printDayOfWeek(DaysOfWeek::MONDAY());
We could even go further: using constants in the DaysOfWeek class might lead to misusage: e.g. one might mistakenly use it this way:
printDayOfWeek(DaysOfWeek::Monday); //triggers a compiler error.
which is wrong (calls integer constant). We can prevent this using private static variables instead of constants:
class DaysOfWeeks{
private static $monday = 1;
//etc.
private $intVal;
//private constructor
private function __construct($intVal){
$this->intVal = $intVal;
}
//public instantiation methods
public static function MONDAY(){
return new self(self::$monday);
}
//etc.
//convert an instance to its integer value
public function intVal(){
return $this->intVal;
}
}
Of course, it is not possible to access integer constants (this was actually the purpose). The intVal method allows to convert a DaysOfWeek object to its integer representation.
Note that we could even go further by implementing a caching mechanism in instantiation methods to save memory in the case enumerations are extensively used...
Hope this will help
This is my take on "dynamic" enum... so that i can call it with variables, ex. from a form.
look at updated verison below this codeblock...
$value = "concert";
$Enumvalue = EnumCategory::enum($value);
//$EnumValue = 1
class EnumCategory{
const concert = 1;
const festival = 2;
const sport = 3;
const nightlife = 4;
const theatre = 5;
const musical = 6;
const cinema = 7;
const charity = 8;
const museum = 9;
const other = 10;
public function enum($string){
return constant('EnumCategory::'.$string);
}
}
UPDATE: Better way of doing it...
class EnumCategory {
static $concert = 1;
static $festival = 2;
static $sport = 3;
static $nightlife = 4;
static $theatre = 5;
static $musical = 6;
static $cinema = 7;
static $charity = 8;
static $museum = 9;
static $other = 10;
}
Call with
EnumCategory::${$category};
Pointed out solution works well. Clean and smooth.
However, if you want strongly typed enumerations, you can use this:
class TestEnum extends Enum
{
public static $TEST1;
public static $TEST2;
}
TestEnum::init(); // Automatically initializes enum values
With an Enum class looking like:
class Enum
{
public static function parse($enum)
{
$class = get_called_class();
$vars = get_class_vars($class);
if (array_key_exists($enum, $vars)) {
return $vars[$enum];
}
return null;
}
public static function init()
{
$className = get_called_class();
$consts = get_class_vars($className);
foreach ($consts as $constant => $value) {
if (is_null($className::$$constant)) {
$constantValue = $constant;
$constantValueName = $className . '::' . $constant . '_VALUE';
if (defined($constantValueName)) {
$constantValue = constant($constantValueName);
}
$className::$$constant = new $className($constantValue);
}
}
}
public function __construct($value)
{
$this->value = $value;
}
}
This way, enum values are strongly typed and
TestEnum::$TEST1 === TestEnum::parse('TEST1') // true statement
One of the aspects missing from some of the other answers here is a way to use enums with type hinting.
If you define your enum as a set of constants in an abstract class, e.g.
abstract class ShirtSize {
public const SMALL = 1;
public const MEDIUM = 2;
public const LARGE = 3;
}
then you can't type hint it in a function parameter - for one, because it's not instantiable, but also because the type of ShirtSize::SMALL is int, not ShirtSize.
That's why native enums in PHP would be so much better than anything we can come up with. However, we can approximate an enum by keeping a private property which represents the value of the enum, and then restricting the initialization of this property to our predefined constants. To prevent the enum from being instantiated arbitrarily (without the overhead of type-checking a whitelist), we make the constructor private.
class ShirtSize {
private $size;
private function __construct ($size) {
$this->size = $size;
}
public function equals (ShirtSize $s) {
return $this->size === $s->size;
}
public static function SMALL () { return new self(1); }
public static function MEDIUM () { return new self(2); }
public static function LARGE () { return new self(3); }
}
Then we can use ShirtSize like this:
function sizeIsAvailable ($productId, ShirtSize $size) {
// business magic
}
if(sizeIsAvailable($_GET["id"], ShirtSize::LARGE())) {
echo "Available";
} else {
echo "Out of stock.";
}
$s2 = ShirtSize::SMALL();
$s3 = ShirtSize::MEDIUM();
echo $s2->equals($s3) ? "SMALL == MEDIUM" : "SMALL != MEDIUM";
This way, the biggest difference from the user's perspective is that you have to tack on a () on the constant's name.
One downside though is that === (which compares object equality) will return false when == returns true. For that reason, it's best to provide an equals method, so that users don't have to remember to use == and not === to compare two enum values.
EDIT: A couple of the existing answers are very similar, particularly: https://stackoverflow.com/a/25526473/2407870.
Stepping on the answer of #Brian Cline I thought I might give my 5 cents
<?php
/**
* A class that simulates Enums behaviour
* <code>
* class Season extends Enum{
* const Spring = 0;
* const Summer = 1;
* const Autumn = 2;
* const Winter = 3;
* }
*
* $currentSeason = new Season(Season::Spring);
* $nextYearSeason = new Season(Season::Spring);
* $winter = new Season(Season::Winter);
* $whatever = new Season(-1); // Throws InvalidArgumentException
* echo $currentSeason.is(Season::Spring); // True
* echo $currentSeason.getName(); // 'Spring'
* echo $currentSeason.is($nextYearSeason); // True
* echo $currentSeason.is(Season::Winter); // False
* echo $currentSeason.is(Season::Spring); // True
* echo $currentSeason.is($winter); // False
* </code>
*
* Class Enum
*
* PHP Version 5.5
*/
abstract class Enum
{
/**
* Will contain all the constants of every enum that gets created to
* avoid expensive ReflectionClass usage
* #var array
*/
private static $_constCacheArray = [];
/**
* The value that separates this instance from the rest of the same class
* #var mixed
*/
private $_value;
/**
* The label of the Enum instance. Will take the string name of the
* constant provided, used for logging and human readable messages
* #var string
*/
private $_name;
/**
* Creates an enum instance, while makes sure that the value given to the
* enum is a valid one
*
* #param mixed $value The value of the current
*
* #throws \InvalidArgumentException
*/
public final function __construct($value)
{
$constants = self::_getConstants();
if (count($constants) !== count(array_unique($constants))) {
throw new \InvalidArgumentException('Enums cannot contain duplicate constant values');
}
if ($name = array_search($value, $constants)) {
$this->_value = $value;
$this->_name = $name;
} else {
throw new \InvalidArgumentException('Invalid enum value provided');
}
}
/**
* Returns the constant name of the current enum instance
*
* #return string
*/
public function getName()
{
return $this->_name;
}
/**
* Returns the value of the current enum instance
*
* #return mixed
*/
public function getValue()
{
return $this->_value;
}
/**
* Checks whether this enum instance matches with the provided one.
* This function should be used to compare Enums at all times instead
* of an identity comparison
* <code>
* // Assuming EnumObject and EnumObject2 both extend the Enum class
* // and constants with such values are defined
* $var = new EnumObject('test');
* $var2 = new EnumObject('test');
* $var3 = new EnumObject2('test');
* $var4 = new EnumObject2('test2');
* echo $var->is($var2); // true
* echo $var->is('test'); // true
* echo $var->is($var3); // false
* echo $var3->is($var4); // false
* </code>
*
* #param mixed|Enum $enum The value we are comparing this enum object against
* If the value is instance of the Enum class makes
* sure they are instances of the same class as well,
* otherwise just ensures they have the same value
*
* #return bool
*/
public final function is($enum)
{
// If we are comparing enums, just make
// sure they have the same toString value
if (is_subclass_of($enum, __CLASS__)) {
return get_class($this) === get_class($enum)
&& $this->getValue() === $enum->getValue();
} else {
// Otherwise assume $enum is the value we are comparing against
// and do an exact comparison
return $this->getValue() === $enum;
}
}
/**
* Returns the constants that are set for the current Enum instance
*
* #return array
*/
private static function _getConstants()
{
if (self::$_constCacheArray == null) {
self::$_constCacheArray = [];
}
$calledClass = get_called_class();
if (!array_key_exists($calledClass, self::$_constCacheArray)) {
$reflect = new \ReflectionClass($calledClass);
self::$_constCacheArray[$calledClass] = $reflect->getConstants();
}
return self::$_constCacheArray[$calledClass];
}
}
Some good solutions on here!
Here's my version.
It's strongly typed
It works with IDE auto-completion
Enums are defined by a code and a description, where the code can be an integer, a binary value, a short string, or basically anything else you want. The pattern could easily be extended to support orther properties.
It asupports value (==) and reference (===) comparisons and works in switch statements.
I think the main disadvantage is that enum members do have to be separately declared and instantiated, due to the descriptions and PHP's inability to construct objects at static member declaration time. I guess a way round this might be to use reflection with parsed doc comments instead.
The abstract enum looks like this:
<?php
abstract class AbstractEnum
{
/** #var array cache of all enum instances by class name and integer value */
private static $allEnumMembers = array();
/** #var mixed */
private $code;
/** #var string */
private $description;
/**
* Return an enum instance of the concrete type on which this static method is called, assuming an instance
* exists for the passed in value. Otherwise an exception is thrown.
*
* #param $code
* #return AbstractEnum
* #throws Exception
*/
public static function getByCode($code)
{
$concreteMembers = &self::getConcreteMembers();
if (array_key_exists($code, $concreteMembers)) {
return $concreteMembers[$code];
}
throw new Exception("Value '$code' does not exist for enum '".get_called_class()."'");
}
public static function getAllMembers()
{
return self::getConcreteMembers();
}
/**
* Create, cache and return an instance of the concrete enum type for the supplied primitive value.
*
* #param mixed $code code to uniquely identify this enum
* #param string $description
* #throws Exception
* #return AbstractEnum
*/
protected static function enum($code, $description)
{
$concreteMembers = &self::getConcreteMembers();
if (array_key_exists($code, $concreteMembers)) {
throw new Exception("Value '$code' has already been added to enum '".get_called_class()."'");
}
$concreteMembers[$code] = $concreteEnumInstance = new static($code, $description);
return $concreteEnumInstance;
}
/**
* #return AbstractEnum[]
*/
private static function &getConcreteMembers() {
$thisClassName = get_called_class();
if (!array_key_exists($thisClassName, self::$allEnumMembers)) {
$concreteMembers = array();
self::$allEnumMembers[$thisClassName] = $concreteMembers;
}
return self::$allEnumMembers[$thisClassName];
}
private function __construct($code, $description)
{
$this->code = $code;
$this->description = $description;
}
public function getCode()
{
return $this->code;
}
public function getDescription()
{
return $this->description;
}
}
Here's an example concrete enum:
<?php
require('AbstractEnum.php');
class EMyEnum extends AbstractEnum
{
/** #var EMyEnum */
public static $MY_FIRST_VALUE;
/** #var EMyEnum */
public static $MY_SECOND_VALUE;
/** #var EMyEnum */
public static $MY_THIRD_VALUE;
public static function _init()
{
self::$MY_FIRST_VALUE = self::enum(1, 'My first value');
self::$MY_SECOND_VALUE = self::enum(2, 'My second value');
self::$MY_THIRD_VALUE = self::enum(3, 'My third value');
}
}
EMyEnum::_init();
Which can be used like this:
<?php
require('EMyEnum.php');
echo EMyEnum::$MY_FIRST_VALUE->getCode().' : '.EMyEnum::$MY_FIRST_VALUE->getDescription().PHP_EOL.PHP_EOL;
var_dump(EMyEnum::getAllMembers());
echo PHP_EOL.EMyEnum::getByCode(2)->getDescription().PHP_EOL;
And produces this output:
1 : My first value
array(3) {
[1]=>
object(EMyEnum)#1 (2) {
["code":"AbstractEnum":private]=>
int(1)
["description":"AbstractEnum":private]=>
string(14) "My first value"
}
[2]=>
object(EMyEnum)#2 (2) {
["code":"AbstractEnum":private]=>
int(2)
["description":"AbstractEnum":private]=>
string(15) "My second value"
}
[3]=>
object(EMyEnum)#3 (2) {
["code":"AbstractEnum":private]=>
int(3)
["description":"AbstractEnum":private]=>
string(14) "My third value"
}
}
My second value

Categories