Acceptance Testing

At the end of Part One, we were creating a lot of acceptance tests.  We had a pretty antiquated HTML form for the students to fill in a lot of data, some of which we didn’t need to collect from them.  We knew the templates would be changed and had a designer’s help making those look better and sensible.  We knew what data we needed to keep collecting in the application, so our Codeception tests checked that imaginary student’s application had the same information.  We planned knowing that the User Interface was changing and tried to ensure the data integrity.  Codeception tests are pretty brittle when you have to link to a specific DOM element, but we did the best we could to ensure that data wasn’t left off the Student’s Application for Scholarships.

Bindings

As we began writing the new code, we needed to bind those namespaces to the CodeIgniter application, so the new code could be linked within the application.

We created /app/bindings.php

<?php

use ScholarshipsSelectionInfrastructureStorageHybridSourcedScholarshipRepository;

$container['ScholarshipsSelectionDomainScholarshipScholarshipRepository'] = function($c) {
    return new HybridSourcedScholarshipRepository(
        $c['ScholarshipsSupportEventsEventStore'],
        $c['database'],
        $c['ScholarshipsCommonServicesDepartmentsService'],
        $c['ScholarshipsCommonServicesResidenciesService'],
        $c['ScholarshipsCommonServicesAcademicPlansService'],
        $c['ScholarshipsSelectionDomainCollaborationsService']
); };

The bindings file is used for most of our new functionality in the /src path.  For example, the HybridSourcedScholarshipRepository builds all the current Scholarships with information from our database tables and our Events.  The constructor requires objects of type EventStore, Database, DepartmentsService, ResidenciesService, AcademicPlansService, and CollaborationsService.  We give the paths to these in the bindings for the container ScholarshipRepository.  The container can then be included in the Selection controller.

In /app/services.php, we included the bindings and facades:

/**
 * Facade registration.
 */
Facade::setFacadeContainer($container);
class_alias('ScholarshipsSupportFacadesRequest', 'Request');
class_alias('ScholarshipsSupportFacadesDB', 'DB');
class_alias('ScholarshipsSupportFacadesLog', 'Log');

// Bring in application-specific bindings require_once('bindings.php');

Then we created a new file called /src/CowboyBridge.php, which was used to initialize the Pimple Container.

<?php

namespace CodeceptionModule;

use CodeceptionModule;

class CowboyBridge extends Module
{
    /**
     * @var PimpleContainer
     */
    private $container;

    public function _initialize()
    {
        require_once(__DIR__ . '/../app/services.php');
        $this->container = $container;
        $this->debug("Loaded container.");
    }

    public function getContainer()
    {
        return $this->container;
    }
}

Controllers

Now we needed to be able to see the new code in our controllers.  This means, for example, ,being able to pass our collection of available Scholarships to the Selection controller, and from the controller into the views.  We named our new selection controller Selectionnext in order to leave the old Selection controller intact.

class Selectionnext extends BaseController
{
    public function Selectionnext()
    {
        parent::BaseController();

        $this->scholarshipRepository = $this->container['ScholarshipsSelectionDomainScholarshipScholarshipRepository'];
    }
protected function scholarshipsDashboard($scholarshipId)
{
    $committeeMember = $this->getCommitteeMember();

    $scholDetails = $this->getScholarshipDashboard($scholarshipId);

    $this->render('pages/scholarship-details.twig', array("scholarship" => $scholDetails)));
}


private function getScholarshipDashboard($scholarshipId,$keepItLight=false)
{
 $committeeMember = $this->getCommitteeMember();

 $presenter = new ScholarshipDashboardPresenter(
 $committeeMember,
 ScholarshipId::fromString($scholarshipId),
 $this->events,
 $this->scholarshipRepository,
 $this->container['ScholarshipsSelectionDomainApplicantQueryService']
 );

 $scholDetails = $presenter->asArrayForJson();
 $scholDetails['json_string'] = json_encode($scholDetails);

 return $scholDetails;
}

I have tried to remove some of the complexity of this part and hopefully not removed anything critical.  We compiled a lot more information into our $scholDetails and sent many more variables to our views.  But this should show a bit of how we mapped the information into the Controllers of CodeIgniter.

Views

Lastly, we changed our views.  Leaving the old views in the /app/views directory as they were in CodeIgniter, we opted to use Twig views for our new interfaces, and we put those files in the /app/templates directory.    We set these paths in our app/config.php

return array( 
  ...
 'twig' => array(
   'template_path' => array(
     __DIR__ . '/templates',
     __DIR__ . '/templates/dashboard',
   )
 ),

  ...
)

In the file app/services.php we pick up these configured paths and pull them into CodeIgniter.

$container['twig'] = function ($c) {
    $loader = new Twig_Loader_Filesystem($c['config']['twig']['template_path']);

    $inDevelopment = $_SERVER['APP_ENV'] === 'development';

    $twig = new Twig_Environment($loader, array('debug' => $inDevelopment));

    $app = $c['config']['app'];
    if ($inDevelopment) {
        $app['debug'] = TRUE;
    }

    $twig->addGlobal('app', $app);
    $twig->addGlobal('notice', $c['config']['notice']);

    $twig->addFunction(new Twig_SimpleFunction('route', function($path) {
        return site_url($path);
    }));

    $twig->addFunction(new Twig_SimpleFunction('asset', function($path) {
        return base_url() . 'public/' . $path;
    }));

    $twig->addFunction( new Twig_SimpleFunction('flash_notice', function() {
        if (isset($_SESSION['flash_notice'])) {
            $_SESSION['old_flash_notice'] = $_SESSION['flash_notice'];
            unset($_SESSION['flash_notice']);
        }

        return isset($_SESSION['old_flash_notice']) ? $_SESSION['old_flash_notice'] : array();
    }));

    if ($inDevelopment) {
        $twig->addExtension(new Twig_Extension_Debug());
    }

    $twig->addFunction( new Twig_SimpleFunction('flash_exists', function() {
        return !empty($_SESSION['flash_notice']);
    }));

    return $twig;
};

The $_SERVER['APP_ENV'] variable was set in our .htaccess file.  This Twig configuration allows us to get debug information in our development environments.  We added two simple functions to build our asset path and our route path.  This meant we could use route() and asset() in our templates to correct paths across environments.  There are also simple functions to determine whether there are existing flash messages to pass into the template.

Conclusion

This code grew over a long period along with the new functionality of our application.  If you have questions about something that seems missing, please feel free to ask in comments.  I may be able to include a little more code to clarify the details.  Our Scholarships application is pretty complicated, and I’ve tried to skim a lot of that detail out of the code examples in order to simplify and focus on the bootstrapping.  I hope that it has been useful to you because it was really useful for me to re-examine how this all ended up working together.