Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
68.75% covered (warning)
68.75%
11 / 16
CRAP
81.16% covered (success)
81.16%
56 / 69
MongoPipeline
0.00% covered (danger)
0.00%
0 / 1
68.75% covered (warning)
68.75%
11 / 16
41.73
81.16% covered (success)
81.16%
56 / 69
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 sort
0.00% covered (danger)
0.00%
0 / 1
10.12
89.47% covered (success)
89.47%
17 / 19
 project
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 unwind
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
7 / 7
 match
0.00% covered (danger)
0.00%
0 / 1
2.15
66.67% covered (warning)
66.67%
4 / 6
 out
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 3
 merge
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 3
 group
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 replaceRoot
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 toArray
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 aggregate
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 raw
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 clear
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 cleanFormat
0.00% covered (danger)
0.00%
0 / 1
8.83
57.14% covered (warning)
57.14%
4 / 7
 decodeJson
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
5 / 5
 startsWith
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
1<?php
2
3namespace Qmp\Laravel\MongoLow;
4
5use Illuminate\Support\Collection;
6use Qmp\Laravel\MongoLow\Exceptions\MongoPipelineJsonDecodeException;
7
8class MongoPipeline
9{
10    /**
11     * Array that holds aggregation pipeline stages informations
12     *
13     * @var array
14     */
15    private $pipeline = [];
16
17    /**
18     * Model associated with this pipeline
19     *
20     * @var Qmp\Laravel\MongoLow\Models\AbstractQmpMoloquent
21     */
22    private $model;
23
24    /**
25     * Class constructor
26     *
27     * @param \Moloquent $model
28     */
29    public function __construct(\Moloquent $model = null)
30    {
31        $this->model = $model;
32    }
33
34    /**
35     * Sort Stage
36     *
37     * @param string $field
38     * @param string $order | "asc" or "desc"
39     * @return Qmp\Laravel\MongoLow\MongoPipeline
40     *
41     * @see https://docs.mongodb.com/manual/reference/operator/aggregation/sort/
42     * 
43     * Future update : Sort on multiples fileds
44     */
45    public function sort(...$params): MongoPipeline
46    {
47        $sort = collect($params)->chunk(2)->map(function ($chunk) {
48            $maped =  $chunk->map(function ($order, $key) {
49                if ($key === 1) {
50                    if (is_int($order) && ($order === 1 || $order === -1)) {
51                        return $order;
52                    } else if (is_string($order) && ($order === 'asc' || $order === 'desc')) {
53                        return $order === 'asc' ? 1 : -1;
54                    } else {
55                        return 1;
56                    }
57                }
58                return $order;
59            });
60
61            if ($maped->count() !== 2) {
62                $maped->push(1);
63            }
64
65            return $maped->values();
66        })->mapWithKeys(function ($item) {
67            return [$item[0] => $item[1]];
68        });
69
70        $this->pipeline[] = [
71            '$sort' => $sort->toArray()
72        ];
73        return $this;
74    }
75
76    /**
77     * Project stage
78     *
79     * @param mixed $string
80     * @return Qmp\Laravel\MongoLow\MongoPipeline
81     *
82     * @see https://docs.mongodb.com/manual/reference/operator/aggregation/project/
83     */
84    public function project($projection): MongoPipeline
85    {
86        $this->pipeline[] = [
87            '$project' => $this->cleanFormat($projection)
88        ];
89        return $this;
90    }
91
92    /**
93     * Unwind stage
94     *
95     * @param string $path
96     * @param boolean $preserveEmpty
97     * @return Qmp\Laravel\MongoLow\MongoPipeline
98     *
99     * @see https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/
100     */
101    public function unwind(string $path, bool $preserveEmpty = false): MongoPipeline
102    {
103        $path = '$' . $path;
104        $params = $preserveEmpty ? [
105            'path' => $path,
106            'preserveNullAndEmptyArrays' => true
107        ] : $path;
108
109        $this->pipeline[] = [
110            '$unwind' => $params
111        ];
112        return $this;
113    }
114
115    /**
116     * Match stage
117     *
118     * @param string $haystack
119     * @return Qmp\Laravel\MongoLow\MongoPipeline
120     *
121     * @see https://docs.mongodb.com/manual/reference/operator/aggregation/match/
122     */
123    public function match($haystack, $needle = null): MongoPipeline
124    {
125        if ($this->startsWith(trim($haystack), '{')) {
126            $this->pipeline[] = [
127                '$match' => $this->decodeJson($haystack, true)
128            ];
129        } else {
130            $this->pipeline[] = [
131                '$match' => [$haystack => $needle]
132            ];
133        }
134
135        return $this;
136    }
137
138    /**
139     * out stage
140     *
141     * @param mixed $haystack
142     * @return Qmp\Laravel\MongoLow\MongoPipeline
143     *
144     * @see https://docs.mongodb.com/manual/reference/operator/aggregation/match/
145     */
146    public function out($out): MongoPipeline
147    {
148        $this->pipeline[] = [
149            '$out' => $this->cleanFormat($out)
150        ];
151
152        return $this;
153    }
154
155    /**
156     * merge stage
157     *
158     * @param mixed $haystack
159     * @return Qmp\Laravel\MongoLow\MongoPipeline
160     *
161     * @see https://docs.mongodb.com/manual/reference/operator/aggregation/match/
162     */
163    public function merge($merge): MongoPipeline
164    {
165        $this->pipeline[] = [
166            '$merge' => $this->cleanFormat($merge)
167        ];
168
169        return $this;
170    }
171
172    /**
173     * Group stage
174     *
175     * @param mixed $on
176     * @param mixed $values
177     * @return Qmp\Laravel\MongoLow\MongoPipeline
178     *
179     * @see https://docs.mongodb.com/manual/reference/operator/aggregation/group/
180     */
181    public function group($on, $values = []): MongoPipeline
182    {
183        $this->pipeline[] = [
184            '$group' => array_merge(['_id' => $this->cleanFormat($on)], $this->cleanFormat($values))
185        ];
186        return $this;
187    }
188
189    /**
190     * ReplaceRoot stage
191     *
192     * @param mixed $values
193     * @return Qmp\Laravel\MongoLow\MongoPipeline
194     *
195     * @see https://docs.mongodb.com/manual/reference/operator/aggregation/replaceRoot/
196     */
197    public function replaceRoot($newRoot): MongoPipeline
198    {
199        $this->pipeline[] = [
200            '$replaceRoot' => [
201                'newRoot' => $this->cleanFormat($newRoot)
202            ]
203        ];
204        return $this;
205    }
206
207    /**
208     * Return pipeline array
209     *
210     * @return array
211     */
212    public function toArray(): array
213    {
214        return $this->pipeline;
215    }
216
217    /**
218     * Lunch aggregation on model
219     *
220     * @return Jenssegers\Mongodb\Collection
221     * 
222     */
223
224    public function aggregate($options = []): \Illuminate\Database\Eloquent\Collection
225    {
226        return MongoAggregator::raw($this->model, $this, $options);
227    }
228
229    /**
230     * Call a raw json pipeline
231     *
232     * @param mixed $string
233     * @return Qmp\Laravel\MongoLow\MongoPipeline
234     */
235    public function raw($string, $options = [])
236    {
237        $this->pipeline = $this->cleanFormat($string);
238
239        return $this->aggregate($options);
240    }
241
242    /**
243     * Clear pipeline array
244     *
245     * @return Qmp\Laravel\MongoLow\MongoPipeline
246     */
247    public function clear(): MongoPipeline
248    {
249        $this->pipeline = [];
250        return $this;
251    }
252
253    /**
254     * Format array, collection or json
255     *
256     * @param mixed $data
257     * @return array
258     */
259    protected function cleanFormat($data)
260    {
261        if (is_array($data)) {
262            return $data;
263        } else if (is_string($data) && ($this->startsWith($data, '{') || $this->startsWith($data, '['))) {
264            return $this->decodeJson($data);
265        } else if ($data instanceof Collection) {
266            return $data->toArray();
267        }
268
269        return $data;
270    }
271
272    protected function decodeJson($json)
273    {
274        $decoded = json_decode($json, true);
275        $error = json_last_error();
276
277        if ($decoded === null && $error !== JSON_ERROR_NONE) {
278            throw new MongoPipelineJsonDecodeException('Invalid Json', $error, $json);
279        }
280
281
282
283        return $decoded;
284    }
285
286
287    protected function startsWith($haystack, $needle)
288    {
289        $length = strlen($needle);
290        return (substr(trim($haystack), 0, $length) === $needle);
291    }
292}