Matcher::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Pbmedia\Specifications;
4
5
use Illuminate\Support\Collection;
6
use Pbmedia\Specifications\Interfaces\Attribute;
7
use Pbmedia\Specifications\Interfaces\CanBeSpecified;
8
use Pbmedia\Specifications\Interfaces\Specifications as SpecificationsInterface;
9
use Pbmedia\Specifications\Specifications;
10
11
class Matcher implements CanBeSpecified
12
{
13
    use HasSpecifications;
14
15
    /**
16
     * Collection instance.
17
     *
18
     * @var \Illuminate\Support\Collection
19
     */
20
    protected $candidates;
21
22
    /**
23
     * Create a new Matches instance and instantiates a new Collection.
24
     */
25
    public function __construct()
26
    {
27
        $this->candidates = new Collection;
28
    }
29
30
    /**
31
     * Add an object that implements the CanBeSpecified interface.
32
     *
33
     * @param  \Pbmedia\Specifications\Interfaces\CanBeSpecified   $candidate
34
     * @return $this
35
     */
36
    public function addCandidate(CanBeSpecified $candidate): Matcher
37
    {
38
        $this->candidates->push($candidate);
39
40
        return $this;
41
    }
42
43
    /**
44
     * Helper method to add multiple candidates at once.
45
     *
46
     * @param  mixed   $candidates
47
     * @return $this
48
     */
49
    public function addCandidates($candidates): Matcher
50
    {
51
        $candidates = is_array($candidates) ? $candidates : func_get_args();
52
53
        Collection::make($candidates)->each(function ($candidate) {
54
            $this->addCandidate($candidate);
55
        });
56
57
        return $this;
58
    }
59
60
    /**
61
     * Returns a collection containing all candidates.
62
     *
63
     * @return \Illuminate\Support\Collection
64
     */
65
    public function getCandidates(): Collection
66
    {
67
        return $this->candidates;
68
    }
69
70
    /**
71
     * Returns a collection where the keys matches the keys of the
72
     * candidates Collection but contain the score of the given
73
     * Attribute object.
74
     *
75
     * @param  \Pbmedia\Specifications\Interfaces\Attribute   $attribute
76
     * @return \Illuminate\Support\Collection
77
     */
78
    public function getScoresByAttribute(Attribute $attribute): Collection
79
    {
80
        return $this->candidates->map(function (CanBeSpecified $candidate) {
81
            return $candidate->specifications();
82
        })->map(function (SpecificationsInterface $specifications) use ($attribute) {
83
            if (!$specifications->has($attribute)) {
84
                return;
85
            }
86
87
            return $specifications->get($attribute)->getScoreValue();
88
        });
89
    }
90
91
    /**
92
     * This method does the same as the 'getScoresByAttribute' method
93
     * but has the scores normalized.
94
     *
95
     * @param  \Pbmedia\Specifications\Interfaces\Attribute   $attribute
96
     * @return \Illuminate\Support\Collection
97
     */
98
    public function getNormalizedScoresByAttribute(Attribute $attribute): Collection
99
    {
100
        $scores = $this->getScoresByAttribute($attribute);
101
102
        $max = $scores->max();
103
104
        return $scores->map(function ($score) use ($max) {
105
            return is_null($score) ? null : ($score / $max);
106
        });
107
    }
108
109
    /**
110
     * Returns a collection where the keys matches the keys of the
111
     * candidates Collection but contain the normalized score compaired
112
     * to the given AttributeScore.
113
     *
114
     * @param  \Pbmedia\Specifications\AttributeScore  $attributeScore
115
     * @return \Illuminate\Support\Collection
116
     */
117
    public function getMatchingScoreByAttributeScore(AttributeScore $attributeScore): Collection
118
    {
119
        $attribute  = $attributeScore->getAttribute();
120
        $scoreValue = $attributeScore->getScoreValue();
121
122
        $scores = $this->getScoresByAttribute($attribute);
123
124
        if (!$scores->max()) {
125
            return $this->candidates->map(function () {
126
                return 0;
127
            });
128
        }
129
130
        $scoreToCompareTo = $scoreValue / $scores->max();
131
132
        return $this->getNormalizedScoresByAttribute($attribute)->map(function ($normalizedScore) use ($scoreToCompareTo) {
133
            if ($normalizedScore === null) {
134
                return null;
135
            }
136
137
            return 1 - abs($normalizedScore - $scoreToCompareTo);
138
        });
139
    }
140
141
    /**
142
     * Returns a collection where the candidaties are sorted based
143
     * on how close they are to the specifications.
144
     *
145
     * @return \Illuminate\Support\Collection
146
     */
147
    public function get(): Collection
148
    {
149
        if ($this->specifications()->all()->isEmpty()) {
150
            return $this->getCandidates();
151
        }
152
153
        $scores = [];
154
155
        $this->specifications()->all()->map(function (AttributeScore $attributeScore) {
156
            return $this->getMatchingScoreByAttributeScore($attributeScore);
157
        })->each(function (Collection $matchingScoreByAttributeScore) use (&$scores) {
158
            $matchingScoreByAttributeScore->each(function ($score, $productKey) use (&$scores) {
159
                if (!isset($scores[$productKey])) {
160
                    $scores[$productKey] = 0;
161
                }
162
163
                $scores[$productKey] += $score;
164
            });
165
        });
166
167
        return $this->getCandidates()->map(function (CanBeSpecified $candidate, $productKey) use ($scores) {
168
            $score = $scores[$productKey];
169
170
            return compact('candidate', 'score');
171
        })->sortByDesc('score')->map(function ($candidateWithScore): CanBeSpecified {
172
            return $candidateWithScore['candidate'];
173
        })->values();
174
    }
175
}
176