ServiceFactory::buildArg()   C
last analyzed

Complexity

Conditions 11
Paths 7

Size

Total Lines 29
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 6 Features 0
Metric Value
c 7
b 6
f 0
dl 0
loc 29
rs 5.2653
cc 11
eloc 22
nc 7
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PEIP\Factory;
4
5
/*
6
 * To change this template, choose Tools | Templates
7
 * and open the template in the editor.
8
 */
9
10
/*
11
 * Description of ServiceFactory
12
 *
13
 * @author timo
14
 */
15
use PEIP\Base\GenericBuilder;
16
use PEIP\Context\XMLContext;
17
use PEIP\Util\Test;
18
19
class ServiceFactory
20
{
21
    /**
22
     * Creates and initializes service instance from a given configuration.
23
     *
24
     * @param $config configuration of the service
25
     *
26
     * @return object the initialized service instance
27
     */
28
    public static function createService(array $config)
29
    {
30
        $args = [];
31
        //build arguments for constructor
32
        if (isset($config['constructor_arg'])) {
33
            foreach ($config['constructor_arg'] as $arg) {
34
                $args[] = self::buildArg($arg);
35
            }
36
        }
37
38
        return self::buildAndModify($config, $args);
39
    }
40
41
    /**
42
     * Builds an arbitrary service/object instance from a config-obect.
43
     *
44
     * @static
45
     *
46
     * @param object $config       configuration object to build a service instance from.
47
     * @param array  $arguments    arguments for the service constructor
48
     * @param string $defaultClass class to create instance for if none is set in config
49
     *
50
     * @return object build and modified srvice instance
51
     */
52
    public static function doBuild($config, $arguments, $defaultClass = false)
53
    {
54
        $cls = isset($config['class']) ? trim((string) $config['class']) : (string) $defaultClass;
55
        if ($cls != '') {
56
            try {
57
                $constructor = isset($config['constructor']) ? (string) $config['constructor'] : '';
58
                if ($constructor != '' && Test::assertMethod($cls, $constructor)) {
59
                    $service = call_user_func_array([$cls, $constructor], $arguments);
60
                } else {
61
                    $service = self::build($cls, $arguments);
62
                }
63
            } catch (\Exception $e) {
64
                throw new \RuntimeException('Could not create Service "'.$cls.'" -> '.$e->getMessage());
65
            }
66
        }
67
        if (is_object($service)) {
68
            return $service;
0 ignored issues
show
Bug introduced by
The variable $service does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
69
        }
70
        throw new \RuntimeException('Could not create Service "'.$cls.'". Class does not exist.');
71
    }
72
73
    /**
74
     * Utility function to build an object instance for given class with given constructor-arguments.
75
     *
76
     * @see GenericBuilder
77
     * @static
78
     *
79
     * @param string $className name of class to build instance for.
80
     * @param array  $arguments arguments for the constructor
81
     *
82
     * @return object build and modified srvice instance
83
     */
84
    public static function build($className, $arguments)
85
    {
86
        return GenericBuilder::getInstance($className)->build($arguments);
87
    }
88
89
    /**
90
     * Builds single argument (to call a method with later) from a config-obect.
91
     *
92
     * @param object $config configuration object to create argument from.
93
     *
94
     * @return mixed build argument
95
     */
96
    protected static function buildArg($config)
97
    {
98
        if (trim((string) $config['value']) != '') {
99
            $arg = (string) $config['value'];
100
        } elseif ($config->getName() == 'value') {
101
            $arg = (string) $config;
102
        } elseif ($config->getName() == 'list') {
103
            $arg = [];
104
            foreach ($config->children() as $entry) {
105
                if ($entry->getName() == 'value') {
106
                    if ($entry['key']) {
107
                        $arg[(string) $entry['key']] = (string) $entry;
108
                    } else {
109
                        $arg[] = (string) $entry;
110
                    }
111
                } elseif ($entry->getName() == 'service') {
112
                    $arg[] = $this->provideService($entry);
0 ignored issues
show
Bug introduced by
The variable $this does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The method provideService() does not seem to exist on object<PEIP\Factory\ServiceFactory>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
113
                }
114
            }
115
        } elseif ($config->getName() == 'service') {
116
            $arg = self::provideService($config);
0 ignored issues
show
Bug introduced by
The method provideService() does not seem to exist on object<PEIP\Factory\ServiceFactory>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
117
        } elseif ($config->list) {
118
            $arg = self::buildArg($config->list);
119
        } elseif ($config->service) {
120
            $arg = self::buildArg($config->service);
121
        }
122
123
        return $arg;
0 ignored issues
show
Bug introduced by
The variable $arg does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
124
    }
125
126
    /**
127
     * Builds and modifies an arbitrary service/object instance from a config-obect.
128
     *
129
     * @see XMLContext::doBuild
130
     * @see PEIP\Factory\ServiceFactory::modifyService
131
     * @implements \PEIP\INF\Context\Context
132
     *
133
     * @param object $config       configuration array to build a service instance from.
134
     * @param array  $arguments    arguments for the service constructor
135
     * @param string $defaultClass class to create instance for if none is set in config
136
     *
137
     * @return object build and modified srvice instance
138
     */
139
    public static function buildAndModify(array $config, $arguments, $defaultClass = '')
140
    {
141
        if ((isset($config['class']) && '' != (string) $config['class']) || $defaultClass !== '') {
142
            $service = self::doBuild($config, $arguments, $defaultClass);
0 ignored issues
show
Documentation introduced by
$config is of type array, but the function expects a object.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
143
        } else {
144
            throw new \RuntimeException('Could not create Service. no class or reference given.');
145
        }
146
        if (isset($config['ref_property'])) {
147
            $service = $service->{(string) $config['ref_property']};
148
        } elseif (isset($config['ref_method'])) {
149
            $args = [];
150
            if (isset($config['argument'])) {
151
                foreach ($config['argument'] as $arg) {
152
                    $args[] = self::buildArg($arg);
153
                }
154
            }
155
            $service = call_user_func_array([$service, (string) $config['ref_method']], $args);
156
        }
157
        if (!is_object($service)) {
158
            throw new \RuntimeException('Could not create Service.');
159
        }
160
        $service = self::modifyService($service, $config);
0 ignored issues
show
Documentation introduced by
$config is of type array<string,?,{"ref_property":"?"}>, but the function expects a object.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
161
162
        return $service;
163
    }
164
165
    /**
166
     * Modifies a service instance from configuration.
167
     *  - Sets properties on the instance.
168
     *  -- Calls a public setter method if exists.
169
     *  -- Else sets a public property if exists.
170
     *  - Calls methods on the instance.
171
     *  - Registers listeners to events on the instance.
172
     *
173
     * @param object $service the service instance to modify
174
     * @param object $config  configuration to get the modification instructions from.
175
     *
176
     * @return object the modificated service
177
     */
178
    protected function modifyService($service, $config)
179
    {
180
        $config = is_array($config) ? new \ArrayObject($config) : $config;
181
        // set instance properties
182 View Code Duplication
        if (isset($config->property)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
183
            foreach ($config->property as $property) {
184
                $arg = self::buildArg($property);
185
                if ($arg) {
186
                    $setter = self::getSetter($property);
0 ignored issues
show
Bug introduced by
The method getSetter() does not seem to exist on object<PEIP\Factory\ServiceFactory>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
187
                    if ($setter && self::hasPublicProperty($service, 'Method', $setter)) {
0 ignored issues
show
Bug introduced by
The method hasPublicProperty() does not seem to exist on object<PEIP\Factory\ServiceFactory>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
188
                        $service->{$setter}($arg);
189
                    } elseif (in_array($property, self::hasPublicProperty($service, 'Property', $setter))) {
0 ignored issues
show
Bug introduced by
The method hasPublicProperty() does not seem to exist on object<PEIP\Factory\ServiceFactory>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
190
                        $service->$setter = $arg;
191
                    }
192
                }
193
            }
194
        }
195
        // call instance methods
196 View Code Duplication
        if (isset($config->action)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
197
            foreach ($config->action as $action) {
198
                $method = (string) $action['method'] != '' ? (string) $action['method'] : null;
199
                if ($method && self::hasPublicProperty($service, 'Method', $method)) {
0 ignored issues
show
Bug introduced by
The method hasPublicProperty() does not seem to exist on object<PEIP\Factory\ServiceFactory>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug Best Practice introduced by
The expression $method of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
200
                    $args = [];
201
                    foreach ($action->children() as $argument) {
202
                        $args[] = self::buildArg($argument);
203
                    }
204
                    call_user_func_array([$service, (string) $action['method']], $args);
205
                }
206
            }
207
        }
208
        // register instance listeners
209
        if ($service instanceof \PEIP\INF\Event\Connectable) {
210 View Code Duplication
            if (isset($config->listener)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
211
                foreach ($config->listener as $listenerConf) {
212
                    $event = (string) $listenerConf['event'];
213
                    $listener = $this->provideService($listenerConf);
0 ignored issues
show
Bug introduced by
The method provideService() does not seem to exist on object<PEIP\Factory\ServiceFactory>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
214
                    $service->connect($event, $listener);
215
                }
216
            }
217
        }
218
219
        return $service;
220
    }
221
}
222