Failed Conditions
Pull Request — master (#6935)
by Michael
95:58
created

Configuration::getDefaultRepositoryClassName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM;
6
7
use Doctrine\Common\Annotations\AnnotationReader;
8
use Doctrine\Common\Annotations\AnnotationRegistry;
9
use Doctrine\Common\Annotations\CachedReader;
10
use Doctrine\Common\Cache\ArrayCache;
11
use Doctrine\Common\Cache\Cache as CacheDriver;
12
use Doctrine\Common\Persistence\ObjectRepository;
13
use Doctrine\DBAL\Configuration as DBALConfiguration;
14
use Doctrine\ORM\Cache\CacheConfiguration;
15
use Doctrine\ORM\Mapping\ClassMetadataFactory;
16
use Doctrine\ORM\Mapping\DefaultEntityListenerResolver;
17
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
18
use Doctrine\ORM\Mapping\Driver\MappingDriver;
19
use Doctrine\ORM\Mapping\EntityListenerResolver;
20
use Doctrine\ORM\Mapping\Factory\DefaultNamingStrategy;
21
use Doctrine\ORM\Mapping\Factory\NamingStrategy;
22
use Doctrine\ORM\Proxy\Factory\ProxyFactory;
23
use Doctrine\ORM\Query\ResultSetMapping;
24
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
25
use Doctrine\ORM\Repository\RepositoryFactory;
26
use ProxyManager\Configuration as ProxyManagerConfiguration;
27
use ProxyManager\Factory\LazyLoadingGhostFactory;
28
use ProxyManager\FileLocator\FileLocator;
29
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
30
use ProxyManager\GeneratorStrategy\FileWriterGeneratorStrategy;
31
32
/**
33
 * Configuration container for all configuration options of Doctrine.
34
 * It combines all configuration options from DBAL & ORM.
35
 *
36
 * Internal note: When adding a new configuration option just write a getter/setter pair.
37
 */
38
class Configuration extends DBALConfiguration
39
{
40
    /**
41
     * @var ProxyManagerConfiguration|null
42
     */
43
    private $proxyManagerConfiguration;
44
45
    /**
46
     * @var MappingDriver|null
47
     */
48
    private $metadataDriver;
49
50
    /**
51
     * @var CacheDriver|null
52
     */
53
    private $queryCache;
54
55
    /**
56
     * @var CacheDriver|null
57
     */
58
    private $hydrationCache;
59
60
    /**
61
     * @var CacheDriver|null
62
     */
63
    private $metadataCache;
64
65
    /**
66
     * @var string[] of DQL, indexed by query name
67
     */
68
    private $namedQueries = [];
69
70
    /**
71
     * @var string[][]|ResultSetMapping[][] tuples of [$sqlString, $resultSetMapping] indexed by query name
72
     */
73
    private $namedNativeQueries = [];
74
75
    /**
76
     * @var string[][]|ResultSetMapping[][] tuples of [$sqlString, $resultSetMapping] indexed by query name
77
     */
78
    private $customStringFunctions = [];
79
80
    /**
81
     * @var string[][]|ResultSetMapping[][] tuples of [$sqlString, $resultSetMapping] indexed by query name
82
     */
83
    private $customNumericFunctions = [];
84
85
    /**
86
     * @var string[][]|ResultSetMapping[][] tuples of [$sqlString, $resultSetMapping] indexed by query name
87
     */
88
    private $customDatetimeFunctions = [];
89
90
    /**
91
     * @var string[] of hydrator class names, indexed by mode name
92
     */
93
    private $customHydrationModes = [];
94
95
    /**
96
     * @var string
97
     */
98
    private $classMetadataFactoryClassName = ClassMetadataFactory::class;
99
100
    /**
101
     * @var string[] of filter class names, indexed by filter name
102
     */
103
    private $filters;
104
105
    /**
106
     * @var string
107
     */
108
    private $defaultRepositoryClassName = EntityRepository::class;
109
110
    /**
111
     * @var NamingStrategy|null
112
     */
113
    private $namingStrategy;
114
115
    /**
116
     * @var EntityListenerResolver|null
117
     */
118
    private $entityListenerResolver;
119
120
    /**
121
     * @var RepositoryFactory|null
122
     */
123
    private $repositoryFactory;
124
125
    /**
126
     * @var bool
127
     */
128
    private $isSecondLevelCacheEnabled = false;
129
130
    /**
131
     * @var CacheConfiguration|null
132
     */
133
    private $secondLevelCacheConfiguration;
134
135
    /**
136
     * @var mixed[] indexed by hint name
137
     */
138
    private $defaultQueryHints = [];
139
140
    /**
141
     * Sets the directory where Doctrine generates any necessary proxy class files.
142
     */
143 91
    public function setProxyDir(string $directory) : void
144
    {
145 91
        $this->getProxyManagerConfiguration()->setProxiesTargetDir($directory);
146 91
        $this->setAutoGenerateProxyClasses(ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS);
147 91
    }
148
149
    /**
150
     * Sets the strategy for automatically generating proxy classes.
151
     *
152
     * @param bool|int $autoGenerate Possible values are constants of Doctrine\ORM\Proxy\Factory\ProxyFactory.
153
     *                               True is converted to AUTOGENERATE_ALWAYS, false to AUTOGENERATE_NEVER.
154
     */
155 2281
    public function setAutoGenerateProxyClasses($autoGenerate) : void
156
    {
157 2281
        $proxyManagerConfig = $this->getProxyManagerConfiguration();
158
159 2281
        switch ((int) $autoGenerate) {
160
            case ProxyFactory::AUTOGENERATE_ALWAYS:
161
            case ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS:
162 100
                $proxyManagerConfig->setGeneratorStrategy(new FileWriterGeneratorStrategy(
163 100
                    new FileLocator($proxyManagerConfig->getProxiesTargetDir())
164
                ));
165
166 100
                return;
167
            case ProxyFactory::AUTOGENERATE_NEVER:
168
            case ProxyFactory::AUTOGENERATE_EVAL:
169
            default:
170 2196
                $proxyManagerConfig->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
171
172 2196
                return;
173
        }
174
    }
175
176
    /**
177
     * Sets the namespace where proxy classes reside.
178
     */
179 2268
    public function setProxyNamespace(string $namespace) : void
180
    {
181 2268
        $this->getProxyManagerConfiguration()->setProxiesNamespace($namespace);
182 2268
    }
183
184
    /**
185
     * Sets the cache driver implementation that is used for metadata caching.
186
     *
187
     * @todo Force parameter to be a Closure to ensure lazy evaluation
188
     *       (as soon as a metadata cache is in effect, the driver never needs to initialize).
189
     */
190 2264
    public function setMetadataDriverImpl(MappingDriver $metadataDriver) : void
191
    {
192 2264
        $this->metadataDriver = $metadataDriver;
193 2264
    }
194
195
    /**
196
     * Adds a new default annotation driver with a correctly configured annotation reader.
197
     *
198
     * @param string[] $paths
199
     */
200 2253
    public function newDefaultAnnotationDriver(array $paths = []) : AnnotationDriver
201
    {
202 2253
        AnnotationRegistry::registerFile(__DIR__ . '/Annotation/DoctrineAnnotations.php');
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\Common\Annotati...egistry::registerFile() has been deprecated: this method is deprecated and will be removed in doctrine/annotations 2.0 autoloading should be deferred to the globally registered autoloader by then. For now, use @example AnnotationRegistry::registerLoader('class_exists') ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

202
        /** @scrutinizer ignore-deprecated */ AnnotationRegistry::registerFile(__DIR__ . '/Annotation/DoctrineAnnotations.php');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
203
204 2253
        $reader = new CachedReader(new AnnotationReader(), new ArrayCache());
205
206 2253
        return new AnnotationDriver($reader, $paths);
207
    }
208
209
    /**
210
     * Gets the cache driver implementation that is used for the mapping metadata.
211
     */
212 2262
    public function getMetadataDriverImpl() : ?MappingDriver
213
    {
214 2262
        return $this->metadataDriver;
215
    }
216
217
    /**
218
     * Gets the cache driver implementation that is used for the query cache (SQL cache).
219
     */
220 767
    public function getQueryCacheImpl() : ?CacheDriver
221
    {
222 767
        return $this->queryCache;
223
    }
224
225
    /**
226
     * Sets the cache driver implementation that is used for the query cache (SQL cache).
227
     */
228 2203
    public function setQueryCacheImpl(CacheDriver $queryCache) : void
229
    {
230 2203
        $this->queryCache = $queryCache;
231 2203
    }
232
233
    /**
234
     * Gets the cache driver implementation that is used for the hydration cache (SQL cache).
235
     */
236 1
    public function getHydrationCacheImpl() : ?CacheDriver
237
    {
238 1
        return $this->hydrationCache;
239
    }
240
241
    /**
242
     * Sets the cache driver implementation that is used for the hydration cache (SQL cache).
243
     */
244 1
    public function setHydrationCacheImpl(CacheDriver $hydrationCache) : void
245
    {
246 1
        $this->hydrationCache = $hydrationCache;
247 1
    }
248
249
    /**
250
     * Gets the cache driver implementation that is used for metadata caching.
251
     */
252 2269
    public function getMetadataCacheImpl() : ?CacheDriver
253
    {
254 2269
        return $this->metadataCache;
255
    }
256
257
    /**
258
     * Sets the cache driver implementation that is used for metadata caching.
259
     */
260 2203
    public function setMetadataCacheImpl(CacheDriver $metadataCache) : void
261
    {
262 2203
        $this->metadataCache = $metadataCache;
263 2203
    }
264
265
    /**
266
     * Adds a named DQL query to the configuration.
267
     */
268 1
    public function addNamedQuery(string $queryName, string $dqlQuery) : void
269
    {
270 1
        $this->namedQueries[$queryName] = $dqlQuery;
271 1
    }
272
273
    /**
274
     * Gets a previously registered named DQL query.
275
     *
276
     * @throws ORMException
277
     */
278 1
    public function getNamedQuery(string $queryName) : string
279
    {
280 1
        if (! isset($this->namedQueries[$queryName])) {
281 1
            throw ORMException::namedQueryNotFound($queryName);
282
        }
283
284 1
        return $this->namedQueries[$queryName];
285
    }
286
287
    /**
288
     * Adds a named native query to the configuration.
289
     */
290 1
    public function addNamedNativeQuery(string $queryName, string $sql, ResultSetMapping $resultSetMapping) : void
291
    {
292 1
        $this->namedNativeQueries[$queryName] = [$sql, $resultSetMapping];
293 1
    }
294
295
    /**
296
     * Gets the components of a previously registered named native query.
297
     *
298
     * @return string[]|ResultSetMapping[] tuple of [$sqlString, $resultSetMaping]
299
     *
300
     * @throws ORMException
301
     */
302 1
    public function getNamedNativeQuery(string $queryName) : array
303
    {
304 1
        if (! isset($this->namedNativeQueries[$queryName])) {
305 1
            throw ORMException::namedNativeQueryNotFound($queryName);
306
        }
307
308 1
        return $this->namedNativeQueries[$queryName];
309
    }
310
311
    /**
312
     * Ensures that this Configuration instance contains settings that are
313
     * suitable for a production environment.
314
     *
315
     * @throws ORMException If a configuration setting has a value that is not
316
     *                      suitable for a production environment.
317
     */
318 6
    public function ensureProductionSettings() : void
319
    {
320 6
        $queryCacheImpl = $this->getQueryCacheImpl();
321
322 6
        if (! $queryCacheImpl) {
323 1
            throw ORMException::queryCacheNotConfigured();
324
        }
325
326 5
        if ($queryCacheImpl instanceof ArrayCache) {
327 1
            throw ORMException::queryCacheUsesNonPersistentCache($queryCacheImpl);
328
        }
329
330 4
        $metadataCacheImpl = $this->getMetadataCacheImpl();
331
332 4
        if (! $metadataCacheImpl) {
333 1
            throw ORMException::metadataCacheNotConfigured();
334
        }
335
336 3
        if ($metadataCacheImpl instanceof ArrayCache) {
337 1
            throw ORMException::metadataCacheUsesNonPersistentCache($metadataCacheImpl);
338
        }
339
340 2
        if ($this->getProxyManagerConfiguration()->getGeneratorStrategy() instanceof EvaluatingGeneratorStrategy) {
341 1
            throw ORMException::proxyClassesAlwaysRegenerating();
342
        }
343 1
    }
344
345
    /**
346
     * Registers a custom DQL function that produces a string value.
347
     * Such a function can then be used in any DQL statement in any place where string
348
     * functions are allowed.
349
     *
350
     * DQL function names are case-insensitive.
351
     *
352
     * @param string|callable $classNameOrFactory Class name or a callable that returns the function.
353
     */
354 4
    public function addCustomStringFunction(string $functionName, $classNameOrFactory) : void
355
    {
356 4
        $this->customStringFunctions[\strtolower($functionName)] = $classNameOrFactory;
357 4
    }
358
359
    /**
360
     * Gets the implementation class name of a registered custom string DQL function.
361
     *
362
     * @return string|callable|null
363
     */
364 157
    public function getCustomStringFunction(string $functionName)
365
    {
366 157
        return $this->customStringFunctions[\strtolower($functionName)] ?? null;
367
    }
368
369
    /**
370
     * Sets a map of custom DQL string functions.
371
     *
372
     * Keys must be function names and values the FQCN of the implementing class.
373
     * The function names will be case-insensitive in DQL.
374
     *
375
     * Any previously added string functions are discarded.
376
     *
377
     * @param string[]|callable[] $functions The map of custom DQL string functions.
378
     */
379 1
    public function setCustomStringFunctions(array $functions) : void
380
    {
381 1
        foreach ($functions as $name => $className) {
382 1
            $this->addCustomStringFunction($name, $className);
383
        }
384 1
    }
385
386
    /**
387
     * Registers a custom DQL function that produces a numeric value.
388
     * Such a function can then be used in any DQL statement in any place where numeric
389
     * functions are allowed.
390
     *
391
     * DQL function names are case-insensitive.
392
     *
393
     * @param string|callable $classNameOrFactory Class name or a callable that returns the function.
394
     */
395 3
    public function addCustomNumericFunction(string $functionName, $classNameOrFactory) : void
396
    {
397 3
        $this->customNumericFunctions[\strtolower($functionName)] = $classNameOrFactory;
398 3
    }
399
400
    /**
401
     * Gets the implementation class name of a registered custom numeric DQL function.
402
     *
403
     * @return string|callable|null
404
     */
405 155
    public function getCustomNumericFunction(string $functionName)
406
    {
407 155
        return $this->customNumericFunctions[\strtolower($functionName)] ?? null;
408
    }
409
410
    /**
411
     * Sets a map of custom DQL numeric functions.
412
     *
413
     * Keys must be function names and values the FQCN of the implementing class.
414
     * The function names will be case-insensitive in DQL.
415
     *
416
     * Any previously added numeric functions are discarded.
417
     *
418
     * @param string[]|callable[] $functions The map of custom DQL numeric functions.
419
     */
420 2
    public function setCustomNumericFunctions(array $functions) : void
421
    {
422 2
        foreach ($functions as $name => $className) {
423 1
            $this->addCustomNumericFunction($name, $className);
424
        }
425 2
    }
426
427
    /**
428
     * Registers a custom DQL function that produces a date/time value.
429
     * Such a function can then be used in any DQL statement in any place where date/time
430
     * functions are allowed.
431
     *
432
     * DQL function names are case-insensitive.
433
     *
434
     * @param string|callable $classNameOrFactory Class name or a callable that returns the function.
435
     */
436 1
    public function addCustomDatetimeFunction(string $functionName, $classNameOrFactory)
437
    {
438 1
        $this->customDatetimeFunctions[\strtolower($functionName)] = $classNameOrFactory;
439 1
    }
440
441
    /**
442
     * Gets the implementation class name of a registered custom date/time DQL function.
443
     *
444
     * @return string|callable|null
445
     */
446 153
    public function getCustomDatetimeFunction(string $functionName)
447
    {
448 153
        return $this->customDatetimeFunctions[\strtolower($functionName)] ?? null;
449
    }
450
451
    /**
452
     * Sets a map of custom DQL date/time functions.
453
     *
454
     * Keys must be function names and values the FQCN of the implementing class.
455
     * The function names will be case-insensitive in DQL.
456
     *
457
     * Any previously added date/time functions are discarded.
458
     *
459
     * @param iterable|string[] $functions The map of custom DQL date/time functions.
460
     */
461 1
    public function setCustomDatetimeFunctions(array $functions) : void
462
    {
463 1
        foreach ($functions as $name => $className) {
464 1
            $this->addCustomDatetimeFunction($name, $className);
465
        }
466 1
    }
467
468
    /**
469
     * Sets the custom hydrator modes in one pass.
470
     *
471
     * @param iterable|string[] $modes An iterable of string $modeName => string $hydratorClassName
472
     */
473 1
    public function setCustomHydrationModes(iterable $modes) : void
474
    {
475 1
        $this->customHydrationModes = [];
476
477 1
        foreach ($modes as $modeName => $hydrator) {
478 1
            $this->addCustomHydrationMode($modeName, $hydrator);
479
        }
480 1
    }
481
482
    /**
483
     * Gets the hydrator class for the given hydration mode name.
484
     *
485
     * @return string|null The hydrator class name.
486
     */
487 3
    public function getCustomHydrationMode(string $modeName) : ?string
488
    {
489 3
        return $this->customHydrationModes[$modeName] ?? null;
490
    }
491
492
    /**
493
     * Adds a custom hydration mode.
494
     */
495 3
    public function addCustomHydrationMode(string $modeName, string $hydratorClassName) : void
496
    {
497 3
        $this->customHydrationModes[$modeName] = $hydratorClassName;
498 3
    }
499
500
    /**
501
     * Sets a class metadata factory.
502
     */
503 1
    public function setClassMetadataFactoryName(string $classMetadataFactoryClassName) : void
504
    {
505 1
        $this->classMetadataFactoryClassName = $classMetadataFactoryClassName;
506 1
    }
507
508 2260
    public function getClassMetadataFactoryName() : string
509
    {
510 2260
        return $this->classMetadataFactoryClassName;
511
    }
512
513
    /**
514
     * Adds a filter to the list of possible filters.
515
     */
516 46
    public function addFilter(string $filterName, string $filterClassName) : void
517
    {
518 46
        $this->filters[$filterName] = $filterClassName;
519 46
    }
520
521
    /**
522
     * Gets the class name for a given filter name.
523
     *
524
     * @return string|null The class name of the filter
525
     */
526 45
    public function getFilterClassName(string $filterName) : ?string
527
    {
528 45
        return $this->filters[$filterName] ?? null;
529
    }
530
531
    /**
532
     * Sets default repository class.
533
     *
534
     * @throws ORMException If not is a \Doctrine\Common\Persistence\ObjectRepository implementation.
535
     */
536 2
    public function setDefaultRepositoryClassName(string $repositoryClassName) : void
537
    {
538 2
        $reflectionClass = new \ReflectionClass($repositoryClassName);
539
540 2
        if (! $reflectionClass->implementsInterface(ObjectRepository::class)) {
541 1
            throw ORMException::invalidEntityRepository($repositoryClassName);
542
        }
543
544 1
        $this->defaultRepositoryClassName = $repositoryClassName;
545 1
    }
546
547
    /**
548
     * Get default repository class.
549
     */
550 149
    public function getDefaultRepositoryClassName() : string
551
    {
552 149
        return $this->defaultRepositoryClassName;
553
    }
554
555
    /**
556
     * Sets naming strategy.
557
     */
558 3
    public function setNamingStrategy(NamingStrategy $namingStrategy) : void
559
    {
560 3
        $this->namingStrategy = $namingStrategy;
561 3
    }
562
563
    /**
564
     * Gets naming strategy..
565
     */
566 1959
    public function getNamingStrategy() : ?NamingStrategy
567
    {
568 1959
        return $this->namingStrategy
569 1959
            ?? $this->namingStrategy = new DefaultNamingStrategy();
570
    }
571
572
    /**
573
     * Set the entity listener resolver.
574
     */
575 1
    public function setEntityListenerResolver(EntityListenerResolver $resolver) : void
576
    {
577 1
        $this->entityListenerResolver = $resolver;
578 1
    }
579
580
    /**
581
     * Get the entity listener resolver.
582
     */
583 2260
    public function getEntityListenerResolver() : EntityListenerResolver
584
    {
585 2260
        return $this->entityListenerResolver
586 2260
            ?? $this->entityListenerResolver = new DefaultEntityListenerResolver();
587
    }
588
589
    /**
590
     * Set the entity repository factory.
591
     */
592
    public function setRepositoryFactory(RepositoryFactory $repositoryFactory) : void
593
    {
594
        $this->repositoryFactory = $repositoryFactory;
595
    }
596
597
    /**
598
     * Get the entity repository factory.
599
     */
600 2259
    public function getRepositoryFactory() : RepositoryFactory
601
    {
602 2259
        return $this->repositoryFactory
603 2259
            ?? $this->repositoryFactory = new DefaultRepositoryFactory();
604
    }
605
606 2259
    public function isSecondLevelCacheEnabled() : bool
607
    {
608 2259
        return $this->isSecondLevelCacheEnabled;
609
    }
610
611 279
    public function setSecondLevelCacheEnabled(bool $flag = true) : void
612
    {
613 279
        $this->isSecondLevelCacheEnabled = $flag;
614 279
    }
615
616 280
    public function setSecondLevelCacheConfiguration(CacheConfiguration $cacheConfig) : void
617
    {
618 280
        $this->secondLevelCacheConfiguration = $cacheConfig;
619 280
    }
620
621 280
    public function getSecondLevelCacheConfiguration() : ?CacheConfiguration
622
    {
623 280
        if ($this->isSecondLevelCacheEnabled && ! $this->secondLevelCacheConfiguration) {
624
            $this->secondLevelCacheConfiguration = new CacheConfiguration();
625
        }
626
627 280
        return $this->secondLevelCacheConfiguration;
628
    }
629
630
    /**
631
     * Returns query hints, which will be applied to every query in application
632
     *
633
     * @return mixed[]
634
     */
635 974
    public function getDefaultQueryHints() : array
636
    {
637 974
        return $this->defaultQueryHints;
638
    }
639
640
    /**
641
     * Sets array of query hints, which will be applied to every query in application
642
     *
643
     * @param mixed[] $defaultQueryHints
644
     */
645 1
    public function setDefaultQueryHints(array $defaultQueryHints) : void
646
    {
647 1
        $this->defaultQueryHints = $defaultQueryHints;
648 1
    }
649
650
    /**
651
     * Gets the value of a default query hint. If the hint name is not recognized, FALSE is returned.
652
     *
653
     * @return mixed The value of the hint or FALSE, if the hint name is not recognized.
654
     */
655
    public function getDefaultQueryHint(string $hintName)
656
    {
657
        return $this->defaultQueryHints[$hintName] ?? false;
658
    }
659
660
    /**
661
     * Sets a default query hint. If the hint name is not recognized, it is silently ignored.
662
     *
663
     * @param mixed $value The value of the hint.
664
     */
665 1
    public function setDefaultQueryHint(string $hintName, $value) : void
666
    {
667 1
        $this->defaultQueryHints[$hintName] = $value;
668 1
    }
669
670 2260
    public function buildGhostObjectFactory() : LazyLoadingGhostFactory
671
    {
672 2260
        return new LazyLoadingGhostFactory(clone $this->getProxyManagerConfiguration());
673
    }
674
675 2283
    public function getProxyManagerConfiguration() : ProxyManagerConfiguration
676
    {
677 2283
        return $this->proxyManagerConfiguration
678 2283
            ?? $this->proxyManagerConfiguration = new ProxyManagerConfiguration();
679
    }
680
}
681