Implement your own ConfigurationLoader

Out-of-the-box this library provides a single ConfigurationLoader that will always return a single Configuration for all requests. If you need something more complex this guide details how to create your own ConfigurationLoader instance.

In our example we're going to create a ConfigurationLoader that allows different Configuration to be used based on the domain of the Request. We assume in our example that you're running a multi-tenant web app that can receive requests from many domains. We are going to put our code under the Acme\Http\Cors namespace, you should change this to match the appropriate namespace for your project.

1. Create your new class

The first step is to create your new class and implement the ConfigurationLoader interface; don't worry that the method doesn't return anything yet... we'll complete that later in this guide.

<?php

namespace Acme\Http\Cors;

use Amp\Http\Server\Request;
use Cspray\Labrador\Http\Cors\Configuration;
use Cspray\Labrador\Http\Cors\ConfigurationLoader;

final class MultiTenantConfigurationLoader implements ConfigurationLoader {

    public function loadConfiguration(Request $request) : Configuration {

    }

}

2. Add methods to store tenant specific Configuration

Next we need to add a way to specify which Configuration each tenant receives. Since this is your code and called by you on the middleware setup you can design whatever system is best for your application. In our example we have simply added a method that will accept a tenant's domain and the Configuration associated with it.

<?php

namespace Acme\Http\Cors;

use Amp\Http\Server\Request;
use Cspray\Labrador\Http\Cors\Configuration;
use Cspray\Labrador\Http\Cors\ConfigurationLoader;

final class MultiTenantConfigurationLoader implements ConfigurationLoader {

    private $tenantConfigs = [];

    public function addTenantConfiguration(string $tenantDomain, Configuration $configuration) : void {
        $this->tenantConfigs[$tenantDomain] = $configuration;
    }

    public function loadConfiguration(Request $request) : Configuration {

    }

}

3. Return Configuration based on Request host

<?php

namespace Acme\Http\Cors;

use Amp\Http\Server\Request;
use Cspray\Labrador\Http\Cors\Configuration;
use Cspray\Labrador\Http\Cors\ConfigurationLoader;

final class MultiTenantConfigurationLoader implements ConfigurationLoader {

    private $tenantConfigs = [];

    public function addTenantConfiguration(string $tenantDomain, Configuration $configuration) : void {
        $this->tenantConfigs[$tenantDomain] = $configuration;
    }

    public function loadConfiguration(Request $request) : Configuration {
        $host = $request->getUri()->getHost();
        $configuration = $this->tenantConfigs[$host] ?? null;
        return $configuration;
    }

}

4. Handle no Configuration for Request

If you've added logic to have many types of Configurations it is likely you may encounter a Request that does not match that logic and you cannot reliably determine a Configuration for. Based on your application's needs you will need to determine how to handle this scenario to fix the Null Pointer Error that can occur in code from step 3.

Option 1: Return a default Configuration instance

The recommended way to deal with this scenario is to provide a default Configuration instance specific for the requested Origin.

<?php

namespace Acme\Http\Cors;

use Amp\Http\Server\Request;
use Cspray\Labrador\Http\Cors\Configuration;
use Cspray\Labrador\Http\Cors\ConfigurationBuilder;
use Cspray\Labrador\Http\Cors\ConfigurationLoader;

final class MultiTenantConfigurationLoader implements ConfigurationLoader {

    private $tenantConfigs = [];

    public function addTenantConfiguration(string $tenantDomain, Configuration $configuration) : void {
        $this->tenantConfigs[$tenantDomain] = $configuration;
    }

    public function loadConfiguration(Request $request) : Configuration {
        $host = $request->getUri()->getHost();
        $configuration = $this->tenantConfigs[$host] ?? null;
        if ($configuration === null) {
            $origin = $request->getHeader('Origin');
            $configuration = ConfigurationBuilder::forOrigins($origin)->build();
        }
        return $configuration;
    }

}
Option 2: Throw an Exception

If getting a Request from a domain that you haven't configured for CORS really is an exceptional situation with no reasonable way to handle it you can throw an exception.

<?php

namespace Acme\Http\Cors;

use Amp\Http\Server\Request;
use Cspray\Labrador\Http\Cors\Configuration;
use Cspray\Labrador\Http\Cors\ConfigurationLoader;

final class MultiTenantConfigurationLoader implements ConfigurationLoader {

    private $tenantConfigs = [];

    public function addTenantConfiguration(string $tenantDomain, Configuration $configuration) : void {
        $this->tenantConfigs[$tenantDomain] = $configuration;
    }

    public function loadConfiguration(Request $request) : Configuration {
        $host = $request->getUri()->getHost();
        $configuration = $this->tenantConfigs[$host] ?? null;
        if ($configuration === null) {
            throw new \InvalidArgumentException(sprintf('No Configuration for host: %s', $host));
        }   
        return $configuration;
    }

}