Skip to content

Feat: configurable postcode resolver#2021

Open
wychoong wants to merge 11 commits intolunarphp:1.xfrom
wychoong:feat-configurable-postcode-resolver
Open

Feat: configurable postcode resolver#2021
wychoong wants to merge 11 commits intolunarphp:1.xfrom
wychoong:feat-configurable-postcode-resolver

Conversation

@wychoong
Copy link
Copy Markdown
Contributor

postcode format varies for each countries. it will be hard to only have 1 lunar resolver to cater for all

this PR enables dev to replace with own resolver that suit the countries the store is running

@vercel
Copy link
Copy Markdown

vercel bot commented Nov 19, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
lunar-docs ✅ Ready (Inspect) Visit Preview 💬 Add feedback Apr 10, 2025 9:40am

@glennjacobs
Copy link
Copy Markdown
Contributor

Sounds like a good idea. I'll review with @alecritson when he's back from annual leave.

@alecritson
Copy link
Copy Markdown
Collaborator

I had a little look at this, my 15 minutes of thought is, instead of making this config specific, why not make it country specific? This way we just need to register new postcode resolvers for countries and let the core handle it...

We already have a Lunar\Shipping\DataTransferObjects\PostcodeLookup DTO which has the postcode string and also a Country model property. So have both of what we need already. We could then add a method to the DTO getParts which resolves to the correct postcode resolver based on the country (or fallsback to the default)

So instead of this:

$postcodeParts = (new PostcodeResolver)->getParts(
    $this->postcodeLookup->postcode
);
$query->whereIn('postcode', $postcodeParts);

We have this:

$query->whereIn('postcode', $this->postcodeLookup->getParts());

The getParts method could look something like this:

public function getParts(): array
{
   // New facade? Could resolve to a postcode manager class
   return Postcode::country($this->country)->getParts($this->postcode);
}

On this new manager class we could have

class PostcodeManager
{
    protected array $resolvers = [];
    
    public function addResolver(string $class)
    {
        $this->resolvers[] = $class;
    }
    
    public function country(Country $country)
    {
        foreach ($this->resolvers as $resolver) {
            // Check the resolver works for this country and return it...
        }
    }
}

Need to think about if multiple resolvers are registered, however we could have some sort of key signature on the array or make it so the last in the array is the "first" so the default will always come last in the matching 🤷

A postcode resolver could look like:

class PostcodeResolver implements PostcodeResolveInterface
{
    protected array $countries = ['GB'];

    public function supportsCountry(Country $country): bool
    {
        // The default one will just return true
        return in_array($country->iso2, $this->countries);
    }
    
    // ...
}

Then maybe to register a new resolve you just do:

// Again, new facade?
Postcode::addResolver(MyCustomResolver::class);

Idk this was my "thinking out loud" 🤔

@wychoong
Copy link
Copy Markdown
Contributor Author

I like the idea postcode resolvers. The only thing I would add on is to allow adding the supported countries to a resolver

@alecritson alecritson marked this pull request as draft December 2, 2024 08:03
@alecritson
Copy link
Copy Markdown
Collaborator

@wychoong Are you still planning on working on this one? or do you want us to take it over

@wychoong
Copy link
Copy Markdown
Contributor Author

@wychoong Are you still planning on working on this one? or do you want us to take it over

yes, you can take it over. I still need this but just not the top priority for now

@alecritson alecritson added this to the v1.1 milestone Apr 10, 2025
@alecritson
Copy link
Copy Markdown
Collaborator

Thanks @wychoong I will suggest this gets done after v1 in v1.1.0 :)

@alecritson alecritson self-assigned this Apr 11, 2025
@glennjacobs glennjacobs added the core takeover The core team will takeover the development of this PR label Jul 1, 2025
@glennjacobs glennjacobs removed this from the v1.1 milestone Nov 4, 2025
@alecritson
Copy link
Copy Markdown
Collaborator

Implemented the postcode resolver classes, this replaces the original draft's single-resolver config swap with a country-aware resolver registry. Apps can now register multiple postcode resolvers, each scoped to the countries it handles, and Lunar picks the right one per shipping lookup.

  • New Lunar\Shipping\Managers\PostcodeManager (singleton) backs a Postcode facade. Resolvers are registered via Postcode::addResolver(...) in a service provider.
  • PostcodeResolverInterface now requires supportsCountry(CountryContract): bool and getParts(string $postcode, CountryContract $country): Collection.
  • The existing PostcodeResolver becomes the default catch-all. Its protected $countries array can be overridden by subclasses to narrow it to specific ISO-2 codes.
  • Matching iterates in reverse registration order, so apps' custom resolvers override the default for the countries they claim and the default continues to handle everything else.
  • PostcodeLookup::getParts() delegates to the matched resolver, making the consumer call site in ShippingZoneResolver a one-liner.
  • NoPostcodeResolverException guards the "no resolver matches" case (can't happen under the default configuration).
  • The unreleased lunar.shipping-tables.resolvers.postcode config key is removed; registration now goes through the manager.

@alecritson alecritson requested a review from glennjacobs April 15, 2026 13:15
@alecritson alecritson marked this pull request as ready for review April 15, 2026 13:16
/**
* Register a resolver. Class strings are resolved lazily via the container.
*/
public function addResolver(string|PostcodeResolverInterface $resolver): self
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice DX if this can accept an array of resolvers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core takeover The core team will takeover the development of this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants