Completed
Push — master ( 3ef236...d81c02 )
by Mathieu
13:20
created

AbstractModel::postSave()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Charcoal\Model;
4
5
use \DateTimeInterface;
6
use \Exception;
7
use \InvalidArgumentException;
8
9
use \PDO;
10
11
// Dependencies from PSR-3 (Logger)
12
use \Psr\Log\LoggerAwareInterface;
13
use \Psr\Log\LoggerAwareTrait;
14
use \Psr\Log\NullLogger;
15
16
use \Pimple\Container;
17
18
// Dependency from 'charcoal-config'
19
use \Charcoal\Config\AbstractEntity;
20
21
// Dependencies from 'charcoal-view'
22
use \Charcoal\View\GenericView;
23
use \Charcoal\View\ViewableInterface;
24
use \Charcoal\View\ViewableTrait;
25
26
// Dependencies from 'charcoal-property'
27
use \Charcoal\Property\DescribablePropertyInterface;
28
use \Charcoal\Property\DescribablePropertyTrait;
29
30
// Intra-module ('charcoal-core') dependencies
31
use \Charcoal\Model\DescribableInterface;
32
use \Charcoal\Model\DescribableTrait;
33
use \Charcoal\Source\SourceFactory;
34
use \Charcoal\Source\StorableInterface;
35
use \Charcoal\Source\StorableTrait;
36
use \Charcoal\Validator\ValidatableInterface;
37
use \Charcoal\Validator\ValidatableTrait;
38
39
// Local namespace dependencies
40
use \Charcoal\Model\ModelInterface;
41
use \Charcoal\Model\ModelMetadata;
42
use \Charcoal\Model\ModelValidator;
43
44
/**
45
 * An abstract class that implements most of `ModelInterface`.
46
 *
47
 * In addition to `ModelInterface`, the abstract model implements the following interfaces:
48
 *
49
 * - `DescribableInterface`
50
 * - `StorableInterface
51
 * - `ValidatableInterface`
52
 * - `ViewableInterface`.
53
 *
54
 * Those interfaces
55
 * are implemented (in parts, at least) with the `DescribableTrait`, `StorableTrait`,
56
 * `ValidatableTrait` and the `ViewableTrait`.
57
 *
58
 * The `JsonSerializable` interface is fully provided by the `DescribableTrait`.
59
 */
60
abstract class AbstractModel extends AbstractEntity implements
61
    ModelInterface,
62
    DescribableInterface,
63
    DescribablePropertyInterface,
64
    LoggerAwareInterface,
65
    StorableInterface,
66
    ValidatableInterface,
67
    ViewableInterface
68
{
69
    use LoggerAwareTrait;
70
    use DescribableTrait;
71
    use DescribablePropertyTrait;
72
    use StorableTrait;
73
    use ValidatableTrait;
74
    use ViewableTrait;
75
76
    const DEFAULT_SOURCE_TYPE = 'database';
77
78
    /**
79
     * @param array $data Dependencies.
80
     */
81
    public function __construct(array $data = null)
82
    {
83
        // LoggerAwareInterface dependencies
84
        $this->setLogger($data['logger']);
85
86
        // Optional DescribableInterface dependencies
87
        if (isset($data['property_factory'])) {
88
            $this->setPropertyFactory($data['property_factory']);
89
        }
90
        if (isset($data['metadata'])) {
91
            $this->setMetadata($data['metadata']);
92
        }
93
        if (isset($data['metadata_loader'])) {
94
            $this->setMetadataLoader($data['metadata_loader']);
95
        }
96
97
        // Optional StorableInterface dependencies
98
        if (isset($data['source'])) {
99
             $this->setSource($data['source']);
100
        }
101
        if (isset($data['source_factory'])) {
102
            $this->setSourceFactory($data['source_factory']);
103
        }
104
105
        // Optional ViewableInterface dependencies
106
        if (isset($data['view'])) {
107
            $this->setView($data['view']);
108
        }
109
110
        if (isset($data['container'])) {
111
            $this->setDependencies($data['container']);
112
        }
113
    }
114
115
    /**
116
     * Inject dependencies from a DI Container.
117
     *
118
     * @param Container $container A dependencies container instance.
119
     * @return void
120
     */
121
    public function setDependencies(Container $container)
0 ignored issues
show
Unused Code introduced by
The parameter $container is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
122
    {
123
        // This method is a stub. Reimplement in children method
124
    }
125
126
    /**
127
     * Set the object's ID from an associative array map (or any other Traversable).
128
     *
129
     * Useful for setting the object ID before the rest of the object's data.
130
     *
131
     * @param array|\Traversable $data The object data.
132
     * @return array|\Traversable The object data without the pre-set ID.
133
     */
134
    protected function setIdFromData($data)
135
    {
136
        $key = $this->key();
137
        if (isset($data[$key])) {
138
            $this->setId($data[$key]);
139
            unset($data[$key]);
140
        }
141
142
        return $data;
143
    }
144
145
    /**
146
     * Sets the object data, from an associative array map (or any other Traversable).
147
     *
148
     * @param array|\Traversable $data The entity data. Will call setters.
149
     * @return AbstractModel Chainable
150
     */
151
    public function setData($data)
152
    {
153
        $data = $this->setIdFromData($data);
154
155
        return parent::setData($data);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return parent::setData($data); (Charcoal\Config\EntityInterface) is incompatible with the return type declared by the interface Charcoal\Model\ModelInterface::setData of type Charcoal\Model\ModelInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
156
    }
157
158
    /**
159
     * Return the object data as an array.
160
     *
161
     * @param array $propertyFilters Optional. Property filter.
162
     * @return array
163
     */
164
    public function data(array $propertyFilters = null)
165
    {
166
        $data = [];
167
        $properties = $this->properties($propertyFilters);
0 ignored issues
show
Unused Code introduced by
The call to AbstractModel::properties() has too many arguments starting with $propertyFilters.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
168
        foreach ($properties as $propertyIdent => $property) {
169
            // Ensure objects are properly encoded.
170
            $v = $this->propertyValue($propertyIdent);
171
            $v = $this->serializedValue($v);
172
            $data[$propertyIdent] = $v;
173
        }
174
        return $data;
175
    }
176
177
    /**
178
     * @param mixed $val The value to serialize.
179
     * @return mixed
180
     */
181
    private function serializedValue($val)
182
    {
183
        if (is_scalar($val)) {
184
            return $val;
185
        } elseif ($val instanceof DateTimeInterface) {
186
            return $val->format('Y-m-d H:i:s');
187
        } else {
188
            return json_decode(json_encode($val), true);
189
        }
190
    }
191
192
    /**
193
     * Override's `\Charcoal\Config\AbstractEntity`'s `setData` method to take properties into consideration.
194
     *
195
     * Also add a special case, to merge values for l10n properties.
196
     *
197
     * @param array|\Traversable $data The data to merge.
198
     * @return EntityInterface Chainable
199
     * @see \Charcoal\Config\AbstractEntity::offsetSet()
200
     */
201
    public function mergeData($data)
202
    {
203
        $data = $this->setIdFromData($data);
204
205
        foreach ($data as $propIdent => $val) {
206
            if (!$this->hasProperty($propIdent)) {
207
                $this->logger->warning(
208
                    sprintf('Cannot set property "%s" on object; not defined in metadata.', $propIdent)
209
                );
210
                continue;
211
            }
212
            $property = $this->p($propIdent);
213
            if ($property->l10n() && is_array($val)) {
0 ignored issues
show
Bug introduced by
The method l10n does only exist in Charcoal\Property\PropertyInterface, but not in Generator.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
214
                $currentValue = json_decode(json_encode($this[$propIdent]), true);
215
                if (is_array($currentValue)) {
216
                    $this[$propIdent] = array_merge($currentValue, $val);
217
                } else {
218
                    $this[$propIdent] = $val;
219
                }
220
            } else {
221
                $this[$propIdent] = $val;
222
            }
223
        }
224
225
        return $this;
226
    }
227
228
    /**
229
     * @return array
230
     */
231
    public function defaultData()
232
    {
233
        $metadata = $this->metadata();
234
        return $metadata->defaultData();
235
    }
236
237
    /**
238
     * Sets the data
239
     *
240
     * This function takes a 1-dimensional array and fill the object with its value.
241
     *
242
     * @param array $flatData The data, as a flat (1-dimension) array.
243
     * @return AbstractModel Chainable
244
     */
245
    public function setFlatData(array $flatData)
246
    {
247
        $flatData = $this->setIdFromData($flatData);
248
249
        $data = [];
250
        $properties = $this->properties();
251
        foreach ($properties as $propertyIdent => $property) {
252
            $fields = $property->fields(null);
253
254
            foreach ($fields as $k => $f) {
255
                if (is_string($k)) {
256
                    $f_id = $f->ident();
257
                    $key = str_replace($propertyIdent.'_', '', $f_id);
258 View Code Duplication
                    if (isset($flatData[$f_id])) {
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...
259
                        $data[$propertyIdent][$key] = $flatData[$f_id];
260
                        unset($flatData[$f_id]);
261
                    }
262
                } else {
263
                    $f_id = $f->ident();
264 View Code Duplication
                    if (isset($flatData[$f_id])) {
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...
265
                        $data[$propertyIdent] = $flatData[$f_id];
266
                        unset($flatData[$f_id]);
267
                    }
268
                }
269
            }
270
        }
271
272
        $this->setData($data);
273
274
        // Set remaining (non-property) data.
275
        if (!empty($flatData)) {
276
            $this->setData($flatData);
277
        }
278
279
        return $this;
280
    }
281
282
    /**
283
     * @return array
284
     * @todo Implement retrieval of flattened data
285
     */
286
    public function flatData()
287
    {
288
        return [];
289
    }
290
291
    /**
292
     * @param string $propertyIdent The property ident to get the value from.
293
     * @return mixed
294
     */
295
    public function propertyValue($propertyIdent)
296
    {
297
        $getter = $this->getter($propertyIdent);
298
        $func   = [ $this, $getter ];
299
300
        if (is_callable($func)) {
301
            return call_user_func($func);
302
        } elseif (isset($this->{$propertyIdent})) {
303
            return $this->{$propertyIdent};
304
        }
305
306
        return null;
307
    }
308
309
    /**
310
     * @param array $properties Optional array of properties to save. If null, use all object's properties.
311
     * @return boolean
312
     */
313
    public function saveProperties(array $properties = null)
314
    {
315
        if ($properties === null) {
316
            $properties = array_keys($this->metadata()->properties());
317
        }
318
319
        foreach ($properties as $propertyIdent) {
320
            $p = $this->p($propertyIdent);
321
            $v = $p->save($this->propertyValue($propertyIdent));
0 ignored issues
show
Bug introduced by
The method save does only exist in Charcoal\Property\PropertyInterface, but not in Generator.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
322
323
            if ($v === null) {
324
                continue;
325
            }
326
327
            $this[$propertyIdent] = $v;
328
        }
329
330
        return true;
331
    }
332
333
    /**
334
     * Load an object from the database from its l10n key $key.
335
     * Also retrieve and return the actual language that matched.
336
     *
337
     * @param string $key   Key pointing a column's l10n base ident.
338
     * @param mixed  $value Value to search in all languages.
339
     * @param array  $langs List of languages (code, ex: "en") to check into.
340
     * @throws Exception If the PDO query fails.
341
     * @return string The matching language.
342
     */
343
    public function loadFromL10n($key, $value, array $langs)
344
    {
345
        $switch = [];
346
        $where = [];
347
        foreach ($langs as $lang) {
348
            $switch[] = 'when `'.$key.'_'.$lang.'` = :ident then \''.$lang.'\'';
349
            $where[] = '`'.$key.'_'.$lang.'` = :ident';
350
        }
351
352
        $q = '
353
            SELECT
354
                *,
355
                (case
356
                    '.implode("\n", $switch).'
357
                end) as _lang
358
            FROM
359
               `'.$this->source()->table().'`
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Charcoal\Source\SourceInterface as the method table() does only exist in the following implementations of said interface: Charcoal\Source\DatabaseSource.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
360
            WHERE
361
                ('.implode(' OR ', $where).')
362
            LIMIT
363
               1';
364
365
        $binds = [
366
            'ident' => $value
367
        ];
368
369
        $sth = $this->source()->dbQuery($q, $binds);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Charcoal\Source\SourceInterface as the method dbQuery() does only exist in the following implementations of said interface: Charcoal\Source\DatabaseSource.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
370
        if ($sth === false) {
371
            throw new Exception('Error');
372
        }
373
374
        $data = $sth->fetch(PDO::FETCH_ASSOC);
375
        $lang = $data['_lang'];
376
        unset($data['_lang']);
377
378
        if ($data) {
379
            $this->setFlatData($data);
380
        }
381
382
        return $lang;
383
    }
384
385
    /**
386
     * Save the object's current state to storage.
387
     *
388
     * Overrides default StorableTrait save() method to also save properties.
389
     *
390
     * @see    Charcoal\Source\StorableTrait::save() For the "create" event.
391
     * @return boolean
392
     * @todo   Enable model validation.
393
     */
394
    public function save()
395
    {
396
        $pre = $this->preSave();
397
        if ($pre === false) {
398
            return false;
399
        }
400
401
        // Disabled: Invalid models can not be saved.
402
        if (!!false) {
403
            $valid = $this->validate();
404
            if ($valid === false) {
405
                return false;
406
            }
407
        }
408
409
        $ret = $this->source()->saveItem($this);
410
        if ($ret === false) {
411
            return false;
412
        } else {
413
            $this->setId($ret);
414
        }
415
        $this->postSave();
0 ignored issues
show
Unused Code introduced by
The call to the method Charcoal\Model\AbstractModel::postSave() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
416
        return $ret;
417
    }
418
419
    /**
420
     * StorableTrait > preSave(). Save hook called before saving the model.
421
     *
422
     * @return boolean
423
     */
424
    protected function preSave()
425
    {
426
        return $this->saveProperties();
427
    }
428
429
    /**
430
     * StorableTrait > preUpdate(). Update hook called before updating the model.
431
     *
432
     * @param string[] $properties Optional. The properties to update.
433
     * @return boolean
434
     */
435
    protected function preUpdate(array $properties = null)
436
    {
437
        // $properties is unused for now
438
        unset($properties);
439
        return $this->saveProperties();
440
    }
441
442
    /**
443
     * DescribableTrait > createMetadata().
444
     *
445
     * @return MetadataInterface
446
     */
447
    protected function createMetadata()
448
    {
449
        return new ModelMetadata();
450
    }
451
452
    /**
453
     * StorableInterface > createSource()
454
     *
455
     * @throws Exception If the metadata source can not be found.
456
     * @return SourceInterface
457
     */
458
    protected function createSource()
459
    {
460
        $metadata = $this->metadata();
461
        $defaultSource = $metadata->defaultSource();
0 ignored issues
show
Bug introduced by
The method defaultSource() does not exist on Charcoal\Model\MetadataInterface. Did you maybe mean defaults()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
462
        $sourceConfig = $metadata->source($defaultSource);
463
464
        if (!$sourceConfig) {
465
            throw new Exception(
466
                sprintf('Can not create %s source: invalid metadata.', get_class($this))
467
            );
468
        }
469
470
        $sourceType = isset($sourceConfig['type']) ? $sourceConfig['type'] : self::DEFAULT_SOURCE_TYPE;
471
        $sourceFactory = $this->sourceFactory();
472
        $source = $sourceFactory->create($sourceType);
473
        $source->setModel($this);
474
475
        $source->setData($sourceConfig);
476
477
        return $source;
478
    }
479
480
    /**
481
     * ValidatableInterface > create_validator().
482
     *
483
     * @param array $data Optional.
484
     * @return ValidatorInterface
485
     */
486
    protected function createValidator(array $data = null)
487
    {
488
        $validator = new ModelValidator($this);
489
        if ($data !== null) {
490
            $validator->setData($data);
0 ignored issues
show
Bug introduced by
The method setData() does not seem to exist on object<Charcoal\Model\ModelValidator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
491
        }
492
        return $validator;
493
    }
494
495
    /**
496
     * @param array $data Optional. View data.
497
     * @return ViewInterface
498
     */
499
    public function createView(array $data = null)
500
    {
501
        $this->logger->warning('Obsolete method createView called.');
502
        $view = new GenericView([
503
            'logger'=>$this->logger
504
        ]);
505
        if ($data !== null) {
506
            $view->setData($data);
0 ignored issues
show
Bug introduced by
The method setData() does not seem to exist on object<Charcoal\View\GenericView>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
507
        }
508
        return $view;
509
    }
510
511
    /**
512
     * Convert the current class name in "type-ident" format.
513
     *
514
     * @return string
515
     */
516
    public function objType()
517
    {
518
        $ident = preg_replace('/([a-z])([A-Z])/', '$1-$2', get_class($this));
519
        $objType = strtolower(str_replace('\\', '/', $ident));
520
        return $objType;
521
    }
522
}
523