Finishing the Battle
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeHead to the controller. We've got this form flow mastered! Step 1: create a new BattleModel object: $battleModel = new BattleModel():
| // ... lines 1 - 11 | |
| class BattleController extends BaseController | |
| { | |
| /** | |
| * @Route("/api/battles") | |
| * @Method("POST") | |
| */ | |
| public function newAction(Request $request) | |
| { | |
| $battleModel = new BattleModel(); | |
| // ... lines 21 - 34 | |
| } | |
| } |
Step 2: create the form: $form = $this->createForm() with BattleType::class:
| // ... lines 1 - 5 | |
| use AppBundle\Form\BattleType; | |
| // ... lines 7 - 11 | |
| class BattleController extends BaseController | |
| { | |
| // ... lines 14 - 17 | |
| public function newAction(Request $request) | |
| { | |
| $battleModel = new BattleModel(); | |
| $form = $this->createForm(BattleType::class, $battleModel); | |
| // ... lines 22 - 34 | |
| } | |
| } |
On my version of PhpStorm, I need to go back and re-type the e to trigger auto-completion so that the use statement is added above.
For the second argument to createForm: pass it $battleModel.
Step 3: Use $this->processForm():
| // ... lines 1 - 11 | |
| class BattleController extends BaseController | |
| { | |
| // ... lines 14 - 17 | |
| public function newAction(Request $request) | |
| { | |
| $battleModel = new BattleModel(); | |
| $form = $this->createForm(BattleType::class, $battleModel); | |
| $this->processForm($request, $form); | |
| // ... lines 23 - 34 | |
| } | |
| } |
Remember, this is a method we added in BaseController:
| // ... lines 1 - 19 | |
| abstract class BaseController extends Controller | |
| { | |
| // ... lines 22 - 142 | |
| protected function processForm(Request $request, FormInterface $form) | |
| { | |
| $data = json_decode($request->getContent(), true); | |
| if ($data === null) { | |
| $apiProblem = new ApiProblem(400, ApiProblem::TYPE_INVALID_REQUEST_BODY_FORMAT); | |
| throw new ApiProblemException($apiProblem); | |
| } | |
| $clearMissing = $request->getMethod() != 'PATCH'; | |
| $form->submit($data, $clearMissing); | |
| } | |
| // ... lines 155 - 185 | |
| } |
it decodes the Request body and submits it into the form, which is what we do on every endpoint that processes data.
Type-hint the Request argument for the controller and pass this to processForm().
If the form is not valid, we need to send back errors. Use another method from earlier: $this->throwApiProblemValidationException() and pass it the $form object:
| // ... lines 1 - 11 | |
| class BattleController extends BaseController | |
| { | |
| // ... lines 14 - 17 | |
| public function newAction(Request $request) | |
| { | |
| $battleModel = new BattleModel(); | |
| $form = $this->createForm(BattleType::class, $battleModel); | |
| $this->processForm($request, $form); | |
| if (!$form->isValid()) { | |
| $this->throwApiProblemValidationException($form); | |
| } | |
| // ... lines 27 - 34 | |
| } | |
| } |
This will grab the validation errors off and create that response.
At this point, we have a BattleModel object that's populated with the Programmer and Project objects sent in the request. To create the battle, we need to use the BattleManager. Do that with $this->getBattleManager() - that's just a shortcut to get the service - ->battle() and pass it $battleModel->getProgrammer() and $battleModel->getProject():
| // ... lines 1 - 11 | |
| class BattleController extends BaseController | |
| { | |
| // ... lines 14 - 17 | |
| public function newAction(Request $request) | |
| { | |
| $battleModel = new BattleModel(); | |
| $form = $this->createForm(BattleType::class, $battleModel); | |
| $this->processForm($request, $form); | |
| if (!$form->isValid()) { | |
| $this->throwApiProblemValidationException($form); | |
| } | |
| $battle = $this->getBattleManager()->battle( | |
| $battleModel->getProgrammer(), | |
| $battleModel->getProject() | |
| ); | |
| // ... lines 32 - 34 | |
| } | |
| } |
Put a little $battle = in the beginning of all of this to get the new Battle object. Perfect!
Now that the battle has been heroically fought, let's send back the gory details. Use return $this->createApiResponse() and pass it $battle and the 201 status code:
| // ... lines 1 - 11 | |
| class BattleController extends BaseController | |
| { | |
| // ... lines 14 - 17 | |
| public function newAction(Request $request) | |
| { | |
| $battleModel = new BattleModel(); | |
| $form = $this->createForm(BattleType::class, $battleModel); | |
| $this->processForm($request, $form); | |
| if (!$form->isValid()) { | |
| $this->throwApiProblemValidationException($form); | |
| } | |
| $battle = $this->getBattleManager()->battle( | |
| $battleModel->getProgrammer(), | |
| $battleModel->getProject() | |
| ); | |
| // todo - set Location header | |
| return $this->createApiResponse($battle, 201); | |
| } | |
| } |
We aren't setting a Location header yet, so let's at least add a todo for that.
We are done! Controller, model and form: these are the only pieces we need to create a robust endpoint. Try the test:
./vendor/bin/phpunit --filter testPOSTCreateBattle
We are in prime battling shape.
Now, let's complicate things and learn how to really take control of every field in our endpoint. And, learn more about relations.
6 Comments
If anyone is getting an error running this test with the downloaded code, be aware that the jwt_key_pass_phrase needs to be set to happyapi from the last course.
Thanks Agnes! I just updated the
parameters.yml.distfile to use this as the default value... just to help people a bit more. Thanks for the tip on this!Hi,
I'm getting an error, when running `./vendor/bin/phpunit --filter testPOSTCreateBattle`:
There was 1 error:
1) AppBundle\Controller\Api\BattleControllerTest::testPOSTCreateBattle
Doctrine\DBAL\Exception\InvalidFieldNameException: An exception occurred while executing 'INSERT INTO battle_project (name, difficulty_level) VALUES (?, ?)' with params ["my_project", 4]:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'difficulty_level' in 'field list'
the column is certainly there. I have even dropped the database and done the following:
./bin/console doctrine:database:create
./bin/console doctrine:schema:create
and get the same error.
What could be going on?
When I run the query manually, it works fine:
INSERT INTO battle_project (name, difficulty_level) VALUES ("my_project",4)
Sorry, Ryan, I figured it out! I was using the test config (config_test.yml), which had a different database name (suffixed with '_test').
Please delete my comments.
Thank you!
Ha, nice detective work Vlad! It's probably better you figured it out - you won't forget about this in the future :)
"Houston: no signs of life"
Start the conversation!