Completed
Pull Request — master (#157)
by
unknown
01:35
created

DriverEntityBase::id()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Drupal\Driver\Wrapper\Entity;
4
5
use Drupal\Driver\Exception\Exception;
6
use Drupal\Driver\Plugin\DriverEntityPluginInterface;
7
use Drupal\Driver\Plugin\DriverEntityPluginManager;
8
use Drupal\Driver\Plugin\DriverPluginManagerInterface;
9
use Drupal\Driver\Wrapper\Field\DriverFieldInterface;
10
use Drupal\Driver\Wrapper\Field\DriverFieldDrupal8;
11
use Drupal\Component\Utility\DriverNameMatcher;
12
13
/**
14
 * A base class for a Driver entity object that holds information about a
15
 * Drupal entity.
16
 */
17
abstract class DriverEntityBase implements DriverEntityWrapperInterface
18
{
19
20
  /**
21
   * Entity type's machine name.
22
   *
23
   * @var string
24
   */
25
    protected $type;
26
27
  /**
28
   * Entity bundle's machine name.
29
   *
30
   * @var string
31
   */
32
    protected $bundle;
33
34
  /**
35
   * A driver entity plugin manager object.
36
   *
37
   * @var \Drupal\Driver\Plugin\DriverPluginManagerInterface
38
   */
39
    protected $entityPluginManager;
40
41
  /**
42
   * A driver field plugin manager object.
43
   *
44
   * @var \Drupal\Driver\Plugin\DriverPluginManagerInterface
45
   */
46
    protected $fieldPluginManager;
47
48
  /**
49
   * The directory to search for additional project-specific driver plugins.
50
   *
51
   * @var string
52
   */
53
    protected $projectPluginRoot;
54
55
  /**
56
   * The preliminary bundle-agnostic matched driver entity plugin.
57
   *
58
   * @var \Drupal\Driver\Plugin\DriverEntityPluginInterface
59
   */
60
    protected $provisionalPlugin;
61
62
  /**
63
   * The final bundle-specific matched driver entity plugin.
64
   *
65
   * @var \Drupal\Driver\Plugin\DriverEntityPluginInterface
66
   */
67
    protected $finalPlugin;
68
69
  /**
70
   * Constructs a driver entity wrapper object.
71
   *
72
   * @param string $type
73
   *   Machine name of the entity type.
74
   * @param string $bundle
75
   *   (optional) Machine name of the entity bundle.
76
   * @param \Drupal\Driver\Plugin\DriverPluginManagerInterface $entityPluginManager
77
   *   (optional) An driver entity plugin manager.
78
   * @param \Drupal\Driver\Plugin\DriverPluginManagerInterface $entityPluginManager
79
   *   (optional) An driver entity plugin manager.
80
   * @param string $projectPluginRoot
81
   *   The directory to search for additional project-specific driver plugins .
82
   */
83
    public function __construct(
84
        $type,
85
        $bundle = null,
86
        DriverPluginManagerInterface $entityPluginManager = null,
87
        DriverPluginManagerInterface $fieldPluginManager = null,
88
        $projectPluginRoot = null
89
    ) {
90
91
        $this->setEntityPluginManager($entityPluginManager, $projectPluginRoot);
0 ignored issues
show
Bug introduced by
It seems like $entityPluginManager defined by parameter $entityPluginManager on line 86 can be null; however, Drupal\Driver\Wrapper\En...etEntityPluginManager() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
92
        $this->fieldPluginManager = $fieldPluginManager;
93
        $this->projectPluginRoot = $projectPluginRoot;
94
        $this->setType($type);
0 ignored issues
show
Documentation Bug introduced by
The method setType does not exist on object<Drupal\Driver\Wra...ntity\DriverEntityBase>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
95
96
        // Provisional plugin set before bundle as it's used in bundle validation.
97
        $this->setProvisionalPlugin($this->getPlugin());
98
99
        if (!empty($bundle)) {
100
            $this->setBundle($bundle);
101
            // Only set final plugin if bundle is known.
102
            $this->setFinalPlugin($this->getPlugin());
103
        }
104
    }
105
106
  /**
107
   * {@inheritdoc}
108
   */
109
    public function __call($name, $arguments)
110
    {
111
        // Forward unknown calls to the plugin.
112
        if ($this->hasFinalPlugin()) {
113
            return call_user_func_array([
114
            $this->getFinalPlugin(),
115
            $name,
116
            ], $arguments);
117
        }
118
        throw new \Exception("Method '$name' unknown on Driver entity wrapper and plugin not yet available.");
119
    }
120
121
  /**
122
   * {@inheritdoc}
123
   */
124
    public function __get($name)
125
    {
126
        // Forward unknown calls to the plugin.
127
        if ($this->hasFinalPlugin()) {
128
            return $this->getFinalPlugin()->$name;
129
        }
130
        throw new \Exception("Property '$name' unknown on Driver entity wrapper and plugin not yet available.");
131
    }
132
133
  /**
134
   * {@inheritdoc}
135
   */
136
    public function bundle()
137
    {
138
        // Default to entity type as bundle. This is used when the bundle is not
139
        // yet known, for example during DriverField processing of the bundle field.
140
        // If no bundle is supplied, this default is permanently set as the bundle
141
        // later by getFinalPlugin().
142
        if (is_null($this->bundle)) {
143
            return $this->getEntityTypeId();
144
        }
145
        return $this->bundle;
146
    }
147
148
  /**
149
   * {@inheritdoc}
150
   */
151
    public function delete()
152
    {
153
        $this->getEntity()->delete();
154
        return $this;
155
    }
156
157
  /**
158
   * {@inheritdoc}
159
   */
160
    public function getEntity()
161
    {
162
        return $this->getFinalPlugin()->getEntity();
163
    }
164
165
  /**
166
   * Get an entity plugin.
167
   *
168
   * This may or may not be bundle-specific, depending on whether the bundle is
169
   * known at this point.
170
   *
171
   * @return \Drupal\Driver\Plugin\DriverEntityPluginInterface
172
   *   An instantiated driver entity plugin object.
173
   */
174
    protected function getPlugin()
175
    {
176
        if (is_null($this->getEntityTypeId())) {
177
            throw new \Exception("Entity type is required to discover matched plugins.");
178
        }
179
180
        // Build the basic config for the plugin.
181
        $config = [
182
        'type' => $this->getEntityTypeId(),
183
        'bundle' => $this->bundle(),
184
        'projectPluginRoot' => $this->projectPluginRoot,
185
        'fieldPluginManager' => $this->fieldPluginManager,
186
        ];
187
188
        // Discover, instantiate and store plugin.
189
        $manager = $this->getFinalPluginManager();
190
        // Get only the highest priority matched plugin.
191
        $matchedDefinitions = $manager->getMatchedDefinitions($this);
192
        if (count($matchedDefinitions) === 0) {
193
            throw new \Exception("No matching DriverEntity plugins found.");
194
        }
195
        $topDefinition = $matchedDefinitions[0];
196
        $plugin = $manager->createInstance($topDefinition['id'], $config);
197
        if (!($plugin instanceof DriverEntityPluginInterface)) {
198
          throw new \Exception("DriverEntity plugin '" . $topDefinition['id'] . "' failed to instantiate.");
199
        }
200
        return $plugin;
201
    }
202
203
  /**
204
   * {@inheritdoc}
205
   */
206
    public function getFinalPlugin()
207
    {
208
        if (!$this->hasFinalPlugin()) {
209
            // Commit to default bundle if still using that.
210
            if ($this->isBundleMissing()) {
211
                $this->setBundle($this->bundle());
212
            }
213
            $this->setFinalPlugin($this->getPlugin());
214
        }
215
        if (!$this->hasFinalPlugin()) {
216
            throw new \Exception("Failed to discover or instantiate bundle-specific plugin.");
217
        }
218
219
        return $this->finalPlugin;
220
    }
221
222
  /**
223
   * {@inheritdoc}
224
   */
225
    public function getEntityTypeId()
226
    {
227
        return $this->type;
228
    }
229
230
  /**
231
   * {@inheritdoc}
232
   */
233
    public function id()
234
    {
235
        return $this->getFinalPlugin()->id();
236
    }
237
238
  /**
239
   * {@inheritdoc}
240
   */
241
    public function isNew()
242
    {
243
        if ($this->hasFinalPlugin()) {
244
            return $this->getFinalPlugin()->isNew();
245
        } else {
246
            return true;
247
        }
248
    }
249
250
  /**
251
   * {@inheritdoc}
252
   */
253
    public function label()
254
    {
255
        return $this->getFinalPlugin()->label();
256
    }
257
258
  /**
259
   * {@inheritdoc}
260
   */
261
    public function load($entityId)
262
    {
263
        if (!is_string($entityId) && !is_integer($entityId)) {
264
            throw new \Exception("Entity ID to be loaded must be string or integer.");
265
        }
266
        if ($this->hasFinalPlugin()) {
267
            $this->getFinalPlugin()->load($entityId);
268
        } else {
269
            $entity = $this->getProvisionalPlugin()->load($entityId);
270
            if ($this->isBundleMissing()) {
271
                $this->setBundle($entity->bundle());
272
            }
273
            $this->getFinalPlugin()->load($entityId);
274
        }
275
        return $this;
276
    }
277
278
  /**
279
   * {@inheritdoc}
280
   */
281
    public function reload()
282
    {
283
        $this->getFinalPlugin()->reload();
284
        return $this;
285
    }
286
287
  /**
288
   * {@inheritdoc}
289
   */
290
    public function save()
291
    {
292
        $this->getFinalPlugin()->save();
293
        return $this;
294
    }
295
296
  /**
297
   * {@inheritdoc}
298
   */
299
    public function set($identifier, $field)
300
    {
301
        $this->setFields([$identifier => $field]);
302
        return $this;
303
    }
304
305
  /**
306
   * {@inheritdoc}
307
   */
308
    public function setBundle($identifier)
309
    {
310
        if ($this->hasFinalPlugin()) {
311
            throw new \Exception("Cannot change entity bundle after final plugin discovery has taken place");
312
        }
313
        $this->bundle = $identifier;
314
        return $this;
315
    }
316
317
  /**
318
   * {@inheritdoc}
319
   */
320
    public function setFinalPlugin($plugin)
321
    {
322
        if ($this->hasFinalPlugin()) {
323
            throw new \Exception("Cannot change entity plugin without risk of data loss.");
324
        }
325
        $this->finalPlugin = $plugin;
326
        return $this;
327
    }
328
329
  /**
330
   * {@inheritdoc}
331
   */
332
    public function setFields($fields)
333
    {
334
        // We don't try to identify all the fields here - or even check that they
335
        // are all identifiable - because we want to pass everything on to the
336
        // plugin as raw as possible. But we must extract the bundle field (if the
337
        // bundle is not already known) as the bundle is used in plugin discovery.
338
        if ($this->isBundleMissing()) {
339
            $fields = $this->extractBundleField($fields);
340
        }
341
        $this->getFinalPlugin()->setFields($fields);
342
        return $this;
343
    }
344
345
  /**
346
   * {@inheritdoc}
347
   */
348
    public function url($rel = 'canonical', $options = [])
349
    {
350
        return $this->getFinalPlugin()->url($rel, $options);
351
    }
352
353
  /**
354
   * {@inheritdoc}
355
   */
356
    public function tearDown()
357
    {
358
        return $this->getFinalPlugin()->tearDown();
359
    }
360
361
  /**
362
   * Extract the bundle field from a set of fields, and store the bundle.
363
   *
364
   * @param array $fields
365
   *   An array of inputs that represent fields.
366
   *
367
   * @return array
368
   *   An array of inputs that represent fields, without the bundle field.
369
   */
370
    protected function extractBundleField($fields)
371
    {
372
        $bundleKey = $this->getProvisionalPlugin()->getBundleKey();
373
        // If this is a bundle-less entity, there's nothing to do.
374
        if (empty($bundleKey)) {
375
            return $fields;
376
        } else {
377
            // BC support for identifying the bundle by the name 'step_bundle'.
378
            if (isset($fields['step_bundle'])) {
379
              $fields[$bundleKey] = $fields['step_bundle'];
380
              unset($fields['step_bundle']);
381
            }
382
            // Find the bundle field, if it is present among the fields.
383
            $bundleKeyLabels = $this->getProvisionalPlugin()->getBundleKeyLabels();
384
            $candidates = [];
385
            foreach ($bundleKeyLabels as $label) {
386
                $candidates[$label] = $bundleKey;
387
            }
388
            $matcher = new DriverNameMatcher($candidates);
389
            $bundleFieldMatch = $matcher->identifySet($fields);
390
391
            // If the bundle field has been found, process it and set the bundle.
392
            // Don't throw an exception if none if found, as it is possible to have
393
            // entities (like entity_test) that have a bundle key but don't require
394
            // a bundle to be set.
395
            if (count($bundleFieldMatch) !== 0) {
396
                if ($bundleFieldMatch[$bundleKey] instanceof DriverFieldInterface) {
397
                    $bundleField = $bundleFieldMatch[$bundleKey];
398
                } else {
399
                    $bundleField = $this->getNewDriverField($bundleKey, $bundleFieldMatch[$bundleKey]);
400
                }
401
                $this->setBundle($bundleField->getProcessedValues()[0]['target_id']);
402
            }
403
404
            // Return the other fields (with the bundle field now removed).
405
            return $matcher->getUnmatchedTargets();
406
        }
407
    }
408
409
  /**
410
   * Get the driver entity plugin manager.
411
   *
412
   * @return \Drupal\Driver\Plugin\DriverPluginManagerInterface
413
   */
414
    protected function getFinalPluginManager()
415
    {
416
        return $this->entityPluginManager;
417
    }
418
419
  /**
420
   * Get a new driver field with values.
421
   *
422
   * @param string $fieldName
423
   *   A string identifying an entity field.
424
   * @param string|array $values
425
   *   An input that can be transformed into Driver field values.
426
   */
427
    protected function getNewDriverField($fieldName, $values)
428
    {
429
        $driverFieldVersionClass = "Drupal\Driver\Wrapper\Field\DriverFieldDrupal" . $this->version;
0 ignored issues
show
Documentation introduced by
The property version does not exist on object<Drupal\Driver\Wra...ntity\DriverEntityBase>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
430
        $field = new $driverFieldVersionClass(
431
        $values,
432
        $fieldName,
433
        $this->getEntityTypeId(),
434
        $this->bundle(),
435
        $this->projectPluginRoot,
436
        $this->fieldPluginManager
437
        );
438
        return $field;
439
    }
440
441
  /**
442
   * Gets the provisional entity plugin.
443
   *
444
   * @return \Drupal\Driver\Plugin\DriverEntityPluginInterface
445
   */
446
    protected function getProvisionalPlugin()
447
    {
448
        if ($this->hasFinalPlugin()) {
449
            return $this->getFinalPlugin();
450
        }
451
        return $this->provisionalPlugin;
452
    }
453
454
  /**
455
   * Whether a matched plugin has yet been discovered and stored.
456
   *
457
   * @return boolean
458
   */
459
    protected function hasFinalPlugin()
460
    {
461
        $hasFinalPlugin = !is_null($this->finalPlugin);
462
        if ($hasFinalPlugin) {
463
            $hasFinalPlugin = $this->finalPlugin instanceof DriverEntityPluginInterface;
464
        }
465
        return $hasFinalPlugin;
466
    }
467
468
  /**
469
   * Whether a bundle has been set yet.
470
   *
471
   * @return boolean
472
   */
473
    protected function isBundleMissing()
474
    {
475
        $supportsBundles = $this->getProvisionalPlugin()->supportsBundles();
476
        return ($supportsBundles && is_null($this->bundle));
477
    }
478
479
  /**
480
   * Set the driver entity plugin manager.
481
   *
482
   * @param \Drupal\Driver\Plugin\DriverPluginManagerInterface $manager
483
   *   The driver entity plugin manager.
484
   * @param string $projectPluginRoot
485
   *   The directory to search for additional project-specific driver plugins.
486
   */
487 View Code Duplication
    protected function setEntityPluginManager($manager, $projectPluginRoot)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
488
    {
489
        if (!($manager instanceof DriverPluginManagerInterface)) {
490
            $manager = new DriverEntityPluginManager($this->namespaces, $this->cache_backend, $this->module_handler, $this->version, $projectPluginRoot);
0 ignored issues
show
Documentation introduced by
The property namespaces does not exist on object<Drupal\Driver\Wra...ntity\DriverEntityBase>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property cache_backend does not exist on object<Drupal\Driver\Wra...ntity\DriverEntityBase>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property module_handler does not exist on object<Drupal\Driver\Wra...ntity\DriverEntityBase>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property version does not exist on object<Drupal\Driver\Wra...ntity\DriverEntityBase>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
491
        }
492
        $this->entityPluginManager = $manager;
493
    }
494
495
  /**
496
   * Sets the provisional entity plugin.
497
   *
498
   * @param \Drupal\Driver\Plugin\DriverEntityPluginInterface
499
   */
500
    protected function setProvisionalPlugin($plugin)
501
    {
502
        $this->provisionalPlugin = $plugin;
503
    }
504
}
505