ServiceOptions.php 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. <?php
  2. namespace MediaWiki\Config;
  3. use Config;
  4. use InvalidArgumentException;
  5. use Wikimedia\Assert\Assert;
  6. /**
  7. * A class for passing options to services. It can be constructed from a Config, and in practice
  8. * most options will be taken from site configuration, but they don't have to be. The options passed
  9. * are copied and will not reflect subsequent updates to site configuration (assuming they're not
  10. * objects).
  11. *
  12. * Services that take this type as a parameter to their constructor should specify a list of the
  13. * keys they expect to receive in an array. The convention is to make it a public const called
  14. * CONSTRUCTOR_OPTIONS. In the constructor, they should call assertRequiredOptions() to make sure
  15. * that they weren't passed too few or too many options. This way it's clear what each class
  16. * depends on, and that it's getting passed the correct set of options. (This means there are no
  17. * optional options. This makes sense for services, since they shouldn't be constructed by
  18. * outside code.)
  19. *
  20. * @since 1.34
  21. */
  22. class ServiceOptions {
  23. private $keys = [];
  24. private $options = [];
  25. /**
  26. * @param string[] $keys Which keys to extract from $sources
  27. * @param Config|array ...$sources Each source is either a Config object or an array. If the
  28. * same key is present in two sources, the first one takes precedence. Keys that are not in
  29. * $keys are ignored.
  30. * @throws InvalidArgumentException if one of $keys is not found in any of $sources
  31. */
  32. public function __construct( array $keys, ...$sources ) {
  33. $this->keys = $keys;
  34. foreach ( $keys as $key ) {
  35. foreach ( $sources as $source ) {
  36. if ( $source instanceof Config ) {
  37. if ( $source->has( $key ) ) {
  38. $this->options[$key] = $source->get( $key );
  39. continue 2;
  40. }
  41. } else {
  42. if ( array_key_exists( $key, $source ) ) {
  43. $this->options[$key] = $source[$key];
  44. continue 2;
  45. }
  46. }
  47. }
  48. throw new InvalidArgumentException( "Key \"$key\" not found in input sources" );
  49. }
  50. }
  51. /**
  52. * Assert that the list of options provided in this instance exactly match $expectedKeys,
  53. * without regard for order.
  54. *
  55. * @param string[] $expectedKeys
  56. */
  57. public function assertRequiredOptions( array $expectedKeys ) {
  58. if ( $this->keys !== $expectedKeys ) {
  59. $extraKeys = array_diff( $this->keys, $expectedKeys );
  60. $missingKeys = array_diff( $expectedKeys, $this->keys );
  61. Assert::precondition( !$extraKeys && !$missingKeys,
  62. (
  63. $extraKeys
  64. ? 'Unsupported options passed: ' . implode( ', ', $extraKeys ) . '!'
  65. : ''
  66. ) . ( $extraKeys && $missingKeys ? ' ' : '' ) . (
  67. $missingKeys
  68. ? 'Required options missing: ' . implode( ', ', $missingKeys ) . '!'
  69. : ''
  70. )
  71. );
  72. }
  73. }
  74. /**
  75. * @param string $key
  76. * @return mixed
  77. */
  78. public function get( $key ) {
  79. if ( !array_key_exists( $key, $this->options ) ) {
  80. throw new InvalidArgumentException( "Unrecognized option \"$key\"" );
  81. }
  82. return $this->options[$key];
  83. }
  84. }