Completed
Push — master ( 48290c...e43ea6 )
by Adam
07:23
created

TEntityContainer::iterate()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 7
nc 3
nop 1
1
<?php
2
/**
3
 * TEntityContainer.php
4
 *
5
 * @copyright      More in license.md
6
 * @license        http://www.ipublikuj.eu
7
 * @author         Adam Kadlec http://www.ipublikuj.eu
8
 * @package        iPublikuj:Forms!
9
 * @subpackage     Forms
10
 * @since          1.0.0
11
 *
12
 * @date           10.01.16
13
 */
14
15
declare(strict_types = 1);
16
17
namespace IPub\Forms\Forms;
18
19
use Doctrine\ORM;
20
21
use Nette;
22
use Nette\Forms;
23
24
use IPub;
25
use IPub\Forms\Exceptions;
26
27
/**
28
 * Class TEntity
29
 * @package IPub\Forms\Application\UI
30
 *
31
 * @property ORM\EntityManager $entityManager
32
 *
33
 * @method Nette\Application\UI\Form getForm($need = TRUE)
34
 * @method setValues($values, $erase = FALSE)
35
 */
36
trait TEntityContainer
37
{
38
	/**
39
	 * @var mixed
40
	 */
41
	private $entity;
42
43
	/**
44
	 * @param ORM\EntityManager $entityManager
45
	 *
46
	 * @return $this
47
	 */
48
	public function injectEntityManager(ORM\EntityManager $entityManager)
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $entityManager. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
49
	{
50
		$this->entityManager = $entityManager;
51
52
		return $this;
53
	}
54
55
	/**
56
	 * @param array|\Traversable $values
57
	 * @param bool $erase
58
	 */
59
	public function setDefaults($values, $erase = FALSE)
60
	{
61
		$form = $this->getForm(FALSE);
62
63
		if (!$form || !$form->isAnchored() || !$form->isSubmitted()) {
64
			if ($this->isEntity($values)) {
65
				$this->bindEntity($this, $values, $erase);
0 ignored issues
show
Bug introduced by
It seems like $values defined by parameter $values on line 59 can also be of type array; however, IPub\Forms\Forms\TEntityContainer::bindEntity() does only seem to accept object, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
66
67
			} else {
68
				$this->setValues($values, $erase);
69
			}
70
		}
71
	}
72
73
	/**
74
	 * @param mixed $entity
75
	 */
76
	public function setEntity($entity)
77
	{
78
		$this->entity = $entity;
79
	}
80
81
	/**
82
	 * @return mixed
83
	 */
84
	public function getEntity()
85
	{
86
		return $this->entity;
87
	}
88
89
	/**
90
	 * @param Nette\ComponentModel\Component $formElement
91
	 * @param object $entity
92
	 * @param bool $erase
93
	 *
94
	 * @throws Exceptions\InvalidArgumentException
95
	 */
96
	private function bindEntity($formElement, $entity, $erase)
97
	{
98
		$classMetadata = $this->getMetadata($entity);
99
100
		foreach (self::iterate($formElement) as $name => $component) {
0 ignored issues
show
Documentation introduced by
$formElement is of type object<Nette\ComponentModel\Component>, but the function expects a object<Nette\Forms\Contr...<Nette\Forms\Container>.

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...
101
			if ($component instanceof Forms\IControl) {
102
				if (($classMetadata->hasField($name) || $classMetadata->hasAssociation($name)) && $value = $classMetadata->getFieldValue($entity, $name)) {
103 View Code Duplication
					if (is_object($value) && $this->isEntity($value)) {
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...
104
						$UoW = $this->entityManager->getUnitOfWork();
105
						$value = $UoW->getSingleIdentifierValue($value);
106
107
						$component->setValue($value);
108
109
					} else {
110
						$component->setValue($value);
111
					}
112
113
				} else {
114
					$methodName = 'get' . ucfirst($name);
115
116
					if (method_exists($entity, $methodName) && $value = call_user_func([$entity, $methodName])) {
117 View Code Duplication
						if (is_object($value) && $this->isEntity($value)) {
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...
118
							$UoW = $this->entityManager->getUnitOfWork();
119
							$value = $UoW->getSingleIdentifierValue($value);
120
121
							$component->setValue($value);
122
123
						} else {
124
							$component->setValue($value);
125
						}
126
127
					} else {
128
						if ($erase) {
129
							$component->setValue(NULL);
130
						}
131
					}
132
				}
133
134
			} else if ($component instanceof Forms\Container) {
135
				if (($classMetadata->hasField($name) || $classMetadata->hasAssociation($name)) && $value = $classMetadata->getFieldValue($entity, $name)) {
136 View Code Duplication
					if (is_object($value) && $this->isEntity($value)) {
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...
137
						$this->bindEntity($component, $value, $erase);
138
139
					} else {
140
						$component->setValues($value, $erase);
141
					}
142
143
				} else {
144
					$methodName = 'get' . ucfirst($name);
145
146
					if (method_exists($entity, $methodName) && $value = call_user_func([$entity, $methodName])) {
147 View Code Duplication
						if (is_object($value) && $this->isEntity($value)) {
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...
148
							$this->bindEntity($component, $value, $erase);
149
150
						} else {
151
							$component->setValues($value, $erase);
152
						}
153
154
					} else {
155
						if ($erase) {
156
							$component->setValues([], $erase);
157
						}
158
					}
159
				}
160
			}
161
		}
162
	}
163
164
	/**
165
	 * @param object $entity
166
	 *
167
	 * @return ORM\Mapping\ClassMetadata
168
	 *
169
	 * @throws Exceptions\InvalidArgumentException
170
	 */
171
	private function getMetadata($entity)
172
	{
173
		if (!$this->isEntity($entity)) {
174
			throw new Exceptions\InvalidArgumentException('Expected object, ' . gettype($entity) . ' given.');
175
		}
176
177
		return $this->entityManager->getClassMetadata(get_class($entity));
178
	}
179
180
	/**
181
	 * @param mixed $entity
182
	 *
183
	 * @return bool
184
	 */
185
	private function isEntity($entity)
186
	{
187
		return is_object($entity) && $this->entityManager->getMetadataFactory()->hasMetadataFor(get_class($entity));
188
	}
189
190
	/**
191
	 * @param Nette\Forms\Controls\BaseControl|Nette\Forms\Container $formElement
192
	 *
193
	 * @return array|\ArrayIterator
194
	 *
195
	 * @throws Exceptions\InvalidArgumentException
196
	 */
197
	private static function iterate($formElement)
198
	{
199
		if ($formElement instanceof Forms\Container) {
200
			return $formElement->getComponents();
201
202
		} elseif ($formElement instanceof Forms\IControl) {
203
			return [$formElement];
204
205
		} else {
206
			throw new Exceptions\InvalidArgumentException('Expected Nette\Forms\Container or Nette\Forms\IControl, but ' . get_class($formElement) . ' given');
207
		}
208
	}
209
}
210