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

[RFC] Improve type inference for getData() #238

Closed
gsteel opened this issue Jul 17, 2023 · 3 comments · Fixed by #248
Closed

[RFC] Improve type inference for getData() #238

gsteel opened this issue Jul 17, 2023 · 3 comments · Fixed by #248
Assignees
Milestone

Comments

@gsteel
Copy link
Member

gsteel commented Jul 17, 2023

Feature Request

Q A
New Feature yes
RFC yes
BC Break no

Summary

With laminas/laminas-inputfilter#91 landing in input-filter, it'll be easier to annotate forms with something like:

/**
 * @psalm-type ValidPayload = array{
 *     field1: int<1, 365>,
 *     field2: non-empty-string,
 *     field3: list<non-empty-string>,
 * }
 * @extends Form<ValidPayload>
 */
final class MyForm extends Form
{
}

It's been a while since I used form for anything other than regular associative arrays, but given forms can be bound to objects too - it's also feasible that forms could be annotated to return models too, such as:

/** @extends Form<SomeModel> */
final class MyForm extends Form
{
}

The patch in InputFilter helps because users will be able to do something like the following:

/**
 * @psalm-import-type ValidPayload from MyInputFilter
 * @extends Form<ValidPayload>
 */
final class MyForm extends Form
{
    public function __construct(MyInputFilter $inputFilter)
    {
        parent::__construct();
        $this->setInputFilter($inputFilter);
    }
}
@Ocramius
Copy link
Member

/** @extends Form<SomeModel> */

Very cool and elegant 💪

@gsteel
Copy link
Member Author

gsteel commented Nov 23, 2023

Hopefully the psalm bot is working here. This is what I have in mind:

https://psalm.dev/r/0d6f0ee752

Copy link

I found these snippets:

https://psalm.dev/r/0d6f0ee752
<?php

/**
 * @template TValues
 */
interface Filter {
    /** @return TValues */
    public function getValues(): array;
}

/**
 * @psalm-type MyPayload = array{
 *    foo: non-empty-string,
 * }
 * @implements Filter<MyPayload>
 */
class MyFilter implements Filter {
    
    public function getValues(): array
    {
        return ['foo' => 'bar'];
    }
}

/** @template TFilterShape */
interface Form
{
    /** @return TFilterShape */
    public function getData(): array;
    
    /** @param Filter<TFilterShape> $filter **/
    public function setFilter(Filter $filter): void;
}

/**
 * @template TFilterShape
 * @implements Form<TFilterShape>
 */
abstract class BaseForm implements Form
{
    /** @var Filter<TFilterShape> */
    private Filter|null $filter = null;
    
    /** @return TFilterShape */
    public function getData(): array
    {
        if (! $this->filter) { throw new Exception('Bad News'); }
        
        return $this->filter->getValues();
    }
    
    /** @param Filter<TFilterShape> $filter **/
    public function setFilter(Filter $filter): void
    {
        $this->filter = $filter;
    }
}

/**
 * @psalm-import-type MyPayload from MyFilter
 * @extends BaseForm<MyPayload>
 */
class MyForm extends BaseForm
{
}

$form = new MyForm();
$form->setFilter(new MyFilter());

$foo = $form->getData()['foo'];

/** @psalm-trace $foo */
Psalm output (using commit baa6d5d):

INFO: Trace - 72:25 - $foo: non-empty-string

INFO: UnusedVariable - 70:1 - $foo is never referenced or the value is not used

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

Successfully merging a pull request may close this issue.

2 participants