Skip to content

Commit

Permalink
Adds HasAddresses trait and support
Browse files Browse the repository at this point in the history
  • Loading branch information
Patrick Breen committed Mar 10, 2021
1 parent 6154a69 commit bf80acd
Show file tree
Hide file tree
Showing 11 changed files with 475 additions and 34 deletions.
29 changes: 29 additions & 0 deletions database/factories/AddressFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Tipoff\Addresses\Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Tipoff\Addresses\Models\Address;
use Tipoff\Addresses\Models\DomesticAddress;
use Tipoff\Authorization\Models\User;

class AddressFactory extends Factory
{
protected $model = Address::class;

public function definition()
{
$user = User::factory()->create();

return [
'domestic_address_id' => randomOrCreate(DomesticAddress::class),
'addressable_type' => get_class($user),
'addressable_id' => $user->id,
'type' => $this->faker->randomElement(['shipping', 'billing']),
'creator_id' => randomOrCreate(app('user')),
'updater_id' => randomOrCreate(app('user')),
];
}
}
3 changes: 1 addition & 2 deletions database/factories/CityFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ class CityFactory extends Factory

public function definition()
{
$title = $this->faker->unique()->state;
$title = $this->faker->unique()->city;

return [
'title' => $title,
'slug' => Str::slug($title),
];
}
}
13 changes: 13 additions & 0 deletions psalm.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,17 @@
<directory name="vendor"/>
</ignoreFiles>
</projectFiles>

<issueHandlers>
<!--
When using property annotations, psalm wants you to use the `__get(...)` and `__set(..)` methods
directly within the class instead of the now annotated properties.
https://github.com/vimeo/psalm/issues/721#issuecomment-387504151
-->
<UndefinedMagicPropertyFetch>
<errorLevel type="suppress">
<directory name="src/Models" />
</errorLevel>
</UndefinedMagicPropertyFetch>
</issueHandlers>
</psalm>
41 changes: 36 additions & 5 deletions src/Models/Address.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,61 @@

namespace Tipoff\Addresses\Models;

use Assert\Assert;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Tipoff\Authorization\Models\User;
use Tipoff\Support\Models\BaseModel;
use Tipoff\Support\Traits\HasCreator;
use Tipoff\Support\Traits\HasPackageFactory;
use Tipoff\Support\Traits\HasUpdater;

/**
* @property int id
* @property DomesticAddress domesticAddress
* @property Model addressable
* @property string type
* @property string first_name
* @property string last_name
* @property string care_of
* @property string company
* @property string extended_zip
* @property string phone
* @property User creator
* @property User updater
* @property Carbon created_at
* @property Carbon updated_at
* // Raw relations
* @property int domestic_address_id
* @property string addressable_type
* @property int addressable_id
* @property int creator_id
* @property int updater_id
*/
class Address extends BaseModel
{
use HasCreator;
use HasPackageFactory;
use HasUpdater;

protected static function boot()
{
parent::boot();

static::saving(function ($address) {
if (empty($address->domestic_address_id)) {
throw new \Exception('An address must have a US domestic postal address.');
}
static::saving(function (Address $address) {
Assert::lazy()
->that($address->domestic_address_id)->notEmpty('An address must have a US domestic postal address.')
->verifyNow();
});
}

public function domesticAddress()
{
return $this->belongsTo(DomesticAddress::class);
}

public function addressable()
{
return $this->morphTo();
}
}
30 changes: 23 additions & 7 deletions src/Models/City.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,21 @@

namespace Tipoff\Addresses\Models;

use Assert\Assert;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str;
use Tipoff\Support\Models\BaseModel;
use Tipoff\Support\Traits\HasPackageFactory;

/**
* @property int id
* @property string slug
* @property string title
* @property string description
* @property Carbon created_at
* @property Carbon updated_at
*/
class City extends BaseModel
{
use HasPackageFactory;
Expand All @@ -17,16 +29,20 @@ protected static function boot()
{
parent::boot();

static::saving(function ($city) {
if (empty($city->title)) {
throw new \Exception('A city must have a name.');
}
if (empty($city->slug)) {
throw new \Exception('A city must have a slug.');
}
static::saving(function (City $city) {
$city->slug = $city->slug ?: Str::slug($city->title);

Assert::lazy()
->that($city->title)->notEmpty('A city must have a name.')
->verifyNow();
});
}

public function scopeByTitle(Builder $query, string $title): Builder
{
return $query->where('title', '=', trim($title));
}

public function zipCodes()
{
return $this->belongsToMany(Zip::class)
Expand Down
65 changes: 54 additions & 11 deletions src/Models/DomesticAddress.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,72 @@

namespace Tipoff\Addresses\Models;

use Assert\Assert;
use Carbon\Carbon;
use Tipoff\Support\Models\BaseModel;
use Tipoff\Support\Traits\HasPackageFactory;

/**
* @property int id
* @property string address_line_1
* @property string address_line_2
* @property City city
* @property Zip zip
* @property Carbon created_at
* @property Carbon updated_at
* // Raw relations
* @property int city_id
* @property string zip_code
*/
class DomesticAddress extends BaseModel
{
use HasPackageFactory;

protected $casts = [];
protected $casts = [
'id' => 'integer',
'city_id' => 'integer',
];

protected $fillable = [
'address_line_1',
'address_line_2',
'city_id',
'zip_code',
];

/**
* @param string $line1
* @param string|null $line2
* @param string|City $city
* @param string|Zip $zip
* @return static
*/
public static function createDomesticAddress(string $line1, ?string $line2, $city, $zip): self
{
$zip = ($zip instanceof Zip) ? $zip : Zip::query()->findOrFail(trim($zip));
$city = ($city instanceof City) ? $city : City::query()->byTitle($city)->firstOrFail();

/** @var DomesticAddress $domesticAddress */
$domesticAddress = static::query()->firstOrCreate([
'address_line_1' => trim($line1),
'address_line_2' => empty($line2) ? null : trim($line2),
'city_id' => $city->id,
'zip_code' => $zip->code,
]);

return $domesticAddress;
}

protected static function boot()
{
parent::boot();

static::saving(function ($address) {
if (empty($address->address_line_1)) {
throw new \Exception('US domestic addresses must have a street.');
}
if (empty($address->city_id)) {
throw new \Exception('US domestic addresses must have a city.');
}
if (empty($address->zip_code)) {
throw new \Exception('US domestic addresses must have a zip code.');
}
static::saving(function (DomesticAddress $address) {
Assert::lazy()
->that($address->address_line_1)->notEmpty('US domestic addresses must have a street.')
->that($address->city_id)->notEmpty('US domestic addresses must have a city.')
->that($address->zip_code)->notEmpty('US domestic addresses must have zip code.')
->verifyNow();
});
}

Expand Down
39 changes: 31 additions & 8 deletions src/Models/Zip.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,27 @@

namespace Tipoff\Addresses\Models;

use Assert\Assert;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Tipoff\Support\Models\BaseModel;
use Tipoff\Support\Traits\HasPackageFactory;

/**
* @property string code
* @property State state
* @property Region|null region
* @property Timezone|null timezone
* @property float|null latitude
* @property float|null longitude
* @property bool decommissioned
* @property Carbon created_at
* @property Carbon updated_at
* // raw relations
* @property int state_id
* @property int region_id
* @property int timezone_id
*/
class Zip extends BaseModel
{
use HasPackageFactory;
Expand All @@ -16,19 +34,24 @@ class Zip extends BaseModel
public $incrementing = false;
public $keyType = 'string';

protected $casts = [];
protected $casts = [
'latitude' => 'float',
'longitude' => 'float',
'decommissioned' => 'bool',
'state_id' => 'integer',
'region_id' => 'integer',
'timezone_id' => 'integer',
];

protected static function boot()
{
parent::boot();

static::saving(function ($zip) {
if (empty($zip->code)) {
throw new \Exception('A ZIP must have a code.');
}
if (empty($zip->state_id)) {
throw new \Exception('A ZIP Code must belong to a state.');
}
static::saving(function (Zip $zip) {
Assert::lazy()
->that($zip->code)->notEmpty('A ZIP must have a code.')
->that($zip->state_id)->notEmpty('A ZIP Code must belong to a state.')
->verifyNow();
});
}

Expand Down
70 changes: 70 additions & 0 deletions src/Traits/HasAddresses.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

namespace Tipoff\Addresses\Traits;


use Illuminate\Support\Collection;
use Tipoff\Addresses\Models\Address;
use Tipoff\Addresses\Models\City;
use Tipoff\Addresses\Models\DomesticAddress;
use Tipoff\Addresses\Models\Zip;

/**
* @property Collection addresses
*/
trait HasAddresses
{
/**
* @param string $line1
* @param string|null $line2
* @param string|City $city
* @param string|Zip $zip
* @return DomesticAddress
*/
public static function createDomesticAddress(string $line1, ?string $line2, $city, $zip): DomesticAddress
{
return DomesticAddress::createDomesticAddress($line1, $line2, $city, $zip);
}

public function getAddressByType(string $type): ?Address
{
return $this->addresses
->filter(function (Address $address) use ($type) {
return $address->type === $type;
})
->first();
}

public function setAddressByType(string $type, DomesticAddress $domesticAddress): Address
{
$address = $this->getAddressByType($type);
if (!$address) {
$address = new Address();
$address->type = $type;
$address->addressable()->associate($this);
}

$address->domesticAddress()->associate($domesticAddress)->save();
$address->load('domesticAddress');
$this->load('addresses');

return $address;
}

public function getAddress(): ?Address
{
return $this->getAddressByType(get_class($this));
}

public function setAddress(DomesticAddress $domesticAddress): Address
{
return $this->setAddressByType(get_class($this), $domesticAddress);
}

public function addresses()
{
return $this->morphMany(Address::class, 'addressable');
}
}
Loading

0 comments on commit bf80acd

Please sign in to comment.