FluentDOM   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 248
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 12

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 248
ccs 73
cts 73
cp 1
rs 9.8
c 0
b 0
f 0
wmc 31
lcom 3
cbo 12

12 Methods

Rating   Name   Duplication   Size   Complexity  
A load() 0 7 3
A create() 0 3 1
A save() 0 9 3
A Query() 0 9 2
A QueryCss() 0 11 1
A setLoader() 0 11 3
A registerLoader() 0 16 4
A getDefaultLoaders() 0 6 2
A registerSerializerFactory() 0 5 2
B getXPathTransformer() 0 16 6
A registerXpathTransformer() 0 6 2
B getSerializerFactories() 0 24 2
1
<?php
2
3
use FluentDOM\Loadable;
4
5
abstract class FluentDOM {
6
7
  /**
8
   * @var FluentDOM\Loadable
9
   */
10
  private static $_loader;
11
12
  /**
13
   * @var array
14
   */
15
  private static $_xpathTransformers = [];
16
17
  /**
18
   * @var FluentDOM\Loadable
19
   */
20
  private static $_defaultLoaders = [];
21
22
  /**
23
   * @var FluentDOM\Serializer\Factory\Group
24
   */
25
  private static $_serializerFactories;
26
27
  /**
28
   * Load a data source into a FluentDOM\DOM\Document
29
   *
30
   * @param mixed $source
31
   * @param string $contentType
32
   * @param array $options
33
   * @return \FluentDOM\DOM\Document
34
   */
35 5
  public static function load($source, string $contentType = 'text/xml', array $options = []): \FluentDOM\DOM\Document {
36 5
    if (NULL === self::$_loader) {
37 4
      self::$_loader = self::getDefaultLoaders();
38
    }
39 5
    $result = self::$_loader->load($source, $contentType, $options);
40 5
    return $result instanceof \DOMDocument ? $result : $result->getDocument();
41
  }
42
43
  /**
44
   * Return a FluentDOM Creator instance, allow to create a DOM using nested function calls
45
   *
46
   * @param string $version
47
   * @param string $encoding
48
   * @return FluentDOM\Creator
49
   */
50 2
  public static function create(string $version = '1.0', string $encoding = 'UTF-8'): \FluentDOM\Creator {
51 2
    return new FluentDOM\Creator($version, $encoding);
52
  }
53
54
  /**
55
   * @param \DOMNode|FluentDOM\Query $node
56
   * @param string $contentType
57
   * @return string
58
   * @throws \FluentDOM\Exceptions\NoSerializer
59
   */
60 5
  public static function save($node, string $contentType = 'text/xml'): string {
61 5
    if ($node instanceof FluentDOM\Query) {
62 1
      $node = $node->document;
63
    }
64 5
    if ($serializer = self::getSerializerFactories()->createSerializer($contentType, $node)) {
65 4
      return (string)$serializer;
66
    }
67 1
    throw new FluentDOM\Exceptions\NoSerializer($contentType);
68
  }
69
70
  /**
71
   * Create an FluentDOM::Query instance and load the source into it.
72
   *
73
   * @param mixed $source
74
   * @param string $contentType
75
   * @param array $options
76
   * @return FluentDOM\Query
77
   * @throws \LogicException
78
   * @throws \OutOfBoundsException
79
   * @throws \InvalidArgumentException
80
   * @throws \FluentDOM\Exceptions\InvalidSource\Variable
81
   */
82 3
  public static function Query(
83
    $source = NULL, string $contentType = 'text/xml', array $options = []
84
  ): FluentDOM\Query {
85 3
    $query = new FluentDOM\Query();
86 3
    if (NULL !== $source) {
87 2
      $query->load($source, $contentType, $options);
88
    }
89 3
    return $query;
90
  }
91
92
  /**
93
   * Create an FluentDOM::Query instance with a modified selector callback.
94
   * This allows to use CSS selectors instead of Xpath expression.
95
   *
96
   * @param mixed $source
97
   * @param string $contentType
98
   * @param array $options
99
   * @throws \LogicException
100
   * @return FluentDOM\Query
101
   * @throws \InvalidArgumentException
102
   * @throws \OutOfBoundsException
103
   * @throws \FluentDOM\Exceptions\InvalidSource\Variable
104
   * @codeCoverageIgnore
105
   */
106
  public static function QueryCss(
107
    $source = NULL, string $contentType = 'text/xml', array $options = []
108
  ): FluentDOM\Query {
109
    $builder = self::getXPathTransformer();
110
    $query = self::Query($source, $contentType, $options);
111
    $isHtml = ($query->contentType === 'text/html');
112
    $query->onPrepareSelector = function($selector, $contextMode) use ($builder, $isHtml) {
113
      return $builder->toXpath($selector, $contextMode, $isHtml);
114
    };
115
    return $query;
116
  }
117
118
  /**
119
   * Set a loader used in FluentDOM::load(), NULL will reset the loader.
120
   * If no loader is provided an FluentDOM\Loader\Standard() will be created.
121
   *
122
   * @param FluentDOM\Loadable|NULL $loader
123
   * @throws \FluentDOM\Exceptions\InvalidArgument
124
   */
125 3
  public static function setLoader($loader) {
126 3
    if ($loader instanceof FluentDOM\Loadable) {
127 1
      self::$_loader = $loader;
128 2
    } elseif (NULL === $loader) {
129 1
      self::$_loader = NULL;
130
    } else {
131 1
      throw new FluentDOM\Exceptions\InvalidArgument(
132 1
        'loader', [Loadable::class]
133
      );
134
    }
135 2
  }
136
137
  /**
138
   * Register an additional default loader
139
   *
140
   * @param FluentDOM\Loadable|callable $loader
141
   * @param string[] $contentTypes
142
   * @return FluentDOM\Loaders
143
   * @throws \UnexpectedValueException
144
   */
145 3
  public static function registerLoader($loader, string ...$contentTypes): FluentDOM\Loaders {
146 3
    $loaders = self::getDefaultLoaders();
147 3
    if (count($contentTypes) > 0) {
148 1
      $lazyLoader = new \FluentDOM\Loader\Lazy();
149 1
      foreach ($contentTypes as $contentType) {
150 1
        $lazyLoader->add($contentType, $loader);
151
      }
152 1
      $loaders->add($lazyLoader);
153 2
    } else if (is_callable($loader)) {
154 1
      $loaders->add($loader());
155
    } else {
156 1
      $loaders->add($loader);
157
    }
158 3
    self::$_loader = NULL;
159 3
    return $loaders;
160
  }
161
162
  /**
163
   * Standard loader + any registered loader.
164
   *
165
   * @return FluentDOM\Loaders
166
   * @codeCoverageIgnore
167
   */
168
  public static function getDefaultLoaders(): FluentDOM\Loaders {
169
    if (!(self::$_defaultLoaders instanceof FluentDOM\Loaders)) {
170
      self::$_defaultLoaders = new FluentDOM\Loaders(new FluentDOM\Loader\Standard());
171
    }
172
    return self::$_defaultLoaders;
173
  }
174
175
  /**
176
   * Register a serializer factory for a specified content type(s). This can be
177
   * a callable returning the create serializer.
178
   *
179
   * @param \FluentDOM\Serializer\Factory|callable $factory
180
   * @param string[] $contentTypes
181
   */
182 1
  public static function registerSerializerFactory($factory, string ...$contentTypes) {
183 1
    foreach ($contentTypes as $contentType) {
184 1
      self::getSerializerFactories()[$contentType] = $factory;
185
    }
186 1
  }
187
188
  /**
189
   * Return registered serializer factories
190
   *
191
   * @return FluentDOM\Serializer\Factory\Group
192
   */
193 9
  public static function getSerializerFactories(): FluentDOM\Serializer\Factory\Group {
194 9
    if (!(self::$_serializerFactories instanceof FluentDOM\Serializer\Factory)) {
195 6
      $xml = function($contentType, \DOMNode $node) {
196 6
        return new FluentDOM\Serializer\Xml($node);
197
      };
198 2
      $html = function($contentType, \DOMNode $node) {
199 2
        return new FluentDOM\Serializer\Html($node);
200
      };
201 1
      $json = function($contentType, \DOMNode $node) {
202 1
        return new FluentDOM\Serializer\Json($node);
203
      };
204 1
      self::$_serializerFactories = new FluentDOM\Serializer\Factory\Group(
205
        [
206 1
          'text/html' => $html,
207 1
          'html' => $html,
208 1
          'text/xml' => $xml,
209 1
          'xml' => $xml,
210 1
          'text/json' => $json,
211 1
          'json' => $json
212
        ]
213
      );
214
    }
215 9
    return self::$_serializerFactories;
216
  }
217
218
  /**
219
   * Get a xpath expression builder to convert css selectors to xpath
220
   *
221
   * @param string $errorMessage
222
   * @return \FluentDOM\Xpath\Transformer
223
   * @throws \LogicException
224
   */
225 6
  public static function getXPathTransformer(
226
    string $errorMessage = 'No CSS selector support installed'
227
  ): \FluentDOM\Xpath\Transformer {
228 6
    foreach (self::$_xpathTransformers as $index => $transformer) {
229 6
      if (is_string($transformer) && class_exists($transformer)) {
230 1
        self::$_xpathTransformers[$index] = new $transformer();
231 5
      } elseif (is_callable($transformer)) {
232 1
        self::$_xpathTransformers[$index] = $transformer();
233
      }
234 6
      if (self::$_xpathTransformers[$index] instanceof \FluentDOM\Xpath\Transformer) {
235 5
        return self::$_xpathTransformers[$index];
236
      }
237 1
      unset(self::$_xpathTransformers[$index]);
238
    }
239 1
    throw new \LogicException($errorMessage);
240
  }
241
242
  /**
243
   * @param string|callable|FluentDOM\Xpath\Transformer $transformer
244
   * @param bool $reset
245
   */
246 6
  public static function registerXpathTransformer($transformer, bool $reset = FALSE) {
247 6
    if ($reset) {
248 6
      self::$_xpathTransformers = [];
249
    }
250 6
    array_unshift(self::$_xpathTransformers, $transformer);
251 6
  }
252
}
253
254
255
/**
256
 * FluentDOM function, is an Alias for the \FluentDOM\FluentDOM::Query()
257
 * factory class function.
258
 *
259
 * @param mixed $source
260
 * @param string $contentType
261
 * @param array $options
262
 * @return FluentDOM\Query
263
 * @codeCoverageIgnore
264
 */
265
function FluentDOM($source = NULL, string $contentType = 'text/xml', array $options = []): FluentDOM\Query {
266
  return FluentDOM::Query($source, $contentType, $options);
267
}
268