ServiceParamConverter::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
/*
4
 * All rights reserved
5
 * Copyright 2015 Gendoria
6
 */
7
8
namespace Gendoria\ParamConverterBundle\Request\ParamConverter;
9
10
use InvalidArgumentException;
11
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
12
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface;
13
use Symfony\Component\DependencyInjection\Container;
14
use Symfony\Component\HttpFoundation\Request;
15
16
/**
17
 * This param converter converts parameters based on service method call.
18
 *
19
 * Param converter invocation:
20
 *
21
 * `@ParamConverter("parameter_name", converter="service_param_converter", options={"service" = "service_id", "method" = "service_method", "arguments" = {"%requestParamName%", "@otherServiceId", "someParameter"})`
22
 *
23
 * Using this converter you can inject virtually any parameter into a controller, as service method call is not restricted with anything.
24
 * You should be **very cautious**, though, when passing request parameters into a service method call, as it can potentially lead to injections of malicious code.
25
 * All of the parameters injected should be properly restricted (eg to integers) using routing configuration.
26
 *
27
 * @author Tomasz Struczyński <[email protected]>
28
 */
29
class ServiceParamConverter implements ParamConverterInterface
30
{
31
    /**
32
     * Service container.
33
     *
34
     * @var Container
35
     */
36
    private $container;
37
38
    /**
39
     * Class constructor.
40
     *
41
     * @param Container $container Service container.
42
     */
43 12
    public function __construct(Container $container)
44
    {
45 12
        $this->container = $container;
46 12
    }
47
48
    /**
49
     * Apply param converter.
50
     *
51
     * @param Request        $request       Request
52
     * @param ParamConverter $configuration Param converter configuration.
53
     *
54
     * @return bool
55
     *
56
     * @throws InvalidArgumentException
57
     */
58 11
    public function apply(Request $request, ParamConverter $configuration)
59
    {
60 11
        $param = $configuration->getName();
61 11
        $options = $this->getOptions($configuration);
62
63 11
        foreach ($options['arguments'] as &$value) {
64 9
            $value = $this->parseArgument($value, $request);
65 9
        }
66
67 9
        $service = $this->container->get($options['service']);
68 9
        $return = call_user_func_array(array($service, $options['method']), $options['arguments']);
69
70 9
        if ($configuration->getClass() && (!is_object($return) || !is_a($return, $configuration->getClass()))) {
71 1
            return false;
72
        }
73
74 8
        $request->attributes->set($param, $return);
75
76 8
        return true;
77
    }
78
79
    /**
80
     * Parse single argument.
81
     *
82
     * @param string  $value
83
     * @param Request $request
84
     *
85
     * @return mixed
86
     *
87
     * @throws InvalidArgumentException
88
     */
89 9
    private function parseArgument($value, Request $request)
90
    {
91 9
        if (strpos($value, '%') === 0 && strrpos($value, '%') === strlen($value)-1) {
92 6
            return $this->parseParameterArgument(substr($value, 1, strlen($value)-2), $request);
93 3
        } elseif (strpos($value, '@') === 0) {
94 2
            if ($this->container->has(substr($value, 1)) === false) {
95 1
                throw new InvalidArgumentException('Unknown service requested: '.$value);
96
            }
97
98 1
            return $this->container->get(substr($value, 1));
99
        }
100
101 1
        return $value;
102
    }
103
    
104
    /**
105
     * Parse parameter type argument.
106
     * 
107
     * @param string $value
108
     * @param Request $request
109
     * @return type
110
     */
111 6
    private function parseParameterArgument($value, Request $request)
112
    {
113 6
        if ($this !== $result = $request->get($value, $this)) {
114 2
            return $result;
115
        }
116
        
117
        //Argument may refer to function call of another argument
118 5
        if (strpos($value, '::')) {
119 4
            $paramName = substr($value, 0, strpos($value, '::'));
120 4
            $fnData = substr($value, strpos($value, '::')+2);
121 4
            if (strpos($fnData, '(')) {
122 2
                $fnName = substr($fnData, 0, strpos($fnData, '('));
123 2
                $fnArguments = $this->parseFunctionArguments(substr($fnData, strpos($fnData, '(')), $request);
124 1
            } else {
125 2
                $fnName = $fnData;
126 2
                $fnArguments = array();
127
            }
128 3
            $possibleObject = $request->get($paramName);
129 3
            if (!$possibleObject || !is_callable(array($possibleObject, $fnName))) {
130 1
                return null;
131
            }
132 2
            return call_user_func_array(array($possibleObject, $fnName), $fnArguments);
133
        }
134 1
        return null;
135
    }
136
    
137
    /**
138
     * Parse function arguments.
139
     * 
140
     * @param string $argumentsStr
141
     * @param Request $request
142
     * @return array
143
     * @throws InvalidArgumentException Thrown, when function arguments cannot be parsed.
144
     */
145 2
    private function parseFunctionArguments($argumentsStr, Request $request)
146
    {
147 2
        if (strpos($argumentsStr, '(') !== 0 || strpos($argumentsStr, ')') !== strlen($argumentsStr)-1) {
148 1
            throw new \InvalidArgumentException("Incorrect function arguments string");
149
        }
150 1
        $argumentsArr = explode(",", substr($argumentsStr, 1, strlen($argumentsStr)-2));
151 1
        foreach ($argumentsArr as &$argument) {
152 1
            $argument = $this->parseArgument($argument, $request);
153 1
        }
154 1
        return $argumentsArr;
155
    }
156
157
    /**
158
     * Return true, if current configuration is supported.
159
     * 
160
     * @param ParamConverter $configuration
161
     * @return boolean
162
     */
163 1
    public function supports(ParamConverter $configuration)
164
    {
165 1
        $options = $this->getOptions($configuration);
166
167 1
        return $this->container->has($options['service']) && !empty($options['method']);
168
    }
169
170 12
    protected function getOptions(ParamConverter $configuration)
171
    {
172 12
        $options = $configuration->getOptions();
173 12
        if (!is_array($options)) {
174 1
            $options = array();
175 1
        }
176
177 12
        return array_replace(array(
178 12
            'service' => null,
179 12
            'method' => null,
180 12
            'arguments' => array(),
181 12
        ), $options);
182
    }
183
}
184