AttributeManager::getAttribute()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 8
cts 8
cp 1
rs 9.7998
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
namespace PhpAbac\Manager;
4
5
use PhpAbac\Configuration\Configuration;
6
7
use PhpAbac\Model\{
8
    AbstractAttribute,
9
    Attribute,
10
    EnvironmentAttribute
11
};
12
13
class AttributeManager implements AttributeManagerInterface
14
{
15
    /** @var array **/
16
    private $attributes;
17
    /** @var string Prefix to add before getter name (default)'get' */
18
    private $getter_prefix = 'get';
19
    /** @var string Function to apply on the getter name ( before adding prefix ) (default)'ucfirst' */
20
    private $getter_name_transformation_function = 'ucfirst';
21
    
22
    /**
23
     *  A List of option to configure This Abac Instance
24
     *  Options list :
25
     *    'getter_prefix' => Prefix to add before getter name (default)'get'
26
     *    'getter_name_transformation_function' => Function to apply on the getter name ( before adding prefix ) (default)'ucfirst'
27
     */
28 8
    public function __construct(Configuration $configuration, array $options = [])
29
    {
30 8
        $this->attributes = $configuration->getAttributes();
31
    
32 8
        $options = array_intersect_key($options, array_flip([
33 8
            'getter_prefix',
34
            'getter_name_transformation_function',
35
        ]));
36
        
37 8
        foreach ($options as $name => $value) {
38
            $this->$name = $value;
39
        }
40 8
    }
41
42 8
    public function getAttribute(string $attributeId): AbstractAttribute
43
    {
44 8
        $attributeKeys = explode('.', $attributeId);
45
        // The first element will be the attribute ID, then the field ID
46 8
        $attributeId = array_shift($attributeKeys);
47 8
        $attributeName = implode('.', $attributeKeys);
48
        // The field ID is also the attribute object property
49 8
        $attributeData = $this->attributes[$attributeId];
50
        return
51 8
            ($attributeId === 'environment')
52 3
            ? $this->getEnvironmentAttribute($attributeData, $attributeName)
53 8
            : $this->getClassicAttribute($attributeData, $attributeName)
54
        ;
55
    }
56
57 6
    private function getClassicAttribute(array $attributeData, string $property): Attribute
58
    {
59
        return
60 6
            (new Attribute())
61 6
            ->setName($attributeData['fields'][$property]['name'])
62 6
            ->setType($attributeData['type'])
63 6
            ->setProperty($property)
64 6
            ->setSlug($this->slugify($attributeData['fields'][$property]['name']))
65
        ;
66
    }
67
68 3
    private function getEnvironmentAttribute(array $attributeData, string $key): EnvironmentAttribute
69
    {
70
        return
71 3
            (new EnvironmentAttribute())
72 3
            ->setName($attributeData[$key]['name'])
73 3
            ->setType('environment')
74 3
            ->setVariableName($attributeData[$key]['variable_name'])
75 3
            ->setSlug($this->slugify($attributeData[$key]['name']))
76
        ;
77
    }
78
79 6
    public function retrieveAttribute(AbstractAttribute $attribute, $user = null, $object = null, array $getter_params = [])
80
    {
81 6
        switch ($attribute->getType()) {
82 6
            case 'user':
83 5
                return $this->retrieveClassicAttribute($attribute, $user, $getter_params);
0 ignored issues
show
Compatibility introduced by
$attribute of type object<PhpAbac\Model\AbstractAttribute> is not a sub-type of object<PhpAbac\Model\Attribute>. It seems like you assume a child class of the class PhpAbac\Model\AbstractAttribute to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
84 4
            case 'resource':
85 3
                return $this->retrieveClassicAttribute($attribute, $object);
0 ignored issues
show
Compatibility introduced by
$attribute of type object<PhpAbac\Model\AbstractAttribute> is not a sub-type of object<PhpAbac\Model\Attribute>. It seems like you assume a child class of the class PhpAbac\Model\AbstractAttribute to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
86 2
            case 'environment':
87 2
                return $this->retrieveEnvironmentAttribute($attribute);
0 ignored issues
show
Compatibility introduced by
$attribute of type object<PhpAbac\Model\AbstractAttribute> is not a sub-type of object<PhpAbac\Model\EnvironmentAttribute>. It seems like you assume a child class of the class PhpAbac\Model\AbstractAttribute to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
88
        }
89
    }
90
91 5
    private function retrieveClassicAttribute(Attribute $attribute, $object, array $getter_params = [])
92
    {
93 5
        $propertyPath = explode('.', $attribute->getProperty());
94 5
        $propertyValue = $object;
95 5
        foreach ($propertyPath as $property) {
96 5
            $getter = $this->getter_prefix.call_user_func($this->getter_name_transformation_function, $property);
97
            // Use is_callable, instead of method_exists, to deal with __call magic method
98 5
            if (!is_callable([$propertyValue,$getter])) {
99
                throw new \InvalidArgumentException('There is no getter for the "'.$attribute->getProperty().'" attribute for object "'.get_class($propertyValue).'" with getter "'.$getter.'"');
100
            }
101 5
            if (($propertyValue = call_user_func_array([
102 5
                    $propertyValue,
103 5
                    $getter,
104 5
                ], isset($getter_params[ $property ]) ? $getter_params[ $property ] : [])) === null
105
            ) {
106 5
                return null;
107
            }
108
        }
109 5
        return $propertyValue;
110
    }
111
112 2
    private function retrieveEnvironmentAttribute(EnvironmentAttribute $attribute)
113
    {
114 2
        return getenv($attribute->getVariableName());
115
    }
116
117 8
    public function slugify(string $name): string
118
    {
119
        // replace non letter or digits by -
120 8
        $name = trim(preg_replace('~[^\\pL\d]+~u', '-', $name), '-');
121
        // transliterate
122 8
        if (function_exists('iconv')) {
123 8
            $name = iconv('utf-8', 'us-ascii//TRANSLIT', $name);
124
        }
125
        // remove unwanted characters
126 8
        $name = preg_replace('~[^-\w]+~', '', strtolower($name));
127 8
        if (empty($name)) {
128
            return 'n-a';
129
        }
130 8
        return $name;
131
    }
132
}
133