1<?php
2
3declare(strict_types=1);
4
5namespace JMS\Serializer\Exclusion;
6
7use JMS\Serializer\Context;
8use JMS\Serializer\Exception\RuntimeException;
9use JMS\Serializer\Metadata\ClassMetadata;
10use JMS\Serializer\Metadata\PropertyMetadata;
11
12final class GroupsExclusionStrategy implements ExclusionStrategyInterface
13{
14    public const DEFAULT_GROUP = 'Default';
15
16    /**
17     * @var array
18     */
19    private $groups = [];
20
21    /**
22     * @var bool
23     */
24    private $nestedGroups = false;
25
26    public function __construct(array $groups)
27    {
28        if (empty($groups)) {
29            $groups = [self::DEFAULT_GROUP];
30        }
31
32        foreach ($groups as $group) {
33            if (is_array($group)) {
34                $this->nestedGroups = true;
35                break;
36            }
37        }
38
39        if ($this->nestedGroups) {
40            $this->groups = $groups;
41        } else {
42            foreach ($groups as $group) {
43                $this->groups[$group] = true;
44            }
45        }
46    }
47
48    /**
49     * {@inheritDoc}
50     */
51    public function shouldSkipClass(ClassMetadata $metadata, Context $navigatorContext): bool
52    {
53        return false;
54    }
55
56    /**
57     * {@inheritDoc}
58     */
59    public function shouldSkipProperty(PropertyMetadata $property, Context $navigatorContext): bool
60    {
61        if ($this->nestedGroups) {
62            $groups = $this->getGroupsFor($navigatorContext);
63
64            if (!$property->groups) {
65                return !in_array(self::DEFAULT_GROUP, $groups);
66            }
67
68            return $this->shouldSkipUsingGroups($property, $groups);
69        } else {
70            if (!$property->groups) {
71                return !isset($this->groups[self::DEFAULT_GROUP]);
72            }
73
74            foreach ($property->groups as $group) {
75                if (isset($this->groups[$group])) {
76                    return false;
77                }
78            }
79            return true;
80        }
81    }
82
83    private function shouldSkipUsingGroups(PropertyMetadata $property, array $groups): bool
84    {
85        foreach ($property->groups as $group) {
86            if (in_array($group, $groups)) {
87                return false;
88            }
89        }
90
91        return true;
92    }
93
94    private function getGroupsFor(Context $navigatorContext): array
95    {
96        $paths = $navigatorContext->getCurrentPath();
97
98        $groups = $this->groups;
99        foreach ($paths as $index => $path) {
100            if (!array_key_exists($path, $groups)) {
101                break;
102            }
103
104            if (!is_array($groups[$path])) {
105                throw new RuntimeException(sprintf('The group value for the property path "%s" should be an array, "%s" given', $index, gettype($groups[$path])));
106            }
107
108            $groups = $groups[$path];
109        }
110
111        return $groups;
112    }
113}
114