Skip to content

Commit 0ce0cf5

Browse files
author
Codeliner
committed
Implement complete booking procedure as single page js app
- New resource RouteCandidate - Add endpoint to update CargoRouting - Expand UI functionality - Adapt book new cargo feature test
1 parent 83214fb commit 0ce0cf5

File tree

17 files changed

+568
-242
lines changed

17 files changed

+568
-242
lines changed

README.md

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ the [features folder](https://github.com/codeliner/php-ddd-cargo-sample/tree/mas
4646
We make use of [Behat](http://behat.org/) and [Mink](http://mink.behat.org/) to test our
4747
business expectations.
4848

49+
You can run the feature tests by navigating to the project root and start the selenium server shipped with the sample app with following command:
50+
`java -jar bin/selenium-server-standalone-2.37.0.jar`
51+
After the server started successful open another console, navigate to project root again and run Behat with the command `php bin/behat`.
52+
53+
*If it does not work, check that the behat file is executable.
54+
4955
Unit Tests
5056
----------
5157
Unit Tests are of course also available. You can find them in [module/CargoBackend/tests](https://github.com/codeliner/php-ddd-cargo-sample/tree/master/module/CargoBackend/tests).
@@ -62,34 +68,4 @@ Maybe I've missed a concept that you hoped to find in the example.
6268
Chapter Overview
6369
----------------
6470

65-
###ChapterOne
66-
`git checkout ChapterOne`
67-
68-
Chapter One release contains the first draft of the Cargo DDD model.
69-
It contains the Entities `Cargo` and `Voyage` and also an `Application BookingService` that works with an `overbooking policy`
70-
to allow the booking of a Cargo even when the Voyage has not enough free capacity.
71-
72-
[ChapterOne Review](https://github.com/codeliner/php-ddd-cargo-sample/blob/master/docs/ChapterOne-Review.md)
73-
###ChapterTwo
74-
`git checkout ChapterTwo`
75-
76-
In Chapter Two we learn the importance of the Ubiquitous Language. With it's help the team works out a Cargo Router and redefines the use cases for the Shipping Application. The `Application BookingService` is replaced with a `Application RoutingService`, cause the system focuses on planing an `Itinerary` for a `Cargo` that satisfies a `RouteSpecification`.
77-
78-
[ChapterTwo Review](https://github.com/codeliner/php-ddd-cargo-sample/blob/master/docs/ChapterTwo-Review.md)
79-
80-
###ChapterThree
81-
`git checkout ChapterThree`
82-
83-
ChapterThree is about Model-Driven Design.
84-
85-
> "Design a portion of the software system to reflect the domain model in a very literal way, so that
86-
> mapping is obvious. Revisit the model and modify it to be implemented more naturally in software,
87-
> even as you seek to make it reflect deeper insight into the domain. Demand a single model that
88-
> serves both purposes well, in addition to supporting a robust UBIQUITOUS LANGUAGE.
89-
> Draw from the model the terminology used in the design and the basic assignment of responsibilities.
90-
> The code becomes an expression of the model, so a change to the code may be a change to the
91-
> model. Its effect must ripple through the rest of the project's activities accordingly."
92-
>
93-
> -- Eric Evans: Domain-Driven Design: Tackling Complexity in the Heart of Software
94-
95-
[ChapterThree Review](https://github.com/codeliner/php-ddd-cargo-sample/blob/master/docs/ChapterThree-Review.md)
71+
The chapter overview has moved to the [PHP DDD Cargo Sample project page](http://codeliner.github.io/php-ddd-cargo-sample/)

composer.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
"keywords": [
66
"DDD",
77
"zf2",
8-
"Domain Driven Design"
8+
"doctrine",
9+
"domain driven design",
10+
"sample",
11+
"REST"
912
],
1013
"homepage": "https://github.com/codeliner/php-ddd-cargo-sample",
1114
"authors": [

features/book_new_cargo.feature

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
Feature: Book new Cargo
22
In order to manage a transport of a Cargo
33
As a booking clerk
4-
I need to define an origin and a destination of a Cargo and assign it to a proper itinerary
4+
I need to define an origin and a destination of a Cargo and assign it to a proper route
55

66
@javascript
7-
Scenario: Add a Cargo and assign Itinerary
8-
Given I am on "application/cargo/add"
9-
When I select "DEHAM" from "origin"
10-
And I select "USNYC" from "destination"
7+
Scenario: Add a Cargo and assign route
8+
Given I am on "application/bookingApp/index"
9+
Then I should wait until I see "#book-cargo"
10+
When I follow "book-cargo"
11+
And I select "DEHAM" from "origin"
12+
And I select "USNYC" from "final_destination"
1113
And I click the submit button
12-
And I follow "assign-itinerary-link-1"
13-
Then the url should match "application/cargo/show/trackingid/[\w-]{36,36}"
14-
And I should see 1 ".itinerary" elements
14+
Then I should wait until I see "#route-candidate-list"
15+
When I follow first ".assign-cargo-btn" link
16+
Then I should wait until I see "#cargo-list"
17+
When I click on first item in the list "#cargo-list"
18+
Then I should see 1 ".itinerary" elements

features/bootstrap/FeatureContext.php

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ static public function iniializeZendFramework()
5656
public static function clearDatabase()
5757
{
5858
$em = self::$zendApp->getServiceManager()->get('doctrine.entitymanager.orm_default');
59-
$q = $em->createQuery('delete from Application\Domain\Model\Cargo\Cargo');
59+
$q = $em->createQuery('delete from CargoBackend\Model\Cargo\Cargo');
6060
$q->execute();
61-
$q = $em->createQuery('delete from Application\Domain\Model\Cargo\RouteSpecification');
61+
$q = $em->createQuery('delete from CargoBackend\Model\Cargo\RouteSpecification');
6262
$q->execute();
63-
$q = $em->createQuery('delete from Application\Domain\Model\Cargo\Itinerary');
63+
$q = $em->createQuery('delete from CargoBackend\Model\Cargo\Itinerary');
6464
$q->execute();
6565
}
6666

@@ -80,6 +80,16 @@ public function iClickTheSubmitButton()
8080
throw new \RuntimeException("Can not find the submit btn");
8181
}
8282
}
83+
84+
/**
85+
* @Then /^I should wait until I see "([^"]*)"$/
86+
*/
87+
public function iShouldSeeAvailableRoutes($arg1)
88+
{
89+
$this->getSession()->wait(5000, '(0 === jQuery.active)');
90+
91+
$this->assertElementOnPage($arg1);
92+
}
8393

8494
/**
8595
* @When /^I click on first item in the list "([^"]*)"$/
@@ -95,6 +105,20 @@ public function iClickOnFirstItemInTheList($arg1)
95105

96106
$li->find('css', 'a')->click();
97107
}
108+
109+
/**
110+
* @When /^I follow first "([^"]*)" link$/
111+
*/
112+
public function iFollowFirstLink($arg1)
113+
{
114+
$page = $this->getSession()->getPage();
115+
116+
$link = $page->find('css', $arg1);
117+
118+
if ($link) {
119+
$link->click();
120+
}
121+
}
98122

99123
/**
100124
* @Given /^I wait until I am on page "(?P<page>[^"]+)"$/

module/Application/config/module.config.php

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,18 @@
6868
),
6969

7070
),
71+
'may_terminate' => true,
72+
'child_routes' => array(
73+
'routecandidates' => array(
74+
'type' => 'Segment',
75+
'options' => array(
76+
'route' => '/routecandidates',
77+
'defaults' => array(
78+
'controller' => 'Api\Controller\RouteCandidates',
79+
),
80+
),
81+
),
82+
),
7183
)
7284
)
7385
),
@@ -90,9 +102,10 @@
90102
),
91103
'service_manager' => array(
92104
'factories' => array(
93-
'main_navigation' => 'Zend\Navigation\Service\DefaultNavigationFactory',
94-
'cargo_form' => 'Application\Form\Service\CargoFormFactory',
95-
'cargo_routing_resource' => 'Application\Resource\Service\CargoRoutingFactory',
105+
'main_navigation' => 'Zend\Navigation\Service\DefaultNavigationFactory',
106+
'cargo_form' => 'Application\Form\Service\CargoFormFactory',
107+
'cargo_routing_resource' => 'Application\Resource\Service\CargoRoutingFactory',
108+
'route_candidate_resource' => 'Application\Resource\Service\RouteCandidateFactory'
96109
),
97110
'abstract_factories' => array(
98111
'Zend\Cache\Service\StorageCacheAbstractServiceFactory',
@@ -178,6 +191,15 @@
178191
'route_name' => 'api/cargoroutings',
179192
'identifier_name' => 'tracking_id',
180193
'collection_name' => 'cargoroutings',
194+
'collection_http_options' => array('get', 'post'),
195+
'resource_http_options' => array('get', 'put'),
196+
),
197+
'Api\Controller\RouteCandidates' => array(
198+
'listener' => 'route_candidate_resource',
199+
'route_name' => 'api/cargoroutings/routecandidates',
200+
'collection_name' => 'routecandidates',
201+
'collection_http_options' => array('get'),
202+
'resource_http_options' => array(),
181203
)
182204
),
183205
'renderer' => array(
@@ -188,6 +210,10 @@
188210
'hydrator' => 'ArraySerializable',
189211
'identifier_name' => 'tracking_id',
190212
'route' => 'api/cargoroutings',
213+
),
214+
'CargoBackend\API\Booking\Dto\RouteCandidateDto' => array(
215+
'hydrator' => 'ArraySerializable',
216+
'route' => 'api/cargoroutings/routecandidates',
191217
)
192218
),
193219
),

module/Application/src/Application/Form/CargoForm.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public function __construct(array $aLocationList, $anOptionList = array())
5454
));
5555

5656
$this->add(array(
57-
'name' => 'finalDestination',
57+
'name' => 'final_destination',
5858
'options' => array(
5959
'label' => 'Destination',
6060
'value_options' => $this->locations,
@@ -114,7 +114,7 @@ public function getInputFilter()
114114
)
115115
));
116116

117-
$destinationInput = new Input('finalDestination');
117+
$destinationInput = new Input('final_destination');
118118
$destinationInput->getValidatorChain()
119119
->attach($inArrayValidator)
120120
->attach($notSameValidator);

module/Application/src/Application/Resource/CargoRouting.php

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414
use Application\Form\CargoForm;
1515
use CargoBackend\API\Booking\BookingServiceInterface;
1616
use CargoBackend\API\Booking\Dto\CargoRoutingDto;
17+
use CargoBackend\API\Booking\Dto\LegDto;
18+
use CargoBackend\API\Booking\Dto\RouteCandidateDto;
1719
use CargoBackend\API\Exception\CargoNotFoundException;
1820
use PhlyRestfully\Exception\CreationException;
1921
use PhlyRestfully\Exception\DomainException;
22+
use PhlyRestfully\Exception\UpdateException;
2023
use PhlyRestfully\ResourceEvent;
2124
use Zend\EventManager\AbstractListenerAggregate;
2225
use Zend\EventManager\EventInterface;
@@ -63,6 +66,7 @@ public function __construct(BookingServiceInterface $aBookingService, CargoForm
6366
public function attach(EventManagerInterface $events)
6467
{
6568
$this->listeners[] = $events->attach('create', array($this, 'onCreate'));
69+
$this->listeners[] = $events->attach('update', array($this, 'onUpdate'));
6670
$this->listeners[] = $events->attach('fetch', array($this, 'onFetch'));
6771
$this->listeners[] = $events->attach('fetchAll', array($this, 'onFetchAll'));
6872

@@ -71,6 +75,11 @@ public function attach(EventManagerInterface $events)
7175
$sharedEvents->attach('PhlyRestfully\Plugin\HalLinks', 'getIdFromResource', array($this, 'onGetIdFromResource'));
7276
}
7377

78+
/**
79+
* @param ResourceEvent $e
80+
* @return CargoRoutingDto
81+
* @throws \PhlyRestfully\Exception\CreationException
82+
*/
7483
public function onCreate(ResourceEvent $e)
7584
{
7685
$data = $e->getParam('data');
@@ -87,17 +96,22 @@ public function onCreate(ResourceEvent $e)
8796

8897
$trackingId = $this->bookingService->bookNewCargo(
8998
$this->cargoFrom->get('origin')->getValue(),
90-
$this->cargoFrom->get('finalDestination')->getValue()
99+
$this->cargoFrom->get('final_destination')->getValue()
91100
);
92101

93102
$cargoRouting = new CargoRoutingDto();
94103
$cargoRouting->setTrackingId($trackingId);
95104
$cargoRouting->setOrigin($this->cargoFrom->get('origin')->getValue());
96-
$cargoRouting->setFinalDestination($this->cargoFrom->get('finalDestination')->getValue());
105+
$cargoRouting->setFinalDestination($this->cargoFrom->get('final_destination')->getValue());
97106

98107
return $cargoRouting;
99108
}
100109

110+
/**
111+
* @param ResourceEvent $e
112+
* @return CargoRoutingDto
113+
* @throws \PhlyRestfully\Exception\DomainException
114+
*/
101115
public function onFetch(ResourceEvent $e)
102116
{
103117
$trackingId = $e->getRouteMatch()->getParam('tracking_id');
@@ -111,11 +125,43 @@ public function onFetch(ResourceEvent $e)
111125
return $cargoRouting;
112126
}
113127

128+
/**
129+
* @param ResourceEvent $e
130+
* @return \CargoBackend\API\Booking\Dto\CargoRoutingDto[]
131+
*/
114132
public function onFetchAll(ResourceEvent $e)
115133
{
116134
return $this->bookingService->listAllCargos();
117135
}
118136

137+
/**
138+
* @param ResourceEvent $e
139+
* @return \CargoBackend\API\Booking\Dto\CargoRoutingDto
140+
* @throws \PhlyRestfully\Exception\UpdateException
141+
*/
142+
public function onUpdate(ResourceEvent $e)
143+
{
144+
$trackingId = $e->getRouteMatch()->getParam('tracking_id');
145+
146+
$data = $e->getParam('data');
147+
148+
if (! isset($data->legs)) {
149+
throw new UpdateException("Legs missing in CargoRouting payload", 400);
150+
}
151+
152+
$routeCandidate = new RouteCandidateDto();
153+
154+
$routeCandidate->setLegs($this->toLegDtosFromData($data->legs));
155+
156+
$this->bookingService->assignCargoToRoute($trackingId, $routeCandidate);
157+
158+
return $this->bookingService->loadCargoForRouting($trackingId);
159+
}
160+
161+
/**
162+
* @param EventInterface $e
163+
* @return bool|string
164+
*/
119165
public function onGetIdFromResource(EventInterface $e)
120166
{
121167
$resource = $e->getParam('resource');
@@ -127,6 +173,26 @@ public function onGetIdFromResource(EventInterface $e)
127173
return false;
128174
}
129175

130-
//@TODO: Implement methods
176+
/**
177+
* @param array $legs
178+
* @return LegDto[]
179+
*/
180+
private function toLegDtosFromData(array $legs)
181+
{
182+
$legDtos = array();
183+
184+
foreach ($legs as $legData) {
185+
$legDto = new LegDto();
186+
187+
$legDto->setLoadLocation($legData['load_location']);
188+
$legDto->setUnloadLocation($legData['unload_location']);
189+
$legDto->setLoadTime($legData['load_time']);
190+
$legDto->setUnloadTime($legData['unload_time']);
191+
192+
$legDtos[] = $legDto;
193+
}
194+
195+
return $legDtos;
196+
}
131197
}
132198

0 commit comments

Comments
 (0)