phpstan / phpstan-nette

Nette Framework class reflection extension for PHPStan & framework-specific rules
MIT License
100 stars 35 forks source link

Call to undefined method Nette\Forms\Control #115

Closed IJVo closed 1 year ago

IJVo commented 1 year ago

Since version 1.2.2 it reports these errors:

Call to an undefined method Nette\Forms\Control::getOption(). Call to an undefined method Nette\Forms\Control::getControlPrototype(). Call to an undefined method Nette\Forms\Control::getContainerPrototype(). Call to an undefined method Nette\Forms\Control::getItemLabelPrototype().

ondrejmirtes commented 1 year ago

Please show the piece of code that reports this.

/cc @lulco

petrparolek commented 1 year ago
<?php declare(strict_types = 1);

namespace App\Components\Forms;

use Nette;
use Nette\Application\UI\Form;
use Nette\Forms\Rendering\DefaultFormRenderer;

final class FormFactory
{

    public function create(): Form
    {
        return new Form();
    }

    public static function makeBootstrap4(Form $form): void
    {
        /** @var DefaultFormRenderer $renderer */
        $renderer = $form->getRenderer();
        $renderer->wrappers['controls']['container'] = null;
        $renderer->wrappers['pair']['container'] = 'div class="form-group"';
        $renderer->wrappers['pair']['.error'] = 'has-danger';
        $renderer->wrappers['control']['container'] = '';
        $renderer->wrappers['label']['container'] = '';
        $renderer->wrappers['control']['description'] = 'span class=form-text';
        $renderer->wrappers['control']['errorcontainer'] = 'span class=form-control-feedback';
        $renderer->wrappers['control']['.error'] = 'is-invalid';

        $usedPrimary = false;

        foreach ($form->getControls() as $control) {
            $type = $control->getOption('type');

            if ($type === 'button') {
                $control->getControlPrototype()->addClass($usedPrimary === false ? 'btn btn-primary' : 'btn btn-secondary');
                $usedPrimary = true;

            } elseif (in_array($type, ['text', 'textarea', 'select'], true)) {
                $control->getControlPrototype()->addClass('form-control');

            } elseif ($type === 'file') {
                $control->getControlPrototype()->addClass('form-control-file');

            } elseif (in_array($type, ['checkbox', 'radio'], true)) {
                if ($control instanceof Nette\Forms\Controls\Checkbox) {
                    $control->getLabelPrototype()->addClass('form-check-label');
                } else {
                    $control->getItemLabelPrototype()->addClass('form-check-label');
                }

                $control->getControlPrototype()->addClass('form-check-input');
                $control->getContainerPrototype()->setName('div')->addClass('form-check');
            }
        }
    }

}

 ------ --------------------------------------------------------------------------- 
  Line   app/Components/Forms/FormFactory.php                               
 ------ --------------------------------------------------------------------------- 
  33     Call to an undefined method Nette\Forms\Control::getOption().              
  36     Call to an undefined method Nette\Forms\Control::getControlPrototype().    
  40     Call to an undefined method Nette\Forms\Control::getControlPrototype().    
  43     Call to an undefined method Nette\Forms\Control::getControlPrototype().    
  49     Call to an undefined method Nette\Forms\Control::getItemLabelPrototype().  
  52     Call to an undefined method Nette\Forms\Control::getControlPrototype().    
  53     Call to an undefined method Nette\Forms\Control::getContainerPrototype().  
 ------ --------------------------------------------------------------------------- 

see https://github.com/nette/forms/blob/v3.1.10/examples/bootstrap4-rendering.php

lulco commented 1 year ago

Well, it's correct.

You can add any Control to your Form. Your code use wrong assumption that all controls are BaseControl (I guess).

What happens if you create some custom control (implementung Control interface) and use this renderer? It will fail because method getOption() will not be implemented.

petrparolek commented 1 year ago

ping @dg

ondrejmirtes commented 1 year ago

I'm reverting the change for now. It was more precise, yes, but the practical value was questionable. The marked code was most likely fine but static analysis can't understand that.

lulco commented 1 year ago

I thought that's what are stubs about - to be more precise. Now the type of Control is mixed... I understand that change cause some errors in wild, but it just did what phpstan do - protect users from errors.

Potential error is still there, it just isn't reported by phpstan.

Marked code would be correct (and analysed without errors) if instanceof would be used instead of comparing $type

petrparolek commented 1 year ago

@lulco How should code of FormFactory kook?

lulco commented 1 year ago

Like I said:

foreach ($form->getControls() as $control) {
    if ($control instanceof \Nette\Forms\Controls\Button) {
        $control->getControlPrototype()->addClass($usedPrimary === false ? 'btn btn-primary' : 'btn btn-secondary');
        $usedPrimary = true;
    }
    // ...
}
github-actions[bot] commented 1 year ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.