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

DriverEntityBase::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 9.2
c 0
b 0
f 0
cc 2
eloc 14
nc 2
nop 5
1
<?php
2
3
namespace Drupal\Driver\Wrapper\Entity;
4
5
use Drupal\Driver\Plugin\DriverEntityPluginInterface;
6
use Drupal\Driver\Plugin\DriverEntityPluginManager;
7
use Drupal\Driver\Plugin\DriverPluginManagerInterface;
8
use Drupal\Driver\Wrapper\Field\DriverFieldInterface;
9
use Drupal\Driver\Plugin\DriverNameMatcher;
10
11
/**
12
 * A base class for a Driver entity object that wraps a Drupal entity.
13
 */
14
abstract class DriverEntityBase implements DriverEntityWrapperInterface {
15
16
  /**
17
   * Entity type's machine name.
18
   *
19
   * @var string
20
   */
21
  protected $type;
22
23
  /**
24
   * Entity bundle's machine name.
25
   *
26
   * @var string
27
   */
28
  protected $bundle;
29
30
  /**
31
   * A driver entity plugin manager object.
32
   *
33
   * @var \Drupal\Driver\Plugin\DriverPluginManagerInterface
34
   */
35
  protected $entityPluginManager;
36
37
  /**
38
   * A driver field plugin manager object.
39
   *
40
   * @var \Drupal\Driver\Plugin\DriverPluginManagerInterface
41
   */
42
  protected $fieldPluginManager;
43
44
  /**
45
   * The directory to search for additional project-specific driver plugins.
46
   *
47
   * @var string
48
   */
49
  protected $projectPluginRoot;
50
51
  /**
52
   * The preliminary bundle-agnostic matched driver entity plugin.
53
   *
54
   * @var \Drupal\Driver\Plugin\DriverEntityPluginInterface
55
   */
56
  protected $provisionalPlugin;
57
58
  /**
59
   * The final bundle-specific matched driver entity plugin.
60
   *
61
   * @var \Drupal\Driver\Plugin\DriverEntityPluginInterface
62
   */
63
  protected $finalPlugin;
64
65
  /**
66
   * Constructs a driver entity wrapper object.
67
   *
68
   * @param string $type
69
   *   Machine name of the entity type.
70
   * @param string $bundle
71
   *   (optional) Machine name of the entity bundle.
72
   * @param \Drupal\Driver\Plugin\DriverPluginManagerInterface $entityPluginManager
73
   *   (optional) An driver entity plugin manager.
74
   * @param \Drupal\Driver\Plugin\DriverPluginManagerInterface $fieldPluginManager
75
   *   (optional) An driver entity plugin manager.
76
   * @param string $projectPluginRoot
77
   *   The directory to search for additional project-specific driver plugins .
78
   */
79
  public function __construct(
80
        $type,
81
        $bundle = NULL,
82
        DriverPluginManagerInterface $entityPluginManager = NULL,
83
        DriverPluginManagerInterface $fieldPluginManager = NULL,
84
        $projectPluginRoot = NULL
85
    ) {
86
87
    $this->setEntityPluginManager($entityPluginManager, $projectPluginRoot);
88
    $this->fieldPluginManager = $fieldPluginManager;
89
    $this->projectPluginRoot = $projectPluginRoot;
90
    $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...
91
92
    // Provisional plugin set before bundle as it's used in bundle validation.
93
    $this->setProvisionalPlugin($this->getPlugin());
94
95
    if (!empty($bundle)) {
96
      $this->setBundle($bundle);
97
      // Only set final plugin if bundle is known.
98
      $this->setFinalPlugin($this->getPlugin());
99
    }
100
  }
101
102
  /**
103
   * {@inheritdoc}
104
   */
105
  public function __call($name, $arguments) {
106
    // Forward unknown calls to the plugin.
107
    if ($this->hasFinalPlugin()) {
108
      return call_user_func_array([
109
        $this->getFinalPlugin(),
110
        $name,
111
      ], $arguments);
112
    }
113
    throw new \Exception("Method '$name' unknown on Driver entity wrapper and plugin not yet available.");
114
  }
115
116
  /**
117
   * {@inheritdoc}
118
   */
119
  public function __get($name) {
120
    // Forward unknown calls to the plugin.
121
    if ($this->hasFinalPlugin()) {
122
      return $this->getFinalPlugin()->$name;
123
    }
124
    throw new \Exception("Property '$name' unknown on Driver entity wrapper and plugin not yet available.");
125
  }
126
127
  /**
128
   * {@inheritdoc}
129
   */
130
  public function bundle() {
131
    // Default to entity type as bundle. This is used when the bundle is not
132
    // yet known, for example during DriverField processing of the bundle field.
133
    // If no bundle is supplied, this default is permanently set as the bundle
134
    // later by getFinalPlugin().
135
    if (is_null($this->bundle)) {
136
      return $this->getEntityTypeId();
137
    }
138
    return $this->bundle;
139
  }
140
141
  /**
142
   * {@inheritdoc}
143
   */
144
  public function delete() {
145
    $this->getEntity()->delete();
146
    return $this;
147
  }
148
149
  /**
150
   * {@inheritdoc}
151
   */
152
  public function getEntity() {
153
    return $this->getFinalPlugin()->getEntity();
154
  }
155
156
  /**
157
   * Get an entity plugin.
158
   *
159
   * This may or may not be bundle-specific, depending on whether the bundle is
160
   * known at this point.
161
   *
162
   * @return \Drupal\Driver\Plugin\DriverEntityPluginInterface
163
   *   An instantiated driver entity plugin object.
164
   */
165
  protected function getPlugin() {
166
    if (is_null($this->getEntityTypeId())) {
167
      throw new \Exception("Entity type is required to discover matched plugins.");
168
    }
169
170
    // Build the basic config for the plugin.
171
    $config = [
172
      'type' => $this->getEntityTypeId(),
173
      'bundle' => $this->bundle(),
174
      'projectPluginRoot' => $this->projectPluginRoot,
175
      'fieldPluginManager' => $this->fieldPluginManager,
176
    ];
177
178
    // Discover, instantiate and store plugin.
179
    // Get only the highest priority matched plugin.
180
    $matchedDefinitions = $this->entityPluginManager->getMatchedDefinitions($this);
181
    if (count($matchedDefinitions) === 0) {
182
      throw new \Exception("No matching DriverEntity plugins found.");
183
    }
184
    $topDefinition = $matchedDefinitions[0];
185
    $plugin = $this->entityPluginManager->createInstance($topDefinition['id'], $config);
186
    if (!($plugin instanceof DriverEntityPluginInterface)) {
187
      throw new \Exception("DriverEntity plugin '" . $topDefinition['id'] . "' failed to instantiate.");
188
    }
189
    return $plugin;
190
  }
191
192
  /**
193
   * {@inheritdoc}
194
   */
195
  public function getFinalPlugin() {
196
    if (!$this->hasFinalPlugin()) {
197
      // Commit to default bundle if still using that.
198
      if ($this->isBundleMissing()) {
199
        $this->setBundle($this->bundle());
200
      }
201
      $this->setFinalPlugin($this->getPlugin());
202
    }
203
    if (!$this->hasFinalPlugin()) {
204
      throw new \Exception("Failed to discover or instantiate bundle-specific plugin.");
205
    }
206
207
    return $this->finalPlugin;
208
  }
209
210
  /**
211
   * {@inheritdoc}
212
   */
213
  public function getEntityTypeId() {
214
    return $this->type;
215
  }
216
217
  /**
218
   * {@inheritdoc}
219
   */
220
  public function id() {
221
    return $this->getFinalPlugin()->id();
222
  }
223
224
  /**
225
   * {@inheritdoc}
226
   */
227
  public function isNew() {
228
    if ($this->hasFinalPlugin()) {
229
      return $this->getFinalPlugin()->isNew();
230
    }
231
    else {
232
      return TRUE;
233
    }
234
  }
235
236
  /**
237
   * {@inheritdoc}
238
   */
239
  public function label() {
240
    return $this->getFinalPlugin()->label();
241
  }
242
243
  /**
244
   * {@inheritdoc}
245
   */
246
  public function load($entityId) {
247
    if (!is_string($entityId) && !is_int($entityId)) {
248
      throw new \Exception("Entity ID to be loaded must be string or integer.");
249
    }
250
    if ($this->hasFinalPlugin()) {
251
      $this->getFinalPlugin()->load($entityId);
252
    }
253
    else {
254
      $entity = $this->getProvisionalPlugin()->load($entityId);
255
      if ($this->isBundleMissing()) {
256
        $this->setBundle($entity->bundle());
257
      }
258
      $this->getFinalPlugin()->load($entityId);
259
    }
260
    return $this;
261
  }
262
263
  /**
264
   * {@inheritdoc}
265
   */
266
  public function reload() {
267
    $this->getFinalPlugin()->reload();
268
    return $this;
269
  }
270
271
  /**
272
   * {@inheritdoc}
273
   */
274
  public function save() {
275
    $this->getFinalPlugin()->save();
276
    return $this;
277
  }
278
279
  /**
280
   * {@inheritdoc}
281
   */
282
  public function set($identifier, $field) {
283
    $this->setFields([$identifier => $field]);
284
    return $this;
285
  }
286
287
  /**
288
   * {@inheritdoc}
289
   */
290
  public function setBundle($identifier) {
291
    if ($this->hasFinalPlugin()) {
292
      throw new \Exception("Cannot change entity bundle after final plugin discovery has taken place");
293
    }
294
    $this->bundle = $identifier;
295
    return $this;
296
  }
297
298
  /**
299
   * {@inheritdoc}
300
   */
301
  public function setFinalPlugin(DriverEntityPluginInterface $plugin) {
302
    if ($this->hasFinalPlugin()) {
303
      throw new \Exception("Cannot change entity plugin without risk of data loss.");
304
    }
305
    $this->finalPlugin = $plugin;
306
    return $this;
307
  }
308
309
  /**
310
   * {@inheritdoc}
311
   */
312
  public function setFields(array $fields) {
313
    // We don't try to identify all the fields here - or even check that they
314
    // are all identifiable - because we want to pass everything on to the
315
    // plugin as raw as possible. But we must extract the bundle field (if the
316
    // bundle is not already known) as the bundle is used in plugin discovery.
317
    if ($this->isBundleMissing()) {
318
      $fields = $this->extractBundleField($fields);
319
    }
320
    $this->getFinalPlugin()->setFields($fields);
321
    return $this;
322
  }
323
324
  /**
325
   * {@inheritdoc}
326
   */
327
  public function url($rel = 'canonical', array $options = []) {
328
    return $this->getFinalPlugin()->url($rel, $options);
329
  }
330
331
  /**
332
   * {@inheritdoc}
333
   */
334
  public function tearDown() {
335
    return $this->getFinalPlugin()->tearDown();
336
  }
337
338
  /**
339
   * Extract the bundle field from a set of fields, and store the bundle.
340
   *
341
   * @param array $fields
342
   *   An array of inputs that represent fields.
343
   *
344
   * @return array
345
   *   An array of inputs that represent fields, without the bundle field.
346
   */
347
  protected function extractBundleField(array $fields) {
348
    $bundleKey = $this->getProvisionalPlugin()->getBundleKey();
349
    // If this is a bundle-less entity, there's nothing to do.
350
    if (empty($bundleKey)) {
351
      return $fields;
352
    }
353
    else {
354
      // BC support for identifying the bundle by the name 'step_bundle'.
355
      if (isset($fields['step_bundle'])) {
356
        $fields[$bundleKey] = $fields['step_bundle'];
357
        unset($fields['step_bundle']);
358
      }
359
      // Find the bundle field, if it is present among the fields.
360
      $bundleKeyLabels = $this->getProvisionalPlugin()->getBundleKeyLabels();
361
      $candidates = [];
362
      foreach ($bundleKeyLabels as $label) {
363
        $candidates[$label] = $bundleKey;
364
      }
365
      $matcher = new DriverNameMatcher($candidates);
366
      $bundleFieldMatch = $matcher->identifySet($fields);
367
368
      // If the bundle field has been found, process it and set the bundle.
369
      // Don't throw an exception if none if found, as it is possible to have
370
      // entities (like entity_test) that have a bundle key but don't require
371
      // a bundle to be set.
372
      if (count($bundleFieldMatch) !== 0) {
373
        if ($bundleFieldMatch[$bundleKey] instanceof DriverFieldInterface) {
374
          $bundleField = $bundleFieldMatch[$bundleKey];
375
        }
376
        else {
377
          $bundleField = $this->getNewDriverField($bundleKey, $bundleFieldMatch[$bundleKey]);
378
        }
379
        $this->setBundle($bundleField->getProcessedValues()[0]['target_id']);
380
      }
381
382
      // Return the other fields (with the bundle field now removed).
383
      return $matcher->getUnmatchedTargets();
384
    }
385
  }
386
387
  /**
388
   * Get a new driver field with values.
389
   *
390
   * @param string $fieldName
391
   *   A string identifying an entity field.
392
   * @param string|array $values
393
   *   An input that can be transformed into Driver field values.
394
   */
395
  protected function getNewDriverField($fieldName, $values) {
396
    $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...
397
    $field = new $driverFieldVersionClass(
398
    $values,
399
    $fieldName,
400
    $this->getEntityTypeId(),
401
    $this->bundle(),
402
    $this->projectPluginRoot,
403
    $this->fieldPluginManager
404
    );
405
    return $field;
406
  }
407
408
  /**
409
   * Gets the provisional entity plugin.
410
   *
411
   * @return \Drupal\Driver\Plugin\DriverEntityPluginInterface
412
   *   The provisional (bundle-unaware) entity plugin.
413
   */
414
  protected function getProvisionalPlugin() {
415
    if ($this->hasFinalPlugin()) {
416
      return $this->getFinalPlugin();
417
    }
418
    return $this->provisionalPlugin;
419
  }
420
421
  /**
422
   * Whether a matched plugin has yet been discovered and stored.
423
   *
424
   * @return bool
425
   *   Whether a matched plugin has yet been discovered and stored.
426
   */
427
  protected function hasFinalPlugin() {
428
    $hasFinalPlugin = !is_null($this->finalPlugin);
429
    if ($hasFinalPlugin) {
430
      $hasFinalPlugin = $this->finalPlugin instanceof DriverEntityPluginInterface;
431
    }
432
    return $hasFinalPlugin;
433
  }
434
435
  /**
436
   * Whether a bundle has been set yet.
437
   *
438
   * @return bool
439
   *   Whether a bundle has been set yet.
440
   */
441
  protected function isBundleMissing() {
442
    $supportsBundles = $this->getProvisionalPlugin()->supportsBundles();
443
    return ($supportsBundles && is_null($this->bundle));
444
  }
445
446
  /**
447
   * Set the driver entity plugin manager.
448
   *
449
   * @param mixed $manager
450
   *   The driver entity plugin manager.
451
   * @param string $projectPluginRoot
452
   *   The directory to search for additional project-specific driver plugins.
453
   */
454
  protected function setEntityPluginManager($manager, $projectPluginRoot) {
455 View Code Duplication
    if (!($manager instanceof DriverPluginManagerInterface)) {
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...
456
      $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...
457
    }
458
    $this->entityPluginManager = $manager;
459
  }
460
461
  /**
462
   * Sets the provisional entity plugin.
463
   *
464
   * @param \Drupal\Driver\Plugin\DriverEntityPluginInterface $plugin
465
   *   The provisional entity plugin.
466
   */
467
  protected function setProvisionalPlugin(DriverEntityPluginInterface $plugin) {
468
    $this->provisionalPlugin = $plugin;
469
  }
470
471
}
472