GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

ServerRepository::generateSFTPUsername()   B
last analyzed

Complexity

Conditions 5
Paths 6

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 11
nc 6
nop 2
1
<?php
2
/**
3
 * Pterodactyl - Panel
4
 * Copyright (c) 2015 - 2017 Dane Everitt <[email protected]>.
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to deal
8
 * in the Software without restriction, including without limitation the rights
9
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 * copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in all
14
 * copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
 * SOFTWARE.
23
 */
24
25
namespace Pterodactyl\Repositories;
26
27
use DB;
28
use Crypt;
29
use Validator;
30
use Pterodactyl\Models\Node;
31
use Pterodactyl\Models\Pack;
32
use Pterodactyl\Models\User;
33
use Pterodactyl\Models\Server;
34
use Pterodactyl\Models\Service;
35
use Pterodactyl\Models\Allocation;
36
use Pterodactyl\Models\ServiceOption;
37
use Pterodactyl\Services\UuidService;
38
use Pterodactyl\Models\ServerVariable;
39
use Pterodactyl\Models\ServiceVariable;
40
use GuzzleHttp\Exception\ClientException;
41
use GuzzleHttp\Exception\TransferException;
42
use Pterodactyl\Services\DeploymentService;
43
use Pterodactyl\Exceptions\DisplayException;
44
use Pterodactyl\Exceptions\DisplayValidationException;
45
46
class ServerRepository
47
{
48
    /**
49
     * An array of daemon permission to assign to this server.
50
     *
51
     * @var array
52
     */
53
    protected $daemonPermissions = [
54
        's:*',
55
    ];
56
57
    /**
58
     * Generates a SFTP username for a server given a server name.
59
     * format: mumble_67c7a4b0.
60
     *
61
     * @param  string       $name
62
     * @param  null|string  $identifier
63
     * @return string
64
     */
65
    protected function generateSFTPUsername($name, $identifier = null)
66
    {
67
        if (is_null($identifier) || ! ctype_alnum($identifier)) {
68
            $unique = str_random(8);
69
        } else {
70
            if (strlen($identifier) < 8) {
71
                $unique = $identifier . str_random((8 - strlen($identifier)));
72
            } else {
73
                $unique = substr($identifier, 0, 8);
74
            }
75
        }
76
77
        // Filter the Server Name
78
        $name = trim(preg_replace('/[^\w]+/', '', $name), '_');
79
        $name = (strlen($name) < 1) ? str_random(6) : $name;
80
81
        return strtolower(substr($name, 0, 6) . '_' . $unique);
82
    }
83
84
    /**
85
     * Adds a new server to the system.
86
     *
87
     * @param   array  $data
88
     * @return \Pterodactyl\Models\Server
89
     *
90
     * @throws \Pterodactyl\Exceptions\DisplayException
91
     * @throws \Pterodactyl\Exceptions\AutoDeploymentException
92
     * @throws \Pterodactyl\Exceptions\DisplayValidationException
93
     */
94
    public function create(array $data)
95
    {
96
        $validator = Validator::make($data, [
97
            'user_id' => 'required|exists:users,id',
98
            'name' => 'required|regex:/^([\w .-]{1,200})$/',
99
            'description' => 'sometimes|nullable|string',
100
            'memory' => 'required|numeric|min:0',
101
            'swap' => 'required|numeric|min:-1',
102
            'io' => 'required|numeric|min:10|max:1000',
103
            'cpu' => 'required|numeric|min:0',
104
            'disk' => 'required|numeric|min:0',
105
            'service_id' => 'required|numeric|min:1|exists:services,id',
106
            'option_id' => 'required|numeric|min:1|exists:service_options,id',
107
            'location_id' => 'required|numeric|min:1|exists:locations,id',
108
            'pack_id' => 'sometimes|nullable|numeric|min:0',
109
            'custom_container' => 'string',
110
            'startup' => 'string',
111
            'auto_deploy' => 'sometimes|required|accepted',
112
            'custom_id' => 'sometimes|required|numeric|unique:servers,id',
113
            'skip_scripts' => 'sometimes|required|boolean',
114
        ]);
115
116
        $validator->sometimes('node_id', 'required|numeric|min:1|exists:nodes,id', function ($input) {
117
            return ! ($input->auto_deploy);
118
        });
119
120
        $validator->sometimes('allocation_id', 'required|numeric|exists:allocations,id', function ($input) {
121
            return ! ($input->auto_deploy);
122
        });
123
124
        $validator->sometimes('allocation_additional.*', 'sometimes|required|numeric|exists:allocations,id', function ($input) {
125
            return ! ($input->auto_deploy);
126
        });
127
128
        // Run validator, throw catchable and displayable exception if it fails.
129
        // Exception includes a JSON result of failed validation rules.
130
        if ($validator->fails()) {
131
            throw new DisplayValidationException(json_encode($validator->errors()));
132
        }
133
134
        $user = User::findOrFail($data['user_id']);
135
136
        $deployment = false;
137
        if (isset($data['auto_deploy'])) {
138
            $deployment = new DeploymentService;
139
140
            if (isset($data['location_id'])) {
141
                $deployment->setLocation($data['location_id']);
142
            }
143
144
            $deployment->setMemory($data['memory'])->setDisk($data['disk'])->select();
145
        }
146
147
        $node = (! $deployment) ? Node::findOrFail($data['node_id']) : $deployment->node();
148
149
        // Verify IP & Port are a.) free and b.) assigned to the node.
150
        // We know the node exists because of 'exists:nodes,id' in the validation
151
        if (! $deployment) {
152
            $allocation = Allocation::where('id', $data['allocation_id'])->where('node_id', $data['node_id'])->whereNull('server_id')->first();
153
        } else {
154
            $allocation = $deployment->allocation();
155
        }
156
157
        // Something failed in the query, either that combo doesn't exist, or it is in use.
158
        if (! $allocation) {
159
            throw new DisplayException('The selected Allocation ID is either already in use, or unavaliable for this node.');
160
        }
161
162
        // Validate those Service Option Variables
163
        // We know the service and option exists because of the validation.
164
        // We need to verify that the option exists for the service, and then check for
165
        // any required variable fields. (fields are labeled env_<env_variable>)
166
        $option = ServiceOption::where('id', $data['option_id'])->where('service_id', $data['service_id'])->first();
167
        if (! $option) {
168
            throw new DisplayException('The requested service option does not exist for the specified service.');
169
        }
170
171
        // Validate the Pack
172 View Code Duplication
        if (! isset($data['pack_id']) || (int) $data['pack_id'] < 1) {
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...
173
            $data['pack_id'] = null;
174
        } else {
175
            $pack = Pack::where('id', $data['pack_id'])->where('option_id', $data['option_id'])->first();
176
            if (! $pack) {
177
                throw new DisplayException('The requested service pack does not seem to exist for this combination.');
178
            }
179
        }
180
181
        // Load up the Service Information
182
        $service = Service::find($option->service_id);
183
184
        // Check those Variables
185
        $variables = ServiceVariable::where('option_id', $data['option_id'])->get();
186
        $variableList = [];
187
        if ($variables) {
188
            foreach ($variables as $variable) {
189
190
                // Is the variable required?
191
                if (! isset($data['env_' . $variable->env_variable])) {
192
                    if ($variable->required) {
193
                        throw new DisplayException('A required service option variable field (env_' . $variable->env_variable . ') was missing from the request.');
194
                    }
195
                    $variableList[] = [
196
                        'id' => $variable->id,
197
                        'env' => $variable->env_variable,
198
                        'val' => $variable->default_value,
199
                    ];
200
                    continue;
201
                }
202
203
                // Check aganist Regex Pattern
204
                if (! is_null($variable->regex) && ! preg_match($variable->regex, $data['env_' . $variable->env_variable])) {
205
                    throw new DisplayException('Failed to validate service option variable field (env_' . $variable->env_variable . ') aganist regex (' . $variable->regex . ').');
206
                }
207
208
                $variableList[] = [
209
                    'id' => $variable->id,
210
                    'env' => $variable->env_variable,
211
                    'val' => $data['env_' . $variable->env_variable],
212
                ];
213
                continue;
214
            }
215
        }
216
217
        // Check Overallocation
218
        if (! $deployment) {
219
            if (is_numeric($node->memory_overallocate) || is_numeric($node->disk_overallocate)) {
220
                $totals = Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node_id', $node->id)->first();
221
222
                // Check memory limits
223 View Code Duplication
                if (is_numeric($node->memory_overallocate)) {
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...
224
                    $newMemory = $totals->memory + $data['memory'];
225
                    $memoryLimit = ($node->memory * (1 + ($node->memory_overallocate / 100)));
226
                    if ($newMemory > $memoryLimit) {
227
                        throw new DisplayException('The amount of memory allocated to this server would put the node over its allocation limits. This node is allowed ' . ($node->memory_overallocate + 100) . '% of its assigned ' . $node->memory . 'Mb of memory (' . $memoryLimit . 'Mb) of which ' . (($totals->memory / $node->memory) * 100) . '% (' . $totals->memory . 'Mb) is in use already. By allocating this server the node would be at ' . (($newMemory / $node->memory) * 100) . '% (' . $newMemory . 'Mb) usage.');
228
                    }
229
                }
230
231
                // Check Disk Limits
232 View Code Duplication
                if (is_numeric($node->disk_overallocate)) {
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...
233
                    $newDisk = $totals->disk + $data['disk'];
234
                    $diskLimit = ($node->disk * (1 + ($node->disk_overallocate / 100)));
235
                    if ($newDisk > $diskLimit) {
236
                        throw new DisplayException('The amount of disk allocated to this server would put the node over its allocation limits. This node is allowed ' . ($node->disk_overallocate + 100) . '% of its assigned ' . $node->disk . 'Mb of disk (' . $diskLimit . 'Mb) of which ' . (($totals->disk / $node->disk) * 100) . '% (' . $totals->disk . 'Mb) is in use already. By allocating this server the node would be at ' . (($newDisk / $node->disk) * 100) . '% (' . $newDisk . 'Mb) usage.');
237
                    }
238
                }
239
            }
240
        }
241
242
        DB::beginTransaction();
243
244
        try {
245
            $uuid = new UuidService;
246
247
            // Add Server to the Database
248
            $server = new Server;
249
            $genUuid = $uuid->generate('servers', 'uuid');
250
            $genShortUuid = $uuid->generateShort('servers', 'uuidShort', $genUuid);
251
252
            if (isset($data['custom_id'])) {
253
                $server->id = $data['custom_id'];
254
            }
255
256
            $server->fill([
257
                'uuid' => $genUuid,
258
                'uuidShort' => $genShortUuid,
259
                'node_id' => $node->id,
260
                'name' => $data['name'],
261
                'description' => $data['description'],
262
                'skip_scripts' => isset($data['skip_scripts']),
263
                'suspended' => false,
264
                'owner_id' => $user->id,
265
                'memory' => $data['memory'],
266
                'swap' => $data['swap'],
267
                'disk' => $data['disk'],
268
                'io' => $data['io'],
269
                'cpu' => $data['cpu'],
270
                'oom_disabled' => isset($data['oom_disabled']),
271
                'allocation_id' => $allocation->id,
272
                'service_id' => $data['service_id'],
273
                'option_id' => $data['option_id'],
274
                'pack_id' => $data['pack_id'],
275
                'startup' => $data['startup'],
276
                'daemonSecret' => $uuid->generate('servers', 'daemonSecret'),
277
                'image' => (isset($data['custom_container']) && ! empty($data['custom_container'])) ? $data['custom_container'] : $option->docker_image,
278
                'username' => $this->generateSFTPUsername($data['name'], $genShortUuid),
279
                'sftp_password' => Crypt::encrypt('not set'),
280
            ]);
281
            $server->save();
282
283
            // Mark Allocation in Use
284
            $allocation->server_id = $server->id;
285
            $allocation->save();
286
287
            // Add Additional Allocations
288
            if (isset($data['allocation_additional']) && is_array($data['allocation_additional'])) {
289
                foreach ($data['allocation_additional'] as $allocation) {
290
                    $model = Allocation::where('id', $allocation)->where('node_id', $data['node_id'])->whereNull('server_id')->first();
291
                    if (! $model) {
292
                        continue;
293
                    }
294
295
                    $model->server_id = $server->id;
296
                    $model->save();
297
                }
298
            }
299
300
            foreach ($variableList as $item) {
301
                ServerVariable::create([
302
                    'server_id' => $server->id,
303
                    'variable_id' => $item['id'],
304
                    'variable_value' => $item['val'],
305
                ]);
306
            }
307
308
            $environment = $this->parseVariables($server);
309
            $server->load('allocation', 'allocations');
310
311
            $node->guzzleClient(['X-Access-Token' => $node->daemonSecret])->request('POST', '/servers', [
312
                'json' => [
313
                    'uuid' => (string) $server->uuid,
314
                    'user' => $server->username,
315
                    'build' => [
316
                        'default' => [
317
                            'ip' => $server->allocation->ip,
318
                            'port' => $server->allocation->port,
319
                        ],
320
                        'ports' => $server->allocations->groupBy('ip')->map(function ($item) {
321
                            return $item->pluck('port');
322
                        })->toArray(),
323
                        'env' => $environment->pluck('value', 'variable')->toArray(),
324
                        'memory' => (int) $server->memory,
325
                        'swap' => (int) $server->swap,
326
                        'io' => (int) $server->io,
327
                        'cpu' => (int) $server->cpu,
328
                        'disk' => (int) $server->disk,
329
                        'image' => $server->image,
330
                    ],
331
                    'service' => [
332
                        'type' => $service->folder,
333
                        'option' => $option->tag,
334
                        'pack' => (isset($pack)) ? $pack->uuid : null,
335
                        'skip_scripts' => $server->skip_scripts,
336
                    ],
337
                    'keys' => [
338
                        (string) $server->daemonSecret => $this->daemonPermissions,
339
                    ],
340
                    'rebuild' => false,
341
                    'start_on_completion' => isset($data['start_on_completion']),
342
                ],
343
            ]);
344
345
            DB::commit();
346
347
            return $server;
348
        } catch (\Exception $ex) {
349
            DB::rollBack();
350
            throw $ex;
351
        }
352
    }
353
354
    /**
355
     * Update the details for a server.
356
     *
357
     * @param  int    $id
358
     * @param  array  $data
359
     * @return \Pterodactyl\Models\Server
360
     *
361
     * @throws \Pterodactyl\Exceptions\DisplayException
362
     * @throws \Pterodactyl\Exceptions\DisplayValidationException
363
     */
364
    public function updateDetails($id, array $data)
365
    {
366
        $uuid = new UuidService;
367
        $resetDaemonKey = false;
368
369
        // Validate Fields
370
        $validator = Validator::make($data, [
371
            'owner_id' => 'sometimes|required|integer|exists:users,id',
372
            'name' => 'sometimes|required|regex:([\w .-]{1,200})',
373
            'description' => 'sometimes|nullable|string',
374
            'reset_token' => 'sometimes|required|accepted',
375
        ]);
376
377
        // Run validator, throw catchable and displayable exception if it fails.
378
        // Exception includes a JSON result of failed validation rules.
379
        if ($validator->fails()) {
380
            throw new DisplayValidationException(json_encode($validator->errors()));
381
        }
382
383
        DB::beginTransaction();
384
385
        try {
386
            $server = Server::with('user')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
387
388
            // Update daemon secret if it was passed.
389
            if (isset($data['reset_token']) || (isset($data['owner_id']) && (int) $data['owner_id'] !== $server->user->id)) {
390
                $oldDaemonKey = $server->daemonSecret;
391
                $server->daemonSecret = $uuid->generate('servers', 'daemonSecret');
392
                $resetDaemonKey = true;
393
            }
394
395
            // Save our changes
396
            $server->fill($data)->save();
397
398
            // Do we need to update? If not, return successful.
399
            if (! $resetDaemonKey) {
400
                return DB::commit();
401
            }
402
403
            $res = $server->node->guzzleClient([
404
                'X-Access-Server' => $server->uuid,
405
                'X-Access-Token' => $server->node->daemonSecret,
406
            ])->request('PATCH', '/server', [
407
                'exceptions' => false,
408
                'json' => [
409
                    'keys' => [
410
                        (string) $oldDaemonKey => [],
0 ignored issues
show
Bug introduced by
The variable $oldDaemonKey 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...
411
                        (string) $server->daemonSecret => $this->daemonPermissions,
412
                    ],
413
                ],
414
            ]);
415
416
            if ($res->getStatusCode() === 204) {
417
                DB::commit();
418
419
                return $server;
420
            } else {
421
                throw new DisplayException('Daemon returned a a non HTTP/204 error code. HTTP/' + $res->getStatusCode());
422
            }
423
        } catch (\Exception $ex) {
424
            DB::rollBack();
425
            throw $ex;
426
        }
427
    }
428
429
    /**
430
     * Update the container for a server.
431
     *
432
     * @param  int    $id
433
     * @param  array  $data
434
     * @return \Pterodactyl\Models\Server
435
     *
436
     * @throws \Pterodactyl\Exceptions\DisplayValidationException
437
     */
438
    public function updateContainer($id, array $data)
439
    {
440
        $validator = Validator::make($data, [
441
            'docker_image' => 'required|string',
442
        ]);
443
444
        // Run validator, throw catchable and displayable exception if it fails.
445
        // Exception includes a JSON result of failed validation rules.
446
        if ($validator->fails()) {
447
            throw new DisplayValidationException(json_encode($validator->errors()));
448
        }
449
450
        DB::beginTransaction();
451
        try {
452
            $server = Server::findOrFail($id);
453
454
            $server->image = $data['docker_image'];
455
            $server->save();
456
457
            $server->node->guzzleClient([
458
                'X-Access-Server' => $server->uuid,
459
                'X-Access-Token' => $server->node->daemonSecret,
460
            ])->request('PATCH', '/server', [
461
                'json' => [
462
                    'build' => [
463
                        'image' => $server->image,
464
                    ],
465
                ],
466
            ]);
467
468
            DB::commit();
469
470
            return $server;
471
        } catch (\Exception $ex) {
472
            DB::rollBack();
473
            throw $ex;
474
        }
475
    }
476
477
    /**
478
     * Update the build details for a server.
479
     *
480
     * @param  int    $id
481
     * @param  array  $data
482
     * @return \Pterodactyl\Models\Server
483
     *
484
     * @throws \Pterodactyl\Exceptions\DisplayException
485
     * @throws \Pterodactyl\Exceptions\DisplayValidationException
486
     */
487
    public function changeBuild($id, array $data)
488
    {
489
        $validator = Validator::make($data, [
490
            'allocation_id' => 'sometimes|required|exists:allocations,id',
491
            'add_allocations' => 'sometimes|required|array',
492
            'remove_allocations' => 'sometimes|required|array',
493
            'memory' => 'sometimes|required|integer|min:0',
494
            'swap' => 'sometimes|required|integer|min:-1',
495
            'io' => 'sometimes|required|integer|min:10|max:1000',
496
            'cpu' => 'sometimes|required|integer|min:0',
497
            'disk' => 'sometimes|required|integer|min:0',
498
        ]);
499
500
        // Run validator, throw catchable and displayable exception if it fails.
501
        // Exception includes a JSON result of failed validation rules.
502
        if ($validator->fails()) {
503
            throw new DisplayValidationException(json_encode($validator->errors()));
504
        }
505
506
        DB::beginTransaction();
507
508
        try {
509
            $server = Server::with('allocation', 'allocations')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
510
            $newBuild = [];
511
            $newAllocations = [];
0 ignored issues
show
Unused Code introduced by
$newAllocations 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...
512
513
            if (isset($data['allocation_id'])) {
514
                if ((int) $data['allocation_id'] !== $server->allocation_id) {
515
                    $selection = $server->allocations->where('id', $data['allocation_id'])->first();
516
                    if (! $selection) {
517
                        throw new DisplayException('The requested default connection is not allocated to this server.');
518
                    }
519
520
                    $server->allocation_id = $selection->id;
521
                    $newBuild['default'] = ['ip' => $selection->ip, 'port' => $selection->port];
522
523
                    $server->load('allocation');
524
                }
525
            }
526
527
            $newPorts = false;
528
            $firstNewAllocation = null;
529
            // Add Assignments
530
            if (isset($data['add_allocations'])) {
531
                foreach ($data['add_allocations'] as $allocation) {
532
                    $model = Allocation::where('id', $allocation)->whereNull('server_id')->first();
533
                    if (! $model) {
534
                        continue;
535
                    }
536
537
                    $newPorts = true;
538
                    $firstNewAllocation = (is_null($firstNewAllocation)) ? $model->id : $firstNewAllocation;
539
                    $model->update([
540
                        'server_id' => $server->id,
541
                    ]);
542
                }
543
544
                $server->load('allocations');
545
            }
546
547
            // Remove Assignments
548
            if (isset($data['remove_allocations'])) {
549
                foreach ($data['remove_allocations'] as $allocation) {
550
                    // Can't remove the assigned IP/Port combo
551
                    if ((int) $allocation === $server->allocation_id) {
552
                        // No New Allocation
553
                        if (is_null($firstNewAllocation)) {
554
                            continue;
555
                        }
556
557
                        // New Allocation, set as the default.
558
                        $server->allocation_id = $firstNewAllocation;
559
                    }
560
561
                    $newPorts = true;
562
                    Allocation::where('id', $allocation)->where('server_id', $server->id)->update([
563
                        'server_id' => null,
564
                    ]);
565
                }
566
567
                $server->load('allocations');
568
            }
569
570
            if ($newPorts) {
571
                $newBuild['ports|overwrite'] = $server->allocations->groupBy('ip')->map(function ($item) {
572
                    return $item->pluck('port');
573
                })->toArray();
574
575
                $newBuild['env|overwrite'] = $this->parseVariables($server)->pluck('value', 'variable')->toArray();
576
            }
577
578
            // @TODO: verify that server can be set to this much memory without
579
            // going over node limits.
580 View Code Duplication
            if (isset($data['memory']) && $server->memory !== (int) $data['memory']) {
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...
581
                $server->memory = $data['memory'];
582
                $newBuild['memory'] = (int) $server->memory;
583
            }
584
585 View Code Duplication
            if (isset($data['swap']) && $server->swap !== (int) $data['swap']) {
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...
586
                $server->swap = $data['swap'];
587
                $newBuild['swap'] = (int) $server->swap;
588
            }
589
590
            // @TODO: verify that server can be set to this much disk without
591
            // going over node limits.
592 View Code Duplication
            if (isset($data['disk']) && $server->disk !== (int) $data['disk']) {
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...
593
                $server->disk = $data['disk'];
594
                $newBuild['disk'] = (int) $server->disk;
595
            }
596
597 View Code Duplication
            if (isset($data['cpu']) && $server->cpu !== (int) $data['cpu']) {
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...
598
                $server->cpu = $data['cpu'];
599
                $newBuild['cpu'] = (int) $server->cpu;
600
            }
601
602 View Code Duplication
            if (isset($data['io']) && $server->io !== (int) $data['io']) {
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...
603
                $server->io = $data['io'];
604
                $newBuild['io'] = (int) $server->io;
605
            }
606
607
            // Try save() here so if it fails we haven't contacted the daemon
608
            // This won't be committed unless the HTTP request succeedes anyways
609
            $server->save();
610
611
            if (! empty($newBuild)) {
612
                $server->node->guzzleClient([
613
                    'X-Access-Server' => $server->uuid,
614
                    'X-Access-Token' => $server->node->daemonSecret,
615
                ])->request('PATCH', '/server', [
616
                    'json' => [
617
                        'build' => $newBuild,
618
                    ],
619
                ]);
620
            }
621
622
            DB::commit();
623
624
            return $server;
625
        } catch (\Exception $ex) {
626
            DB::rollBack();
627
            throw $ex;
628
        }
629
    }
630
631
    /**
632
     * Process the variables for a server, and save to the database.
633
     *
634
     * @param  \Pterodactyl\Models\Server  $server
635
     * @param  array                       $data
636
     * @param  bool                        $admin
637
     * @return \Illuminate\Support\Collection
638
     *
639
     * @throws \Pterodactyl\Exceptions\DisplayValidationException
640
     */
641
    protected function processVariables(Server $server, $data, $admin = false)
642
    {
643
        $server->load('option.variables');
644
645
        if ($admin) {
646
            $server->startup = $data['startup'];
647
            $server->save();
648
        }
649
650
        if ($server->option->variables) {
651
            foreach ($server->option->variables as &$variable) {
652
                $set = isset($data['env_' . $variable->id]);
653
654
                // If user is not an admin and are trying to edit a non-editable field
655
                // or an invisible field just silently skip the variable.
656
                if (! $admin && (! $variable->user_editable || ! $variable->user_viewable)) {
657
                    continue;
658
                }
659
660
                // Perform Field Validation
661
                $validator = Validator::make([
662
                    'variable_value' => ($set) ? $data['env_' . $variable->id] : null,
663
                ], [
664
                    'variable_value' => $variable->rules,
665
                ]);
666
667
                if ($validator->fails()) {
668
                    throw new DisplayValidationException(json_encode(
669
                        collect([
670
                            'notice' => ['There was a validation error with the `' . $variable->name . '` variable.'],
671
                        ])->merge($validator->errors()->toArray())
672
                    ));
673
                }
674
675
                $svar = ServerVariable::firstOrNew([
676
                    'server_id' => $server->id,
677
                    'variable_id' => $variable->id,
678
                ]);
679
680
                // Set the value; if one was not passed set it to the default value
681
                if ($set) {
682
                    $svar->variable_value = $data['env_' . $variable->id];
683
684
                // Not passed, check if this record exists if so keep value, otherwise set default
685
                } else {
686
                    $svar->variable_value = ($svar->exists) ? $svar->variable_value : $variable->default_value;
687
                }
688
689
                $svar->save();
690
            }
691
        }
692
693
        return $this->parseVariables($server);
694
    }
695
696
    /**
697
     * Parse the variables and return in a standardized format.
698
     *
699
     * @param  \Pterodactyl\Models\Server  $server
700
     * @return \Illuminate\Support\Collection
701
     */
702
    protected function parseVariables(Server $server)
703
    {
704
        // Reload Variables
705
        $server->load('variables');
706
707 View Code Duplication
        $parsed = $server->option->variables->map(function ($item, $key) use ($server) {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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...
708
            $display = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first();
709
710
            return [
711
                'variable' => $item->env_variable,
712
                'value' => (! is_null($display)) ? $display : $item->default_value,
713
            ];
714
        });
715
716
        $merge = [[
717
            'variable' => 'STARTUP',
718
            'value' => $server->startup,
719
        ], [
720
            'variable' => 'P_VARIABLE__LOCATION',
721
            'value' => $server->location->short,
722
        ]];
723
724
        $allocations = $server->allocations->where('id', '!=', $server->allocation_id);
725
        $i = 0;
726
727
        foreach ($allocations as $allocation) {
728
            $merge[] = [
729
                'variable' => 'ALLOC_' . $i . '__PORT',
730
                'value' => $allocation->port,
731
            ];
732
733
            $i++;
734
        }
735
736
        if ($parsed->count() === 0) {
737
            return collect($merge);
738
        }
739
740
        return $parsed->merge($merge);
741
    }
742
743
    /**
744
     * Update the startup details for a server.
745
     *
746
     * @param  int    $id
747
     * @param  array  $data
748
     * @param  bool   $admin
749
     * @return bool
750
     *
751
     * @throws \GuzzleHttp\Exception\RequestException
752
     * @throws \Pterodactyl\Exceptions\DisplayException
753
     * @throws \Pterodactyl\Exceptions\DisplayValidationException
754
     */
755
    public function updateStartup($id, array $data, $admin = false)
756
    {
757
        $server = Server::with('variables', 'option.variables')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
758
        $hasServiceChanges = false;
759
760
        if ($admin) {
761
            // User is an admin, lots of things to do here.
762
            $validator = Validator::make($data, [
763
                'startup' => 'required|string',
764
                'skip_scripts' => 'sometimes|required|boolean',
765
                'service_id' => 'required|numeric|min:1|exists:services,id',
766
                'option_id' => 'required|numeric|min:1|exists:service_options,id',
767
                'pack_id' => 'sometimes|nullable|numeric|min:0',
768
            ]);
769
770
            if ((int) $data['pack_id'] < 1) {
771
                $data['pack_id'] = null;
772
            }
773
774
            if ($validator->fails()) {
775
                throw new DisplayValidationException(json_encode($validator->errors()));
776
            }
777
778
            if (
779
                $server->service_id != $data['service_id'] ||
780
                $server->option_id != $data['option_id'] ||
781
                $server->pack_id != $data['pack_id']
782
            ) {
783
                $hasServiceChanges = true;
784
            }
785
        }
786
787
        // If user isn't an administrator, this function is being access from the front-end
788
        // Just try to update specific variables.
789
        if (! $admin || ! $hasServiceChanges) {
790
            return DB::transaction(function () use ($admin, $data, $server) {
791
                $environment = $this->processVariables($server, $data, $admin);
792
793
                $server->node->guzzleClient([
794
                    'X-Access-Server' => $server->uuid,
795
                    'X-Access-Token' => $server->node->daemonSecret,
796
                ])->request('PATCH', '/server', [
797
                    'json' => [
798
                        'build' => [
799
                            'env|overwrite' => $environment->pluck('value', 'variable')->toArray(),
800
                        ],
801
                    ],
802
                ]);
803
804
                return false;
805
            });
806
        }
807
808
        // Validate those Service Option Variables
809
        // We know the service and option exists because of the validation.
810
        // We need to verify that the option exists for the service, and then check for
811
        // any required variable fields. (fields are labeled env_<env_variable>)
812
        $option = ServiceOption::where('id', $data['option_id'])->where('service_id', $data['service_id'])->first();
813
        if (! $option) {
814
            throw new DisplayException('The requested service option does not exist for the specified service.');
815
        }
816
817
        // Validate the Pack
818 View Code Duplication
        if (! isset($data['pack_id']) || (int) $data['pack_id'] < 1) {
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...
819
            $data['pack_id'] = null;
820
        } else {
821
            $pack = Pack::where('id', $data['pack_id'])->where('option_id', $data['option_id'])->first();
822
            if (! $pack) {
823
                throw new DisplayException('The requested service pack does not seem to exist for this combination.');
824
            }
825
        }
826
827
        return DB::transaction(function () use ($admin, $data, $server) {
828
            $server->installed = 0;
829
            $server->service_id = $data['service_id'];
830
            $server->option_id = $data['option_id'];
831
            $server->pack_id = $data['pack_id'];
832
            $server->skip_scripts = isset($data['skip_scripts']);
833
            $server->save();
834
835
            $server->variables->each->delete();
836
837
            $server->load('service', 'pack');
838
839
            // Send New Environment
840
            $environment = $this->processVariables($server, $data, $admin);
841
842
            $server->node->guzzleClient([
843
                'X-Access-Server' => $server->uuid,
844
                'X-Access-Token' => $server->node->daemonSecret,
845
            ])->request('POST', '/server/reinstall', [
846
                'json' => [
847
                    'build' => [
848
                        'env|overwrite' => $environment->pluck('value', 'variable')->toArray(),
849
                    ],
850
                    'service' => [
851
                        'type' => $server->option->service->folder,
852
                        'option' => $server->option->tag,
853
                        'pack' => (! is_null($server->pack_id)) ? $server->pack->uuid : null,
854
                        'skip_scripts' => $server->skip_scripts,
855
                    ],
856
                ],
857
            ]);
858
859
            return true;
860
        });
861
    }
862
863
    /**
864
     * Delete a server from the system permanetly.
865
     *
866
     * @param  int   $id
867
     * @param  bool  $force
868
     * @return void
869
     *
870
     * @throws \Pterodactyl\Exceptions\DisplayException
871
     */
872
    public function delete($id, $force = false)
873
    {
874
        $server = Server::with('node', 'allocations', 'variables')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
875
876
        // Due to MySQL lockouts if the daemon response fails, we need to
877
        // delete the server from the daemon first. If it succeedes and then
878
        // MySQL fails, users just need to force delete the server.
879
        //
880
        // If this is a force delete, continue anyways.
881
        try {
882
            $server->node->guzzleClient([
883
                'X-Access-Token' => $server->node->daemonSecret,
884
                'X-Access-Server' => $server->uuid,
885
            ])->request('DELETE', '/servers');
886
        } catch (ClientException $ex) {
887
            // Exception is thrown on 4XX HTTP errors, so catch and determine
888
            // if we should continue, or if there is a permissions error.
889
            //
890
            // Daemon throws a 404 if the server doesn't exist, if that is returned
891
            // continue with deletion, even if not a force deletion.
892
            $response = $ex->getResponse();
0 ignored issues
show
Unused Code introduced by
$response 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...
893
            if ($ex->getResponse()->getStatusCode() !== 404 && ! $force) {
894
                throw new DisplayException($ex->getMessage());
895
            }
896
        } catch (TransferException $ex) {
897
            if (! $force) {
898
                throw new DisplayException($ex->getMessage());
899
            }
900
        } catch (\Exception $ex) {
901
            throw $ex;
902
        }
903
904
        DB::transaction(function () use ($server) {
905
            $server->allocations->each(function ($item) {
906
                $item->server_id = null;
907
                $item->save();
908
            });
909
910
            $server->variables->each->delete();
911
912
            $server->load('subusers.permissions');
913
            $server->subusers->each(function ($subuser) {
914
                $subuser->permissions->each->delete();
915
                $subuser->delete();
916
            });
917
918
            $server->tasks->each->delete();
919
920
            // Delete Databases
921
            // This is the one un-recoverable point where
922
            // transactions will not save us.
923
            $repository = new DatabaseRepository;
924
            $server->databases->each(function ($item) use ($repository) {
925
                $repository->drop($item->id);
926
            });
927
928
            // Fully delete the server.
929
            $server->delete();
930
        });
931
    }
932
933
    /**
934
     * Toggle the install status of a serve.
935
     *
936
     * @param  int    $id
937
     * @return bool
938
     *
939
     * @throws \Pterodactyl\Exceptions\DisplayException
940
     */
941
    public function toggleInstall($id)
942
    {
943
        $server = Server::findOrFail($id);
944
        if ($server->installed > 1) {
945
            throw new DisplayException('This server was marked as having a failed install or being deleted, you cannot override this.');
946
        }
947
        $server->installed = ! $server->installed;
948
949
        return $server->save();
950
    }
951
952
    /**
953
     * Suspends or unsuspends a server.
954
     *
955
     * @param  int   $id
956
     * @param  bool  $unsuspend
957
     * @return void
958
     */
959
    public function toggleAccess($id, $unsuspend = true)
960
    {
961
        $server = Server::with('node')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
962
963
        DB::transaction(function () use ($server, $unsuspend) {
964
            if (
965
                (! $unsuspend && $server->suspended) ||
966
                ($unsuspend && ! $server->suspended)
967
            ) {
968
                return true;
969
            }
970
971
            $server->suspended = ! $unsuspend;
972
            $server->save();
973
974
            $server->node->guzzleClient([
975
                'X-Access-Token' => $server->node->daemonSecret,
976
                'X-Access-Server' => $server->uuid,
977
            ])->request('POST', ($unsuspend) ? '/server/unsuspend' : '/server/suspend');
978
        });
979
    }
980
981
    /**
982
     * Updates the SFTP password for a server.
983
     *
984
     * @param  int     $id
985
     * @param  string  $password
986
     * @return void
987
     *
988
     * @throws \Pterodactyl\Exceptions\DisplayValidationException
989
     */
990
    public function updateSFTPPassword($id, $password)
991
    {
992
        $server = Server::with('node')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
993
994
        $validator = Validator::make(['password' => $password], [
995
            'password' => 'required|regex:/^((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})$/',
996
        ]);
997
998
        if ($validator->fails()) {
999
            throw new DisplayValidationException(json_encode($validator->errors()));
1000
        }
1001
1002
        DB::transaction(function () use ($password, $server) {
1003
            $server->sftp_password = Crypt::encrypt($password);
1004
            $server->save();
1005
1006
            $server->node->guzzleClient([
1007
                'X-Access-Token' => $server->node->daemonSecret,
1008
                'X-Access-Server' => $server->uuid,
1009
            ])->request('POST', '/server/password', [
1010
                'json' => ['password' => $password],
1011
            ]);
1012
        });
1013
    }
1014
1015
    /**
1016
     * Marks a server for reinstallation on the node.
1017
     *
1018
     * @param  int     $id
1019
     * @return void
1020
     */
1021
    public function reinstall($id)
1022
    {
1023
        $server = Server::with('node')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1024
1025
        DB::transaction(function () use ($server) {
1026
            $server->installed = 0;
1027
            $server->save();
1028
1029
            $server->node->guzzleClient([
1030
                'X-Access-Token' => $server->node->daemonSecret,
1031
                'X-Access-Server' => $server->uuid,
1032
            ])->request('POST', '/server/reinstall');
1033
        });
1034
    }
1035
}
1036