Matcher   A
last analyzed

Complexity

Total Complexity 15

Size/Duplication

Total Lines 165
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 15
c 1
b 0
f 0
lcom 1
cbo 6
dl 0
loc 165
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A addCandidate() 0 6 1
A addCandidates() 0 10 2
A getCandidates() 0 4 1
A getScoresByAttribute() 0 12 2
A getNormalizedScoresByAttribute() 0 10 2
A getMatchingScoreByAttributeScore() 0 23 3
B get() 0 28 3
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