Skip to content

Commit

Permalink
feat!: Split Notification objects from API Entities (#38)
Browse files Browse the repository at this point in the history
Allows long term backwards compatibility with notifications whilst not
reducing the type safety of API entities

BREAKING CHANGE: Any existing notification handling will result in
different types for objects but their behaviour/functionality remains the same
  • Loading branch information
mikeymike authored Feb 16, 2024
1 parent 5b60860 commit 0eaee85
Show file tree
Hide file tree
Showing 127 changed files with 3,637 additions and 84 deletions.
2 changes: 2 additions & 0 deletions rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Php81\Rector\Class_\MyCLabsClassToEnumRector;
use Rector\Set\ValueObject\LevelSetList;
use Rector\Set\ValueObject\SetList;

Expand All @@ -22,6 +23,7 @@
$rectorConfig->skip([
'*.json',
'*/Fixture/*',
MyCLabsClassToEnumRector::class,
]);

$rectorConfig->importNames();
Expand Down
16 changes: 5 additions & 11 deletions src/Entities/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
namespace Paddle\SDK\Entities;

use Paddle\SDK\Entities\Event\EventTypeName;
use Paddle\SDK\Entities\Notification\NotificationDiscount;
use Paddle\SDK\Entities\Notification\NotificationSubscription;
use Paddle\SDK\Notifications\Entities\Entity as NotificationEntity;

class Event implements Entity
{
Expand All @@ -17,23 +16,18 @@ protected function __construct(
public string $eventId,
public EventTypeName $eventType,
public \DateTimeInterface $occurredAt,
public Entity $data,
public NotificationEntity $data,
) {
}

public static function from(array $data): self
{
$type = explode('.', (string) $data['event_type'])[0] ?? '';

/** @var class-string<Entity> $entity */
$entity = match ($type) {
'discount' => NotificationDiscount::class,
'subscription' => NotificationSubscription::class,
'adjustment' => Adjustment::class,
default => sprintf('\Paddle\SDK\Entities\%s', ucfirst($type)),
};
/** @var class-string<NotificationEntity> $entity */
$entity = sprintf('\Paddle\SDK\Notifications\Entities\%s', ucfirst($type));

if (! class_exists($entity) || ! in_array(Entity::class, class_implements($entity), true)) {
if (! class_exists($entity) || ! in_array(NotificationEntity::class, class_implements($entity), true)) {
throw new \UnexpectedValueException("Event type '{$type}' cannot be mapped to an object");
}

Expand Down
59 changes: 59 additions & 0 deletions src/Notifications/Entities/Address.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

/**
* |------
* | ! Generated code !
* | Altering this code will result in changes being overwritten |
* |-------------------------------------------------------------|.
*/

namespace Paddle\SDK\Notifications\Entities;

use Paddle\SDK\Notifications\Entities\Shared\CountryCode;
use Paddle\SDK\Notifications\Entities\Shared\CustomData;
use Paddle\SDK\Notifications\Entities\Shared\ImportMeta;
use Paddle\SDK\Notifications\Entities\Shared\Status;

class Address implements Entity
{
/**
* @internal
*/
protected function __construct(
public string $id,
public string|null $description,
public string|null $firstLine,
public string|null $secondLine,
public string|null $city,
public string|null $postalCode,
public string|null $region,
public CountryCode $countryCode,
public CustomData|null $customData,
public Status $status,
public \DateTimeInterface $createdAt,
public \DateTimeInterface $updatedAt,
public ImportMeta|null $importMeta,
) {
}

public static function from(array $data): self
{
return new self(
id: $data['id'],
description: $data['description'] ?? null,
firstLine: $data['first_line'] ?? null,
secondLine: $data['second_line'] ?? null,
city: $data['city'] ?? null,
postalCode: $data['postal_code'] ?? null,
region: $data['region'] ?? null,
countryCode: CountryCode::from($data['country_code']),
customData: isset($data['custom_data']) ? new CustomData($data['custom_data']) : null,
status: Status::from($data['status']),
createdAt: DateTime::from($data['created_at']),
updatedAt: DateTime::from($data['updated_at']),
importMeta: isset($data['import_meta']) ? ImportMeta::from($data['import_meta']) : null,
);
}
}
65 changes: 65 additions & 0 deletions src/Notifications/Entities/Adjustment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

/**
* |------
* | ! Generated code !
* | Altering this code will result in changes being overwritten |
* |-------------------------------------------------------------|.
*/

namespace Paddle\SDK\Notifications\Entities;

use Paddle\SDK\Notifications\Entities\Adjustment\AdjustmentItem;
use Paddle\SDK\Notifications\Entities\Shared\Action;
use Paddle\SDK\Notifications\Entities\Shared\AdjustmentStatus;
use Paddle\SDK\Notifications\Entities\Shared\AdjustmentTotals;
use Paddle\SDK\Notifications\Entities\Shared\CurrencyCode;
use Paddle\SDK\Notifications\Entities\Shared\PayoutTotalsAdjustment;

class Adjustment implements Entity
{
/**
* @internal
*
* @param array<AdjustmentItem> $items
*/
protected function __construct(
public string $id,
public Action $action,
public string $transactionId,
public string|null $subscriptionId,
public string $customerId,
public string $reason,
public bool|null $creditAppliedToBalance,
public CurrencyCode $currencyCode,
public AdjustmentStatus $status,
public array $items,
public AdjustmentTotals $totals,
public PayoutTotalsAdjustment|null $payoutTotals,
public \DateTimeInterface $createdAt,
public \DateTimeInterface|null $updatedAt,
) {
}

public static function from(array $data): self
{
return new self(
id: $data['id'],
action: Action::from($data['action']),
transactionId: $data['transaction_id'],
subscriptionId: $data['subscription_id'] ?? null,
customerId: $data['customer_id'],
reason: $data['reason'],
creditAppliedToBalance: $data['credit_applied_to_balance'] ?? null,
currencyCode: CurrencyCode::from($data['currency_code']),
status: AdjustmentStatus::from($data['status']),
items: array_map(fn (array $item): AdjustmentItem => AdjustmentItem::from($item), $data['items']),
totals: AdjustmentTotals::from($data['totals']),
payoutTotals: isset($data['payout_totals']) ? PayoutTotalsAdjustment::from($data['payout_totals']) : null,
createdAt: DateTime::from($data['created_at']),
updatedAt: isset($data['updated_at']) ? DateTime::from($data['updated_at']) : null,
);
}
}
41 changes: 41 additions & 0 deletions src/Notifications/Entities/Adjustment/AdjustmentItem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

/**
* |------
* | ! Generated code !
* | Altering this code will result in changes being overwritten |
* |-------------------------------------------------------------|.
*/

namespace Paddle\SDK\Notifications\Entities\Adjustment;

use Paddle\SDK\Notifications\Entities\Shared\AdjustmentItemTotals;
use Paddle\SDK\Notifications\Entities\Shared\AdjustmentProration;
use Paddle\SDK\Notifications\Entities\Shared\AdjustmentType;

class AdjustmentItem
{
public function __construct(
public string $id,
public string $itemId,
public AdjustmentType $type,
public string|null $amount,
public AdjustmentProration|null $proration,
public AdjustmentItemTotals $totals,
) {
}

public static function from(array $data): self
{
return new self(
id: $data['id'],
itemId: $data['item_id'],
type: AdjustmentType::from($data['type']),
amount: $data['amount'] ?? null,
proration: $data['proration'] ? AdjustmentProration::from($data['proration']) : null,
totals: AdjustmentItemTotals::from($data['totals']),
);
}
}
55 changes: 55 additions & 0 deletions src/Notifications/Entities/Business.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

/**
* |------
* | ! Generated code !
* | Altering this code will result in changes being overwritten |
* |-------------------------------------------------------------|.
*/

namespace Paddle\SDK\Notifications\Entities;

use Paddle\SDK\Notifications\Entities\Business\BusinessesContacts;
use Paddle\SDK\Notifications\Entities\Shared\CustomData;
use Paddle\SDK\Notifications\Entities\Shared\ImportMeta;
use Paddle\SDK\Notifications\Entities\Shared\Status;

class Business implements Entity
{
/**
* @internal
*
* @param array<BusinessesContacts> $contacts
*/
protected function __construct(
public string $id,
public string $name,
public string|null $companyNumber,
public string|null $taxIdentifier,
public Status $status,
public array $contacts,
public \DateTimeInterface $createdAt,
public \DateTimeInterface $updatedAt,
public CustomData|null $customData,
public ImportMeta|null $importMeta,
) {
}

public static function from(array $data): self
{
return new self(
id: $data['id'],
name: $data['name'],
companyNumber: $data['company_number'] ?? null,
taxIdentifier: $data['tax_identifier'] ?? null,
status: Status::from($data['status']),
contacts: array_map(fn (array $contact): BusinessesContacts => BusinessesContacts::from($contact), $data['contacts']),
createdAt: DateTime::from($data['created_at']),
updatedAt: DateTime::from($data['updated_at']),
customData: isset($data['custom_data']) ? new CustomData($data['custom_data']) : null,
importMeta: isset($data['import_meta']) ? ImportMeta::from($data['import_meta']) : null,
);
}
}
26 changes: 26 additions & 0 deletions src/Notifications/Entities/Business/BusinessesContacts.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

/**
* |------
* | ! Generated code !
* | Altering this code will result in changes being overwritten |
* |-------------------------------------------------------------|.
*/

namespace Paddle\SDK\Notifications\Entities\Business;

class BusinessesContacts
{
public function __construct(
public string $name,
public string $email,
) {
}

public static function from(array $data): self
{
return new self($data['name'], $data['email']);
}
}
52 changes: 52 additions & 0 deletions src/Notifications/Entities/Customer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

/**
* |------
* | ! Generated code !
* | Altering this code will result in changes being overwritten |
* |-------------------------------------------------------------|.
*/

namespace Paddle\SDK\Notifications\Entities;

use Paddle\SDK\Notifications\Entities\Shared\CustomData;
use Paddle\SDK\Notifications\Entities\Shared\ImportMeta;
use Paddle\SDK\Notifications\Entities\Shared\Status;

class Customer implements Entity
{
/**
* @internal
*/
protected function __construct(
public string $id,
public string|null $name,
public string $email,
public bool $marketingConsent,
public Status $status,
public CustomData|null $customData,
public string $locale,
public \DateTimeInterface $createdAt,
public \DateTimeInterface $updatedAt,
public ImportMeta|null $importMeta,
) {
}

public static function from(array $data): self
{
return new self(
id: $data['id'],
name: $data['name'] ?? null,
email: $data['email'],
marketingConsent: $data['marketing_consent'],
status: Status::from($data['status']),
customData: isset($data['custom_data']) ? new CustomData($data['custom_data']) : null,
locale: $data['locale'],
createdAt: DateTime::from($data['created_at']),
updatedAt: DateTime::from($data['updated_at']),
importMeta: isset($data['import_meta']) ? ImportMeta::from($data['import_meta']) : null,
);
}
}
36 changes: 36 additions & 0 deletions src/Notifications/Entities/DateTime.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Paddle\SDK\Notifications\Entities;

class DateTime extends \DateTimeImmutable
{
final public const PADDLE_RFC3339 = 'Y-m-d\TH:i:s.up';

public function __construct(string $datetime = 'now')
{
// Ensure formatted dates are in UTC
parent::__construct(datetime: $datetime, timezone: new \DateTimeZone('UTC'));
}

public function format(string|null $format = null): string
{
return parent::format($format ?? self::PADDLE_RFC3339);
}

public static function from(string|\DateTimeInterface $date): self|null
{
if ($date === '0001-01-01T00:00:00Z') {
return null;
}

$date = is_string($date) ? $date : $date->format(self::PADDLE_RFC3339);

try {
return new self($date);
} catch (\Exception) {
return null;
}
}
}
Loading

0 comments on commit 0eaee85

Please sign in to comment.