Skip to content

Document how to create a custom type guesser #3597

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

Merged
merged 3 commits into from
Mar 9, 2014
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components/form/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
:maxdepth: 2

introduction
type_guesser
169 changes: 169 additions & 0 deletions components/form/type_guesser.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
.. index::
single: Forms; Custom Type Guesser

Creating a Custom Type Guesser
==============================

The Form component can guess the type and some options of a form field by
using type guessers. The component already includes a type guesser using the
assertions of the Validation component, but you can also add your own custom
type guessers.

.. sidebar:: Form Type Guessers in the Bridges

Symfony also provides some form type guessers in the bridges. These can be
used if you use that library.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd remove the second sentence entirely - it doesn't sound right and I don't think it adds anything


* :class:`Symfony\\Bridge\\Propel1\\Form\\PropelTypeGuesser` provided by
the Propel1 bridge;
* :class:`Symfony\\Bridge\\Doctrine\\Form\\DoctrineOrmTypeGuesser`
provided by the Doctrine bridge.

A PHPDoc Type Guesser
---------------------

In this section, you are going to build a PHPDoc type guesser. At first, you
need to create a class which extends
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wich implements

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

AND, how about we change the header to "Create a PHPDoc Type Guesser" and then remove the first sentence (or replace it with something describing what this means - e.g. "In this section, you are going to build a guesser that reads information about fields from the PHPDoc above the properties").

:class:`Symfony\\Component\\Form\\FormTypeGuesserInterface`. This interface
requires 4 methods:

* :method:`Symfony\\Component\\Form\\FormTypeGuesserInterface::guessType` -
tries to guess the type of a field;
* :method:`Symfony\\Component\\Form\\FormTypeGuesserInterface::guessRequired` -
tries to guess the value of the ``required`` option;
* :method:`Symfony\\Component\\Form\\FormTypeGuesserInterface::guessMaxLength` -
tries to guess the value of the ``max_length`` option;
* :method:`Symfony\\Component\\Form\\FormTypeGuesserInterface::guessPattern` -
tries to guess the value of the ``pattern`` option.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could link the option to their descriptions in the reference docs


The most basic class looks like::
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this better as a command so that it's clear that this is step 1:

Start by creating the class and these methods. Next, you'll learn how to fill each on.


use Symfony\Component\Form\FormTypeGuesserInterface;

class PHPDocTypeGuesser implements FormTypeGuesserInterface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer PhpDocTypeGuesser.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, either actually, since it does appear to be called PHPDoc

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, uppercasing letters is a big CS problem if you ask me :) Doctrine always use things like ORM and not Orm in their classes/namespaces, but the CMF has switched to Orm (Phpcr actually...). So I'll change it to PhpDoc to make you happy :P

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to change, but I will leave it as it is. Since it should be PhpdocTypeGuesser or PHPDocTypeGuesser and then I like the one using the official notation more.

{
public function guessType($class, $property)
{
}

public function guessRequired($class, $property)
{
}

public function guessMaxLength($class, $property)
{
}

public function guessPattern($class, $property)
{
}
}

Guessing the Type
~~~~~~~~~~~~~~~~~

When guessing a type, the method returns either an instance of
:class:`Symfony\\Component\\Form\\Guess\\TypeGuess` or nothing, to determine
that the type guesser cannot guess the type.

The ``TypeGuess`` constructor requires 3 options:

* The type name (one of the :doc:`form types </reference/forms/types`);
* Additionally options (for instance, when the type is ``entity``, you also
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional

want to set the ``class`` option). If no types are guessed, this should be
set to an empty array;
* The confidence that the guessed type is correct. This can be one of the
constants of the :class:`Symfony\\Component\\Form\\Guess\Guess` class:
``LOW_CONFIDENCE``, ``MEDIUM_CONFIDENCE``, ``HIGH_CONFIDENCE``,
``VERY_HIGH_CONFIDENCE``. After all type guessers are executed, the type
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have been executed

with the highest confidence is used.

With this knowledge, you can easily implement the ``guessType`` method of the
``PHPDocTypeGuesser``::

use Symfony\Component\Form\Guess\Guess;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it was good to add a namespace declaration

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean? It's not inside a class and I've just copied the example from the introduction chapter

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading the article I had the understanding that this is a part of the PHPDocTypeGuesser class.

Maybe it's more clear if you wrap the method inside a class.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I thought you commented on another code example, I'll include the namespace here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, this is part of the PHPDocTypeGuesser class, @xabbuh

use Symfony\Component\Form\Guess\TypeGuess;

// ...
public function guessType($class, $property)
{
$annotations = $this->readPhpDocAnnotations($class, $property);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're going to at least need to make a mention that we're leaving the implementation of this function to the user, and maybe a clue on how it might work (because after all, the example you chose for this entry is actually pretty awesome and real-world).


if (!isset($annotations['var'])) {
return; // guess nothing if the @var annotation is not available
}

// otherwise, base the type on the @var annotation
switch ($annotations['var']) {
case 'string':
// there is a high confidence that the type is a string when
// @var string is used
return new TypeGuess('text', array(), Guess::HIGH_CONFIDENCE);

case 'int':
case 'integer':
// integers can also be the id of an entity or a checkbox (0 or 1)
return new TypeGuess('integer', array(), Guess::MEDIUM_CONFIDENCE);

case 'float':
case 'double':
case 'real':
return new TypeGuess('number', array(), Guess::MEDIUM_CONFIDENCE);

case 'boolean':
case 'bool':
return new TypeGuess('checkbox', array(), Guess::HIGH_CONFIDENCE);

default:
// there is a very low confidence that this one is correct
return new TypeGuess('text', array(), Guess::LOW_CONFIDENCE);
}
}

This type guesser can now guess the field type for a property if it has
PHPdoc!

Guessing Field Options
~~~~~~~~~~~~~~~~~~~~~~

The other 3 methods (``guessMaxLength``, ``guessRequired`` and
``guessPattern``) return a :class:`Symfony\\Component\\Form\\Guess\\ValueGuess`
instance with the value of the option. This constructor has 2 arguments:

* The value of the option;
* The confidence that the guessed value is correct (using the constants of the
``Guess`` class).

``null`` is guessed when you believe the value of the option should not be
set.

.. caution::

You should be very careful with the ``guessPattern`` method. When the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[...] be careful using the [...]

type is a float, you cannot use it to determine a min or max value of the
float (e.g. you want a float to be greater than ``5``, ``4.512313`` is not valid
but ``length(4.512314) > length(5)`` is, so the pattern will success). In
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[...] so the pattern will succeed.

this case, the value should be set to ``null`` with a ``MEDIUM_CONFIDENCE``.

Registering a Type Guesser
--------------------------

The last thing you need to do is registering your custom type guesser by using
:method:`Symfony\\Component\\Form\\FormFactoryBuilder::addTypeGuesser` or
:method:`Symfony\\Component\\Form\\FormFactoryBuilder::addTypeGuessers`::

use Symfony\Component\Form\Forms;
use Acme\Form\PHPDocTypeGuesser;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move the type guesser into a Guess subnamespace.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The symfony convention is to put them directly into the Form namespace


$formFactory = Forms::createFormFactoryBuilder()
// ...
->addTypeGuesser(new PHPDocTypeGuesser())
->getFormFactory();

// ...

.. note::

When you use the full stack framework, you need to register your type
guesser and tag it with ``form.type_guesser``. For more information see
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

guesser as a service

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, maybe we should be saying "If you're using the Symfony Framework" - calling it "Symfony Framework" instead of "full stack framework"

:ref:`the tag reference <reference-dic-type_guesser>`.
1 change: 1 addition & 0 deletions components/map.rst.inc
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
* :doc:`/components/form/index`

* :doc:`/components/form/introduction`
* :doc:`/components/form/type_guesser`

* :doc:`/components/http_foundation/index`

Expand Down
13 changes: 7 additions & 6 deletions reference/dic_tags.rst
Original file line number Diff line number Diff line change
Expand Up @@ -332,21 +332,22 @@ The ``alias`` key of the tag is the type of field that this extension should
be applied to. For example, to apply the extension to any form/field, use the
"form" value.

.. _reference-dic-type_guesser:

form.type_guesser
-----------------

**Purpose**: Add your own logic for "form type guessing"

This tag allows you to add your own logic to the :ref:`Form Guessing <book-forms-field-guessing>`
process. By default, form guessing is done by "guessers" based on the validation
metadata and Doctrine metadata (if you're using Doctrine).
metadata and Doctrine metadata (if you're using Doctrine) or Propel metadata
(if you're using Propel).

To add your own form type guesser, create a class that implements the
:class:`Symfony\\Component\\Form\\FormTypeGuesserInterface` interface. Next,
tag its service definition with ``form.type_guesser`` (it has no options).
.. seelalso::

To see an example of how this class might look, see the ``ValidatorTypeGuesser``
class in the Form component.
For information on how to create your own type guesser, see
:doc:`/components/form/type_guesser`.

kernel.cache_clearer
--------------------
Expand Down