• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rotexsoft / versatile-collections / 6764770557

07 Sep 2023 08:54AM UTC coverage: 98.356% (-0.2%) from 98.536%
6764770557

push

github

rotimi
Upgraded to rector 0.18 & did some refactoring for PHP 8.1

49 of 53 new or added lines in 9 files covered. (92.45%)

1 existing line in 1 file now uncovered.

1017 of 1034 relevant lines covered (98.36%)

9.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.05
/src/helper-functions.php
1
<?php /** @noinspection DuplicatedCode */ /** @noinspection PhpFullyQualifiedNameUsageInspection */
2
declare(strict_types=1);
3
namespace VersatileCollections {
4

5
    use Error;
6
    use Exception;
7
    use InvalidArgumentException;
8
    use LengthException;
9
    use ReflectionClass;
10
    use ReflectionException;
11
    use RuntimeException;
12
    use stdClass;
13
    use TypeError;
14

15
    /**
16
     * A robust way of retrieving the value of a specified property in
17
     * an instance of a class.
18
     *
19
     * Works with \stdClass objects created from arrays with numeric key(s)
20
     * (the value of the propertie(s) with numeric key(s) in such \stdClass
21
     * objects will be retrieved by this function).
22
     * 
23
     * @param bool $access_private_or_protected true if value associated with private or protected property should be returned.
24
     *                                          If false is specified and you try to access a private or protected property, a
25
     *                                          \RuntimeException will be thrown.
26
     * @return mixed
27
     *
28
     * @throws InvalidArgumentException
29
     * @throws RuntimeException
30
     * @throws ReflectionException
31
     *
32
     * @noinspection DuplicatedCode
33
     */
34
    function get_object_property_value(object $obj, string|int $property, mixed $default_val=null, bool $access_private_or_protected=false)
35
    {
36
        $property = ''.$property;
36✔
37
        $return_val = $default_val;
36✔
38

39
        if( object_has_property($obj, $property) ) {
36✔
40

41
            if( $obj instanceof stdClass ) {
36✔
42

43
                // will work for stdClass instances that were created 
44
                // by casting an array with numeric and / or string keys to an object.
45
                // e.g. ( (object)[ 777=>'Some Value', 'a_string_property'=>'Another Value'] )
46
                $obj_as_array = ((array)$obj);
20✔
47
                $return_val = $obj_as_array[$property];
20✔
48

49
            } else if(
50
                \property_exists ($obj, $property) // is either public, protected or private
28✔
51
                && !\array_key_exists($property, \get_object_vars($obj)) // definitely a protected or a private property
28✔
52
            ) {
53
                if( $access_private_or_protected ) {
16✔
54

55
                    // use some reflection gymnastics to retrieve the value
56
                    $reflection_class = new ReflectionClass($obj::class);
8✔
57
                    $property = $reflection_class->getProperty($property);
8✔
58
                    $property->setAccessible(true);
8✔
59
                    $return_val = $property->getValue($obj); //$property->setAccessible(false);
8✔
60

61
                } else {
62

63
                    // throw exception letting user know that they are
64
                    // trying to access a private or protected value
65
                    $function = __FUNCTION__;
8✔
66
                    $ns = __NAMESPACE__;
8✔
67
                    $obj_type = $obj::class;
8✔
68
                    $msg = "Error [{$ns}::{$function}(...)]:"
8✔
69
                    . " Trying to access a protected or private property named `{$property}` on the instance of `$obj_type` below:" . PHP_EOL . var_to_string($obj)
8✔
70
                    . PHP_EOL . "To access a protected or private property named `{$property}` call `{$ns}::{$function}()` with `true` as the fourth argument.";
8✔
71
                    throw new RuntimeException($msg);
12✔
72
                }
73

74
            } else {
75

76
                $return_val = $obj->{$property};
16✔
77
            }
78
        }
79

80
        return $return_val;
28✔
81
    }
82

83
    /**
84
     * A more robust way than property_exists of checking if an instance of a class
85
     * has a specified property.
86
     * 
87
     * @throws InvalidArgumentException
88
     *
89
     * @noinspection PhpMissingReturnTypeInspection
90
     */
91
    function object_has_property(object $obj, string|int $property): bool
92
    {
93
        $property = ''.$property;
68✔
94

95
        return (
68✔
96
                    \property_exists($obj, $property) // check if property is public, protected or private
68✔
97
                    ||
68✔
98
                    (
68✔
99
                        \method_exists($obj, '__isset')
68✔
100
                        && \method_exists($obj, '__get')
68✔
101
                        && $obj->__isset($property)
68✔
102
                    ) // check if property is accessible via magic method
68✔
103
                    ||
68✔
104
                    (
68✔
105
                        \array_key_exists( $property, ((array)$obj) )
68✔
106
                    ) // works for arrays with numeric keys that were
68✔
107
                      // cast into an object. E.g $item === ((object)[777=>'boo'])
68✔
108
                      // Also detects properties that are not defined in the class
68✔
109
                      // (i.e. they were not explicitly defined as public, private
68✔
110
                      // or protected) but were assigned during run-time, like 
68✔
111
                      // properties assigned to instances of \stdClass (which 
68✔
112
                      // could also be assigned to instances of any php class)
68✔
113
               );
68✔
114
    }
115

116
    /**
117
     * A potentially more cryptographically secure way  (as opposed to array_rand) 
118
     * of getting a random key from an array.
119
     * If the system contains sources of randomness like: 
120
     * On Windows, » CryptGenRandom() will always be used.
121
     * On Linux, the » getrandom(2) syscall will be used if available.
122
     * On other platforms, /dev/urandom will be used.
123
     * If none of the aforementioned sources are available, then array_rand will be used.
124
     *
125
     * @param array $array array from which a random key is to be extracted
126
     *  
127
     * @return string|int|null a random key from the specified array
128
     *  
129
     * @throws LengthException
130
     */
131
    function random_array_key(array $array)
132
    {
133
        if( \count($array) <= 0 ) {
28✔
134

135
            $function = __FUNCTION__;
4✔
136
            $ns = __NAMESPACE__;
4✔
137
            $msg = "Error [{$ns}::{$function}(...)]: You cannot request a random key from an empty array.";
4✔
138
            throw new LengthException($msg);
4✔
139
        }
140

141
        $error_occurred = false;
28✔
142
        $keys = \array_keys($array);
28✔
143
        $random_key = null;
28✔
144

145
        try {
146
            // random_int is more cryptographically secure than array_rand
147
            $min = 0;
28✔
148
            $max = \count($keys) - 1;
28✔
149
            $random_index = \random_int( $min, $max );
28✔
150
            $random_key = $keys[$random_index];
28✔
151

NEW
152
        } catch ( TypeError) {
×
153

154
            // random_int: If invalid parameters are given, a TypeError will be thrown.
155
            // This is okay, so long as `Error` is caught before `Exception`.
156
            // Probably will never occur since $min and $max above will always be ints.
157
            $error_occurred = true;
×
158

NEW
159
        } catch ( Error) {
×
160

161
            // random_int: If max is less than min, an Error will be thrown.
162
            // This is required, if you do not need to do anything just rethrow.
163
            // Probably will never occur since $min and $max above will always have $min < $max.
164
            $error_occurred = true;
×
165

NEW
166
        } catch ( Exception) {
×
167

168
            // random_int: If an appropriate source of randomness cannot be found, an Exception will be thrown.
169
            // This is optional and maybe omitted if you do not want to handle errors
170
            // during generation.
171
            // Hard to consistently test this since it's an internal 
172
            // random number generator specific logic error.
173
            $error_occurred = true;
×
174
        }
175

176
        if( $error_occurred ) {
28✔
177

178
            // fallback to array_rand since an error / exception occurred
179
            // while trying to use random_int
180
            $random_key = \array_rand($array);
×
181
        }
182

183
        return $random_key;
28✔
184
    }
185

186
    /**
187
     * A potentially more cryptographically secure way  (as opposed to array_rand) 
188
     * of getting unique random keys from an array.
189
     *  
190
     * If the system contains sources of randomness like: 
191
     * On Windows, » CryptGenRandom() will always be used.
192
     * On Linux, the » getrandom(2) syscall will be used if available.
193
     * On other platforms, /dev/urandom will be used.
194
     * If none of the aforementioned sources are available, then array_rand will be used.
195
     *  
196
     * @param array $array array from which a random keys are to be extracted
197
     * @param int $number_of_random_keys number of unique random keys to return
198
     *  
199
     * @return array an array of random keys
200
     * @throws LengthException
201
     * @throws InvalidArgumentException
202
     */
203
    function random_array_keys(array $array, int $number_of_random_keys = 1): array
204
    {
205
        if( \count($array) <= 0 ) {
24✔
206

207
            $function = __FUNCTION__;
8✔
208
            $ns = __NAMESPACE__;
8✔
209
            $msg = "Error [{$ns}::{$function}(...)]: You cannot request random keys from an empty array.";
8✔
210
            throw new LengthException($msg);
8✔
211
        }
212

213
        if(  $number_of_random_keys > \count($array) ) {
20✔
214

215
            $function = __FUNCTION__;
4✔
216
            $ns = __NAMESPACE__;
4✔
217
            $num_items = \count($array);
4✔
218
            $msg = "Error [{$ns}::{$function}(...)]:"
4✔
219
            . " You requested {$number_of_random_keys} key(s), but there are only {$num_items} keys available.";
4✔
220
            throw new InvalidArgumentException($msg);
4✔
221
        }
222

223
        $random_keys = [];
16✔
224

225
        for( $i=0; $i < $number_of_random_keys; $i++) {
16✔
226

227
            $current_random_key = null;
16✔
228

229
            do { // loop to ensure uniqueness of selected random keys
230
                $current_random_key = random_array_key($array);
16✔
231

232
            } while( \in_array($current_random_key, $random_keys, true) );
16✔
233

234
            $random_keys[] = $current_random_key;
16✔
235
        }
236

237
        return $random_keys; 
16✔
238
    }
239

240
    /**
241
     * Generate a (screen/user)-friendly string representation of a variable.
242
     *  
243
     * @return string a (screen / user)-friendly string representation of a variable
244
     * 
245
     * @psalm-suppress ForbiddenCode
246
     */
247
    function var_to_string(mixed $var): string 
248
    {
249
        ob_start(); // Start capturing the output
144✔
250

251
        var_dump($var);
144✔
252

253
        // Get the captured output, close the buffer & return the captured output
254
        $output = ob_get_clean();
144✔
255

256
        return ($output === false) ? '' : $output;
144✔
257
    }
258

259
    /**
260
     * Generate a (screen/user)-friendly string representation of a variable and print it out to the screen.
261
     *  
262
     * @return void
263
     */
264
    function dump_var(mixed $var): void 
265
    {
266
        $line_breaker = (PHP_SAPI === 'cli') ? PHP_EOL : '<br>';
4✔
267
        echo var_to_string($var). $line_breaker . $line_breaker;
4✔
268
    }
269
} // namespace VersatileCollections
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc