Completed
Push — master ( 31e4dd...c38430 )
by Christian
07:22
created

AppKernelController::getAppKernelAction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 8
rs 9.4285
cc 1
eloc 5
nc 1
nop 0
1
<?php
2
3
/**
4
 * This file is part of tenside/core-bundle.
5
 *
6
 * (c) Christian Schiffler <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * This project is provided in good faith and hope to be usable by anyone.
12
 *
13
 * @package    tenside/core-bundle
14
 * @author     Christian Schiffler <[email protected]>
15
 * @copyright  2015 Christian Schiffler <[email protected]>
16
 * @license    https://github.com/tenside/core-bundle/blob/master/LICENSE MIT
17
 * @link       https://github.com/tenside/core-bundle
18
 * @filesource
19
 */
20
21
namespace Tenside\CoreBundle\Controller;
22
23
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
24
use Symfony\Component\HttpFoundation\JsonResponse;
25
use Symfony\Component\HttpFoundation\Request;
26
use Symfony\Component\HttpFoundation\Response;
27
use Tenside\Core\Util\PhpProcessSpawner;
28
use Tenside\CoreBundle\Annotation\ApiDescription;
29
30
/**
31
 * Controller for manipulating the AppKernel file.
32
 */
33
class AppKernelController extends AbstractController
34
{
35
    /**
36
     * Retrieve the AppKernel.php.
37
     *
38
     * @return Response
39
     *
40
     * @ApiDoc(
41
     *   section="files",
42
     *   statusCodes = {
43
     *     200 = "When everything worked out ok"
44
     *   },
45
     *   authentication = true,
46
     *   authenticationRoles = {
47
     *     "ROLE_EDIT_APP_KERNEL"
48
     *   }
49
     * )
50
     */
51
    public function getAppKernelAction()
52
    {
53
        return new Response(
54
            file_get_contents($this->getAppKernelPath()),
55
            200,
56
            ['Content-Type' => 'application/x-httpd-php-source']
57
        );
58
    }
59
60
    /**
61
     * Update the AppKernel.php with the given data if it is valid.
62
     *
63
     * The whole submitted data is used as file.
64
     *
65
     * @param Request $request The request to process.
66
     *
67
     * @return JsonResponse
68
     *
69
     * @ApiDoc(
70
     *   section="files",
71
     *   statusCodes = {
72
     *     200 = "When everything worked out ok"
73
     *   },
74
     *   authentication = true,
75
     *   authenticationRoles = {
76
     *     "ROLE_EDIT_APP_KERNEL"
77
     *   }
78
     * )
79
     * @ApiDescription(
80
     *   response={
81
     *     "status" = {
82
     *       "dataType" = "string",
83
     *       "description" = "Either OK or ERROR"
84
     *     },
85
     *     "errors" = {
86
     *       "description" = "List of contained errors",
87
     *       "subType" = "object",
88
     *       "actualType" = "collection",
89
     *       "children" = {
90
     *         "line" = {
91
     *           "dataType" = "string",
92
     *           "description" = "The line number containing the error",
93
     *           "required" = true
94
     *         },
95
     *         "msg" = {
96
     *           "dataType" = "string",
97
     *           "description" = "The error message",
98
     *           "required" = true
99
     *         }
100
     *       }
101
     *     },
102
     *     "warnings" = {
103
     *       "description" = "List of contained warnings",
104
     *       "subType" = "object",
105
     *       "actualType" = "collection",
106
     *       "children" = {
107
     *         "line" = {
108
     *           "dataType" = "string",
109
     *           "description" = "The line number containing the warning",
110
     *           "required" = true
111
     *         },
112
     *         "msg" = {
113
     *           "dataType" = "string",
114
     *           "description" = "The error message",
115
     *           "required" = true
116
     *         }
117
     *       }
118
     *     }
119
     *   }
120
     * )
121
     */
122
    public function putAppKernelAction(Request $request)
123
    {
124
        $content = $request->getContent();
125
        $errors  = $this->checkAppKernel($content);
0 ignored issues
show
Bug introduced by
It seems like $content defined by $request->getContent() on line 124 can also be of type resource; however, Tenside\CoreBundle\Contr...oller::checkAppKernel() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
126
127
        if (!empty($errors['errors'])) {
128
            $errors['status'] = 'ERROR';
129
        } else {
130
            $errors['status'] = 'OK';
131
132
            $this->saveAppKernel($content);
0 ignored issues
show
Bug introduced by
It seems like $content defined by $request->getContent() on line 124 can also be of type resource; however, Tenside\CoreBundle\Contr...roller::saveAppKernel() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
133
        }
134
135
        return new JsonResponse($errors);
136
    }
137
138
    /**
139
     * Check the contents and return the error array.
140
     *
141
     * @param string $content The PHP content.
142
     *
143
     * @return array<string,string[]>
144
     */
145
    private function checkAppKernel($content)
146
    {
147
        if (substr($content, 0, 5) !== '<?php') {
148
            return [
149
                'errors' => [
150
                    [
151
                        'line' => '1',
152
                        'msg'  => 'AppKernel.php must start with "<?php" to work correctly'
153
                    ]
154
                ],
155
                'warnings' => []
156
            ];
157
        }
158
159
        $config = $this->getTensideConfig();
160
161
        $home    = $this->get('tenside.home')->homeDir();
162
        $process = PhpProcessSpawner::create($config, $home)->spawn(
163
            [
164
                '-l',
165
            ]
166
        );
167
168
        $process->setInput($content);
169
        $process->run();
170
171
        if (!$process->isSuccessful()) {
172
            $output = $process->getErrorOutput();
173
            if ((bool) preg_match('/Parse error:\s*syntax error,(.+?)\s+in\s+.+?\s*line\s+(\d+)/', $output, $match)) {
174
                return [
175
                    'errors' => [
176
                        [
177
                            'line' => (int) $match[2],
178
                            'msg'  => $match[1]
179
                        ]
180
                    ],
181
                    'warnings' => []
182
                ];
183
            }
184
185
            // This might expose sensitive data but as we are in authenticated context, this is ok.
186
            return [
187
                'errors' => [
188
                    [
189
                        'line' => '0',
190
                        'msg'  => $output
191
                    ]
192
                ],
193
                'warnings' => []
194
            ];
195
        }
196
197
        return [];
198
    }
199
200
    /**
201
     * Retrieve the path to AppKernel.php
202
     *
203
     * @return string
204
     */
205
    private function getAppKernelPath()
206
    {
207
        return $this->getTensideHome() . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'AppKernel.php';
208
    }
209
210
    /**
211
     * Retrieve a file object for the AppKernel.php.
212
     *
213
     * @param string $content The PHP content.
214
     *
215
     * @return void
216
     */
217
    private function saveAppKernel($content)
218
    {
219
        $file = new \SplFileObject($this->getAppKernelPath(), 'r+');
220
        $file->ftruncate(0);
221
        $file->fwrite($content);
222
        unset($file);
223
    }
224
}
225