Completed
Push — master ( 071c16...b9f390 )
by Mike
03:33
created

WriterAbstract::router()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * This file is part of phpDocumentor.
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @author    Mike van Riel <[email protected]>
11
 * @copyright 2010-2018 Mike van Riel / Naenius (http://www.naenius.com)
12
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
13
 * @link      http://phpdoc.org
14
 */
15
16
namespace phpDocumentor\Transformer\Writer;
17
18
use InvalidArgumentException;
19
use phpDocumentor\Descriptor\Descriptor;
20
use phpDocumentor\Descriptor\ProjectDescriptor;
21
use phpDocumentor\Transformer\Router\Router;
22
use phpDocumentor\Transformer\Transformation;
23
use UnexpectedValueException;
24
25
/**
26
 * Base class for the actual transformation business logic (writers).
27
 */
28
abstract class WriterAbstract
29
{
30
    /**
31
     * This method verifies whether PHP has all requirements needed to run this writer.
32
     *
33
     * If one of the requirements is missing for this Writer then an exception of type RequirementMissing
34
     * should be thrown; this indicates to the calling process that this writer will not function.
35
     *
36
     * @throws Exception\RequirementMissing when a requirements is missing stating which one.
37
     */
38
    public function checkRequirements()
39
    {
40
        // empty body since most writers do not have requirements
41
    }
42
43
    /**
44
     * Checks if there is a space in the path.
45
     *
46
     * @param string $path
47
     *
48
     * @throws \InvalidArgumentException if path contains a space.
49
     */
50
    protected function checkForSpacesInPath($path)
51
    {
52
        if (strpos($path, ' ') !== false) {
53
            throw new \InvalidArgumentException('No spaces allowed in destination path: ' . $path);
54
        }
55
    }
56
57
    /**
58
     * Abstract definition of the transformation method.
59
     *
60
     * @param ProjectDescriptor $project        Document containing the structure.
61
     * @param Transformation    $transformation Transformation to execute.
62
     */
63
    abstract public function transform(ProjectDescriptor $project, Transformation $transformation);
64
65
    /**
66
     * Uses the currently selected node and transformation to assemble the destination path for the file.
67
     *
68
     * Writers accept the use of a Query to be able to generate output for multiple objects using the same
69
     * template.
70
     *
71
     * The given node is the result of such a query, or if no query given the selected element, and the transformation
72
     * contains the destination file.
73
     *
74
     * Since it is important to be able to generate a unique name per element can the user provide a template variable
75
     * in the name of the file.
76
     * Such a template variable always resides between double braces and tries to take the node value of a given
77
     * query string.
78
     *
79
     * Example:
80
     *
81
     *   An artifact stating `classes/{{name}}.html` will try to find the
82
     *   node 'name' as a child of the given $node and use that value instead.
83
     *
84
     * @throws InvalidArgumentException if no artifact is provided and no routing rule matches.
85
     * @throws UnexpectedValueException if the provided node does not contain anything.
86
     *
87
     * @return null|string returns the destination location or false if generation should be aborted.
88
     */
89
    public function destination(Descriptor $descriptor, Transformation $transformation): ?string
90
    {
91
        $path = $transformation->getTransformer()->getTarget() . DIRECTORY_SEPARATOR . $transformation->getArtifact();
92
        if (!$transformation->getArtifact()) {
93
            if (!$this->router()) {
94
                throw new InvalidArgumentException(
95
                    'The artifact location needs to be provided by this transformation; '
96
                    . 'the writer doesn\'t support automatically determining paths'
97
                );
98
            }
99
100
            $url = $this->router()->generate($descriptor);
101
            if ($url === null) {
102
                throw new InvalidArgumentException(
103
                    'No matching routing rule could be found for the given node, please provide an artifact location, '
104
                    . 'encountered: ' . get_class($descriptor)
105
                );
106
            }
107
108
            if (!$url || $url[0] !== DIRECTORY_SEPARATOR) {
109
                return null;
110
            }
111
112
            $path = $transformation->getTransformer()->getTarget()
113
                . str_replace('/', DIRECTORY_SEPARATOR, $url);
114
        }
115
116
        $finder = new Pathfinder();
117
        $destination = preg_replace_callback(
118
            '/{{([^}]+)}}/', // explicitly do not use the unicode modifier; this breaks windows
119
            function ($query) use ($descriptor, $finder) {
120
                // strip any surrounding \ or /
121
                $filepart = trim((string) current($finder->find($descriptor, $query[1])), '\\/');
122
123
                // make it windows proof
124
                if (extension_loaded('iconv')) {
125
                    $filepart = iconv('UTF-8', 'ASCII//TRANSLIT', $filepart);
126
                }
127
128
                return strpos($filepart, '/') !== false
129
                    ? implode('/', array_map('urlencode', explode('/', $filepart)))
130
                    : implode('\\', array_map('urlencode', explode('\\', $filepart)));
131
            },
132
            $path
133
        );
134
135
        // replace any \ with the directory separator to be compatible with the
136
        // current filesystem and allow the next file_exists to do its work
137
        $destination = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $destination);
138
139
        // create directory if it does not exist yet
140
        if (!file_exists(dirname($destination))) {
141
            mkdir(dirname($destination), 0777, true);
142
        }
143
144
        return $destination;
145
    }
146
147
    public function __toString(): string
148
    {
149
        return get_class($this);
150
    }
151
152
    protected function router(): ?Router
153
    {
154
        return null;
155
    }
156
}
157