aboutsummaryrefslogtreecommitdiffstats
path: root/includes/Settings/Source
diff options
context:
space:
mode:
Diffstat (limited to 'includes/Settings/Source')
-rw-r--r--includes/Settings/Source/ArraySource.php25
-rw-r--r--includes/Settings/Source/FileSource.php161
-rw-r--r--includes/Settings/Source/Format/JsonFormat.php57
-rw-r--r--includes/Settings/Source/Format/SettingsFormat.php35
-rw-r--r--includes/Settings/Source/SettingsSource.php25
5 files changed, 303 insertions, 0 deletions
diff --git a/includes/Settings/Source/ArraySource.php b/includes/Settings/Source/ArraySource.php
new file mode 100644
index 000000000000..a7a1cdfbf66f
--- /dev/null
+++ b/includes/Settings/Source/ArraySource.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace MediaWiki\Settings\Source;
+
+/**
+ * Settings loaded from an array.
+ *
+ * @since 1.38
+ */
+class ArraySource implements SettingsSource {
+
+ private $settings;
+
+ public function __construct( array $settings ) {
+ $this->settings = $settings;
+ }
+
+ public function load(): array {
+ return $this->settings;
+ }
+
+ public function __toString(): string {
+ return '<array>';
+ }
+}
diff --git a/includes/Settings/Source/FileSource.php b/includes/Settings/Source/FileSource.php
new file mode 100644
index 000000000000..2320f377049b
--- /dev/null
+++ b/includes/Settings/Source/FileSource.php
@@ -0,0 +1,161 @@
+<?php
+
+namespace MediaWiki\Settings\Source;
+
+use MediaWiki\Settings\SettingsBuilderException;
+use MediaWiki\Settings\Source\Format\JsonFormat;
+use MediaWiki\Settings\Source\Format\SettingsFormat;
+use UnexpectedValueException;
+use Wikimedia\AtEase\AtEase;
+
+/**
+ * Settings loaded from a local file path.
+ *
+ * @since 1.38
+ */
+class FileSource implements SettingsSource {
+ /**
+ * Default format with which to attempt decoding if none are given to the
+ * constructor.
+ */
+ private const DEFAULT_FORMAT = JsonFormat::class;
+
+ /**
+ * Possible formats.
+ * @var array
+ */
+ private $formats;
+
+ /**
+ * Path to local file.
+ * @var string
+ */
+ private $path;
+
+ /**
+ * Constructs a new FileSource for the given path and possible matching
+ * formats. The first format to match the path's file extension will be
+ * used to decode the content.
+ *
+ * An end-user caller may be explicit about the given path's format by
+ * providing only one format.
+ *
+ * <code>
+ * <?php
+ * $source = new FileSource( 'my/settings.json', new JsonFormat() );
+ * $source->load();
+ * </code>
+ *
+ * While a generalized caller may want to pass a number of supported
+ * formats.
+ *
+ * <code>
+ * <?php
+ * function loadAllPossibleFormats( string $path ) {
+ * $source = new FileSource(
+ * $path,
+ * new JsonFormat(),
+ * new YamlFormat(),
+ * new TomlFormat()
+ * )
+ * }
+ * </code>
+ *
+ * @param string $path
+ * @param SettingsFormat ...$formats
+ */
+ public function __construct( string $path, SettingsFormat ...$formats ) {
+ $this->path = $path;
+ $this->formats = $formats;
+
+ if ( empty( $this->formats ) ) {
+ $class = self::DEFAULT_FORMAT;
+ $this->formats = [ new $class() ];
+ }
+ }
+
+ /**
+ * Loads contents from the file and decodes them using the first format
+ * to claim support for the file's extension.
+ *
+ * @throws SettingsBuilderException
+ * @return array
+ */
+ public function load(): array {
+ $ext = pathinfo( $this->path, PATHINFO_EXTENSION );
+
+ // If there's only one format, don't bother to match the file
+ // extension.
+ if ( count( $this->formats ) == 1 ) {
+ return $this->readAndDecode( $this->formats[0] );
+ }
+
+ foreach ( $this->formats as $format ) {
+ if ( $format->supportsFileExtension( $ext ) ) {
+ return $this->readAndDecode( $format );
+ }
+ }
+
+ throw new SettingsBuilderException(
+ "None of the given formats ({formats}) are suitable for '{path}'",
+ [
+ 'formats' => implode( ', ', $this->formats ),
+ 'path' => $this->path,
+ ]
+ );
+ }
+
+ /**
+ * Returns this file source as a string.
+ *
+ * @return string
+ */
+ public function __toString(): string {
+ return $this->path;
+ }
+
+ /**
+ * Reads and decodes the file contents using the given format.
+ *
+ * @param SettingsFormat $format
+ *
+ * @return array
+ * @throws SettingsBuilderException
+ */
+ private function readAndDecode( SettingsFormat $format ): array {
+ $contents = AtEase::quietCall( 'file_get_contents', $this->path );
+
+ if ( $contents === false ) {
+ if ( !is_readable( $this->path ) ) {
+ throw new SettingsBuilderException(
+ "File '{path}' is not readable",
+ [ 'path' => $this->path ]
+ );
+ }
+
+ if ( is_dir( $this->path ) ) {
+ throw new SettingsBuilderException(
+ "'{path}' is a directory, not a file",
+ [ 'path' => $this->path ]
+ );
+ }
+
+ throw new SettingsBuilderException(
+ "Failed to read file '{path}'",
+ [ 'path' => $this->path ]
+ );
+ }
+
+ try {
+ return $format->decode( $contents );
+ } catch ( UnexpectedValueException $e ) {
+ throw new SettingsBuilderException(
+ "Failed to decode file '{path}': {message}",
+ [
+ 'path' => $this->path,
+ 'message' => $e->getMessage()
+ ]
+ );
+ }
+ }
+}
diff --git a/includes/Settings/Source/Format/JsonFormat.php b/includes/Settings/Source/Format/JsonFormat.php
new file mode 100644
index 000000000000..1151ab93a2cb
--- /dev/null
+++ b/includes/Settings/Source/Format/JsonFormat.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace MediaWiki\Settings\Source\Format;
+
+use UnexpectedValueException;
+
+/**
+ * Decodes settings data from JSON.
+ */
+class JsonFormat implements SettingsFormat {
+
+ /**
+ * Decodes JSON.
+ *
+ * @param string $data JSON string to decode.
+ *
+ * @return array
+ * @throws UnexpectedValueException
+ */
+ public function decode( string $data ): array {
+ $settings = json_decode( $data, true );
+
+ if ( $settings === null ) {
+ throw new UnexpectedValueException(
+ 'Failed to decode JSON: ' . json_last_error_msg()
+ );
+ }
+
+ if ( !is_array( $settings ) ) {
+ throw new UnexpectedValueException(
+ 'Decoded settings must be an array'
+ );
+ }
+
+ return $settings;
+ }
+
+ /**
+ * Returns true for the file extension 'json'. Case insensitive.
+ *
+ * @param string $ext File extension.
+ *
+ * @return bool
+ */
+ public function supportsFileExtension( string $ext ): bool {
+ return strtolower( $ext ) == 'json';
+ }
+
+ /**
+ * Returns the name/type of this format (JSON).
+ *
+ * @return string
+ */
+ public function __toString(): string {
+ return 'JSON';
+ }
+}
diff --git a/includes/Settings/Source/Format/SettingsFormat.php b/includes/Settings/Source/Format/SettingsFormat.php
new file mode 100644
index 000000000000..943faa88d544
--- /dev/null
+++ b/includes/Settings/Source/Format/SettingsFormat.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace MediaWiki\Settings\Source\Format;
+
+use Stringable;
+use UnexpectedValueException;
+
+/**
+ * A SettingsFormat is meant to detect supported file types and/or decode
+ * source contents into settings arrays.
+ *
+ * @since 1.38
+ * @todo mark as stable before the 1.38 release
+ */
+interface SettingsFormat extends Stringable {
+ /**
+ * Decodes the given settings data and returns an associative array.
+ *
+ * @param string $data Settings data.
+ *
+ * @return array
+ * @throws UnexpectedValueException
+ */
+ public function decode( string $data ): array;
+
+ /**
+ * Whether or not the format claims to support a file with the given
+ * extension.
+ *
+ * @param string $ext File extension.
+ *
+ * @return bool
+ */
+ public function supportsFileExtension( string $ext ): bool;
+}
diff --git a/includes/Settings/Source/SettingsSource.php b/includes/Settings/Source/SettingsSource.php
new file mode 100644
index 000000000000..31df14fefde1
--- /dev/null
+++ b/includes/Settings/Source/SettingsSource.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace MediaWiki\Settings\Source;
+
+use MediaWiki\Settings\SettingsBuilderException;
+use Stringable;
+
+/**
+ * A SettingsSource is meant to represent any kind of local or remote store
+ * from which settings can be read, be it a local file, remote URL, database,
+ * etc. It is concerned with reading (and possibly decoding) settings data.
+ *
+ * @since 1.38
+ * @todo mark as stable before the 1.38 release
+ */
+interface SettingsSource extends Stringable {
+ /**
+ * Loads and returns all settings from this source as an associative
+ * array.
+ *
+ * @return array
+ * @throws SettingsBuilderException
+ */
+ public function load(): array;
+}