phpstan/phpstan-nette

Call to undefined method Nette\Forms\Control

IJVo opened this issue · 9 comments

IJVo commented

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().

Please show the piece of code that reports this.

/cc @lulco

<?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

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.

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

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

@lulco How should code of FormFactory kook?

lulco commented

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;
    }
    // ...
}

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.