README.md 5.91 KB
Newer Older
1
[![Build Status](https://travis-ci.org/mateu-aguilo-bosch/typed_entity.svg?branch=7.x-1.x)](https://travis-ci.org/mateu-aguilo-bosch/typed_entity) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/mateu-aguilo-bosch/typed_entity/badges/quality-score.png?b=7.x-1.x)](https://scrutinizer-ci.com/g/mateu-aguilo-bosch/typed_entity/?branch=7.x-1.x)
2

Mateu Aguiló Bosch's avatar
Mateu Aguiló Bosch committed
3
4
# Typed entity

Mateu Aguiló Bosch's avatar
Mateu Aguiló Bosch committed
5
_Use Typed Entity as a namespace for your business logic. It **provides a scope to place your business logic**, and help you keep your global scope clean of myriads of small functions._
Mateu Aguiló Bosch's avatar
Mateu Aguiló Bosch committed
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

This module provides a simple way to treat you existing entities like typed
objects. This will allow you to have a more maintainable and easier to debug
codebase.

## Scenario

You have modelled your content using Drupal's entities and Field API, but now
you are stuck with `stdClass` and `field_get_items`. You could go further and
use `Entity Metadata Wrapper` to have a more comprehensive entity interaction.
That's fine so far.

Now imagine that you need to have some custom business logic that applies only
to `node > article`. You could create your custom module and have your custom
function:

```php
/**
 * Gets the aspect ratio of the field_image attached to a node article.
 * 
 * @param object $node
 *   The node.
 *
 * @return array
 *   Array with width and height.
 */
 function custom_module_article_image_ratio($node) {
   // Do some computation.
   return array('width' => $width, 'height' => $height);
 }
```

As the project grows this is hard to maintain and can get pretty wild. Imagine
`custom_module_article_related_article_image_ratio`, and the several of
variations –even those that don't relate to image ratio. Gasp!–.

## Proposal

Mateu Aguiló Bosch's avatar
Mateu Aguiló Bosch committed
44
Create a typed entity in your custom module under `src/TypedEntity` and make it implement `TypedEntityInterface`. Basically you just have to follow [the example](modules/typed_entity_example/src/TypedEntity/TypedNode.php).
Mateu Aguiló Bosch's avatar
Mateu Aguiló Bosch committed
45

Mateu Aguiló Bosch's avatar
Mateu Aguiló Bosch committed
46
For our example above we would create [`TypedNodeArticle`](modules/typed_entity_example/src/TypedEntity/Node/Article.php) and then another class called `TypedFileImage`. `TypedFileImage` would have a method to get the aspect ratio:
Mateu Aguiló Bosch's avatar
Mateu Aguiló Bosch committed
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

```php
<?php

/**
 * @file
 * Contains \Drupal\custom_module\TypedEntity\TypedFileImage.
 */

namespace Drupal\custom_module\TypedEntity;

use Drupal\typed_entity\TypedEntity\TypedEntity;

class TypedFileImage extends TypedEntity implements TypedFileImageInterface {

  /**
   * Gets the aspect ratio for the underlying entity.
   *
   * @return array
   *   The width and height.
   */
  public function getAspectRatio() {
    $file = $this->getEntity();
    // Do some computation on $file.
    return array('width' => $width, 'height' => $height);
  }

}
```

Now you can do things like:

```php
Mateu Aguiló Bosch's avatar
Mateu Aguiló Bosch committed
80
81
82
// Use the following comment to hint your IDE about the object type. That will
// enable IDE autocomplete.
/** @var \Drupal\typed_entity_example\TypedEntity\Node\Article $typed_node */
Mateu Aguiló Bosch's avatar
Mateu Aguiló Bosch committed
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
$typed_article = TypedEntityManager::create('node', $node);

// custom_module_article_image_ratio(…)
$ratio = $typed_article
  ->getImage()
  ->getAspectRatio();

// custom_module_article_related_article_image_ratio(…)
$ratio = $typed_article
  ->getRelatedArticle()
  ->getImage()
  ->getAspectRatio();
```

## Usage
To declare your typed entities you just have to create the class following the name convention:

Mateu Aguiló Bosch's avatar
Mateu Aguiló Bosch committed
100
101
  - Typed\<EntityTypeCamelCase\>\<BundleCamelCase\> (Ex: `TypedNodeArticle`).
  - Typed\<EntityTypeCamelCase\> (Ex: `TypedUser`, `TypedNode`).
Mateu Aguiló Bosch's avatar
Mateu Aguiló Bosch committed
102
103
104
105
106
107
108
109

That class will be discovered and returned by the factory function in
`TypedEntityManager`. Use:

```php
$typed_entity = TypedEntityManager::create($entity_type, $entity);
```

110
111
112
113
If you don't like the naming convention or you prefer to use something else, you can provide the class for your entity. For that you will have to implement `hook_typed_entity_registry_info`. See [the example](modules/typed_entity_example/typed_entity_example.module).

```php
/**
114
 * Implements hook_typed_entity_registry_info().
115
116
117
118
 */
function custom_module_typed_entity_registry_info() {
  $items['user'] = array(
    'entity_type' => 'user',
119
    'class' => '\\Drupal\\custom_module\\Foo\\Bar\\User',
120
  );
121
  $items['image'] = array(
122
123
    'entity_type' => 'file',
    'bundle' => 'image',
124
    'class' => '\\Drupal\\custom_module\\File\\Image',
125
126
127
128
129
130
  );

  return $items;
}
```

Mateu Aguiló Bosch's avatar
Mateu Aguiló Bosch committed
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
### Accessing the underlying entity
This module uses the PHP magic methods to allow you to do things like:

```php
$typed_entity = TypedEntityManager::create($entity_type, $entity);

// Access the uuid property on the underlying entity.
print $typed_entity->uuid;

// Set properties on the entity.
$typed_entity->status = NODE_PUBLISHED;

// Execute entity methods.
print $typed_entity->myEntityMethod('arg1', 2);
```

This has the benefit (over doing `$typed_entity->getEntity()->myEntityMethod('arg1', 2)`)
that you can then pass the `$typed_entity` in place of a node to functions that
access the node properties or call custom methods on the entity.

Mateu Aguiló Bosch's avatar
Mateu Aguiló Bosch committed
151
152
153
154
155
156
157
158
## Benefits
Besides [the benefits of OOP](https://duckduckgo.com/?q=object+oriented+programming+benefits)
you can have more structured Drupal entities with clearer relationships. Stop
passing ($entity_type, $entity) around, an entity should be able to know its own
type and bundle.

Care to add **tests**? You can even have unit testing on your custom business logic
(make sure those computations on the aspect ratio return the expected values).
159

Mateu Aguiló Bosch's avatar
Mateu Aguiló Bosch committed
160
Check out the [unit test example](modules/typed_entity_example/lib/Drupal/typed_entity_example/Tests/TypedEntityExampleUnitTestCase.php).
161
162
163
164
165
166
167
168
169

## Installation

This module uses unit testing as an example of how you should test your custom business logic. Sometimes your custom
logic contains calls to the drupal api that is not loaded for unit testing. To work around that you can use the Service
Container module.

This module needs an extra patch to do this, so you will have to patch:
  - `entity` with: https://www.drupal.org/files/issues/2455851-add-additional-interfaces-1.patch