We will start off by creating a new module called Foggyline\ShipmentBundle. We will do so with the help of the console by running the following command:
php bin/console generate:bundle --namespace=Foggyline/ShipmentBundle
The command triggers an interactive process, which asks us several questions along the way, shown as follows:

Once done, files app/AppKernel.php and app/config/routing.yml are modified automatically. The registerBundles method of an AppKernel class has been added to the following line under the $bundles array:
new Foggyline\PaymentBundle\FoggylineShipmentBundle(),
The routing.yml file has been updated with the following entry:
foggyline_payment: resource: "@FoggylineShipmentBundle/Resources/config/routing.xml" prefix: /
In order to avoid colliding with the core application code, we need to change prefix: /into prefix: /shipment/.
The flat rate shipment service is going to provide the fixed shipment method that our sales module is going to use for its checkout process. Its role is to provide the shipment method labels, code, delivery options, and processing URLs.
We will start by defining the following service under the services element of the src/Foggyline/ShipmentBundle/Resources/config/services.xml file:
<service id="foggyline_shipment.dynamicrate_shipment"class="Foggyline\ShipmentBundle\Service\DynamicRateShipment"> <argument type="service" id="router"/> <tag name="shipment_method"/> </service>
This service accepts only one argument: the router. The tagname value is set to shipment_method, as our SalesBundle module will be looking for shipment methods based on the shipment_method tag assigned to the service.
We will now create the actual service class, within the src/Foggyline/ShipmentBundle/Service/FlatRateShipment.php file as follows:
namespace Foggyline\ShipmentBundle\Service;
class FlatRateShipment
{
private $router;
public function __construct(
\Symfony\Bundle\FrameworkBundle\Routing\Router $router
)
{
$this->router = $router;
}
public function getInfo($street, $city, $country, $postcode, $amount, $qty)
{
return array(
'shipment' => array(
'title' =>'Foggyline FlatRate Shipment',
'code' =>'flat_rate',
'delivery_options' => array(
'title' =>'Fixed',
'code' =>'fixed',
'price' => 9.99
),
'url_process' => $this->router->generate('foggyline_shipment_flat_rate_process'),
)
;
}
}The getInfo method is what's going to provide the necessary information to our future SalesBundle module in order for it to construct the shipment step of the checkout process. It accepts a series of arguments:$street, $city, $country, $postcode, $amount, and $qty. We can consider these to be part of some unified shipment interface. delivery_options in this case returns a single, fixed value. url_process is the URL to which we will be inserting our selected shipment method. Our future SalesBundle module will then merely be doing an AJAX POST to this URL, expecting either a success or error JSON response, which is quite similar to what we imagined doing with payment methods.
We edit the src/Foggyline/ShipmentBundle/Resources/config/routing.xml file by adding the following route definitions to it:
<route id="foggyline_shipment_flat_rate_process"path="/flat_rate/process"> <default key="_controller">FoggylineShipmentBundle:FlatRate:process </default> </route>
We then create a src/Foggyline/ShipmentBundle/Controller/FlatRateController.php. file with content as follows:
namespace Foggyline\ShipmentBundle\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class FlatRateController extends Controller
{
public function processAction(Request $request)
{
// Simulating some transaction id, if any
$transaction = md5(time() . uniqid());
return new JsonResponse(array(
'success' => $transaction
));
}
}We should now be able to access a URL, like /app_dev.php/shipment/flat_rate/process, and see the output of processAction. Implementations given here are dummy ones. What is important for us to know is that the sales module will, during its checkout process, render any possible delivery_options pushed through the getInfo method of a shipment_method tagged service. Meaning, the checkout process should show flat rate shipment as an option. The behavior of checking out will be coded such that if a shipment method is not selected, it will prevent the checkout process from going any further. We will touch upon this some more when we get to the SalesBundle module.
Aside from the flat rate shipment method, let's go ahead and define one more dynamic shipment, called Dynamic Rate.
We will start by defining the following service under the services element of the src/Foggyline/ShipmentBundle/Resources/config/services.xml file:
<service id="foggyline_shipment.dynamicrate_shipment"class="Foggyline\ShipmentBundle\Service\DynamicRateShipment"> <argument type="service" id="router"/> <tag name="shipment_method"/> </service>
The service defined here accepts only one router argument. The tag name property is the same as with the flat rate shipment service.
We will then create the src/Foggyline/ShipmentBundle/Service/DynamicRateShipment.php file, with content as follows:
namespace Foggyline\ShipmentBundle\Service;
class DynamicRateShipment
{
private $router;
public function __construct(
\Symfony\Bundle\FrameworkBundle\Routing\Router $router
)
{
$this->router = $router;
}
public function getInfo($street, $city, $country, $postcode, $amount, $qty)
{
return array(
'shipment' => array(
'title' =>'Foggyline DynamicRate Shipment',
'code' =>'dynamic_rate_shipment',
'delivery_options' => $this->getDeliveryOptions($street, $city, $country, $postcode, $amount, $qty),
'url_process' => $this->router->generate('foggyline_shipment_dynamic_rate_process'),
)
);
}
public function getDeliveryOptions($street, $city, $country, $postcode, $amount, $qty)
{
// Imagine we are hitting the API with: $street, $city, $country, $postcode, $amount, $qty
return array(
array(
'title' =>'Same day delivery',
'code' =>'dynamic_rate_sdd',
'price' => 9.99
),
array(
'title' =>'Standard delivery',
'code' =>'dynamic_rate_sd',
'price' => 4.99
),
);
}
}Unlike the flat rate shipment, here the delivery_options key of the getInfo method is constructed with the response of the getDeliveryOptions method. The method is internal to the service and is not imagined as exposed or to be looked at as part of an interface. We can easily imagine doing some API calls within it, in order to fetch calculated rates for our dynamic shipment method.
Once the dynamic rates shipment service is in place, we can go ahead and create the necessary route for it. We will start by adding the following route definition to the src/Foggyline/ShipmentBundle/Resources/config/routing.xml file:
<route id="foggyline_shipment_dynamic_rate_process" path="/dynamic_rate/process"> <default key="_controller">FoggylineShipmentBundle:DynamicRate:process </default> </route>
We will then create the src/Foggyline/ShipmentBundle/Controller/DynamicRateController.php file, with content as follows:
namespace Foggyline\ShipmentBundle\Controller;
use Foggyline\ShipmentBundle\Entity\DynamicRate;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class DynamicRateController extends Controller
{
public function processAction(Request $request)
{
// Just a dummy string, simulating some transaction id
$transaction = md5(time() . uniqid());
if ($transaction) {
return new JsonResponse(array(
'success' => $transaction
));
}
return new JsonResponse(array(
'error' =>'Error occurred while processing DynamicRate shipment.'
));
}
}Similar to the flat rate shipment, here we have added a simple dummy implementation of the process and method. The incoming $request should contain the same info as the service getInfo method, meaning, it should have the following arguments available: $street, $city, $country, $postcode, $amount, and $qty. The method responses will feed into the SalesBundle module later on. We can easily implement more robust functionality from within these methods, but that is out of the scope of this chapter.