/**
* Perform a binary safe arbritrary precision sum of all elements of an array
*
* @param array
* @param integer precision of the result.
* @return string
*/
function bc_array_sum( array $array, $precision ) {
$total = '0';
foreach ($array as $value) {
$total = bcadd($total, $value, $precision);
}
return $total;
}
How in the world do you handle such a generic function when trying to strictly control dependencies? In an MVC deployment pattern it really doesn’t belong in any particular object.
I mean, 99% of the time functions can (and should) be bundled to a class for organizational reasons. And maybe I’m just at a loss at which object should get this unless I put it in a wrapping class for the bc_math library of PHP.
But then there are isolated cases like this that I’m stumped at a bit.
… I wonder where you would draw the line though. I mean, what about other global functions that are derived from extensions that may or may not be enabled?
I’d probably check for these when bootstrapping.
Sorry, just thinking aloud and not really helping.
I’m going with placing the functionality in an object, if say, your cart object required bc_* assistance, delegate it out to a strategy with this object as the dependency.
<?php
class Cart
{
public function __construct(ICalculationStrategy $calculator){
}
}
?>
It’s alright to think aloud. My post was me thinking aloud. For testing safety I probably will use a wrapper function, but it feels “busy” to me, and I’ve groused more than once about how I hate busybox coding.
Eh, I dunno. Summing an array is a pretty basic and fundamental functionality of the language. To me at least, a bc_array_sum() sort of function is not a calculation strategy, but an enhancement to the programming language.
That is not to say that a Cart couldn’t use a calculation strategy, but I just don’t think fundamental functionality like summing an array would be part of said strategy. It just feels much lower level than that.
In such cases, my gut reaction is to place it in a Utility Class, perhaps Array_Utils or something similar. But hell even just the function bc_array_sum() by itself is probably perfectly fine since PHP just gives us functions like array_sum() and all we’re doing is modifying that function of the language.
However, passing a Array_Utils object to a Cart object is a little vague no?
Any object which needs BCMath must be performing some sort of calculation, if this is subject to change (as it is an extension, I would say it is), it must be encapsulated.
I’m well aware of that function’s existence, but it isn’t safe on floats.
To me at least, a bc_array_sum() sort of function is not a calculation strategy, but an enhancement to the programming language.
That is not to say that a Cart couldn’t use a calculation strategy, but I just don’t think fundamental functionality like summing an array would be part of said strategy. It just feels much lower level than that.
In such cases, my gut reaction is to place it in a Utility Class, perhaps Array_Utils or something similar. But hell even just the function bc_array_sum() by itself is probably perfectly fine since PHP just gives us functions like array_sum() and all we’re doing is modifying that function of the language.
It’s fine up until test time. That’s the problem - dependency injection wants all functionality encapsulated somewhere so it can be tracked. Or is it overkill to try to track a framework library’s low level functions?
Then there’s the ugly possibility of namespace rewriting
namespace myNameSpace {
function array_sum( $array )...
}
Convenient yes, but fun doesn’t even begin describing the process of testing code that is looking for the over-ridding function.
It wouldn’t be passed. Utility Classes are typically always static in nature.
If the cart needed to use a calculator, it’d still be passed an instance of ICalculationStrategy, which would use array_sum, bc_array_sum, Array_Utils, or whatever function(s) it needs to use to do it’s thing.
My point was, something like bc_array_sum would be used by ICalculationStrategy (and any other part of the app that needs to use it) in the same way that any other core language function would be used. It’s not strictly part of the ICalculationStrategy implementation… i.e, used by, not part of.
If one could simply override array_sum() with their own implementation, I’d say do that but since we can’t, bc_array_sum() or Array_Utils::sum() seems like a decent choice for extending the language – which is essentially what we’re doing – short of writing a PHP module.
This seems like a very bad idea to me. You potentially introduce unwanted behaviour into any 3rd party code (or even your own existing code) which is unknowingly using a different function than it expects.
On the problem at hand, wouldn’t it be better to make use of ArrayObject?
class BcArrayObject extends ArrayObject {
public function sum() {
//...
}
protected function bcadd() {
}
}
If the function has no internal state (no static variables inside or globals) then it always returns the same result with the same input. This makes it very easy to test.
If there are no external dependencies tucked away inside the function, such as network connections, then it should run super fast. This means you don’t need to mock it.
Are you sure you cannot just use the function as it is? What scenario forces the function into a class?
That sounds about as practical as using a chainsaw to cut a toothpick.
None that I know of. I’m not too worried about testing a function like this itself, but rather testing any other function or class that calls it. Doesn’t that create a hidden dependency on this function?
That’s the sort of thing I’m trying to learn to track.
I’d say: put it in functions.php and be done with it. There is a “dependency” on the function, yes, but then again: there’s a dependency on every function in the core of PHP as well. Make sure you test it, and make sure you include it upon deployment. I don’t see the issue.
@TomB No, it is not a good alternatively. In your own words its marginally slower, and involves the busybox step of setting up the array object before you can call the method. Inefficiencies like that add up over time.
Moving on…
The consensus seems to be that functions that have, as lastcraft put it, no external dependencies and no internal state are eligible to be non-classed. What about binding them to a static object so that the autoloader can find them when they are called for?
class BC {
public static function arraySum($array, $precision) {
// code above.
}
public static function arrayProduct($array, $precision) {
// same as 1st post code, but total starts at 1 and bcmul is used.
}
public static function pow($num, $exp, $precision) ....
}
And maybe a couple other related. The only advantage the class has here is the functions can be located by the autoloader, and the static can be ported between namespaces with the use keyword. The syntax isn’t that bad either
There’s no fundamental difference between a function, or a static method, as long as you make sure it’s deterministic. If you want to use autoloading, there really isn’t any reason you should go for the static class. I hate static, because it is (almost?) impossible to swap for a different implementation, but then, any function in PHP has the exact same “issue”. Just go for it.
Array objects are not arrays. They are a close emulation of them, but importantly they do not work with all of the array functions. So if you make the object that needs to be summed an array object you are committing yourself to not using that library, which is an unnecessary limitation. Sure, you can call $foo->getArrayCopy(), but that would have been unnecessary if you’d used an array to start with.
Add to that the fact it is a waste of resources to cast an array as an ArrayObject unless you need the functionality ArrayObject provides. In my opinion ArrayObject in and of itself adds nothing - it’s usefulness lies in its ability to be extended.
Finally I wrote that statement with the expectation of this code structure
// given an array. We don't know how it was built.
$array
// this
$foo = new SumArrayObject($array);
$sum = $foo->sum();
// is less efficient than
$sum = bc_array_sum($array);
And you are locked into the code above unless you keep all arrays as examples of your SumArrayObject class. Unfortunately you’ll run into trouble if you don’t cast from that class or include that class in the inheritance of any other object that descends from ArrayObject.
So there it is, the longform and not so snarky version of my original answer – you’re proposing using a chainsaw to cut a toothpick.