1<?php declare(strict_types=1);
2
3namespace PhpParser\Builder;
4
5use PhpParser\Builder;
6use PhpParser\BuilderHelpers;
7use PhpParser\Modifiers;
8use PhpParser\Node;
9use PhpParser\Node\Stmt;
10
11class TraitUseAdaptation implements Builder {
12    private const TYPE_UNDEFINED  = 0;
13    private const TYPE_ALIAS      = 1;
14    private const TYPE_PRECEDENCE = 2;
15
16    protected int $type;
17    protected ?Node\Name $trait;
18    protected Node\Identifier $method;
19    protected ?int $modifier = null;
20    protected ?Node\Identifier $alias = null;
21    /** @var Node\Name[] */
22    protected array $insteadof = [];
23
24    /**
25     * Creates a trait use adaptation builder.
26     *
27     * @param Node\Name|string|null $trait Name of adapted trait
28     * @param Node\Identifier|string $method Name of adapted method
29     */
30    public function __construct($trait, $method) {
31        $this->type = self::TYPE_UNDEFINED;
32
33        $this->trait = is_null($trait) ? null : BuilderHelpers::normalizeName($trait);
34        $this->method = BuilderHelpers::normalizeIdentifier($method);
35    }
36
37    /**
38     * Sets alias of method.
39     *
40     * @param Node\Identifier|string $alias Alias for adapted method
41     *
42     * @return $this The builder instance (for fluid interface)
43     */
44    public function as($alias) {
45        if ($this->type === self::TYPE_UNDEFINED) {
46            $this->type = self::TYPE_ALIAS;
47        }
48
49        if ($this->type !== self::TYPE_ALIAS) {
50            throw new \LogicException('Cannot set alias for not alias adaptation buider');
51        }
52
53        $this->alias = BuilderHelpers::normalizeIdentifier($alias);
54        return $this;
55    }
56
57    /**
58     * Sets adapted method public.
59     *
60     * @return $this The builder instance (for fluid interface)
61     */
62    public function makePublic() {
63        $this->setModifier(Modifiers::PUBLIC);
64        return $this;
65    }
66
67    /**
68     * Sets adapted method protected.
69     *
70     * @return $this The builder instance (for fluid interface)
71     */
72    public function makeProtected() {
73        $this->setModifier(Modifiers::PROTECTED);
74        return $this;
75    }
76
77    /**
78     * Sets adapted method private.
79     *
80     * @return $this The builder instance (for fluid interface)
81     */
82    public function makePrivate() {
83        $this->setModifier(Modifiers::PRIVATE);
84        return $this;
85    }
86
87    /**
88     * Adds overwritten traits.
89     *
90     * @param Node\Name|string ...$traits Traits for overwrite
91     *
92     * @return $this The builder instance (for fluid interface)
93     */
94    public function insteadof(...$traits) {
95        if ($this->type === self::TYPE_UNDEFINED) {
96            if (is_null($this->trait)) {
97                throw new \LogicException('Precedence adaptation must have trait');
98            }
99
100            $this->type = self::TYPE_PRECEDENCE;
101        }
102
103        if ($this->type !== self::TYPE_PRECEDENCE) {
104            throw new \LogicException('Cannot add overwritten traits for not precedence adaptation buider');
105        }
106
107        foreach ($traits as $trait) {
108            $this->insteadof[] = BuilderHelpers::normalizeName($trait);
109        }
110
111        return $this;
112    }
113
114    protected function setModifier(int $modifier): void {
115        if ($this->type === self::TYPE_UNDEFINED) {
116            $this->type = self::TYPE_ALIAS;
117        }
118
119        if ($this->type !== self::TYPE_ALIAS) {
120            throw new \LogicException('Cannot set access modifier for not alias adaptation buider');
121        }
122
123        if (is_null($this->modifier)) {
124            $this->modifier = $modifier;
125        } else {
126            throw new \LogicException('Multiple access type modifiers are not allowed');
127        }
128    }
129
130    /**
131     * Returns the built node.
132     *
133     * @return Node The built node
134     */
135    public function getNode(): Node {
136        switch ($this->type) {
137            case self::TYPE_ALIAS:
138                return new Stmt\TraitUseAdaptation\Alias($this->trait, $this->method, $this->modifier, $this->alias);
139            case self::TYPE_PRECEDENCE:
140                return new Stmt\TraitUseAdaptation\Precedence($this->trait, $this->method, $this->insteadof);
141            default:
142                throw new \LogicException('Type of adaptation is not defined');
143        }
144    }
145}
146