Le domaine Scraping ne génère plus d'archives CBZ ni ne modifie les
entités du domaine Manga directement. Il scrape, stocke les images
individuellement, et émet un événement partagé.
## Changements principaux
### Domaine Scraping
- Suppression : CbzGeneratorInterface, CbzGenerator, CbzGenerationRequest,
CbzPath, CbzGenerationException
- Suppression : save() de ChapterRepositoryInterface (Scraping)
- Suppression : cbzPath du modèle Chapter (Scraping)
- Ajout : ImageStorageInterface + LocalImageStorage
(stockage dans {MANGA_DATA_PATH}/pages/{chapterId}/)
- ScrapeChapterHandler utilise ImageStorage au lieu du générateur CBZ
### Événement partagé
- ChapterScraped déplacé dans Domain/Shared/Domain/Event/
avec jobId, chapterId, pagesDirectory, pageCount
- Routing Messenger ajouté
### Domaine Manga
- Ajout : ChapterScrapedEventListener + ChapterScrapedMessageHandler
pour mettre à jour Chapter.pagesDirectory via le Repository Manga
### Domaine Reader
- LegacyChapterRepository en dual-mode :
pagesDirectory en priorité, fallback cbzPath (backward compat)
- Requêtes prev/next : filtrent pagesDirectory IS NOT NULL OR cbzPath IS NOT NULL
- ChapterContext expose pagesDirectory
### Architecture
- phparkitect.php : App\Domain\Shared\Domain\Event autorisé dans
les couches Application (correction violations pré-existantes
ChapterImported/VolumeImported + nouvelle ChapterScraped)
## Tests
- 218/218 tests passent (+3 nouveaux)
- InMemoryImageStorage créé pour les tests unitaires
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
81 lines
3.3 KiB
PHP
81 lines
3.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use Arkitect\ClassSet;
|
|
use Arkitect\CLI\Config;
|
|
use Arkitect\Expression\ForClasses\HaveNameMatching;
|
|
use Arkitect\Expression\ForClasses\NotDependsOnTheseNamespaces;
|
|
use Arkitect\Expression\ForClasses\NotHaveDependencyOutsideNamespace;
|
|
use Arkitect\Expression\ForClasses\ResideInOneOfTheseNamespaces;
|
|
use Arkitect\Rules\Rule;
|
|
|
|
return static function (Config $config): void {
|
|
$domainClassSet = ClassSet::fromDir(__DIR__ . '/src/Domain');
|
|
$businessDomains = ['Manga', 'Reader', 'Scraping', 'Conversion'];
|
|
|
|
// Classes PHP standards et utilitaires
|
|
$standardExceptions = [
|
|
'DateTimeImmutable',
|
|
'RuntimeException',
|
|
'Exception',
|
|
'DomainException',
|
|
'Symfony\Component\HttpKernel\Exception',
|
|
'Throwable',
|
|
'InvalidArgumentException',
|
|
'App\Domain\Shared\Domain\Model\AggregateRoot',
|
|
];
|
|
|
|
// Dépendances externes autorisées
|
|
$externalDependencies = [
|
|
'Symfony\Component\Messenger',
|
|
'Ramsey\Uuid'
|
|
];
|
|
|
|
// Règle pour le namespace cohérent
|
|
$namespaceRule = Rule::allClasses()
|
|
->that(new ResideInOneOfTheseNamespaces('App\Domain'))
|
|
->should(new HaveNameMatching('*'))
|
|
->because('nous voulons un namespace cohérent pour notre domaine');
|
|
|
|
$rules = [$namespaceRule];
|
|
|
|
// Règles spécifiques pour le domaine Shared
|
|
$rules[] = Rule::allClasses()
|
|
->that(new ResideInOneOfTheseNamespaces('App\Domain\Shared'))
|
|
->should(new NotHaveDependencyOutsideNamespace(
|
|
'App\Domain\Shared',
|
|
$standardExceptions
|
|
))
|
|
->because('le domaine Shared ne doit dépendre de personne');
|
|
|
|
// Génération des règles pour chaque domaine métier
|
|
foreach ($businessDomains as $domain) {
|
|
// Règle pour la couche Domain
|
|
$rules[] = Rule::allClasses()
|
|
->that(new ResideInOneOfTheseNamespaces("App\Domain\\$domain\Domain"))
|
|
->should(new NotHaveDependencyOutsideNamespace(
|
|
"App\Domain\\$domain\Domain",
|
|
$standardExceptions
|
|
))
|
|
->because("la couche Domain de $domain ne doit dépendre que de ses propres classes, des contrats partagés et des exceptions autorisées");
|
|
|
|
// Règle pour la couche Application
|
|
$rules[] = Rule::allClasses()
|
|
->that(new ResideInOneOfTheseNamespaces("App\Domain\\$domain\Application"))
|
|
->should(new NotHaveDependencyOutsideNamespace(
|
|
"App\Domain\\$domain",
|
|
array_merge($standardExceptions, $externalDependencies, ['App\Domain\Shared\Domain\Contract', 'App\Domain\Shared\Domain\Event'])
|
|
))
|
|
->because("la couche Application de $domain ne peut dépendre que de son propre domaine, des contrats partagés et des dépendances autorisées");
|
|
|
|
// Interdiction explicite pour l'Application d'accéder à l'Infrastructure
|
|
$rules[] = Rule::allClasses()
|
|
->that(new ResideInOneOfTheseNamespaces("App\Domain\\$domain\Application"))
|
|
->should(new NotDependsOnTheseNamespaces("App\Domain\\$domain\Infrastructure"))
|
|
->because("la couche Application de $domain ne doit jamais dépendre de l'Infrastructure, même au sein de son propre domaine");
|
|
}
|
|
|
|
$config->add($domainClassSet, ...$rules);
|
|
};
|