Passed
Push — 1.x ( 5002a5...60cc56 )
by butschster
02:42
created

OptimisticLock::computeRule()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1.0156

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 1
dl 0
loc 7
ccs 3
cts 4
cp 0.75
crap 1.0156
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cycle\ORM\Entity\Behavior;
6
7
use Cycle\Database\ColumnInterface;
8
use Cycle\Database\Schema\AbstractColumn;
0 ignored issues
show
Bug introduced by
The type Cycle\Database\Schema\AbstractColumn was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use Cycle\ORM\Entity\Behavior\Schema\BaseModifier;
10
use Cycle\ORM\Entity\Behavior\Schema\RegistryModifier;
11
use Cycle\ORM\Entity\Behavior\Exception\BehaviorCompilationException;
12
use Cycle\ORM\Entity\Behavior\Listener\OptimisticLock as Listener;
13
use Cycle\Schema\Registry;
14
use Doctrine\Common\Annotations\Annotation\Enum;
15
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
16
use Doctrine\Common\Annotations\Annotation\Target;
17
use JetBrains\PhpStorm\ArrayShape;
18
use JetBrains\PhpStorm\ExpectedValues;
19
20
/**
21
 * Implements the Optimistic Lock strategy.
22
 * Used to prevent concurrent editing of a record in the database. When an entity is locked, the transaction is aborted.
23
 * Please keep in mind, the behavior wraps the command in a special WrappedCommand wrapper.
24
 * The behavior has three parameters:
25
 *    - field - is a property with the version in the entity
26
 *    - column - is a column in the database.
27
 *    - rule - the strategy for storing the version of the entity
28
 * Rule can be one of several rules (class constants can be used):
29
 *    - RULE_MICROTIME - string with microtime value
30
 *    - RULE_RAND_STR - random string
31
 *    - RULE_INCREMENT - automatically incrementing integer version
32
 *    - RULE_DATETIME - datetime of the entity version
33
 *    - RULE_MANUAL - manually configured rule
34
 * The MANUAL rule provides for the completely manual configuration of an entity property and entity versioning.
35
 *
36
 * @Annotation
37
 * @NamedArgumentConstructor()
38
 * @Target({"CLASS"})
39
 */
40
#[\Attribute(\Attribute::TARGET_CLASS), NamedArgumentConstructor]
41
final class OptimisticLock extends BaseModifier
42
{
43
    public const RULE_MICROTIME = Listener::RULE_MICROTIME;
44
    public const RULE_RAND_STR = Listener::RULE_RAND_STR;
45
    public const RULE_INCREMENT = Listener::RULE_INCREMENT;
46
    public const RULE_DATETIME = Listener::RULE_DATETIME;
47
    public const RULE_MANUAL = Listener::RULE_MANUAL;
48
49
    private const DEFAULT_INT_VERSION = 1;
50
    private const STRING_COLUMN_LENGTH = 32;
51
52
    private ?string $column = null;
53
54
    /**
55
     * @param non-empty-string $field Version property name
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
56
     * @param non-empty-string|null $column Version column name
57
     * @param non-empty-string|null $rule
58
     */
59 40
    public function __construct(
60
        private string $field = 'version',
61
        /** @Enum({"microtime", "random-string", "increment", "datetime"}) */
62
        #[ExpectedValues(valuesFromClass: Listener::class)]
63
        ?string $column = null,
64
        private ?string $rule = null
65
    ) {
66 40
        $this->column = $column;
67
    }
68
69 40
    protected function getListenerClass(): string
70
    {
71 40
        return Listener::class;
72
    }
73
74 40
    #[ArrayShape(['field' => 'string', 'rule' => 'null|string'])]
75
    protected function getListenerArgs(): array
76
    {
77
        return [
78 40
            'field' => $this->field,
79 40
            'rule' => $this->rule
80
        ];
81
    }
82
83 40
    public function compute(Registry $registry): void
84
    {
85 40
        $modifier = new RegistryModifier($registry, $this->role);
86 40
        $this->column = $modifier->findColumnName($this->field, $this->column);
87
88 40
        if ($this->column !== null) {
89 40
            $this->addField($registry);
90
        }
91
    }
92
93 40
    public function render(Registry $registry): void
94
    {
95 40
        $this->column = (new RegistryModifier($registry, $this->role))
96 40
                ->findColumnName($this->field, $this->column)
97 40
            ?? $this->field;
98
99 40
        $this->addField($registry);
100
    }
101
102
    /**
103
     * Compute rule based on column type
104
     *
105
     * @return non-empty-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
106
     *
107
     * @throws BehaviorCompilationException
108
     */
109 40
    private function computeRule(AbstractColumn $column): string
110
    {
111 40
        return match ($column->getType()) {
112
            ColumnInterface::INT => self::RULE_INCREMENT,
113
            ColumnInterface::STRING => self::RULE_MICROTIME,
114
            'datetime' => self::RULE_DATETIME,
115 40
            default => throw new BehaviorCompilationException('Failed to compute rule based on column type.')
116
        };
117
    }
118
119 40
    private function addField(Registry $registry): void
120
    {
121 40
        $fields = $registry->getEntity($this->role)->getFields();
122
123
        assert($this->column !== null);
124
125 40
        $this->rule ??= $fields->has($this->field)
126 40
            ? $this->computeRule($registry->getTableSchema($registry->getEntity($this->role))->column($this->column))
127
            // rule not set, field not fount
128 40
            : Listener::DEFAULT_RULE;
129
130 40
        $modifier = new RegistryModifier($registry, $this->role);
131
132 40
        switch ($this->rule) {
133 40
            case self::RULE_INCREMENT:
134 40
                $modifier->addIntegerColumn($this->column, $this->field)
135
                    ->nullable(false)
136
                    ->defaultValue(self::DEFAULT_INT_VERSION);
137 40
                break;
138 40
            case self::RULE_RAND_STR:
139 40
            case self::RULE_MICROTIME:
140 40
                $modifier->addStringColumn($this->column, $this->field)
141
                    ->nullable(false)
142
                    ->string(self::STRING_COLUMN_LENGTH);
143 40
                break;
144 40
            case self::RULE_DATETIME:
145 40
                $modifier->addDatetimeColumn($this->column, $this->field);
146 40
                break;
147
            default:
148
                throw new BehaviorCompilationException(
149
                    sprintf(
150
                        'Wrong rule `%s` for the %s behavior in the `%s.%s` field.',
151
                        $this->rule,
152
                        self::class,
153
                        $this->role,
154
                        $this->field
155
                    )
156
                );
157
        }
158
    }
159
}
160