Failed Conditions
Push — 1.0 ( 9f5a0b...fe7a2f )
by Bernhard
30:36 queued 17:00
created

AbstractJsonRepository   D

Complexity

Total Complexity 64

Size/Duplication

Total Lines 613
Duplicated Lines 1.79 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 98.85%

Importance

Changes 13
Bugs 4 Features 1
Metric Value
wmc 64
c 13
b 4
f 1
lcom 1
cbo 14
dl 11
loc 613
ccs 172
cts 174
cp 0.9885
rs 4.7584

28 Methods

Rating   Name   Duplication   Size   Complexity  
A setLogger() 0 4 1
insertReference() 0 1 ?
removeReferences() 0 1 ?
getReferencesForPath() 0 1 ?
getReferencesForGlob() 0 1 ?
getReferencesForRegex() 0 1 ?
getReferencesInDirectory() 0 1 ?
A addFilesystemResource() 0 10 1
A isLinkReference() 0 4 2
A isFilesystemReference() 0 4 2
A getShortClassName() 0 8 2
A __construct() 0 12 2
B add() 11 26 4
A get() 0 16 3
A find() 0 14 2
A contains() 0 13 2
A remove() 0 17 2
A clear() 0 15 2
A listChildren() 0 21 4
B hasChildren() 0 22 4
A log() 0 6 2
A logReferenceNotFound() 0 9 2
B load() 0 26 6
B flush() 0 27 5
B createResource() 0 21 6
A createResources() 0 8 2
A ensureDirectoryExists() 0 13 3
B addResource() 0 28 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like AbstractJsonRepository 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 AbstractJsonRepository, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the puli/repository package.
5
 *
6
 * (c) Bernhard Schussek <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Puli\Repository;
13
14
use Psr\Log\LoggerAwareInterface;
15
use Psr\Log\LoggerInterface;
16
use Psr\Log\LogLevel;
17
use Puli\Repository\Api\ChangeStream\ChangeStream;
18
use Puli\Repository\Api\Resource\FilesystemResource;
19
use Puli\Repository\Api\Resource\PuliResource;
20
use Puli\Repository\Api\ResourceCollection;
21
use Puli\Repository\Api\ResourceNotFoundException;
22
use Puli\Repository\Api\UnsupportedResourceException;
23
use Puli\Repository\Resource\Collection\ArrayResourceCollection;
24
use Puli\Repository\Resource\DirectoryResource;
25
use Puli\Repository\Resource\FileResource;
26
use Puli\Repository\Resource\GenericResource;
27
use Puli\Repository\Resource\LinkResource;
28
use RuntimeException;
29
use Webmozart\Assert\Assert;
30
use Webmozart\Json\JsonDecoder;
31
use Webmozart\Json\JsonEncoder;
32
use Webmozart\PathUtil\Path;
33
34
/**
35
 * Base class for repositories backed by a JSON file.
36
 *
37
 * The generated JSON file is described by res/schema/repository-schema-1.0.json.
38
 *
39
 * @since  1.0
40
 *
41
 * @author Bernhard Schussek <[email protected]>
42
 * @author Titouan Galopin <[email protected]>
43
 */
44
abstract class AbstractJsonRepository extends AbstractEditableRepository implements LoggerAwareInterface
45
{
46
    /**
47
     * Flag: Whether to stop after the first result.
48
     *
49
     * @internal
50
     */
51
    const STOP_ON_FIRST = 1;
52
53
    /**
54
     * @var array
55
     */
56
    protected $json;
57
58
    /**
59
     * @var string
60
     */
61
    protected $baseDirectory;
62
63
    /**
64
     * @var string
65
     */
66
    private $path;
67
68
    /**
69
     * @var string
70
     */
71
    private $schemaPath;
72
73
    /**
74
     * @var JsonEncoder
75
     */
76
    private $encoder;
77
78
    /**
79
     * @var LoggerInterface
80
     */
81
    private $logger;
82
83
    /**
84
     * Creates a new repository.
85
     *
86
     * @param string            $path          The path to the JSON file. If
87
     *                                         relative, it must be relative to
88
     *                                         the base directory.
89
     * @param string            $baseDirectory The base directory of the store.
90
     *                                         Paths inside that directory are
91
     *                                         stored as relative paths. Paths
92
     *                                         outside that directory are stored
93
     *                                         as absolute paths.
94
     * @param bool              $validateJson  Whether to validate the JSON file
95
     *                                         against the schema. Slow but
96
     *                                         spots problems.
97
     * @param ChangeStream|null $changeStream  If provided, the repository will
98
     *                                         append resource changes to this
99
     *                                         change stream.
100
     */
101 392
    public function __construct($path, $baseDirectory, $validateJson = false, ChangeStream $changeStream = null)
102
    {
103 392
        parent::__construct($changeStream);
104
105 392
        $this->baseDirectory = $baseDirectory;
106 392
        $this->path = Path::makeAbsolute($path, $baseDirectory);
107 392
        $this->encoder = new JsonEncoder();
108
109 392
        if ($validateJson) {
110 392
            $this->schemaPath = realpath(__DIR__.'/../res/schema/path-mappings-schema-1.0.json');
111
        }
112 392
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117 22
    public function setLogger(LoggerInterface $logger = null)
118
    {
119 22
        $this->logger = $logger;
120 22
    }
121
122
    /**
123
     * {@inheritdoc}
124
     */
125 352
    public function add($path, $resource)
126
    {
127 352
        if (null === $this->json) {
128 352
            $this->load();
129
        }
130
131 352
        $path = $this->sanitizePath($path);
132
133 340 View Code Duplication
        if ($resource instanceof ResourceCollection) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
134 4
            $this->ensureDirectoryExists($path);
135
136 4
            foreach ($resource as $child) {
137 4
                $this->addResource($path.'/'.$child->getName(), $child);
138
            }
139
140 4
            $this->flush();
141
142 4
            return;
143
        }
144
145 336
        $this->ensureDirectoryExists(Path::getDirectory($path));
146
147 336
        $this->addResource($path, $resource);
0 ignored issues
show
Documentation introduced by
$resource is of type object<Puli\Repository\Api\Resource\PuliResource>, but the function expects a object<Puli\Repository\A...\Resource\LinkResource>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
148
149 332
        $this->flush();
150 332
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155 156
    public function get($path)
156
    {
157 156
        if (null === $this->json) {
158 24
            $this->load();
159
        }
160
161 156
        $path = $this->sanitizePath($path);
162 144
        $references = $this->getReferencesForPath($path);
163
164
        // Might be null, don't use isset()
165 144
        if (array_key_exists($path, $references)) {
166 134
            return $this->createResource($path, $references[$path]);
167
        }
168
169 10
        throw ResourceNotFoundException::forPath($path);
170
    }
171
172
    /**
173
     * {@inheritdoc}
174
     */
175 48
    public function find($query, $language = 'glob')
176
    {
177 48
        if (null === $this->json) {
178 6
            $this->load();
179
        }
180
181 48
        $this->failUnlessGlob($language);
182 44
        $query = $this->sanitizePath($query);
183 32
        $results = $this->createResources($this->getReferencesForGlob($query));
184
185 32
        ksort($results);
186
187 32
        return new ArrayResourceCollection(array_values($results));
188
    }
189
190
    /**
191
     * {@inheritdoc}
192
     */
193 68
    public function contains($query, $language = 'glob')
194
    {
195 68
        if (null === $this->json) {
196 22
            $this->load();
197
        }
198
199 68
        $this->failUnlessGlob($language);
200 64
        $query = $this->sanitizePath($query);
201
202 52
        $results = $this->getReferencesForGlob($query, self::STOP_ON_FIRST);
203
204 52
        return !empty($results);
205
    }
206
207
    /**
208
     * {@inheritdoc}
209
     */
210 66
    public function remove($query, $language = 'glob')
211
    {
212 66
        if (null === $this->json) {
213 24
            $this->load();
214
        }
215
216 66
        $this->failUnlessGlob($language);
217 62
        $query = $this->sanitizePath($query);
218
219 50
        Assert::notEmpty(trim($query, '/'), 'The root directory cannot be removed.');
220
221 42
        $removed = $this->removeReferences($query);
222
223 40
        $this->flush();
224
225 40
        return $removed;
226
    }
227
228
    /**
229
     * {@inheritdoc}
230
     */
231 6
    public function clear()
232
    {
233 6
        if (null === $this->json) {
234 2
            $this->load();
235
        }
236
237
        // Subtract root which is not deleted
238 6
        $removed = count($this->getReferencesForRegex('/', '~.~')) - 1;
239
240 6
        $this->json = array();
241
242 6
        $this->flush();
243
244 6
        return $removed;
245
    }
246
247
    /**
248
     * {@inheritdoc}
249
     */
250 48
    public function listChildren($path)
251
    {
252 48
        if (null === $this->json) {
253 2
            $this->load();
254
        }
255
256 48
        $path = $this->sanitizePath($path);
257 36
        $results = $this->createResources($this->getReferencesInDirectory($path));
258
259 36
        if (empty($results)) {
260 16
            $pathResults = $this->getReferencesForPath($path);
261
262 16
            if (empty($pathResults)) {
263 4
                throw ResourceNotFoundException::forPath($path);
264
            }
265
        }
266
267 32
        ksort($results);
268
269 32
        return new ArrayResourceCollection(array_values($results));
270
    }
271
272
    /**
273
     * {@inheritdoc}
274
     */
275 32
    public function hasChildren($path)
276
    {
277 32
        if (null === $this->json) {
278 2
            $this->load();
279
        }
280
281 32
        $path = $this->sanitizePath($path);
282
283 20
        $results = $this->getReferencesInDirectory($path, self::STOP_ON_FIRST);
284
285 20
        if (empty($results)) {
286 12
            $pathResults = $this->getReferencesForPath($path);
287
288 12
            if (empty($pathResults)) {
289 4
                throw ResourceNotFoundException::forPath($path);
290
            }
291
292 8
            return false;
293
        }
294
295 12
        return true;
296
    }
297
298
    /**
299
     * Inserts a path reference into the JSON file.
300
     *
301
     * The path reference can be:
302
     *
303
     *  * a link starting with `@`
304
     *  * an absolute filesystem path
305
     *
306
     * @param string      $path      The Puli path.
307
     * @param string|null $reference The path reference.
308
     */
309
    abstract protected function insertReference($path, $reference);
1 ignored issue
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
310
311
    /**
312
     * Removes all path references matching the given glob from the JSON file.
313
     *
314
     * @param string $glob The glob for a list of Puli paths.
315
     */
316
    abstract protected function removeReferences($glob);
1 ignored issue
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
317
318
    /**
319
     * Returns the references for a given Puli path.
320
     *
321
     * Each reference returned by this method can be:
322
     *
323
     *  * `null`
324
     *  * a link starting with `@`
325
     *  * an absolute filesystem path
326
     *
327
     * The result has either one entry or none, if no path was found. The key
328
     * of the single entry is the path passed to this method.
329
     *
330
     * @param string $path The Puli path.
331
     *
332
     * @return string[]|null[] A one-level array of references with Puli paths
333
     *                         as keys. The array has at most one entry.
334
     */
335
    abstract protected function getReferencesForPath($path);
336
337
    /**
338
     * Returns the references matching a given Puli path glob.
339
     *
340
     * Each reference returned by this method can be:
341
     *
342
     *  * `null`
343
     *  * a link starting with `@`
344
     *  * an absolute filesystem path
345
     *
346
     * The keys of the returned array are Puli paths. Their order is undefined.
347
     *
348
     * @param string $glob  The glob.
349
     * @param int    $flags A bitwise combination of the flag constants in this
350
     *                      class.
351
     *
352
     * @return string[]|null[] A one-level array of references with Puli paths
353
     *                         as keys.
354
     */
355
    abstract protected function getReferencesForGlob($glob, $flags = 0);
356
357
    /**
358
     * Returns the references matching a given Puli path regular expression.
359
     *
360
     * Each reference returned by this method can be:
361
     *
362
     *  * `null`
363
     *  * a link starting with `@`
364
     *  * an absolute filesystem path
365
     *
366
     * The keys of the returned array are Puli paths. Their order is undefined.
367
     *
368
     * @param string $staticPrefix The static prefix of all Puli paths matching
369
     *                             the regular expression.
370
     * @param string $regex        The regular expression.
371
     * @param int    $flags        A bitwise combination of the flag constants
372
     *                             in this class.
373
     *
374
     * @return string[]|null[] A one-level array of references with Puli paths
375
     *                         as keys.
376
     */
377
    abstract protected function getReferencesForRegex($staticPrefix, $regex, $flags = 0);
378
379
    /**
380
     * Returns the references in a given Puli path.
381
     *
382
     * Each reference returned by this method can be:
383
     *
384
     *  * `null`
385
     *  * a link starting with `@`
386
     *  * an absolute filesystem path
387
     *
388
     * The keys of the returned array are Puli paths. Their order is undefined.
389
     *
390
     * @param string $path  The Puli path.
391
     * @param int    $flags A bitwise combination of the flag constants in this
392
     *                      class.
393
     *
394
     * @return string[]|null[] A one-level array of references with Puli paths
395
     *                         as keys.
396
     */
397
    abstract protected function getReferencesInDirectory($path, $flags = 0);
398
399
    /**
400
     * Logs a message.
401
     *
402
     * @param mixed  $level   One of the level constants in {@link LogLevel}.
403
     * @param string $message The message.
404
     */
405 22
    protected function log($level, $message)
406
    {
407 22
        if (null !== $this->logger) {
408 22
            $this->logger->log($level, $message);
409
        }
410 22
    }
411
412
    /**
413
     * Logs a warning that a reference could not be found.
414
     *
415
     * @param string $path              The Puli path of a path mapping.
416
     * @param string $reference         The reference that was not found.
417
     * @param string $absoluteReference The absolute filesystem path of the
418
     *                                  reference.
419
     */
420 22
    protected function logReferenceNotFound($path, $reference, $absoluteReference)
421
    {
422 22
        $this->log(LogLevel::WARNING, sprintf(
423 22
            'The reference "%s"%s mapped by the path %s could not be found.',
424
            $reference,
425 22
            $reference !== $absoluteReference ? ' ('.$absoluteReference.')' : '',
426
            $path
427
        ));
428 22
    }
429
430
    /**
431
     * Adds a filesystem resource to the JSON file.
432
     *
433
     * @param string             $path     The Puli path.
434
     * @param FilesystemResource $resource The resource to add.
435
     */
436 336
    protected function addFilesystemResource($path, FilesystemResource $resource)
437
    {
438 336
        $resource->attachTo($this, $path);
439
440 336
        $relativePath = Path::makeRelative($resource->getFilesystemPath(), $this->baseDirectory);
441
442 336
        $this->insertReference($path, $relativePath);
443
444 336
        $this->storeVersion($resource);
445 336
    }
446
447
    /**
448
     * Loads the JSON file.
449
     */
450 392
    protected function load()
451
    {
452 392
        $decoder = new JsonDecoder();
453
454 392
        $this->json = file_exists($this->path)
455 65
            ? (array) $decoder->decodeFile($this->path, $this->schemaPath)
456 392
            : array();
457
458 392
        if (isset($this->json['_order'])) {
459 5
            $this->json['_order'] = (array) $this->json['_order'];
460
461 5
            foreach ($this->json['_order'] as $path => $entries) {
462 5
                foreach ($entries as $key => $entry) {
463 5
                    $this->json['_order'][$path][$key] = (array) $entry;
464
                }
465
            }
466
        }
467
468
        // The root node always exists
469 392
        if (!isset($this->json['/'])) {
470 392
            $this->json['/'] = null;
471
        }
472
473
        // Make sure the JSON is sorted in reverse order
474 392
        krsort($this->json);
475 392
    }
476
477
    /**
478
     * Writes the JSON file.
479
     */
480 340
    protected function flush()
481
    {
482
        // The root node always exists
483 340
        if (!isset($this->json['/'])) {
484 144
            $this->json['/'] = null;
485
        }
486
487
        // Always save in reverse order
488 340
        krsort($this->json);
489
490
        // Comply to schema
491 340
        $json = (object) $this->json;
492
493 340
        if (isset($json->{'_order'})) {
494 10
            $order = $json->{'_order'};
495
496 10
            foreach ($order as $path => $entries) {
497 10
                foreach ($entries as $key => $entry) {
498 10
                    $order[$path][$key] = (object) $entry;
499
                }
500
            }
501
502 10
            $json->{'_order'} = (object) $order;
503
        }
504
505 340
        $this->encoder->encodeFile($json, $this->path, $this->schemaPath);
506 340
    }
507
508
    /**
509
     * Returns whether a reference contains a link.
510
     *
511
     * @param string $reference The reference.
512
     *
513
     * @return bool Whether the reference contains a link.
514
     */
515 280
    protected function isLinkReference($reference)
516
    {
517 280
        return isset($reference{0}) && '@' === $reference{0};
518
    }
519
520
    /**
521
     * Returns whether a reference contains an absolute or relative filesystem
522
     * path.
523
     *
524
     * @param string $reference The reference.
525
     *
526
     * @return bool Whether the reference contains a filesystem path.
527
     */
528 286
    protected function isFilesystemReference($reference)
529
    {
530 286
        return null !== $reference && !$this->isLinkReference($reference);
531
    }
532
533
    /**
534
     * Turns a reference into a resource.
535
     *
536
     * @param string      $path      The Puli path.
537
     * @param string|null $reference The reference.
538
     *
539
     * @return PuliResource The resource.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use GenericResource.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
540
     */
541 152
    protected function createResource($path, $reference)
542
    {
543 152
        if (null === $reference) {
544 12
            $resource = new GenericResource();
545 140
        } elseif (isset($reference{0}) && '@' === $reference{0}) {
546 8
            $resource = new LinkResource(substr($reference, 1));
547 140
        } elseif (is_dir($reference)) {
548 76
            $resource = new DirectoryResource($reference);
549 92
        } elseif (is_file($reference)) {
550 92
            $resource = new FileResource($reference);
551
        } else {
552
            throw new RuntimeException(sprintf(
553
                'Trying to create a FilesystemResource on a non-existing file or directory "%s"',
554
                $reference
555
            ));
556
        }
557
558 152
        $resource->attachTo($this, $path);
559
560 152
        return $resource;
561
    }
562
563
    /**
564
     * Turns a list of references into a list of resources.
565
     *
566
     * The references are expected to be in the format returned by
567
     * {@link getReferencesForPath()}, {@link getReferencesForGlob()} and
568
     * {@link getReferencesInDirectory()}.
569
     *
570
     * The result contains Puli paths as keys and {@link PuliResource}
571
     * implementations as values. The order of the results is undefined.
572
     *
573
     * @param string[]|null[] $references The references indexed by Puli paths.
574
     *
575
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string|null|GenericResource>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
576
     */
577 68
    private function createResources(array $references)
578
    {
579 68
        foreach ($references as $path => $reference) {
580 44
            $references[$path] = $this->createResource($path, $reference);
0 ignored issues
show
Bug introduced by
It seems like $reference defined by $reference on line 579 can also be of type object<Puli\Repository\Resource\GenericResource>; however, Puli\Repository\Abstract...itory::createResource() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
581
        }
582
583 68
        return $references;
584
    }
585
586
    /**
587
     * Adds all ancestor directories of a path to the repository.
588
     *
589
     * @param string $path A Puli path.
590
     */
591 340
    private function ensureDirectoryExists($path)
592
    {
593 340
        if (array_key_exists($path, $this->json)) {
594 340
            return;
595
        }
596
597
        // Recursively initialize parent directories
598 112
        if ('/' !== $path) {
599 112
            $this->ensureDirectoryExists(Path::getDirectory($path));
600
        }
601
602 112
        $this->json[$path] = null;
603 112
    }
604
605
    /**
606
     * Adds a resource to the repository.
607
     *
608
     * @param string                          $path     The Puli path to add the
609
     *                                                  resource at.
610
     * @param FilesystemResource|LinkResource $resource The resource to add.
611
     */
612 340
    private function addResource($path, $resource)
613
    {
614 340
        if (!$resource instanceof FilesystemResource && !$resource instanceof LinkResource) {
615 4
            throw new UnsupportedResourceException(sprintf(
616
                'The %s only supports adding FilesystemResource and '.
617 4
                'LinkedResource instances. Got: %s',
618
                // Get the short class name
619 4
                $this->getShortClassName(get_class($this)),
620 4
                $this->getShortClassName(get_class($resource))
621
            ));
622
        }
623
624
        // Don't modify resources attached to other repositories
625 336
        if ($resource->isAttached()) {
626 4
            $resource = clone $resource;
627
        }
628
629 336
        if ($resource instanceof LinkResource) {
630 8
            $resource->attachTo($this, $path);
631
632 8
            $this->insertReference($path, '@'.$resource->getTargetPath());
633
634 8
            $this->storeVersion($resource);
635
        } else {
636
            // Extension point for the optimized repository
637 336
            $this->addFilesystemResource($path, $resource);
638
        }
639 336
    }
640
641
    /**
642
     * Returns the short name of a fully-qualified class name.
643
     *
644
     * @param string $className The fully-qualified class name.
645
     *
646
     * @return string The short class name.
647
     */
648 4
    private function getShortClassName($className)
649
    {
650 4
        if (false !== ($pos = strrpos($className, '\\'))) {
651 4
            return substr($className, $pos + 1);
652
        }
653
654 4
        return $className;
655
    }
656
}
657