Completed
Push — v1.ns ( c7f51f...3baf09 )
by Timo
07:05
created

ServiceFactory::buildAndModify()   D

Complexity

Conditions 9
Paths 9

Size

Total Lines 25
Code Lines 18

Duplication

Lines 11
Ratio 44 %

Importance

Changes 2
Bugs 2 Features 0
Metric Value
c 2
b 2
f 0
dl 11
loc 25
rs 4.909
cc 9
eloc 18
nc 9
nop 3
1
<?php
2
3
namespace PEIP\Factory;
4
/* 
5
 * To change this template, choose Tools | Templates
6
 * and open the template in the editor.
7
 */
8
9
/**
10
 * Description of ServiceFactory
11
 *
12
 * @author timo
13
 */
14
use PEIP\Util\Test;
15
use PEIP\Base\GenericBuilder;
16
use PEIP\Context\XMLContext;
17
18
class ServiceFactory {
19
     /**
20
     * Creates and initializes service instance from a given configuration.
21
     *
22
     * @access public
23
     * @param $config
24
     * @return object the initialized service instance
25
     */
26
    public static function createService($config){
27
        $args = array();
28
        //build arguments for constructor
29
        if($config->constructor_arg){
30
            foreach($config->constructor_arg as $arg){
31
                $args[] = self::buildArg($arg);
32
            }
33
        }
34
        return self::buildAndModify($config, $args);
35
    }
36
37
        /**
38
     * Builds an arbitrary service/object instance from a config-obect.
39
     *
40
     * @static
41
     * @access protected
42
     * @param object $config configuration object to build a service instance from.
43
     * @param array $arguments arguments for the service constructor
44
     * @param string $defaultClass class to create instance for if none is set in config
45
     * @return object build and modified srvice instance
46
     */
47
    public static function doBuild($config, $arguments, $defaultClass = false){
48
        $cls = isset($config["class"]) ? trim((string)$config["class"]) : (string)$defaultClass;
49
        if($cls != ''){
50
            try {
51
                $constructor = isset($config["constructor"])?(string)$config["constructor"]:"";
52
                if($constructor != '' && Test::assertMethod($cls, $constructor)){
53
                    $service = call_user_func_array(array($cls, $constructor), $arguments);
54
                }else{
55
                    $service = self::build($cls, $arguments);
0 ignored issues
show
Documentation introduced by
$cls is of type string, 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...
56
                }
57
            }catch(\Exception $e){
58
                throw new \RuntimeException('Could not create Service "'.$cls.'" -> '.$e->getMessage());
59
            }
60
        }
61
        if(is_object($service)){
62
            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...
63
        }
64
        throw new \RuntimeException('Could not create Service "'.$cls.'". Class does not exist.');
65
    }
66
67
68
    /**
69
     * Utility function to build an object instance for given class with given constructor-arguments.
70
     *
71
     * @see GenericBuilder
72
     * @static
73
     * @access protected
74
     * @param object $className name of class to build instance for.
75
     * @param array $arguments arguments for the constructor
76
     * @return object build and modified srvice instance
77
     */
78
    public static function build($className, $arguments){
79
        return GenericBuilder::getInstance($className)->build($arguments);
0 ignored issues
show
Documentation introduced by
$className is of type object, but the function expects a string.

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...
80
    }
81
82
        /**
83
     * Builds single argument (to call a method with later) from a config-obect.
84
     *
85
     * @access protected
86
     * @param object $config configuration object to create argument from.
87
     * @return mixed build argument
88
     */
89
    protected static function buildArg($config){
90
        if(trim((string)$config['value']) != ''){
91
            $arg = (string)$config['value'];
92
        }elseif($config->getName() == 'value'){
93
            $arg = (string)$config;
94 View Code Duplication
        }elseif($config->getName() == 'list'){
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...
95
            $arg = array();
96
            foreach($config->children() as $entry){
97
                if($entry->getName() == 'value'){
98
                    if($entry['key']){
99
                        $arg[(string)$entry['key']] = (string)$entry;
100
                    }else{
101
                        $arg[] = (string)$entry;
102
                    }
103
                }elseif($entry->getName() == 'service'){
104
                    $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...
105
                }
106
            }
107
        }elseif($config->getName() == 'service'){
108
            $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...
109
        }elseif($config->list){
110
            $arg = self::buildArg($config->list);
111
        }elseif($config->service){
112
            $arg = self::buildArg($config->service);
113
        }
114
        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...
115
    }
116
117
    /**
118
     * Builds and modifies an arbitrary service/object instance from a config-obect.
119
     *
120
     * @see XMLContext::doBuild
121
     * @see XMLContext::modifyService
122
     * @implements \PEIP\INF\Context\Context
123
     * @access public
124
     * @param object $config configuration object to build a service instance from.
125
     * @param array $arguments arguments for the service constructor
126
     * @param string $defaultClass class to create instance for if none is set in config
127
     * @return object build and modified srvice instance
128
     */
129
    public static function buildAndModify($config, $arguments, $defaultClass = ""){
130
        if((isset($config["class"]) && "" != (string)$config["class"])  || $defaultClass !== ""){
131
             $service = ServiceFactory::doBuild($config, $arguments, $defaultClass);
132
        }else{
133
            throw new \RuntimeException('Could not create Service. no class or reference given.');
134
        }
135 View Code Duplication
        if(isset($config["ref_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...
136
            $service = $service->{(string)$config["ref_property"]};
137
        }elseif(isset($config["ref_method"])){
138
                $args = array();
139
            if($config->argument){
140
                        foreach($config->argument as $arg){
141
                            $args[] = $this->buildArg($arg);
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...
142
                        }
143
                }
144
            $service = call_user_func_array(array($service, (string)$config["ref_method"]), $args);
145
        }
146
        if(!is_object($service)){
147
            throw new \RuntimeException('Could not create Service.');
148
        }
149
        $service = self::modifyService($service, $config);
150
        $id = trim((string)$config['id']);
0 ignored issues
show
Unused Code introduced by
$id is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
151
        
152
        return $service;
153
    }
154
155
156
    /**
157
     * Modifies a service instance from configuration.
158
     *  - Sets properties on the instance.
159
     *  -- Calls a public setter method if exists.
160
     *  -- Else sets a public property if exists.
161
     *  - Calls methods on the instance.
162
     *  - Registers listeners to events on the instance
163
     *
164
     * @access protected
165
     * @param object $service the service instance to modify
166
     * @param object $config configuration to get the modification instructions from.
167
     * @return object the modificated service
168
     */
169
    protected function modifyService($service, $config){        
170
        $config = is_array($config) ? new \ArrayObject($config) : $config;
171
        $reflection = GenericBuilder::getInstance(get_class($service))->getReflectionClass();
0 ignored issues
show
Unused Code introduced by
$reflection is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
172
        // set instance properties
173
        if(isset($config->property)){
174
            foreach($config->property as $property){
175
                $arg = self::buildArg($property);
176
                if($arg){
177
                    $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...
178
                    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...
179
                        $service->{$setter}($arg);
180
                    }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...
181
                        $service->$setter = $arg;
182
                    }
183
                }
184
            }
185
        }
186
        // call instance methods
187
        if(isset($config->action)){
188
            foreach($config->action as $action){
189
                $method = (string)$action['method'] != '' ? (string)$action['method'] : NULL;
190
                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...
191
                    $args = array();
192
                    foreach($action->children() as $argument){
193
                        $args[] = $this->buildArg($argument);
194
                    }
195
                    call_user_func_array(array($service, (string)$action['method']), $args);
196
                }
197
            }
198
        }
199
        // register instance listeners
200 View Code Duplication
        if($service instanceof \PEIP\INF\Event\Connectable){
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...
201
            if(isset($config->listener)){
202
                foreach($config->listener as $listenerConf){
203
                    $event = (string)$listenerConf['event'];
204
                    $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...
205
                    $service->connect($event, $listener);
206
                }
207
            }
208
        }
209
        return $service;
210
    } 
211
}
212
213