Completed
Push — dev ( a4ab81...299db4 )
by Jordan
02:42
created

Numbers::makePi()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 16
Ratio 100 %

Importance

Changes 0
Metric Value
dl 16
loc 16
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 8
nc 3
nop 1
1
<?php
2
3
namespace Samsara\Fermat;
4
5
use Samsara\Exceptions\UsageError\IntegrityConstraint;
6
use Samsara\Fermat\Values\ImmutableFraction;
7
use Samsara\Fermat\Values\ImmutableNumber;
8
use Samsara\Fermat\Values\MutableFraction;
9
use Samsara\Fermat\Values\MutableNumber;
10
use Samsara\Fermat\Types\Base\NumberInterface;
11
12
class Numbers
13
{
14
15
    const MUTABLE = MutableNumber::class;
16
    const IMMUTABLE = ImmutableNumber::class;
17
    const MUTABLE_FRACTION = MutableFraction::class;
18
    const IMMUTABLE_FRACTION = ImmutableFraction::class;
19
    /* 105 digits after decimal, which is going to be overkill in almost all places */
20
    const PI = '3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148';
21
    /* Tau (2pi) to 100 digits */
22
    const TAU = '6.283185307179586476925286766559005768394338798750211641949889184615632812572417997256069650684234136';
23
    /* Euler's Number to 100 digits */
24
    const E = '2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178525166427';
25
    /* Golden Ratio to 100 digits */
26
    const GOLDEN_RATIO = '1.618033988749894848204586834365638117720309179805762862135448622705260462818902449707207204189391137';
27
28
    /**
29
     * @param $type
30
     * @param $value
31
     * @param int|null $precision
32
     * @param int $base
33
     * @return ImmutableNumber|MutableNumber|ImmutableFraction|MutableFraction|NumberInterface
34
     */
35
    public static function make($type, $value, $precision = null, $base = 10)
36
    {
37
38
        if ($type == self::IMMUTABLE) {
39
            return new ImmutableNumber(trim($value), $precision, $base);
40
        } elseif ($type == self::MUTABLE) {
41
            return new MutableNumber(trim($value), $precision, $base);
42
        } elseif ($type == self::IMMUTABLE_FRACTION) {
43
            return self::makeFractionFromString($value, $type)->convertToBase($base);
44
        } elseif ($type == self::MUTABLE_FRACTION) {
45
            return self::makeFractionFromString($value, $type)->convertToBase($base);
46
        } else {
47
            $reflector = new \ReflectionClass($type);
48
49
            if ($reflector->implementsInterface(NumberInterface::class)) {
50
                /** @var NumberInterface $customNumber */
51
                $customNumber = $reflector->newInstance([
52
                    trim($value),
53
                    $precision,
54
                    $base
55
                ]);
56
                return $customNumber;
57
            }
58
        }
59
60
        throw new \InvalidArgumentException('The $type argument was not an implementation of NumberInterface.');
61
    }
62
63
    /**
64
     * @param $type
65
     * @param $value
66
     * @param int|null $precision
67
     * @param int $base
68
     * @return NumberInterface
69
     */
70
    public static function makeFromBase10($type, $value, $precision = null, $base = 10)
71
    {
72
        /**
73
         * @var ImmutableNumber|MutableNumber
74
         */
75
        $number = self::make($type, $value, $precision, 10);
76
77
        return $number->convertToBase($base);
78
    }
79
80
    /**
81
     * @param $type
82
     * @param int|float|string|NumberInterface $value
83
     * @param int|null $precision
84
     * @param int $base
85
     *
86
*@throws \InvalidArgumentException
87
     * @return ImmutableNumber|MutableNumber|NumberInterface|ImmutableNumber[]|MutableNumber[]|NumberInterface[]
88
     */
89
    public static function makeOrDont($type, $value, $precision = null, $base = 10)
90
    {
91
92
        if (is_numeric($value)) {
93
            return self::make($type, $value, $precision, $base);
94
        } elseif (is_object($value)) {
95
            $reflector = new \ReflectionClass($value);
96
97
            if ($value instanceof $type) {
98
                return $value;
99
            }
100
101
            if ($reflector->implementsInterface(NumberInterface::class)) {
102
                return self::make($type, $value->getValue(), $precision, $base);
103
            }
104
        } elseif (is_array($value)) {
105
            $newInput = [];
106
            
107
            foreach ($value as $key => $item) {
108
                $newInput[$key] = self::makeOrDont($type, $item, $precision, $base);
109
            }
110
111
            return $newInput;
112
        }
113
114
        throw new \InvalidArgumentException('The $input argument was not numeric or an implementation of NumberInterface.');
115
116
    }
117
118
    public static function makeFractionFromString($value, $type = self::IMMUTABLE_FRACTION)
119
    {
120
        $parts = explode('/', $value);
121
122
        if (count($parts) > 2) {
123
            throw new IntegrityConstraint(
124
                'Only one division symbol (/) can be used',
125
                'Change the calling code to not provide more than one division symbol',
126
                'makeFractionFromString needs either one or zero division symbols in the $value argument; '.$value.' given'
127
            );
128
        }
129
130
        $numerator = Numbers::make(Numbers::IMMUTABLE, trim(ltrim($parts[0])))->round();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Samsara\Fermat\Types\Base\NumberInterface as the method round() does only exist in the following implementations of said interface: Samsara\Fermat\Values\ImmutableNumber, Samsara\Fermat\Values\MutableNumber.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
131
        $denominator = isset($parts[1]) ? Numbers::make(Numbers::IMMUTABLE, trim(ltrim($parts[1])))->round() : Numbers::makeOne();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Samsara\Fermat\Types\Base\NumberInterface as the method round() does only exist in the following implementations of said interface: Samsara\Fermat\Values\ImmutableNumber, Samsara\Fermat\Values\MutableNumber.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
132
133
        if ($type == self::IMMUTABLE_FRACTION) {
134
            return new ImmutableFraction($numerator, $denominator);
135
        } elseif ($type == self::MUTABLE_FRACTION) {
136
            return new MutableFraction($numerator, $denominator);
137
        } else {
138
            throw new IntegrityConstraint(
139
                'Type must be ImmutableFraction or MutableFraction',
140
                'Alter to calling code to use the correct type',
141
                'makeFractionFromString can only make objects of type ImmutableFraction or MutableFraction; '.$type.' given'
142
            );
143
        }
144
    }
145
146
    /**
147
     * @param int|null $precision
148
     *
149
     * @return NumberInterface
150
     */
151 View Code Duplication
    public static function makePi($precision = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
152
    {
153
        
154
        if (!is_null($precision) && ($precision > 105 || $precision < 1)) {
155
            throw new \InvalidArgumentException('The PI constant cannot have a precision higher than the constant stored (105).');
156
        }
157
        
158
        $pi = self::make(self::IMMUTABLE, self::PI);
159
        
160
        if (!is_null($precision)) {
161
            return $pi->roundToPrecision($precision);
162
        } else {
163
            return $pi;
164
        }
165
        
166
    }
167
168
    /**
169
     * @param int|null $precision
170
     *
171
     * @return NumberInterface
172
     */
173 View Code Duplication
    public static function makeTau($precision = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
174
    {
175
        if (!is_null($precision) && ($precision > 100 || $precision < 1)) {
176
            throw new \InvalidArgumentException('The TAU constant cannot have a precision higher than the constant stored (100).');
177
        }
178
179
        $tau = self::make(self::IMMUTABLE, self::TAU);
180
181
        if (!is_null($tau)) {
182
            return $tau->roundToPrecision($precision);
183
        } else {
184
            return $tau;
185
        }
186
    }
187
188
    /**
189
     * @param int|null $precision
190
     *
191
     * @return NumberInterface
192
     */
193
    public static function make2Pi($precision = null)
194
    {
195
        return self::makeTau($precision);
196
    }
197
198
    /**
199
     * @param int|null $precision
200
     *
201
     * @return NumberInterface
202
     */
203 View Code Duplication
    public static function makeE($precision = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
204
    {
205
206
        if (!is_null($precision) && ($precision > 100 || $precision < 1)) {
207
            throw new \InvalidArgumentException('The E constant cannot have a precision higher than the constant stored (100).');
208
        }
209
210
        $e = self::make(self::IMMUTABLE, self::E);
211
212
        if (!is_null($e)) {
213
            return $e->roundToPrecision($precision);
214
        } else {
215
            return $e;
216
        }
217
218
    }
219
220
    /**
221
     * @param int|null $precision
222
     *
223
     * @return NumberInterface
224
     */
225 View Code Duplication
    public static function makeGoldenRatio($precision = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
226
    {
227
228
        if (!is_null($precision) && ($precision > 100 || $precision < 1)) {
229
            throw new \InvalidArgumentException('The Golden Ratio constant cannot have a precision higher than the constant stored (100).');
230
        }
231
232
        $goldenRatio = self::make(self::IMMUTABLE, self::GOLDEN_RATIO);
233
234
        if (!is_null($precision)) {
235
            return $goldenRatio->roundToPrecision($precision);
236
        } else {
237
            return $goldenRatio;
238
        }
239
240
    }
241
242
    /**
243
     * @return ImmutableNumber
244
     */
245
    public static function makeOne()
246
    {
247
        return self::make(self::IMMUTABLE, 1);
248
    }
249
250
    /**
251
     * @return ImmutableNumber
252
     */
253
    public static function makeZero()
254
    {
255
        return self::make(self::IMMUTABLE, 0);
256
    }
257
258
}