These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php namespace Demo; |
||
0 ignored issues
–
show
|
|||
2 | /** |
||
3 | * This script is an example of usage Tarsana\Functional |
||
4 | * It is used to generate the Reference documentation of |
||
5 | * functions in the library as markdown files using |
||
6 | * [dox](https://github.com/tj/dox) to parse source |
||
7 | * files and extract documentation from comments. This is not a |
||
8 | * perfect example of Functional Programming, it simply shows a |
||
9 | * real use case of this library. |
||
10 | */ |
||
11 | require __DIR__ . '/vendor/autoload.php'; |
||
12 | |||
13 | use Tarsana\Functional as F; |
||
14 | |||
15 | define('URL', 'https://github.com/tarsana/functional/blob/master'); |
||
16 | |||
17 | // Reads the list of sources files from 'composer.json' |
||
18 | // * -> IO [String] |
||
19 | function modules() { |
||
20 | $composer = json_decode(file_get_contents(__DIR__.'/composer.json')); |
||
21 | return $composer->autoload->files; |
||
22 | } |
||
23 | |||
24 | // Extracts tags with specific type from a docblock. |
||
25 | // String -> Object -> [name => String, description => String, string => String] |
||
26 | function tags($type, $data) { |
||
27 | return F\filter(function($tag) use($type) { |
||
28 | return $tag->type == $type; |
||
29 | }, $data->tags); |
||
30 | } |
||
31 | |||
32 | // Extracts list of arguments from a docblock. |
||
33 | // Object -> [Arg] |
||
34 | /** |
||
35 | * @type Arg |
||
36 | * Argument of a function. |
||
37 | * |
||
38 | * @field name String |
||
39 | * @field type String |
||
40 | */ |
||
41 | function argsOf($data) { |
||
42 | return F\map(function($tag){ |
||
43 | return (object) [ |
||
44 | 'type' => $tag->name, |
||
45 | 'name' => $tag->description |
||
46 | ]; |
||
47 | }, tags('param', $data)); |
||
48 | } |
||
49 | |||
50 | // Extracts signatures of a function. |
||
51 | // Object -> [String] |
||
52 | function signaturesOf($data) { |
||
53 | return F\map(function($tag){ |
||
54 | return $tag->string; |
||
55 | }, tags('signature', $data)); |
||
56 | } |
||
57 | |||
58 | // Extracts the return type of a function. |
||
59 | // Object -> String |
||
60 | function returnOf($data) { |
||
61 | $returns = tags('return', $data); |
||
62 | return (F\length($returns) > 0) |
||
63 | ? $returns[0]->description |
||
64 | : null; |
||
65 | } |
||
66 | |||
67 | // Extracts the type of a block |
||
68 | // Object -> String |
||
69 | function typeOf($data) { |
||
70 | if (isset($data->ctx->type)) |
||
71 | return $data->ctx->type; |
||
72 | if (F\length(tags('var', $data)) > 0) |
||
73 | return 'attr'; |
||
74 | if (F\length(tags('return', $data)) > 0) |
||
75 | return 'method'; |
||
76 | } |
||
77 | |||
78 | // Extract keywords |
||
79 | // Object -> [String] |
||
80 | function keywords($data) { |
||
81 | if (!isset($data->code)) { |
||
82 | return []; |
||
83 | } |
||
84 | $size = strpos($data->code, '('); |
||
85 | if ($size === false) |
||
86 | $size = strlen($data->code); |
||
87 | $keywords = F\pipe( |
||
88 | F\take($size), |
||
89 | F\split(' '), |
||
90 | F\map('trim'), |
||
91 | F\filter(F\notEq('')) |
||
92 | ); |
||
93 | return $keywords($data->code); |
||
94 | } |
||
95 | |||
96 | // Object -> DocBlock |
||
97 | /** |
||
98 | * @type DocBlock |
||
99 | * Documentation block of a function. |
||
100 | * |
||
101 | * @field type String |
||
102 | * @field name String |
||
103 | * @field args [Arg] |
||
104 | * @field return String |
||
105 | * @field signatures [String] |
||
106 | * @field description String |
||
107 | * @field is_internal Boolean |
||
108 | * @field is_static Boolean |
||
109 | */ |
||
110 | function block($data) { |
||
111 | $keywords = keywords($data); |
||
112 | return (object) [ |
||
113 | 'type' => typeOf($data), |
||
114 | 'name' => isset($data->ctx->name) ? $data->ctx->name : F\last($keywords), |
||
115 | 'args' => argsOf($data), |
||
116 | 'return' => returnOf($data), |
||
117 | 'signatures' => signaturesOf($data), |
||
118 | 'description' => $data->description->full, |
||
119 | 'is_static' => in_array('static', $keywords), |
||
120 | 'is_internal' => in_array('private', $keywords) || in_array('protected', $keywords) || (0 < F\length(tags('internal', $data))) |
||
121 | ]; |
||
122 | } |
||
123 | |||
124 | // Get a markdown code block |
||
125 | // String -> String -> String |
||
126 | function code($lang, $text) { |
||
127 | if(trim($text) == '') |
||
128 | return ''; |
||
129 | return "```{$lang}\n{$text}\n```"; |
||
130 | } |
||
131 | |||
132 | // Gets the markdown of a function/method/class |
||
133 | // DocBlock -> String |
||
134 | function markdown($fn) { |
||
135 | if ($fn->type == 'class') { |
||
136 | return $fn->description; |
||
137 | } else { |
||
138 | $args = F\map(function($arg) { |
||
139 | return $arg->type . ' ' . $arg->name; |
||
140 | }, $fn->args); |
||
141 | $proto = $fn->name . '('. F\join(', ', $args) .') : ' . $fn->return; |
||
142 | return F\join("\n\n", [ |
||
143 | "## {$fn->name}", |
||
144 | code('php', $proto), |
||
145 | code('', F\join("\n", $fn->signatures)), |
||
146 | $fn->description |
||
147 | ]); |
||
148 | } |
||
149 | } |
||
150 | |||
151 | // Adds title and table of contents |
||
152 | // String -> [Object] -> [String] |
||
153 | function addContents() { |
||
154 | $addContents = function($name, $parts) { |
||
155 | $names = F\filter(F\notEq($name), F\map(F\get('name'), $parts)); |
||
156 | var_dump($names); |
||
0 ignored issues
–
show
|
|||
157 | $contents = F\map(function ($partname) use($name) { |
||
158 | $link = URL . "/docs/{$name}.md#{$partname}"; |
||
159 | return "- [{$partname}]($link)"; |
||
160 | }, $names); |
||
161 | file_put_contents ("docs/README.md", |
||
162 | F\join("\n\n", F\concat(["## {$name}"], $contents)) . "\n\n" |
||
163 | , FILE_APPEND); |
||
164 | return array_merge(['# ' . $name, '## Table Of Contents'], $contents, F\map(F\get('md'), $parts)); |
||
165 | }; |
||
166 | return F\apply(F\curry($addContents), func_get_args()); |
||
167 | } |
||
168 | // Generates documentation for a module of functions |
||
169 | // String -> IO |
||
170 | function generateModule($file) { |
||
171 | $content = F\pipe( |
||
172 | F\map('Demo\\block'), |
||
173 | F\filter(function($block){ |
||
174 | return $block->type == 'function' && !$block->is_internal; |
||
175 | }), |
||
176 | F\map(function($block){ |
||
177 | return [ |
||
178 | 'name' => $block->name, |
||
179 | 'md' => markdown($block) |
||
180 | ]; |
||
181 | }), |
||
182 | addContents(F\replace(['src/', '.php'], '', $file)), |
||
183 | F\join("\n\n") |
||
184 | ); |
||
185 | |||
186 | file_put_contents ( |
||
187 | F\replace(['src', '.php'], ['docs', '.md'], $file), |
||
188 | $content(json_decode(shell_exec("dox -r < {$file}"))) |
||
189 | ); |
||
190 | } |
||
191 | |||
192 | // Generates documentation for a class |
||
193 | // String -> IO |
||
194 | function generateClass($name) { |
||
195 | $content = F\pipe( |
||
196 | F\map('Demo\\block'), |
||
197 | F\filter(function($block){ |
||
198 | return in_array($block->type, ['method', 'class']) && !$block->is_internal; |
||
199 | }), |
||
200 | f\map(function($block) use ($name) { |
||
201 | if ($block->type == 'method') { |
||
202 | $block->name = ($block->is_static ? $name . '::' : '') . $block->name; |
||
203 | } |
||
204 | return $block; |
||
205 | }), |
||
206 | F\map(function($block){ |
||
207 | return [ |
||
208 | 'name' => $block->name, |
||
209 | 'md' => markdown($block) |
||
210 | ]; |
||
211 | }), |
||
212 | addContents($name), |
||
213 | F\join("\n\n") |
||
214 | ); |
||
215 | |||
216 | file_put_contents ( |
||
217 | "docs/{$name}.md", |
||
218 | $content(json_decode(shell_exec("dox -r < src/{$name}.php"))) |
||
219 | ); |
||
220 | } |
||
221 | |||
222 | // The entry point |
||
223 | file_put_contents('docs/README.md', "# Reference Documentation \n\n"); |
||
224 | file_put_contents ("docs/README.md", "# Function Modules\n\n" , FILE_APPEND); |
||
225 | F\each('Demo\\generateModule', modules()); |
||
226 | file_put_contents ("docs/README.md", "# Containers\n\n" , FILE_APPEND); |
||
227 | F\each('Demo\\generateClass', ['Stream', 'Error']); |
||
228 |
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.