PlanOut PHP Port Code Review

This PR should provide the project maintainers over at [PlanOut](https://github.com/facebook/planout) an easy way to review adding the proposed PHP port contained within this repository as a submodule for the original project.

Timeline for merge:
- [ ] review proposed port in this branch
- [ ] merge changes back into this repository's master branch
- [ ] update submodule checkout commit in [PlanOut PR](https://github.com/facebook/planout/pull/31) to reflect changes
- [ ] merge port as submodule into main PlanOut repository

cc @eytan for review
**This will do the wrong thing if your choices array includes repeated choices**. You instead want to just keep a map of cumulative weights, one for each element in choices.  There are also some additional sanity checks you might want to make.  This is what our WeightedChoice method looks like:

```php
// returns a randomly selected element from 'choices' with probability 'weights'
final class PlanOutOpWeightedChoice extends PlanOutOpRNG {
  protected function simpleEvaluate(): mixed {
    $choices = must_have_idx($this->parameters, 'choices');
    $weights = must_have_idx($this->parameters, 'weights');
    invariant($choices instanceof Vector, "{$choices} must be a vector.");
    invariant($weights instanceof Vector, "{$weights} must be a vector.");
    invariant(
      $choices->count() === $weights->count(),
      '"choices" and "weights" must have the same length.'
    );
    invariant(
      $choices->count() > 0 && $weights->count() > 0,
      'choices and weights must have at least one elment.'
    );
    $non_numeric_weights = $weights->filter(
      $x ==> !is_numeric($x) || $x < 0.0
    );
    invariant(
      $non_numeric_weights->count() === 0,
      'All weights must be non-negative numbers.'
    );

    // initialize array for making weighted draw
    $cum_sum = 0;
    $cum_weights = array();
    for ($i = 0; $i < $choices->count(); $i++) {
      $cum_sum += $weights[$i];
      $cum_weights[$i] = $cum_sum;
    }
    invariant($cum_sum > 0, 'The sum of weights must be positive.');

    // find first choice where cumulative weight is > stopping value
    $stop_value = $this->getUniform(0.0, $cum_sum);
    for ($i = 0; $i < $choices->count(); $i++) {
      if ($stop_value <= $cum_weights[$i] && $cum_weights[$i] > 0) {
        return $choices[$i];
      }
    }
  }
}
```