Issues (345)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Internal/_stream.php (17 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php namespace Tarsana\Functional;
2
/**
3
 * This file contains Stream internal functions.
4
 * @file
5
 */
6
7
/**
8
 * Operation :: {
9
 *     name: String,
10
 *     signatures: [[String]],
11
 *     fn: Function
12
 * }
13
 *
14
 * @type
15
 */
16
17
/**
18
 * Transformation :: {
19
 *     operations: [Operation],
20
 *     args: [Any]
21
 * }
22
 *
23
 * @type
24
 */
25
26
/**
27
 * Stream :: {
28
 *     operations: {name1: Operation1, ...},
29
 *     data: Any,
30
 *     transformations: [Transformation],
31
 *     type: String,
32
 *     result: Any,
33
 *     resolved: Boolean (The result is computed)
34
 * }
35
 *
36
 * @type
37
 */
38
39
/**
40
 * Returns supported types in signatures.
41
 *
42
 * @signature List
43
 * @return array
44
 */
45
function _stream_types() {
46
    return [
47
        'Null',
48
        'Boolean',
49
        'Number',
50
        'String',
51
        'Resource',
52
        'Function',
53
        'List', // [1, 2, 'Hi']
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
54
        'Array', // ['foo' => 'bar', 'baz']
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
55
        'Object',
56
        'Any'
57
    ];
58
}
59
60
/**
61
 * Throws an error.
62
 *
63
 * @signature String -> *
64
 *     'unknown-callable', callable
65
 *     'invalid-signature', string
66
 *     'unknown-operation', string
67
 *     'invalid-args', string, [string], [[string]]
68
 *     'duplicated-operation', string
69
 * @param  string $type
70
 * @return void
71
 */
72
function _stream_throw_error($type) {
73
    $params = tail(func_get_args());
74
    $msg = 'Stream: unknown error happened';
75
    switch ($type) {
76
        case 'unknown-callable':
77
            $fn = is_string($params[0]) ? $params[0] : toString($params[0]);
78
            $msg = "Stream: unknown callable '{$fn}'";
79
        break;
80
        case 'invalid-signature':
81
            $msg = "Stream: invalid signature '{$params[0]}' it should follow the syntax 'TypeArg1 -> TypeArg2 -> ... -> ReturnType' and types to use are Boolean, Number, String, Resource, Function, List, Array, Object, Any";
82
        break;
83
        case 'unknown-operation':
84
            $msg = "Stream: call to unknown operation '{$params[0]}'";
85
        break;
86
        case 'duplicated-operation':
87
            $msg = "Stream: operation '{$params[0]}' already exists";
88
        break;
89
        case 'ambiguous-signatures':
90
            $msg = "Stream: signatures of the operation '{$params[0]}' are duplicated or ambiguous";
91
        break;
92
        case 'wrong-operation-args':
93
            $args = join(', ', $params[0]);
94
            $msg = "Stream: wrong arguments ({$args}) given to operation '{$params[1]}'";
95
        break;
96
        case 'wrong-transformation-args':
97
            $args = join(', ', $params[1]);
98
            $types = join(' or ', map(pipe(join(', '), prepend('('), append(')')), $params[2]));
99
100
            $msg = "Stream: operation '{$params[0]}' could not be called with arguments types ({$args}); expected types are {$types}";
101
        break;
102
    }
103
    throw Error::of($msg);
104
}
105
106
/**
107
 * Creates an operation.
108
 *
109
 * ```php
110
 * // Using function name
111
 * $length = F\_stream_operation('length', 'List|Array -> Number', 'count');
112
 * $length; //=> ['name' => 'length', 'signatures' => [['List', 'Number'], ['Array', 'Number']], 'fn' => 'count']
113
 * // Using closure
114
 * $increment = function($x) {
115
 *     return 1 + $x;
116
 * };
117
 * $operation = F\_stream_operation('increment', 'Number -> Number', $increment);
118
 * $operation; //=> ['name' => 'increment', 'signatures' => [['Number', 'Number']], 'fn' => $increment]
119
 * // Without callable
120
 * F\_stream_operation('count', 'List -> Number'); //=> ['name' => 'count', 'signatures' => [['List', 'Number']], 'fn' => 'count']
121
 * // Invalid signature
122
 * F\_stream_operation('count', 'Number'); // throws "Stream: invalid signature 'Number' it should follow the syntax 'TypeArg1 -> TypeArg2 -> ... -> ReturnType' and types to use are Boolean, Number, String, Resource, Function, List, Array, Object, Any"
123
 * // Invalid callable
124
 * F\_stream_operation('foo', 'List -> Number'); // throws "Stream: unknown callable 'foo'"
125
 * ```
126
 *
127
 * @signature String -> String -> Maybe(Function) -> Operation
128
 * @param  string $name
129
 * @param  string $signature
130
 * @param  callable $fn
0 ignored issues
show
There is no parameter named $fn. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
131
 * @return array
132
 */
133
function _stream_operation($name, $signature, $callable = null) {
134
    $callable = $callable ?: $name;
135
    if (!is_callable($callable))
136
        _stream_throw_error('unknown-callable', $callable);
137
138
    return [
139
        'name' => $name,
140
        'signatures' => _stream_make_signatures($signature),
141
        'fn' => $callable
142
    ];
143
}
144
145
/**
146
 * Transforms a signature text to an array of signatures.
147
 *
148
 * ```php
149
 * F\_stream_make_signatures('Number|List -> Number -> String|Array -> Number'); //=> [
150
 *     ['Number', 'Number', 'String', 'Number'],
151
 *     ['List', 'Number', 'String', 'Number'],
152
 *     ['Number', 'Number', 'Array', 'Number'],
153
 *     ['List', 'Number', 'Array', 'Number']
154
 * ]
155
 * F\_stream_make_signatures('List'); // throws "Stream: invalid signature 'List' it should follow the syntax 'TypeArg1 -> TypeArg2 -> ... -> ReturnType' and types to use are Boolean, Number, String, Resource, Function, List, Array, Object, Any"
156
 * F\_stream_make_signatures('List -> Foo'); // throws "Stream: invalid signature 'List -> Foo' it should follow the syntax 'TypeArg1 -> TypeArg2 -> ... -> ReturnType' and types to use are Boolean, Number, String, Resource, Function, List, Array, Object, Any"
157
 * ```
158
 *
159
 * @signature String -> [[String]]
160
 * @param  string $text
161
 * @return array
162
 */
163
function _stream_make_signatures($text) {
164
    // Assuming $text  = 'Number|List -> Number -> String|Array -> Number'
165
    $parts = map(pipe(split('|'), map(pipe('trim', _stream_ensure_type($text)))), split('->', $text));
166
    // $parts = [['Number', 'List'], ['Number'], ['String', 'Array'], ['Number']]
0 ignored issues
show
Unused Code Comprehensibility introduced by
71% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
167
    if (length($parts) < 2)
168
        _stream_throw_error('invalid-signature', $text);
169
170
    return reduce(function($result, $part){
171
        return chain(function($item) use($result){
172
            return map(append($item), $result);
173
        }, $part);
174
    }, [[]], $parts);
175
    // 0: $result = [[]]
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
176
    // 1: $part = ['Number', 'List']  => $result = [['Number'], ['List']]
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
177
    // 2: $part = ['Number']          => $result = [['Number', 'Number'], ['List', 'Number']]
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
178
    // 2: $part = ['String', 'Array'] => $result = [['Number', 'Number', 'String'], ['List', 'Number', 'String'],
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
179
    //                                              ['Number', 'Number', 'Array'], ['List', 'Number', 'Array']]
0 ignored issues
show
Unused Code Comprehensibility introduced by
73% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
180
    // 3: $part = ['Number']          => $result = [['Number', 'Number', 'String', 'Number'],
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
181
    //                                              ['List', 'Number', 'String', 'Number'],
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
182
    //                                              ['Number', 'Number', 'Array', 'Number'],
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
183
    //                                              ['List', 'Number', 'Array', 'Number']]
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
184
}
185
186
/**
187
 * Ensures an element of a signature is a correct type or throws an error if not.
188
 *
189
 * ```php
190
 * F\_stream_ensure_type('List -> Bar', 'List'); //=> 'List'
191
 * F\_stream_ensure_type('List -> Bar', 'Bar'); // throws "Stream: invalid signature 'List -> Bar' it should follow the syntax 'TypeArg1 -> TypeArg2 -> ... -> ReturnType' and types to use are Boolean, Number, String, Resource, Function, List, Array, Object, Any"
192
 * ```
193
 * @signature String -> String -> String
194
 * @param  string $signature
0 ignored issues
show
There is no parameter named $signature. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
195
 * @param  string $type
0 ignored issues
show
There is no parameter named $type. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
196
 * @return string
197
 */
198
function _stream_ensure_type() {
199
    $ensureType = function($signature, $type) {
200
        if (! contains($type, _stream_types()))
201
            _stream_throw_error('invalid-signature', $signature);
202
        return $type;
203
    };
204
    return apply(curry($ensureType), func_get_args());
205
}
206
207
/**
208
 * Makes a Stream with the given args.
209
 *
210
 * @signature [Operation] -> Any -> [Transformation] -> String -> Any -> Boolean -> Stream
211
 * @param  array $operations
212
 * @param  mixed $data
213
 * @param  array $transformations
214
 * @param  string $type
215
 * @param  mixed $result
216
 * @param  bool $resolved
217
 * @return array
218
 */
219
function _stream_make($operations, $data, $transformations, $type, $result, $resolved) {
220
    return [
221
        'data' => $data,
222
        'type' => $type,
223
        'result' => $result,
224
        'resolved' => $resolved,
225
        'operations' => $operations,
226
        'transformations' => $transformations
227
    ];
228
}
229
230
/**
231
 * Creates a Stream with operations and initial data.
232
 *
233
 * ```php
234
 * $map = F\map();
235
 * $operations = [
236
 *     F\_stream_operation('length', 'List|Array -> Number', 'count'),
237
 *     F\_stream_operation('length', 'String -> Number', 'strlen'),
238
 *     F\_stream_operation('map', 'Function -> List -> List', $map)
239
 * ];
240
 *
241
 * F\_stream($operations, 11); //=> [
242
 *     'data' => 11,
243
 *     'type' => 'Number',
244
 *     'result' => 11,
245
 *     'resolved' => true,
246
 *     'operations' => [
247
 *         'length' => [
248
 *             [
249
 *                 'name' => 'length',
250
 *                 'signatures' => [['List', 'Number'], ['Array', 'Number']],
251
 *                 'fn' => 'count'
252
 *             ],
253
 *             [
254
 *                 'name' => 'length',
255
 *                 'signatures' => [['String', 'Number']],
256
 *                 'fn' => 'strlen'
257
 *             ]
258
 *         ],
259
 *         'map' => [
260
 *             [
261
 *                 'name' => 'map',
262
 *                 'signatures' => [['Function', 'List', 'List']],
263
 *                 'fn' => $map
264
 *             ]
265
 *         ]
266
 *     ],
267
 *     'transformations' => []
268
 * ]
269
 * ```
270
 *
271
 * @param  array $operations
272
 * @param  mixed $data
273
 * @return array
274
 */
275
function _stream($operations, $data) {
276
    return _stream_make(
277
        map(_f('_stream_validate_operations'), groupBy(get('name'), $operations)),
278
        $data,
279
        [],
280
        type($data),
281
        $data,
282
        true
283
    );
284
}
285
286
/**
287
 * Validates a list of operations having the same name.
288
 * It throws an error if a signature is duplicated.
289
 * ```php
290
 * F\_stream_validate_operations([
291
 *     [
292
 *         'name' => 'length',
293
 *         'signatures' => [['List', 'Number'], ['Array', 'Number']],
294
 *         'fn' => 'count'
295
 *     ],
296
 *     [
297
 *         'name' => 'length',
298
 *         'signatures' => [['String', 'Number']],
299
 *         'fn' => 'strlen'
300
 *     ]
301
 * ]); //=> [
302
 *     [
303
 *         'name' => 'length',
304
 *         'signatures' => [['List', 'Number'], ['Array', 'Number']],
305
 *         'fn' => 'count'
306
 *     ],
307
 *     [
308
 *         'name' => 'length',
309
 *         'signatures' => [['String', 'Number']],
310
 *         'fn' => 'strlen'
311
 *     ]
312
 * ]
313
 *
314
 * F\_stream_validate_operations([
315
 *     [
316
 *         'name' => 'length',
317
 *         'signatures' => [['List', 'Number'], ['Array', 'Number']],
318
 *         'fn' => 'count'
319
 *     ],
320
 *     [
321
 *         'name' => 'length',
322
 *         'signatures' => [['String', 'Number'], ['List', 'Number']],
323
 *         'fn' => 'strlen'
324
 *     ]
325
 * ]); // throws "Stream: signatures of the operation 'length' are duplicated or ambiguous"
326
 * ```
327
 *
328
 * @param  array $operations
329
 * @return array
330
 */
331
function _stream_validate_operations($operations) {
332
    $signatures = chain(get('signatures'), $operations);
333
    if (length($signatures) != length(uniqueBy(equalBy(map(init())), $signatures))) {
334
        _stream_throw_error('ambiguous-signatures', get('name', head($operations)));
335
    }
336
    return $operations;
337
}
338
339
/**
340
 * Applies an operation (adds a transformation) to a stream.
341
 * ```php
342
 * $operations = [
343
 *     F\_stream_operation('length', 'List|Array -> Number', 'count'),
344
 *     F\_stream_operation('length', 'String -> Number', 'strlen'),
345
 *     F\_stream_operation('map', 'Function -> List -> List', F\map())
346
 * ];
347
 *
348
 * $stream = F\_stream($operations, [1, 2, 3]);
349
 *
350
 * F\_stream_apply_operation('length', [], $stream); //=> [
351
 *     'data' => [1, 2, 3],
352
 *     'type' => 'Number',
353
 *     'result' => null,
354
 *     'resolved' => false,
355
 *     'operations' => [
356
 *         'length' => [
357
 *             [
358
 *                 'name' => 'length',
359
 *                 'signatures' => [['List', 'Number'], ['Array', 'Number']],
360
 *                 'fn' => 'count'
361
 *             ],
362
 *             [
363
 *                 'name' => 'length',
364
 *                 'signatures' => [['String', 'Number']],
365
 *                 'fn' => 'strlen'
366
 *             ]
367
 *         ],
368
 *         'map' => [
369
 *             [
370
 *                 'name' => 'map',
371
 *                 'signatures' => [['Function', 'List', 'List']],
372
 *                 'fn' => F\map()
373
 *             ]
374
 *         ]
375
 *     ],
376
 *     'transformations' => [
377
 *         [
378
 *             'operations' => [[
379
 *                 'name' => 'length',
380
 *                 'signatures' => [['List', 'Number']],
381
 *                 'fn' => 'count'
382
 *             ]],
383
 *             'args' => []
384
 *         ]
385
 *     ]
386
 * ]
387
 *
388
 * F\_stream_apply_operation('foo', [], $stream); // throws "Stream: call to unknown operation 'foo'"
389
 * F\_stream_apply_operation('length', [5], $stream); // throws "Stream: wrong arguments (Number, List) given to operation 'length'"
390
 * F\_stream_apply_operation('map', [], $stream); // throws "Stream: wrong arguments (List) given to operation 'map'"
391
 * F\_stream_apply_operation('map', [[1, 2]], $stream); // throws "Stream: wrong arguments (List, List) given to operation 'map'"
392
 *
393
 * ```
394
 *
395
 * @signature String -> [Any] -> Stream -> Stream
396
 * @param  string $name
397
 * @param  array $args
398
 * @param  array $stream
399
 * @return array
400
 */
401
function _stream_apply_operation($name, $args, $stream) {
402
    $operations = getPath(['operations', $name], $stream);
403
    if (null == $operations) {
404
        _stream_throw_error('unknown-operation', $name);
405
    }
406
407
    $argsTypes = append(get('type', $stream), map(type(), $args));
408
    $validOperations = filter(_stream_operation_is_applicable($argsTypes),
409
        chain(_f('_stream_split_operation_signatures'), $operations));
410
    if (0 == length($validOperations)) {
411
        _stream_throw_error('wrong-operation-args', $argsTypes, $name);
412
    }
413
414
    $returnTypes = map(_f('_stream_return_type_of_operation'), $validOperations);
415
    $returnType = reduce(_f('_stream_merge_types'), head($returnTypes), $returnTypes);
416
    return _stream_make(
417
        get('operations', $stream), // operations
418
        get('data', $stream), // data
419
        append([
420
            'operations' => $validOperations,
421
            'args' => $args
422
        ], get('transformations', $stream)), // transformations
423
        $returnType, // type
424
        null, // result
425
        false // resolved
426
    );
427
}
428
429
/**
430
 * Splits an operation with multiple signatures into a list of operation; each having one signature.
431
 * ```php
432
 * F\_stream_split_operation_signatures([
433
 *     'name' => 'length',
434
 *     'signatures' => [['List', 'Number'], ['Array', 'Number']],
435
 *     'fn' => 'count'
436
 * ]); //=> [
437
 *     [
438
 *         'name' => 'length',
439
 *         'signatures' => [['List', 'Number']],
440
 *         'fn' => 'count'
441
 *     ],
442
 *     [
443
 *         'name' => 'length',
444
 *         'signatures' => [['Array', 'Number']],
445
 *         'fn' => 'count'
446
 *     ]
447
 * ]
448
 * ```
449
 *
450
 * @signature Operation -> [Operation]
451
 * @param  array $operation
452
 * @return array
453
 */
454
function _stream_split_operation_signatures($operation) {
455
    $name = get('name', $operation);
456
    $fn = get('fn', $operation);
457
    return map(function($signature) use($name, $fn) {
458
        return [
459
            'name' => $name,
460
            'fn' => $fn,
461
            'signatures' => [$signature]
462
        ];
463
    }, get('signatures', $operation));
464
}
465
466
/**
467
 * Checks if the operation can be applied with the given arguments types.
468
 *
469
 * ```php
470
 * $isApplicable = F\_stream_operation_is_applicable(['Number', 'Number']);
471
 * $isApplicable(F\_stream_operation('add', 'Number -> Number -> Number', F\plus())); //=> true
472
 * $isApplicable(F\_stream_operation('length', 'List|Array|String -> Number', F\length())); //=> false
473
 * F\_stream_operation_is_applicable(
474
 *     ['List'],
475
 *     F\_stream_operation('length', 'List|Array|String -> Number', F\length())
476
 * ); //=> true
477
 * F\_stream_operation_is_applicable(
478
 *     ['String'],
479
 *     F\_stream_operation('length', 'List|Array|String -> Number', F\length())
480
 * ); //=> true
481
 * F\_stream_operation_is_applicable(
482
 *     ['Number'],
483
 *     F\_stream_operation('length', 'List|Array|String -> Number', F\length())
484
 * ); //=> false
485
 * F\_stream_operation_is_applicable(
486
 *     ['Number', 'String'],
487
 *     F\_stream_operation('fill', 'Number -> Any -> List', function(){})
488
 * ); //=> true
489
 * F\_stream_operation_is_applicable(
490
 *     ['Any', 'String'],
491
 *     F\_stream_operation('fill', 'Number -> Any -> List', function(){})
492
 * ); //=> true
493
 * ```
494
 *
495
 * @signature [String] -> Operation -> Boolean
496
 * @param  array $argsTypes
0 ignored issues
show
There is no parameter named $argsTypes. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
497
 * @param  array $operation
0 ignored issues
show
There is no parameter named $operation. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
498
 * @return bool
499
 */
500
function _stream_operation_is_applicable() {
501
    static $operationIsApplicable = false;
502
    $operationIsApplicable = $operationIsApplicable ?: curry(function($argsTypes, $operation) {
503
        return null !== findIndex(function($signature) use($argsTypes) {
504
            $types = init($signature);
505
            if (length($types) == length($argsTypes)) {
506
                return allSatisfies(function($pair) {
507
                    return 'Any' == $pair[0] || 'Any' == $pair[1] || $pair[0] == $pair[1];
508
                }, pairsFrom($types, $argsTypes));
509
            }
510
            return false;
511
        }, get('signatures', $operation));
512
    });
513
    return _apply($operationIsApplicable, func_get_args());
514
}
515
516
/**
517
 * Gets the return type of an operation having a single signature.
518
 *
519
 * ```php
520
 * F\_stream_return_type_of_operation(F\_stream_operation(
521
 *     'count', 'List -> Number'
522
 * )); //=> 'Number'
523
 * F\_stream_return_type_of_operation(F\_stream_operation(
524
 *     'count', 'List ->Function -> String'
525
 * )); //=> 'String'
526
 * F\_stream_return_type_of_operation(F\_stream_operation(
527
 *     'count', 'List ->Function -> Any'
528
 * )); //=> 'Any'
529
 * ```
530
 *
531
 * @signature Operation -> String
532
 * @param  array $operation
533
 * @return string
534
 */
535
function _stream_return_type_of_operation($operation) {
536
    return last(getPath(['signatures', 0], $operation));
537
}
538
539
/**
540
 * Returns `$type1` if types are equal and `Any` if not.
541
 *
542
 * ```php
543
 * F\_stream_merge_types('Number', 'Number'); //=> 'Number'
544
 * F\_stream_merge_types('Number', 'String'); //=> 'Any'
545
 * F\_stream_merge_types('Any', 'String'); //=> 'Any'
546
 * ```
547
 *
548
 * @signature String -> String -> String
549
 * @param  string $type1
550
 * @param  string $type2
551
 * @return string
552
 */
553
function _stream_merge_types($type1, $type2) {
554
    return ($type1 == $type2)
555
        ? $type1
556
        : 'Any';
557
}
558
559
/**
560
 * Computes the result of a stream.
561
 *
562
 * ```php
563
 * $operations = [
564
 *     F\_stream_operation('length', 'List|Array -> Number', 'count'),
565
 *     F\_stream_operation('length', 'String -> Number', 'strlen'),
566
 *     F\_stream_operation('map', 'Function -> List -> List', F\map()),
567
 *     F\_stream_operation('reduce', 'Function -> Any -> List -> Any', F\reduce()),
568
 *     F\_stream_operation('increment', 'Number -> Number', F\plus(1)),
569
 *     F\_stream_operation('upperCase', 'String -> String', F\upperCase()),
570
 *     F\_stream_operation('toString', 'Any -> String', F\toString()),
571
 *     F\_stream_operation('head', 'List -> Any', F\head())
572
 * ];
573
 *
574
 * $stream = F\_stream($operations, [1, 2, 3]);
575
 * $stream = F\_stream_apply_operation('length', [], $stream);
576
 * $stream = F\_stream_resolve($stream);
577
 * F\get('resolved', $stream); //=> true
578
 * F\get('result', $stream); //=> 3
579
 *
580
 * $stream = F\_stream($operations, [1, 2, 3]);
581
 * $stream = F\_stream_apply_operation('map', [F\plus(2)], $stream); // [3, 4, 5]
582
 * $stream = F\_stream_apply_operation('reduce', [F\plus(), 0], $stream); // 12
583
 * $stream = F\_stream_apply_operation('increment', [], $stream); // 13
584
 * $stream = F\_stream_resolve($stream);
585
 * F\get('resolved', $stream); //=> true
586
 * F\get('result', $stream); //=> 13
587
 *
588
 * $stream = F\_stream($operations, []);
589
 * $stream = F\_stream_apply_operation('head', [], $stream); // null
590
 * $stream = F\_stream_apply_operation('increment', [], $stream); // Error
591
 * $stream = F\_stream_apply_operation('toString', [], $stream); // Error
592
 * $stream = F\_stream_resolve( $stream); // throws "Stream: operation 'increment' could not be called with arguments types (Null); expected types are (Number)"
593
 *
594
 * ```
595
 *
596
 * @signature Stream -> Stream
597
 * @param  array $stream
598
 * @return array
599
 */
600
function _stream_resolve($stream) {
601
    if (get('resolved', $stream))
602
        return $stream;
603
    $transformations = get('transformations', $stream);
604
    $transformation = head($transformations);
605
    if (null === $transformation) {
606
        return _stream_make(
607
            get('operations', $stream), // operations
608
            get('data', $stream), // data
609
            get('transformations', $stream), // transformations
610
            get('type', $stream), // type
611
            get('data', $stream), // result
612
            true // resolved
613
        );
614
    }
615
616
    $args = append(get('data', $stream), get('args', $transformation));
617
    $argsTypes = map(type(), $args);
618
    $operations = get('operations', $transformation);
619
    $applicableOperations = filter(_stream_operation_is_applicable($argsTypes), $operations);
620
    if (empty($applicableOperations)) {
621
        $types = map(pipe(get('signatures'), head(), init()), $operations);
622
        _stream_throw_error('wrong-transformation-args', getPath([0, 'name'], $operations), $argsTypes, $types);
623
    }
624
625
    return _stream_resolve(_stream_make(
626
        get('operations', $stream), // operations
627
        _apply(getPath([0, 'fn'], $applicableOperations), $args), // data
628
        tail($transformations), // transformations
629
        get('type', $stream), // type
630
        null, // result
631
        false // resolved
632
    ));
633
}
634
635
/**
636
 * Applies a function to a single argument.
637
 * To be used as the `then()` method of Stream.
638
 *
639
 * @signature (a -> b) -> a -> b
640
 * @param  callable $fn
641
 * @param  mixed $arg
642
 * @return mixed
643
 */
644
function _stream_then($fn, $arg) {
645
    return _apply($fn, [$arg]);
646
}
647
648