• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

daycry / class-finder / 16724626674

04 Aug 2025 01:32PM UTC coverage: 82.262% (-12.2%) from 94.493%
16724626674

push

github

web-flow
Update php.yml

320 of 389 relevant lines covered (82.26%)

3.29 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.75
/src/Libraries/Files/FilesEntry.php
1
<?php
2

3
namespace Daycry\ClassFinder\Libraries\Files;
4

5
use Daycry\ClassFinder\ClassFinder;
6
use PhpParser\Error;
7
use PhpParser\NodeTraverser;
8
use PhpParser\ParserFactory;
9

10
class FilesEntry
11
{
12
    private string $file;
13
    private string $php;
14
    private ?array $cachedClasses                = null;
15
    private static ?ParserFactory $parserFactory = null;
16

17
    public function __construct(string $fileToInclude, string $php)
18
    {
19
        $this->file = $this->normalizePath($fileToInclude);
11✔
20
        $this->php  = $php;
11✔
21
    }
22

23
    public function knowsNamespace(string $namespace): bool
24
    {
25
        $classes = $this->getClassesInFile(ClassFinder::ALLOW_ALL);
8✔
26

27
        foreach ($classes as $class) {
8✔
28
            if (str_contains($class, $namespace)) {
8✔
29
                return true;
6✔
30
            }
31
        }
32

33
        return false;
5✔
34
    }
35

36
    /**
37
     * Gets a list of classes that belong to the given namespace.
38
     *
39
     * @return list<string>
40
     */
41
    public function getClasses(string $namespace, int $options): array
42
    {
43
        $classes = $this->getClassesInFile($options);
6✔
44

45
        return array_values(array_filter($classes, static function ($class) use ($namespace) {
6✔
46
            $classNameFragments = explode('\\', $class);
6✔
47

48
            if (count($classNameFragments) > 1) {
6✔
49
                array_pop($classNameFragments);
6✔
50
            }
51

52
            $classNamespace = implode('\\', $classNameFragments);
6✔
53
            $namespace      = trim($namespace, '\\');
6✔
54

55
            return $namespace === $classNamespace;
6✔
56
        }));
6✔
57
    }
58

59
    /**
60
     * Dynamically execute files and check for defined classes.
61
     *
62
     * Uses static analysis with PHP-Parser for better performance and security.
63
     */
64
    private function getClassesInFile(int $options): array
65
    {
66
        if ($this->cachedClasses !== null) {
11✔
67
            return $this->filterClassesByOptions($this->cachedClasses, $options);
3✔
68
        }
69

70
        try {
71
            // Try static analysis first (faster and safer)
72
            $this->cachedClasses = $this->parseFileStatically();
11✔
73
        } catch (Error $e) {
×
74
            // Fallback to dynamic analysis if static analysis fails
75
            $this->cachedClasses = $this->parseFileDynamically();
×
76
        }
77

78
        return $this->filterClassesByOptions($this->cachedClasses, $options);
11✔
79
    }
80

81
    /**
82
     * Parse PHP file using static analysis (PHP-Parser)
83
     */
84
    private function parseFileStatically(): array
85
    {
86
        if (! file_exists($this->file)) {
11✔
87
            return ['classes' => [], 'interfaces' => [], 'traits' => [], 'functions' => []];
×
88
        }
89

90
        $code = file_get_contents($this->file);
11✔
91
        if ($code === false) {
11✔
92
            return ['classes' => [], 'interfaces' => [], 'traits' => [], 'functions' => []];
×
93
        }
94

95
        if (self::$parserFactory === null) {
11✔
96
            self::$parserFactory = new ParserFactory();
1✔
97
        }
98

99
        // Fixed API call for php-parser v5.x
100
        $parser = self::$parserFactory->createForNewestSupportedVersion();
11✔
101
        $ast    = $parser->parse($code);
11✔
102

103
        if ($ast === null) {
11✔
104
            return ['classes' => [], 'interfaces' => [], 'traits' => [], 'functions' => []];
×
105
        }
106

107
        $visitor   = new ClassExtractionVisitor();
11✔
108
        $traverser = new NodeTraverser();
11✔
109
        $traverser->addVisitor($visitor);
11✔
110
        $traverser->traverse($ast);
11✔
111

112
        return $visitor->getAllElements();
11✔
113
    }
114

115
    /**
116
     * Fallback: Parse file using dynamic analysis (original exec method)
117
     */
118
    private function parseFileDynamically(): array
119
    {
120
        $initialData = $this->execReturn("var_export(array(get_declared_interfaces(), get_declared_classes(), get_declared_traits(), get_defined_functions()['user']));");
×
121

122
        $allData = $this->execReturn("require_once '{$this->file}'; var_export(array(get_declared_interfaces(), get_declared_classes(), get_declared_traits(), get_defined_functions()['user']));");
×
123

124
        return [
×
125
            'interfaces' => array_diff($allData[0], $initialData[0]),
×
126
            'classes'    => array_diff($allData[1], $initialData[1]),
×
127
            'traits'     => array_diff($allData[2], $initialData[2]),
×
128
            'functions'  => array_diff($allData[3], $initialData[3]),
×
129
        ];
×
130
    }
131

132
    /**
133
     * Filter cached classes by options
134
     */
135
    private function filterClassesByOptions(array $cachedClasses, int $options): array
136
    {
137
        $final = [];
11✔
138

139
        if ($options & ClassFinder::ALLOW_CLASSES) {
11✔
140
            $final = array_merge($final, $cachedClasses['classes']);
11✔
141
        }
142
        if ($options & ClassFinder::ALLOW_INTERFACES) {
11✔
143
            $final = array_merge($final, $cachedClasses['interfaces']);
11✔
144
        }
145
        if ($options & ClassFinder::ALLOW_TRAITS) {
11✔
146
            $final = array_merge($final, $cachedClasses['traits']);
11✔
147
        }
148
        if ($options & ClassFinder::ALLOW_FUNCTIONS) {
11✔
149
            $final = array_merge($final, $cachedClasses['functions']);
11✔
150
        }
151

152
        return $final;
11✔
153
    }
154

155
    /**
156
     * Execute PHP code and return returned value
157
     *
158
     * @return mixed
159
     */
160
    private function execReturn(string $script): array
161
    {
162
        $command = $this->php . " -r \"{$script}\"";
×
163
        exec($command, $output, $return);
×
164

165
        if ($return === 0 && ! empty($output)) {
×
166
            $classes = 'return ' . implode('', $output) . ';';
×
167
            $result  = eval($classes);
×
168

169
            return is_array($result) ? $result : [[], [], [], []];
×
170
        }
171

172
        return [[], [], [], []];
×
173
    }
174

175
    /**
176
     * Normalize file path separators
177
     */
178
    private function normalizePath(string $path): string
179
    {
180
        return str_replace('\\', '/', $path);
11✔
181
    }
182
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc