diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results index 5681332..964a014 100644 --- a/.phpunit.cache/test-results +++ b/.phpunit.cache/test-results @@ -1 +1 @@ -{"version":"pest_3.1.0","defects":{"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_return_empty_addresses_collection_when_eloquent_has_no_addresses":8,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_billing_address_primary_unmarked_event_on_make_primary":8,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_return_addresses_collection_when_eloquent_has_addresses":8,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_billing_address_primary_marked_event_on_make_primary":8,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_address_primary_unmarked_event_on_make_primary":8,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_shipping_address_primary_unmarked_event_on_make_primary":8,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_shipping_address_primary_marked_event_on_make_primary":8,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_address_primary_marked_event_on_make_primary":8},"times":{"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_return_addresses_collection_when_eloquent_has_addresses":0.006,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_address_primary_unmarked_event_on_make_primary":0.004,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_shipping_address_primary_unmarked_event_on_make_primary":0.004,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_address_primary_marked_event_on_make_primary":0.044,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_billing_address_primary_marked_event_on_make_primary":0.007,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_billing_address_primary_unmarked_event_on_make_primary":0.005,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_return_empty_addresses_collection_when_eloquent_has_no_addresses":0.019,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_shipping_address_primary_marked_event_on_make_primary":0.004,"P\\Tests\\ArchTest::__pest_evaluable_it_will_not_use_debugging_functions":0.045}} \ No newline at end of file +{"version":"pest_3.6.0","defects":{"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_billing_address_primary_unmarked_event_on_make_primary":8,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_return_addresses_collection_when_eloquent_has_addresses":8,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_shipping_address_primary_unmarked_event_on_make_primary":8,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_billing_address_primary_marked_event_on_make_primary":8,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_address_primary_marked_event_on_make_primary":8,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_address_primary_unmarked_event_on_make_primary":8,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_return_empty_addresses_collection_when_eloquent_has_no_addresses":8,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_shipping_address_primary_marked_event_on_make_primary":8,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_stores_lat_lng":8},"times":{"P\\Tests\\ArchTest::__pest_evaluable_it_will_not_use_debugging_functions":0.055,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_address_primary_unmarked_event_on_make_primary":0.004,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_billing_address_primary_marked_event_on_make_primary":0.004,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_return_addresses_collection_when_eloquent_has_addresses":0.004,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_shipping_address_primary_marked_event_on_make_primary":0.007,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_shipping_address_primary_unmarked_event_on_make_primary":0.005,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_address_primary_marked_event_on_make_primary":0.046,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_return_empty_addresses_collection_when_eloquent_has_no_addresses":0.003,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_fires_billing_address_primary_unmarked_event_on_make_primary":0.004,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_stores_lat_lng":0.004,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_stores_lat_lng_and_retrieve_correctly":0.004,"P\\Tests\\Feature\\Models\\AddressTest::__pest_evaluable_it_stores_and_retrieves_lat_lng_correctly":0.006}} \ No newline at end of file diff --git a/composer.json b/composer.json index 277ee3a..e7ace89 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": "^8.1|^8.2|^8.3", + "php": "^8.2|^8.3|^8.4", "illuminate/contracts": "^11.23" }, "require-dev": { @@ -26,6 +26,7 @@ "pestphp/pest": "^3.1", "pestphp/pest-plugin-arch": "^3.0", "pestphp/pest-plugin-drift": "3.x-dev", + "pestphp/pest-plugin-faker": "^3.0", "pestphp/pest-plugin-laravel": "^3.0", "phpstan/extension-installer": "^1.4", "phpstan/phpstan-deprecation-rules": "^1.2", diff --git a/database/factories/AddressFactory.php b/database/factories/AddressFactory.php index d339e52..efefcb9 100644 --- a/database/factories/AddressFactory.php +++ b/database/factories/AddressFactory.php @@ -2,6 +2,7 @@ namespace Masterix21\Addressable\Database\Factories; +use Clickbar\Magellan\Data\Geometries\Point; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Model; use Masterix21\Addressable\Models\Address; @@ -23,7 +24,7 @@ public function definition(): array 'city' => $this->faker->city, 'state' => $this->faker->state, 'country' => $this->faker->countryCode, - 'coordinates' => [$this->faker->latitude, $this->faker->longitude], + 'coordinates' => ['lat' => $this->faker->latitude, 'lng' => $this->faker->longitude], ]; } diff --git a/src/Models/Address.php b/src/Models/Address.php index 0a012d4..445d3cc 100644 --- a/src/Models/Address.php +++ b/src/Models/Address.php @@ -2,9 +2,10 @@ namespace Masterix21\Addressable\Models; -use Illuminate\Database\Eloquent\Casts\AsArrayObject; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Masterix21\Addressable\Models\Casts\GeographyCast; use Masterix21\Addressable\Models\Concerns\ImplementsMarkPrimary; class Address extends Model @@ -36,23 +37,42 @@ class Address extends Model 'is_primary' => 'bool', 'is_billing' => 'bool', 'is_shipping' => 'bool', - 'coordinates' => AsArrayObject::class, + 'coordinates' => GeographyCast::class, ]; - public function getDisplayAddressAttribute(): string + public function displayAddress(): Attribute { - $keys = [ - 'street_address1', - 'street_address2', - 'zip', - 'city', - 'state', - 'country', - ]; - - return collect($this->getAttributes()) - ->filter(fn ($item, $key) => in_array($key, $keys) && ! blank($item)) - ->values() - ->join(' - '); + return Attribute::get(function () { + $keys = [ + 'street_address1', + 'street_address2', + 'zip', + 'city', + 'state', + 'country', + ]; + + return collect($this->getAttributes()) + ->filter(fn ($item, $key) => in_array($key, $keys) && ! blank($item)) + ->values() + ->join(' - '); + }); + } + + public function latitude(): Attribute + { + return Attribute::get(fn () => $this->coordinates['lat'] ?? null); + } + + public function longitude(): Attribute + { + return Attribute::get(fn () => $this->coordinates['lng'] ?? null); + } + + public function setCoordinates(float $latitude, float $longitude): self + { + $this->coordinates = compact('latitude', 'longitude'); + + return $this; } } diff --git a/src/Models/Casts/GeographyCast.php b/src/Models/Casts/GeographyCast.php new file mode 100644 index 0000000..c0f1c1e --- /dev/null +++ b/src/Models/Casts/GeographyCast.php @@ -0,0 +1,38 @@ + $coords[1], + 'lng' => $coords[0], + ]; + } + + public function set($model, string $key, $value, array $attributes) + { + if (! $value) { + return null; + } + + $latitude = $value['latitude'] ?? $value['lat'] ?? $value[0] ?? null; + $longitude = $value['longitude'] ?? $value['lng'] ?? $value[1] ?? null; + + if ($latitude === null || $longitude === null) { + throw new \InvalidArgumentException('Invalid coordinates format.'); + } + + return sprintf('POINT(%f %f)', $longitude, $latitude); + } +} diff --git a/tests/Feature/Models/AddressTest.php b/tests/Feature/Models/AddressTest.php index 99f50db..23d5223 100644 --- a/tests/Feature/Models/AddressTest.php +++ b/tests/Feature/Models/AddressTest.php @@ -101,3 +101,22 @@ Event::assertDispatched(AddressPrimaryUnmarked::class); Event::assertDispatched(ShippingAddressPrimaryUnmarked::class); }); + +it('stores and retrieves lat/lng correctly', function () { + $user = User::factory()->createOne(); + + $address = Address::factory()->addressable($user)->primary()->shipping()->createOne(); + + expect($address->latitude)->not->toBeNull() + ->and($address->longitude)->not->toBeNull(); + + $newLat = fake()->latitude(); + $newLng = fake()->longitude(); + + $address + ->setCoordinates($newLat, $newLng) + ->save(); + + expect($address->latitude)->toBe($newLat) + ->and($address->longitude)->toBe($newLng); +}); diff --git a/tests/TestCase.php b/tests/TestCase.php index 929009c..2911b2b 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -3,6 +3,7 @@ namespace Masterix21\Addressable\Tests; use Illuminate\Database\Eloquent\Factories\Factory; +use Illuminate\Foundation\Testing\RefreshDatabase; use Masterix21\Addressable\AddressableServiceProvider; use Orchestra\Testbench\Concerns\WithLaravelMigrations; use Orchestra\Testbench\TestCase as Orchestra; @@ -10,6 +11,7 @@ class TestCase extends Orchestra { use WithLaravelMigrations; + use RefreshDatabase; protected function setUp(): void {