Complex classes like Graph often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Graph, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 40 | class Graph |
||
| 41 | { |
||
| 42 | use Attributes; |
||
| 43 | |||
| 44 | public const RANK_SEP = 'RankSep'; |
||
| 45 | public const CENTER = 'Center'; |
||
| 46 | public const RANK = 'Rank'; |
||
| 47 | public const RANK_DIR = 'RankDir'; |
||
| 48 | public const SPLINES = 'Splines'; |
||
| 49 | public const CONCENTRATE = 'Concentrate'; |
||
| 50 | public const LABEL = 'Label'; |
||
| 51 | public const SHAPE = 'Shape'; |
||
| 52 | public const FONT_COLOR = 'FontColor'; |
||
| 53 | |||
| 54 | /** @var string Name of this graph */ |
||
| 55 | protected $name = 'G'; |
||
| 56 | |||
| 57 | /** @var string Type of this graph; may be digraph, graph or subgraph */ |
||
| 58 | protected $type = 'digraph'; |
||
| 59 | |||
| 60 | /** @var bool If the graph is strict then multiple edges are not allowed between the same pairs of nodes */ |
||
| 61 | protected $strict = false; |
||
| 62 | |||
| 63 | /** @var Graph[] A list of subgraphs for this Graph */ |
||
| 64 | protected $graphs = []; |
||
| 65 | |||
| 66 | /** @var Node[] A list of nodes for this Graph */ |
||
| 67 | protected $nodes = []; |
||
| 68 | |||
| 69 | /** @var Edge[] A list of edges / arrows for this Graph */ |
||
| 70 | protected $edges = []; |
||
| 71 | |||
| 72 | /** @var string The path to execute dot from */ |
||
| 73 | protected $path = ''; |
||
| 74 | |||
| 75 | /** |
||
| 76 | * Factory method to instantiate a Graph so that you can use fluent coding |
||
| 77 | * to chain everything. |
||
| 78 | * |
||
| 79 | * @param string $name The name for this graph. |
||
| 80 | * @param bool $directional Whether this is a directed or undirected graph. |
||
| 81 | 1 | * |
|
| 82 | * @return \phpDocumentor\GraphViz\Graph |
||
| 83 | 1 | */ |
|
| 84 | public static function create(string $name = 'G', bool $directional = true) : self |
||
| 85 | 1 | { |
|
| 86 | 1 | $graph = new self(); |
|
| 87 | $graph |
||
| 88 | 1 | ->setName($name) |
|
| 89 | ->setType($directional ? 'digraph' : 'graph'); |
||
| 90 | |||
| 91 | return $graph; |
||
| 92 | } |
||
| 93 | |||
| 94 | /** |
||
| 95 | * Sets the path for the execution. Only needed if it is not in the PATH env. |
||
| 96 | 1 | * |
|
| 97 | * @param string $path The path to execute dot from |
||
| 98 | 1 | */ |
|
| 99 | 1 | public function setPath(string $path) : self |
|
| 100 | 1 | { |
|
| 101 | $realpath = realpath($path); |
||
| 102 | if ($path && $path === $realpath) { |
||
| 103 | 1 | $this->path = $path . DIRECTORY_SEPARATOR; |
|
| 104 | } |
||
| 105 | |||
| 106 | return $this; |
||
| 107 | } |
||
| 108 | |||
| 109 | /** |
||
| 110 | * Sets the name for this graph. |
||
| 111 | * |
||
| 112 | * If this is a subgraph you can prefix the name with _cluster_ to group all |
||
| 113 | * contained nodes and add a border. |
||
| 114 | 1 | * |
|
| 115 | * @param string $name The new name for this graph. |
||
| 116 | 1 | */ |
|
| 117 | 1 | public function setName(string $name) : self |
|
| 118 | { |
||
| 119 | $this->name = $name; |
||
| 120 | return $this; |
||
| 121 | } |
||
| 122 | |||
| 123 | 1 | /** |
|
| 124 | * Returns the name for this Graph. |
||
| 125 | 1 | */ |
|
| 126 | public function getName() : string |
||
| 127 | { |
||
| 128 | return $this->name; |
||
| 129 | } |
||
| 130 | |||
| 131 | /** |
||
| 132 | * Sets the type for this graph. |
||
| 133 | * |
||
| 134 | * @param string $type Must be either "digraph", "graph" or "subgraph". |
||
| 135 | * |
||
| 136 | 2 | * @throws InvalidArgumentException If $type is not "digraph", "graph" or |
|
| 137 | * "subgraph". |
||
| 138 | 2 | */ |
|
| 139 | 1 | public function setType(string $type) : self |
|
| 140 | { |
||
| 141 | 1 | if (!in_array($type, ['digraph', 'graph', 'subgraph'], true)) { |
|
| 142 | throw new InvalidArgumentException( |
||
| 143 | 'The type for a graph must be either "digraph", "graph" or ' |
||
| 144 | . '"subgraph"' |
||
| 145 | 1 | ); |
|
| 146 | 1 | } |
|
| 147 | |||
| 148 | $this->type = $type; |
||
| 149 | return $this; |
||
| 150 | } |
||
| 151 | |||
| 152 | 1 | /** |
|
| 153 | * Returns the type of this Graph. |
||
| 154 | 1 | */ |
|
| 155 | public function getType() : string |
||
| 156 | { |
||
| 157 | return $this->type; |
||
| 158 | } |
||
| 159 | |||
| 160 | /** |
||
| 161 | 2 | * Set if the Graph should be strict. If the graph is strict then |
|
| 162 | * multiple edges are not allowed between the same pairs of nodes |
||
| 163 | 2 | */ |
|
| 164 | 2 | public function setStrict(bool $isStrict) : self |
|
| 165 | { |
||
| 166 | $this->strict = $isStrict; |
||
| 167 | 1 | return $this; |
|
| 168 | } |
||
| 169 | 1 | ||
| 170 | public function isStrict() : bool |
||
| 171 | { |
||
| 172 | return $this->strict; |
||
| 173 | } |
||
| 174 | |||
| 175 | /** |
||
| 176 | * Magic method to provide a getter/setter to add attributes on the Graph. |
||
| 177 | * |
||
| 178 | * Using this method we make sure that we support any attribute without |
||
| 179 | * too much hassle. If the name for this method does not start with get |
||
| 180 | * or set we return null. |
||
| 181 | * |
||
| 182 | * Set methods return this graph (fluent interface) whilst get methods |
||
| 183 | * return the attribute value. |
||
| 184 | * |
||
| 185 | * @param string $name Name of the method including get/set |
||
| 186 | * @param mixed[] $arguments The arguments, should be 1: the value |
||
| 187 | * |
||
| 188 | * @return Attribute|Graph|null |
||
| 189 | 1 | * |
|
| 190 | * @throws AttributeNotFound |
||
| 191 | 1 | */ |
|
| 192 | 1 | public function __call(string $name, array $arguments) |
|
| 193 | 1 | { |
|
| 194 | $key = strtolower(substr($name, 3)); |
||
| 195 | if (strtolower(substr($name, 0, 3)) === 'set') { |
||
| 196 | 1 | return $this->setAttribute($key, (string) $arguments[0]); |
|
| 197 | 1 | } |
|
| 198 | |||
| 199 | if (strtolower(substr($name, 0, 3)) === 'get') { |
||
| 200 | 1 | return $this->getAttribute($key); |
|
| 201 | } |
||
| 202 | |||
| 203 | return null; |
||
| 204 | } |
||
| 205 | |||
| 206 | /** |
||
| 207 | * Adds a subgraph to this graph; automatically changes the type to subgraph. |
||
| 208 | * |
||
| 209 | * Please note that an index is maintained using the name of the subgraph. |
||
| 210 | * Thus if you have 2 subgraphs with the same name that the first will be |
||
| 211 | * overwritten by the latter. |
||
| 212 | * |
||
| 213 | * @see Graph::create() |
||
| 214 | * |
||
| 215 | 1 | * @param Graph $graph The graph to add onto this graph as |
|
| 216 | * subgraph. |
||
| 217 | 1 | */ |
|
| 218 | 1 | public function addGraph(self $graph) : self |
|
| 219 | 1 | { |
|
| 220 | $graph->setType('subgraph'); |
||
| 221 | $this->graphs[$graph->getName()] = $graph; |
||
| 222 | return $this; |
||
| 223 | } |
||
| 224 | |||
| 225 | /** |
||
| 226 | * Checks whether a graph with a certain name already exists. |
||
| 227 | 1 | * |
|
| 228 | * @param string $name Name of the graph to find. |
||
| 229 | 1 | */ |
|
| 230 | public function hasGraph(string $name) : bool |
||
| 231 | { |
||
| 232 | return isset($this->graphs[$name]); |
||
| 233 | } |
||
| 234 | |||
| 235 | /** |
||
| 236 | * Returns the subgraph with a given name. |
||
| 237 | 1 | * |
|
| 238 | * @param string $name Name of the requested graph. |
||
| 239 | 1 | */ |
|
| 240 | public function getGraph(string $name) : self |
||
| 241 | { |
||
| 242 | return $this->graphs[$name]; |
||
| 243 | } |
||
| 244 | |||
| 245 | /** |
||
| 246 | * Sets a node in the $nodes array; uses the name of the node as index. |
||
| 247 | * |
||
| 248 | * Nodes can be retrieved by retrieving the property with the same name. |
||
| 249 | * Thus 'node1' can be retrieved by invoking: $graph->node1 |
||
| 250 | * |
||
| 251 | * @see Node::create() |
||
| 252 | 1 | * |
|
| 253 | * @param Node $node The node to set onto this Graph. |
||
| 254 | 1 | */ |
|
| 255 | 1 | public function setNode(Node $node) : self |
|
| 256 | { |
||
| 257 | $this->nodes[$node->getName()] = $node; |
||
| 258 | return $this; |
||
| 259 | } |
||
| 260 | |||
| 261 | /** |
||
| 262 | * Finds a node in this graph or any of its subgraphs. |
||
| 263 | 1 | * |
|
| 264 | * @param string $name Name of the node to find. |
||
| 265 | 1 | */ |
|
| 266 | 1 | public function findNode(string $name) : ?Node |
|
| 281 | |||
| 282 | /** |
||
| 283 | * Sets a node using a custom name. |
||
| 284 | * |
||
| 285 | * @see Graph::setNode() |
||
| 286 | * |
||
| 287 | 1 | * @param string $name Name of the node. |
|
| 288 | * @param Node $value Node to set on the given name. |
||
| 289 | 1 | */ |
|
| 290 | 1 | public function __set(string $name, Node $value) : self |
|
| 295 | |||
| 296 | /** |
||
| 297 | * Returns the requested node by its name. |
||
| 298 | * |
||
| 299 | * @see Graph::setNode() |
||
| 300 | 1 | * |
|
| 301 | * @param string $name The name of the node to retrieve. |
||
| 302 | 1 | */ |
|
| 303 | public function __get(string $name) : ?Node |
||
| 307 | |||
| 308 | /** |
||
| 309 | * Links two nodes to eachother and registers the Edge onto this graph. |
||
| 310 | * |
||
| 311 | * @see Edge::create() |
||
| 312 | 1 | * |
|
| 313 | * @param Edge $edge The link between two classes. |
||
| 314 | 1 | */ |
|
| 315 | 1 | public function link(Edge $edge) : self |
|
| 316 | { |
||
| 317 | $this->edges[] = $edge; |
||
| 318 | return $this; |
||
| 319 | } |
||
| 320 | |||
| 321 | /** |
||
| 322 | * Exports this graph to a generated image. |
||
| 323 | * |
||
| 324 | * This is the only method that actually requires GraphViz. |
||
| 325 | * |
||
| 326 | * @link http://www.graphviz.org/content/output-formats |
||
| 327 | * @uses GraphViz/dot |
||
| 328 | * |
||
| 329 | * @param string $type The type to export to; see the link above for a |
||
| 330 | * list of supported types. |
||
| 331 | * @param string $filename The path to write to. |
||
| 332 | 2 | * |
|
| 333 | * @throws Exception If an error occurred in GraphViz. |
||
| 334 | 2 | */ |
|
| 335 | 2 | public function export(string $type, string $filename) : self |
|
| 362 | |||
| 363 | /** |
||
| 364 | * Generates a DOT file for use with GraphViz. |
||
| 365 | * |
||
| 366 | 1 | * GraphViz is not used in this method; it is safe to call it even without |
|
| 367 | * GraphViz installed. |
||
| 368 | 1 | */ |
|
| 369 | 1 | public function __toString() : string |
|
| 393 | |||
| 394 | public function setRankSep(string $RankSep) : Graph |
||
| 398 | |||
| 399 | public function setCenter(string $Center) : Graph |
||
| 403 | |||
| 404 | public function setRank(string $Rank) : Graph |
||
| 408 | |||
| 409 | public function setRankDir(string $RankDir) : Graph |
||
| 413 | |||
| 414 | public function setSplines(string $Splines) : Graph |
||
| 418 | |||
| 419 | public function setConcentrate(string $Concentrate) : Graph |
||
| 423 | |||
| 424 | public function setLabel(string $Label) : Graph |
||
| 428 | |||
| 429 | public function setShape(string $Shape) : Graph |
||
| 433 | |||
| 434 | public function setFontColor(string $FontColor) : Graph |
||
| 438 | |||
| 439 | public function getRankSep() : Attribute |
||
| 443 | |||
| 444 | public function getCenter() : Attribute |
||
| 448 | |||
| 449 | public function getRank() : Attribute |
||
| 453 | |||
| 454 | public function getRankDir() : Attribute |
||
| 458 | |||
| 459 | public function getSplines() : Attribute |
||
| 463 | |||
| 464 | public function getConcentrate() : Attribute |
||
| 468 | |||
| 469 | public function getLabel() : Attribute |
||
| 473 | |||
| 474 | public function getShape() : Attribute |
||
| 478 | |||
| 479 | public function getFontColor() : Attribute |
||
| 483 | } |
||
| 484 |