├── app ├── cache │ └── .gitkeep ├── logs │ └── .gitkeep ├── config │ ├── routing.yml │ ├── config_test.yml │ ├── routing_dev.yml │ ├── parameters.yml │ ├── parameters.yml.dist │ ├── config_prod.yml │ ├── config_dev.yml │ ├── security.yml │ └── config.yml ├── .htaccess ├── AppCache.php ├── autoload.php ├── Resources │ └── views │ │ └── base.html.twig ├── console ├── phpunit.xml.dist ├── AppKernel.php ├── check.php ├── DoctrineMigrations │ └── Version20140609123841.php └── SymfonyRequirements.php ├── .gitignore ├── web ├── favicon.ico ├── apple-touch-icon.png ├── robots.txt ├── bundles │ ├── framework │ │ ├── images │ │ │ ├── logo_symfony.png │ │ │ ├── blue_picto_less.gif │ │ │ ├── blue_picto_more.gif │ │ │ └── grey_magnifier.png │ │ └── css │ │ │ ├── structure.css │ │ │ ├── exception.css │ │ │ └── body.css │ └── sensiodistribution │ │ └── webconfigurator │ │ ├── images │ │ ├── favicon.ico │ │ ├── blue-arrow.png │ │ └── notification.gif │ │ └── css │ │ ├── install.css │ │ └── configurator.css ├── app.php ├── app_dev.php ├── .htaccess └── config.php ├── src └── Leopro │ └── TripPlanner │ ├── PresentationBundle │ ├── Resources │ │ ├── config │ │ │ └── routing.yml │ │ └── views │ │ │ └── Api │ │ │ └── createTrip.html.twig │ ├── PresentationBundle.php │ ├── DependencyInjection │ │ ├── Configuration.php │ │ └── PresentationExtension.php │ ├── Controller │ │ └── ApiController.php │ ├── Form │ │ └── Type │ │ │ └── CreateTripType.php │ └── Tests │ │ └── ApiControllerTest.php │ ├── Domain │ ├── Exception │ │ ├── DomainException.php │ │ ├── DateAlreadyUsedException.php │ │ └── ResourceNotFoundException.php │ ├── Adapter │ │ └── ArrayCollection.php │ ├── ValueObject │ │ ├── TripIdentity.php │ │ ├── Date.php │ │ └── Point.php │ ├── Contract │ │ ├── TripRepository.php │ │ └── Collection.php │ ├── Tests │ │ ├── PointTest.php │ │ ├── LegTest.php │ │ ├── DateTest.php │ │ ├── TripTest.php │ │ └── RouteTest.php │ └── Entity │ │ ├── Location.php │ │ ├── Leg.php │ │ ├── Trip.php │ │ └── Route.php │ ├── Application │ ├── Exception │ │ └── ValidationException.php │ ├── Contract │ │ ├── Command.php │ │ ├── EventListener.php │ │ ├── Validator.php │ │ ├── UseCase.php │ │ └── EventDispatcher.php │ ├── Event │ │ ├── Events.php │ │ ├── PreCommandEvent.php │ │ ├── ExceptionEvent.php │ │ ├── PostCommandEvent.php │ │ └── EventDispatcher.php │ ├── Response │ │ └── Response.php │ ├── UseCase │ │ ├── AbstractUseCase.php │ │ ├── CreateTripUseCase.php │ │ ├── UpdateLocationUseCase.php │ │ └── AddLegToRouteUseCase.php │ ├── Command │ │ ├── CreateTripCommand.php │ │ ├── UpdateLocationCommand.php │ │ ├── AddLegToRouteCommand.php │ │ └── CommandHandler.php │ └── Tests │ │ ├── CreateTripTest.php │ │ ├── AddLegToRouteTest.php │ │ ├── UdpateLocationTest.php │ │ ├── EventDispatcherTest.php │ │ └── CommandHandlerTest.php │ └── InfrastructureBundle │ ├── Resources │ └── config │ │ ├── doctrine │ │ ├── value_object │ │ │ ├── TripIdentity.orm.yml │ │ │ ├── Date.orm.yml │ │ │ └── Point.orm.yml │ │ └── entity │ │ │ ├── Location.orm.yml │ │ │ ├── Leg.orm.yml │ │ │ ├── Trip.orm.yml │ │ │ └── Route.orm.yml │ │ ├── validation.yml │ │ └── services.xml │ ├── Adapter │ ├── DomainApplicationWrappedEvent.php │ ├── Validator.php │ └── EventDispatcher.php │ ├── InfrastructureBundle.php │ ├── DataFixtures │ └── ORM │ │ └── CompleteTripData.php │ ├── DependencyInjection │ ├── Configuration.php │ ├── InfrastructureExtension.php │ └── Compiler │ │ ├── CommandHandlerCompilerPass.php │ │ └── EventDispatcherCompilerPass.php │ ├── Repository │ └── TripRepository.php │ └── Tests │ ├── TripRepositoryTest.php │ └── CreateCompleteTripIntegrationTest.php ├── README.md └── composer.json /app/cache/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/logs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | bin 3 | app/logs/* 4 | app/cache/* 5 | -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leopro/trip-planner/HEAD/web/favicon.ico -------------------------------------------------------------------------------- /web/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leopro/trip-planner/HEAD/web/apple-touch-icon.png -------------------------------------------------------------------------------- /app/config/routing.yml: -------------------------------------------------------------------------------- 1 | api: 2 | resource: "@PresentationBundle/Resources/config/routing.yml" 3 | prefix: / -------------------------------------------------------------------------------- /web/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | # www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449 3 | 4 | User-agent: * 5 | -------------------------------------------------------------------------------- /web/bundles/framework/images/logo_symfony.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leopro/trip-planner/HEAD/web/bundles/framework/images/logo_symfony.png -------------------------------------------------------------------------------- /web/bundles/framework/images/blue_picto_less.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leopro/trip-planner/HEAD/web/bundles/framework/images/blue_picto_less.gif -------------------------------------------------------------------------------- /web/bundles/framework/images/blue_picto_more.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leopro/trip-planner/HEAD/web/bundles/framework/images/blue_picto_more.gif -------------------------------------------------------------------------------- /web/bundles/framework/images/grey_magnifier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leopro/trip-planner/HEAD/web/bundles/framework/images/grey_magnifier.png -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/PresentationBundle/Resources/config/routing.yml: -------------------------------------------------------------------------------- 1 | api: 2 | resource: "@PresentationBundle/Controller" 3 | prefix: / 4 | type: annotation -------------------------------------------------------------------------------- /app/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Require all denied 3 | 4 | 5 | Order deny,allow 6 | Deny from all 7 | 8 | -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Domain/Exception/DomainException.php: -------------------------------------------------------------------------------- 1 | method_to_call 9 | */ 10 | public function getSubscribedEvents(); 11 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/Contract/Validator.php: -------------------------------------------------------------------------------- 1 | id = $id; 12 | } 13 | 14 | public function getId() 15 | { 16 | return $this->id; 17 | } 18 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/Contract/UseCase.php: -------------------------------------------------------------------------------- 1 | content = $content; 12 | } 13 | 14 | public function getContent() 15 | { 16 | return $this->content; 17 | } 18 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/Contract/EventDispatcher.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}Welcome!{% endblock %} 6 | {% block stylesheets %}{% endblock %} 7 | 8 | 9 | 10 | {% block body %}{% endblock %} 11 | {% block javascripts %}{% endblock %} 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/Event/PreCommandEvent.php: -------------------------------------------------------------------------------- 1 | command = $command; 14 | } 15 | 16 | public function getCommand() 17 | { 18 | return $this->command; 19 | } 20 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/UseCase/AbstractUseCase.php: -------------------------------------------------------------------------------- 1 | getManagedCommand()) { 11 | throw new \LogicException($commandClass . ' is not a managed command'); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/InfrastructureBundle/Resources/config/doctrine/entity/Location.orm.yml: -------------------------------------------------------------------------------- 1 | Leopro\TripPlanner\Domain\Entity\Location: 2 | type: entity 3 | table: location 4 | id: 5 | internalIdentity: 6 | type: integer 7 | generator: 8 | strategy: AUTO 9 | embedded: 10 | point: 11 | class: Leopro\TripPlanner\Domain\ValueObject\Point 12 | fields: 13 | name: 14 | type: string 15 | length: 250 -------------------------------------------------------------------------------- /app/config/parameters.yml: -------------------------------------------------------------------------------- 1 | # This file is auto-generated during the composer install 2 | parameters: 3 | database_driver: pdo_mysql 4 | database_host: 127.0.0.1 5 | database_port: null 6 | database_name: trip_planner 7 | database_user: root 8 | database_password: null 9 | mailer_transport: smtp 10 | mailer_host: 127.0.0.1 11 | mailer_user: null 12 | mailer_password: null 13 | locale: en 14 | secret: ThisTokenIsNotSoSecretChangeIt 15 | debug_toolbar: true 16 | debug_redirects: false 17 | use_assetic_controller: true 18 | -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Domain/Contract/TripRepository.php: -------------------------------------------------------------------------------- 1 | assertEquals(89, $firstPoint->getCartographicDistance($secondPoint)); 15 | $this->assertEquals(98, $firstPoint->getApproximateRoadDistance($secondPoint)); 16 | } 17 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/InfrastructureBundle/Adapter/DomainApplicationWrappedEvent.php: -------------------------------------------------------------------------------- 1 | domainApplicationEvent = $domainApplicationEvent; 14 | } 15 | 16 | public function getDomainApplicationEvent() 17 | { 18 | return $this->domainApplicationEvent; 19 | } 20 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Domain/ValueObject/Date.php: -------------------------------------------------------------------------------- 1 | input = \DateTime::createFromFormat($format, $input); 13 | $this->format = $format; 14 | } 15 | 16 | public function getFormattedDate($format = null) 17 | { 18 | if (!$format) { 19 | $format = $this->format; 20 | } 21 | 22 | return $this->input->format($format); 23 | } 24 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/Event/ExceptionEvent.php: -------------------------------------------------------------------------------- 1 | command = $command; 15 | } 16 | 17 | public function getCommand() 18 | { 19 | return $this->command; 20 | } 21 | 22 | public function getException() 23 | { 24 | return $this->exception; 25 | } 26 | } -------------------------------------------------------------------------------- /app/config/config_prod.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: config.yml } 3 | 4 | #framework: 5 | # validation: 6 | # cache: apc 7 | 8 | #doctrine: 9 | # orm: 10 | # metadata_cache_driver: apc 11 | # result_cache_driver: apc 12 | # query_cache_driver: apc 13 | 14 | monolog: 15 | handlers: 16 | main: 17 | type: fingers_crossed 18 | action_level: error 19 | handler: nested 20 | nested: 21 | type: stream 22 | path: "%kernel.logs_dir%/%kernel.environment%.log" 23 | level: debug 24 | console: 25 | type: console 26 | -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/InfrastructureBundle/Resources/config/doctrine/entity/Leg.orm.yml: -------------------------------------------------------------------------------- 1 | Leopro\TripPlanner\Domain\Entity\Leg: 2 | type: entity 3 | table: leg 4 | id: 5 | internalIdentity: 6 | type: integer 7 | generator: 8 | strategy: AUTO 9 | embedded: 10 | date: 11 | class: Leopro\TripPlanner\Domain\ValueObject\Date 12 | oneToOne: 13 | location: 14 | targetEntity: Leopro\TripPlanner\Domain\Entity\Location 15 | joinColumn: 16 | name: internalIdentity 17 | referencedColumnName: internalIdentity 18 | cascade: ["persist"] -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/Event/PostCommandEvent.php: -------------------------------------------------------------------------------- 1 | command = $command; 16 | } 17 | 18 | public function getCommand() 19 | { 20 | return $this->command; 21 | } 22 | 23 | public function getResponse() 24 | { 25 | return $this->response; 26 | } 27 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/Command/CreateTripCommand.php: -------------------------------------------------------------------------------- 1 | name = $name; 15 | } 16 | 17 | public function getRequest() 18 | { 19 | return new ArrayCollection( 20 | array( 21 | 'name' => $this->name 22 | ) 23 | ); 24 | } 25 | 26 | /** 27 | * @return mixed 28 | */ 29 | public function getName() 30 | { 31 | return $this->name; 32 | } 33 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/InfrastructureBundle/InfrastructureBundle.php: -------------------------------------------------------------------------------- 1 | addCompilerPass(new CommandHandlerCompilerPass()); 17 | $container->addCompilerPass(new EventDispatcherCompilerPass()); 18 | } 19 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/InfrastructureBundle/Resources/config/doctrine/entity/Trip.orm.yml: -------------------------------------------------------------------------------- 1 | Leopro\TripPlanner\Domain\Entity\Trip: 2 | type: entity 3 | table: trip 4 | embedded: 5 | identity: 6 | class: Leopro\TripPlanner\Domain\ValueObject\TripIdentity 7 | fields: 8 | name: 9 | type: string 10 | length: 250 11 | manyToMany: 12 | routes: 13 | targetEntity: Leopro\TripPlanner\Domain\Entity\Route 14 | joinTable: 15 | name: trip_routes 16 | joinColumns: 17 | link_id: 18 | referencedColumnName: identity_id 19 | inverseJoinColumns: 20 | report_id: 21 | referencedColumnName: internalIdentity 22 | cascade: ["persist"] -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Domain/Contract/Collection.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('Leopro\TripPlanner\Domain\Entity\Leg', $leg); 14 | 15 | $location = $leg->getLocation(); 16 | $this->assertInstanceOf('Leopro\TripPlanner\Domain\Entity\Location', $location); 17 | 18 | $point = $location->getPoint(); 19 | $this->assertInstanceOf('Leopro\TripPlanner\Domain\ValueObject\Point', $point); 20 | $this->assertEquals(-3.386665, $point->getLatitude()); 21 | $this->assertEquals(36.736908, $point->getLongitude()); 22 | } 23 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Domain/Tests/DateTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($expected, $date->getFormattedDate($formatOutput)); 17 | } 18 | 19 | public function provider() 20 | { 21 | return array( 22 | array('2014-6-1', 'Y-m-d', 'Y-m-d', '2014-06-01'), 23 | array('1-6-2014', 'd-m-Y', 'Y-m-d', '2014-06-01'), 24 | array('2014-6-1', 'Y-m-d', 'd-m-Y', '01-06-2014'), 25 | array('01/01/2014', 'd/m/Y', null, '01/01/2014'), 26 | ); 27 | } 28 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/Tests/CreateTripTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('Leopro\TripPlanner\Domain\Contract\TripRepository') 13 | ->disableOriginalConstructor() 14 | ->getMock(); 15 | 16 | $tripRepository 17 | ->expects($this->once()) 18 | ->method('add'); 19 | 20 | $command = new CreateTripCommand('my trip'); 21 | $useCase = new CreateTripUseCase($tripRepository); 22 | 23 | $trip = $useCase->run($command); 24 | 25 | $this->assertInstanceOf('Leopro\TripPlanner\Domain\Entity\Trip', $trip); 26 | } 27 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Domain/Entity/Location.php: -------------------------------------------------------------------------------- 1 | name = $name; 17 | $this->point = $point; 18 | } 19 | 20 | public static function create($name, $latitude, $longitude) 21 | { 22 | return new self( 23 | $name, 24 | new Point($latitude, $longitude) 25 | ); 26 | } 27 | 28 | public function updateName($name) 29 | { 30 | $this->name = $name; 31 | } 32 | 33 | public function getPoint() 34 | { 35 | return $this->point; 36 | } 37 | 38 | public function getName() 39 | { 40 | return $this->name; 41 | } 42 | } -------------------------------------------------------------------------------- /app/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getParameterOption(array('--env', '-e'), getenv('SYMFONY_ENV') ?: 'dev'); 19 | $debug = getenv('SYMFONY_DEBUG') !== '0' && !$input->hasParameterOption(array('--no-debug', '')) && $env !== 'prod'; 20 | 21 | if ($debug) { 22 | Debug::enable(); 23 | } 24 | 25 | $kernel = new AppKernel($env, $debug); 26 | $application = new Application($kernel); 27 | $application->run($input); 28 | -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Domain/Entity/Leg.php: -------------------------------------------------------------------------------- 1 | date = $date; 17 | $this->location = $location; 18 | } 19 | 20 | public static function create($date, $dateFormat, $latitude, $longitude) 21 | { 22 | $date = new Date($date, $dateFormat); 23 | return new self( 24 | $date, 25 | Location::create($date->getFormattedDate(), $latitude, $longitude) 26 | ); 27 | } 28 | 29 | public function getDate() 30 | { 31 | return $this->date; 32 | } 33 | 34 | public function getLocation() 35 | { 36 | return $this->location; 37 | } 38 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/InfrastructureBundle/DataFixtures/ORM/CompleteTripData.php: -------------------------------------------------------------------------------- 1 | getRoutes()->first(); 19 | $route->addLeg('01/01/2014', -3.386665, 36.736908, 'd/m/Y'); 20 | 21 | $manager->persist($trip); 22 | 23 | $manager->flush(); 24 | } 25 | 26 | public function getOrder() 27 | { 28 | return 1; 29 | } 30 | } -------------------------------------------------------------------------------- /app/config/config_dev.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: config.yml } 3 | 4 | framework: 5 | router: 6 | resource: "%kernel.root_dir%/config/routing_dev.yml" 7 | strict_requirements: true 8 | profiler: { only_exceptions: false } 9 | 10 | web_profiler: 11 | toolbar: %debug_toolbar% 12 | intercept_redirects: %debug_redirects% 13 | 14 | monolog: 15 | handlers: 16 | main: 17 | type: stream 18 | path: "%kernel.logs_dir%/%kernel.environment%.log" 19 | level: debug 20 | console: 21 | type: console 22 | bubble: false 23 | # uncomment to get logging in your browser 24 | # you may have to allow bigger header sizes in your Web server configuration 25 | #firephp: 26 | # type: firephp 27 | # level: info 28 | #chromephp: 29 | # type: chromephp 30 | # level: info 31 | 32 | assetic: 33 | use_controller: %use_assetic_controller% 34 | 35 | #swiftmailer: 36 | # delivery_address: me@example.com 37 | -------------------------------------------------------------------------------- /web/app.php: -------------------------------------------------------------------------------- 1 | unregister(); 14 | $apcLoader->register(true); 15 | */ 16 | 17 | require_once __DIR__.'/../app/AppKernel.php'; 18 | //require_once __DIR__.'/../app/AppCache.php'; 19 | 20 | $kernel = new AppKernel('prod', false); 21 | $kernel->loadClassCache(); 22 | //$kernel = new AppCache($kernel); 23 | 24 | // When using the HttpCache, you need to call the method in your front controller instead of relying on the configuration parameter 25 | //Request::enableHttpMethodParameterOverride(); 26 | $request = Request::createFromGlobals(); 27 | $response = $kernel->handle($request); 28 | $response->send(); 29 | $kernel->terminate($request, $response); 30 | -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/InfrastructureBundle/Adapter/Validator.php: -------------------------------------------------------------------------------- 1 | validator = $validator; 16 | } 17 | 18 | /** 19 | * @param $value 20 | * @return \Leopro\TripPlanner\Domain\Contract\Collection 21 | */ 22 | public function validate($value) 23 | { 24 | $applicationErrors = new ArrayCollection(); 25 | $errors = $this->validator->validate($value); 26 | foreach ($errors as $error) { 27 | $applicationErrors->set($error->getPropertyPath(), $error->getMessage()); 28 | } 29 | 30 | return $applicationErrors; 31 | } 32 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/PresentationBundle/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | root('presentation'); 22 | 23 | // Here you should define the parameters that are allowed to 24 | // configure your bundle. See the documentation linked above for 25 | // more information on that topic. 26 | 27 | return $treeBuilder; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/InfrastructureBundle/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | root('infrastructure'); 22 | 23 | // Here you should define the parameters that are allowed to 24 | // configure your bundle. See the documentation linked above for 25 | // more information on that topic. 26 | 27 | return $treeBuilder; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/PresentationBundle/DependencyInjection/PresentationExtension.php: -------------------------------------------------------------------------------- 1 | processConfiguration($configuration, $configs); 24 | 25 | $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 26 | //$loader->load('services.xml'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/InfrastructureBundle/DependencyInjection/InfrastructureExtension.php: -------------------------------------------------------------------------------- 1 | processConfiguration($configuration, $configs); 24 | 25 | $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 26 | $loader->load('services.xml'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Domain/Tests/TripTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('Leopro\TripPlanner\Domain\Entity\Trip', $trip); 14 | $this->assertEquals(1, $trip->getRoutes()->count()); 15 | $this->assertInstanceOf('Leopro\TripPlanner\Domain\Entity\Route', $trip->getRoutes()->first()); 16 | $this->assertEquals('first route for trip: my first planning', $trip->getRoutes()->first()->getName()); 17 | } 18 | 19 | public function testADuplicatedRouteCouldBeAdded() 20 | { 21 | $trip = Trip::createWithFirstRoute(new TripIdentity(1), 'my first planning'); 22 | $trip->duplicateRoute(null); 23 | $this->assertEquals(2, $trip->getRoutes()->count()); 24 | } 25 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/InfrastructureBundle/DependencyInjection/Compiler/CommandHandlerCompilerPass.php: -------------------------------------------------------------------------------- 1 | hasDefinition('command_handler')) { 14 | return; 15 | } 16 | 17 | $definition = $container->getDefinition('command_handler'); 18 | $taggedServices = $container->findTaggedServiceIds('use_case'); 19 | 20 | $useCases = array(); 21 | 22 | foreach ($taggedServices as $id => $attributes) { 23 | $useCases[] = new Reference($id); 24 | } 25 | 26 | $definition->addMethodCall( 27 | 'registerCommands', 28 | array($useCases) 29 | ); 30 | } 31 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/InfrastructureBundle/Adapter/EventDispatcher.php: -------------------------------------------------------------------------------- 1 | eventDispatcher = $eventDispatcher; 16 | } 17 | 18 | public function getSubscribedEvents() 19 | { 20 | return array( 21 | Events::PRE_COMMAND => 'onDomainApplicationEvent', 22 | Events::POST_COMMAND => 'onDomainApplicationEvent', 23 | Events::EXCEPTION => 'onDomainApplicationEvent' 24 | ); 25 | } 26 | 27 | public function onDomainApplicationEvent($event) 28 | { 29 | $wrapped = new DomainApplicationWrappedEvent($event); 30 | 31 | $this->eventDispatcher->dispatch('domain-application-event', $wrapped); 32 | } 33 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/InfrastructureBundle/DependencyInjection/Compiler/EventDispatcherCompilerPass.php: -------------------------------------------------------------------------------- 1 | hasDefinition('application.event_dispatcher')) { 14 | return; 15 | } 16 | 17 | $definition = $container->getDefinition('application.event_dispatcher'); 18 | $taggedServices = $container->findTaggedServiceIds('event_dispatcher_listener'); 19 | 20 | $listeners = array(); 21 | 22 | foreach ($taggedServices as $id => $attributes) { 23 | $listeners[] = new Reference($id); 24 | } 25 | 26 | $definition->addMethodCall( 27 | 'registerListeners', 28 | array($listeners) 29 | ); 30 | } 31 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/PresentationBundle/Controller/ApiController.php: -------------------------------------------------------------------------------- 1 | createForm(new CreateTripType()); 24 | $form->handleRequest($request); 25 | 26 | if ($form->isValid()) { 27 | $trip = $this->get('command_handler')->execute($form->getData()); 28 | 29 | return new Response('ok'); 30 | } 31 | 32 | return array( 33 | 'form' => $form->createView(), 34 | ); 35 | } 36 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/UseCase/CreateTripUseCase.php: -------------------------------------------------------------------------------- 1 | tripRepository = $tripRepository; 18 | } 19 | 20 | public function getManagedCommand() 21 | { 22 | return 'Leopro\TripPlanner\Application\Command\CreateTripCommand'; 23 | } 24 | 25 | public function run(Command $command) 26 | { 27 | $this->exceptionIfCommandNotManaged($command); 28 | 29 | $request = $command->getRequest(); 30 | 31 | $trip = Trip::createWithFirstRoute( 32 | new TripIdentity(uniqid()), 33 | $request->get('name') 34 | ); 35 | 36 | $this->tripRepository->add($trip); 37 | 38 | return $trip; 39 | } 40 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/PresentationBundle/Form/Type/CreateTripType.php: -------------------------------------------------------------------------------- 1 | add('name') 17 | ->add('save', 'submit'); 18 | } 19 | 20 | public function getName() 21 | { 22 | return 'trip'; 23 | } 24 | 25 | public function setDefaultOptions(OptionsResolverInterface $resolver) 26 | { 27 | $resolver->setDefaults(array( 28 | 'data_class' => 'Leopro\TripPlanner\Application\Command\CreateTripCommand', 29 | 'empty_data' => function (FormInterface $form) { 30 | $command = new CreateTripCommand( 31 | $form->get('name')->getData() 32 | ); 33 | 34 | return $command; 35 | }, 36 | )); 37 | } 38 | } -------------------------------------------------------------------------------- /app/config/security.yml: -------------------------------------------------------------------------------- 1 | security: 2 | encoders: 3 | Symfony\Component\Security\Core\User\User: plaintext 4 | 5 | role_hierarchy: 6 | ROLE_ADMIN: ROLE_USER 7 | ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] 8 | 9 | providers: 10 | in_memory: 11 | memory: 12 | users: 13 | user: { password: userpass, roles: [ 'ROLE_USER' ] } 14 | admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] } 15 | 16 | firewalls: 17 | dev: 18 | pattern: ^/(_(profiler|wdt)|css|images|js)/ 19 | security: false 20 | 21 | demo_login: 22 | pattern: ^/demo/secured/login$ 23 | security: false 24 | 25 | demo_secured_area: 26 | pattern: ^/demo/secured/ 27 | form_login: 28 | check_path: _demo_security_check 29 | login_path: _demo_login 30 | logout: 31 | path: _demo_logout 32 | target: _demo 33 | #anonymous: ~ 34 | #http_basic: 35 | # realm: "Secured Demo Area" 36 | 37 | access_control: 38 | #- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https } -------------------------------------------------------------------------------- /web/app_dev.php: -------------------------------------------------------------------------------- 1 | loadClassCache(); 27 | $request = Request::createFromGlobals(); 28 | $response = $kernel->handle($request); 29 | $response->send(); 30 | $kernel->terminate($request, $response); 31 | -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/InfrastructureBundle/Repository/TripRepository.php: -------------------------------------------------------------------------------- 1 | em = $em; 17 | } 18 | 19 | /** 20 | * @param TripIdentity $identity 21 | * @return \Leopro\TripPlanner\Domain\Entity\Trip 22 | */ 23 | public function get(TripIdentity $identity) 24 | { 25 | $qb = $this->em->createQueryBuilder() 26 | ->select('t') 27 | ->from("TripPlannerDomain:Trip", 't') 28 | ->where('t.identity.id = :identity'); 29 | 30 | $qb->setParameter('identity', $identity); 31 | 32 | return $qb->getQuery()->getOneOrNullResult(); 33 | } 34 | 35 | /** 36 | * @param Trip $trip 37 | * @return void 38 | */ 39 | public function add(Trip $trip) 40 | { 41 | $this->em->persist($trip); 42 | $this->em->flush(); 43 | } 44 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/PresentationBundle/Tests/ApiControllerTest.php: -------------------------------------------------------------------------------- 1 | request('GET', '/'); 14 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 15 | 16 | $form = $crawler->filter('#trip_save')->form(); 17 | $client->submit( 18 | $form, 19 | array( 20 | 21 | ) 22 | ); 23 | 24 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 25 | $this->assertContains('This value should not be blank', $client->getCrawler()->filter('body')->text()); 26 | 27 | $form = $crawler->filter('#trip_save')->form(); 28 | $client->submit( 29 | $form, 30 | array( 31 | 'trip[name]' => 'my new wonderful trip' 32 | ) 33 | ); 34 | 35 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 36 | $this->assertContains('ok', $client->getCrawler()->filter('body')->text()); 37 | } 38 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/Command/UpdateLocationCommand.php: -------------------------------------------------------------------------------- 1 | routeIdentity = $routeIdentity; 19 | $this->date = $date; 20 | $this->name = $name; 21 | } 22 | 23 | public function getRequest() 24 | { 25 | return new ArrayCollection( 26 | array( 27 | 'routeIdentity' => $this->routeIdentity, 28 | 'date' => $this->date, 29 | 'name' => $this->name 30 | ) 31 | ); 32 | } 33 | 34 | /** 35 | * @return mixed 36 | */ 37 | public function getDate() 38 | { 39 | return $this->date; 40 | } 41 | 42 | /** 43 | * @return mixed 44 | */ 45 | public function getName() 46 | { 47 | return $this->name; 48 | } 49 | 50 | /** 51 | * @return mixed 52 | */ 53 | public function getRouteIdentity() 54 | { 55 | return $this->routeIdentity; 56 | } 57 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/Event/EventDispatcher.php: -------------------------------------------------------------------------------- 1 | listeners[] = $listener; 21 | } else { 22 | throw new \LogicException('EventDispatcher registerListeners expects an array of EventListener'); 23 | } 24 | } 25 | } 26 | 27 | /** 28 | * @param $event 29 | */ 30 | public function notify($name, $event) 31 | { 32 | foreach ($this->listeners as $listener) { 33 | $subscribedEvents = $listener->getSubscribedEvents(); 34 | if (is_array($subscribedEvents) && count($subscribedEvents) > 0 && array_key_exists($name, $subscribedEvents)) { 35 | $method = $subscribedEvents[$name]; 36 | call_user_func_array(array($listener, $method), array($event)); 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/Tests/AddLegToRouteTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(0, $trip->getRoute(null)->getLegs()->count()); 16 | 17 | $tripRepository = $this->getMockBuilder('Leopro\TripPlanner\Domain\Contract\TripRepository') 18 | ->disableOriginalConstructor() 19 | ->getMock(); 20 | 21 | $tripRepository 22 | ->expects($this->once()) 23 | ->method('get') 24 | ->will($this->returnValue($trip)); 25 | 26 | $tripRepository 27 | ->expects($this->once()) 28 | ->method('add'); 29 | 30 | $command = new AddLegToRouteCommand('abcde', null, '01/01/2014', 'd/m/Y', -3.386665, 36.736908); 31 | $useCase = new AddLegToRouteUseCase($tripRepository); 32 | 33 | $trip = $useCase->run($command); 34 | 35 | $this->assertInstanceOf('Leopro\TripPlanner\Domain\Entity\Trip', $trip); 36 | $this->assertEquals(1, $trip->getRoute(null)->getLegs()->count()); 37 | } 38 | } -------------------------------------------------------------------------------- /web/bundles/framework/css/structure.css: -------------------------------------------------------------------------------- 1 | html { 2 | background: #eee; 3 | } 4 | body { 5 | font: 11px Verdana, Arial, sans-serif; 6 | color: #333; 7 | } 8 | .sf-reset, .sf-reset .block, .sf-reset #message { 9 | margin: auto; 10 | } 11 | img { 12 | border: 0; 13 | } 14 | .clear { 15 | clear: both; 16 | height: 0; 17 | font-size: 0; 18 | line-height: 0; 19 | } 20 | .clear-fix:after { 21 | content: "\0020"; 22 | display: block; 23 | height: 0; 24 | clear: both; 25 | visibility: hidden; 26 | } 27 | .clear-fix { 28 | display: inline-block; 29 | } 30 | * html .clear-fix { 31 | height: 1%; 32 | } 33 | .clear-fix { 34 | display: block; 35 | } 36 | .header { 37 | padding: 30px 30px 20px 30px; 38 | } 39 | .header-logo { 40 | float: left; 41 | } 42 | .search { 43 | float: right; 44 | padding-top: 20px; 45 | } 46 | .search label { 47 | line-height: 28px; 48 | vertical-align: middle; 49 | } 50 | .search input { 51 | width: 195px; 52 | font-size: 12px; 53 | border: 1px solid #dadada; 54 | background: #fff url(data:image/gif;base64,R0lGODlhAQAFAKIAAPX19e/v7/39/fr6+urq6gAAAAAAAAAAACH5BAAAAAAALAAAAAABAAUAAAMESAEjCQA7) repeat-x left top; 55 | padding: 5px 6px; 56 | color: #565656; 57 | } 58 | .search input[type="search"] { 59 | -webkit-appearance: textfield; 60 | } 61 | #content { 62 | width: 970px; 63 | margin: 0 auto; 64 | } 65 | pre { 66 | white-space: normal; 67 | font-family: Arial, Helvetica, sans-serif; 68 | } 69 | -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/Tests/UdpateLocationTest.php: -------------------------------------------------------------------------------- 1 | getRoute(null); 16 | $route->addLeg('01-01-2014', -3.386665, 36.736908); 17 | 18 | $tripRepository = $this->getMockBuilder('Leopro\TripPlanner\Domain\Contract\TripRepository') 19 | ->disableOriginalConstructor() 20 | ->getMock(); 21 | 22 | $tripRepository 23 | ->expects($this->once()) 24 | ->method('get') 25 | ->will($this->returnValue($trip)); 26 | 27 | $tripRepository 28 | ->expects($this->once()) 29 | ->method('add'); 30 | 31 | $command = new UpdateLocationCommand(null, '01-01-2014', 'new location name'); 32 | $useCase = new UpdateLocationUseCase($tripRepository); 33 | 34 | $trip = $useCase->run($command); 35 | 36 | $this->assertInstanceOf('Leopro\TripPlanner\Domain\Entity\Trip', $trip); 37 | $this->assertEquals('new location name', $route->getLegs()->first()->getLocation()->getName()); 38 | } 39 | } -------------------------------------------------------------------------------- /app/phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | 17 | 18 | ../src/*/*Bundle/Tests 19 | ../src/*/*/*Bundle/Tests 20 | ../src/*/Bundle/*Bundle/Tests 21 | ../src/*/*/*/Tests 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | ../src 34 | 35 | ../src/*/*Bundle/Resources 36 | ../src/*/*Bundle/Tests 37 | ../src/*/Bundle/*Bundle/Resources 38 | ../src/*/Bundle/*Bundle/Tests 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Domain/Entity/Trip.php: -------------------------------------------------------------------------------- 1 | identity = $identity; 22 | $this->name = $name; 23 | $this->routes = new ArrayCollection(); 24 | } 25 | 26 | public static function createWithFirstRoute(TripIdentity $identity, $name) 27 | { 28 | $trip = new self($identity, $name); 29 | $trip->routes->add(Route::create($trip->name)); 30 | 31 | return $trip; 32 | } 33 | 34 | public function getRoutes() 35 | { 36 | return $this->routes; 37 | } 38 | 39 | public function getRoute($routeId) 40 | { 41 | foreach ($this->routes as $route) { 42 | if ($routeId == $route->getInternalIdentity()) { 43 | return $route; 44 | } 45 | } 46 | 47 | throw new ResourceNotFoundException(sprintf('Route with identity \'%s\' not found', $routeId)); 48 | } 49 | 50 | public function duplicateRoute($routeId) 51 | { 52 | $route = $this->getRoute($routeId); 53 | $this->routes->add($route->duplicate()); 54 | } 55 | 56 | public function getIdentity() 57 | { 58 | return $this->identity; 59 | } 60 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/UseCase/UpdateLocationUseCase.php: -------------------------------------------------------------------------------- 1 | tripRepository = $tripRepository; 19 | } 20 | 21 | public function getManagedCommand() 22 | { 23 | return 'Leopro\TripPlanner\Application\Command\UpdateLocationCommand'; 24 | } 25 | 26 | public function run(Command $command) 27 | { 28 | $this->exceptionIfCommandNotManaged($command); 29 | 30 | $request = $command->getRequest(); 31 | 32 | $trip = $this->tripRepository->get( 33 | new TripIdentity($request->get('tripIdentity')) 34 | ); 35 | 36 | if (!$trip) { 37 | throw new ResourceNotFoundException( 38 | sprintf('Trip with identity \'%s\' not found', $request->get('tripIdentity')) 39 | ); 40 | } 41 | 42 | $route = $trip->getRoute($request->get('routeIdentity')); 43 | $leg = $route->getLegByDate($request->get('date')); 44 | $leg->getLocation()->updateName($request->get('name')); 45 | 46 | $this->tripRepository->add($trip); 47 | 48 | return $trip; 49 | } 50 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/InfrastructureBundle/Tests/TripRepositoryTest.php: -------------------------------------------------------------------------------- 1 | boot(); 16 | $this->em = static::$kernel->getContainer() 17 | ->get('doctrine') 18 | ->getManager(); 19 | 20 | $this->repo = new TripRepository($this->em); 21 | } 22 | 23 | public function testGet() 24 | { 25 | $trip = $this->repo->get(new TripIdentity('abcd')); 26 | $this->assertInstanceOf('Leopro\TripPlanner\Domain\Entity\Trip', $trip); 27 | } 28 | 29 | public function testAdd() 30 | { 31 | $identity = uniqid(); 32 | $tripIdentity = new TripIdentity($identity); 33 | $trip = Trip::createWithFirstRoute($tripIdentity, 'another trip'); 34 | 35 | $this->repo->add($trip); 36 | 37 | $trip = $this->em->getRepository('TripPlannerDomain:Trip')->findOneBy(array('name' => 'another trip')); 38 | $this->assertInstanceOf('Leopro\TripPlanner\Domain\Entity\Trip', $trip); 39 | } 40 | 41 | public function tearDown() 42 | { 43 | $trip = $this->em->getRepository('TripPlannerDomain:Trip')->findOneBy(array('name' => 'another trip')); 44 | if ($trip) { 45 | $this->em->remove($trip); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /app/AppKernel.php: -------------------------------------------------------------------------------- 1 | getEnvironment(), array('dev', 'test'))) { 27 | $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); 28 | $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle(); 29 | $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle(); 30 | } 31 | 32 | return $bundles; 33 | } 34 | 35 | public function registerContainerConfiguration(LoaderInterface $loader) 36 | { 37 | $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/UseCase/AddLegToRouteUseCase.php: -------------------------------------------------------------------------------- 1 | tripRepository = $tripRepository; 18 | } 19 | 20 | public function getManagedCommand() 21 | { 22 | return 'Leopro\TripPlanner\Application\Command\AddLegToRouteCommand'; 23 | } 24 | 25 | public function run(Command $command) 26 | { 27 | $this->exceptionIfCommandNotManaged($command); 28 | 29 | $request = $command->getRequest(); 30 | 31 | $trip = $this->tripRepository->get( 32 | new TripIdentity($request->get('tripIdentity')) 33 | ); 34 | 35 | if (!$trip) { 36 | throw new ResourceNotFoundException( 37 | sprintf('Trip with identity \'%s\' not found', $request->get('tripIdentity')) 38 | ); 39 | } 40 | 41 | $route = $trip->getRoute($request->get('routeIdentity')); 42 | $route->addLeg( 43 | $request->get('date'), 44 | $request->get('latitude'), 45 | $request->get('longitude'), 46 | $request->get('dateFormat') 47 | ); 48 | 49 | $this->tripRepository->add($trip); 50 | 51 | return $trip; 52 | } 53 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Domain/Tests/RouteTest.php: -------------------------------------------------------------------------------- 1 | addLeg('06-06-2014', -3.386665, 36.736908); 13 | 14 | $this->assertEquals(1, $route->getLegs()->count()); 15 | } 16 | 17 | public function testAnotherLegToTrip() 18 | { 19 | $route = Route::create('my first trip'); 20 | $route->addLeg('06-06-2014', -3.386665, 36.736908); 21 | $route->addLeg('07-06-2014', -3.386665, 36.736908); 22 | 23 | $this->assertEquals(2, $route->getLegs()->count()); 24 | } 25 | 26 | /** 27 | * @expectedException \Leopro\TripPlanner\Domain\Exception\DateAlreadyUsedException 28 | */ 29 | public function testNoDuplicationDateForTheRoute() 30 | { 31 | $route = Route::create('my first trip'); 32 | $route->addLeg('06-06-2014', -3.386665, 36.736908); 33 | $route->addLeg('06-06-2014', -3.386665, 36.736908); 34 | } 35 | 36 | public function testRouteKnowsApproximateRoadDistance() 37 | { 38 | $route = Route::create('my first trip'); 39 | $route->addLeg('06-06-2014', -3.386665, 36.736908); 40 | $route->addLeg('07-06-2014', -3.428112, 35.932846); 41 | $route->addLeg('08-06-2014', -3.112770, 35.644455); 42 | 43 | $distance = $route->getApproximateRoadDistance(); 44 | 45 | $this->assertEquals(150, $distance); 46 | } 47 | 48 | public function testRouteCouldBeDuplicated() 49 | { 50 | $route = Route::create('my first trip'); 51 | $routeDuplicated = $route->duplicate(); 52 | 53 | $comparison = $route === $routeDuplicated; 54 | 55 | $this->assertFalse($comparison); 56 | } 57 | } -------------------------------------------------------------------------------- /web/bundles/sensiodistribution/webconfigurator/css/install.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 14px; 3 | font-family: "Lucida Sans Unicode", "Lucida Grande", Verdana, Arial, Helvetica, sans-serif; 4 | } 5 | .sf-reset h1.title { 6 | font-size: 45px; 7 | padding-bottom: 30px; 8 | } 9 | .sf-reset h2 { 10 | font-weight: bold; 11 | color: #FFFFFF; 12 | /* Font is reset to sans-serif (like body) */ 13 | font-family: "Lucida Sans Unicode", "Lucida Grande", Verdana, Arial, Helvetica, sans-serif; 14 | margin-bottom: 10px; 15 | background-color: #aacd4e; 16 | padding: 2px 4px; 17 | display: inline-block; 18 | text-transform: uppercase; 19 | } 20 | .sf-reset ul a, 21 | .sf-reset ul a:hover { 22 | background: url(../images/blue-arrow.png) no-repeat right 6px; 23 | padding-right: 10px; 24 | } 25 | .sf-reset ul, ol { 26 | padding-left: 20px; 27 | } 28 | .sf-reset li { 29 | padding-bottom: 18px; 30 | } 31 | .sf-reset ol li { 32 | list-style-type: decimal; 33 | } 34 | .sf-reset ul li { 35 | list-style-type: none; 36 | } 37 | .sf-reset .symfony-blocks-install { 38 | overflow: hidden; 39 | } 40 | .sf-reset .symfony-install-continue { 41 | font-size: 0.95em; 42 | padding-left: 0; 43 | } 44 | .sf-reset .symfony-install-continue li { 45 | padding-bottom: 10px; 46 | } 47 | .sf-reset .ok { 48 | color: #fff; 49 | font-family: "Lucida Sans Unicode", "Lucida Grande", Verdana, Arial, Helvetica, sans-serif; 50 | background-color: #6d6; 51 | padding: 10px; 52 | margin-bottom: 20px; 53 | } 54 | .sf-reset .ko { 55 | background-color: #d66; 56 | } 57 | .version { 58 | text-align: right; 59 | font-size: 10px; 60 | margin-right: 20px; 61 | } 62 | .sf-reset a, 63 | .sf-reset li a { 64 | color: #08C; 65 | text-decoration: none; 66 | } 67 | .sf-reset a:hover, 68 | .sf-reset li a:hover { 69 | color: #08C; 70 | text-decoration: underline; 71 | } 72 | .sf-reset textarea { 73 | padding: 7px; 74 | } 75 | -------------------------------------------------------------------------------- /web/bundles/sensiodistribution/webconfigurator/css/configurator.css: -------------------------------------------------------------------------------- 1 | @import url("install.css"); 2 | 3 | .step h1 { 4 | margin-top: 10px; 5 | margin-bottom: 30px; 6 | font-size: 26px; 7 | } 8 | .step p { 9 | line-height: 20px; 10 | padding-bottom: 20px; 11 | } 12 | .step .symfony-block-steps span { 13 | display: inline-block; 14 | padding: 2px 3px; 15 | font-size: 11px; 16 | line-height: 15px; 17 | color: #868686; 18 | font-weight: bold; 19 | text-transform: uppercase; 20 | } 21 | .step .symfony-block-steps span.selected { 22 | background-color: #aacd4e; 23 | color: #FFFFFF; 24 | } 25 | .step .symfony-form-row { 26 | padding-bottom: 40px; 27 | } 28 | .step .symfony-form-column { 29 | width: 430px; 30 | float: left; 31 | } 32 | .step .symfony-form-footer { 33 | padding-top: 20px; 34 | clear: both; 35 | } 36 | .step .symfony-form-field { 37 | height: 20px; 38 | } 39 | .step .symfony-form-row label { 40 | display: block; 41 | padding-bottom: 8px; 42 | } 43 | .step .symfony-form-field input[type=text], 44 | .step .symfony-form-field input[type=password], 45 | .step .symfony-form-field textarea, 46 | .step .symfony-form-field select { 47 | font-size: 13px; 48 | color: #565656; 49 | width: 200px; 50 | } 51 | .step .symfony-form-field input[type=text], 52 | .step .symfony-form-field input[type=password], 53 | .step .symfony-form-field textarea { 54 | border: 1px solid #dadada; 55 | background: #FFFFFF url(../../../framework/images/input_bg.gif) repeat-x left top; 56 | width: 194px; 57 | padding: 5px 6px; 58 | } 59 | .step .symfony-form-errors ul { 60 | padding: 0; 61 | } 62 | .step .symfony-form-errors li { 63 | background: url(../images/notification.gif) no-repeat left 6px; 64 | font-size: 11px; 65 | line-height: 16px; 66 | color: #759e1a; 67 | padding: 10px 25px; 68 | } 69 | .step .symfony-configuration { 70 | margin: 10px 0; 71 | width: 100%; 72 | height: 240px; 73 | } 74 | -------------------------------------------------------------------------------- /app/check.php: -------------------------------------------------------------------------------- 1 | getPhpIniConfigPath(); 8 | 9 | echo "********************************\n"; 10 | echo "* *\n"; 11 | echo "* Symfony requirements check *\n"; 12 | echo "* *\n"; 13 | echo "********************************\n\n"; 14 | 15 | echo $iniPath ? sprintf("* Configuration file used by PHP: %s\n\n", $iniPath) : "* WARNING: No configuration file (php.ini) used by PHP!\n\n"; 16 | 17 | echo "** ATTENTION **\n"; 18 | echo "* The PHP CLI can use a different php.ini file\n"; 19 | echo "* than the one used with your web server.\n"; 20 | if ('\\' == DIRECTORY_SEPARATOR) { 21 | echo "* (especially on the Windows platform)\n"; 22 | } 23 | echo "* To be on the safe side, please also launch the requirements check\n"; 24 | echo "* from your web server using the web/config.php script.\n"; 25 | 26 | echo_title('Mandatory requirements'); 27 | 28 | $checkPassed = true; 29 | foreach ($symfonyRequirements->getRequirements() as $req) { 30 | /** @var $req Requirement */ 31 | echo_requirement($req); 32 | if (!$req->isFulfilled()) { 33 | $checkPassed = false; 34 | } 35 | } 36 | 37 | echo_title('Optional recommendations'); 38 | 39 | foreach ($symfonyRequirements->getRecommendations() as $req) { 40 | echo_requirement($req); 41 | } 42 | 43 | exit($checkPassed ? 0 : 1); 44 | 45 | /** 46 | * Prints a Requirement instance 47 | */ 48 | function echo_requirement(Requirement $requirement) 49 | { 50 | $result = $requirement->isFulfilled() ? 'OK' : ($requirement->isOptional() ? 'WARNING' : 'ERROR'); 51 | echo ' ' . str_pad($result, 9); 52 | echo $requirement->getTestMessage() . "\n"; 53 | 54 | if (!$requirement->isFulfilled()) { 55 | echo sprintf(" %s\n\n", $requirement->getHelpText()); 56 | } 57 | } 58 | 59 | function echo_title($title) 60 | { 61 | echo "\n** $title **\n\n"; 62 | } 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Trip Planner 2 | ======================== 3 | 4 | A sample code for a DDD application. 5 | It's not a complete code ready for production, but it's working. 6 | You can follow the building steps, starting from first commit. 7 | 8 | The purpose: planning a trip, choosing a leg for every date and a location for every leg. 9 | 10 | 11 | Domain 12 | ======================== 13 | 14 | - Trip has a name and at least one Route 15 | - Trip has an identity; a Route has a name, and an internal identity 16 | - Route can have one or more Leg; a Leg has a Date and an internal identity 17 | - A Route can't have two Leg with same Data 18 | - A Leg has a Location; a Location has a name and a Point 19 | - Point has coordinates and the logic for distance calculation 20 | - The Route knows the approximate road distance 21 | - A route could be duplicated 22 | - A duplicated Route could be added to the Trip 23 | - Trade-off: removed InternalIdentity value object 24 | - Fix: Date::input should be a DateTime 25 | - Trade-off: we need a custom Event Dispatcher 26 | - Trade-off: getters on commands, for integration with form 27 | 28 | Application 29 | ======================== 30 | 31 | - Command Handler: manages the flow "Command -> Use Case" 32 | - CreateTrip Command and UseCase 33 | - We need the Repository for Trip 34 | - AddLegToRoute Command and UseCase 35 | - UpdateLocation Command and UseCase 36 | - We need validation: command validation using external service is a good trade-off 37 | - Mmmh too many exceptions ... we need uniform responses 38 | - Finally Events and Event Dispatcher 39 | 40 | Infrastructure 41 | ======================== 42 | 43 | - Framework's revenge: Symfony 2.5 and Doctrine 2.5 44 | - InfrastructureBundle 45 | - Mapping entities and value objects ... ops, Doctrine seems not work with a Value Object (with strategy auto) as identity for another entity 46 | - Configuring services and creating Infrastructure services: Repository, Adapter for Validator, Validation ... ops, the Symfony's dispatcher does not fit 47 | 48 | 49 | Presentation 50 | ======================== 51 | 52 | - A simple controller with a form 53 | - Symfony2 Form requires getters for the mapped entity -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trip planner", 3 | "autoload": { 4 | "psr-0": { "": "src/" } 5 | }, 6 | "require": { 7 | "php": ">=5.4.0", 8 | "doctrine/collections": "v1.2", 9 | "symfony/symfony": "~2.4", 10 | "doctrine/dbal": "dev-master", 11 | "doctrine/orm": "dev-master", 12 | "doctrine/doctrine-bundle": "dev-master", 13 | "twig/extensions": "~1.0", 14 | "symfony/assetic-bundle": "~2.3", 15 | "symfony/swiftmailer-bundle": "~2.3", 16 | "symfony/monolog-bundle": "~2.4", 17 | "sensio/distribution-bundle": "~2.3", 18 | "sensio/framework-extra-bundle": "~3.0", 19 | "sensio/generator-bundle": "~2.3", 20 | "incenteev/composer-parameter-handler": "~2.0", 21 | "doctrine/data-fixtures": "dev-master", 22 | "doctrine/migrations": "dev-master", 23 | "doctrine/doctrine-migrations-bundle": "dev-master", 24 | "doctrine/doctrine-fixtures-bundle": "dev-master" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "3.7.*" 28 | }, 29 | "scripts": { 30 | "post-install-cmd": [ 31 | "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters", 32 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap", 33 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache", 34 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets", 35 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile" 36 | ], 37 | "post-update-cmd": [ 38 | "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters", 39 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap", 40 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache", 41 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets", 42 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile" 43 | ] 44 | }, 45 | "config": { 46 | "bin-dir": "bin" 47 | }, 48 | "extra": { 49 | "symfony-app-dir": "app", 50 | "symfony-web-dir": "web", 51 | "incenteev-parameters": { 52 | "file": "app/config/parameters.yml" 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/InfrastructureBundle/Tests/CreateCompleteTripIntegrationTest.php: -------------------------------------------------------------------------------- 1 | boot(); 15 | 16 | $this->em = static::$kernel->getContainer() 17 | ->get('doctrine') 18 | ->getManager(); 19 | 20 | $this->commandHandler = static::$kernel->getContainer() 21 | ->get('command_handler'); 22 | } 23 | 24 | public function testCreateTripWithRouteAndLegs() 25 | { 26 | $createTrip = new CreateTripCommand('my new trip'); 27 | $response = $this->commandHandler->execute($createTrip); 28 | 29 | $trip = $response->getContent(); 30 | $tripIdentity = $trip->getIdentity()->getId(); 31 | 32 | $route = $trip->getRoutes()->first(); 33 | $this->assertEquals(0, $route->getLegs()->count()); 34 | $routeIdentity = $route->getInternalIdentity(); 35 | 36 | $addLeg = new AddLegToRouteCommand( 37 | $tripIdentity, 38 | $routeIdentity, 39 | '01/01/2014', 40 | 'd/m/Y', 41 | -3.386665, 42 | 36.736908) 43 | ; 44 | 45 | $this->commandHandler->execute($addLeg); 46 | $this->assertEquals(1, $route->getLegs()->count()); 47 | } 48 | 49 | public function testCommandValidation() 50 | { 51 | $createTrip = new CreateTripCommand(null); 52 | $response = $this->commandHandler->execute($createTrip); 53 | 54 | $errorField = $response->getContent()->key(); 55 | $errorMessage = $response->getContent()->current(); 56 | 57 | $this->assertContains('name', $errorField); 58 | $this->assertContains('This value should not be blank', $errorMessage); 59 | } 60 | 61 | public function tearDown() 62 | { 63 | $trip = $this->em->getRepository('TripPlannerDomain:Trip')->findOneBy(array('name' => 'my new trip')); 64 | if ($trip) { 65 | $this->em->remove($trip); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Domain/ValueObject/Point.php: -------------------------------------------------------------------------------- 1 | isValidLatitude($latitude)) { 13 | throw new \InvalidArgumentException('latitude is not valid'); 14 | } 15 | 16 | if (!$this->isValidLongitude($longitude)) { 17 | throw new \InvalidArgumentException('longitude is not valid'); 18 | } 19 | 20 | $this->latitude = $latitude; 21 | $this->longitude = $longitude; 22 | } 23 | 24 | public function getLatitude() 25 | { 26 | return $this->latitude; 27 | } 28 | 29 | public function getLongitude() 30 | { 31 | return $this->longitude; 32 | } 33 | 34 | public function getApproximateRoadDistance(Point $point, $degreeApproximation = 10) 35 | { 36 | $distance = $this->getCartographicDistance($point); 37 | 38 | return round($distance + $distance * ($degreeApproximation / 100)); 39 | } 40 | 41 | /** 42 | * @param Point $point 43 | * @return float 44 | * 45 | * thanks to http://stackoverflow.com/questions/7672759/how-to-calculate-distance-from-lat-long-in-php 46 | */ 47 | public function getCartographicDistance(Point $point) 48 | { 49 | $earthRadius = 3958.75; 50 | 51 | $dLat = deg2rad($point->getLatitude() - $this->latitude); 52 | $dLng = deg2rad($point->getLongitude() - $this->longitude); 53 | 54 | $a = sin($dLat / 2) * sin($dLat / 2) + 55 | cos(deg2rad($this->latitude)) * cos(deg2rad($point->getLatitude())) * 56 | sin($dLng / 2) * sin($dLng / 2); 57 | 58 | $c = 2 * atan2(sqrt($a), sqrt(1 - $a)); 59 | $dist = $earthRadius * $c; 60 | 61 | // from miles to km 62 | $meterConversion = 1.609344; 63 | $geopointDistance = $dist * $meterConversion; 64 | 65 | return round($geopointDistance, 0); 66 | } 67 | 68 | private function isValidLatitude($latitude) 69 | { 70 | return preg_match("/^-?([1-8]?[1-9]|[1-9]0)\.{1}\d{1,6}$/", $latitude); 71 | } 72 | 73 | private function isValidLongitude($longitude) 74 | { 75 | return preg_match("/^-?([1]?[1-7][1-9]|[1]?[1-8][0]|[1-9]?[0-9])\.{1}\d{1,6}$/", $longitude); 76 | } 77 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/Command/AddLegToRouteCommand.php: -------------------------------------------------------------------------------- 1 | tripIdentity = $tripIdentity; 25 | $this->routeIdentity = $routeIdentity; 26 | $this->date = $date; 27 | $this->dateFormat = $dateFormat; 28 | $this->latitude = $latitude; 29 | $this->longitude = $longitude; 30 | } 31 | 32 | public function getRequest() 33 | { 34 | return new ArrayCollection( 35 | array( 36 | 'tripIdentity' => $this->tripIdentity, 37 | 'routeIdentity' => $this->routeIdentity, 38 | 'date' => $this->date, 39 | 'dateFormat' => $this->dateFormat, 40 | 'latitude' => $this->latitude, 41 | 'longitude' => $this->longitude 42 | ) 43 | ); 44 | } 45 | 46 | /** 47 | * @return mixed 48 | */ 49 | public function getDate() 50 | { 51 | return $this->date; 52 | } 53 | 54 | /** 55 | * @return mixed 56 | */ 57 | public function getDateFormat() 58 | { 59 | return $this->dateFormat; 60 | } 61 | 62 | /** 63 | * @return mixed 64 | */ 65 | public function getLatitude() 66 | { 67 | return $this->latitude; 68 | } 69 | 70 | /** 71 | * @return mixed 72 | */ 73 | public function getLongitude() 74 | { 75 | return $this->longitude; 76 | } 77 | 78 | /** 79 | * @return mixed 80 | */ 81 | public function getRouteIdentity() 82 | { 83 | return $this->routeIdentity; 84 | } 85 | 86 | /** 87 | * @return mixed 88 | */ 89 | public function getTripIdentity() 90 | { 91 | return $this->tripIdentity; 92 | } 93 | } -------------------------------------------------------------------------------- /web/.htaccess: -------------------------------------------------------------------------------- 1 | # Use the front controller as index file. It serves as a fallback solution when 2 | # every other rewrite/redirect fails (e.g. in an aliased environment without 3 | # mod_rewrite). Additionally, this reduces the matching process for the 4 | # start page (path "/") because otherwise Apache will apply the rewriting rules 5 | # to each configured DirectoryIndex file (e.g. index.php, index.html, index.pl). 6 | DirectoryIndex app.php 7 | 8 | 9 | RewriteEngine On 10 | 11 | # Determine the RewriteBase automatically and set it as environment variable. 12 | # If you are using Apache aliases to do mass virtual hosting or installed the 13 | # project in a subdirectory, the base path will be prepended to allow proper 14 | # resolution of the app.php file and to redirect to the correct URI. It will 15 | # work in environments without path prefix as well, providing a safe, one-size 16 | # fits all solution. But as you do not need it in this case, you can comment 17 | # the following 2 lines to eliminate the overhead. 18 | RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$ 19 | RewriteRule ^(.*) - [E=BASE:%1] 20 | 21 | # Redirect to URI without front controller to prevent duplicate content 22 | # (with and without `/app.php`). Only do this redirect on the initial 23 | # rewrite by Apache and not on subsequent cycles. Otherwise we would get an 24 | # endless redirect loop (request -> rewrite to front controller -> 25 | # redirect -> request -> ...). 26 | # So in case you get a "too many redirects" error or you always get redirected 27 | # to the start page because your Apache does not expose the REDIRECT_STATUS 28 | # environment variable, you have 2 choices: 29 | # - disable this feature by commenting the following 2 lines or 30 | # - use Apache >= 2.3.9 and replace all L flags by END flags and remove the 31 | # following RewriteCond (best solution) 32 | RewriteCond %{ENV:REDIRECT_STATUS} ^$ 33 | RewriteRule ^app\.php(/(.*)|$) %{ENV:BASE}/$2 [R=301,L] 34 | 35 | # If the requested filename exists, simply serve it. 36 | # We only want to let Apache serve files and not directories. 37 | RewriteCond %{REQUEST_FILENAME} -f 38 | RewriteRule .? - [L] 39 | 40 | # Rewrite all other queries to the front controller. 41 | RewriteRule .? %{ENV:BASE}/app.php [L] 42 | 43 | 44 | 45 | 46 | # When mod_rewrite is not available, we instruct a temporary redirect of 47 | # the start page to the front controller explicitly so that the website 48 | # and the generated links can still be used. 49 | RedirectMatch 302 ^/$ /app.php/ 50 | # RedirectTemp cannot be used instead 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/config/config.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: parameters.yml } 3 | - { resource: security.yml } 4 | 5 | framework: 6 | #esi: ~ 7 | #translator: { fallback: "%locale%" } 8 | secret: "%secret%" 9 | router: 10 | resource: "%kernel.root_dir%/config/routing.yml" 11 | strict_requirements: ~ 12 | form: ~ 13 | csrf_protection: ~ 14 | validation: { enable_annotations: true } 15 | templating: 16 | engines: ['twig'] 17 | #assets_version: SomeVersionScheme 18 | default_locale: "%locale%" 19 | trusted_hosts: ~ 20 | trusted_proxies: ~ 21 | session: 22 | # handler_id set to null will use default session handler from php.ini 23 | handler_id: ~ 24 | fragments: ~ 25 | http_method_override: true 26 | 27 | # Twig Configuration 28 | twig: 29 | debug: "%kernel.debug%" 30 | strict_variables: "%kernel.debug%" 31 | 32 | # Assetic Configuration 33 | assetic: 34 | debug: "%kernel.debug%" 35 | use_controller: false 36 | bundles: [ ] 37 | #java: /usr/bin/java 38 | filters: 39 | cssrewrite: ~ 40 | #closure: 41 | # jar: "%kernel.root_dir%/Resources/java/compiler.jar" 42 | #yui_css: 43 | # jar: "%kernel.root_dir%/Resources/java/yuicompressor-2.4.7.jar" 44 | 45 | # Doctrine Configuration 46 | doctrine: 47 | dbal: 48 | driver: "%database_driver%" 49 | host: "%database_host%" 50 | port: "%database_port%" 51 | dbname: "%database_name%" 52 | user: "%database_user%" 53 | password: "%database_password%" 54 | charset: UTF8 55 | # if using pdo_sqlite as your database driver, add the path in parameters.yml 56 | # e.g. database_path: "%kernel.root_dir%/data/data.db3" 57 | # path: 58 | 59 | orm: 60 | auto_generate_proxy_classes: "%kernel.debug%" 61 | auto_mapping: false 62 | mappings: 63 | TripPlannerDomain: 64 | type: yml 65 | prefix: Leopro\TripPlanner\Domain\Entity 66 | dir: %kernel.root_dir%/../src/Leopro/TripPlanner/InfrastructureBundle/Resources/config/doctrine/entity 67 | is_bundle: false 68 | TripPlannerDomainValueObjects: 69 | type: yml 70 | prefix: Leopro\TripPlanner\Domain\ValueObject 71 | dir: %kernel.root_dir%/../src/Leopro/TripPlanner/InfrastructureBundle/Resources/config/doctrine/value_object 72 | is_bundle: false 73 | 74 | 75 | # Swiftmailer Configuration 76 | swiftmailer: 77 | transport: "%mailer_transport%" 78 | host: "%mailer_host%" 79 | username: "%mailer_user%" 80 | password: "%mailer_password%" 81 | spool: { type: memory } 82 | -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Domain/Entity/Route.php: -------------------------------------------------------------------------------- 1 | name = $name; 22 | $this->legs = new ArrayCollection(); 23 | } 24 | 25 | public static function create($tripName) 26 | { 27 | return new self( 28 | 'first route for trip: ' . $tripName 29 | ); 30 | } 31 | 32 | public function addLeg($date, $latitude, $longitude, $dateFormat = 'd-m-Y') 33 | { 34 | $leg = Leg::create($date, $dateFormat, $latitude, $longitude); 35 | 36 | $dateAlreadyUsed = function($key, $element) use($leg) { 37 | return $element->getDate() == $leg->getDate(); 38 | }; 39 | 40 | if ($this->legs->exists($dateAlreadyUsed)) { 41 | throw new DateAlreadyUsedException($date . ' already used'); 42 | } 43 | 44 | $this->legs->add($leg); 45 | } 46 | 47 | public function getApproximateRoadDistance() 48 | { 49 | $distance = 0; 50 | $iterator = $this->legs->getIterator(); 51 | 52 | while($iterator->valid()) { 53 | 54 | $pointFrom = $iterator->current()->getLocation()->getPoint(); 55 | 56 | $iterator->next(); 57 | if (!$iterator->valid()) { 58 | break; 59 | } 60 | 61 | $pointTo = $iterator->current()->getLocation()->getPoint(); 62 | 63 | $distance += $pointFrom->getApproximateRoadDistance($pointTo); 64 | } 65 | 66 | return $distance; 67 | } 68 | 69 | public function duplicate() 70 | { 71 | return clone $this; 72 | } 73 | 74 | public function getLegByDate($date) 75 | { 76 | foreach ($this->legs as $leg) { 77 | $legDate = $leg->getDate()->getFormattedDate(); 78 | if ($legDate == $date) { 79 | return $leg; 80 | } 81 | } 82 | 83 | throw new ResourceNotFoundException(sprintf('Leg with date \'%s\' not found', $date)); 84 | } 85 | 86 | public function getLegs() 87 | { 88 | return $this->legs; 89 | } 90 | 91 | public function getName() 92 | { 93 | return $this->name; 94 | } 95 | 96 | public function getInternalIdentity() 97 | { 98 | return $this->internalIdentity; 99 | } 100 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/Tests/EventDispatcherTest.php: -------------------------------------------------------------------------------- 1 | listener1 = $this->getMockBuilder('Leopro\TripPlanner\Application\Contract\EventListener') 12 | ->setMethods(array('onMyEvent', 'getSubscribedEvents')) 13 | ->disableOriginalConstructor() 14 | ->getMock(); 15 | 16 | $this->listener2 = $this->getMockBuilder('Leopro\TripPlanner\Application\Contract\EventListener') 17 | ->setMethods(array('onMyEvent', 'getSubscribedEvents')) 18 | ->disableOriginalConstructor() 19 | ->getMock(); 20 | 21 | $this->listener3 = $this->getMockBuilder('Leopro\TripPlanner\Application\Contract\EventListener') 22 | ->setMethods(array('onMyEvent', 'getSubscribedEvents')) 23 | ->disableOriginalConstructor() 24 | ->getMock(); 25 | 26 | $this->eventDispatcher = new EventDispatcher(); 27 | } 28 | 29 | /** 30 | * @expectedException \LogicException 31 | */ 32 | public function testRegisterListenersExceptionIfListenerDoesNotRespectInterface() 33 | { 34 | $this->eventDispatcher->registerListeners(array( 35 | new \stdClass() 36 | )); 37 | } 38 | 39 | public function testRegisterListenersWithCorrectInterface() 40 | { 41 | $this->eventDispatcher->registerListeners(array( 42 | $this->listener1, 43 | $this->listener2 44 | )); 45 | } 46 | 47 | public function testNotify() 48 | { 49 | $subscribedEvents = array( 50 | 'my_event' => 'onMyEvent' 51 | ); 52 | 53 | $event = new Event; 54 | 55 | $this->eventDispatcher->registerListeners(array( 56 | $this->listener1, 57 | $this->listener2, 58 | $this->listener3 59 | )); 60 | 61 | $this->listener1 62 | ->expects($this->once()) 63 | ->method('getSubscribedEvents') 64 | ->will($this->returnValue($subscribedEvents)); 65 | 66 | $this->listener2 67 | ->expects($this->once()) 68 | ->method('getSubscribedEvents') 69 | ->will($this->returnValue($subscribedEvents)); 70 | 71 | $this->listener3 72 | ->expects($this->once()) 73 | ->method('getSubscribedEvents') 74 | ->will($this->returnValue(array())); 75 | 76 | $this->listener1->expects($this->once()) 77 | ->method('onMyEvent') 78 | ->with($event); 79 | 80 | $this->listener2->expects($this->once()) 81 | ->method('onMyEvent') 82 | ->with($event); 83 | 84 | $this->eventDispatcher->notify('my_event', $event); 85 | } 86 | } 87 | 88 | class Event 89 | { 90 | 91 | } -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/Command/CommandHandler.php: -------------------------------------------------------------------------------- 1 | validator = $validator; 25 | $this->eventDispatcher = $eventDispatcher; 26 | } 27 | 28 | /** 29 | * @var UseCase[] 30 | */ 31 | private $useCases; 32 | 33 | public function registerCommands(array $useCases) 34 | { 35 | foreach ($useCases as $useCase) { 36 | if ($useCase instanceof UseCase) { 37 | $this->useCases[$useCase->getManagedCommand()] = $useCase; 38 | } else { 39 | throw new \LogicException('CommandHandler registerCommands expects an array of UseCase'); 40 | } 41 | } 42 | } 43 | 44 | public function execute($command) 45 | { 46 | $this->exceptionIfCommandNotManaged($command); 47 | 48 | try { 49 | 50 | $this->eventDispatcher->notify(Events::PRE_COMMAND, new PreCommandEvent($command)); 51 | 52 | $errors = $this->validator->validate($command); 53 | if ($errors->count() > 0) { 54 | throw new ValidationException($errors); 55 | } 56 | 57 | $result = $this->useCases[get_class($command)]->run($command); 58 | $response = new Response($result); 59 | 60 | $this->eventDispatcher->notify(Events::POST_COMMAND, new PostCommandEvent($command, $response)); 61 | 62 | return $response; 63 | 64 | } catch (DomainException $e) { 65 | $this->eventDispatcher->notify(Events::EXCEPTION, new ExceptionEvent($command, $e)); 66 | return new Response($e->getMessage()); 67 | } catch (ValidationException $e) { 68 | $this->eventDispatcher->notify(Events::EXCEPTION, new ExceptionEvent($command, $e)); 69 | return new Response($errors); 70 | } 71 | } 72 | 73 | private function exceptionIfCommandNotManaged($command) 74 | { 75 | $commandClass = get_class($command); 76 | if (!array_key_exists($commandClass, $this->useCases)) { 77 | throw new \LogicException($commandClass . ' is not a managed command'); 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /web/bundles/framework/css/exception.css: -------------------------------------------------------------------------------- 1 | .sf-reset .traces { 2 | padding-bottom: 14px; 3 | } 4 | .sf-reset .traces li { 5 | font-size: 12px; 6 | color: #868686; 7 | padding: 5px 4px; 8 | list-style-type: decimal; 9 | margin-left: 20px; 10 | white-space: break-word; 11 | } 12 | .sf-reset #logs .traces li.error { 13 | font-style: normal; 14 | color: #AA3333; 15 | background: #f9ecec; 16 | } 17 | .sf-reset #logs .traces li.warning { 18 | font-style: normal; 19 | background: #ffcc00; 20 | } 21 | /* fix for Opera not liking empty
  • */ 22 | .sf-reset .traces li:after { 23 | content: "\00A0"; 24 | } 25 | .sf-reset .trace { 26 | border: 1px solid #D3D3D3; 27 | padding: 10px; 28 | overflow: auto; 29 | margin: 10px 0 20px; 30 | } 31 | .sf-reset .block-exception { 32 | -moz-border-radius: 16px; 33 | -webkit-border-radius: 16px; 34 | border-radius: 16px; 35 | margin-bottom: 20px; 36 | background-color: #f6f6f6; 37 | border: 1px solid #dfdfdf; 38 | padding: 30px 28px; 39 | word-wrap: break-word; 40 | overflow: hidden; 41 | } 42 | .sf-reset .block-exception div { 43 | color: #313131; 44 | font-size: 10px; 45 | } 46 | .sf-reset .block-exception-detected .illustration-exception, 47 | .sf-reset .block-exception-detected .text-exception { 48 | float: left; 49 | } 50 | .sf-reset .block-exception-detected .illustration-exception { 51 | width: 152px; 52 | } 53 | .sf-reset .block-exception-detected .text-exception { 54 | width: 670px; 55 | padding: 30px 44px 24px 46px; 56 | position: relative; 57 | } 58 | .sf-reset .text-exception .open-quote, 59 | .sf-reset .text-exception .close-quote { 60 | position: absolute; 61 | } 62 | .sf-reset .open-quote { 63 | top: 0; 64 | left: 0; 65 | } 66 | .sf-reset .close-quote { 67 | bottom: 0; 68 | right: 50px; 69 | } 70 | .sf-reset .block-exception p { 71 | font-family: Arial, Helvetica, sans-serif; 72 | } 73 | .sf-reset .block-exception p a, 74 | .sf-reset .block-exception p a:hover { 75 | color: #565656; 76 | } 77 | .sf-reset .logs h2 { 78 | float: left; 79 | width: 654px; 80 | } 81 | .sf-reset .error-count { 82 | float: right; 83 | width: 170px; 84 | text-align: right; 85 | } 86 | .sf-reset .error-count span { 87 | display: inline-block; 88 | background-color: #aacd4e; 89 | -moz-border-radius: 6px; 90 | -webkit-border-radius: 6px; 91 | border-radius: 6px; 92 | padding: 4px; 93 | color: white; 94 | margin-right: 2px; 95 | font-size: 11px; 96 | font-weight: bold; 97 | } 98 | .sf-reset .toggle { 99 | vertical-align: middle; 100 | } 101 | .sf-reset .linked ul, 102 | .sf-reset .linked li { 103 | display: inline; 104 | } 105 | .sf-reset #output-content { 106 | color: #000; 107 | font-size: 12px; 108 | } 109 | .sf-reset #traces-text pre { 110 | white-space: pre; 111 | font-size: 12px; 112 | font-family: monospace; 113 | } 114 | -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/InfrastructureBundle/Resources/config/services.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Leopro\TripPlanner\Application\Event\EventDispatcher 8 | Leopro\TripPlanner\Application\Command\CommandHandler 9 | Leopro\TripPlanner\InfrastructureBundle\Adapter\EventDispatcher 10 | Leopro\TripPlanner\InfrastructureBundle\Repository\TripRepository 11 | Leopro\TripPlanner\InfrastructureBundle\Adapter\Validator 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/Leopro/TripPlanner/Application/Tests/CommandHandlerTest.php: -------------------------------------------------------------------------------- 1 | useCase = $this->getMockBuilder('Leopro\TripPlanner\Application\Contract\UseCase') 14 | ->disableOriginalConstructor() 15 | ->getMock(); 16 | 17 | $this->validator = $this->getMockBuilder('Leopro\TripPlanner\Application\Contract\Validator') 18 | ->disableOriginalConstructor() 19 | ->getMock(); 20 | 21 | $this->eventDispatcher = $this->getMockBuilder('Leopro\TripPlanner\Application\Contract\EventDispatcher') 22 | ->disableOriginalConstructor() 23 | ->getMock(); 24 | 25 | $this->commandHandler = new CommandHandler($this->validator, $this->eventDispatcher); 26 | } 27 | 28 | public function testRegisterCommandsWithCorrectInterface() 29 | { 30 | $this->commandHandler->registerCommands(array( 31 | $this->useCase, 32 | $this->useCase 33 | )); 34 | } 35 | 36 | /** 37 | * @expectedException \LogicException 38 | */ 39 | public function testRegisterCommandsExceptionIfUseCaseDoesNotRespectInterface() 40 | { 41 | $this->commandHandler->registerCommands(array( 42 | new \stdClass() 43 | )); 44 | } 45 | 46 | public function testExecute() 47 | { 48 | $this->useCase 49 | ->expects($this->once()) 50 | ->method('getManagedCommand') 51 | ->will($this->returnValue('Leopro\TripPlanner\Application\Tests\Fake')); 52 | 53 | $this->validator 54 | ->expects($this->once()) 55 | ->method('validate') 56 | ->will($this->returnValue(new ArrayCollection())); 57 | 58 | $this->useCase 59 | ->expects($this->once()) 60 | ->method('run'); 61 | 62 | $this->commandHandler->registerCommands(array( 63 | $this->useCase 64 | )); 65 | 66 | $result = $this->commandHandler->execute(new Fake()); 67 | $this->assertInstanceOf('Leopro\TripPlanner\Application\Response\Response', $result); 68 | } 69 | 70 | public function testCommandValidation() 71 | { 72 | $this->validator 73 | ->expects($this->once()) 74 | ->method('validate') 75 | ->will($this->returnValue(new ArrayCollection(array('name' => 'This value should not be blank')))); 76 | 77 | $this->useCase 78 | ->expects($this->once()) 79 | ->method('getManagedCommand') 80 | ->will($this->returnValue('Leopro\TripPlanner\Application\Tests\Fake')); 81 | 82 | $this->commandHandler->registerCommands(array( 83 | $this->useCase 84 | )); 85 | 86 | $response = $this->commandHandler->execute(new Fake()); 87 | $errors = $response->getContent(); 88 | $this->assertEquals('This value should not be blank', $errors->get('name')); 89 | } 90 | } 91 | 92 | class Fake implements Command 93 | { 94 | public function getRequest() 95 | { 96 | 97 | } 98 | } -------------------------------------------------------------------------------- /app/DoctrineMigrations/Version20140609123841.php: -------------------------------------------------------------------------------- 1 | abortIf($this->connection->getDatabasePlatform()->getName() != "mysql", "Migration can only be executed safely on 'mysql'."); 17 | 18 | $this->addSql("CREATE TABLE location (internalIdentity INT AUTO_INCREMENT NOT NULL, name VARCHAR(250) NOT NULL, point_latitude VARCHAR(50) NOT NULL, point_longitude VARCHAR(50) NOT NULL, PRIMARY KEY(internalIdentity)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); 19 | $this->addSql("CREATE TABLE route (internalIdentity INT AUTO_INCREMENT NOT NULL, name VARCHAR(250) NOT NULL, PRIMARY KEY(internalIdentity)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); 20 | $this->addSql("CREATE TABLE route_legs (link_id INT NOT NULL, report_id INT NOT NULL, INDEX IDX_505D420FADA40271 (link_id), INDEX IDX_505D420F4BD2A4C0 (report_id), PRIMARY KEY(link_id, report_id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); 21 | $this->addSql("CREATE TABLE leg (internalIdentity INT AUTO_INCREMENT NOT NULL, date_input DATE NOT NULL, date_format VARCHAR(10) NOT NULL, PRIMARY KEY(internalIdentity)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); 22 | $this->addSql("CREATE TABLE trip (identity_id VARCHAR(13) NOT NULL, name VARCHAR(250) NOT NULL, PRIMARY KEY(identity_id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); 23 | $this->addSql("CREATE TABLE trip_routes (link_id VARCHAR(13) NOT NULL, report_id INT NOT NULL, INDEX IDX_DDDA9919ADA40271 (link_id), INDEX IDX_DDDA99194BD2A4C0 (report_id), PRIMARY KEY(link_id, report_id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); 24 | $this->addSql("ALTER TABLE route_legs ADD CONSTRAINT FK_505D420FADA40271 FOREIGN KEY (link_id) REFERENCES route (internalIdentity)"); 25 | $this->addSql("ALTER TABLE route_legs ADD CONSTRAINT FK_505D420F4BD2A4C0 FOREIGN KEY (report_id) REFERENCES leg (internalIdentity)"); 26 | $this->addSql("ALTER TABLE leg ADD CONSTRAINT FK_75D0804FFFB32BD9 FOREIGN KEY (internalIdentity) REFERENCES location (internalIdentity)"); 27 | $this->addSql("ALTER TABLE trip_routes ADD CONSTRAINT FK_DDDA9919ADA40271 FOREIGN KEY (link_id) REFERENCES trip (identity_id)"); 28 | $this->addSql("ALTER TABLE trip_routes ADD CONSTRAINT FK_DDDA99194BD2A4C0 FOREIGN KEY (report_id) REFERENCES route (internalIdentity)"); 29 | } 30 | 31 | public function down(Schema $schema) 32 | { 33 | // this down() migration is auto-generated, please modify it to your needs 34 | $this->abortIf($this->connection->getDatabasePlatform()->getName() != "mysql", "Migration can only be executed safely on 'mysql'."); 35 | 36 | $this->addSql("ALTER TABLE leg DROP FOREIGN KEY FK_75D0804FFFB32BD9"); 37 | $this->addSql("ALTER TABLE route_legs DROP FOREIGN KEY FK_505D420FADA40271"); 38 | $this->addSql("ALTER TABLE trip_routes DROP FOREIGN KEY FK_DDDA99194BD2A4C0"); 39 | $this->addSql("ALTER TABLE route_legs DROP FOREIGN KEY FK_505D420F4BD2A4C0"); 40 | $this->addSql("ALTER TABLE trip_routes DROP FOREIGN KEY FK_DDDA9919ADA40271"); 41 | $this->addSql("DROP TABLE location"); 42 | $this->addSql("DROP TABLE route"); 43 | $this->addSql("DROP TABLE route_legs"); 44 | $this->addSql("DROP TABLE leg"); 45 | $this->addSql("DROP TABLE trip"); 46 | $this->addSql("DROP TABLE trip_routes"); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /web/config.php: -------------------------------------------------------------------------------- 1 | getFailedRequirements(); 20 | $minorProblems = $symfonyRequirements->getFailedRecommendations(); 21 | 22 | ?> 23 | 24 | 25 | 26 | 27 | 28 | Symfony Configuration 29 | 30 | 31 | 32 | 33 | 34 |
    35 |
    36 | 39 | 40 | 60 |
    61 | 62 |
    63 |
    64 |
    65 |

    Welcome!

    66 |

    Welcome to your new Symfony project.

    67 |

    68 | This script will guide you through the basic configuration of your project. 69 | You can also do the same by editing the ‘app/config/parameters.yml’ file directly. 70 |

    71 | 72 | 73 |

    Major problems

    74 |

    Major problems have been detected and must be fixed before continuing:

    75 |
      76 | 77 |
    1. getHelpHtml() ?>
    2. 78 | 79 |
    80 | 81 | 82 | 83 |

    Recommendations

    84 |

    85 | Additionally, toTo enhance your Symfony experience, 86 | it’s recommended that you fix the following: 87 |

    88 |
      89 | 90 |
    1. getHelpHtml() ?>
    2. 91 | 92 |
    93 | 94 | 95 | hasPhpIniConfigIssue()): ?> 96 |

    * 97 | getPhpIniConfigPath()): ?> 98 | Changes to the php.ini file must be done in "getPhpIniConfigPath() ?>". 99 | 100 | To change settings, create a "php.ini". 101 | 102 |

    103 | 104 | 105 | 106 |

    Your configuration looks good to run Symfony.

    107 | 108 | 109 | 118 |
    119 |
    120 |
    121 |
    Symfony Standard Edition
    122 |
    123 | 124 | 125 | -------------------------------------------------------------------------------- /web/bundles/framework/css/body.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2010, Yahoo! Inc. All rights reserved. 3 | Code licensed under the BSD License: 4 | http://developer.yahoo.com/yui/license.html 5 | version: 3.1.2 6 | build: 56 7 | */ 8 | .sf-reset div,.sf-reset dl,.sf-reset dt,.sf-reset dd,.sf-reset ul,.sf-reset ol,.sf-reset li,.sf-reset h1,.sf-reset h2,.sf-reset h3,.sf-reset h4,.sf-reset h5,.sf-reset h6,.sf-reset pre,.sf-reset code,.sf-reset form,.sf-reset fieldset,.sf-reset legend,.sf-reset input,.sf-reset textarea,.sf-reset p,.sf-reset blockquote,.sf-reset th,.sf-reset td{margin:0;padding:0;}.sf-reset table{border-collapse:collapse;border-spacing:0;}.sf-reset fieldset,.sf-reset img{border:0;}.sf-reset address,.sf-reset caption,.sf-reset cite,.sf-reset code,.sf-reset dfn,.sf-reset em,.sf-reset strong,.sf-reset th,.sf-reset var{font-style:normal;font-weight:normal;}.sf-reset li{list-style:none;}.sf-reset caption,.sf-reset th{text-align:left;}.sf-reset h1,.sf-reset h2,.sf-reset h3,.sf-reset h4,.sf-reset h5,.sf-reset h6{font-size:100%;font-weight:normal;}.sf-reset q:before,.sf-reset q:after{content:'';}.sf-reset abbr,.sf-reset acronym{border:0;font-variant:normal;}.sf-reset sup{vertical-align:text-top;}.sf-reset sub{vertical-align:text-bottom;}.sf-reset input,.sf-reset textarea,.sf-reset select{font-family:inherit;font-size:inherit;font-weight:inherit;}.sf-reset input,.sf-reset textarea,.sf-reset select{font-size:100%;}.sf-reset legend{color:#000;} 9 | .sf-reset abbr { 10 | border-bottom: 1px dotted #000; 11 | cursor: help; 12 | } 13 | .sf-reset p { 14 | font-size: 14px; 15 | line-height: 20px; 16 | padding-bottom: 20px; 17 | } 18 | .sf-reset strong { 19 | color: #313131; 20 | font-weight: bold; 21 | } 22 | .sf-reset a { 23 | color: #6c6159; 24 | } 25 | .sf-reset a img { 26 | border: none; 27 | } 28 | .sf-reset a:hover { 29 | text-decoration: underline; 30 | } 31 | .sf-reset em { 32 | font-style: italic; 33 | } 34 | .sf-reset h2, 35 | .sf-reset h3 { 36 | font-weight: bold; 37 | } 38 | .sf-reset h1 { 39 | font-family: Georgia, "Times New Roman", Times, serif; 40 | font-size: 20px; 41 | color: #313131; 42 | word-break: break-all; 43 | } 44 | .sf-reset li { 45 | padding-bottom: 10px; 46 | } 47 | .sf-reset .block { 48 | -moz-border-radius: 16px; 49 | -webkit-border-radius: 16px; 50 | border-radius: 16px; 51 | margin-bottom: 20px; 52 | background-color: #FFFFFF; 53 | border: 1px solid #dfdfdf; 54 | padding: 40px 50px; 55 | } 56 | .sf-reset h2 { 57 | font-size: 16px; 58 | font-family: Arial, Helvetica, sans-serif; 59 | } 60 | .sf-reset li a { 61 | background: none; 62 | color: #868686; 63 | text-decoration: none; 64 | } 65 | .sf-reset li a:hover { 66 | background: none; 67 | color: #313131; 68 | text-decoration: underline; 69 | } 70 | .sf-reset ol { 71 | padding: 10px 0; 72 | } 73 | .sf-reset ol li { 74 | list-style: decimal; 75 | margin-left: 20px; 76 | padding: 2px; 77 | padding-bottom: 20px; 78 | } 79 | .sf-reset ol ol li { 80 | list-style-position: inside; 81 | margin-left: 0; 82 | white-space: nowrap; 83 | font-size: 12px; 84 | padding-bottom: 0; 85 | } 86 | .sf-reset li .selected { 87 | background-color: #ffd; 88 | } 89 | .sf-button { 90 | display: -moz-inline-box; 91 | display: inline-block; 92 | text-align: center; 93 | vertical-align: middle; 94 | border: 0; 95 | background: transparent none; 96 | text-transform: uppercase; 97 | cursor: pointer; 98 | font: bold 11px Arial, Helvetica, sans-serif; 99 | } 100 | .sf-button span { 101 | text-decoration: none; 102 | display: block; 103 | height: 28px; 104 | float: left; 105 | } 106 | .sf-button .border-l { 107 | text-decoration: none; 108 | display: block; 109 | height: 28px; 110 | float: left; 111 | padding: 0 0 0 7px; 112 | background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAcCAYAAACtQ6WLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQtJREFUeNpiPHnyJAMakARiByDWYEGT8ADiYGVlZStubm5xlv///4MEQYoKZGRkQkRERLRYWVl5wYJQyXBZWdkwCQkJUxAHKgaWlAHSLqKiosb//v1DsYMFKGCvoqJiDmQzwXTAJYECulxcXNLoumCSoszMzDzoumDGghQwYZUECWIzkrAkSIIGOmlkLI10AiX//P379x8jIyMTNmPf/v79+ysLCwsvuiQoNi5//fr1Kch4dAyS3P/gwYMTQBP+wxwHw0xA4gkQ73v9+vUZdJ2w1Lf82bNn4iCHCQoKasHsZw4ODgbRIL8c+/Lly5M3b978Y2dn5wC6npkFLXnsAOKLjx49AmUHLYAAAwBoQubG016R5wAAAABJRU5ErkJggg==) no-repeat top left; 113 | } 114 | .sf-button .border-r { 115 | padding: 0 7px 0 0; 116 | background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAcCAYAAACtQ6WLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAR1JREFUeNpiPHnyZCMDA8MNID5gZmb2nAEJMH7//v3N169fX969e/cYkL8WqGAHXPLv37//QYzfv39/fvPmzbUnT56sAXInmJub/2H5/x8sx8DCwsIrISFhDmQyPX78+CmQXs70798/BmQsKipqBNTgdvz4cWkmkE5kDATMioqKZkCFdiwg1eiAi4tLGqhQF24nMmBmZuYEigth1QkEbEBxTlySYPvJkwSJ00AnjYylgU6gxB8g/oFVEphkvgLF32KNMmCCewYUv4qhEyj47+HDhyeBzIMYOoEp8CxQw56wsLAncJ1//vz5/P79+2svX74EJc2V4BT58+fPd8CE/QKYHMGJOiIiAp6oWW7evDkNSF8DZYfIyEiU7AAQYACJ2vxVdJW4eQAAAABJRU5ErkJggg==) right top no-repeat; 117 | } 118 | .sf-button .btn-bg { 119 | padding: 0px 14px; 120 | color: #636363; 121 | line-height: 28px; 122 | background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAcCAYAAACgXdXMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAClJREFUeNpiPnny5EKGf//+/Wf6//8/A4QAcrGzKCZwGc9sa2urBBBgAIbDUoYVp9lmAAAAAElFTkSuQmCC) repeat-x top left; 123 | } 124 | .sf-button:hover .border-l, 125 | .sf-button-selected .border-l { 126 | background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAcCAYAAACtQ6WLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAR9JREFUeNpi/P//PwMyOHfunDqQSgNiexZkibNnzxYBqZa3HOs5v7PcYQBLnjlzhg1IbfzIdsTjA/t+ht9Mr8GKwZL//v3r+sB+0OMN+zqIEf8gFMvJkyd1gXTOa9YNDP//otrPAtSV/Jp9HfPff78Z0AEL0LUeXxivMfxD0wXTqfjj/2ugkf+wSrL9/YtpJEyS4S8WI5Ek/+GR/POPFjr//cenE6/kP9q4Fo/kr39/mdj+M/zFkGQCSj5i+ccPjLJ/GBgkuYOHQR1sNDpmAkb2LBmWwL///zKCIxwZM0VHR18G6p4uxeLLAA4tJMwEshiou1iMxXaHLGswA+t/YbhORuQUv2DBAnCifvxzI+enP3dQJUFg/vz5sOzgBBBgAPxX9j0YnH4JAAAAAElFTkSuQmCC) no-repeat top left; 127 | } 128 | .sf-button:hover .border-r, 129 | .sf-button-selected .border-r { 130 | background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAcCAYAAACtQ6WLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAT5JREFUeNpiPHv27BkGBoaDQDzLyMjoJgMSYHrM3WX8hn1d0f///88DFRYhSzIuv2X5H8Rg/SfKIPDTkYH/l80OINffxMTkF9O/f/8ZQPgnwyuGl+wrGd6x7vf49+9fO9jYf3+Bkkj4NesmBqAV+SdPntQC6vzHgIz//gOawbqOGchOxtAJwp8Zr4F0e7D8/fuPAR38/P8eZIo0yz8skv8YvoIk+YE6/zNgAyD7sRqLkPzzjxY6/+HS+R+fTkZ8djLh08lCUCcuSWawJGbwMTGwg7zyBatX2Bj5QZKPsBrLzaICktzN8g/NWEYGZgYZjoC/wMiei5FMpFh8QPSU6Ojoy3Cd7EwiDBJsDgxiLNY7gLrKQGIsHAxSDHxAO2TZ/b8D+TVxcXF9MCtYtLiKLgDpfUDVsxITE1GyA0CAAQA2E/N8VuHyAAAAAABJRU5ErkJggg==) right top no-repeat; 131 | } 132 | .sf-button:hover .btn-bg, 133 | .sf-button-selected .btn-bg { 134 | color: #FFFFFF; 135 | text-shadow:0 1px 1px #6b9311; 136 | background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAcCAIAAAAvP0KbAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEFJREFUeNpiPnv2LNMdvlymf///M/37B8R/QfQ/MP33L4j+B6Qh7L9//sHpf2h8MA1V+w/KRjYLaDaLCU8vQIABAFO3TxZriO4yAAAAAElFTkSuQmCC) repeat-x top left; 137 | } 138 | -------------------------------------------------------------------------------- /app/SymfonyRequirements.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | /* 13 | * Users of PHP 5.2 should be able to run the requirements checks. 14 | * This is why the file and all classes must be compatible with PHP 5.2+ 15 | * (e.g. not using namespaces and closures). 16 | * 17 | * ************** CAUTION ************** 18 | * 19 | * DO NOT EDIT THIS FILE as it will be overridden by Composer as part of 20 | * the installation/update process. The original file resides in the 21 | * SensioDistributionBundle. 22 | * 23 | * ************** CAUTION ************** 24 | */ 25 | 26 | /** 27 | * Represents a single PHP requirement, e.g. an installed extension. 28 | * It can be a mandatory requirement or an optional recommendation. 29 | * There is a special subclass, named PhpIniRequirement, to check a php.ini configuration. 30 | * 31 | * @author Tobias Schultze 32 | */ 33 | class Requirement 34 | { 35 | private $fulfilled; 36 | private $testMessage; 37 | private $helpText; 38 | private $helpHtml; 39 | private $optional; 40 | 41 | /** 42 | * Constructor that initializes the requirement. 43 | * 44 | * @param Boolean $fulfilled Whether the requirement is fulfilled 45 | * @param string $testMessage The message for testing the requirement 46 | * @param string $helpHtml The help text formatted in HTML for resolving the problem 47 | * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 48 | * @param Boolean $optional Whether this is only an optional recommendation not a mandatory requirement 49 | */ 50 | public function __construct($fulfilled, $testMessage, $helpHtml, $helpText = null, $optional = false) 51 | { 52 | $this->fulfilled = (Boolean) $fulfilled; 53 | $this->testMessage = (string) $testMessage; 54 | $this->helpHtml = (string) $helpHtml; 55 | $this->helpText = null === $helpText ? strip_tags($this->helpHtml) : (string) $helpText; 56 | $this->optional = (Boolean) $optional; 57 | } 58 | 59 | /** 60 | * Returns whether the requirement is fulfilled. 61 | * 62 | * @return Boolean true if fulfilled, otherwise false 63 | */ 64 | public function isFulfilled() 65 | { 66 | return $this->fulfilled; 67 | } 68 | 69 | /** 70 | * Returns the message for testing the requirement. 71 | * 72 | * @return string The test message 73 | */ 74 | public function getTestMessage() 75 | { 76 | return $this->testMessage; 77 | } 78 | 79 | /** 80 | * Returns the help text for resolving the problem 81 | * 82 | * @return string The help text 83 | */ 84 | public function getHelpText() 85 | { 86 | return $this->helpText; 87 | } 88 | 89 | /** 90 | * Returns the help text formatted in HTML. 91 | * 92 | * @return string The HTML help 93 | */ 94 | public function getHelpHtml() 95 | { 96 | return $this->helpHtml; 97 | } 98 | 99 | /** 100 | * Returns whether this is only an optional recommendation and not a mandatory requirement. 101 | * 102 | * @return Boolean true if optional, false if mandatory 103 | */ 104 | public function isOptional() 105 | { 106 | return $this->optional; 107 | } 108 | } 109 | 110 | /** 111 | * Represents a PHP requirement in form of a php.ini configuration. 112 | * 113 | * @author Tobias Schultze 114 | */ 115 | class PhpIniRequirement extends Requirement 116 | { 117 | /** 118 | * Constructor that initializes the requirement. 119 | * 120 | * @param string $cfgName The configuration name used for ini_get() 121 | * @param Boolean|callback $evaluation Either a Boolean indicating whether the configuration should evaluate to true or false, 122 | or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement 123 | * @param Boolean $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false. 124 | This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin. 125 | Example: You require a config to be true but PHP later removes this config and defaults it to true internally. 126 | * @param string|null $testMessage The message for testing the requirement (when null and $evaluation is a Boolean a default message is derived) 127 | * @param string|null $helpHtml The help text formatted in HTML for resolving the problem (when null and $evaluation is a Boolean a default help is derived) 128 | * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 129 | * @param Boolean $optional Whether this is only an optional recommendation not a mandatory requirement 130 | */ 131 | public function __construct($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null, $optional = false) 132 | { 133 | $cfgValue = ini_get($cfgName); 134 | 135 | if (is_callable($evaluation)) { 136 | if (null === $testMessage || null === $helpHtml) { 137 | throw new InvalidArgumentException('You must provide the parameters testMessage and helpHtml for a callback evaluation.'); 138 | } 139 | 140 | $fulfilled = call_user_func($evaluation, $cfgValue); 141 | } else { 142 | if (null === $testMessage) { 143 | $testMessage = sprintf('%s %s be %s in php.ini', 144 | $cfgName, 145 | $optional ? 'should' : 'must', 146 | $evaluation ? 'enabled' : 'disabled' 147 | ); 148 | } 149 | 150 | if (null === $helpHtml) { 151 | $helpHtml = sprintf('Set %s to %s in php.ini*.', 152 | $cfgName, 153 | $evaluation ? 'on' : 'off' 154 | ); 155 | } 156 | 157 | $fulfilled = $evaluation == $cfgValue; 158 | } 159 | 160 | parent::__construct($fulfilled || ($approveCfgAbsence && false === $cfgValue), $testMessage, $helpHtml, $helpText, $optional); 161 | } 162 | } 163 | 164 | /** 165 | * A RequirementCollection represents a set of Requirement instances. 166 | * 167 | * @author Tobias Schultze 168 | */ 169 | class RequirementCollection implements IteratorAggregate 170 | { 171 | private $requirements = array(); 172 | 173 | /** 174 | * Gets the current RequirementCollection as an Iterator. 175 | * 176 | * @return Traversable A Traversable interface 177 | */ 178 | public function getIterator() 179 | { 180 | return new ArrayIterator($this->requirements); 181 | } 182 | 183 | /** 184 | * Adds a Requirement. 185 | * 186 | * @param Requirement $requirement A Requirement instance 187 | */ 188 | public function add(Requirement $requirement) 189 | { 190 | $this->requirements[] = $requirement; 191 | } 192 | 193 | /** 194 | * Adds a mandatory requirement. 195 | * 196 | * @param Boolean $fulfilled Whether the requirement is fulfilled 197 | * @param string $testMessage The message for testing the requirement 198 | * @param string $helpHtml The help text formatted in HTML for resolving the problem 199 | * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 200 | */ 201 | public function addRequirement($fulfilled, $testMessage, $helpHtml, $helpText = null) 202 | { 203 | $this->add(new Requirement($fulfilled, $testMessage, $helpHtml, $helpText, false)); 204 | } 205 | 206 | /** 207 | * Adds an optional recommendation. 208 | * 209 | * @param Boolean $fulfilled Whether the recommendation is fulfilled 210 | * @param string $testMessage The message for testing the recommendation 211 | * @param string $helpHtml The help text formatted in HTML for resolving the problem 212 | * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 213 | */ 214 | public function addRecommendation($fulfilled, $testMessage, $helpHtml, $helpText = null) 215 | { 216 | $this->add(new Requirement($fulfilled, $testMessage, $helpHtml, $helpText, true)); 217 | } 218 | 219 | /** 220 | * Adds a mandatory requirement in form of a php.ini configuration. 221 | * 222 | * @param string $cfgName The configuration name used for ini_get() 223 | * @param Boolean|callback $evaluation Either a Boolean indicating whether the configuration should evaluate to true or false, 224 | or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement 225 | * @param Boolean $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false. 226 | This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin. 227 | Example: You require a config to be true but PHP later removes this config and defaults it to true internally. 228 | * @param string $testMessage The message for testing the requirement (when null and $evaluation is a Boolean a default message is derived) 229 | * @param string $helpHtml The help text formatted in HTML for resolving the problem (when null and $evaluation is a Boolean a default help is derived) 230 | * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 231 | */ 232 | public function addPhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null) 233 | { 234 | $this->add(new PhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence, $testMessage, $helpHtml, $helpText, false)); 235 | } 236 | 237 | /** 238 | * Adds an optional recommendation in form of a php.ini configuration. 239 | * 240 | * @param string $cfgName The configuration name used for ini_get() 241 | * @param Boolean|callback $evaluation Either a Boolean indicating whether the configuration should evaluate to true or false, 242 | or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement 243 | * @param Boolean $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false. 244 | This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin. 245 | Example: You require a config to be true but PHP later removes this config and defaults it to true internally. 246 | * @param string $testMessage The message for testing the requirement (when null and $evaluation is a Boolean a default message is derived) 247 | * @param string $helpHtml The help text formatted in HTML for resolving the problem (when null and $evaluation is a Boolean a default help is derived) 248 | * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 249 | */ 250 | public function addPhpIniRecommendation($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null) 251 | { 252 | $this->add(new PhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence, $testMessage, $helpHtml, $helpText, true)); 253 | } 254 | 255 | /** 256 | * Adds a requirement collection to the current set of requirements. 257 | * 258 | * @param RequirementCollection $collection A RequirementCollection instance 259 | */ 260 | public function addCollection(RequirementCollection $collection) 261 | { 262 | $this->requirements = array_merge($this->requirements, $collection->all()); 263 | } 264 | 265 | /** 266 | * Returns both requirements and recommendations. 267 | * 268 | * @return array Array of Requirement instances 269 | */ 270 | public function all() 271 | { 272 | return $this->requirements; 273 | } 274 | 275 | /** 276 | * Returns all mandatory requirements. 277 | * 278 | * @return array Array of Requirement instances 279 | */ 280 | public function getRequirements() 281 | { 282 | $array = array(); 283 | foreach ($this->requirements as $req) { 284 | if (!$req->isOptional()) { 285 | $array[] = $req; 286 | } 287 | } 288 | 289 | return $array; 290 | } 291 | 292 | /** 293 | * Returns the mandatory requirements that were not met. 294 | * 295 | * @return array Array of Requirement instances 296 | */ 297 | public function getFailedRequirements() 298 | { 299 | $array = array(); 300 | foreach ($this->requirements as $req) { 301 | if (!$req->isFulfilled() && !$req->isOptional()) { 302 | $array[] = $req; 303 | } 304 | } 305 | 306 | return $array; 307 | } 308 | 309 | /** 310 | * Returns all optional recommendations. 311 | * 312 | * @return array Array of Requirement instances 313 | */ 314 | public function getRecommendations() 315 | { 316 | $array = array(); 317 | foreach ($this->requirements as $req) { 318 | if ($req->isOptional()) { 319 | $array[] = $req; 320 | } 321 | } 322 | 323 | return $array; 324 | } 325 | 326 | /** 327 | * Returns the recommendations that were not met. 328 | * 329 | * @return array Array of Requirement instances 330 | */ 331 | public function getFailedRecommendations() 332 | { 333 | $array = array(); 334 | foreach ($this->requirements as $req) { 335 | if (!$req->isFulfilled() && $req->isOptional()) { 336 | $array[] = $req; 337 | } 338 | } 339 | 340 | return $array; 341 | } 342 | 343 | /** 344 | * Returns whether a php.ini configuration is not correct. 345 | * 346 | * @return Boolean php.ini configuration problem? 347 | */ 348 | public function hasPhpIniConfigIssue() 349 | { 350 | foreach ($this->requirements as $req) { 351 | if (!$req->isFulfilled() && $req instanceof PhpIniRequirement) { 352 | return true; 353 | } 354 | } 355 | 356 | return false; 357 | } 358 | 359 | /** 360 | * Returns the PHP configuration file (php.ini) path. 361 | * 362 | * @return string|false php.ini file path 363 | */ 364 | public function getPhpIniConfigPath() 365 | { 366 | return get_cfg_var('cfg_file_path'); 367 | } 368 | } 369 | 370 | /** 371 | * This class specifies all requirements and optional recommendations that 372 | * are necessary to run the Symfony Standard Edition. 373 | * 374 | * @author Tobias Schultze 375 | * @author Fabien Potencier 376 | */ 377 | class SymfonyRequirements extends RequirementCollection 378 | { 379 | const REQUIRED_PHP_VERSION = '5.3.3'; 380 | 381 | /** 382 | * Constructor that initializes the requirements. 383 | */ 384 | public function __construct() 385 | { 386 | /* mandatory requirements follow */ 387 | 388 | $installedPhpVersion = phpversion(); 389 | 390 | $this->addRequirement( 391 | version_compare($installedPhpVersion, self::REQUIRED_PHP_VERSION, '>='), 392 | sprintf('PHP version must be at least %s (%s installed)', self::REQUIRED_PHP_VERSION, $installedPhpVersion), 393 | sprintf('You are running PHP version "%s", but Symfony needs at least PHP "%s" to run. 394 | Before using Symfony, upgrade your PHP installation, preferably to the latest version.', 395 | $installedPhpVersion, self::REQUIRED_PHP_VERSION), 396 | sprintf('Install PHP %s or newer (installed version is %s)', self::REQUIRED_PHP_VERSION, $installedPhpVersion) 397 | ); 398 | 399 | $this->addRequirement( 400 | version_compare($installedPhpVersion, '5.3.16', '!='), 401 | 'PHP version must not be 5.3.16 as Symfony won\'t work properly with it', 402 | 'Install PHP 5.3.17 or newer (or downgrade to an earlier PHP version)' 403 | ); 404 | 405 | $this->addRequirement( 406 | is_dir(__DIR__.'/../vendor/composer'), 407 | 'Vendor libraries must be installed', 408 | 'Vendor libraries are missing. Install composer following instructions from http://getcomposer.org/. ' . 409 | 'Then run "php composer.phar install" to install them.' 410 | ); 411 | 412 | $baseDir = basename(__DIR__); 413 | 414 | $this->addRequirement( 415 | is_writable(__DIR__.'/cache'), 416 | "$baseDir/cache/ directory must be writable", 417 | "Change the permissions of the \"$baseDir/cache/\" directory so that the web server can write into it." 418 | ); 419 | 420 | $this->addRequirement( 421 | is_writable(__DIR__.'/logs'), 422 | "$baseDir/logs/ directory must be writable", 423 | "Change the permissions of the \"$baseDir/logs/\" directory so that the web server can write into it." 424 | ); 425 | 426 | $this->addPhpIniRequirement( 427 | 'date.timezone', true, false, 428 | 'date.timezone setting must be set', 429 | 'Set the "date.timezone" setting in php.ini* (like Europe/Paris).' 430 | ); 431 | 432 | if (version_compare($installedPhpVersion, self::REQUIRED_PHP_VERSION, '>=')) { 433 | $timezones = array(); 434 | foreach (DateTimeZone::listAbbreviations() as $abbreviations) { 435 | foreach ($abbreviations as $abbreviation) { 436 | $timezones[$abbreviation['timezone_id']] = true; 437 | } 438 | } 439 | 440 | $this->addRequirement( 441 | isset($timezones[date_default_timezone_get()]), 442 | sprintf('Configured default timezone "%s" must be supported by your installation of PHP', date_default_timezone_get()), 443 | 'Your default timezone is not supported by PHP. Check for typos in your php.ini file and have a look at the list of deprecated timezones at http://php.net/manual/en/timezones.others.php.' 444 | ); 445 | } 446 | 447 | $this->addRequirement( 448 | function_exists('json_encode'), 449 | 'json_encode() must be available', 450 | 'Install and enable the JSON extension.' 451 | ); 452 | 453 | $this->addRequirement( 454 | function_exists('session_start'), 455 | 'session_start() must be available', 456 | 'Install and enable the session extension.' 457 | ); 458 | 459 | $this->addRequirement( 460 | function_exists('ctype_alpha'), 461 | 'ctype_alpha() must be available', 462 | 'Install and enable the ctype extension.' 463 | ); 464 | 465 | $this->addRequirement( 466 | function_exists('token_get_all'), 467 | 'token_get_all() must be available', 468 | 'Install and enable the Tokenizer extension.' 469 | ); 470 | 471 | $this->addRequirement( 472 | function_exists('simplexml_import_dom'), 473 | 'simplexml_import_dom() must be available', 474 | 'Install and enable the SimpleXML extension.' 475 | ); 476 | 477 | if (function_exists('apc_store') && ini_get('apc.enabled')) { 478 | if (version_compare($installedPhpVersion, '5.4.0', '>=')) { 479 | $this->addRequirement( 480 | version_compare(phpversion('apc'), '3.1.13', '>='), 481 | 'APC version must be at least 3.1.13 when using PHP 5.4', 482 | 'Upgrade your APC extension (3.1.13+).' 483 | ); 484 | } else { 485 | $this->addRequirement( 486 | version_compare(phpversion('apc'), '3.0.17', '>='), 487 | 'APC version must be at least 3.0.17', 488 | 'Upgrade your APC extension (3.0.17+).' 489 | ); 490 | } 491 | } 492 | 493 | $this->addPhpIniRequirement('detect_unicode', false); 494 | 495 | if (extension_loaded('suhosin')) { 496 | $this->addPhpIniRequirement( 497 | 'suhosin.executor.include.whitelist', 498 | create_function('$cfgValue', 'return false !== stripos($cfgValue, "phar");'), 499 | false, 500 | 'suhosin.executor.include.whitelist must be configured correctly in php.ini', 501 | 'Add "phar" to suhosin.executor.include.whitelist in php.ini*.' 502 | ); 503 | } 504 | 505 | if (extension_loaded('xdebug')) { 506 | $this->addPhpIniRequirement( 507 | 'xdebug.show_exception_trace', false, true 508 | ); 509 | 510 | $this->addPhpIniRequirement( 511 | 'xdebug.scream', false, true 512 | ); 513 | 514 | $this->addPhpIniRecommendation( 515 | 'xdebug.max_nesting_level', 516 | create_function('$cfgValue', 'return $cfgValue > 100;'), 517 | true, 518 | 'xdebug.max_nesting_level should be above 100 in php.ini', 519 | 'Set "xdebug.max_nesting_level" to e.g. "250" in php.ini* to stop Xdebug\'s infinite recursion protection erroneously throwing a fatal error in your project.' 520 | ); 521 | } 522 | 523 | $pcreVersion = defined('PCRE_VERSION') ? (float) PCRE_VERSION : null; 524 | 525 | $this->addRequirement( 526 | null !== $pcreVersion, 527 | 'PCRE extension must be available', 528 | 'Install the PCRE extension (version 8.0+).' 529 | ); 530 | 531 | /* optional recommendations follow */ 532 | 533 | $this->addRecommendation( 534 | file_get_contents(__FILE__) === file_get_contents(__DIR__.'/../vendor/sensio/distribution-bundle/Sensio/Bundle/DistributionBundle/Resources/skeleton/app/SymfonyRequirements.php'), 535 | 'Requirements file should be up-to-date', 536 | 'Your requirements file is outdated. Run composer install and re-check your configuration.' 537 | ); 538 | 539 | $this->addRecommendation( 540 | version_compare($installedPhpVersion, '5.3.4', '>='), 541 | 'You should use at least PHP 5.3.4 due to PHP bug #52083 in earlier versions', 542 | 'Your project might malfunction randomly due to PHP bug #52083 ("Notice: Trying to get property of non-object"). Install PHP 5.3.4 or newer.' 543 | ); 544 | 545 | $this->addRecommendation( 546 | version_compare($installedPhpVersion, '5.3.8', '>='), 547 | 'When using annotations you should have at least PHP 5.3.8 due to PHP bug #55156', 548 | 'Install PHP 5.3.8 or newer if your project uses annotations.' 549 | ); 550 | 551 | $this->addRecommendation( 552 | version_compare($installedPhpVersion, '5.4.0', '!='), 553 | 'You should not use PHP 5.4.0 due to the PHP bug #61453', 554 | 'Your project might not work properly due to the PHP bug #61453 ("Cannot dump definitions which have method calls"). Install PHP 5.4.1 or newer.' 555 | ); 556 | 557 | $this->addRecommendation( 558 | version_compare($installedPhpVersion, '5.4.11', '>='), 559 | 'When using the logout handler from the Symfony Security Component, you should have at least PHP 5.4.11 due to PHP bug #63379 (as a workaround, you can also set invalidate_session to false in the security logout handler configuration)', 560 | 'Install PHP 5.4.11 or newer if your project uses the logout handler from the Symfony Security Component.' 561 | ); 562 | 563 | $this->addRecommendation( 564 | (version_compare($installedPhpVersion, '5.3.18', '>=') && version_compare($installedPhpVersion, '5.4.0', '<')) 565 | || 566 | version_compare($installedPhpVersion, '5.4.8', '>='), 567 | 'You should use PHP 5.3.18+ or PHP 5.4.8+ to always get nice error messages for fatal errors in the development environment due to PHP bug #61767/#60909', 568 | 'Install PHP 5.3.18+ or PHP 5.4.8+ if you want nice error messages for all fatal errors in the development environment.' 569 | ); 570 | 571 | if (null !== $pcreVersion) { 572 | $this->addRecommendation( 573 | $pcreVersion >= 8.0, 574 | sprintf('PCRE extension should be at least version 8.0 (%s installed)', $pcreVersion), 575 | 'PCRE 8.0+ is preconfigured in PHP since 5.3.2 but you are using an outdated version of it. Symfony probably works anyway but it is recommended to upgrade your PCRE extension.' 576 | ); 577 | } 578 | 579 | $this->addRecommendation( 580 | class_exists('DomDocument'), 581 | 'PHP-XML module should be installed', 582 | 'Install and enable the PHP-XML module.' 583 | ); 584 | 585 | $this->addRecommendation( 586 | function_exists('mb_strlen'), 587 | 'mb_strlen() should be available', 588 | 'Install and enable the mbstring extension.' 589 | ); 590 | 591 | $this->addRecommendation( 592 | function_exists('iconv'), 593 | 'iconv() should be available', 594 | 'Install and enable the iconv extension.' 595 | ); 596 | 597 | $this->addRecommendation( 598 | function_exists('utf8_decode'), 599 | 'utf8_decode() should be available', 600 | 'Install and enable the XML extension.' 601 | ); 602 | 603 | if (!defined('PHP_WINDOWS_VERSION_BUILD')) { 604 | $this->addRecommendation( 605 | function_exists('posix_isatty'), 606 | 'posix_isatty() should be available', 607 | 'Install and enable the php_posix extension (used to colorize the CLI output).' 608 | ); 609 | } 610 | 611 | $this->addRecommendation( 612 | class_exists('Locale'), 613 | 'intl extension should be available', 614 | 'Install and enable the intl extension (used for validators).' 615 | ); 616 | 617 | if (class_exists('Collator')) { 618 | $this->addRecommendation( 619 | null !== new Collator('fr_FR'), 620 | 'intl extension should be correctly configured', 621 | 'The intl extension does not behave properly. This problem is typical on PHP 5.3.X x64 WIN builds.' 622 | ); 623 | } 624 | 625 | if (class_exists('Locale')) { 626 | if (defined('INTL_ICU_VERSION')) { 627 | $version = INTL_ICU_VERSION; 628 | } else { 629 | $reflector = new ReflectionExtension('intl'); 630 | 631 | ob_start(); 632 | $reflector->info(); 633 | $output = strip_tags(ob_get_clean()); 634 | 635 | preg_match('/^ICU version +(?:=> )?(.*)$/m', $output, $matches); 636 | $version = $matches[1]; 637 | } 638 | 639 | $this->addRecommendation( 640 | version_compare($version, '4.0', '>='), 641 | 'intl ICU version should be at least 4+', 642 | 'Upgrade your intl extension with a newer ICU version (4+).' 643 | ); 644 | } 645 | 646 | $accelerator = 647 | (extension_loaded('eaccelerator') && ini_get('eaccelerator.enable')) 648 | || 649 | (extension_loaded('apc') && ini_get('apc.enabled')) 650 | || 651 | (extension_loaded('Zend OPcache') && ini_get('opcache.enable')) 652 | || 653 | (extension_loaded('xcache') && ini_get('xcache.cacher')) 654 | || 655 | (extension_loaded('wincache') && ini_get('wincache.ocenabled')) 656 | ; 657 | 658 | $this->addRecommendation( 659 | $accelerator, 660 | 'a PHP accelerator should be installed', 661 | 'Install and enable a PHP accelerator like APC (highly recommended).' 662 | ); 663 | 664 | $this->addPhpIniRecommendation('short_open_tag', false); 665 | 666 | $this->addPhpIniRecommendation('magic_quotes_gpc', false, true); 667 | 668 | $this->addPhpIniRecommendation('register_globals', false, true); 669 | 670 | $this->addPhpIniRecommendation('session.auto_start', false); 671 | 672 | $this->addRecommendation( 673 | class_exists('PDO'), 674 | 'PDO should be installed', 675 | 'Install PDO (mandatory for Doctrine).' 676 | ); 677 | 678 | if (class_exists('PDO')) { 679 | $drivers = PDO::getAvailableDrivers(); 680 | $this->addRecommendation( 681 | count($drivers), 682 | sprintf('PDO should have some drivers installed (currently available: %s)', count($drivers) ? implode(', ', $drivers) : 'none'), 683 | 'Install PDO drivers (mandatory for Doctrine).' 684 | ); 685 | } 686 | } 687 | } 688 | --------------------------------------------------------------------------------