Healthcheck [FOSRESTBundle]
We have our Symfony 4 project up and running, and we've added all the dependencies needed for FOSRESTBundle to function on a basic level. As it stands, our functionality is still limited, and we'll need to add in various extra code and configuration to make things behave as we require.
If you haven't already done so, start your web server. I'm using the Symfony web server, but you can use the built in PHP server if you'd prefer:
bin/console server:start
[OK] Server listening on http://127.0.0.1:8000
To begin with, we're going to run our Behat test suite, specifically the healthcheck.feature
:
vendor/bin/behat features/healthcheck.feature
Feature: To ensure the API is responding in a simple manner
In order to offer a working product
As a conscientious software developer
I need to ensure my JSON API is functioning
Scenario: Basic healthcheck # features/healthcheck.feature:8
Given I request "/ping" using HTTP GET # Imbo\BehatApiExtension\Context\ApiContext::requestPath()
Then the response code is 200 # Imbo\BehatApiExtension\Context\ApiContext::assertResponseCodeIs()
Expected response code 200, got 404. (Imbo\BehatApiExtension\Exception\AssertionFailedException)
And the response body is: # Imbo\BehatApiExtension\Context\ApiContext::assertResponseBodyIs()
"""
"pong"
"""
--- Failed scenarios:
features/healthcheck.feature:8
1 scenario (1 failed)
3 steps (1 passed, 1 failed, 1 skipped)
0m0.26s (9.97Mb)
This is a good start.
Our server is being hit, but the /ping
endpoint is not yet a real thing. We need to implement that.
We can cheat entirely here and copy paste from the Symfony 4 standalone JSON API project:
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class HealthcheckController extends Controller
{
/**
* @Route("/ping", name="healthcheck")
*/
public function index()
{
return new JsonResponse('pong');
}
}
Check the router:
bin/console debug:router
-------------------------- -------- -------- ------ -----------------------------------
Name Method Scheme Host Path
-------------------------- -------- -------- ------ -----------------------------------
healthcheck ANY ANY ANY /ping
_twig_error_test ANY ANY ANY /_error/{code}.{_format}
_wdt ANY ANY ANY /_wdt/{token}
_profiler_home ANY ANY ANY /_profiler/
_profiler_search ANY ANY ANY /_profiler/search
_profiler_search_bar ANY ANY ANY /_profiler/search_bar
_profiler_phpinfo ANY ANY ANY /_profiler/phpinfo
_profiler_search_results ANY ANY ANY /_profiler/{token}/search/results
_profiler_open_file ANY ANY ANY /_profiler/open
_profiler ANY ANY ANY /_profiler/{token}
_profiler_router ANY ANY ANY /_profiler/{token}/router
_profiler_exception ANY ANY ANY /_profiler/{token}/exception
_profiler_exception_css ANY ANY ANY /_profiler/{token}/exception.css
-------------------------- -------- -------- ------ -----------------------------------
Yup, it's there, and:
vendor/bin/behat features/healthcheck.feature
Feature: To ensure the API is responding in a simple manner
In order to offer a working product
As a conscientious software developer
I need to ensure my JSON API is functioning
Scenario: Basic healthcheck # features/healthcheck.feature:8
Given I request "/ping" using HTTP GET # Imbo\BehatApiExtension\Context\ApiContext::requestPath()
Then the response code is 200 # Imbo\BehatApiExtension\Context\ApiContext::assertResponseCodeIs()
And the response body is: # Imbo\BehatApiExtension\Context\ApiContext::assertResponseBodyIs()
"""
"pong"
"""
1 scenario (1 passed)
3 steps (3 passed)
0m0.48s (9.59Mb)
Are we done?
Yes, and no.
This works. That's good enough.
But it's not using FOSRESTBundle.
Let's change the implementation a little. Please note, you really do not need to do this. We've just seen that this works, and passes.
FOSRESTBundle Changes
<?php
namespace App\Controller;
+use FOS\RestBundle\Controller\FOSRestController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
-use Symfony\Bundle\FrameworkBundle\Controller\Controller;
-class HealthcheckController extends Controller
+class HealthcheckController extends FOSRestController
{
/**
* @Route("/ping", name="healthcheck")
*/
public function get()
{
return new JsonResponse('pong');
}
}
Firstly, I need to highlight that whilst Symfony 4 controllers allow route naming without the Action
suffix (get
vs getAction
), we must (at the time of recording) still use the Action
suffix in FOSRESTBundle controllers:
bin/console debug:router
In HealthcheckController.php line 9:
Warning: Declaration of
App\Controller\HealthcheckController::get()
should be compatible with
Symfony\Bundle\FrameworkBundle\Controller\Controller::get(string $id)
Therefore:
<?php
namespace App\Controller;
use FOS\RestBundle\Controller\FOSRestController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
class HealthcheckController extends FOSRestController
{
/**
* @Route("/ping", name="healthcheck")
*/
- public function get()
+ public function getAction()
{
return new JsonResponse('pong');
}
}
Again, at this point we have a passing test:
vendor/bin/behat features/healthcheck.feature
Feature: To ensure the API is responding in a simple manner
In order to offer a working product
As a conscientious software developer
I need to ensure my JSON API is functioning
Scenario: Basic healthcheck # features/healthcheck.feature:8
Given I request "/ping" using HTTP GET # Imbo\BehatApiExtension\Context\ApiContext::requestPath()
Then the response code is 200 # Imbo\BehatApiExtension\Context\ApiContext::assertResponseCodeIs()
And the response body is: # Imbo\BehatApiExtension\Context\ApiContext::assertResponseBodyIs()
"""
"pong"
"""
1 scenario (1 passed)
3 steps (3 passed)
0m0.07s (9.59Mb)
We still haven't locked down our route though:
bin/console debug:router
-------------------------- -------- -------- ------ -----------------------------------
Name Method Scheme Host Path
-------------------------- -------- -------- ------ -----------------------------------
healthcheck ANY ANY ANY /ping
We could use the methods
option of the Routes
annotation:
* @Route(
* "/ping",
* name="healthcheck",
* methods={"GET"}
* )
But FOSRESTBundle provides a more specific annotation for this purpose:
<?php
namespace App\Controller;
use FOS\RestBundle\Controller\FOSRestController;
use Symfony\Component\HttpFoundation\JsonResponse;
-use Symfony\Component\Routing\Annotation\Route;
+use FOS\RestBundle\Controller\Annotations;
class HealthcheckController extends FOSRestController
{
- /**
- * @Route("/ping", name="healthcheck")
- */
+ /**
+ * @Annotations\Get(
+ * path="/ping"
+ * )
+ */
public function getAction()
{
return new JsonResponse('pong');
}
}
And a quick check of the router:
bin/console debug:router
-------------------------- -------- -------- ------ -----------------------------------
Name Method Scheme Host Path
-------------------------- -------- -------- ------ -----------------------------------
app_healthcheck_get GET ANY ANY /ping
Ok, we are now locked down to just a GET
request, and we've overridden the generated path
with our own /ping
endpoint.
We could also add in a name
option to change app_healthcheck_get
to healthcheck
, but I don't see any value in doing that.
And just to confirm:
vendor/bin/behat features/healthcheck.feature
Feature: To ensure the API is responding in a simple manner
In order to offer a working product
As a conscientious software developer
I need to ensure my JSON API is functioning
Scenario: Basic healthcheck # features/healthcheck.feature:8
Given I request "/ping" using HTTP GET # Imbo\BehatApiExtension\Context\ApiContext::requestPath()
Then the response code is 200 # Imbo\BehatApiExtension\Context\ApiContext::assertResponseCodeIs()
And the response body is: # Imbo\BehatApiExtension\Context\ApiContext::assertResponseBodyIs()
"""
"pong"
"""
1 scenario (1 passed)
3 steps (3 passed)
0m0.06s (9.59Mb)
In this example we've made use of Manual Route Definitions.
If we follow a set of naming conventions, FOSRESTBundle can take care of generating routes for us automatically. Depending on the complexity of our entities / resources, this will either be quite straightforward, or a little more complex.
Personally I like the flexibility of the manual route definitions.
Because the FOSRESTBundle routing annotations are extended / extends
from Sensio\Bundle\FrameworkExtraBundle\Configuration\Route
, we needed to composer require annotations
in the previous video. If you stick to the FOSRESTBundle automated routes, you can skip this dependency.
Ok, that's our basic healthcheck out of the way. Onwards to the creation / POST
of our Albums.