gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
In the Controller/Api
directory, create a new BattleController
. Make it extend the same BaseController
as before: we've put a lot of shortcuts in this:
... lines 1 - 2 | |
namespace AppBundle\Controller\Api; | |
use AppBundle\Controller\BaseController; | |
... lines 6 - 8 | |
class BattleController extends BaseController | |
{ | |
... lines 11 - 17 | |
} |
Then, add public function newAction()
. Set the route above it with @Route
- make sure you hit tab to autocomplete this: it adds the necessary use
statement. Finish the URL: /api/battles
. Do the same thing with @Method
to restrict this to POST
:
... lines 1 - 5 | |
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; | |
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; | |
class BattleController extends BaseController | |
{ | |
/** | |
* @Route("/api/battles") | |
* @Method("POST") | |
*/ | |
public function newAction() | |
{ | |
} | |
} |
Awesome! Our API processes input through a form - you can see that in ProgrammerController
:
... lines 1 - 22 | |
class ProgrammerController extends BaseController | |
{ | |
/** | |
* @Route("/api/programmers") | |
* @Method("POST") | |
*/ | |
public function newAction(Request $request) | |
{ | |
$programmer = new Programmer(); | |
$form = $this->createForm(ProgrammerType::class, $programmer); | |
$this->processForm($request, $form); | |
... lines 34 - 52 | |
} | |
... lines 54 - 149 | |
} |
This form modifies the Programmer
entity directly and we save it. Simple.
Well, not so simple in this case. What? I know, I like to make things as difficult as possible.
To create battles on the frontend, our controller uses a service class called BattleManager
. It's kind of nice: it has a battle()
function:
... lines 1 - 9 | |
class BattleManager | |
{ | |
... lines 12 - 18 | |
/** | |
* Creates and wages an epic battle | |
* | |
* @param Programmer $programmer | |
* @param Project $project | |
* @return Battle | |
*/ | |
public function battle(Programmer $programmer, Project $project) | |
{ | |
... lines 28 - 53 | |
} | |
} |
We pass it a Programmer
and Project
and it takes care of all of the logic for creating a Battle
, figuring out who won, and saving it to the database. We even gave Battle
a __construct()
function with two required arguments:
... lines 1 - 10 | |
class Battle | |
{ | |
... lines 13 - 46 | |
/** | |
* Battle constructor. | |
* @param $programmer | |
* @param $project | |
*/ | |
public function __construct(Programmer $programmer, Project $project) | |
{ | |
$this->programmer = $programmer; | |
$this->project = $project; | |
$this->foughtAt = new \DateTime(); | |
} | |
... lines 58 - 105 | |
} |
This is a really nice setup, so I don't want to change it. But, it doesn't work well with the form system: it prefers to instantiate the object and use setter functions.
Tip
Actually, it is possible to use the form system with the Battle
entity by taking
advantage of data mappers.
But that's ok! In fact, I like this complication: it shows off a very nice workaround. Just create a new model class for the form. In fact, I recommend this whenever you have a form that stops looking like or working nicely with your entity class.
In the Form
directory, create a Model
directory to keep things organized. Inside, add a new class called BattleModel
:
... lines 1 - 2 | |
namespace AppBundle\Form\Model; | |
... lines 4 - 7 | |
class BattleModel | |
{ | |
... lines 10 - 32 | |
} |
Give it the two properties we're expecting as API input: $project
and $programmer
. Hit command
+N
- or go to the "Code"->"Generate" menu - and generate the getter and setter methods for both properties:
... lines 1 - 4 | |
use AppBundle\Entity\Programmer; | |
use AppBundle\Entity\Project; | |
class BattleModel | |
{ | |
private $project; | |
private $programmer; | |
public function getProject() | |
{ | |
return $this->project; | |
} | |
public function setProject(Project $project) | |
{ | |
$this->project = $project; | |
} | |
public function getProgrammer() | |
{ | |
return $this->programmer; | |
} | |
public function setProgrammer(Programmer $programmer) | |
{ | |
$this->programmer = $programmer; | |
} | |
} |
To be extra safe and make your code more hipster, type-hint setProgrammer()
with the Programmer
class and setProject()
with Project
. The form system will love this class.
In the Form
directory, create a new class for the form: BattleType
. Make this extend the normal AbstractType
and then hit command
+N
- or "Code"->"Generate" - and go to "Override Methods". Select the two we need: buildForm
and configureOptions
:
... lines 1 - 2 | |
namespace AppBundle\Form; | |
... lines 5 - 6 | |
use Symfony\Component\Form\AbstractType; | |
use Symfony\Component\Form\FormBuilderInterface; | |
use Symfony\Component\OptionsResolver\OptionsResolver; | |
class BattleType extends AbstractType | |
{ | |
public function buildForm(FormBuilderInterface $builder, array $options) | |
{ | |
... lines 15 - 22 | |
} | |
public function configureOptions(OptionsResolver $resolver) | |
{ | |
... lines 27 - 30 | |
} | |
} |
Take out the parent calls - the parent methods are empty.
Okay, let's think about this. The API client will send programmer
and project
fields and both will be ids. Ultimately, we want to turn those into the entity objects corresponding to those ids before setting the data on the BattleModel
object.
Well, this is exactly what the Entity
type does. Use $builder->add()
with project
set to EntityType::class
. To tell it what entity to use, add a class
option set to AppBundle\Entity\Project
:
... lines 1 - 5 | |
use Symfony\Bridge\Doctrine\Form\Type\EntityType; | |
... lines 7 - 10 | |
class BattleType extends AbstractType | |
{ | |
public function buildForm(FormBuilderInterface $builder, array $options) | |
{ | |
$builder | |
->add('programmer', EntityType::class, [ | |
'class' => 'AppBundle\Entity\Programmer' | |
]) | |
... lines 19 - 21 | |
; | |
} | |
... lines 24 - 31 | |
} |
Do the same for programmer
and set its class to AppBundle\Entity\Programmer
:
... lines 1 - 10 | |
class BattleType extends AbstractType | |
{ | |
public function buildForm(FormBuilderInterface $builder, array $options) | |
{ | |
$builder | |
->add('programmer', EntityType::class, [ | |
'class' => 'AppBundle\Entity\Programmer' | |
]) | |
->add('project', EntityType::class, [ | |
'class' => 'AppBundle\Entity\Project' | |
]) | |
; | |
} | |
... lines 24 - 31 | |
} |
In a web form, the entity type renders as a drop-down of projects or programmers. But it's perfect for an API: it transforms the project id into a Project object by querying for it. That's exactly what we want.
In configureOptions()
, add $resolver->setDefaults()
and pass it two things: first the data_class
set to BattleModel::class
:
... lines 1 - 4 | |
use AppBundle\Form\Model\BattleModel; | |
... lines 6 - 10 | |
class BattleType extends AbstractType | |
{ | |
... lines 13 - 24 | |
public function configureOptions(OptionsResolver $resolver) | |
{ | |
$resolver->setDefaults([ | |
'data_class' => BattleModel::class, | |
... line 29 | |
]); | |
} | |
} |
Make sure PhpStorm adds the use
statement for that class. Then, set csrf_protection
to false
because we can't use normal CSRF protection in an API:
... lines 1 - 10 | |
class BattleType extends AbstractType | |
{ | |
... lines 13 - 24 | |
public function configureOptions(OptionsResolver $resolver) | |
{ | |
$resolver->setDefaults([ | |
'data_class' => BattleModel::class, | |
'csrf_protection' => false, | |
]); | |
} | |
} |
Form, ready! Now let's hit the controller.
Hey mehdi
Sorry for the confusion but Ryan meant that it would return an array of Project objects
Cheers!
Hello,
I read that we should avoid in 90% caces Entity Form type because we will have usually hundreds of entities and app will work slowly (Iin this case api). What we can do in API if we have 500 Programmers and 200 Projects. How to optimize app (api)?
Hey again!
Hmm, I would wait for it to be a problem, if you start seeing that your API response slowly, then you can hunt down which part is the bottle neck and look for an optimization (a query, a process, etc)
Cheers!
One question about flush().
For example:
If we have controller which calls 3 services (every create new collection of objects), so we will need to call ->persist($entity) method inside foreach() in every service. But where to call flush()? Only once in controller before return Response() or in every servise - so this is 3 times.
I am not sure whether is good idea that service only persist objects and controllers call flush()?
Hey axa!
It depends, as always, you can call flush more than once in a request, is not a big deal, but if you are going to do it, is better if you have a reason. In your case, I would consider making every service to call flush so you can save your work in case that another service fails, or maybe you want all the opposite and only flush if everything worked as spected
Cheers!
// composer.json
{
"require": {
"php": ">=5.5.9",
"symfony/symfony": "3.0.*", // v3.0.3
"doctrine/orm": "^2.5", // v2.5.4
"doctrine/doctrine-bundle": "^1.6", // 1.6.2
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
"symfony/swiftmailer-bundle": "^2.3", // v2.3.11
"symfony/monolog-bundle": "^2.8", // v2.10.0
"sensio/distribution-bundle": "^5.0", // v5.0.4
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.14
"incenteev/composer-parameter-handler": "~2.0", // v2.1.2
"jms/serializer-bundle": "^1.1.0", // 1.1.0
"white-october/pagerfanta-bundle": "^1.0", // v1.0.5
"lexik/jwt-authentication-bundle": "^1.4", // v1.4.3
"willdurand/hateoas-bundle": "^1.1" // 1.1.1
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.0.6
"symfony/phpunit-bridge": "^3.0", // v3.0.3
"behat/behat": "~3.1@dev", // dev-master
"behat/mink-extension": "~2.2.0", // v2.2
"behat/mink-goutte-driver": "~1.2.0", // v1.2.1
"behat/mink-selenium2-driver": "~1.3.0", // v1.3.1
"phpunit/phpunit": "~4.6.0", // 4.6.10
"doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
}
}
Hello,
Why EntityType in web forms render a list of projects , and in Api world it renders just a project ?