| Total Complexity | 127 | 
| Total Lines | 731 | 
| Duplicated Lines | 1.92 % | 
| Changes | 0 | ||
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like TranslatableListener often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use TranslatableListener, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 26 | class TranslatableListener extends BaseListener  | 
            ||
| 27 | { | 
            ||
| 28 | /**  | 
            ||
| 29 | * Query hint to override the fallback of translations  | 
            ||
| 30 | * integer 1 for true, 0 false.  | 
            ||
| 31 | */  | 
            ||
| 32 | const HINT_FALLBACK = 'gedmo.translatable.fallback';  | 
            ||
| 33 | |||
| 34 | /**  | 
            ||
| 35 | * Query hint to override the fallback locale.  | 
            ||
| 36 | */  | 
            ||
| 37 | const HINT_TRANSLATABLE_LOCALE = 'gedmo.translatable.locale';  | 
            ||
| 38 | |||
| 39 | /**  | 
            ||
| 40 | * Query hint to use inner join strategy for translations.  | 
            ||
| 41 | */  | 
            ||
| 42 | const HINT_INNER_JOIN = 'gedmo.translatable.inner_join.translations';  | 
            ||
| 43 | |||
| 44 | /**  | 
            ||
| 45 | * Locale which is set on this listener.  | 
            ||
| 46 | * If Entity being translated has locale defined it  | 
            ||
| 47 | * will override this one.  | 
            ||
| 48 | *  | 
            ||
| 49 | * @var string  | 
            ||
| 50 | */  | 
            ||
| 51 | protected $locale = 'en_US';  | 
            ||
| 52 | |||
| 53 | /**  | 
            ||
| 54 | * Default locale, this changes behavior  | 
            ||
| 55 | * to not update the original record field if locale  | 
            ||
| 56 | * which is used for updating is not default. This  | 
            ||
| 57 | * will load the default translation in other locales  | 
            ||
| 58 | * if record is not translated yet.  | 
            ||
| 59 | *  | 
            ||
| 60 | * @var string  | 
            ||
| 61 | */  | 
            ||
| 62 | private $defaultLocale = 'en_US';  | 
            ||
| 63 | |||
| 64 | /**  | 
            ||
| 65 | * If this is set to false, when if entity does  | 
            ||
| 66 | * not have a translation for requested locale  | 
            ||
| 67 | * it will show a blank value.  | 
            ||
| 68 | *  | 
            ||
| 69 | * @var bool  | 
            ||
| 70 | */  | 
            ||
| 71 | private $translationFallback = false;  | 
            ||
| 72 | |||
| 73 | /**  | 
            ||
| 74 | * List of translations which do not have the foreign  | 
            ||
| 75 | * key generated yet - MySQL case. These translations  | 
            ||
| 76 | * will be updated with new keys on postPersist event.  | 
            ||
| 77 | *  | 
            ||
| 78 | * @var array  | 
            ||
| 79 | */  | 
            ||
| 80 | private $pendingTranslationInserts = [];  | 
            ||
| 81 | |||
| 82 | /**  | 
            ||
| 83 | * Currently in case if there is TranslationQueryWalker  | 
            ||
| 84 | * in charge. We need to skip issuing additional queries  | 
            ||
| 85 | * on load.  | 
            ||
| 86 | *  | 
            ||
| 87 | * @var bool  | 
            ||
| 88 | */  | 
            ||
| 89 | private $skipOnLoad = true;  | 
            ||
| 90 | |||
| 91 | /**  | 
            ||
| 92 | * Tracks locale the objects currently translated in.  | 
            ||
| 93 | *  | 
            ||
| 94 | * @var array  | 
            ||
| 95 | */  | 
            ||
| 96 | private $translatedInLocale = [];  | 
            ||
| 97 | |||
| 98 | /**  | 
            ||
| 99 | * Whether or not, to persist default locale  | 
            ||
| 100 | * translation or keep it in original record.  | 
            ||
| 101 | *  | 
            ||
| 102 | * @var bool  | 
            ||
| 103 | */  | 
            ||
| 104 | private $persistDefaultLocaleTranslation = false;  | 
            ||
| 105 | |||
| 106 | /**  | 
            ||
| 107 | * Tracks translation object for default locale.  | 
            ||
| 108 | *  | 
            ||
| 109 | * @var array  | 
            ||
| 110 | */  | 
            ||
| 111 | private $translationInDefaultLocale = [];  | 
            ||
| 112 | |||
| 113 | /**  | 
            ||
| 114 | * Specifies the list of events to listen.  | 
            ||
| 115 | *  | 
            ||
| 116 | * @return array  | 
            ||
| 117 | */  | 
            ||
| 118 | public function getSubscribedEvents()  | 
            ||
| 119 |     { | 
            ||
| 120 | return [  | 
            ||
| 121 | 'postLoad',  | 
            ||
| 122 | 'postPersist',  | 
            ||
| 123 | 'preFlush',  | 
            ||
| 124 | 'onFlush',  | 
            ||
| 125 | 'loadClassMetadata',  | 
            ||
| 126 | ];  | 
            ||
| 127 | }  | 
            ||
| 128 | |||
| 129 | /**  | 
            ||
| 130 | * Set to skip or not onLoad event.  | 
            ||
| 131 | *  | 
            ||
| 132 | * @param bool $bool  | 
            ||
| 133 | *  | 
            ||
| 134 | * @return static  | 
            ||
| 135 | */  | 
            ||
| 136 | public function setSkipOnLoad($bool)  | 
            ||
| 137 |     { | 
            ||
| 138 | $this->skipOnLoad = (bool)$bool;  | 
            ||
| 139 | |||
| 140 | return $this;  | 
            ||
| 141 | }  | 
            ||
| 142 | |||
| 143 | /**  | 
            ||
| 144 | * Whether or not, to persist default locale  | 
            ||
| 145 | * translation or keep it in original record.  | 
            ||
| 146 | *  | 
            ||
| 147 | * @param bool $bool  | 
            ||
| 148 | *  | 
            ||
| 149 | * @return static  | 
            ||
| 150 | */  | 
            ||
| 151 | public function setPersistDefaultLocaleTranslation($bool)  | 
            ||
| 152 |     { | 
            ||
| 153 | $this->persistDefaultLocaleTranslation = (bool)$bool;  | 
            ||
| 154 | |||
| 155 | return $this;  | 
            ||
| 156 | }  | 
            ||
| 157 | |||
| 158 | /**  | 
            ||
| 159 | * Check if should persist default locale  | 
            ||
| 160 | * translation or keep it in original record.  | 
            ||
| 161 | *  | 
            ||
| 162 | * @return bool  | 
            ||
| 163 | */  | 
            ||
| 164 | public function getPersistDefaultLocaleTranslation()  | 
            ||
| 165 |     { | 
            ||
| 166 | return (bool)$this->persistDefaultLocaleTranslation;  | 
            ||
| 167 | }  | 
            ||
| 168 | |||
| 169 | /**  | 
            ||
| 170 | * Add additional $translation for pending $oid object  | 
            ||
| 171 | * which is being inserted.  | 
            ||
| 172 | *  | 
            ||
| 173 | * @param string $oid  | 
            ||
| 174 | * @param object $translation  | 
            ||
| 175 | */  | 
            ||
| 176 | public function addPendingTranslationInsert($oid, $translation)  | 
            ||
| 177 |     { | 
            ||
| 178 | $this->pendingTranslationInserts[$oid][] = $translation;  | 
            ||
| 179 | }  | 
            ||
| 180 | |||
| 181 | /**  | 
            ||
| 182 | * Maps additional metadata.  | 
            ||
| 183 | *  | 
            ||
| 184 | * @param EventArgs $eventArgs  | 
            ||
| 185 | */  | 
            ||
| 186 | public function loadClassMetadata(EventArgs $eventArgs)  | 
            ||
| 187 |     { | 
            ||
| 188 | $adapter = $this->getEventAdapter($eventArgs);  | 
            ||
| 189 | $this->loadMetadataForObjectClass($adapter->getObjectManager(), $eventArgs->getClassMetadata());  | 
            ||
| 190 | }  | 
            ||
| 191 | |||
| 192 | /**  | 
            ||
| 193 | * Get the translation class to be used  | 
            ||
| 194 | * for the object $class.  | 
            ||
| 195 | *  | 
            ||
| 196 | * @param TranslatableAdapter $adapter  | 
            ||
| 197 | * @param string $class  | 
            ||
| 198 | *  | 
            ||
| 199 | * @return string  | 
            ||
| 200 | */  | 
            ||
| 201 | public function getTranslationClass(TranslatableAdapter $adapter, $class)  | 
            ||
| 202 |     { | 
            ||
| 203 | return isset(self::$configurations[$this->name][$class]['translationClass']) ? self::$configurations[$this->name][$class]['translationClass'] : $adapter->getDefaultTranslationClass();  | 
            ||
| 204 | }  | 
            ||
| 205 | |||
| 206 | /**  | 
            ||
| 207 | * Enable or disable translation fallback  | 
            ||
| 208 | * to original record value.  | 
            ||
| 209 | *  | 
            ||
| 210 | * @param bool $bool  | 
            ||
| 211 | *  | 
            ||
| 212 | * @return static  | 
            ||
| 213 | */  | 
            ||
| 214 | public function setTranslationFallback($bool)  | 
            ||
| 215 |     { | 
            ||
| 216 | $this->translationFallback = (bool)$bool;  | 
            ||
| 217 | |||
| 218 | return $this;  | 
            ||
| 219 | }  | 
            ||
| 220 | |||
| 221 | /**  | 
            ||
| 222 | * Weather or not is using the translation  | 
            ||
| 223 | * fallback to original record.  | 
            ||
| 224 | *  | 
            ||
| 225 | * @return bool  | 
            ||
| 226 | */  | 
            ||
| 227 | public function getTranslationFallback()  | 
            ||
| 228 |     { | 
            ||
| 229 | return $this->translationFallback;  | 
            ||
| 230 | }  | 
            ||
| 231 | |||
| 232 | /**  | 
            ||
| 233 | * Set the locale to use for translation listener.  | 
            ||
| 234 | *  | 
            ||
| 235 | * @param string $locale  | 
            ||
| 236 | *  | 
            ||
| 237 | * @return static  | 
            ||
| 238 | */  | 
            ||
| 239 | public function setTranslatableLocale($locale)  | 
            ||
| 240 |     { | 
            ||
| 241 | $this->validateLocale($locale);  | 
            ||
| 242 | $this->locale = $locale;  | 
            ||
| 243 | |||
| 244 | return $this;  | 
            ||
| 245 | }  | 
            ||
| 246 | |||
| 247 | /**  | 
            ||
| 248 | * Sets the default locale, this changes behavior  | 
            ||
| 249 | * to not update the original record field if locale  | 
            ||
| 250 | * which is used for updating is not default.  | 
            ||
| 251 | *  | 
            ||
| 252 | * @param string $locale  | 
            ||
| 253 | *  | 
            ||
| 254 | * @return static  | 
            ||
| 255 | */  | 
            ||
| 256 | public function setDefaultLocale($locale)  | 
            ||
| 257 |     { | 
            ||
| 258 | $this->validateLocale($locale);  | 
            ||
| 259 | $this->defaultLocale = $locale;  | 
            ||
| 260 | |||
| 261 | return $this;  | 
            ||
| 262 | }  | 
            ||
| 263 | |||
| 264 | /**  | 
            ||
| 265 | * Gets the default locale.  | 
            ||
| 266 | *  | 
            ||
| 267 | * @return string  | 
            ||
| 268 | */  | 
            ||
| 269 | public function getDefaultLocale()  | 
            ||
| 270 |     { | 
            ||
| 271 | return $this->defaultLocale;  | 
            ||
| 272 | }  | 
            ||
| 273 | |||
| 274 | /**  | 
            ||
| 275 | * Get currently set global locale, used  | 
            ||
| 276 | * extensively during query execution.  | 
            ||
| 277 | *  | 
            ||
| 278 | * @return string  | 
            ||
| 279 | */  | 
            ||
| 280 | public function getListenerLocale()  | 
            ||
| 281 |     { | 
            ||
| 282 | return $this->locale;  | 
            ||
| 283 | }  | 
            ||
| 284 | |||
| 285 | /**  | 
            ||
| 286 | * Gets the locale to use for translation. Loads object  | 
            ||
| 287 | * defined locale first..  | 
            ||
| 288 | *  | 
            ||
| 289 | * @param object $object  | 
            ||
| 290 | * @param object $meta  | 
            ||
| 291 | * @param object $manager  | 
            ||
| 292 | *  | 
            ||
| 293 | * @throws \Gedmo\Exception\RuntimeException - if language or locale property is not  | 
            ||
| 294 | * found in entity  | 
            ||
| 295 | *  | 
            ||
| 296 | * @return string  | 
            ||
| 297 | */  | 
            ||
| 298 | public function getTranslatableLocale($object, $meta, $manager = null)  | 
            ||
| 299 |     { | 
            ||
| 300 | $locale = $this->locale;  | 
            ||
| 301 |         if (isset(self::$configurations[$this->name][$meta->name]['locale'])) { | 
            ||
| 302 | /** @var \ReflectionClass $class */  | 
            ||
| 303 | $class = $meta->getReflectionClass();  | 
            ||
| 304 | $reflectionProperty = $class->getProperty(self::$configurations[$this->name][$meta->name]['locale']);  | 
            ||
| 305 |             if (!$reflectionProperty) { | 
            ||
| 306 | $column = self::$configurations[$this->name][$meta->name]['locale'];  | 
            ||
| 307 |                 throw new RuntimeException("There is no locale or language property ({$column}) found on object: {$meta->name}"); | 
            ||
| 308 | }  | 
            ||
| 309 | $reflectionProperty->setAccessible(true);  | 
            ||
| 310 | $value = $reflectionProperty->getValue($object);  | 
            ||
| 311 |             if (is_object($value) && method_exists($value, '__toString')) { | 
            ||
| 312 | $value = (string)$value;  | 
            ||
| 313 | }  | 
            ||
| 314 |             if ($this->isValidLocale($value)) { | 
            ||
| 315 | $locale = $value;  | 
            ||
| 316 | }  | 
            ||
| 317 | }  | 
            ||
| 318 | |||
| 319 | return $locale;  | 
            ||
| 320 | }  | 
            ||
| 321 | |||
| 322 | /**  | 
            ||
| 323 | * Handle translation changes in default locale.  | 
            ||
| 324 | *  | 
            ||
| 325 | * This has to be done in the preFlush because, when an entity has been loaded  | 
            ||
| 326 | * in a different locale, no changes will be detected.  | 
            ||
| 327 | *  | 
            ||
| 328 | * @param EventArgs $args  | 
            ||
| 329 | */  | 
            ||
| 330 | public function preFlush(EventArgs $args)  | 
            ||
| 331 |     { | 
            ||
| 332 | $adapter = $this->getEventAdapter($args);  | 
            ||
| 333 | $manager = $adapter->getObjectManager();  | 
            ||
| 334 | $uow = $manager->getUnitOfWork();  | 
            ||
| 335 | |||
| 336 |         foreach ($this->translationInDefaultLocale as $oid => $fields) { | 
            ||
| 337 | $trans = reset($fields);  | 
            ||
| 338 |             if ($adapter->usesPersonalTranslation(get_class($trans))) { | 
            ||
| 339 | $entity = $trans->getObject();  | 
            ||
| 340 |             } else { | 
            ||
| 341 | $entity = $uow->tryGetById($trans->getForeignKey(), $trans->getObjectClass());  | 
            ||
| 342 | }  | 
            ||
| 343 | |||
| 344 |             if (!$entity) { | 
            ||
| 345 | continue;  | 
            ||
| 346 | }  | 
            ||
| 347 | |||
| 348 |             try { | 
            ||
| 349 | $uow->scheduleForUpdate($entity);  | 
            ||
| 350 |             } catch (ORMInvalidArgumentException $e) { | 
            ||
| 351 |                 foreach ($fields as $field => $trans) { | 
            ||
| 352 | $this->removeTranslationInDefaultLocale($oid, $field);  | 
            ||
| 353 | }  | 
            ||
| 354 | }  | 
            ||
| 355 | }  | 
            ||
| 356 | }  | 
            ||
| 357 | |||
| 358 | /**  | 
            ||
| 359 | * Looks for translatable objects being inserted or updated  | 
            ||
| 360 | * for further processing.  | 
            ||
| 361 | *  | 
            ||
| 362 | * @param EventArgs $args  | 
            ||
| 363 | */  | 
            ||
| 364 | public function onFlush(EventArgs $args)  | 
            ||
| 365 |     { | 
            ||
| 366 | $adapter = $this->getEventAdapter($args);  | 
            ||
| 367 | $manager = $adapter->getObjectManager();  | 
            ||
| 368 | $uow = $manager->getUnitOfWork();  | 
            ||
| 369 | // check all scheduled inserts for Translatable objects  | 
            ||
| 370 | View Code Duplication |         foreach ($adapter->getScheduledObjectInsertions($uow) as $object) { | 
            |
| 371 | $meta = $manager->getClassMetadata(get_class($object));  | 
            ||
| 372 | $config = $this->getConfiguration($manager, $meta->name);  | 
            ||
| 373 |             if (isset($config['fields'])) { | 
            ||
| 374 | $this->handleTranslatableObjectUpdate($adapter, $object, true);  | 
            ||
| 375 | }  | 
            ||
| 376 | }  | 
            ||
| 377 | // check all scheduled updates for Translatable entities  | 
            ||
| 378 | View Code Duplication |         foreach ($adapter->getScheduledObjectUpdates($uow) as $object) { | 
            |
| 379 | $meta = $manager->getClassMetadata(get_class($object));  | 
            ||
| 380 | $config = $this->getConfiguration($manager, $meta->name);  | 
            ||
| 381 |             if (isset($config['fields'])) { | 
            ||
| 382 | $this->handleTranslatableObjectUpdate($adapter, $object, false);  | 
            ||
| 383 | }  | 
            ||
| 384 | }  | 
            ||
| 385 | // check scheduled deletions for Translatable entities  | 
            ||
| 386 |         foreach ($adapter->getScheduledObjectDeletions($uow) as $object) { | 
            ||
| 387 | $meta = $manager->getClassMetadata(get_class($object));  | 
            ||
| 388 | $config = $this->getConfiguration($manager, $meta->name);  | 
            ||
| 389 |             if (isset($config['fields'])) { | 
            ||
| 390 | $wrapped = AbstractWrapper::wrap($object, $manager);  | 
            ||
| 391 | $transClass = $this->getTranslationClass($adapter, $meta->name);  | 
            ||
| 392 | $adapter->removeAssociatedTranslations($wrapped, $transClass, $config['useObjectClass']);  | 
            ||
| 393 | }  | 
            ||
| 394 | }  | 
            ||
| 395 | }  | 
            ||
| 396 | |||
| 397 | /**  | 
            ||
| 398 | * Checks for inserted object to update their translation  | 
            ||
| 399 | * foreign keys.  | 
            ||
| 400 | *  | 
            ||
| 401 | * @param EventArgs $args  | 
            ||
| 402 | */  | 
            ||
| 403 | public function postPersist(EventArgs $args)  | 
            ||
| 404 |     { | 
            ||
| 405 | $adapter = $this->getEventAdapter($args);  | 
            ||
| 406 | $manager = $adapter->getObjectManager();  | 
            ||
| 407 | $object = $adapter->getObject();  | 
            ||
| 408 | $meta = $manager->getClassMetadata(get_class($object));  | 
            ||
| 409 | // check if entity is tracked by translatable and without foreign key  | 
            ||
| 410 |         if ($this->getConfiguration($manager, $meta->name) && count($this->pendingTranslationInserts)) { | 
            ||
| 411 | $oid = spl_object_hash($object);  | 
            ||
| 412 |             if (array_key_exists($oid, $this->pendingTranslationInserts)) { | 
            ||
| 413 | // load the pending translations without key  | 
            ||
| 414 | $wrapped = AbstractWrapper::wrap($object, $manager);  | 
            ||
| 415 | $objectId = $wrapped->getIdentifier();  | 
            ||
| 416 | $translationClass = $this->getTranslationClass($adapter, get_class($object));  | 
            ||
| 417 |                 foreach ($this->pendingTranslationInserts[$oid] as $translation) { | 
            ||
| 418 |                     if ($adapter->usesPersonalTranslation($translationClass)) { | 
            ||
| 419 | $translation->setObject($objectId);  | 
            ||
| 420 |                     } else { | 
            ||
| 421 | $translation->setForeignKey($objectId);  | 
            ||
| 422 | }  | 
            ||
| 423 | $adapter->insertTranslationRecord($translation);  | 
            ||
| 424 | }  | 
            ||
| 425 | unset($this->pendingTranslationInserts[$oid]);  | 
            ||
| 426 | }  | 
            ||
| 427 | }  | 
            ||
| 428 | }  | 
            ||
| 429 | |||
| 430 | /**  | 
            ||
| 431 | * After object is loaded, listener updates the translations  | 
            ||
| 432 | * by currently used locale.  | 
            ||
| 433 | *  | 
            ||
| 434 | * @param EventArgs $args  | 
            ||
| 435 | */  | 
            ||
| 436 | public function postLoad(EventArgs $args)  | 
            ||
| 437 |     { | 
            ||
| 438 | $adapter = $this->getEventAdapter($args);  | 
            ||
| 439 | $manager = $adapter->getObjectManager();  | 
            ||
| 440 | $object = $adapter->getObject();  | 
            ||
| 441 | $meta = $manager->getClassMetadata(get_class($object));  | 
            ||
| 442 | $config = $this->getConfiguration($manager, $meta->name);  | 
            ||
| 443 |         if (isset($config['fields'])) { | 
            ||
| 444 | $locale = $this->getTranslatableLocale($object, $meta, $manager);  | 
            ||
| 445 | $oid = spl_object_hash($object);  | 
            ||
| 446 | $this->translatedInLocale[$oid] = $locale;  | 
            ||
| 447 | }  | 
            ||
| 448 | |||
| 449 |         if ($this->skipOnLoad) { | 
            ||
| 450 | return;  | 
            ||
| 451 | }  | 
            ||
| 452 | |||
| 453 |         if (isset($config['fields']) && ($locale !== $this->defaultLocale || $this->persistDefaultLocaleTranslation)) { | 
            ||
| 454 | // fetch translations  | 
            ||
| 455 | $translationClass = $this->getTranslationClass($adapter, $config['useObjectClass']);  | 
            ||
| 456 | $result = $adapter->loadTranslations($object, $translationClass, $locale, $config['useObjectClass']);  | 
            ||
| 457 | // translate object's translatable properties  | 
            ||
| 458 |             foreach ($config['fields'] as $field) { | 
            ||
| 459 | $translated = '';  | 
            ||
| 460 |                 foreach ((array)$result as $entry) { | 
            ||
| 461 |                     if ($entry['field'] == $field) { | 
            ||
| 462 | $translated = $entry['content'];  | 
            ||
| 463 | break;  | 
            ||
| 464 | }  | 
            ||
| 465 | }  | 
            ||
| 466 | // update translation  | 
            ||
| 467 |                 if ($translated || (!$this->translationFallback && (!isset($config['fallback'][$field]) || !$config['fallback'][$field])) || ($this->translationFallback && isset($config['fallback'][$field]) && !$config['fallback'][$field])) { | 
            ||
| 468 | $adapter->setTranslationValue($object, $field, $translated);  | 
            ||
| 469 | // ensure clean changeset  | 
            ||
| 470 | $adapter->setOriginalObjectProperty($manager->getUnitOfWork(), $oid, $field, $meta->getReflectionProperty($field)  | 
            ||
| 471 | ->getValue($object));  | 
            ||
| 472 | }  | 
            ||
| 473 | }  | 
            ||
| 474 | }  | 
            ||
| 475 | }  | 
            ||
| 476 | |||
| 477 | /**  | 
            ||
| 478 |      * {@inheritdoc} | 
            ||
| 479 | */  | 
            ||
| 480 | protected function getNamespace()  | 
            ||
| 481 |     { | 
            ||
| 482 | return 'Sludio\HelperBundle\Translatable';  | 
            ||
| 483 | }  | 
            ||
| 484 | |||
| 485 | /**  | 
            ||
| 486 | * Validates the given locale.  | 
            ||
| 487 | *  | 
            ||
| 488 | * @param string $locale - locale to validate  | 
            ||
| 489 | *  | 
            ||
| 490 | * @throws \Gedmo\Exception\InvalidArgumentException if locale is not valid  | 
            ||
| 491 | */  | 
            ||
| 492 | protected function validateLocale($locale)  | 
            ||
| 493 |     { | 
            ||
| 494 |         if (!$this->isValidLocale($locale)) { | 
            ||
| 495 |             throw new InvalidArgumentException('Locale or language cannot be empty and must be set through Listener or Entity'); | 
            ||
| 496 | }  | 
            ||
| 497 | }  | 
            ||
| 498 | |||
| 499 | /**  | 
            ||
| 500 | * Check if the given locale is valid.  | 
            ||
| 501 | *  | 
            ||
| 502 | * @param string $locale - locale to check  | 
            ||
| 503 | *  | 
            ||
| 504 | * @return bool  | 
            ||
| 505 | */  | 
            ||
| 506 | private function isValidlocale($locale)  | 
            ||
| 507 |     { | 
            ||
| 508 | return is_string($locale) && strlen($locale);  | 
            ||
| 509 | }  | 
            ||
| 510 | |||
| 511 | /**  | 
            ||
| 512 | * Creates the translation for object being flushed.  | 
            ||
| 513 | *  | 
            ||
| 514 | * @param TranslatableAdapter $adapter  | 
            ||
| 515 | * @param object $object  | 
            ||
| 516 | * @param bool $isInsert  | 
            ||
| 517 | *  | 
            ||
| 518 | * @throws \UnexpectedValueException - if locale is not valid, or  | 
            ||
| 519 | * primary key is composite, missing or invalid  | 
            ||
| 520 | */  | 
            ||
| 521 | private function handleTranslatableObjectUpdate(TranslatableAdapter $adapter, $object, $isInsert)  | 
            ||
| 522 |     { | 
            ||
| 523 | $manager = $adapter->getObjectManager();  | 
            ||
| 524 | $wrapped = AbstractWrapper::wrap($object, $manager);  | 
            ||
| 525 | $meta = $wrapped->getMetadata();  | 
            ||
| 526 | $config = $this->getConfiguration($manager, $meta->name);  | 
            ||
| 527 | // no need cache, metadata is loaded only once in MetadataFactoryClass  | 
            ||
| 528 | $translationClass = $this->getTranslationClass($adapter, $config['useObjectClass']);  | 
            ||
| 529 | $translationMetadata = $manager->getClassMetadata($translationClass);  | 
            ||
| 530 | |||
| 531 | // check for the availability of the primary key  | 
            ||
| 532 | $objectId = $wrapped->getIdentifier();  | 
            ||
| 533 | // load the currently used locale  | 
            ||
| 534 | $locale = $this->getTranslatableLocale($object, $meta, $manager);  | 
            ||
| 535 | |||
| 536 | $uow = $manager->getUnitOfWork();  | 
            ||
| 537 | $oid = spl_object_hash($object);  | 
            ||
| 538 | $changeSet = $adapter->getObjectChangeSet($uow, $object);  | 
            ||
| 539 | $translatableFields = $config['fields'];  | 
            ||
| 540 |         foreach ($translatableFields as $field) { | 
            ||
| 541 | $wasPersistedSeparetely = false;  | 
            ||
| 542 | $skip = isset($this->translatedInLocale[$oid]) && $locale === $this->translatedInLocale[$oid];  | 
            ||
| 543 | $skip = $skip && !isset($changeSet[$field]) && !$this->getTranslationInDefaultLocale($oid, $field);  | 
            ||
| 544 |             if ($skip) { | 
            ||
| 545 | continue; // locale is same and nothing changed  | 
            ||
| 546 | }  | 
            ||
| 547 | $translation = null;  | 
            ||
| 548 |             foreach ($adapter->getScheduledObjectInsertions($uow) as $trans) { | 
            ||
| 549 |                 if ($locale !== $this->defaultLocale && get_class($trans) === $translationClass && $trans->getLocale() === $this->defaultLocale && $trans->getField() === $field && $this->belongsToObject($adapter, $trans, $object)) { | 
            ||
| 550 | $this->setTranslationInDefaultLocale($oid, $field, $trans);  | 
            ||
| 551 | break;  | 
            ||
| 552 | }  | 
            ||
| 553 | }  | 
            ||
| 554 | |||
| 555 | // lookup persisted translations  | 
            ||
| 556 |             foreach ($adapter->getScheduledObjectInsertions($uow) as $trans) { | 
            ||
| 557 |                 if (get_class($trans) !== $translationClass || $trans->getLocale() !== $locale || $trans->getField() !== $field) { | 
            ||
| 558 | continue;  | 
            ||
| 559 | }  | 
            ||
| 560 | |||
| 561 |                 if ($adapter->usesPersonalTranslation($translationClass)) { | 
            ||
| 562 | $wasPersistedSeparetely = $trans->getObject() === $object;  | 
            ||
| 563 |                 } else { | 
            ||
| 564 | $wasPersistedSeparetely = ($trans->getObjectClass() === $config['useObjectClass'] && $trans->getForeignKey() === $objectId);  | 
            ||
| 565 | }  | 
            ||
| 566 | |||
| 567 |                 if ($wasPersistedSeparetely) { | 
            ||
| 568 | $translation = $trans;  | 
            ||
| 569 | break;  | 
            ||
| 570 | }  | 
            ||
| 571 | }  | 
            ||
| 572 | |||
| 573 | // check if translation already is created  | 
            ||
| 574 |             if (!$isInsert && !$translation) { | 
            ||
| 575 | $translation = $adapter->findTranslation($wrapped, $locale, $field, $translationClass, $config['useObjectClass']);  | 
            ||
| 576 | }  | 
            ||
| 577 | |||
| 578 | // create new translation if translation not already created and locale is different from default locale, otherwise, we have the date in the original record  | 
            ||
| 579 | $persistNewTranslation = !$translation && ($locale !== $this->defaultLocale || $this->persistDefaultLocaleTranslation);  | 
            ||
| 580 |             if ($persistNewTranslation) { | 
            ||
| 581 | $translation = $translationMetadata->newInstance();  | 
            ||
| 582 | $translation->setLocale($locale);  | 
            ||
| 583 | $translation->setField($field);  | 
            ||
| 584 |                 if ($adapter->usesPersonalTranslation($translationClass)) { | 
            ||
| 585 | $translation->setObject($object);  | 
            ||
| 586 |                 } else { | 
            ||
| 587 | $translation->setObjectClass($config['useObjectClass']);  | 
            ||
| 588 | $translation->setForeignKey($objectId);  | 
            ||
| 589 | }  | 
            ||
| 590 | }  | 
            ||
| 591 | |||
| 592 |             if ($translation) { | 
            ||
| 593 | // set the translated field, take value using reflection  | 
            ||
| 594 | $content = $adapter->getTranslationValue($object, $field);  | 
            ||
| 595 | $translation->setContent($content);  | 
            ||
| 596 | // check if need to update in database  | 
            ||
| 597 | $transWrapper = AbstractWrapper::wrap($translation, $manager);  | 
            ||
| 598 |                 if (((is_null($content) && !$isInsert) || is_bool($content) || is_int($content) || (is_string($content) && strlen($content) > 0) || !empty($content)) && ($isInsert || !$transWrapper->getIdentifier() || isset($changeSet[$field]))) { | 
            ||
| 599 |                     if ($isInsert && !$objectId && !$adapter->usesPersonalTranslation($translationClass)) { | 
            ||
| 600 | // if we do not have the primary key yet available  | 
            ||
| 601 | // keep this translation in memory to insert it later with foreign key  | 
            ||
| 602 | $this->pendingTranslationInserts[spl_object_hash($object)][] = $translation;  | 
            ||
| 603 |                     } else { | 
            ||
| 604 | // persist and compute change set for translation  | 
            ||
| 605 |                         if ($wasPersistedSeparetely) { | 
            ||
| 606 | $adapter->recomputeSingleObjectChangeset($uow, $translationMetadata, $translation);  | 
            ||
| 607 |                         } else { | 
            ||
| 608 | $manager->persist($translation);  | 
            ||
| 609 | $uow->computeChangeSet($translationMetadata, $translation);  | 
            ||
| 610 | }  | 
            ||
| 611 | }  | 
            ||
| 612 | }  | 
            ||
| 613 | }  | 
            ||
| 614 | |||
| 615 |             if ($isInsert && $this->getTranslationInDefaultLocale($oid, $field) !== null) { | 
            ||
| 616 | // We can't rely on object field value which is created in non-default locale.  | 
            ||
| 617 | // If we provide translation for default locale as well, the latter is considered to be trusted  | 
            ||
| 618 | // and object content should be overridden.  | 
            ||
| 619 | $wrapped->setPropertyValue($field, $this->getTranslationInDefaultLocale($oid, $field)->getContent());  | 
            ||
| 620 | $adapter->recomputeSingleObjectChangeset($uow, $meta, $object);  | 
            ||
| 621 | $this->removeTranslationInDefaultLocale($oid, $field);  | 
            ||
| 622 | }  | 
            ||
| 623 | }  | 
            ||
| 624 | $this->translatedInLocale[$oid] = $locale;  | 
            ||
| 625 | // check if we have default translation and need to reset the translation  | 
            ||
| 626 |         if (!$isInsert && strlen($this->defaultLocale)) { | 
            ||
| 627 | $this->validateLocale($this->defaultLocale);  | 
            ||
| 628 | $modifiedChangeSet = $changeSet;  | 
            ||
| 629 |             foreach ($changeSet as $field => $changes) { | 
            ||
| 630 |                 if (in_array($field, $translatableFields)) { | 
            ||
| 631 |                     if ($locale !== $this->defaultLocale) { | 
            ||
| 632 | $adapter->setOriginalObjectProperty($uow, $oid, $field, $changes[0]);  | 
            ||
| 633 | unset($modifiedChangeSet[$field]);  | 
            ||
| 634 | }  | 
            ||
| 635 | }  | 
            ||
| 636 | }  | 
            ||
| 637 | $adapter->recomputeSingleObjectChangeset($uow, $meta, $object);  | 
            ||
| 638 | // cleanup current changeset only if working in a another locale different than de default one, otherwise the changeset will always be reverted  | 
            ||
| 639 |             if ($locale !== $this->defaultLocale) { | 
            ||
| 640 | $adapter->clearObjectChangeSet($uow, $oid);  | 
            ||
| 641 | // recompute changeset only if there are changes other than reverted translations  | 
            ||
| 642 |                 if ($modifiedChangeSet || $this->hasTranslationsInDefaultLocale($oid)) { | 
            ||
| 643 |                     foreach ($modifiedChangeSet as $field => $changes) { | 
            ||
| 644 | $adapter->setOriginalObjectProperty($uow, $oid, $field, $changes[0]);  | 
            ||
| 645 | }  | 
            ||
| 646 |                     foreach ($translatableFields as $field) { | 
            ||
| 647 |                         if ($this->getTranslationInDefaultLocale($oid, $field) !== null) { | 
            ||
| 648 | $wrapped->setPropertyValue($field, $this->getTranslationInDefaultLocale($oid, $field)  | 
            ||
| 649 | ->getContent());  | 
            ||
| 650 | $this->removeTranslationInDefaultLocale($oid, $field);  | 
            ||
| 651 | }  | 
            ||
| 652 | }  | 
            ||
| 653 | $adapter->recomputeSingleObjectChangeset($uow, $meta, $object);  | 
            ||
| 654 | }  | 
            ||
| 655 | }  | 
            ||
| 656 | }  | 
            ||
| 657 | }  | 
            ||
| 658 | |||
| 659 | /**  | 
            ||
| 660 | * Sets translation object which represents translation in default language.  | 
            ||
| 661 | *  | 
            ||
| 662 | * @param string $oid hash of basic entity  | 
            ||
| 663 | * @param string $field field of basic entity  | 
            ||
| 664 | * @param mixed $trans Translation object  | 
            ||
| 665 | */  | 
            ||
| 666 | public function setTranslationInDefaultLocale($oid, $field, $trans)  | 
            ||
| 667 |     { | 
            ||
| 668 |         if (!isset($this->translationInDefaultLocale[$oid])) { | 
            ||
| 669 | $this->translationInDefaultLocale[$oid] = [];  | 
            ||
| 670 | }  | 
            ||
| 671 | $this->translationInDefaultLocale[$oid][$field] = $trans;  | 
            ||
| 672 | }  | 
            ||
| 673 | |||
| 674 | /**  | 
            ||
| 675 | * @return bool  | 
            ||
| 676 | */  | 
            ||
| 677 | public function isSkipOnLoad()  | 
            ||
| 678 |     { | 
            ||
| 679 | return $this->skipOnLoad;  | 
            ||
| 680 | }  | 
            ||
| 681 | |||
| 682 | /**  | 
            ||
| 683 | * Removes translation object which represents translation in default language.  | 
            ||
| 684 | * This is for internal use only.  | 
            ||
| 685 | *  | 
            ||
| 686 | * @param string $oid hash of the basic entity  | 
            ||
| 687 | * @param string $field field of basic entity  | 
            ||
| 688 | */  | 
            ||
| 689 | private function removeTranslationInDefaultLocale($oid, $field)  | 
            ||
| 690 |     { | 
            ||
| 691 |         if (isset($this->translationInDefaultLocale[$oid])) { | 
            ||
| 692 |             if (isset($this->translationInDefaultLocale[$oid][$field])) { | 
            ||
| 693 | unset($this->translationInDefaultLocale[$oid][$field]);  | 
            ||
| 694 | }  | 
            ||
| 695 |             if (!$this->translationInDefaultLocale[$oid]) { | 
            ||
| 696 | // We removed the final remaining elements from the  | 
            ||
| 697 | // translationInDefaultLocale[$oid] array, so we might as well  | 
            ||
| 698 | // completely remove the entry at $oid.  | 
            ||
| 699 | unset($this->translationInDefaultLocale[$oid]);  | 
            ||
| 700 | }  | 
            ||
| 701 | }  | 
            ||
| 702 | }  | 
            ||
| 703 | |||
| 704 | /**  | 
            ||
| 705 | * Gets translation object which represents translation in default language.  | 
            ||
| 706 | * This is for internal use only.  | 
            ||
| 707 | *  | 
            ||
| 708 | * @param string $oid hash of the basic entity  | 
            ||
| 709 | * @param string $field field of basic entity  | 
            ||
| 710 | *  | 
            ||
| 711 | * @return mixed Returns translation object if it exists or NULL otherwise  | 
            ||
| 712 | */  | 
            ||
| 713 | private function getTranslationInDefaultLocale($oid, $field)  | 
            ||
| 726 | }  | 
            ||
| 727 | |||
| 728 | /**  | 
            ||
| 729 | * Check if object has any translation object which represents translation in default language.  | 
            ||
| 730 | * This is for internal use only.  | 
            ||
| 731 | *  | 
            ||
| 732 | * @param string $oid hash of the basic entity  | 
            ||
| 733 | *  | 
            ||
| 734 | * @return bool  | 
            ||
| 735 | */  | 
            ||
| 736 | public function hasTranslationsInDefaultLocale($oid)  | 
            ||
| 739 | }  | 
            ||
| 740 | |||
| 741 | /**  | 
            ||
| 742 | * Checks if the translation entity belongs to the object in question.  | 
            ||
| 743 | *  | 
            ||
| 744 | * @param TranslatableAdapter $adapter  | 
            ||
| 745 | * @param object $trans  | 
            ||
| 746 | * @param object $object  | 
            ||
| 747 | *  | 
            ||
| 748 | * @return bool  | 
            ||
| 749 | */  | 
            ||
| 750 | private function belongsToObject(TranslatableAdapter $adapter, $trans, $object)  | 
            ||
| 757 | }  | 
            ||
| 758 | }  | 
            ||
| 759 | 
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths