Giter Site home page Giter Site logo

codeliner / php-ddd-cargo-sample Goto Github PK

View Code? Open in Web Editor NEW
796.0 60.0 151.0 68.21 MB

PHP 7 Version of the cargo sample used in Eric Evans DDD book

Home Page: http://codeliner.github.io/php-ddd-cargo-sample/

License: BSD 3-Clause "New" or "Revised" License

PHP 18.64% CSS 0.18% HTML 3.43% JavaScript 77.62% Gherkin 0.12%
cargo-sample php eric-evans-book ddd ddd-sample

php-ddd-cargo-sample's Introduction

PHP DDD Cargo Sample

PHP 7 port of the cargo sample used in Eric Evans Domain-Driven Design book

Scrutinizer Quality Score Build Status

Cargo Sample Reloaded

After two years of inactivity a new version of the PHP DDD Cargo Sample is available [2015/12/07]. The new version is a complete rewrite of the cargo sample using cutting edge technology.

tl;dr

Click here ๐Ÿ˜„

What Is New?

  • PHP 7 with strict scalar type hints
  • PSR-7 & PSR-15 middleware layer using zend-expressive
  • Doctrine ORM ^2.5 Embeddables
  • PHPUnit ^5.0
  • Behat ^3.0
  • Single Page UI using riot.js

Goal of the Project

We want to show the PHP 7 way of implementing Domain-Driven Design with the help of the original Cargo sample used in Eric Evans book Domain-Driven Design: Tackling Complexity in the Heart of Software. This has also been done using Java, C#, Ruby and other programming languages.

It is not the one way to apply DDD and only covers the tactical part of DDD. However, the cargo sample should help you understand the theory and gives you a starting point. Also see the Caveats of the java implementation. The same applies for our version.

Installation

Structure

The annotated project overview gives you an idea of the system structure.

PHP 5.6 compatible Version

Looking for a PHP 5.6 compatible version of the cargo sample? @josecelano has downgraded his fork. You can find it here.

Support

If you have any problems with the application please open a GitHub issue. Same applies if you have a question or a feature wish.

Contributing

Contributions of any kind are welcome. The PHP 7 DDD cargo sample aims to help people understand the tactical design part of DDD. So we'd be very happy if you tell your friends about it, link it in discussions and mention it on twitter. If you've found a bug or have an idea for an improvement, just submit a PR like usual.

Behavior Driven Design

All features of the application are described in feature files. You can find them in the features folder of the project. We make use of Behat and Mink to test our business expectations.

You can run the feature tests by navigating to the project root and start the selenium server shipped with the sample app: java -jar selenium-server-standalone-2.46.0.jar After the server started successfully open another console, navigate to project root again and run Behat with the command php bin/behat.

*Note: If it does not work, check that the behat file is executable.

Unit Tests

Unit Tests are of course also available. You can find them in CargoBackend/tests. Got to the directory and simply run phpunit.

Sponsoring

prooph software

This brand new cargo sample version is sponsored by prooph software GmbH. You can follow us on twitter

Become A Member

If you want to share your experience with other DDD enthusiasts or want to ask a question about DDD then the DDDinPHP google group is good place to do so.

You can find more DDD stuff like interesting articles and related libraries on the PhpFriendsOfDdd/state-of-the-union project.

php-ddd-cargo-sample's People

Contributors

codeliner avatar danizord avatar mateuszsip avatar snapshotpl avatar tomhaj avatar tomtomsen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

php-ddd-cargo-sample's Issues

Refactor Voyage and Repo

Pay attention to the Doctrine ValueObject as Identifier problem and refactor voyage to use string representation of VoyageNumber internally.

Rename methods in VoyageRepository to get and getAll, like did in CargoRepository.

TrasactionManager in BookingService

Hey,

it's not a real issue, maybe Evans has the same in his book (I didn't read it), but I am curious about transactions in service.

https://github.com/codeliner/php-ddd-cargo-sample/blob/master/CargoBackend/src/Application/Booking/BookingService.php#L88

When we have it there, it introduces a DB dependency in application service layer, which is "not nice", as db-handling is a responsibility of another layer (Repository or even deeper).

As we have a simple Entity, no even an Aggregate Root, we can live easily without this transaction or we could hide it inside Repository. What do you think?

My question is whether it's done intentionally or there is no really good reason.

Implement Route Specification

A Route Specification is a ValueObject and contains an Origin Location, a Destination Location and optionally a customs clearance point.

Implement Leg

A Leg describes one part of an Itinerary. It is composed of a Load and an Unload Port Operation and a Transport Leg that describes an ordered Voyage from an Origin Location to a Destination Location.

Exclude Voyage from current version of the system

The team had some discussions with the domain expert, because the handling of Voyage within a Leg isn't clear at all. For the moment the Voyage is ignored until the team has a better understanding of the association between Voyage and Leg.

Expand Cargo Aggregate

A cargo is identified by a unique tracking id, and it always has an origin
and a route specification. The life cycle of a cargo begins with the booking procedure,
when the tracking id is assigned. During a (short) period of time, between booking
and initial routing, the cargo has no itinerary.

Running sample on PHP 5.6

Hi @codeliner . First at all, I have to say it's a very cool sample. I would like to play with it but I would like to run it with other samples in the sample vagrant machine with PHP 5.6. I have removed composer.lock and changed the composer.json to require only PHP 5.6. I removed the return type for these methods TrackingIdDoctrineType::convertToPHPValue and DoctrineEntityManagerFactory::__invoke, and it seems to work, or at least there is no other parser error. But I have a problem with JS:

This is the index response:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>PHP Cargo Shipping Application</title>
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="/css/bootstrap.min.css">
        <link rel="stylesheet" href="/css/bootstrap-theme.min.css">
        <link rel="stylesheet" href="/css/style.css">
        <script language="javascript" type="text/javascript" src="/js/jquery.min.js"></script>
        <script language="javascript" type="text/javascript" src="/js/jquery.addons.js"></script>
        <script language="javascript" type="text/javascript" src="/js/bootstrap.min.js"></script>
        <script language="javascript" type="text/javascript" src="/js/notify.min.js"></script>
        <script language="javascript" type="text/javascript" src="/js/lodash.js"></script>
        <script language="javascript" type="text/javascript" src="/js/q.js"></script>
        <script language="javascript" type="text/javascript" src="/js/moment.min.js"></script>
        <script language="javascript" type="text/javascript" src="/js/riot.min.js"></script>
        <script language="javascript" type="text/javascript" src="/js/prooph.riot.app.js"></script>
        <script language="javascript" type="text/javascript" src="/js/stores.js"></script>
    </head>
    <body>
        <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
            <div class="container">
                <div class="navbar-header">                    
                    <a class="navbar-brand" href="/">PHP Cargo Shipping System</a>
                </div>
                <div class="collapse navbar-collapse">
                    <ul class="nav navbar-nav">
                        <li><a href="#booking">Booking App</a></li>
                    </ul>
                </div><!--/.nav-collapse -->
            </div>
        </nav>
        <div class="container">
            <main></main>
            <hr>
            <footer>
                <p>&copy; 2013 - 2015 by Alexander Miertsch. All rights reserved.</p>
            </footer>
        </div> <!-- /container -->
        <script type="text/javascript">
            riot.tag("assign-route", "<div if=\"{ !ready }\">
    <p class=\"alert alert-info\">Fetching data from server ...</p>
</div>
<div if=\"{ ready }\">
    <div class=\"row\">
        <div class=\"col-md-12\">
            <h2>Cargo from {getLocation(cargo.get('origin')).get('name')} to {getLocation(cargo.get('final_destination')).get('name')}<span class=\"subheading\">TrackingId: { cargo.getId() }</span></h2>
        </div>
    </div>
    <div class=\"row\">
        <div class=\"col-md-12\">
            <h3>Available routes</h3>
        </div>
    </div>
    <div id=\"route-candidate-list\" >
        <route-candidate each=\"{ routeCandidates }\"
                         routecandidate=\"{ this }\"
                         trigger_event=\"{ parent.triggerEvent }\"
                         route_to=\"{ parent.routeTo }\"
                         get_location=\"{ parent.getLocation }\"></route-candidate>
    </div>
</div>
", function (context) {
        var self = this,
            _checkReady = function() {
                if (self.locations.length === 0) {
                    return;
                }

                if (! self.cargo) {
                    return;
                }

                if (! self.routeCandidates) {
                    return;
                }

                self.ready = true;
            };

        self.trackingId = context.trackingId;
        self.locations = [];
        self.routeCandidates = null;
        self.cargo = null;
        self.ready = false;

        self.getLocation = function(unLocode) {
            return _.find(self.locations, function(location) {
                return location.getId() === unLocode;
            });
        }

        self.triggerEvent = function(eventName, eventData) {
            context.app.trigger(eventName, eventData);
        }

        self.routeTo = function(routePath) {
            context.app.routeTo(context.router.get(routePath));
        }

        self.onLocationsRefreshed = function(locations) {
            self.locations = locations;
            _checkReady();
            self.update();
        }

        self.onCargoRefreshed = function(cargo) {
            if (cargo.getId() === self.trackingId) {
                self.cargo = cargo;

                context.app.trigger('load_cargo_routecandidates', cargo);
            }
        }

        self.onCargoRoutecandidatesLoaded = function(routeCandidates) {
            if (_.isEmpty(routeCandidates)) {
                return;
            }

            var first = _.first(routeCandidates);
            if (first.getParentId() !== self.trackingId) {
                return;
            }

            self.routeCandidates = routeCandidates;
            _checkReady();
            self.update();
        }

        self.on("mount", function() {
            context.app.on("locations_refreshed", self.onLocationsRefreshed);
            context.app.on("cargo_refreshed", self.onCargoRefreshed);
            context.app.on("cargo_routecandidates_loaded", self.onCargoRoutecandidatesLoaded);

            context.app.trigger("refresh_locations");
            context.app.trigger("refresh_cargo", new Resource(self.trackingId, 'cargo', {}));
        });

        self.on("unmount", function() {
            context.app.off("locations_refreshed", self.onLocationsRefreshed);
            context.app.off("cargo_refreshed", self.onCargoRefreshed);
            context.app.off("cargo_routecandidates_loaded", self.onCargoRoutecandidatesLoaded);
        });
    });riot.tag("cargo-list", "<div if=\"{ !ready }\">
    <img src=\"/img/ajax-loader.gif\" alt=\"loading ...\" />
</div>
<div if=\"{ ready }\">
    <div class=\"row\">
        <div class=\"col-md-12\">
            <p if=\"{ !cargos.length }\" class=\"alert alert-danger\">The list is empty. How about booking the first <a href=\"{ router.get('cargos/add') }\">cargo</a>?</p>
            <ul if=\"{ cargos.length }\" id=\"cargo-list\">
                <cargo-list-entry each=\"{ cargos }\" cargo=\"{ this }\" get_location=\"{ parent.getLocation }\" router=\"{ parent.router }\"></cargo-list-entry>
            </ul>
        </div>
    </div>
    <div class=\"row\">
        <div class=\"col-md-12\">
            <p><a id=\"book-cargo\" class=\"btn btn-success btn-lg\" href=\"{ router.get('cargos/add') }\">Book new Cargo &raquo;</a></p>
        </div>
    </div>
</div>
", function (context) {
        var self = this,
            _checkReady = function() {
                if (! self.cargos) {
                    return;
                }

                if (! self.locations) {
                    return;
                }

                self.ready = true;
                self.update();
            };

        self.ready = false;
        self.router = context.router;

        self.getLocation = function(unLocode) {
            return _.find(self.locations, function(location) {
                return location.getId() === unLocode;
            })
        }

        self.onCargoListRefreshed = function(cargos) {
            self.cargos = cargos;
            _checkReady();
            self.update();
        }

        self.onLocationsRefreshed = function(locations) {
            self.locations = locations;
            _checkReady();
            self.update();
        }

        self.on('mount', function() {
            context.app.on('cargo_list_refreshed', self.onCargoListRefreshed);
            context.app.on('locations_refreshed', self.onLocationsRefreshed);

            context.app.trigger('refresh_cargo_list');
            context.app.trigger('refresh_locations');
        })

        self.on("unmount", function() {
            context.app.off('cargo_list_refreshed', self.onCargoListRefreshed);
            context.app.off('locations_refreshed', self.onLocationsRefreshed);
        })
    });riot.tag("cargo-list-entry", "<li>
    <a href=\"{ router.get('cargos/' + cargo.getId()) }\" class=\"cargo-details-link\">{ cargo.getId() }</a> From: { getLocation(cargo.get('origin')).get('name') } To: { getLocation(cargo.get('final_destination')).get('name') }
</li>
", function (context) {
        var self = this;

        self.cargo = context.cargo;
        self.getLocation = context.get_location;
        console.log(self.getLocation(self.cargo.get('origin')));
        self.router = context.router;
    });riot.tag("jb-assign-route", "<h1>Assign Route to Cargo</h1>
", function (context) {

    });riot.tag("jb-cargo-list", "<h1>Cargo Booking Application</h1>
<p>Choose a Cargo from the list to view it's routing details or book a new Cargo.</p>
", function (context) {

    });riot.tag("jb-new-cargo", "<h1>Book a new Cargo</h1>
<p>In the ChapterFour version of the Shipping System you can specify origin and destination of a Cargo. In a second step, the booking system suggest possible routes and you can assign the new Cargo to one of the routes.</p>
", function (context) {

    });riot.tag("jb-show-cargo", "<h1>Cargo Overview</h1>
", function (context) {

    });riot.tag("new-cargo", "<div class=\"row\">
    <div class=\"col-md-12\">
        <form id=\"new-cargo-form\">
            <div class=\"form-group\">
                <label>Origin</label>
                <select name=\"origin\" class=\"form-control\" onchange=\"{ onChangeOrigin }\">
                    <option each=\"{ locations }\" value=\"{ unLocode }\">{ name }</option>
                </select>
            </div>
            <div class=\"{ form-group: true, has-error: hasError }\">
                <label>Destination</label>
                <select name=\"final_destination\" class=\"form-control\" onchange=\"{ onChangeDestination }\">
                    <option each=\"{ locations }\" value=\"{ unLocode }\">{ name }</option>
                </select>
                <span if=\"{ hasError }\" class=\"help-block\">Origin and Destination must not be the same location!</span>
            </div>
            <div class=\"form-group\">
                <input type=\"submit\" name=\"save\" value=\"assign to itinerary\" class=\"{ btn: true, btn-success: true, disabled: !isValid }\" onclick=\"{ onSave }\">
            </div>
        </form>
    </div>
</div>
", function (context) {
        var self = this,
            _checkIsValid = function () {
                self.hasError = false;
                self.isValid = false;

                if (!_.isEmpty(self.origin.value) && !_.isEmpty(self.final_destination.value)) {
                    if (self.origin.value === self.final_destination.value) {
                        self.hasError = true;
                        self.isValid = false;
                        return;
                    }

                    self.isValid = true;
                }
            };

        self.locations = [];

        self.hasError = false;
        self.isValid = false;

        self.locations.push({
            unLocode : '',
            name : 'Fetching locations from server ...'
        });

        self.onChangeOrigin = function (e) {
            _checkIsValid();
        }

        self.onChangeDestination = function (e) {
            _checkIsValid();
        }

        self.onSave = function (e) {
            e.preventDefault();

            _checkIsValid();

            if (! self.isValid) {
                return;
            }

            context.app.one('cargo_created', function(cargo) {
                window.location.hash = context.router.get('cargos/' + cargo.getId() + '/assign_route');
            });

            context.app.trigger('create_cargo', {origin: self.origin.value, destination: self.final_destination.value});
        }

        self.on('mount', function () {
            context.app.one("locations_refreshed", function(locations) {
                self.locations = [];

                self.locations.push({
                    unLocode : '',
                    name : 'Select a location'
                })

                _.forEach(locations, function (location) {
                    self.locations.push(location.getProps())
                });

                self.update();
            });

            context.app.trigger("refresh_locations");
        });
    });riot.tag("route-candidate-leg", "<div class=\"row highlight\">
    <div class=\"col-md-12\">
        <strong>{context.get_location(leg.load_location).get('name')}</strong>&nbsp;{print_time(leg.load_time)}&nbsp;<i class=\"glyphicon glyphicon-arrow-right\"></i> <strong>{context.get_location(leg.unload_location).get('name')}</strong>&nbsp;{print_time(leg.unload_time)}
    </div>
</div>
", function (context) {
        var self = this;
        self.leg = context.leg;
        self.context = context;
        self.print_time = function(timeStr) {
            return moment(timeStr).format('YYYY/MM/DD HH:mm');
        }
    });riot.tag("route-candidate", "<div class=\"row\">
    <div class=\"col-md-12\">
        <div class=\"panel panel-default itinerary\">
            <div class=\"panel-body\">
                <route-candidate-leg each=\"{ routeCandidate.get('legs') }\" leg=\"{ this }\" get_location=\"{ parent.context.get_location }\"></route-candidate-leg>
                <p>&nbsp;</p>
                <div class=\"row\">
                    <div class=\"col-md-12\">
                        <a href=\"#\"
                           class=\"btn btn-success assign-cargo-btn\" onclick=\"{ onAssignCargo }\">Assign to Cargo</a>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
", function (context) {
        var self = this;
        self.context = context;
        self.routeCandidate = context.routecandidate;

        self.onAssignCargo = function(e) {
            e.preventDefault();

            context.trigger_event('assign_route_candidate_to_cargo', self.routeCandidate);

            context.route_to('');
        }
    });riot.tag("show-cargo", "<div if=\"{ !ready }\">
    <img src=\"/img/ajax-loader.gif\" alt=\"loading ...\" />
</div>
<div if=\"{ ready }\">
    <div class=\"row\">
        <div class=\"col-md-12\">
            <h2>Cargo from { getLocation(cargo.get('origin')).get('name') } ({ cargo.getId() })</h2>
        </div>
    </div>
    <div class=\"row\">
        <div class=\"col-md-12\">
            <h3>route specification</h3>
        </div>
    </div>
    <div class=\"row\">
        <div class=\"col-md-6 alert-info\">
            <strong>From: </strong>{ getLocation(cargo.get('origin')).get('name') }
        </div>
        <div class=\"col-md-6 alert-info\">
            <strong>To: </strong>{ getLocation(cargo.get('final_destination')).get('name') }
        </div>
    </div>
    <div class=\"row\">
        <div class=\"col-md-12\">
            <h3>Itinerary</h3>
        </div>
    </div>
    <div class=\"row\">
        <div class=\"col-md-12\">
            <div class=\"panel panel-default itinerary\">
                <div class=\"panel-body\">
                    <p class=\"alert alert-warning\" if=\"{ cargo.get('legs').length == 0 }\"><i class=\"glyphicon glyphicon-warning-sign\"></i>&nbsp;Cargo is not assigned to a route! <a href=\"{ router.get('cargos/' + cargo.getId() + '/assign_route') }\" class=\"btn btn-primary\">assign now</a></p>
                    <div if=\"{ cargo.get('legs').length > 0 }\">
                        <route-candidate-leg each=\"{ cargo.get('legs') }\" leg=\"{ this }\" get_location=\"{ parent.getLocation }\"></route-candidate-leg>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
", function (context) {
        var self = this,
            _checkReady = function() {
                if (! self.cargo) {
                    return;
                }

                self.ready = true;
            };

        self.ready = false;

        self.trackingId = context.trackingId;
        self.router = context.router;

        self.getLocation = function(unLocode) {
            return _.find(self.locations, function(location) {
                return location.getId() === unLocode;
            })
        }

        self.onCargoRefreshed = function(cargo) {
            if (! cargo.getId() === self.trackingId) {
                return;
            }

            self.cargo = cargo;

            _checkReady();
            self.update();
        }

        self.onLocationsRefreshed = function(locations) {
            self.locations = locations;
            _checkReady();
            self.update();
        }

        self.on("mount", function() {
            context.app.on('cargo_refreshed', self.onCargoRefreshed);
            context.app.on('locations_refreshed', self.onLocationsRefreshed);

            context.app.trigger('refresh_cargo', new Resource(self.trackingId, 'cargo', {}));
            context.app.trigger('refresh_locations');
        });

        self.on("unmount", function() {
            context.app.off('cargo_refreshed', self.onCargoRefreshed);
            context.app.off('locations_refreshed', self.onLocationsRefreshed);
        })
    });riot.tag("booking-app", "<div class=\"jumbotron\">
    <jb-cargo-list if=\"{ activeMode == 'cargo-list' }\"></jb-cargo-list>
    <jb-show-cargo if=\"{ activeMode == 'show-cargo' }\"></jb-show-cargo>
    <jb-new-cargo if=\"{ activeMode == 'new-cargo' }\"></jb-new-cargo>
    <jb-assign-route if=\"{ activeMode == 'assign-route' }\"></jb-assign-route>
</div>
<div id=\"booking-app-container\">
    <img src=\"/img/ajax-loader.gif\" alt=\"loading ...\" />
</div>
<footer>
    <div class=\"row\">
        <div class=\"col-md-12\">
            <a href=\"{ router.get('') }\" class=\"btn btn-default icon icon-align-left icon-arrow-left pull-right\">back to overview</a>
        </div>
    </div>
</footer>
", function (context) {
        var self = this,
            renderCargoList = function (mixins) {
                context.app.renderInto(self, "#booking-app-container", "cargo-list", mixins);
                self.activeMode = "cargo-list";
                self.update();
            },
            renderNewCargo = function (mixins) {
                context.app.renderInto(self, "#booking-app-container", "new-cargo", mixins);
                self.activeMode = "new-cargo";
                self.update();
            },
            renderShowCargo = function (mixins) {
                context.app.renderInto(self, "#booking-app-container", "show-cargo", mixins);
                self.activeMode = "show-cargo";
                self.update();
            },
            renderAssignRoute = function (mixins) {
                context.app.renderInto(self, "#booking-app-container", "assign-route", mixins);
                self.activeMode = "assign-route";
                self.update();
            },
            _routeListener = function (con) {
                var rm = con.routeMatch;
                if (! rm[0]) {
                    renderCargoList({router : con.router});
                    return;
                }

                if (rm[0] == "cargos") {
                    if (! rm[1]) {
                        renderCargoList({router : con.router});
                        return;
                    }

                    if (rm[1] == "add") {
                        renderNewCargo({router : con.router});
                        return;
                    }

                    if (! rm[2]) {
                        renderShowCargo({router : con.router, trackingId : rm[1]});
                    }

                    if (rm[2] == "assign_route") {
                        renderAssignRoute({router : con.router, trackingId : rm[1]})
                    }
                }
            };

        self.router = context.router;

        self.on("mount", function () {
            context.router.on("route", _routeListener);
        })
    });riot.tag("home", "<div class=\"jumbotron\">
    <h1>Welcome to the PHP DDD Cargo Sample</h1>
    <p><strong>Congratulations!</strong> You have successfully installed the Container Shipping System. You are currently running the <strong>{chapter}</strong> Version of the Application.</p>
    <p><a class=\"btn btn-success btn-lg\" href=\"https://github.com/prooph/php-ddd-cargo-sample\" target=\"_blank\">Fork PHP DDD Cargo Sample on GitHub &raquo;</a></p>
</div>
<div class=\"row\">
    <div class=\"col-md-12\">
        <div class=\"panel panel-default\">
            <div class=\"panel-heading\">
                <h3 class=\"panel-title\">Goal of the Sample Application</h3>
            </div>
            <div class=\"panel-body\">
                <p>The Cargo Shipping System is based on the example used in Eric Evans book Domain-Driven Design: <i>Tackling Complexity in the Heart of Software</i>. It\'s a PHP Version of the Cargo Sample to show you how DDD can be applied to a PHP Project.</p>
            </div>
        </div>
    </div>
</div>
", function (context) {
        var self = this;

        self.chapter = context.app.config.chapter;
    });riot.tag("main", "<div class=\"content\">

</div>
", function(context) {
        var self = this,
                _routeListener = function (con) {
                    var rm = con.routeMatch;

                    if (!rm[0]) {
                        con.app.renderInto(self, ".content", "home");
                        con.app.removeRouterForRoutePath(["booking"]);
                        return;
                    }

                    if (rm[0] == "booking") {
                        var subRouter = con.router.getSubRouter("booking");
                        con.app.renderInto(self, ".content", "booking-app", {router : subRouter});
                        subRouter.route(rm, con);
                        return;
                    }
                };

        context.router.on("route", _routeListener);
    });
            var CargoUI = ProophRiot.App.create({
                config : {
                    url : {
                        apiRoot: '/api/',
                        forResource : function (resource) {
                            return this.apiRoot + this.resourcePlural(resource.getName()) + '/' + resource.getId();
                        },
                        forResourceCollection : function (resourceName) {
                            return this.apiRoot + this.resourcePlural(resourceName);
                        },
                        resourcePlural: function(resourceName) {
                            return resourceName + 's';
                        },
                        forChildResource : function(childResource) {
                            return this.apiRoot + this.resourcePlural(childResource.getParentName())
                                    + '/' + childResource.getParentId()
                                    + '/' + this.resourcePlural(childResource.getName())
                                    + '/' + childResource.getId()
                        },
                        forChildResourceCollection : function(parentResource, childResourceName) {
                            return this.apiRoot + this.resourcePlural(parentResource.getName())
                                    + '/' + parentResource.getId()
                                    + '/' + this.resourcePlural(childResourceName);
                        }
                    },
                    chapter: "ChapterFour"
                }
            });

            var locationsStore = new LocationStore(CargoUI);
            var cargoStore = new CargoStore(CargoUI);
            var routeCandidateStore = new RouteCandidateStore(CargoUI);

            $(function () {
                CargoUI.bootstrap("main").ready();
            });
        </script>
    </body>
</html>

and there is an error in line 42: Uncaught SyntaxError: Unexpected token ILLEGAL'. It seems methodRiotCompiler::compileFileis not working well, may be becuase functionstr_replace` has a different behavior on PHP 7. Is possible to run the sample on PHP 5.6 or are there a lot of errors like this one?

500 internal server error

Hello,

Excelent DDD implementation, really easy to understand.

I tried with local stack environment also using docker and show up 500 internal server error, when click on 'Booking App', GET request on /api/cargos .

Trying to debug some-how, 500 occurs on BookingServiceFactory with

        return new BookingService(
            $container->get(CargoRepositoryInterface::class),
            $container->get(TransactionManager::class),
            $container->get(RoutingServiceInterface::class),
            $locations
        );
php_1      | 172.17.0.4 -  18/Aug/2016:10:39:17 +0000 "GET /index.php" 500
nginx_1    | "time": "2016-08-18T10:39:18+00:00", "remote_addr": "192.168.99.1", "remote_user": "-", "body_bytes_sent": "9717", "request_time": "0.991", "status": "500", "request": "GET /api/cargos HTTP/1.1", "request_method": "GET", "http_referrer": "http://192.168.99.100:8080/", "http_user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0"

PHP Version

Hi! What is the minimal version that I need to use this Demo? I am using PHP 5.3.3 but when I try to test the project I have this error:

Parse error: syntax error, unexpected '[' in /var/www/php-ddd-cargo-sample/module/Application/src/Application/Form/Service/CargoFormFactory.php on line 27 Call Stack: 0.0001 643080 1. {main}() /var/www/php-ddd-cargo-sample/public/index.php:0 0.0417 11117488 2. Zend\Mvc\Application->run() /var/www/php-ddd-cargo-sample/public/index.php:21 0.0426 11183864 3. Zend\EventManager\EventManager->trigger() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/Mvc/Application.php:309 0.0426 11183872 4. Zend\EventManager\EventManager->triggerListeners() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/EventManager/EventManager.php:207 0.0426 11187696 5. call_user_func() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/EventManager/EventManager.php:468 0.0426 11187752 6. Zend\Mvc\DispatchListener->onDispatch() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/EventManager/EventManager.php:0 0.0427 11187752 7. Zend\Mvc\Controller\ControllerManager->get() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/Mvc/DispatchListener.php:97 0.0427 11188144 8. Zend\ServiceManager\AbstractPluginManager->get() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/Mvc/Controller/ControllerManager.php:133 0.0427 11188144 9. Zend\ServiceManager\ServiceManager->get() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/ServiceManager/AbstractPluginManager.php:103 0.0427 11189168 10. Zend\ServiceManager\ServiceManager->create() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/ServiceManager/ServiceManager.php:503 0.0427 11189168 11. Zend\ServiceManager\ServiceManager->doCreate() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/ServiceManager/ServiceManager.php:569 0.0427 11189248 12. Zend\ServiceManager\AbstractPluginManager->createFromFactory() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/ServiceManager/ServiceManager.php:609 0.0429 11201920 13. Zend\ServiceManager\AbstractPluginManager->createServiceViaCallback() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/ServiceManager/AbstractPluginManager.php:205 0.0429 11202552 14. Zend\ServiceManager\ServiceManager->createServiceViaCallback() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/ServiceManager/AbstractPluginManager.php:245 0.0429 11203088 15. call_user_func() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/ServiceManager/ServiceManager.php:905 0.0429 11203160 16. Application\Controller\Service\CargoControllerFactory->createService() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/ServiceManager/ServiceManager.php:0 0.0696 18637368 17. Zend\ServiceManager\ServiceManager->get() /var/www/php-ddd-cargo-sample/module/Application/src/Application/Controller/Service/CargoControllerFactory.php:39 0.0697 18638440 18. Zend\ServiceManager\ServiceManager->create() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/ServiceManager/ServiceManager.php:503 0.0697 18638440 19. Zend\ServiceManager\ServiceManager->doCreate() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/ServiceManager/ServiceManager.php:569 0.0697 18638520 20. Zend\ServiceManager\ServiceManager->createFromFactory() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/ServiceManager/ServiceManager.php:609 0.0697 18638680 21. class_exists() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/ServiceManager/ServiceManager.php:1030 0.0697 18639504 22. Zend\Loader\StandardAutoloader->autoload() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/Loader/StandardAutoloader.php:0 0.0697 18639648 23. Zend\Loader\StandardAutoloader->loadClass() /var/www/php-ddd-cargo-sample/vendor/zendframework/zendframework/library/Zend/Loader/StandardAutoloader.php:215

Thanks!

Refactor Voyage

Remove all attributes from Voyage except the VoyageNumber. The system focuses on planing an Itinerary with the help of an external Routing Service so the capacity and the assignment to a Cargo is no longer managed by the domain. The Voyage is assigned to a Leg. At the moment it only contains a VoyageNumber to reference the Voyage.

http://0.0.0.0:8080/#booking booking page error

[Fri Jan 15 10:36:57 2016] 127.0.0.1:50046 [200]: /api/cargos - Uncaught Error: Call to undefined method Psr7Middlewares\Middleware\Payload::associative() in /home/ubt/php-ddd-cargo-sample-master/CargoBackend/src/Container/Application/Action/PayloadParserFactory.php:21
Stack trace:
#0 /home/ubt/php-ddd-cargo-sample-master/vendor/zendframework/zend-servicemanager/src/ServiceManager.php(693): Codeliner\CargoBackend\Container\Application\Action\PayloadParserFactory->__invoke(Object(Zend\ServiceManager\ServiceManager), 'Psr7Middlewares...', NULL)
#1 /home/ubt/php-ddd-cargo-sample-master/vendor/zendframework/zend-servicemanager/src/ServiceManager.php(187): Zend\ServiceManager\ServiceManager->doCreate('Psr7Middlewares...')
#2 /home/ubt/php-ddd-cargo-sample-master/vendor/zendframework/zend-expressive/src/Application.php(581): Zend\ServiceManager\ServiceManager->get('Psr7Middlewares...')
#3 [internal function]: Zend\Expressive\Application->Zend\Expressive\{closure}(Object(Zend\Stratigility\Http\Request), Object(Zend\Stratigility\Http\Response), Object(Zend\Stratigility\Next))
#4  in /home/ubt/php-ddd-cargo-sample-master/CargoBackend/src/Container/Application/Action/PayloadParserFactory.php on line 21

Implement Itinerary

An Itinerary satisfies a Route Specification and contains a list of Legs.

Change all test methods to new behavior name style

The team decided to use a more descriptive style for naming test methods. Now, all methods should start with "it_" followed by a behavoir description that the method aims to test.
The description-method-name is written in snake_case. The method must be annotated with a @test annotation to be recognized as test method by PHPUnit.
Example:

/**
 * @test
 */
public function it_does_something()
{
    //Test a specific behavior of the related object
}

Confusion between DTO and Value Object

Hi,

I just read some classes in this repository and I found some DTO object that are in fact, to me, value objects.

A DTO is for me, just as stupid data container which is used to transport data between layers and tiers..It shouldn't contains any logic, nor assertions (berberleri/assert)
A value object have some logic inside, and is immutable, it should not contains any getter or setter and just return one primitive.

You have in this repository a mix between DTO and value object.

Is this wanted ? Is it my understanding of DTO or Value Object that is wrong ?

Define Routing Service

Define an interface for a Routing Service that fetches Itineraries that satisfy a given Route Specification.

Container factories are part of infrastructure?

Looking at http://dddsample.sourceforge.net/architecture.html

In addition to the three vertical layers, there is also the infrastructure. As the the picture shows, it supports all of the three layers in different ways, facilitating communication between the layers

Also, we consider code and configuration files that glues the other layers to the infrastructure as part of the infrastructure layer.

Seems like container factories fits the infraestructure layer as it's glue code between our classes and the infraestructure stuff. So, I think we could move Container namespace under Infrasestructure.

What do you think?

HTTP is User Interface

I'm wondering if these action middlewares should not be moved to user interface namespace.

I think application layer should not depend on UI details, so that we can introduce a new UI (CLI, for example), without changing the application layer.

Refactor Cargo

Remove all attributes except the TrackingId. The team recognized that no additional attributes are required for a Cargo. A Route Specification and an Itinerary are assigned to a Cargo so the system has all information it needs.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.