Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\AI;
|
||||
@@ -6,10 +7,10 @@ namespace App\Infrastructure\AI;
|
||||
use App\Domain\AI\AiModel;
|
||||
use App\Domain\AI\AiProvider;
|
||||
use App\Domain\AI\AiQueryHandlerInterface;
|
||||
use App\Framework\HttpClient\HttpClient;
|
||||
use App\Infrastructure\AI\GPT4All\Gpt4AllQueryHandler;
|
||||
use App\Infrastructure\AI\Ollama\OllamaQueryHandler;
|
||||
use App\Infrastructure\AI\OpenAI\OpenAiQueryHandler;
|
||||
use App\Framework\HttpClient\HttpClient;
|
||||
use InvalidArgumentException;
|
||||
|
||||
final readonly class AiHandlerFactory
|
||||
@@ -19,7 +20,8 @@ final readonly class AiHandlerFactory
|
||||
private string $openAiApiKey = '',
|
||||
private string $gpt4AllApiUrl = 'http://host.docker.internal:4891',
|
||||
private string $ollamaApiUrl = 'http://host.docker.internal:11434'
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
public function createForModel(AiModel $model): AiQueryHandlerInterface
|
||||
{
|
||||
@@ -59,12 +61,15 @@ final readonly class AiHandlerFactory
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<AiProvider>
|
||||
*/
|
||||
public function getAvailableProviders(): array
|
||||
{
|
||||
$providers = [];
|
||||
|
||||
// OpenAI ist verfügbar wenn API Key gesetzt ist
|
||||
if (!empty($this->openAiApiKey)) {
|
||||
if (! empty($this->openAiApiKey)) {
|
||||
$providers[] = AiProvider::OPENAI;
|
||||
}
|
||||
|
||||
@@ -86,7 +91,8 @@ final readonly class AiHandlerFactory
|
||||
public function getAvailableModels(): array
|
||||
{
|
||||
$availableProviders = $this->getAvailableProviders();
|
||||
$providerSet = array_flip($availableProviders);
|
||||
$providerValues = array_map(fn (AiProvider $provider) => $provider->value, $availableProviders);
|
||||
$providerSet = array_flip($providerValues);
|
||||
$availableModels = [];
|
||||
|
||||
foreach (AiModel::cases() as $model) {
|
||||
@@ -114,11 +120,12 @@ final readonly class AiHandlerFactory
|
||||
|
||||
public function getOllamaAvailableModels(): array
|
||||
{
|
||||
if (!$this->isProviderAvailable(AiProvider::OLLAMA)) {
|
||||
if (! $this->isProviderAvailable(AiProvider::OLLAMA)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$ollamaHandler = new OllamaQueryHandler($this->httpClient, $this->ollamaApiUrl);
|
||||
|
||||
return $ollamaHandler->getAvailableModels();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\AI;
|
||||
@@ -13,7 +14,8 @@ final readonly class AiService
|
||||
{
|
||||
public function __construct(
|
||||
private AiHandlerFactory $handlerFactory
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
public function query(
|
||||
string $message,
|
||||
@@ -51,6 +53,7 @@ final readonly class AiService
|
||||
|
||||
// Erstes verfügbares Modell verwenden
|
||||
$fallbackModel = $availableModels[0];
|
||||
|
||||
return $this->query($message, $fallbackModel, $messages, $temperature, $maxTokens);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\AI\GPT4All;
|
||||
|
||||
use App\Domain\AI\AiProvider;
|
||||
use App\Domain\AI\AiQuery;
|
||||
use App\Domain\AI\AiQueryHandlerInterface;
|
||||
use App\Domain\AI\AiResponse;
|
||||
use App\Domain\AI\Exception\AiProviderUnavailableException;
|
||||
use App\Domain\AI\AiProvider;
|
||||
use App\Framework\Http\Headers;
|
||||
use App\Framework\Http\Method;
|
||||
use App\Framework\HttpClient\ClientRequest;
|
||||
use App\Framework\HttpClient\HttpClient;
|
||||
use App\Framework\HttpClient\Exception\CurlExecutionFailed;
|
||||
use App\Framework\HttpClient\HttpClient;
|
||||
|
||||
final readonly class Gpt4AllQueryHandler implements AiQueryHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private HttpClient $httpClient,
|
||||
private string $gpt4AllApiUrl = 'http://host.docker.internal:4891'
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
public function isAvailable(): bool
|
||||
{
|
||||
@@ -28,6 +30,7 @@ final readonly class Gpt4AllQueryHandler implements AiQueryHandlerInterface
|
||||
$request = ClientRequest::json(Method::GET, $healthUrl, []);
|
||||
|
||||
$this->httpClient->send($request);
|
||||
|
||||
return true;
|
||||
} catch (CurlExecutionFailed $e) {
|
||||
return false;
|
||||
@@ -38,7 +41,7 @@ final readonly class Gpt4AllQueryHandler implements AiQueryHandlerInterface
|
||||
|
||||
public function __invoke(AiQuery $query): AiResponse
|
||||
{
|
||||
if (!$this->isAvailable()) {
|
||||
if (! $this->isAvailable()) {
|
||||
throw new AiProviderUnavailableException(
|
||||
AiProvider::GPT4ALL,
|
||||
"GPT4All Server ist nicht erreichbar unter {$this->gpt4AllApiUrl}. " .
|
||||
@@ -67,7 +70,7 @@ final readonly class Gpt4AllQueryHandler implements AiQueryHandlerInterface
|
||||
$data
|
||||
)->with([
|
||||
'headers' => new Headers()
|
||||
->with('Content-Type', 'application/json')
|
||||
->with('Content-Type', 'application/json'),
|
||||
]);
|
||||
|
||||
$response = $this->httpClient->send($request);
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\AI\Ollama;
|
||||
|
||||
use App\Domain\AI\AiProvider;
|
||||
use App\Domain\AI\AiQuery;
|
||||
use App\Domain\AI\AiQueryHandlerInterface;
|
||||
use App\Domain\AI\AiResponse;
|
||||
use App\Domain\AI\Exception\AiProviderUnavailableException;
|
||||
use App\Domain\AI\AiProvider;
|
||||
use App\Framework\Http\Headers;
|
||||
use App\Framework\Http\Method;
|
||||
use App\Framework\HttpClient\ClientOptions;
|
||||
use App\Framework\HttpClient\ClientRequest;
|
||||
use App\Framework\HttpClient\HttpClient;
|
||||
use App\Framework\HttpClient\Exception\CurlExecutionFailed;
|
||||
use App\Framework\HttpClient\HttpClient;
|
||||
|
||||
final readonly class OllamaQueryHandler implements AiQueryHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private HttpClient $httpClient,
|
||||
private string $ollamaApiUrl = 'http://host.docker.internal:11434'
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
public function isAvailable(): bool
|
||||
{
|
||||
@@ -59,7 +61,7 @@ final readonly class OllamaQueryHandler implements AiQueryHandlerInterface
|
||||
$response = $this->httpClient->send($request);
|
||||
$body = json_decode($response->body, true);
|
||||
|
||||
return array_map(fn($model) => $model['name'], $body['models'] ?? []);
|
||||
return array_map(fn ($model) => $model['name'], $body['models'] ?? []);
|
||||
} catch (\Exception $e) {
|
||||
return [];
|
||||
}
|
||||
@@ -67,7 +69,7 @@ final readonly class OllamaQueryHandler implements AiQueryHandlerInterface
|
||||
|
||||
public function __invoke(AiQuery $query): AiResponse
|
||||
{
|
||||
if (!$this->isAvailable()) {
|
||||
if (! $this->isAvailable()) {
|
||||
throw new AiProviderUnavailableException(
|
||||
AiProvider::OLLAMA,
|
||||
"Ollama Server ist nicht erreichbar unter {$this->ollamaApiUrl}. " .
|
||||
@@ -79,7 +81,7 @@ final readonly class OllamaQueryHandler implements AiQueryHandlerInterface
|
||||
$availableModels = $this->getAvailableModels();
|
||||
$modelExists = in_array($query->model->value, $availableModels, true);
|
||||
|
||||
if (!$modelExists) {
|
||||
if (! $modelExists) {
|
||||
throw new AiProviderUnavailableException(
|
||||
AiProvider::OLLAMA,
|
||||
"Modell '{$query->model->value}' ist nicht verfügbar. " .
|
||||
@@ -101,7 +103,7 @@ final readonly class OllamaQueryHandler implements AiQueryHandlerInterface
|
||||
'stream' => false, // Keine Streaming-Antwort
|
||||
'options' => [
|
||||
'temperature' => $query->temperature,
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
if ($query->maxTokens !== null) {
|
||||
@@ -115,7 +117,7 @@ final readonly class OllamaQueryHandler implements AiQueryHandlerInterface
|
||||
$data
|
||||
)->with([
|
||||
'headers' => new Headers()
|
||||
->with('Content-Type', 'application/json')
|
||||
->with('Content-Type', 'application/json'),
|
||||
]);
|
||||
|
||||
$response = $this->httpClient->send($request);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\AI\OpenAI;
|
||||
@@ -16,7 +17,8 @@ final readonly class OpenAiQueryHandler implements AiQueryHandlerInterface
|
||||
public function __construct(
|
||||
private HttpClient $httpClient,
|
||||
private string $openAiApiKey = ""
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(AiQuery $query): AiResponse
|
||||
{
|
||||
@@ -40,7 +42,7 @@ final readonly class OpenAiQueryHandler implements AiQueryHandlerInterface
|
||||
$data
|
||||
)->with([
|
||||
'headers' => new Headers()
|
||||
->with('Authorization', 'Bearer ' . $this->openAiApiKey)
|
||||
->with('Authorization', 'Bearer ' . $this->openAiApiKey),
|
||||
]);
|
||||
|
||||
$response = $this->httpClient->send($request);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api;
|
||||
@@ -55,7 +56,7 @@ final class GitHubClient
|
||||
data: [
|
||||
'name' => $name,
|
||||
'description' => $description,
|
||||
'private' => $isPrivate
|
||||
'private' => $isPrivate,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail;
|
||||
|
||||
use App\Framework\Http\Method;
|
||||
@@ -8,7 +10,8 @@ final readonly class BlacklistService
|
||||
{
|
||||
public function __construct(
|
||||
private RapidMailApiClient $apiClient
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt die Blacklist
|
||||
@@ -17,7 +20,7 @@ final readonly class BlacklistService
|
||||
{
|
||||
return $this->apiClient->request(Method::GET, 'blacklist', [], [
|
||||
'page' => $page,
|
||||
'per_page' => $perPage
|
||||
'per_page' => $perPage,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail\Commands;
|
||||
|
||||
use App\Infrastructure\Api\RapidMail\RecipientListId;
|
||||
|
||||
final readonly class CreateRecipientCommand
|
||||
{
|
||||
public function __construct(
|
||||
public string $email,
|
||||
public ?string $firstname = null,
|
||||
public ?string $lastname = null,
|
||||
public ?string $title = null,
|
||||
public ?string $company = null,
|
||||
public ?string $zip = null,
|
||||
public ?string $city = null,
|
||||
public ?string $street = null,
|
||||
public ?string $country = null,
|
||||
public ?string $phone = null,
|
||||
public ?string $birthdate = null,
|
||||
public ?string $gender = null,
|
||||
public ?string $status = null,
|
||||
public ?RecipientListId $recipientListId = null,
|
||||
public array $customFields = []
|
||||
) {
|
||||
if (! filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
throw new \InvalidArgumentException('Invalid email format');
|
||||
}
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
$data = ['email' => $this->email];
|
||||
|
||||
if ($this->firstname !== null) {
|
||||
$data['firstname'] = $this->firstname;
|
||||
}
|
||||
if ($this->lastname !== null) {
|
||||
$data['lastname'] = $this->lastname;
|
||||
}
|
||||
if ($this->title !== null) {
|
||||
$data['title'] = $this->title;
|
||||
}
|
||||
if ($this->company !== null) {
|
||||
$data['company'] = $this->company;
|
||||
}
|
||||
if ($this->zip !== null) {
|
||||
$data['zip'] = $this->zip;
|
||||
}
|
||||
if ($this->city !== null) {
|
||||
$data['city'] = $this->city;
|
||||
}
|
||||
if ($this->street !== null) {
|
||||
$data['street'] = $this->street;
|
||||
}
|
||||
if ($this->country !== null) {
|
||||
$data['country'] = $this->country;
|
||||
}
|
||||
if ($this->phone !== null) {
|
||||
$data['phone'] = $this->phone;
|
||||
}
|
||||
if ($this->birthdate !== null) {
|
||||
$data['birthdate'] = $this->birthdate;
|
||||
}
|
||||
if ($this->gender !== null) {
|
||||
$data['gender'] = $this->gender;
|
||||
}
|
||||
if ($this->status !== null) {
|
||||
$data['status'] = $this->status;
|
||||
}
|
||||
if ($this->recipientListId !== null) {
|
||||
$data['recipientlist_id'] = $this->recipientListId->value;
|
||||
}
|
||||
|
||||
return array_merge($data, $this->customFields);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail\Commands;
|
||||
|
||||
use App\Infrastructure\Api\RapidMail\RecipientId;
|
||||
use App\Infrastructure\Api\RapidMail\RecipientListId;
|
||||
|
||||
final readonly class UpdateRecipientCommand
|
||||
{
|
||||
public function __construct(
|
||||
public RecipientId $id,
|
||||
public string $email,
|
||||
public ?string $firstname = null,
|
||||
public ?string $lastname = null,
|
||||
public ?string $title = null,
|
||||
public ?string $company = null,
|
||||
public ?string $zip = null,
|
||||
public ?string $city = null,
|
||||
public ?string $street = null,
|
||||
public ?string $country = null,
|
||||
public ?string $phone = null,
|
||||
public ?string $birthdate = null,
|
||||
public ?string $gender = null,
|
||||
public ?string $status = null,
|
||||
public ?RecipientListId $recipientListId = null,
|
||||
public array $customFields = []
|
||||
) {
|
||||
if (! filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
throw new \InvalidArgumentException('Invalid email format');
|
||||
}
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
$data = ['email' => $this->email];
|
||||
|
||||
if ($this->firstname !== null) {
|
||||
$data['firstname'] = $this->firstname;
|
||||
}
|
||||
if ($this->lastname !== null) {
|
||||
$data['lastname'] = $this->lastname;
|
||||
}
|
||||
if ($this->title !== null) {
|
||||
$data['title'] = $this->title;
|
||||
}
|
||||
if ($this->company !== null) {
|
||||
$data['company'] = $this->company;
|
||||
}
|
||||
if ($this->zip !== null) {
|
||||
$data['zip'] = $this->zip;
|
||||
}
|
||||
if ($this->city !== null) {
|
||||
$data['city'] = $this->city;
|
||||
}
|
||||
if ($this->street !== null) {
|
||||
$data['street'] = $this->street;
|
||||
}
|
||||
if ($this->country !== null) {
|
||||
$data['country'] = $this->country;
|
||||
}
|
||||
if ($this->phone !== null) {
|
||||
$data['phone'] = $this->phone;
|
||||
}
|
||||
if ($this->birthdate !== null) {
|
||||
$data['birthdate'] = $this->birthdate;
|
||||
}
|
||||
if ($this->gender !== null) {
|
||||
$data['gender'] = $this->gender;
|
||||
}
|
||||
if ($this->status !== null) {
|
||||
$data['status'] = $this->status;
|
||||
}
|
||||
if ($this->recipientListId !== null) {
|
||||
$data['recipientlist_id'] = $this->recipientListId->value;
|
||||
}
|
||||
|
||||
return array_merge($data, $this->customFields);
|
||||
}
|
||||
}
|
||||
286
src/Infrastructure/Api/RapidMail/Examples/UsageExamples.php
Normal file
286
src/Infrastructure/Api/RapidMail/Examples/UsageExamples.php
Normal file
@@ -0,0 +1,286 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail\Examples;
|
||||
|
||||
use App\Infrastructure\Api\RapidMail\Commands\CreateRecipientCommand;
|
||||
use App\Infrastructure\Api\RapidMail\Factories\RecipientCommandFactory;
|
||||
use App\Infrastructure\Api\RapidMail\RecipientId;
|
||||
use App\Infrastructure\Api\RapidMail\RecipientListId;
|
||||
use App\Infrastructure\Api\RapidMail\RecipientListService;
|
||||
use App\Infrastructure\Api\RapidMail\RecipientService;
|
||||
|
||||
/**
|
||||
* Usage examples for the refactored RapidMail API with Value Objects
|
||||
*/
|
||||
class UsageExamples
|
||||
{
|
||||
public function __construct(
|
||||
private RecipientService $recipientService,
|
||||
private RecipientListService $recipientListService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 1: Creating a new recipient using Commands
|
||||
*/
|
||||
public function createRecipientExample(): void
|
||||
{
|
||||
// Create recipient list first
|
||||
$recipientList = $this->recipientListService->create(
|
||||
'Newsletter Subscribers',
|
||||
'Main newsletter subscriber list'
|
||||
);
|
||||
|
||||
// Create recipient using Command
|
||||
$command = new CreateRecipientCommand(
|
||||
email: 'john.doe@example.com',
|
||||
firstname: 'John',
|
||||
lastname: 'Doe',
|
||||
company: 'Example Corp',
|
||||
recipientListId: $recipientList->id,
|
||||
customFields: [
|
||||
'source' => 'website_signup',
|
||||
'interests' => 'php,javascript',
|
||||
]
|
||||
);
|
||||
|
||||
$recipient = $this->recipientService->createWithCommand($command);
|
||||
|
||||
echo "Created recipient with ID: {$recipient->id->value}\n";
|
||||
echo "Full name: {$recipient->getFullName()}\n";
|
||||
echo "Is active: " . ($recipient->isActive() ? 'Yes' : 'No') . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 2: Creating from array data using Factory
|
||||
*/
|
||||
public function createFromArrayExample(): void
|
||||
{
|
||||
$formData = [
|
||||
'email' => 'jane.smith@example.com',
|
||||
'firstname' => 'Jane',
|
||||
'lastname' => 'Smith',
|
||||
'company' => 'Tech Solutions',
|
||||
'newsletter_preference' => 'weekly',
|
||||
'signup_date' => '2025-01-16',
|
||||
];
|
||||
|
||||
$recipientListId = new RecipientListId(776);
|
||||
$command = RecipientCommandFactory::createFromArray($formData, $recipientListId);
|
||||
$recipient = $this->recipientService->createWithCommand($command);
|
||||
|
||||
echo "Created recipient: {$recipient->email}\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 3: Updating an existing recipient
|
||||
*/
|
||||
public function updateRecipientExample(): void
|
||||
{
|
||||
$recipientId = new RecipientId(123);
|
||||
$recipient = $this->recipientService->get($recipientId);
|
||||
|
||||
// Update using factory with changes
|
||||
$updateCommand = RecipientCommandFactory::updateFromRecipientWithChanges(
|
||||
$recipient,
|
||||
[
|
||||
'company' => 'New Company Name',
|
||||
'status' => 'active',
|
||||
'customFields' => ['last_login' => '2025-01-16'],
|
||||
]
|
||||
);
|
||||
|
||||
$updatedRecipient = $this->recipientService->updateWithCommand($updateCommand);
|
||||
echo "Updated recipient company to: {$updatedRecipient->company}\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 4: Working with RecipientLists
|
||||
*/
|
||||
public function recipientListExample(): void
|
||||
{
|
||||
// Get all recipient lists
|
||||
$lists = $this->recipientListService->getAll();
|
||||
|
||||
foreach ($lists as $list) {
|
||||
echo "List: {$list->name} (ID: {$list->id->value})\n";
|
||||
echo " Recipients: {$list->recipientCount}\n";
|
||||
echo " Is default: " . ($list->isDefault() ? 'Yes' : 'No') . "\n";
|
||||
echo " Is empty: " . ($list->isEmpty() ? 'Yes' : 'No') . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 5: Searching recipients
|
||||
*/
|
||||
public function searchRecipientsExample(): void
|
||||
{
|
||||
// Search by email
|
||||
$recipientListId = new RecipientListId(776);
|
||||
$recipient = $this->recipientService->findByEmail(
|
||||
'john.doe@example.com',
|
||||
$recipientListId
|
||||
);
|
||||
|
||||
if ($recipient !== null) {
|
||||
echo "Found recipient: {$recipient->getFullName()}\n";
|
||||
echo "Created at: {$recipient->createdAt?->format('Y-m-d H:i:s')}\n";
|
||||
}
|
||||
|
||||
// Search with filters
|
||||
$recipients = $this->recipientService->search([
|
||||
'status' => 'active',
|
||||
'recipientlist_id' => $recipientListId->value,
|
||||
]);
|
||||
|
||||
echo "Found " . count($recipients) . " active recipients\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 6: Value Object benefits
|
||||
*/
|
||||
public function valueObjectBenefitsExample(): void
|
||||
{
|
||||
// Type safety - this prevents errors
|
||||
$recipientId = new RecipientId(123);
|
||||
$recipientListId = new RecipientListId(776);
|
||||
|
||||
// You can't accidentally mix up IDs
|
||||
// $this->recipientService->get($recipientListId); // ❌ Type error
|
||||
$recipient = $this->recipientService->get($recipientId); // ✅ Correct
|
||||
|
||||
// Value Objects have useful methods
|
||||
$otherRecipientId = new RecipientId(123);
|
||||
if ($recipientId->equals($otherRecipientId)) {
|
||||
echo "Same recipient ID\n";
|
||||
}
|
||||
|
||||
// Easy string conversion
|
||||
echo "Recipient ID: {$recipientId}\n"; // Uses __toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 7: Error handling with Value Objects
|
||||
*/
|
||||
public function errorHandlingExample(): void
|
||||
{
|
||||
try {
|
||||
// This will throw an exception
|
||||
$invalidId = new RecipientId(-1);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
echo "Caught invalid ID: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
try {
|
||||
// This will throw an exception if email is invalid
|
||||
$command = new CreateRecipientCommand('invalid-email');
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
echo "Caught invalid email: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 8: Backward compatibility
|
||||
*/
|
||||
public function backwardCompatibilityExample(): void
|
||||
{
|
||||
// Old way still works (but is deprecated)
|
||||
$legacyRecipient = $this->recipientService->getById(123);
|
||||
|
||||
// New way with Value Objects
|
||||
$newRecipient = $this->recipientService->get(new RecipientId(123));
|
||||
|
||||
echo "Both approaches work, but new way is type-safe\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 9: Complex workflow
|
||||
*/
|
||||
public function complexWorkflowExample(): void
|
||||
{
|
||||
// 1. Create a new recipient list
|
||||
$list = $this->recipientListService->create(
|
||||
'VIP Customers',
|
||||
'High-value customers for special offers'
|
||||
);
|
||||
|
||||
// 2. Create multiple recipients
|
||||
$customers = [
|
||||
['email' => 'vip1@example.com', 'firstname' => 'Alice', 'company' => 'BigCorp'],
|
||||
['email' => 'vip2@example.com', 'firstname' => 'Bob', 'company' => 'MegaInc'],
|
||||
];
|
||||
|
||||
$createdRecipients = [];
|
||||
foreach ($customers as $customerData) {
|
||||
$command = RecipientCommandFactory::createFromArray($customerData, $list->id);
|
||||
$recipient = $this->recipientService->createWithCommand($command);
|
||||
$createdRecipients[] = $recipient;
|
||||
}
|
||||
|
||||
// 3. Update all recipients to VIP status
|
||||
foreach ($createdRecipients as $recipient) {
|
||||
$updateCommand = RecipientCommandFactory::updateFromRecipientWithChanges(
|
||||
$recipient,
|
||||
['status' => 'active', 'customFields' => ['vip_level' => 'gold']]
|
||||
);
|
||||
$this->recipientService->updateWithCommand($updateCommand);
|
||||
}
|
||||
|
||||
echo "Created VIP list with " . count($createdRecipients) . " customers\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 10: Migration from old to new API
|
||||
*/
|
||||
public function migrationExample(): void
|
||||
{
|
||||
// Old way (still works but deprecated)
|
||||
$oldRecipients = $this->recipientService->searchLegacy(['status' => 'active']);
|
||||
|
||||
// New way with proper Value Objects
|
||||
$newRecipients = $this->recipientService->search(['status' => 'active']);
|
||||
|
||||
echo "Old API returned array with meta data\n";
|
||||
echo "New API returns direct array of Recipient ReadModels\n";
|
||||
echo "New recipients have type-safe IDs and helpful methods\n";
|
||||
|
||||
foreach ($newRecipients as $recipient) {
|
||||
echo "- {$recipient->getFullName()}: " .
|
||||
($recipient->isActive() ? 'Active' : 'Inactive') . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick start guide for new users
|
||||
*/
|
||||
class QuickStartGuide
|
||||
{
|
||||
/**
|
||||
* The minimal example to get started
|
||||
*/
|
||||
public static function quickStart(
|
||||
RecipientService $recipientService,
|
||||
RecipientListService $recipientListService
|
||||
): void {
|
||||
// 1. Get or create a recipient list
|
||||
$lists = $recipientListService->getAll();
|
||||
$list = $lists[0] ?? $recipientListService->create('My List', 'Default list');
|
||||
|
||||
// 2. Create a recipient
|
||||
$command = new CreateRecipientCommand(
|
||||
email: 'user@example.com',
|
||||
firstname: 'John',
|
||||
lastname: 'Doe',
|
||||
recipientListId: $list->id
|
||||
);
|
||||
|
||||
$recipient = $recipientService->createWithCommand($command);
|
||||
|
||||
echo "✅ Created recipient: {$recipient->getFullName()}\n";
|
||||
echo " ID: {$recipient->id}\n";
|
||||
echo " List: {$list->name}\n";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail\Factories;
|
||||
|
||||
use App\Infrastructure\Api\RapidMail\Commands\CreateRecipientCommand;
|
||||
use App\Infrastructure\Api\RapidMail\Commands\UpdateRecipientCommand;
|
||||
use App\Infrastructure\Api\RapidMail\ReadModels\Recipient;
|
||||
use App\Infrastructure\Api\RapidMail\RecipientListId;
|
||||
|
||||
final class RecipientCommandFactory
|
||||
{
|
||||
public static function createFromArray(array $data, ?RecipientListId $recipientListId = null): CreateRecipientCommand
|
||||
{
|
||||
return new CreateRecipientCommand(
|
||||
email: $data['email'],
|
||||
firstname: $data['firstname'] ?? null,
|
||||
lastname: $data['lastname'] ?? null,
|
||||
title: $data['title'] ?? null,
|
||||
company: $data['company'] ?? null,
|
||||
zip: $data['zip'] ?? null,
|
||||
city: $data['city'] ?? null,
|
||||
street: $data['street'] ?? null,
|
||||
country: $data['country'] ?? null,
|
||||
phone: $data['phone'] ?? null,
|
||||
birthdate: $data['birthdate'] ?? null,
|
||||
gender: $data['gender'] ?? null,
|
||||
status: $data['status'] ?? null,
|
||||
recipientListId: $recipientListId,
|
||||
customFields: array_diff_key($data, array_flip([
|
||||
'email', 'firstname', 'lastname', 'title', 'company',
|
||||
'zip', 'city', 'street', 'country', 'phone', 'birthdate',
|
||||
'gender', 'status',
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
||||
public static function updateFromRecipient(Recipient $recipient): UpdateRecipientCommand
|
||||
{
|
||||
return new UpdateRecipientCommand(
|
||||
id: $recipient->id,
|
||||
email: $recipient->email,
|
||||
firstname: $recipient->firstname,
|
||||
lastname: $recipient->lastname,
|
||||
title: $recipient->title,
|
||||
company: $recipient->company,
|
||||
zip: $recipient->zip,
|
||||
city: $recipient->city,
|
||||
street: $recipient->street,
|
||||
country: $recipient->country,
|
||||
phone: $recipient->phone,
|
||||
birthdate: $recipient->birthdate,
|
||||
gender: $recipient->gender,
|
||||
status: $recipient->status,
|
||||
recipientListId: $recipient->recipientListId,
|
||||
customFields: $recipient->customFields
|
||||
);
|
||||
}
|
||||
|
||||
public static function updateFromRecipientWithChanges(Recipient $recipient, array $changes): UpdateRecipientCommand
|
||||
{
|
||||
$merged = array_merge([
|
||||
'email' => $recipient->email,
|
||||
'firstname' => $recipient->firstname,
|
||||
'lastname' => $recipient->lastname,
|
||||
'title' => $recipient->title,
|
||||
'company' => $recipient->company,
|
||||
'zip' => $recipient->zip,
|
||||
'city' => $recipient->city,
|
||||
'street' => $recipient->street,
|
||||
'country' => $recipient->country,
|
||||
'phone' => $recipient->phone,
|
||||
'birthdate' => $recipient->birthdate,
|
||||
'gender' => $recipient->gender,
|
||||
'status' => $recipient->status,
|
||||
], $changes);
|
||||
|
||||
return new UpdateRecipientCommand(
|
||||
id: $recipient->id,
|
||||
email: $merged['email'],
|
||||
firstname: $merged['firstname'],
|
||||
lastname: $merged['lastname'],
|
||||
title: $merged['title'],
|
||||
company: $merged['company'],
|
||||
zip: $merged['zip'],
|
||||
city: $merged['city'],
|
||||
street: $merged['street'],
|
||||
country: $merged['country'],
|
||||
phone: $merged['phone'],
|
||||
birthdate: $merged['birthdate'],
|
||||
gender: $merged['gender'],
|
||||
status: $merged['status'],
|
||||
recipientListId: $changes['recipientListId'] ?? $recipient->recipientListId,
|
||||
customFields: array_merge($recipient->customFields, $changes['customFields'] ?? [])
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail;
|
||||
|
||||
final readonly class Mailing
|
||||
@@ -15,7 +17,8 @@ final readonly class Mailing
|
||||
public ?string $updatedAt = null,
|
||||
public ?string $sentAt = null,
|
||||
public ?array $links = null
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
|
||||
31
src/Infrastructure/Api/RapidMail/MailingId.php
Normal file
31
src/Infrastructure/Api/RapidMail/MailingId.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail;
|
||||
|
||||
final readonly class MailingId
|
||||
{
|
||||
public function __construct(
|
||||
public int $value
|
||||
) {
|
||||
if ($value <= 0) {
|
||||
throw new \InvalidArgumentException('MailingId must be positive');
|
||||
}
|
||||
}
|
||||
|
||||
public static function fromInt(int $id): MailingId
|
||||
{
|
||||
return new MailingId($id);
|
||||
}
|
||||
|
||||
public function equals(MailingId $other): bool
|
||||
{
|
||||
return $this->value === $other->value;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return (string) $this->value;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail;
|
||||
|
||||
use App\Framework\Http\Method;
|
||||
@@ -8,7 +10,8 @@ final readonly class MailingService
|
||||
{
|
||||
public function __construct(
|
||||
private RapidMailApiClient $apiClient
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt alle Mailings
|
||||
@@ -17,10 +20,10 @@ final readonly class MailingService
|
||||
{
|
||||
$queryParams = [
|
||||
'page' => $page,
|
||||
'per_page' => $perPage
|
||||
'per_page' => $perPage,
|
||||
];
|
||||
|
||||
if (!empty($filter)) {
|
||||
if (! empty($filter)) {
|
||||
$queryParams['filter'] = $filter;
|
||||
}
|
||||
|
||||
@@ -29,8 +32,8 @@ final readonly class MailingService
|
||||
$mailings = $data['_embedded']['mailings'] ?? [];
|
||||
|
||||
return array_map(
|
||||
fn(array $item) => Mailing::fromArray($item),
|
||||
$mailings
|
||||
fn (array $item) => Mailing::fromArray($item),
|
||||
$mailings
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,10 +41,10 @@ final readonly class MailingService
|
||||
{
|
||||
$queryParams = [
|
||||
'page' => $page,
|
||||
'per_page' => $perPage
|
||||
'per_page' => $perPage,
|
||||
];
|
||||
|
||||
if (!empty($filter)) {
|
||||
if (! empty($filter)) {
|
||||
$queryParams = array_merge($queryParams, $filter);
|
||||
}
|
||||
|
||||
@@ -51,12 +54,12 @@ final readonly class MailingService
|
||||
|
||||
return [
|
||||
'mailings' => array_map(
|
||||
fn(array $item) => Mailing::fromArray($item),
|
||||
fn (array $item) => Mailing::fromArray($item),
|
||||
$mailings
|
||||
),
|
||||
'page' => $apiResponse['page'] ?? $page,
|
||||
'page_count' => $apiResponse['page_count'] ?? 1,
|
||||
'total_count' => $apiResponse['total_count'] ?? count($mailings)
|
||||
'total_count' => $apiResponse['total_count'] ?? count($mailings),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -66,6 +69,7 @@ final readonly class MailingService
|
||||
public function get(int $mailingId): Mailing
|
||||
{
|
||||
$data = $this->apiClient->request(Method::GET, "mailings/{$mailingId}");
|
||||
|
||||
return Mailing::fromArray($data);
|
||||
}
|
||||
|
||||
|
||||
252
src/Infrastructure/Api/RapidMail/README.md
Normal file
252
src/Infrastructure/Api/RapidMail/README.md
Normal file
@@ -0,0 +1,252 @@
|
||||
# RapidMail API Client - Refactored with Value Objects
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese refactorierte Version des RapidMail API Clients folgt Domain-Driven Design Prinzipien und trennt klar zwischen **Commands** (für Schreiboperationen) und **Read Models** (für Leseoperationen).
|
||||
|
||||
## 🆕 Neue Architektur
|
||||
|
||||
### Value Objects für IDs
|
||||
```php
|
||||
$recipientId = new RecipientId(123);
|
||||
$recipientListId = new RecipientListId(776);
|
||||
$mailingId = new MailingId(456);
|
||||
```
|
||||
|
||||
### Commands für Create/Update
|
||||
```php
|
||||
// Neuen Recipient erstellen
|
||||
$command = new CreateRecipientCommand(
|
||||
email: 'user@example.com',
|
||||
firstname: 'John',
|
||||
lastname: 'Doe',
|
||||
recipientListId: $recipientListId
|
||||
);
|
||||
|
||||
// Bestehenden Recipient aktualisieren
|
||||
$updateCommand = new UpdateRecipientCommand(
|
||||
id: $recipientId,
|
||||
email: 'updated@example.com',
|
||||
firstname: 'Jane'
|
||||
);
|
||||
```
|
||||
|
||||
### Read Models für Abfragen
|
||||
```php
|
||||
// Read Models haben IMMER eine ID
|
||||
$recipient = $recipientService->get($recipientId);
|
||||
echo $recipient->id->value; // Garantiert vorhanden
|
||||
echo $recipient->getFullName(); // Helper methods
|
||||
echo $recipient->isActive(); // Business logic
|
||||
```
|
||||
|
||||
## 🔧 Quick Start
|
||||
|
||||
```php
|
||||
// 1. Services injizieren lassen (über DI Container)
|
||||
$recipientService = $container->get(RecipientService::class);
|
||||
$recipientListService = $container->get(RecipientListService::class);
|
||||
|
||||
// 2. Recipient List erstellen oder holen
|
||||
$list = $recipientListService->create('Newsletter', 'Main subscriber list');
|
||||
|
||||
// 3. Recipient erstellen
|
||||
$command = new CreateRecipientCommand(
|
||||
email: 'test@example.com',
|
||||
firstname: 'Test',
|
||||
lastname: 'User',
|
||||
recipientListId: $list->id
|
||||
);
|
||||
|
||||
$recipient = $recipientService->createWithCommand($command);
|
||||
echo "Created: {$recipient->getFullName()}";
|
||||
```
|
||||
|
||||
## 📚 API Reference
|
||||
|
||||
### RecipientService
|
||||
|
||||
#### Neue Methoden (empfohlen)
|
||||
```php
|
||||
// Erstellen
|
||||
$recipient = $recipientService->createWithCommand($createCommand);
|
||||
|
||||
// Lesen
|
||||
$recipient = $recipientService->get($recipientId);
|
||||
$recipients = $recipientService->search(['status' => 'active']);
|
||||
$recipient = $recipientService->findByEmail('test@example.com', $recipientListId);
|
||||
|
||||
// Aktualisieren
|
||||
$recipient = $recipientService->updateWithCommand($updateCommand);
|
||||
|
||||
// Löschen
|
||||
$recipientService->delete($recipientId);
|
||||
```
|
||||
|
||||
#### Legacy Methoden (deprecated, aber funktional)
|
||||
```php
|
||||
$recipient = $recipientService->getById(123); // ❌ Deprecated
|
||||
$recipient = $recipientService->get(new RecipientId(123)); // ✅ Neu
|
||||
```
|
||||
|
||||
### RecipientListService
|
||||
|
||||
```php
|
||||
// Erstellen
|
||||
$list = $recipientListService->create('Name', 'Description');
|
||||
|
||||
// Alle listen
|
||||
$lists = $recipientListService->getAll();
|
||||
|
||||
// Einzelne Liste
|
||||
$list = $recipientListService->get($recipientListId);
|
||||
|
||||
// Aktualisieren
|
||||
$list = $recipientListService->update($recipientListId, 'New Name');
|
||||
|
||||
// Löschen
|
||||
$recipientListService->delete($recipientListId);
|
||||
```
|
||||
|
||||
### Command Factory
|
||||
|
||||
```php
|
||||
// Aus Array erstellen
|
||||
$command = RecipientCommandFactory::createFromArray([
|
||||
'email' => 'test@example.com',
|
||||
'firstname' => 'John'
|
||||
], $recipientListId);
|
||||
|
||||
// Aus bestehendem Recipient
|
||||
$updateCommand = RecipientCommandFactory::updateFromRecipient($recipient);
|
||||
|
||||
// Mit Änderungen
|
||||
$updateCommand = RecipientCommandFactory::updateFromRecipientWithChanges(
|
||||
$recipient,
|
||||
['company' => 'New Company']
|
||||
);
|
||||
```
|
||||
|
||||
## 🎯 Vorteile der neuen Architektur
|
||||
|
||||
### 1. Type Safety
|
||||
```php
|
||||
// ❌ Alte API: Fehleranfällig
|
||||
function processRecipient(int $id) {
|
||||
// Ist das eine RecipientId oder RecipientListId?
|
||||
}
|
||||
|
||||
// ✅ Neue API: Typsicher
|
||||
function processRecipient(RecipientId $id) {
|
||||
// Klar definiert, was erwartet wird
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Klare Trennung
|
||||
```php
|
||||
// ❌ Alte API: Verwirrung
|
||||
$recipient = new Recipient(); // Hat das eine ID oder nicht?
|
||||
|
||||
// ✅ Neue API: Eindeutig
|
||||
$command = new CreateRecipientCommand(...); // Für CREATE
|
||||
$recipient = $recipientService->get($id); // Hat IMMER eine ID
|
||||
```
|
||||
|
||||
### 3. Business Logic
|
||||
```php
|
||||
$recipient = $recipientService->get($recipientId);
|
||||
|
||||
// Helper methods für bessere Lesbarkeit
|
||||
echo $recipient->getFullName();
|
||||
echo $recipient->isActive() ? 'Active' : 'Inactive';
|
||||
echo $list->isDefault() ? 'Default list' : 'Custom list';
|
||||
```
|
||||
|
||||
### 4. Validation
|
||||
```php
|
||||
// Value Objects validieren automatisch
|
||||
$id = new RecipientId(-1); // ❌ Exception: ID muss positiv sein
|
||||
$command = new CreateRecipientCommand('invalid-email'); // ❌ Exception: Ungültige E-Mail
|
||||
```
|
||||
|
||||
## 🔄 Migration Guide
|
||||
|
||||
### Von alter API zu neuer API
|
||||
|
||||
**Alt:**
|
||||
```php
|
||||
$recipient = new Recipient(null, 'test@example.com', 'John');
|
||||
$saved = $recipientService->create($recipient);
|
||||
```
|
||||
|
||||
**Neu:**
|
||||
```php
|
||||
$command = new CreateRecipientCommand('test@example.com', 'John');
|
||||
$saved = $recipientService->createWithCommand($command);
|
||||
```
|
||||
|
||||
**Alt:**
|
||||
```php
|
||||
$recipient = $recipientService->get(123);
|
||||
```
|
||||
|
||||
**Neu:**
|
||||
```php
|
||||
$recipient = $recipientService->get(new RecipientId(123));
|
||||
```
|
||||
|
||||
## 🐛 Debugging & Troubleshooting
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
1. **"Recipient data must contain ID"**
|
||||
- Lösung: Verwende Commands für CREATE, Read Models nur für Responses
|
||||
|
||||
2. **"RecipientId must be positive"**
|
||||
- Lösung: Überprüfe, dass IDs gültig sind
|
||||
|
||||
3. **API-Struktur Probleme**
|
||||
- Alte Services verwenden noch `$data['data']` statt `$data['_embedded']`
|
||||
- Neue Services sind bereits korrigiert
|
||||
|
||||
### Debug Helpers
|
||||
```php
|
||||
// Value Object Informationen
|
||||
echo "Recipient ID: {$recipientId}";
|
||||
echo "Equal? " . $recipientId->equals($otherRecipientId);
|
||||
|
||||
// Read Model Informationen
|
||||
echo "Full name: {$recipient->getFullName()}";
|
||||
echo "Has list: " . $recipient->hasRecipientList();
|
||||
```
|
||||
|
||||
## 📋 API Corrections Applied
|
||||
|
||||
### Fixed Issues:
|
||||
1. ✅ **Data Structure**: `$data['_embedded']['recipientlists']` statt `$data['data']`
|
||||
2. ✅ **Field Names**: `created`/`updated` statt `created_at`/`updated_at`
|
||||
3. ✅ **Return Types**: Direkte Arrays von Objekten statt verschachtelte Strukturen
|
||||
4. ✅ **Pagination**: Korrekte API-Struktur implementiert
|
||||
5. ✅ **Value Objects**: Typisierte IDs für bessere Type Safety
|
||||
6. ✅ **Command/Query**: Saubere Trennung zwischen Commands und Read Models
|
||||
|
||||
## 🔮 Roadmap
|
||||
|
||||
- [ ] MailingService refactoring
|
||||
- [ ] StatisticsService improvement
|
||||
- [ ] Event-driven architecture integration
|
||||
- [ ] Caching layer
|
||||
- [ ] Rate limiting
|
||||
- [ ] Bulk operations
|
||||
|
||||
## 💡 Best Practices
|
||||
|
||||
1. **Verwende immer Value Objects für IDs**
|
||||
2. **Commands für Schreiboperationen, Read Models für Leseoperationen**
|
||||
3. **Factory Pattern für komplexe Command-Erstellung**
|
||||
4. **Helper Methods in Read Models für Business Logic**
|
||||
5. **Backward Compatibility während der Migration**
|
||||
|
||||
## 📖 Weitere Beispiele
|
||||
|
||||
Siehe `Examples/UsageExamples.php` für detaillierte Anwendungsbeispiele und `Examples/QuickStartGuide.php` für einen schnellen Einstieg.
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail;
|
||||
|
||||
use App\Framework\Api\ApiException;
|
||||
@@ -34,6 +36,7 @@ final readonly class RapidMailApiClient
|
||||
array $queryParams = []
|
||||
): array {
|
||||
$response = $this->sendRawRequest($method, $endpoint, $data, $queryParams);
|
||||
|
||||
return $this->handleResponse($response);
|
||||
}
|
||||
|
||||
@@ -49,11 +52,11 @@ final readonly class RapidMailApiClient
|
||||
$url = $this->config->baseUrl . '/' . ltrim($endpoint, '/');
|
||||
|
||||
$options = $this->defaultOptions;
|
||||
if (!empty($queryParams)) {
|
||||
if (! empty($queryParams)) {
|
||||
$options = $options->with(['query' => $queryParams]);
|
||||
}
|
||||
|
||||
if (in_array($method, [Method::GET, Method::DELETE]) && !empty($data)) {
|
||||
if (in_array($method, [Method::GET, Method::DELETE]) && ! empty($data)) {
|
||||
$existingQuery = $options->query;
|
||||
$options = $options->with(['query' => array_merge($existingQuery, $data)]);
|
||||
$data = [];
|
||||
@@ -65,7 +68,7 @@ final readonly class RapidMailApiClient
|
||||
|
||||
$response = $this->httpClient->send($request);
|
||||
|
||||
if (!$response->isSuccessful()) {
|
||||
if (! $response->isSuccessful()) {
|
||||
$this->throwApiException($response);
|
||||
}
|
||||
|
||||
@@ -79,7 +82,7 @@ final readonly class RapidMailApiClient
|
||||
{
|
||||
return [
|
||||
'send_activationmail' => $this->config->sendActivationMail ? 'yes' : 'no',
|
||||
'test_mode' => $this->config->testMode ? 'yes' : 'no'
|
||||
'test_mode' => $this->config->testMode ? 'yes' : 'no',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -88,8 +91,9 @@ final readonly class RapidMailApiClient
|
||||
*/
|
||||
private function handleResponse(ClientResponse $response): array
|
||||
{
|
||||
if (!$response->isJson()) {
|
||||
throw new ApiException('Expected JSON response, got: ' . $response->getContentType(), 0, $response);;
|
||||
if (! $response->isJson()) {
|
||||
throw new ApiException('Expected JSON response, got: ' . $response->getContentType(), 0, $response);
|
||||
;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -115,6 +119,7 @@ final readonly class RapidMailApiClient
|
||||
}
|
||||
|
||||
$message = $this->formatErrorMessage($data, $response);
|
||||
|
||||
throw new ApiException($message, $response->status->value, $response);
|
||||
}
|
||||
|
||||
@@ -128,9 +133,9 @@ final readonly class RapidMailApiClient
|
||||
|
||||
if (isset($responseData['validation_messages'])) {
|
||||
$message .= ' - Validation: ' . json_encode(
|
||||
$responseData['validation_messages'],
|
||||
JSON_UNESCAPED_UNICODE
|
||||
);
|
||||
$responseData['validation_messages'],
|
||||
JSON_UNESCAPED_UNICODE
|
||||
);
|
||||
}
|
||||
|
||||
return $message;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail;
|
||||
|
||||
use App\Framework\HttpClient\HttpClient;
|
||||
@@ -7,9 +9,13 @@ use App\Framework\HttpClient\HttpClient;
|
||||
final readonly class RapidMailClient
|
||||
{
|
||||
public RecipientService $recipients;
|
||||
|
||||
public RecipientListService $recipientLists;
|
||||
|
||||
public MailingService $mailings;
|
||||
|
||||
public StatisticsService $statistics;
|
||||
|
||||
public BlacklistService $blacklist;
|
||||
|
||||
public function __construct(
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail;
|
||||
|
||||
use App\Framework\DI\Container;
|
||||
use App\Framework\DI\Initializer;
|
||||
use App\Framework\HttpClient\CurlHttpClient;
|
||||
|
||||
final readonly class RapidMailClientInitializer
|
||||
{
|
||||
#[Initializer]
|
||||
public function __invoke(): RapidMailClient
|
||||
public function __invoke(Container $container): RapidMailClient
|
||||
{
|
||||
$config = $container->get(\App\Framework\Config\External\ExternalApiConfig::class);
|
||||
|
||||
return new RapidMailClient(
|
||||
new RapidMailConfig(
|
||||
username: '3f60a5c15c3d49c631d0e75b7c1090a3859423a7',
|
||||
password: '572d25dc36e620f14c89e9c75c02c1f3794ba3c0',
|
||||
|
||||
testMode: true,
|
||||
username: $config->rapidMail->username,
|
||||
password: $config->rapidMail->password,
|
||||
testMode: $config->rapidMail->testMode,
|
||||
),
|
||||
new CurlHttpClient()
|
||||
);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail;
|
||||
|
||||
final readonly class RapidMailConfig
|
||||
@@ -11,5 +13,6 @@ final readonly class RapidMailConfig
|
||||
public bool $testMode = false,
|
||||
public bool $sendActivationMail = true,
|
||||
public float $timeout = 30.0
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
100
src/Infrastructure/Api/RapidMail/ReadModels/Recipient.php
Normal file
100
src/Infrastructure/Api/RapidMail/ReadModels/Recipient.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail\ReadModels;
|
||||
|
||||
use App\Infrastructure\Api\RapidMail\RecipientId;
|
||||
use App\Infrastructure\Api\RapidMail\RecipientListId;
|
||||
use DateTimeImmutable;
|
||||
|
||||
/**
|
||||
* Read Model für Recipients - immer mit ID
|
||||
* Für Create/Update Operations verwende stattdessen Commands
|
||||
*/
|
||||
final readonly class Recipient
|
||||
{
|
||||
public function __construct(
|
||||
public RecipientId $id,
|
||||
public string $email,
|
||||
public ?string $firstname = null,
|
||||
public ?string $lastname = null,
|
||||
public ?string $title = null,
|
||||
public ?string $company = null,
|
||||
public ?string $zip = null,
|
||||
public ?string $city = null,
|
||||
public ?string $street = null,
|
||||
public ?string $country = null,
|
||||
public ?string $phone = null,
|
||||
public ?string $birthdate = null,
|
||||
public ?string $gender = null,
|
||||
public ?string $status = null,
|
||||
public ?RecipientListId $recipientListId = null,
|
||||
public ?DateTimeImmutable $createdAt = null,
|
||||
public ?DateTimeImmutable $updatedAt = null,
|
||||
public array $customFields = [],
|
||||
public ?array $links = null
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
if (! isset($data['id'])) {
|
||||
throw new \InvalidArgumentException('Recipient data must contain ID');
|
||||
}
|
||||
|
||||
$knownFields = [
|
||||
'id', 'email', 'firstname', 'lastname', 'title', 'company',
|
||||
'zip', 'city', 'street', 'country', 'phone', 'birthdate',
|
||||
'gender', 'status', 'recipientlist_id', 'created', 'updated', '_links',
|
||||
];
|
||||
|
||||
$customFields = array_diff_key($data, array_flip($knownFields));
|
||||
|
||||
return new self(
|
||||
id: new RecipientId($data['id']),
|
||||
email: $data['email'] ?? '',
|
||||
firstname: $data['firstname'] ?? null,
|
||||
lastname: $data['lastname'] ?? null,
|
||||
title: $data['title'] ?? null,
|
||||
company: $data['company'] ?? null,
|
||||
zip: $data['zip'] ?? null,
|
||||
city: $data['city'] ?? null,
|
||||
street: $data['street'] ?? null,
|
||||
country: $data['country'] ?? null,
|
||||
phone: $data['phone'] ?? null,
|
||||
birthdate: $data['birthdate'] ?? null,
|
||||
gender: $data['gender'] ?? null,
|
||||
status: $data['status'] ?? null,
|
||||
recipientListId: RecipientListId::tryFrom($data['recipientlist_id']),
|
||||
/*recipientListId: isset($data['recipientlist_id'])
|
||||
? new RecipientListId($data['recipientlist_id'])
|
||||
: null,*/
|
||||
createdAt: isset($data['created'])
|
||||
? new DateTimeImmutable($data['created'])
|
||||
: null,
|
||||
updatedAt: isset($data['updated'])
|
||||
? new DateTimeImmutable($data['updated'])
|
||||
: null,
|
||||
customFields: $customFields,
|
||||
links: $data['_links'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
public function getFullName(): string
|
||||
{
|
||||
$parts = array_filter([$this->firstname, $this->lastname]);
|
||||
|
||||
return implode(' ', $parts);
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->status === 'active';
|
||||
}
|
||||
|
||||
public function hasRecipientList(): bool
|
||||
{
|
||||
return $this->recipientListId !== null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail\ReadModels;
|
||||
|
||||
use App\Infrastructure\Api\RapidMail\RecipientListId;
|
||||
|
||||
/**
|
||||
* Read Model für RecipientLists - immer mit ID
|
||||
*/
|
||||
final readonly class RecipientList
|
||||
{
|
||||
public function __construct(
|
||||
public RecipientListId $id,
|
||||
public string $name,
|
||||
public ?string $description = null,
|
||||
public ?int $recipientCount = null,
|
||||
public ?\DateTimeImmutable $createdAt = null,
|
||||
public ?\DateTimeImmutable $updatedAt = null,
|
||||
public ?string $unsubscribeBlacklist = null,
|
||||
public ?string $default = null,
|
||||
public ?string $recipientSubscribeEmail = null,
|
||||
public ?string $subscribeFormUrl = null,
|
||||
public ?string $subscribeFormFieldKey = null,
|
||||
public ?array $links = null
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
if (! isset($data['id'])) {
|
||||
throw new \InvalidArgumentException('RecipientList data must contain ID');
|
||||
}
|
||||
|
||||
return new self(
|
||||
id: RecipientListId::fromInt($data['id']),
|
||||
name: $data['name'] ?? '',
|
||||
description: $data['description'] ?? null,
|
||||
recipientCount: $data['recipient_count'] ?? null,
|
||||
createdAt: isset($data['created'])
|
||||
? new \DateTimeImmutable($data['created'])
|
||||
: null,
|
||||
updatedAt: isset($data['updated'])
|
||||
? new \DateTimeImmutable($data['updated'])
|
||||
: null,
|
||||
unsubscribeBlacklist: $data['unsubscribe_blacklist'] ?? null,
|
||||
default: $data['default'] ?? null,
|
||||
recipientSubscribeEmail: $data['recipient_subscribe_email'] ?? null,
|
||||
subscribeFormUrl: $data['subscribe_form_url'] ?? null,
|
||||
subscribeFormFieldKey: $data['subscribe_form_field_key'] ?? null,
|
||||
links: $data['_links'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
public function isDefault(): bool
|
||||
{
|
||||
return $this->default === 'yes';
|
||||
}
|
||||
|
||||
public function hasUnsubscribeBlacklist(): bool
|
||||
{
|
||||
return $this->unsubscribeBlacklist === 'yes';
|
||||
}
|
||||
|
||||
public function hasRecipientSubscribeEmail(): bool
|
||||
{
|
||||
return $this->recipientSubscribeEmail === 'yes';
|
||||
}
|
||||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return $this->recipientCount === 0;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail;
|
||||
|
||||
final readonly class RecipientId
|
||||
{
|
||||
public function __construct(
|
||||
public int $value,
|
||||
)
|
||||
{
|
||||
) {
|
||||
if ($value <= 0) {
|
||||
throw new \InvalidArgumentException('RecipientId must be positive');
|
||||
}
|
||||
|
||||
40
src/Infrastructure/Api/RapidMail/RecipientListId.php
Normal file
40
src/Infrastructure/Api/RapidMail/RecipientListId.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail;
|
||||
|
||||
final readonly class RecipientListId
|
||||
{
|
||||
private function __construct(
|
||||
public int $value
|
||||
) {
|
||||
if ($value <= 0) {
|
||||
throw new \InvalidArgumentException('RecipientListId must be positive');
|
||||
}
|
||||
}
|
||||
|
||||
public static function fromInt(int $id): RecipientListId
|
||||
{
|
||||
return new RecipientListId($id);
|
||||
}
|
||||
|
||||
public static function tryFrom(mixed $id): ?RecipientListId
|
||||
{
|
||||
if (is_int($id)) {
|
||||
return new RecipientListId($id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function equals(RecipientListId $other): bool
|
||||
{
|
||||
return $this->value === $other->value;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return (string) $this->value;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail;
|
||||
|
||||
use App\Framework\Http\Method;
|
||||
@@ -9,7 +11,8 @@ final readonly class RecipientListService
|
||||
{
|
||||
public function __construct(
|
||||
private RapidMailApiClient $apiClient
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine neue Empfängerliste
|
||||
@@ -22,6 +25,7 @@ final readonly class RecipientListService
|
||||
}
|
||||
|
||||
$result = $this->apiClient->request(Method::POST, 'recipientlists', $data);
|
||||
|
||||
return RecipientList::fromArray($result);
|
||||
}
|
||||
|
||||
@@ -33,13 +37,13 @@ final readonly class RecipientListService
|
||||
{
|
||||
$apiResponse = $this->apiClient->request(Method::GET, 'recipientlists', [], [
|
||||
'page' => $page,
|
||||
'per_page' => $perPage
|
||||
'per_page' => $perPage,
|
||||
]);
|
||||
|
||||
$recipientlists = $apiResponse['_embedded']['recipientlists'] ?? [];
|
||||
|
||||
return array_map(
|
||||
fn(array $item) => RecipientList::fromArray($item),
|
||||
fn (array $item) => RecipientList::fromArray($item),
|
||||
$recipientlists
|
||||
);
|
||||
}
|
||||
@@ -50,6 +54,7 @@ final readonly class RecipientListService
|
||||
public function get(RecipientListId $recipientlistId): RecipientList
|
||||
{
|
||||
$data = $this->apiClient->request(Method::GET, "recipientlists/{$recipientlistId->value}");
|
||||
|
||||
return RecipientList::fromArray($data);
|
||||
}
|
||||
|
||||
@@ -64,6 +69,7 @@ final readonly class RecipientListService
|
||||
}
|
||||
|
||||
$result = $this->apiClient->request(Method::PUT, "recipientlists/{$recipientlistId->value}", $data);
|
||||
|
||||
return RecipientList::fromArray($result);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail;
|
||||
|
||||
use App\Framework\Http\Method;
|
||||
@@ -17,7 +19,8 @@ final readonly class RecipientService
|
||||
{
|
||||
public function __construct(
|
||||
private RapidMailApiClient $apiClient
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new recipient using Command pattern (RECOMMENDED)
|
||||
@@ -40,6 +43,7 @@ final readonly class RecipientService
|
||||
public function get(RecipientId $recipientId): Recipient
|
||||
{
|
||||
$data = $this->apiClient->request(Method::GET, "recipients/{$recipientId->value}");
|
||||
|
||||
return Recipient::fromArray($data);
|
||||
}
|
||||
|
||||
@@ -73,10 +77,10 @@ final readonly class RecipientService
|
||||
{
|
||||
$queryParams = [
|
||||
'page' => $page,
|
||||
'per_page' => $perPage
|
||||
'per_page' => $perPage,
|
||||
];
|
||||
|
||||
if (!empty($filter)) {
|
||||
if (! empty($filter)) {
|
||||
$queryParams = array_merge($queryParams, $filter);
|
||||
}
|
||||
|
||||
@@ -85,7 +89,7 @@ final readonly class RecipientService
|
||||
$recipients = $apiResponse['_embedded']['recipients'] ?? [];
|
||||
|
||||
return array_map(
|
||||
fn(array $item) => Recipient::fromArray($item),
|
||||
fn (array $item) => Recipient::fromArray($item),
|
||||
$recipients
|
||||
);
|
||||
}
|
||||
@@ -101,6 +105,7 @@ final readonly class RecipientService
|
||||
}
|
||||
|
||||
$recipients = $this->search($filter, 1, 1);
|
||||
|
||||
return empty($recipients) ? null : $recipients[0];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail;
|
||||
|
||||
use App\Framework\Http\Method;
|
||||
@@ -8,7 +10,8 @@ final readonly class StatisticsService
|
||||
{
|
||||
public function __construct(
|
||||
private RapidMailApiClient $apiClient
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt Statistiken für ein Mailing
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api;
|
||||
|
||||
use App\Framework\Api\ApiException;
|
||||
use App\Framework\Api\ApiRequestTrait;
|
||||
use App\Framework\Http\Method;
|
||||
use App\Framework\HttpClient\AuthConfig;
|
||||
@@ -43,8 +43,8 @@ final class RapidMailClient
|
||||
options: $this->defaultOptions->with([
|
||||
'query' => [
|
||||
'send_activationmail' => 'yes',
|
||||
'test_mode' => 'yes'
|
||||
]
|
||||
'test_mode' => 'yes',
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
@@ -86,8 +86,8 @@ final class RapidMailClient
|
||||
data: [
|
||||
'filter' => [
|
||||
'email' => $email,
|
||||
'recipientlist_id' => $recipientlistId
|
||||
]
|
||||
'recipientlist_id' => $recipientlistId,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api;
|
||||
|
||||
use App\Framework\Api\ApiException;
|
||||
use App\Framework\Api\ApiRequestTrait;
|
||||
use App\Framework\Http\Method;
|
||||
use App\Framework\HttpClient\AuthConfig;
|
||||
@@ -37,7 +37,7 @@ final class ShopifyClient
|
||||
|
||||
$this->defaultOptions = new ClientOptions(
|
||||
auth: AuthConfig::custom([
|
||||
'headers' => ['X-Shopify-Access-Token' => $accessToken]
|
||||
'headers' => ['X-Shopify-Access-Token' => $accessToken],
|
||||
])
|
||||
);
|
||||
|
||||
@@ -266,8 +266,8 @@ final class ShopifyClient
|
||||
'webhook' => [
|
||||
'topic' => $topic,
|
||||
'address' => $address,
|
||||
'format' => $format
|
||||
]
|
||||
'format' => $format,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
@@ -370,6 +370,7 @@ final class ShopifyClient
|
||||
if (preg_match('/^(\d+)\/(\d+)$/', $limitHeader, $matches)) {
|
||||
$current = (int)$matches[1];
|
||||
$limit = (int)$matches[2];
|
||||
|
||||
return $limit - $current;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\GeoIp;
|
||||
@@ -14,6 +15,9 @@ final readonly class Country
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
@@ -21,7 +25,7 @@ final readonly class Country
|
||||
'name_en' => $this->nameEn,
|
||||
'name_de' => $this->nameDe,
|
||||
'name_native' => $this->nameNative,
|
||||
'updated_at' => $this->updatedAt
|
||||
'updated_at' => $this->updatedAt,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\GeoIp;
|
||||
@@ -20,12 +21,12 @@ final class CountryDataService
|
||||
echo "Lade Länderdaten von RestCountries API...\n";
|
||||
|
||||
$jsonData = file_get_contents(self::RESTCOUNTRIES_API_URL);
|
||||
if (!$jsonData) {
|
||||
if (! $jsonData) {
|
||||
throw new RuntimeException('Konnte Länderdaten nicht herunterladen');
|
||||
}
|
||||
|
||||
$countries = json_decode($jsonData, true);
|
||||
if (!$countries) {
|
||||
if (! $countries) {
|
||||
throw new RuntimeException('Ungültige JSON-Daten erhalten');
|
||||
}
|
||||
|
||||
@@ -43,7 +44,7 @@ final class CountryDataService
|
||||
$country->nameEn,
|
||||
$country->nameDe,
|
||||
$country->nameNative,
|
||||
$country->updatedAt
|
||||
$country->updatedAt,
|
||||
]);
|
||||
$processed++;
|
||||
}
|
||||
@@ -57,11 +58,11 @@ final class CountryDataService
|
||||
|
||||
public function getCountryByCode(string $code): ?Country
|
||||
{
|
||||
$stmt = $this->database->prepare('SELECT * FROM countries WHERE code = ?');
|
||||
$stmt = $this->database->prepare('SELECT code, name_en, name_de, name_native, updated_at FROM countries WHERE code = ?');
|
||||
$stmt->execute([$code]);
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$result) {
|
||||
if (! $result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -79,13 +80,13 @@ final class CountryDataService
|
||||
$code = $countryData['cca2'] ?? null;
|
||||
$nameEn = $countryData['name']['common'] ?? null;
|
||||
|
||||
if (!$code || !$nameEn) {
|
||||
if (! $code || ! $nameEn) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$nameDe = $countryData['translations']['deu']['common'] ?? $nameEn;
|
||||
$nativeNames = $countryData['name']['nativeName'] ?? [];
|
||||
$nameNative = !empty($nativeNames)
|
||||
$nameNative = ! empty($nativeNames)
|
||||
? $nativeNames[array_key_first($nativeNames)]['common'] ?? $nameEn
|
||||
: $nameEn;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\GeoIp;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\GeoIp;
|
||||
@@ -50,7 +51,7 @@ final readonly class DatabaseSetup
|
||||
private function initializeDatabase(string $file): PDO
|
||||
{
|
||||
$directory = dirname($file);
|
||||
if (!is_dir($directory)) {
|
||||
if (! is_dir($directory)) {
|
||||
mkdir($directory, 0755, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\GeoIp;
|
||||
@@ -9,7 +10,9 @@ use PDO;
|
||||
final class GeoIp
|
||||
{
|
||||
private readonly PDO $database;
|
||||
|
||||
private readonly IpRangeService $ipRangeService;
|
||||
|
||||
private readonly CountryDataService $countryDataService;
|
||||
|
||||
public function __construct(?string $databasePath = null)
|
||||
@@ -47,7 +50,7 @@ final class GeoIp
|
||||
{
|
||||
$countryCode = $this->getCountryForString($ip);
|
||||
|
||||
if (!$countryCode) {
|
||||
if (! $countryCode) {
|
||||
return new CountryInfo($ip, null);
|
||||
}
|
||||
|
||||
@@ -83,7 +86,7 @@ final class GeoIp
|
||||
private function initializeDatabase(string $file): PDO
|
||||
{
|
||||
$directory = dirname($file);
|
||||
if (!is_dir($directory)) {
|
||||
if (! is_dir($directory)) {
|
||||
mkdir($directory, 0755, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\GeoIp;
|
||||
|
||||
use PDO;
|
||||
use Generator;
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
final class IpRangeService
|
||||
@@ -14,7 +15,7 @@ final class IpRangeService
|
||||
'ARIN' => 'https://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest', // Nordamerika
|
||||
'APNIC' => 'https://ftp.apnic.net/pub/stats/apnic/delegated-apnic-latest', // Asien-Pazifik
|
||||
'LACNIC' => 'https://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-latest', // Lateinamerika
|
||||
'AFRINIC' => 'https://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-latest' // Afrika
|
||||
'AFRINIC' => 'https://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-latest', // Afrika
|
||||
];
|
||||
|
||||
private const int BATCH_SIZE = 1000;
|
||||
@@ -84,12 +85,12 @@ final class IpRangeService
|
||||
{
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'timeout' => 300 // 5 Minuten Timeout
|
||||
]
|
||||
'timeout' => 300, // 5 Minuten Timeout
|
||||
],
|
||||
]);
|
||||
|
||||
$handle = fopen($url, 'r', false, $context);
|
||||
if (!$handle) {
|
||||
if (! $handle) {
|
||||
throw new RuntimeException("Konnte URL nicht öffnen: {$url}");
|
||||
}
|
||||
|
||||
@@ -118,7 +119,7 @@ final class IpRangeService
|
||||
yield [
|
||||
'ip_start' => $ipStart,
|
||||
'ip_end' => $ipEnd,
|
||||
'country' => $country
|
||||
'country' => $country,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
BIN
src/Infrastructure/GeoIp/data/ip_country.sqlite
Normal file
BIN
src/Infrastructure/GeoIp/data/ip_country.sqlite
Normal file
Binary file not shown.
Reference in New Issue
Block a user