Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tool - Dynamically Declared Properties Auto Detection #6291

Open
juanluislopez89 opened this issue Aug 13, 2024 · 1 comment
Open

Tool - Dynamically Declared Properties Auto Detection #6291

juanluislopez89 opened this issue Aug 13, 2024 · 1 comment

Comments

@juanluislopez89
Copy link

juanluislopez89 commented Aug 13, 2024

Hi! i Just made this controller to analyze wheter a system class has dynamically declared properties. Ithink this may help to adapt CI3 to the neew PHP 8.3 without using [#AllowDynamicProperties] argument that can be depecated in future versions.

Cannot detect if a property is inherited! so in some cases, there can be false positives.

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Analyze_dynamic_properties extends CI_Controller {

    public function __construct()
    {
        parent::__construct();
    }

    public function index()
    {
        $directory = SYSDIR;
        $dynamicProperties = $this->findDynamicProperties($directory);

        // Renderizar la vista directamente aquí
        echo $this->renderView($dynamicProperties);
    }

    private function findDynamicProperties($dir)
    {
        $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
        $dynamicProperties = [];

        foreach ($files as $file) {
            if ($file->isDir() || $file->getExtension() !== 'php') {
                continue;
            }

            $content = file_get_contents($file->getRealPath());
            $tokens = token_get_all($content);
            $classes = [];
            $currentClass = '';
            $collectingClass = false;
            $declaredProperties = [];

            for ($i = 0; $i < count($tokens); $i++) {
                $token = $tokens[$i];

                if (is_array($token)) {
                    if ($token[0] === T_CLASS) {
                        $collectingClass = true;
                    } elseif ($collectingClass && $token[0] === T_STRING) {
                        $currentClass = $token[1];
                        $classes[$currentClass] = ['file' => $file->getRealPath(), 'properties' => [], 'declared' => []];
                        $collectingClass = false;
                    } elseif ($currentClass && $token[0] === T_VARIABLE && $token[1] === '$this') {
                        $nextToken = $tokens[$i + 1] ?? null;
                        if (is_array($nextToken) && $nextToken[0] === T_OBJECT_OPERATOR) {
                            $propertyToken = $tokens[$i + 2] ?? null;
                            $assignmentToken = $tokens[$i + 4] ?? null;
                            if (is_array($propertyToken) && $propertyToken[0] === T_STRING && $assignmentToken === '=') {
                                $propertyName = $propertyToken[1];
                                if (!in_array($propertyName, $declaredProperties) && !in_array($propertyName, $classes[$currentClass]['declared'])) {
                                    $classes[$currentClass]['properties'][] = [
                                        'name' => $propertyName,
                                        'declared' => false
                                    ];
                                }
                            }
                        }
                    } elseif ($currentClass && $token[0] === T_VARIABLE) {
                        $declaredProperties[] = substr($token[1], 1);
                    } elseif ($currentClass && $token[0] === T_PUBLIC) {
                        $nextToken = $tokens[$i + 2] ?? null;
                        if (is_array($nextToken) && $nextToken[0] === T_VARIABLE) {
                            $declaredPropertyName = substr($nextToken[1], 1);
                            $classes[$currentClass]['declared'][] = $declaredPropertyName;
                            $classes[$currentClass]['properties'][] = [
                                'name' => $declaredPropertyName,
                                'declared' => true
                            ];
                        }
                    }
                }
            }

            foreach ($classes as $className => $data) {
                if (!empty($data['properties'])) {
                    $dynamicProperties[$className]['file'] = $data['file'];
                    $dynamicProperties[$className]['properties'] = $data['properties'];
                }
            }
        }

        return $dynamicProperties;
    }

    private function renderView($dynamicProperties)
    {
        ob_start();
        ?>
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>PHP 8 Compatibility Analysis</title>
            <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
            <style>
                .table td.success {
                    background-color: #dff0d8;
                }
                .table td.danger {
                    background-color: #f2dede;
                }
            </style>
        </head>
        <body>
        <div class="container">
            <h1>PHP 8 Compatibility Analysis</h1><hr>
            <div class="btn-group" role="group" aria-label="Filter Buttons">
                <button type="button" class="btn btn-success" onclick="filterClasses('good')">Good Classes</button>
                <button type="button" class="btn btn-danger" onclick="filterClasses('bad')">Wrong Classes</button>
                <button type="button" class="btn btn-primary" onclick="filterClasses('all')">All Classes</button>
            </div>
            <hr>
            <table class="table table-bordered table-striped" id="resultsTable">
                <thead>
                <tr>
                    <th>Class</th>
                    <th>File</th>
                    <th>Property</th>
                    <th>Status</th>
                </tr>
                </thead>
                <tbody>
                <?php foreach ($dynamicProperties as $className => $data): ?>
                    <?php 
                    $classStatus = 'good'; 
                    foreach ($data['properties'] as $property): 
                        if (!$property['declared']) {
                            $classStatus = 'bad';
                            break;
                        }
                    endforeach;
                    ?>
                    <?php foreach ($data['properties'] as $index => $property): ?>
                    <tr class="<?= $classStatus ?>">
                        <?php if ($index === 0): ?>
                            <td rowspan="<?= count($data['properties']) ?>"><?= $className ?></td>
                            <td rowspan="<?= count($data['properties']) ?>"><?= $data['file'] ?></td>
                        <?php endif; ?>
                        <td class="<?= $property['declared'] ? 'success' : 'danger' ?>"><?= $property['name'] ?></td>
                        <td class="<?= $property['declared'] ? 'success' : 'danger' ?>">
                            <strong class="text-<?= $property['declared'] ? 'success' : 'danger' ?>"><?= $property['declared'] ? 'Declared =)' : 'Not Declared !!!' ?></strong>
                        </td>
                    </tr>
                    <?php endforeach; ?>
                <?php endforeach; ?>
                </tbody>
            </table>
        </div>

        <script>
            function filterClasses(status) {
                var rows = document.querySelectorAll('#resultsTable tbody tr');
                rows.forEach(function (row) {
                    if (status === 'all') {
                        row.style.display = '';
                    } else if (status === 'good' && row.classList.contains('good')) {
                        row.style.display = '';
                    } else if (status === 'bad' && row.classList.contains('bad')) {
                        row.style.display = '';
                    } else {
                        row.style.display = 'none';
                    }
                });
            }
        </script>
        </body>
        </html>
        <?php
        return ob_get_clean();
    }
}
`
@daveherman71
Copy link

Sadly this doesn't work and reports many false positives.

For example in the attached image:

image

CI_Session_redis_driver extends CI_Session_driver and all of the properties listed as not being declared are in fact declared in the CI_Session_driver class and therefore are not dynamically declared.

I like the idea but the code needs work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants