Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
93.68% |
89 / 95 |
|
82.50% |
33 / 40 |
|
40.62% |
13 / 32 |
|
66.67% |
4 / 6 |
CRAP | |
0.00% |
0 / 1 |
| NickGeneratorService | |
93.68% |
89 / 95 |
|
82.50% |
33 / 40 |
|
40.62% |
13 / 32 |
|
66.67% |
4 / 6 |
113.31 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| computeTargetGender | |
100.00% |
6 / 6 |
|
100.00% |
13 / 13 |
|
41.67% |
5 / 12 |
|
100.00% |
1 / 1 |
13.15 | |||
| buildGeneratedNick | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| updateNick | |
91.43% |
32 / 35 |
|
66.67% |
8 / 12 |
|
33.33% |
2 / 6 |
|
0.00% |
0 / 1 |
16.67 | |||
| createNick | |
91.67% |
33 / 36 |
|
70.00% |
7 / 10 |
|
20.00% |
2 / 10 |
|
0.00% |
0 / 1 |
17.80 | |||
| generateNick | |
100.00% |
3 / 3 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace App\Service\Generator; |
| 4 | |
| 5 | use App\Dto\Command\GenerateNickCommand; |
| 6 | use App\Dto\Command\GetWordCommand; |
| 7 | use App\Dto\Result\GeneratedNickData; |
| 8 | use App\Entity\Qualifier; |
| 9 | use App\Entity\Subject; |
| 10 | use App\Entity\Word; |
| 11 | use App\Enum\GrammaticalRoleType; |
| 12 | use App\Enum\OffenseLevel; |
| 13 | use App\Enum\WordGender; |
| 14 | use App\Exception\NickNotFoundException; |
| 15 | use App\Exception\NoQualifierFoundException; |
| 16 | use App\Exception\NoSubjectFoundException; |
| 17 | use App\Service\Data\NickServiceInterface; |
| 18 | use App\Service\Data\QualifierServiceInterface; |
| 19 | use App\Service\Data\SubjectServiceInterface; |
| 20 | use App\Service\Nick\NickComposerInterface; |
| 21 | use App\Specification\Criteria; |
| 22 | use App\Specification\Criterion\GenderConstraintType; |
| 23 | use App\Specification\Criterion\GenderCriterion; |
| 24 | use App\Specification\Criterion\LangCriterion; |
| 25 | use App\Specification\Criterion\OffenseConstraintType; |
| 26 | use App\Specification\Criterion\OffenseLevelCriterion; |
| 27 | use App\Specification\Criterion\ValuesCriterion; |
| 28 | use App\Specification\Criterion\ValuesCriterionCheck; |
| 29 | use Random\RandomException; |
| 30 | |
| 31 | /** |
| 32 | * @author Wilhelm Zwertvaegher |
| 33 | */ |
| 34 | class NickGeneratorService implements NickGeneratorServiceInterface |
| 35 | { |
| 36 | public function __construct( |
| 37 | private readonly SubjectServiceInterface $subjectService, |
| 38 | private readonly QualifierServiceInterface $qualifierService, |
| 39 | private readonly NickComposerInterface $nickComposer, |
| 40 | private readonly NickServiceInterface $nickService, |
| 41 | private readonly WordFinderInterface $wordFinder, |
| 42 | ) { |
| 43 | } |
| 44 | |
| 45 | /** |
| 46 | * After a Subject has randomly been found, we have to set a target Gender for our Nick |
| 47 | * This is because the /api/word endpoint allows to replace the Subject or the Qualifier of a Nick and we have to produce |
| 48 | * consistent and compatible words |
| 49 | * For example : |
| 50 | * If the user wanted a AUTO nick, got a NEUTRAL subject, |
| 51 | * and we naively set the target gender as NEUTRAL, it may drastically reduce possibilities. This is ok for a NEUTRAL explicit request, |
| 52 | * but not an AUTO one. |
| 53 | * On the other hand we cannot simply set the target as AUTO because in that case for gender-specific words variation we |
| 54 | * would have to choose a default gender, because reloading the subject or qualifier or a nick would produce gender compatible words |
| 55 | * And we do not want to choose a gender as default, so by default we will randomly choose between M and F |
| 56 | * TL;DR ; a Nick's target Gender cannot be AUTO, it MUST be a defined GENDER. |
| 57 | * |
| 58 | * @throws RandomException |
| 59 | */ |
| 60 | private function computeTargetGender(GenerateNickCommand $command, Subject $subject): WordGender |
| 61 | { |
| 62 | // in case a non-auto gender has been explicitly asked, we have to respect it |
| 63 | if (null !== $command->getGender() && WordGender::AUTO !== $command->getGender()) { |
| 64 | return $command->getGender(); |
| 65 | } |
| 66 | |
| 67 | // in other cases, Gender depends on the found Subject gender |
| 68 | return match ($subject->getWord()->getGender()) { |
| 69 | // neutral is randomly forced to M or F to increase possibilities, otherwise it would be very limited |
| 70 | // because in some languages neutral words are rare |
| 71 | // in any case, having a random M or F target gender will still allow NEUTRAL qualifiers |
| 72 | WordGender::AUTO, WordGender::NEUTRAL => 1 === random_int(0, 1) ? WordGender::M : WordGender::F, |
| 73 | default => $subject->getWord()->getGender(), |
| 74 | }; |
| 75 | } |
| 76 | |
| 77 | /** |
| 78 | * Delegate the final Nick composition, and retrieve it from the system (i.e. get or create). |
| 79 | */ |
| 80 | private function buildGeneratedNick(Subject $subject, Qualifier $qualifier, WordGender $targetGender): GeneratedNickData |
| 81 | { |
| 82 | $composedNick = $this->nickComposer->compose($subject, $qualifier, $subject->getWord()->getLang(), $targetGender); |
| 83 | |
| 84 | $nick = $this->nickService->getOrCreate( |
| 85 | $subject, |
| 86 | $qualifier, |
| 87 | $targetGender, |
| 88 | $subject->getWord()->getOffenseLevel(), |
| 89 | $composedNick->getFinalLabel() |
| 90 | ); |
| 91 | |
| 92 | return new GeneratedNickData( |
| 93 | $nick->getTargetGender(), |
| 94 | $nick->getOffenseLevel(), |
| 95 | $nick, |
| 96 | $composedNick->getWords() |
| 97 | ); |
| 98 | } |
| 99 | |
| 100 | /** |
| 101 | * @throws NickNotFoundException |
| 102 | * @throws NoSubjectFoundException |
| 103 | * @throws NoQualifierFoundException |
| 104 | */ |
| 105 | private function updateNick(GenerateNickCommand $command): GeneratedNickData |
| 106 | { |
| 107 | $previousNick = $this->nickService->getNick($command->getPreviousNickId()); |
| 108 | if (null === $previousNick) { |
| 109 | throw new NickNotFoundException(); |
| 110 | } |
| 111 | |
| 112 | // create a new nick with a replaced word |
| 113 | $subject = $previousNick->getSubject(); |
| 114 | $qualifier = $previousNick->getQualifier(); |
| 115 | |
| 116 | switch ($command->getReplaceRoleType()) { |
| 117 | case GrammaticalRoleType::SUBJECT: |
| 118 | /** @var Subject $subject */ |
| 119 | $subject = $this->wordFinder->findSimilar( |
| 120 | new GetWordCommand( |
| 121 | GrammaticalRoleType::SUBJECT, |
| 122 | // we better trust the previous nick than parameters received |
| 123 | $previousNick->getTargetGender(), |
| 124 | $previousNick->getOffenseLevel(), |
| 125 | null, |
| 126 | $subject, |
| 127 | $command->getExclusions() |
| 128 | ) |
| 129 | ); |
| 130 | if (null === $subject) { |
| 131 | throw new NoSubjectFoundException(); |
| 132 | } |
| 133 | break; |
| 134 | case GrammaticalRoleType::QUALIFIER: |
| 135 | /** @var Qualifier $qualifier */ |
| 136 | $qualifier = $this->wordFinder->findSimilar( |
| 137 | new GetWordCommand( |
| 138 | GrammaticalRoleType::QUALIFIER, |
| 139 | // we better trust the previous nick than parameters received |
| 140 | $previousNick->getTargetGender(), |
| 141 | $previousNick->getOffenseLevel(), |
| 142 | null, |
| 143 | $qualifier, |
| 144 | $command->getExclusions() |
| 145 | ) |
| 146 | ); |
| 147 | if (null === $qualifier) { |
| 148 | throw new NoQualifierFoundException(); |
| 149 | } |
| 150 | break; |
| 151 | } |
| 152 | |
| 153 | return $this->buildGeneratedNick($subject, $qualifier, $previousNick->getTargetGender()); |
| 154 | } |
| 155 | |
| 156 | /** |
| 157 | * @throws NoQualifierFoundException|NoSubjectFoundException|RandomException |
| 158 | */ |
| 159 | private function createNick(GenerateNickCommand $command): GeneratedNickData |
| 160 | { |
| 161 | // get a Subject according to OffenseLevel and Gender |
| 162 | $criteria = [new LangCriterion($command->getLang())]; |
| 163 | $criteria[] = new GenderCriterion( |
| 164 | $command->getGender(), |
| 165 | GenderConstraintType::EXACT |
| 166 | ); |
| 167 | $criteria[] = new OffenseLevelCriterion($command->getOffenseLevel(), OffenseConstraintType::EXACT); |
| 168 | if (count($command->getExclusions())) { |
| 169 | $criteria = new ValuesCriterion(Word::class, 'id', $command->getExclusions(), ValuesCriterionCheck::NOT_IN); |
| 170 | } |
| 171 | |
| 172 | $subject = $this->subjectService->findOneRandomly( |
| 173 | new Criteria($criteria) |
| 174 | ); |
| 175 | if (null === $subject) { |
| 176 | throw new NoSubjectFoundException(); |
| 177 | } |
| 178 | $targetGender = $this->computeTargetGender($command, $subject); |
| 179 | |
| 180 | $exclusions = $command->getExclusions(); |
| 181 | $exclusions[] = $subject->getWord()->getId(); |
| 182 | |
| 183 | $criteria = [ |
| 184 | new LangCriterion($command->getLang()), |
| 185 | new GenderCriterion( |
| 186 | $targetGender, |
| 187 | GenderConstraintType::RELAXED, |
| 188 | ), |
| 189 | new OffenseLevelCriterion( |
| 190 | $subject->getWord()->getOffenseLevel(), |
| 191 | OffenseLevel::MAX === $command->getOffenseLevel() ? OffenseConstraintType::EXACT : OffenseConstraintType::LTE, |
| 192 | ), |
| 193 | new ValuesCriterion(Word::class, 'id', $exclusions, ValuesCriterionCheck::NOT_IN), |
| 194 | ]; |
| 195 | |
| 196 | // get a Qualifier according to the Subject's OffenseLevel and Gender |
| 197 | $qualifier = $this->qualifierService->findOneRandomly( |
| 198 | new Criteria( |
| 199 | $criteria |
| 200 | ) |
| 201 | ); |
| 202 | if (null === $qualifier) { |
| 203 | throw new NoQualifierFoundException(); |
| 204 | } |
| 205 | |
| 206 | return $this->buildGeneratedNick($subject, $qualifier, $targetGender); |
| 207 | } |
| 208 | |
| 209 | /** |
| 210 | * @throws NickNotFoundException|NoQualifierFoundException|NoSubjectFoundException|RandomException |
| 211 | */ |
| 212 | public function generateNick(GenerateNickCommand $command): GeneratedNickData |
| 213 | { |
| 214 | // create a new Nick, or "update" an existing one |
| 215 | if ($command->getPreviousNickId()) { |
| 216 | return $this->updateNick($command); |
| 217 | } |
| 218 | |
| 219 | return $this->createNick($command); |
| 220 | } |
| 221 | } |
Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not
necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once.
Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement
always has an else as part of its logical flow even if you didn't write one.
| 36 | public function __construct( |
| 37 | private readonly SubjectServiceInterface $subjectService, |
| 38 | private readonly QualifierServiceInterface $qualifierService, |
| 39 | private readonly NickComposerInterface $nickComposer, |
| 40 | private readonly NickServiceInterface $nickService, |
| 41 | private readonly WordFinderInterface $wordFinder, |
| 42 | ) { |
| 43 | } |
| 80 | private function buildGeneratedNick(Subject $subject, Qualifier $qualifier, WordGender $targetGender): GeneratedNickData |
| 81 | { |
| 82 | $composedNick = $this->nickComposer->compose($subject, $qualifier, $subject->getWord()->getLang(), $targetGender); |
| 83 | |
| 84 | $nick = $this->nickService->getOrCreate( |
| 85 | $subject, |
| 86 | $qualifier, |
| 87 | $targetGender, |
| 88 | $subject->getWord()->getOffenseLevel(), |
| 89 | $composedNick->getFinalLabel() |
| 90 | ); |
| 91 | |
| 92 | return new GeneratedNickData( |
| 93 | $nick->getTargetGender(), |
| 94 | $nick->getOffenseLevel(), |
| 95 | $nick, |
| 96 | $composedNick->getWords() |
| 97 | ); |
| 98 | } |
| 60 | private function computeTargetGender(GenerateNickCommand $command, Subject $subject): WordGender |
| 61 | { |
| 62 | // in case a non-auto gender has been explicitly asked, we have to respect it |
| 63 | if (null !== $command->getGender() && WordGender::AUTO !== $command->getGender()) { |
| 63 | if (null !== $command->getGender() && WordGender::AUTO !== $command->getGender()) { |
| 63 | if (null !== $command->getGender() && WordGender::AUTO !== $command->getGender()) { |
| 64 | return $command->getGender(); |
| 68 | return match ($subject->getWord()->getGender()) { |
| 69 | // neutral is randomly forced to M or F to increase possibilities, otherwise it would be very limited |
| 70 | // because in some languages neutral words are rare |
| 71 | // in any case, having a random M or F target gender will still allow NEUTRAL qualifiers |
| 72 | WordGender::AUTO, WordGender::NEUTRAL => 1 === random_int(0, 1) ? WordGender::M : WordGender::F, |
| 72 | WordGender::AUTO, WordGender::NEUTRAL => 1 === random_int(0, 1) ? WordGender::M : WordGender::F, |
| 72 | WordGender::AUTO, WordGender::NEUTRAL => 1 === random_int(0, 1) ? WordGender::M : WordGender::F, |
| 72 | WordGender::AUTO, WordGender::NEUTRAL => 1 === random_int(0, 1) ? WordGender::M : WordGender::F, |
| 72 | WordGender::AUTO, WordGender::NEUTRAL => 1 === random_int(0, 1) ? WordGender::M : WordGender::F, |
| 72 | WordGender::AUTO, WordGender::NEUTRAL => 1 === random_int(0, 1) ? WordGender::M : WordGender::F, |
| 72 | WordGender::AUTO, WordGender::NEUTRAL => 1 === random_int(0, 1) ? WordGender::M : WordGender::F, |
| 73 | default => $subject->getWord()->getGender(), |
| 73 | default => $subject->getWord()->getGender(), |
| 74 | }; |
| 75 | } |
| 159 | private function createNick(GenerateNickCommand $command): GeneratedNickData |
| 160 | { |
| 161 | // get a Subject according to OffenseLevel and Gender |
| 162 | $criteria = [new LangCriterion($command->getLang())]; |
| 163 | $criteria[] = new GenderCriterion( |
| 164 | $command->getGender(), |
| 165 | GenderConstraintType::EXACT |
| 166 | ); |
| 167 | $criteria[] = new OffenseLevelCriterion($command->getOffenseLevel(), OffenseConstraintType::EXACT); |
| 168 | if (count($command->getExclusions())) { |
| 169 | $criteria = new ValuesCriterion(Word::class, 'id', $command->getExclusions(), ValuesCriterionCheck::NOT_IN); |
| 170 | } |
| 171 | |
| 172 | $subject = $this->subjectService->findOneRandomly( |
| 172 | $subject = $this->subjectService->findOneRandomly( |
| 173 | new Criteria($criteria) |
| 174 | ); |
| 175 | if (null === $subject) { |
| 176 | throw new NoSubjectFoundException(); |
| 178 | $targetGender = $this->computeTargetGender($command, $subject); |
| 179 | |
| 180 | $exclusions = $command->getExclusions(); |
| 181 | $exclusions[] = $subject->getWord()->getId(); |
| 182 | |
| 183 | $criteria = [ |
| 184 | new LangCriterion($command->getLang()), |
| 185 | new GenderCriterion( |
| 186 | $targetGender, |
| 187 | GenderConstraintType::RELAXED, |
| 188 | ), |
| 189 | new OffenseLevelCriterion( |
| 190 | $subject->getWord()->getOffenseLevel(), |
| 191 | OffenseLevel::MAX === $command->getOffenseLevel() ? OffenseConstraintType::EXACT : OffenseConstraintType::LTE, |
| 191 | OffenseLevel::MAX === $command->getOffenseLevel() ? OffenseConstraintType::EXACT : OffenseConstraintType::LTE, |
| 191 | OffenseLevel::MAX === $command->getOffenseLevel() ? OffenseConstraintType::EXACT : OffenseConstraintType::LTE, |
| 191 | OffenseLevel::MAX === $command->getOffenseLevel() ? OffenseConstraintType::EXACT : OffenseConstraintType::LTE, |
| 192 | ), |
| 193 | new ValuesCriterion(Word::class, 'id', $exclusions, ValuesCriterionCheck::NOT_IN), |
| 194 | ]; |
| 195 | |
| 196 | // get a Qualifier according to the Subject's OffenseLevel and Gender |
| 197 | $qualifier = $this->qualifierService->findOneRandomly( |
| 198 | new Criteria( |
| 199 | $criteria |
| 200 | ) |
| 201 | ); |
| 202 | if (null === $qualifier) { |
| 203 | throw new NoQualifierFoundException(); |
| 206 | return $this->buildGeneratedNick($subject, $qualifier, $targetGender); |
| 207 | } |
| 212 | public function generateNick(GenerateNickCommand $command): GeneratedNickData |
| 213 | { |
| 214 | // create a new Nick, or "update" an existing one |
| 215 | if ($command->getPreviousNickId()) { |
| 216 | return $this->updateNick($command); |
| 219 | return $this->createNick($command); |
| 220 | } |
| 105 | private function updateNick(GenerateNickCommand $command): GeneratedNickData |
| 106 | { |
| 107 | $previousNick = $this->nickService->getNick($command->getPreviousNickId()); |
| 108 | if (null === $previousNick) { |
| 109 | throw new NickNotFoundException(); |
| 113 | $subject = $previousNick->getSubject(); |
| 114 | $qualifier = $previousNick->getQualifier(); |
| 115 | |
| 116 | switch ($command->getReplaceRoleType()) { |
| 117 | case GrammaticalRoleType::SUBJECT: |
| 134 | case GrammaticalRoleType::QUALIFIER: |
| 134 | case GrammaticalRoleType::QUALIFIER: |
| 119 | $subject = $this->wordFinder->findSimilar( |
| 120 | new GetWordCommand( |
| 121 | GrammaticalRoleType::SUBJECT, |
| 122 | // we better trust the previous nick than parameters received |
| 123 | $previousNick->getTargetGender(), |
| 124 | $previousNick->getOffenseLevel(), |
| 125 | null, |
| 126 | $subject, |
| 127 | $command->getExclusions() |
| 128 | ) |
| 129 | ); |
| 130 | if (null === $subject) { |
| 131 | throw new NoSubjectFoundException(); |
| 133 | break; |
| 136 | $qualifier = $this->wordFinder->findSimilar( |
| 137 | new GetWordCommand( |
| 138 | GrammaticalRoleType::QUALIFIER, |
| 139 | // we better trust the previous nick than parameters received |
| 140 | $previousNick->getTargetGender(), |
| 141 | $previousNick->getOffenseLevel(), |
| 142 | null, |
| 143 | $qualifier, |
| 144 | $command->getExclusions() |
| 145 | ) |
| 146 | ); |
| 147 | if (null === $qualifier) { |
| 148 | throw new NoQualifierFoundException(); |
| 150 | break; |
| 150 | break; |
| 151 | } |
| 152 | |
| 153 | return $this->buildGeneratedNick($subject, $qualifier, $previousNick->getTargetGender()); |
| 154 | } |
| 3 | namespace App\Service\Generator; |
| 4 | |
| 5 | use App\Dto\Command\GenerateNickCommand; |
| 6 | use App\Dto\Command\GetWordCommand; |
| 7 | use App\Dto\Result\GeneratedNickData; |
| 8 | use App\Entity\Qualifier; |
| 9 | use App\Entity\Subject; |
| 10 | use App\Entity\Word; |
| 11 | use App\Enum\GrammaticalRoleType; |
| 12 | use App\Enum\OffenseLevel; |
| 13 | use App\Enum\WordGender; |
| 14 | use App\Exception\NickNotFoundException; |
| 15 | use App\Exception\NoQualifierFoundException; |
| 16 | use App\Exception\NoSubjectFoundException; |
| 17 | use App\Service\Data\NickServiceInterface; |
| 18 | use App\Service\Data\QualifierServiceInterface; |
| 19 | use App\Service\Data\SubjectServiceInterface; |
| 20 | use App\Service\Nick\NickComposerInterface; |
| 21 | use App\Specification\Criteria; |
| 22 | use App\Specification\Criterion\GenderConstraintType; |
| 23 | use App\Specification\Criterion\GenderCriterion; |
| 24 | use App\Specification\Criterion\LangCriterion; |
| 25 | use App\Specification\Criterion\OffenseConstraintType; |
| 26 | use App\Specification\Criterion\OffenseLevelCriterion; |
| 27 | use App\Specification\Criterion\ValuesCriterion; |
| 28 | use App\Specification\Criterion\ValuesCriterionCheck; |
| 29 | use Random\RandomException; |
| 30 | |
| 31 | /** |
| 32 | * @author Wilhelm Zwertvaegher |
| 33 | */ |
| 34 | class NickGeneratorService implements NickGeneratorServiceInterface |
| 35 | { |
| 36 | public function __construct( |
| 37 | private readonly SubjectServiceInterface $subjectService, |
| 38 | private readonly QualifierServiceInterface $qualifierService, |
| 39 | private readonly NickComposerInterface $nickComposer, |
| 40 | private readonly NickServiceInterface $nickService, |
| 41 | private readonly WordFinderInterface $wordFinder, |
| 42 | ) { |
| 43 | } |
| 44 | |
| 45 | /** |
| 46 | * After a Subject has randomly been found, we have to set a target Gender for our Nick |
| 47 | * This is because the /api/word endpoint allows to replace the Subject or the Qualifier of a Nick and we have to produce |
| 48 | * consistent and compatible words |
| 49 | * For example : |
| 50 | * If the user wanted a AUTO nick, got a NEUTRAL subject, |
| 51 | * and we naively set the target gender as NEUTRAL, it may drastically reduce possibilities. This is ok for a NEUTRAL explicit request, |
| 52 | * but not an AUTO one. |
| 53 | * On the other hand we cannot simply set the target as AUTO because in that case for gender-specific words variation we |
| 54 | * would have to choose a default gender, because reloading the subject or qualifier or a nick would produce gender compatible words |
| 55 | * And we do not want to choose a gender as default, so by default we will randomly choose between M and F |
| 56 | * TL;DR ; a Nick's target Gender cannot be AUTO, it MUST be a defined GENDER. |
| 57 | * |
| 58 | * @throws RandomException |
| 59 | */ |
| 60 | private function computeTargetGender(GenerateNickCommand $command, Subject $subject): WordGender |
| 61 | { |
| 62 | // in case a non-auto gender has been explicitly asked, we have to respect it |
| 63 | if (null !== $command->getGender() && WordGender::AUTO !== $command->getGender()) { |
| 64 | return $command->getGender(); |
| 65 | } |
| 66 | |
| 67 | // in other cases, Gender depends on the found Subject gender |
| 68 | return match ($subject->getWord()->getGender()) { |
| 69 | // neutral is randomly forced to M or F to increase possibilities, otherwise it would be very limited |
| 70 | // because in some languages neutral words are rare |
| 71 | // in any case, having a random M or F target gender will still allow NEUTRAL qualifiers |
| 72 | WordGender::AUTO, WordGender::NEUTRAL => 1 === random_int(0, 1) ? WordGender::M : WordGender::F, |
| 73 | default => $subject->getWord()->getGender(), |
| 74 | }; |
| 75 | } |
| 76 | |
| 77 | /** |
| 78 | * Delegate the final Nick composition, and retrieve it from the system (i.e. get or create). |
| 79 | */ |
| 80 | private function buildGeneratedNick(Subject $subject, Qualifier $qualifier, WordGender $targetGender): GeneratedNickData |
| 81 | { |
| 82 | $composedNick = $this->nickComposer->compose($subject, $qualifier, $subject->getWord()->getLang(), $targetGender); |
| 83 | |
| 84 | $nick = $this->nickService->getOrCreate( |
| 85 | $subject, |
| 86 | $qualifier, |
| 87 | $targetGender, |
| 88 | $subject->getWord()->getOffenseLevel(), |
| 89 | $composedNick->getFinalLabel() |
| 90 | ); |
| 91 | |
| 92 | return new GeneratedNickData( |
| 93 | $nick->getTargetGender(), |
| 94 | $nick->getOffenseLevel(), |
| 95 | $nick, |
| 96 | $composedNick->getWords() |
| 97 | ); |
| 98 | } |
| 99 | |
| 100 | /** |
| 101 | * @throws NickNotFoundException |
| 102 | * @throws NoSubjectFoundException |
| 103 | * @throws NoQualifierFoundException |
| 104 | */ |
| 105 | private function updateNick(GenerateNickCommand $command): GeneratedNickData |
| 106 | { |
| 107 | $previousNick = $this->nickService->getNick($command->getPreviousNickId()); |
| 108 | if (null === $previousNick) { |
| 109 | throw new NickNotFoundException(); |
| 110 | } |
| 111 | |
| 112 | // create a new nick with a replaced word |
| 113 | $subject = $previousNick->getSubject(); |
| 114 | $qualifier = $previousNick->getQualifier(); |
| 115 | |
| 116 | switch ($command->getReplaceRoleType()) { |
| 117 | case GrammaticalRoleType::SUBJECT: |
| 118 | /** @var Subject $subject */ |
| 119 | $subject = $this->wordFinder->findSimilar( |
| 120 | new GetWordCommand( |
| 121 | GrammaticalRoleType::SUBJECT, |
| 122 | // we better trust the previous nick than parameters received |
| 123 | $previousNick->getTargetGender(), |
| 124 | $previousNick->getOffenseLevel(), |
| 125 | null, |
| 126 | $subject, |
| 127 | $command->getExclusions() |
| 128 | ) |
| 129 | ); |
| 130 | if (null === $subject) { |
| 131 | throw new NoSubjectFoundException(); |
| 132 | } |
| 133 | break; |
| 134 | case GrammaticalRoleType::QUALIFIER: |
| 135 | /** @var Qualifier $qualifier */ |
| 136 | $qualifier = $this->wordFinder->findSimilar( |
| 137 | new GetWordCommand( |
| 138 | GrammaticalRoleType::QUALIFIER, |
| 139 | // we better trust the previous nick than parameters received |
| 140 | $previousNick->getTargetGender(), |
| 141 | $previousNick->getOffenseLevel(), |
| 142 | null, |
| 143 | $qualifier, |
| 144 | $command->getExclusions() |
| 145 | ) |
| 146 | ); |
| 147 | if (null === $qualifier) { |
| 148 | throw new NoQualifierFoundException(); |
| 149 | } |
| 150 | break; |
| 151 | } |
| 152 | |
| 153 | return $this->buildGeneratedNick($subject, $qualifier, $previousNick->getTargetGender()); |
| 154 | } |
| 155 | |
| 156 | /** |
| 157 | * @throws NoQualifierFoundException|NoSubjectFoundException|RandomException |
| 158 | */ |
| 159 | private function createNick(GenerateNickCommand $command): GeneratedNickData |
| 160 | { |
| 161 | // get a Subject according to OffenseLevel and Gender |
| 162 | $criteria = [new LangCriterion($command->getLang())]; |
| 163 | $criteria[] = new GenderCriterion( |
| 164 | $command->getGender(), |
| 165 | GenderConstraintType::EXACT |
| 166 | ); |
| 167 | $criteria[] = new OffenseLevelCriterion($command->getOffenseLevel(), OffenseConstraintType::EXACT); |
| 168 | if (count($command->getExclusions())) { |
| 169 | $criteria = new ValuesCriterion(Word::class, 'id', $command->getExclusions(), ValuesCriterionCheck::NOT_IN); |
| 170 | } |
| 171 | |
| 172 | $subject = $this->subjectService->findOneRandomly( |
| 173 | new Criteria($criteria) |
| 174 | ); |
| 175 | if (null === $subject) { |
| 176 | throw new NoSubjectFoundException(); |
| 177 | } |
| 178 | $targetGender = $this->computeTargetGender($command, $subject); |
| 179 | |
| 180 | $exclusions = $command->getExclusions(); |
| 181 | $exclusions[] = $subject->getWord()->getId(); |
| 182 | |
| 183 | $criteria = [ |
| 184 | new LangCriterion($command->getLang()), |
| 185 | new GenderCriterion( |
| 186 | $targetGender, |
| 187 | GenderConstraintType::RELAXED, |
| 188 | ), |
| 189 | new OffenseLevelCriterion( |
| 190 | $subject->getWord()->getOffenseLevel(), |
| 191 | OffenseLevel::MAX === $command->getOffenseLevel() ? OffenseConstraintType::EXACT : OffenseConstraintType::LTE, |
| 192 | ), |
| 193 | new ValuesCriterion(Word::class, 'id', $exclusions, ValuesCriterionCheck::NOT_IN), |
| 194 | ]; |
| 195 | |
| 196 | // get a Qualifier according to the Subject's OffenseLevel and Gender |
| 197 | $qualifier = $this->qualifierService->findOneRandomly( |
| 198 | new Criteria( |
| 199 | $criteria |
| 200 | ) |
| 201 | ); |
| 202 | if (null === $qualifier) { |
| 203 | throw new NoQualifierFoundException(); |
| 204 | } |
| 205 | |
| 206 | return $this->buildGeneratedNick($subject, $qualifier, $targetGender); |
| 207 | } |
| 208 | |
| 209 | /** |
| 210 | * @throws NickNotFoundException|NoQualifierFoundException|NoSubjectFoundException|RandomException |
| 211 | */ |
| 212 | public function generateNick(GenerateNickCommand $command): GeneratedNickData |
| 213 | { |
| 214 | // create a new Nick, or "update" an existing one |
| 215 | if ($command->getPreviousNickId()) { |
| 216 | return $this->updateNick($command); |
| 217 | } |
| 218 | |
| 219 | return $this->createNick($command); |
| 220 | } |