codeigniter4 / CodeIgniter4

Open Source PHP Framework (originally from EllisLab)
https://codeigniter.com/
MIT License
5.4k stars 1.9k forks source link

Error: Argument #2 ($array) must be of type array, null given - Validation.php #8134

Closed xonipatino closed 1 year ago

xonipatino commented 1 year ago

PHP Version

8.2

CodeIgniter4 Version

4.4.3

CodeIgniter4 Installation Method

Composer (using codeigniter4/appstarter)

Which operating systems have you tested for this bug?

Windows

Which server did you use?

cli-server (PHP built-in webserver)

Database

No response

What happened?

CodeIgniter\\Validation\\DotArrayFilter::run(): Argument #2 ($array) must be of type array, null given, called in ...\\vendor\\codeigniter4\\framework\\system\\Validation\\Validation.php on line 203

Steps to Reproduce

I have the next code:


if (
            !$this->validate(
                [
                    'start' => [
                        'label' => 'Inicio',
                        'rules' => 'if_exist|numeric|integer|is_natural',
                    ],
                    'length' => [
                        'label' => 'Limite',
                        'rules' => 'if_exist|numeric|integer|is_natural_no_zero',
                    ],
                    'columns' => [
                        'label' => 'Columnas',
                        'rules' => 'if_exist|valid_json',
                    ],
                    'order' => [
                        'label' => 'Orden',
                        'rules' => 'if_exist|required_with[columns]|valid_json',
                    ],
                    'filters' => [
                        'label' => 'Filtros',
                        'rules' => 'if_exist|valid_json',
                    ],
                ]
            )
        ) {
            $errores = validation_errors();

            return response()->setJSON(
                [
                    'estado' => false,
                    'msje'   => view_cell(
                        'AlertCell',
                        [
                            'tipo'      => 'warning',
                            'contenido' => ul($errores, ['class' => 'mb-0']),
                        ]
                    ),
                    'errores' => $errores,
                    // Propiedad del datatable para respuesta de error
                    'error'   => current($errores),
                ]
            );
        }

and returns:

{
    "title": "TypeError",
    "type": "TypeError",
    "code": 500,
    "message": "CodeIgniter\\Validation\\DotArrayFilter::run(): Argument #2 ($array) must be of type array, null given, called in C:\\Users\\xonip\\OneDrive - ADN ERP S.A.S\\www\\local_html\\macc\\vendor\\codeigniter4\\framework\\system\\Validation\\Validation.php on line 203",
    "file": "C:\\Users\\xonip\\OneDrive - ADN ERP S.A.S\\www\\local_html\\macc\\vendor\\codeigniter4\\framework\\system\\Validation\\DotArrayFilter.php",
    "line": 29,
    "trace": [
        {
            "file": "C:\\Users\\xonip\\OneDrive - ADN ERP S.A.S\\www\\local_html\\macc\\vendor\\codeigniter4\\framework\\system\\Validation\\Validation.php",
            "line": 203,
            "function": "run",
            "class": "CodeIgniter\\Validation\\DotArrayFilter",
            "type": "::"
        },
        {
            "file": "C:\\Users\\xonip\\OneDrive - ADN ERP S.A.S\\www\\local_html\\macc\\vendor\\codeigniter4\\framework\\system\\Controller.php",
            "line": 152,
            "function": "run",
            "class": "CodeIgniter\\Validation\\Validation",
            "type": "->"
        },
        {
            "file": "C:\\Users\\xonip\\OneDrive - ADN ERP S.A.S\\www\\local_html\\macc\\app\\Controllers\\InformacionController.php",
            "line": 33,
            "function": "validate",
            "class": "CodeIgniter\\Controller",
            "type": "->"
        },
        {
            "file": "C:\\Users\\xonip\\OneDrive - ADN ERP S.A.S\\www\\local_html\\macc\\vendor\\codeigniter4\\framework\\system\\CodeIgniter.php",
            "line": 942,
            "function": "list",
            "class": "App\\Controllers\\InformacionController",
            "type": "->"
        },
        {
            "file": "C:\\Users\\xonip\\OneDrive - ADN ERP S.A.S\\www\\local_html\\macc\\vendor\\codeigniter4\\framework\\system\\CodeIgniter.php",
            "line": 502,
            "function": "runController",
            "class": "CodeIgniter\\CodeIgniter",
            "type": "->"
        },
        {
            "file": "C:\\Users\\xonip\\OneDrive - ADN ERP S.A.S\\www\\local_html\\macc\\vendor\\codeigniter4\\framework\\system\\CodeIgniter.php",
            "line": 361,
            "function": "handleRequest",
            "class": "CodeIgniter\\CodeIgniter",
            "type": "->"
        },
        {
            "file": "C:\\Users\\xonip\\OneDrive - ADN ERP S.A.S\\www\\local_html\\macc\\public\\index.php",
            "line": 79,
            "function": "run",
            "class": "CodeIgniter\\CodeIgniter",
            "type": "->"
        },
        {
            "file": "C:\\Users\\xonip\\OneDrive - ADN ERP S.A.S\\www\\local_html\\macc\\vendor\\codeigniter4\\framework\\system\\Commands\\Server\\rewrite.php",
            "line": 47,
            "args": [
                "C:\\Users\\xonip\\OneDrive - ADN ERP S.A.S\\www\\local_html\\macc\\public\\index.php"
            ],
            "function": "require_once"
        }
    ]
}

Expected Output

a JSON of data or a JSON with invalid input information

Anything else?

This was working in CI 4.4.2

xonipatino commented 1 year ago

Hello. I found the solution by changing the following line in the system\Validation\Validation.php file

Line 481:

/** @var IncomingRequest $request */
if (strpos($request->getHeaderLine('Content-Type'), 'application/json') !== false) {
    // $this->data = $request->getJSON(true);
    $this->data = $request->getJSON(true) ?? [];

    return $this;
}
kenjis commented 1 year ago

Thank you for reporting.

The error should not be thrown, but if $request->getJSON(true) returns null, it means the JSON string is invalid. Do you really want the data to be valid?

kenjis commented 1 year ago

This was working in CI 4.4.2

It is strange. Because there is no change in Validation except the exact_length rule. https://github.com/codeigniter4/framework/commit/a0339851ed3fa122a4a0e7930c62a1463e0cb257#diff-291972258304139308a9538a9fb7abfb92c903481df2a0af105dfdf8515cebd3

kenjis commented 1 year ago

@codeigniter4/core-team If we apply the suggested patch, the following test would pass. However, it seems a bit odd that the test would pass. If the result of the validation were to fail, there would be no field to store the error message. Should we throw an exception?

    public function testJsonInputInvalid(): void
    {
        $config          = new App();
        $json            = 'invalid';
        $request         = new IncomingRequest($config, new URI(), $json, new UserAgent());
        $request->setHeader('Content-Type', 'application/json');

        $rules = [
            'role' => 'if_exist|max_length[5]',
        ];
        $result = $this->validation
            ->withRequest($request->withMethod('POST'))
            ->setRules($rules)
            ->run();

        $this->assertTrue($result);
        $this->assertSame([], $this->validation->getErrors());
        $this->assertSame([], $this->validation->getValidated());
    }
xonipatino commented 1 year ago

Thank you for reporting.

The error should not be thrown, but if $request->getJSON(true) returns null, it means the JSON string is invalid. Do you really want the data to be valid?

Hello, I was checking more carefully and I found that if you make a GET request with the header Content-Type = application/json it generates the error, so I deduced that it is illogical to make a GET request with that header, even though the query parameters they are sent by url.

xonipatino commented 1 year ago

Hello. I found the solution by changing the following line in the system\Validation\Validation.php file

Line 481:

/** @var IncomingRequest $request */
if (strpos($request->getHeaderLine('Content-Type'), 'application/json') !== false) {
    // $this->data = $request->getJSON(true);
    $this->data = $request->getJSON(true) ?? [];

    return $this;
}

It is not necessary to modify the line that I had named, it is just knowing how to put the headings for each case. But they should clarify it in the documentation.

AJAX Requests

kenjis commented 1 year ago

@xonipatino I sent PR #8135

MGatner commented 1 year ago

@kenjis does the doc fix make your question above irrelevant? I didn't understand it at first read but I can process to more if it is still relevant.

kenjis commented 1 year ago

@MGatner I must say no? It is just a fix to a incorrect sample code that was pointed out by the reporter of this.

The TypeError issue still remains.

MGatner commented 1 year ago

Okay I have now fully read and understood the issue. Yes, this is a bug in Validation - IncomingRequest::getJson() has no need to return an array, but Validation will only work on an array of data.

In my opinion this feature of Validation is assuming too much from the underlying stack and it should be pulled. We cannot assume a particular API schema nor even RESTful protocols. If things a desirable feature it should be moved somewhere appropriate, to IncomingRequest or even our API trait.