Subversion-Projekte lars-tiefland.laravel_shop

Revision

Zur aktuellen Revision | Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/*
4
 * This file is part of Psy Shell.
5
 *
6
 * (c) 2012-2022 Justin Hileman
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Psy\CodeCleaner;
13
 
14
use PhpParser\Node;
15
use PhpParser\Node\Arg;
16
use PhpParser\Node\Expr\Include_;
17
use PhpParser\Node\Expr\StaticCall;
18
use PhpParser\Node\Name\FullyQualified as FullyQualifiedName;
19
use PhpParser\Node\Scalar\LNumber;
20
use Psy\Exception\ErrorException;
21
use Psy\Exception\FatalErrorException;
22
 
23
/**
24
 * Add runtime validation for `require` and `require_once` calls.
25
 */
26
class RequirePass extends CodeCleanerPass
27
{
28
    private static $requireTypes = [Include_::TYPE_REQUIRE, Include_::TYPE_REQUIRE_ONCE];
29
 
30
    /**
31
     * {@inheritdoc}
32
     *
33
     * @return int|Node|null Replacement node (or special return value)
34
     */
35
    public function enterNode(Node $origNode)
36
    {
37
        if (!$this->isRequireNode($origNode)) {
38
            return;
39
        }
40
 
41
        $node = clone $origNode;
42
 
43
        /*
44
         * rewrite
45
         *
46
         *   $foo = require $bar
47
         *
48
         * to
49
         *
50
         *   $foo = require \Psy\CodeCleaner\RequirePass::resolve($bar)
51
         */
52
        $node->expr = new StaticCall(
53
            new FullyQualifiedName(self::class),
54
            'resolve',
55
            [new Arg($origNode->expr), new Arg(new LNumber($origNode->getLine()))],
56
            $origNode->getAttributes()
57
        );
58
 
59
        return $node;
60
    }
61
 
62
    /**
63
     * Runtime validation that $file can be resolved as an include path.
64
     *
65
     * If $file can be resolved, return $file. Otherwise throw a fatal error exception.
66
     *
67
     * If $file collides with a path in the currently running PsySH phar, it will be resolved
68
     * relative to the include path, to prevent PHP from grabbing the phar version of the file.
69
     *
70
     * @throws FatalErrorException when unable to resolve include path for $file
71
     * @throws ErrorException      if $file is empty and E_WARNING is included in error_reporting level
72
     *
73
     * @param string $file
74
     * @param int    $lineNumber Line number of the original require expression
75
     *
76
     * @return string Exactly the same as $file, unless $file collides with a path in the currently running phar
77
     */
78
    public static function resolve($file, $lineNumber = null): string
79
    {
80
        $file = (string) $file;
81
 
82
        if ($file === '') {
83
            // @todo Shell::handleError would be better here, because we could
84
            // fake the file and line number, but we can't call it statically.
85
            // So we're duplicating some of the logics here.
86
            if (\E_WARNING & \error_reporting()) {
87
                ErrorException::throwException(\E_WARNING, 'Filename cannot be empty', null, $lineNumber);
88
            }
89
            // @todo trigger an error as fallback? this is pretty ugly…
90
            // trigger_error('Filename cannot be empty', E_USER_WARNING);
91
        }
92
 
93
        $resolvedPath = \stream_resolve_include_path($file);
94
        if ($file === '' || !$resolvedPath) {
95
            $msg = \sprintf("Failed opening required '%s'", $file);
96
            throw new FatalErrorException($msg, 0, \E_ERROR, null, $lineNumber);
97
        }
98
 
99
        // Special case: if the path is not already relative or absolute, and it would resolve to
100
        // something inside the currently running phar (e.g. `vendor/autoload.php`), we'll resolve
101
        // it relative to the include path so PHP won't grab the phar version.
102
        //
103
        // Note that this only works if the phar has `psysh` in the path. We might want to lift this
104
        // restriction and special case paths that would collide with any running phar?
105
        if ($resolvedPath !== $file && $file[0] !== '.') {
106
            $runningPhar = \Phar::running();
107
            if (\strpos($runningPhar, 'psysh') !== false && \is_file($runningPhar.\DIRECTORY_SEPARATOR.$file)) {
108
                foreach (self::getIncludePath() as $prefix) {
109
                    $resolvedPath = $prefix.\DIRECTORY_SEPARATOR.$file;
110
                    if (\is_file($resolvedPath)) {
111
                        return $resolvedPath;
112
                    }
113
                }
114
            }
115
        }
116
 
117
        return $file;
118
    }
119
 
120
    private function isRequireNode(Node $node): bool
121
    {
122
        return $node instanceof Include_ && \in_array($node->type, self::$requireTypes);
123
    }
124
 
125
    private static function getIncludePath(): array
126
    {
127
        if (\PATH_SEPARATOR === ':') {
128
            return \preg_split('#:(?!//)#', \get_include_path());
129
        }
130
 
131
        return \explode(\PATH_SEPARATOR, \get_include_path());
132
    }
133
}