├── .gitignore
├── Builder.php
├── Config.php
├── Exception.php
├── Extractor.php
├── LICENSE
├── README.md
├── Resources
└── views
│ └── default
│ ├── index.html
│ └── partial
│ ├── main.html
│ ├── paramContentTpl.html
│ ├── paramSampleBtnTpl.html
│ ├── paramTableTpl.html
│ ├── samplePostBodyTpl.html
│ ├── sampleReponseHeaderTpl.html
│ ├── sampleReponseTpl.html
│ ├── sandboxFormInputTpl.html
│ └── sandboxFormTpl.html
├── Response.php
├── Template.php
├── TestClasses
├── Article.php
└── User.php
├── View.php
├── View
├── BaseView.php
├── JsonView.php
└── ViewInterface.php
└── composer.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .buildpath
2 | .project
3 | .settings
4 |
--------------------------------------------------------------------------------
/Builder.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | class Builder
17 | {
18 | /**
19 | * Version number
20 | *
21 | * @var string
22 | */
23 | const VERSION = '1.3.8';
24 |
25 | /**
26 | * Classes collection
27 | *
28 | * @var array
29 | */
30 | protected $_st_classes;
31 |
32 | /**
33 | * Output directory for documentation
34 | *
35 | * @var string
36 | */
37 | protected $_output_dir;
38 |
39 | /**
40 | * Title to be displayed
41 | * @var string
42 | */
43 | protected $_title;
44 |
45 | /**
46 | * Output filename for documentation
47 | *
48 | * @var string
49 | */
50 | protected $_output_file;
51 |
52 | /**
53 | * Template file path
54 | * @var string
55 | **/
56 | protected $template_path = __DIR__.'/Resources/views/default';
57 |
58 | /**
59 | * Template object
60 | * @var Template
61 | */
62 | protected $o_template;
63 |
64 | /**
65 | * Constructor
66 | *
67 | * @param array $st_classes
68 | */
69 | public function __construct(array $st_classes, $s_output_dir, $title = 'php-apidoc', $s_output_file = 'index.html', $template_path = null)
70 | {
71 | $this->_st_classes = $st_classes;
72 | $this->_output_dir = $s_output_dir;
73 | $this->_title = $title;
74 | $this->_output_file = $s_output_file;
75 |
76 | if ($template_path) {
77 | $this->template_path = $template_path;
78 | }
79 |
80 | $this->o_template = new Template();
81 | }
82 |
83 | /**
84 | * Extract annotations
85 | *
86 | * @return array
87 | */
88 | protected function extractAnnotations()
89 | {
90 | foreach ($this->_st_classes as $class) {
91 | $st_output[] = Extractor::getAllClassAnnotations($class);
92 | }
93 |
94 | return end($st_output);
95 | }
96 |
97 | protected function saveTemplate($data, $file)
98 | {
99 | $this->o_template->assign('content', $data);
100 | $this->o_template->assign('title', $this->_title);
101 | $this->o_template->assign('date', date('Y-m-d, H:i:s'));
102 | $this->o_template->assign('version', static::VERSION);
103 |
104 | $newContent = $this->o_template->parse($this->template_path.'/index.html');
105 |
106 | if (!is_dir($this->_output_dir)) {
107 | if (!mkdir($this->_output_dir)) {
108 | throw new Exception('Cannot create directory');
109 | }
110 | }
111 | if (!file_put_contents($this->_output_dir.'/'.$file, $newContent)) {
112 | throw new Exception('Cannot save the content to '.$this->_output_dir);
113 | }
114 | }
115 |
116 | /**
117 | * Generate the content of the documentation
118 | *
119 | * @return boolean
120 | */
121 | protected function generateTemplate()
122 | {
123 | $st_annotations = $this->extractAnnotations();
124 |
125 | $template = array();
126 | $counter = 0;
127 | $section = null;
128 | $partial_template = $this->loadPartialTemplate('main');
129 |
130 | foreach ($st_annotations as $class => $methods) {
131 | foreach ($methods as $name => $docs) {
132 | if (isset($docs['ApiDescription'][0]['section'])) {
133 | $section = $docs['ApiDescription'][0]['section'];
134 | }elseif(isset($docs['ApiSector'][0]['name'])){
135 | $section = $docs['ApiSector'][0]['name'];
136 | }else{
137 | $section = $class;
138 | }
139 | if (0 === count($docs)) {
140 | continue;
141 | }
142 |
143 | $sampleOutput = $this->generateSampleOutput($docs, $counter);
144 |
145 | $tr = array(
146 | '{{ elt_id }}' => $counter,
147 | '{{ method }}' => $this->generateBadgeForMethod($docs),
148 | '{{ route }}' => $docs['ApiRoute'][0]['name'],
149 | '{{ description }}' => $docs['ApiDescription'][0]['description'],
150 | '{{ headers }}' => $this->generateHeadersTemplate($counter, $docs),
151 | '{{ parameters }}' => $this->generateParamsTemplate($counter, $docs),
152 | '{{ body }}' => $this->generateBodyTemplate($counter, $docs),
153 | '{{ sandbox_form }}' => $this->generateSandboxForm($docs, $counter),
154 | '{{ sample_response_headers }}' => $sampleOutput[0],
155 | '{{ sample_response_body }}' => $sampleOutput[1]
156 | );
157 |
158 | $template[$section][] = strtr($partial_template, $tr);
159 | $counter++;
160 | }
161 | }
162 |
163 | $output = '';
164 |
165 | foreach ($template as $key => $value) {
166 | array_unshift($value, '
' . $key . '
');
167 | $output .= implode(PHP_EOL, $value);
168 | }
169 |
170 | $this->saveTemplate($output, $this->_output_file);
171 |
172 | return true;
173 | }
174 |
175 | /**
176 | * Generate the sample output
177 | *
178 | * @param array $st_params
179 | * @param integer $counter
180 | * @return string
181 | */
182 | protected function generateSampleOutput($st_params, $counter)
183 | {
184 | if (!isset($st_params['ApiReturn'])) {
185 | $responseBody = '';
186 | } else {
187 | $ret = [];
188 | $partial_template = $this->loadPartialTemplate('sampleReponseTpl');
189 | foreach ($st_params['ApiReturn'] as $params) {
190 | if (in_array($params['type'], array('object', 'array(object) ', 'array', 'string', 'boolean', 'integer', 'number')) && isset($params['sample'])) {
191 | $tr = array(
192 | '{{ elt_id }}' => $counter,
193 | '{{ response }}' => $params['sample'],
194 | '{{ description }}' => '',
195 | );
196 | if (isset($params['description'])) {
197 | $tr['{{ description }}'] = $params['description'];
198 | }
199 | $ret[] = strtr($partial_template, $tr);
200 | }
201 | }
202 |
203 | $responseBody = implode(PHP_EOL, $ret);
204 | }
205 |
206 | if(!isset($st_params['ApiReturnHeaders'])) {
207 | $responseHeaders = '';
208 | } else {
209 | $ret = [];
210 | $partial_template = $this->loadPartialTemplate('sampleReponseHeaderTpl');
211 |
212 | foreach ($st_params['ApiReturnHeaders'] as $headers) {
213 | if(isset($headers['sample'])) {
214 | $tr = array(
215 | '{{ elt_id }}' => $counter,
216 | '{{ response }}' => $headers['sample'],
217 | '{{ description }}' => ''
218 | );
219 |
220 | $ret[] = strtr($partial_template, $tr);
221 | }
222 | }
223 |
224 | $responseHeaders = implode(PHP_EOL, $ret);
225 | }
226 |
227 | return array($responseHeaders, $responseBody);
228 | }
229 |
230 | /**
231 | * Generates the template for headers
232 | * @param int $id
233 | * @param array $st_params
234 | * @return void|string
235 | */
236 | protected function generateHeadersTemplate($id, $st_params)
237 | {
238 | if (!isset($st_params['ApiHeaders']))
239 | {
240 | return;
241 | }
242 |
243 | $body = [];
244 | $partial_template = $this->loadPartialTemplate('paramContentTpl');
245 |
246 | foreach ($st_params['ApiHeaders'] as $params) {
247 | $tr = array(
248 | '{{ name }}' => $params['name'],
249 | '{{ type }}' => $params['type'],
250 | '{{ nullable }}' => @$params['nullable'] == '1' ? 'No' : 'Yes',
251 | '{{ description }}' => @$params['description'],
252 | );
253 | $body[] = strtr($partial_template, $tr);
254 | }
255 |
256 | return strtr($this->loadPartialTemplate('paramTableTpl'), array('{{ tbody }}' => implode(PHP_EOL, $body)));
257 |
258 | }
259 |
260 | /**
261 | * Generates the template for parameters
262 | *
263 | * @param int $id
264 | * @param array $st_params
265 | * @return void|string
266 | */
267 | protected function generateParamsTemplate($id, $st_params)
268 | {
269 | if (!isset($st_params['ApiParams']))
270 | {
271 | return;
272 | }
273 |
274 | $body = [];
275 | $paramSampleBtnTpl = $this->loadPartialTemplate('paramSampleBtnTpl');
276 | $paramContentTpl = $this->loadPartialTemplate('paramContentTpl');
277 |
278 | foreach ($st_params['ApiParams'] as $params) {
279 | $tr = array(
280 | '{{ name }}' => $params['name'],
281 | '{{ type }}' => $params['type'],
282 | '{{ nullable }}' => @$params['nullable'] == '1' ? 'No' : 'Yes',
283 | '{{ description }}' => @$params['description'],
284 | );
285 |
286 | if (isset($params['sample'])) {
287 | $tr['{{ type }}'].= ' '.strtr($paramSampleBtnTpl, array('{{ sample }}' => $params['sample']));
288 | }
289 |
290 | $body[] = strtr($paramContentTpl, $tr);
291 | }
292 |
293 | return strtr($this->loadPartialTemplate('paramTableTpl'), array('{{ tbody }}' => implode(PHP_EOL, $body)));
294 | }
295 |
296 | /**
297 | * Generate POST body template
298 | *
299 | * @param int $id
300 | * @param array $body
301 | * @return void|string
302 | */
303 | private function generateBodyTemplate($id, $docs)
304 | {
305 | if (!isset($docs['ApiBody']))
306 | {
307 | return;
308 | }
309 |
310 | $body = $docs['ApiBody'][0];
311 |
312 | return strtr($this->loadPartialTemplate('samplePostBodyTpl'), array(
313 | '{{ elt_id }}' => $id,
314 | '{{ body }}' => $body['sample']
315 | ));
316 |
317 | }
318 |
319 | /**
320 | * Generate route paramteres form
321 | *
322 | * @param array $st_params
323 | * @param integer $counter
324 | * @return void|mixed
325 | */
326 | protected function generateSandboxForm($st_params, $counter)
327 | {
328 | $headers = [];
329 | $params = [];
330 |
331 | $sandboxFormInputTpl = $this->loadPartialTemplate('sandboxFormInputTpl');
332 |
333 | if (isset($st_params['ApiParams']) && is_array($st_params['ApiParams']))
334 | {
335 | foreach ($st_params['ApiParams'] as $param)
336 | {
337 | $params[] = strtr($sandboxFormInputTpl, [
338 | '{{ type }}' => $param['type'],
339 | '{{ name }}' => $param['name'],
340 | '{{ description }}' => $param['description'],
341 | '{{ sample }}' => $param['sample']
342 | ]);
343 | }
344 | }
345 |
346 | if (isset($st_params['ApiHeaders']) && is_array($st_params['ApiHeaders']))
347 | {
348 | foreach ($st_params['ApiHeaders'] as $header)
349 | {
350 | $headers[] = strtr($sandboxFormInputTpl, [
351 | '{{ type }}' => 'text',
352 | '{{ name }}' => $header['name'],
353 | '{{ description }}' => $header['description'],
354 | '{{ sample }}' => $header['sample']
355 | ]);
356 | }
357 | }
358 |
359 | $tr = array(
360 | '{{ elt_id }}' => $counter,
361 | '{{ method }}' => $st_params['ApiMethod'][0]['type'],
362 | '{{ route }}' => $st_params['ApiRoute'][0]['name'],
363 | '{{ headers }}' => implode(PHP_EOL, $headers),
364 | '{{ params }}' => implode(PHP_EOL, $params),
365 | );
366 |
367 | return strtr($this->loadPartialTemplate('sandboxFormTpl'), $tr);
368 | }
369 |
370 | /**
371 | * Generates a badge for method
372 | *
373 | * @param array $data
374 | * @return string
375 | */
376 | protected function generateBadgeForMethod($data)
377 | {
378 | $method = strtoupper($data['ApiMethod'][0]['type']);
379 |
380 | $st_labels = [
381 | 'POST' => 'label-primary',
382 | 'GET' => 'label-success',
383 | 'PUT' => 'label-warning',
384 | 'DELETE' => 'label-danger',
385 | 'PATCH' => 'label-default',
386 | 'OPTIONS'=> 'label-info'
387 | ];
388 |
389 | return ''.$method.'';
390 | }
391 |
392 | /**
393 | * Load required template
394 | *
395 | * @param string $s_name Template name
396 | * @return string
397 | */
398 | public function loadPartialTemplate($s_name)
399 | {
400 | $content = file_get_contents($this->template_path."/partial/".$s_name.".html");
401 |
402 | return $content;
403 | }
404 |
405 | /**
406 | * Output the annotations in json format
407 | *
408 | * @return json
409 | */
410 | public function renderJson()
411 | {
412 | $st_annotations = $this->extractAnnotations();
413 |
414 | $o_view = new JsonView();
415 | $o_view->set('annotations', $st_annotations);
416 | $o_view->render();
417 | }
418 |
419 | /**
420 | * Output the annotations in json format
421 | *
422 | * @return array
423 | */
424 | public function renderArray()
425 | {
426 | return $this->extractAnnotations();
427 | }
428 |
429 | /**
430 | * Build the docs
431 | */
432 | public function generate()
433 | {
434 | return $this->generateTemplate();
435 | }
436 | }
437 |
--------------------------------------------------------------------------------
/Config.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | class Config
12 | {
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/Exception.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | class Exception extends \Exception
12 | {
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/Extractor.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | class Extractor
17 | {
18 | /**
19 | * Static array to store already parsed annotations
20 | * @var array
21 | */
22 | private static $annotationCache;
23 |
24 | /**
25 | * Indicates that annotations should has strict behavior, 'false' by default
26 | * @var boolean
27 | */
28 | private $strict = false;
29 |
30 | /**
31 | * Stores the default namespace for Objects instance, usually used on methods like getMethodAnnotationsObjects()
32 | * @var string
33 | */
34 | public $defaultNamespace = '';
35 |
36 | /**
37 | * Sets strict variable to true/false
38 | * @param bool $value boolean value to indicate that annotations to has strict behavior
39 | */
40 | public function setStrict($value)
41 | {
42 | $this->strict = (bool) $value;
43 | }
44 |
45 | /**
46 | * Sets default namespace to use in object instantiation
47 | * @param string $namespace default namespace
48 | */
49 | public function setDefaultNamespace($namespace)
50 | {
51 | $this->defaultNamespace = $namespace;
52 | }
53 |
54 | /**
55 | * Gets default namespace used in object instantiation
56 | * @return string $namespace default namespace
57 | */
58 | public function getDefaultAnnotationNamespace()
59 | {
60 | return $this->defaultNamespace;
61 | }
62 |
63 | /**
64 | * Gets all anotations with pattern @SomeAnnotation() from a given class
65 | *
66 | * @param string $className class name to get annotations
67 | * @return array self::$annotationCache all annotated elements
68 | */
69 | public static function getClassAnnotations($className)
70 | {
71 | if (!isset(self::$annotationCache[$className])) {
72 | $class = new \ReflectionClass($className);
73 | self::$annotationCache[$className] = self::parseAnnotations($class->getDocComment());
74 | }
75 |
76 | return self::$annotationCache[$className];
77 | }
78 |
79 | public static function getAllClassAnnotations($className)
80 | {
81 | $class = new \ReflectionClass($className);
82 |
83 | foreach ($class->getMethods() as $object) {
84 | self::$annotationCache['annotations'][$className][$object->name] = self::getMethodAnnotations($className, $object->name);
85 | }
86 |
87 | return self::$annotationCache['annotations'];
88 | }
89 |
90 | /**
91 | * Gets all anotations with pattern @SomeAnnotation() from a determinated method of a given class
92 | *
93 | * @param string $className class name
94 | * @param string $methodName method name to get annotations
95 | * @return array self::$annotationCache all annotated elements of a method given
96 | */
97 | public static function getMethodAnnotations($className, $methodName)
98 | {
99 | if (!isset(self::$annotationCache[$className . '::' . $methodName])) {
100 | try {
101 | $method = new \ReflectionMethod($className, $methodName);
102 | $class = new \ReflectionClass($className);
103 | $annotations = self::consolidateAnnotations($method->getDocComment(), $class->getDocComment());
104 | } catch (\ReflectionException $e) {
105 | $annotations = array();
106 | }
107 |
108 | self::$annotationCache[$className . '::' . $methodName] = $annotations;
109 | }
110 |
111 | return self::$annotationCache[$className . '::' . $methodName];
112 | }
113 |
114 | /**
115 | * Gets all anotations with pattern @SomeAnnotation() from a determinated method of a given class
116 | * and instance its abcAnnotation class
117 | *
118 | * @param string $className class name
119 | * @param string $methodName method name to get annotations
120 | * @return array self::$annotationCache all annotated objects of a method given
121 | */
122 | public function getMethodAnnotationsObjects($className, $methodName)
123 | {
124 | $annotations = $this->getMethodAnnotations($className, $methodName);
125 | $objects = array();
126 |
127 | $i = 0;
128 |
129 | foreach ($annotations as $annotationClass => $listParams) {
130 | $annotationClass = ucfirst($annotationClass);
131 | $class = $this->defaultNamespace . $annotationClass . 'Annotation';
132 |
133 | // verify is the annotation class exists, depending if Annotations::strict is true
134 | // if not, just skip the annotation instance creation.
135 | if (! class_exists($class)) {
136 | if ($this->strict) {
137 | throw new Exception(sprintf('Runtime Error: Annotation Class Not Found: %s', $class));
138 | } else {
139 | // silent skip & continue
140 | continue;
141 | }
142 | }
143 |
144 | if (empty($objects[$annotationClass])) {
145 | $objects[$annotationClass] = new $class();
146 | }
147 |
148 | foreach ($listParams as $params) {
149 | if (is_array($params)) {
150 | foreach ($params as $key => $value) {
151 | $objects[$annotationClass]->set($key, $value);
152 | }
153 | } else {
154 | $objects[$annotationClass]->set($i++, $params);
155 | }
156 | }
157 | }
158 |
159 | return $objects;
160 | }
161 |
162 | private static function consolidateAnnotations ($docblockMethod, $dockblockClass)
163 | {
164 | $methodAnnotations = self::parseAnnotations($docblockMethod);
165 | $classAnnotations = self::parseAnnotations($dockblockClass);
166 |
167 | if(count($methodAnnotations) === 0) {
168 | return array();
169 | }
170 |
171 | foreach ($classAnnotations as $name => $valueClass) {
172 | if (count($valueClass) !== 1) {
173 | continue;
174 | }
175 |
176 | if ($name === 'ApiRoute') {
177 | if (isset($methodAnnotations[$name])) {
178 | foreach ($methodAnnotations[$name] as $key => $valueMethod) {
179 | $methodAnnotations[$name][$key]['name'] = $valueClass[0]['name'] . $valueMethod['name'];
180 | }
181 | }
182 | }
183 |
184 | if($name === 'ApiSector') {
185 | $methodAnnotations[$name] = $valueClass;
186 | }
187 | }
188 |
189 | return $methodAnnotations;
190 | }
191 |
192 |
193 |
194 | /**
195 | * Parse annotations
196 | *
197 | * @param string $docblock
198 | * @return array parsed annotations params
199 | */
200 | private static function parseAnnotations($docblock)
201 | {
202 | $annotations = array();
203 |
204 | // Strip away the docblock header and footer to ease parsing of one line annotations
205 | $docblock = substr($docblock, 3, -2);
206 |
207 | if (preg_match_all('/@(?[A-Za-z_-]+)[\s\t]*\((?(?:(?!\)).)*)\)\r?/s', $docblock, $matches)) {
208 | $numMatches = count($matches[0]);
209 |
210 | for ($i = 0; $i < $numMatches; ++$i) {
211 | // annotations has arguments
212 | if (isset($matches['args'][$i])) {
213 | $argsParts = trim($matches['args'][$i]);
214 | $name = $matches['name'][$i];
215 | $value = self::parseArgs($argsParts);
216 | } else {
217 | $value = array();
218 | }
219 |
220 | $annotations[$name][] = $value;
221 | }
222 | }
223 |
224 | return $annotations;
225 | }
226 |
227 | /**
228 | * Parse individual annotation arguments
229 | *
230 | * @param string $content arguments string
231 | * @return array annotated arguments
232 | */
233 | private static function parseArgs($content)
234 | {
235 | // Replace initial stars
236 | $content = preg_replace('/^\s*\*/m', '', $content);
237 |
238 | $data = array();
239 | $len = strlen($content);
240 | $i = 0;
241 | $var = '';
242 | $val = '';
243 | $level = 1;
244 |
245 | $prevDelimiter = '';
246 | $nextDelimiter = '';
247 | $nextToken = '';
248 | $composing = false;
249 | $type = 'plain';
250 | $delimiter = null;
251 | $quoted = false;
252 | $tokens = array('"', '"', '{', '}', ',', '=');
253 |
254 | while ($i <= $len) {
255 | $prev_c = substr($content, $i -1, 1);
256 | $c = substr($content, $i++, 1);
257 |
258 | if ($c === '"' && $prev_c !== "\\") {
259 | $delimiter = $c;
260 | //open delimiter
261 | if (!$composing && empty($prevDelimiter) && empty($nextDelimiter)) {
262 | $prevDelimiter = $nextDelimiter = $delimiter;
263 | $val = '';
264 | $composing = true;
265 | $quoted = true;
266 | } else {
267 | // close delimiter
268 | if ($c !== $nextDelimiter) {
269 | throw new Exception(sprintf(
270 | "Parse Error: enclosing error -> expected: [%s], given: [%s]",
271 | $nextDelimiter, $c
272 | ));
273 | }
274 |
275 | // validating syntax
276 | if ($i < $len) {
277 | if (',' !== substr($content, $i, 1) && '\\' !== $prev_c) {
278 | throw new Exception(sprintf(
279 | "Parse Error: missing comma separator near: ...%s<--",
280 | substr($content, ($i-10), $i)
281 | ));
282 | }
283 | }
284 |
285 | $prevDelimiter = $nextDelimiter = '';
286 | $composing = false;
287 | $delimiter = null;
288 | }
289 | } elseif (!$composing && in_array($c, $tokens)) {
290 | switch ($c) {
291 | case '=':
292 | $prevDelimiter = $nextDelimiter = '';
293 | $level = 2;
294 | $composing = false;
295 | $type = 'assoc';
296 | $quoted = false;
297 | break;
298 | case ',':
299 | $level = 3;
300 |
301 | // If composing flag is true yet,
302 | // it means that the string was not enclosed, so it is parsing error.
303 | if ($composing === true && !empty($prevDelimiter) && !empty($nextDelimiter)) {
304 | throw new Exception(sprintf(
305 | "Parse Error: enclosing error -> expected: [%s], given: [%s]",
306 | $nextDelimiter, $c
307 | ));
308 | }
309 |
310 | $prevDelimiter = $nextDelimiter = '';
311 | break;
312 | case '{':
313 | $subc = '';
314 | $subComposing = true;
315 |
316 | while ($i <= $len) {
317 | $c = substr($content, $i++, 1);
318 |
319 | if (isset($delimiter) && $c === $delimiter) {
320 | throw new Exception(sprintf(
321 | "Parse Error: Composite variable is not enclosed correctly."
322 | ));
323 | }
324 |
325 | if ($c === '}') {
326 | $subComposing = false;
327 | break;
328 | }
329 | $subc .= $c;
330 | }
331 |
332 | // if the string is composing yet means that the structure of var. never was enclosed with '}'
333 | if ($subComposing) {
334 | throw new Exception(sprintf(
335 | "Parse Error: Composite variable is not enclosed correctly. near: ...%s'",
336 | $subc
337 | ));
338 | }
339 |
340 | $val = self::parseArgs($subc);
341 | break;
342 | }
343 | } else {
344 | if ($level == 1) {
345 | $var .= $c;
346 | } elseif ($level == 2) {
347 | $val .= $c;
348 | }
349 | }
350 |
351 | if ($level === 3 || $i === $len) {
352 | if ($type == 'plain' && $i === $len) {
353 | $data = self::castValue($var);
354 | } else {
355 | $data[trim($var)] = self::castValue($val, !$quoted);
356 | }
357 |
358 | $level = 1;
359 | $var = $val = '';
360 | $composing = false;
361 | $quoted = false;
362 | }
363 | }
364 |
365 | return $data;
366 | }
367 |
368 | /**
369 | * Try determinate the original type variable of a string
370 | *
371 | * @param string $val string containing possibles variables that can be cast to bool or int
372 | * @param boolean $trim indicate if the value passed should be trimmed after to try cast
373 | * @return mixed returns the value converted to original type if was possible
374 | */
375 | private static function castValue($val, $trim = false)
376 | {
377 | if (is_array($val)) {
378 | foreach ($val as $key => $value) {
379 | $val[$key] = self::castValue($value);
380 | }
381 | } elseif (is_string($val)) {
382 | if ($trim) {
383 | $val = trim($val);
384 | }
385 | $val = stripslashes($val);
386 | $tmp = strtolower($val);
387 |
388 | if ($tmp === 'false' || $tmp === 'true') {
389 | $val = $tmp === 'true';
390 | } elseif (is_numeric($val)) {
391 | return $val + 0;
392 | }
393 |
394 | unset($tmp);
395 | }
396 |
397 | return $val;
398 | }
399 | }
400 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Calin Rada
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | php-apidoc
2 | ==========
3 |
4 | Generate documentation for php API based application. No dependency. No framework required.
5 |
6 | * [Requirements](#requirements)
7 | * [Installation](#installation)
8 | * [Usage](#usage)
9 | * [Available Methods](#methods)
10 | * [Preview](#preview)
11 | * [Tips](#tips)
12 | * [Known issues](#known-issues)
13 | * [TODO](#todo)
14 |
15 | ### Requirements
16 |
17 | PHP >= 5.3.2
18 |
19 | ### Installation
20 |
21 | The recommended installation is via composer. Just add the following line to your composer.json:
22 |
23 | ```json
24 | {
25 | ...
26 | "require": {
27 | ...
28 | "crada/php-apidoc": "@dev"
29 | }
30 | }
31 | ```
32 |
33 | ```bash
34 | $ php composer.phar update
35 | ```
36 | ### Usage
37 |
38 | ```php
39 | generate();
99 | } catch (Exception $e) {
100 | echo 'There was an error generating the documentation: ', $e->getMessage();
101 | }
102 |
103 | ```
104 |
105 | Then, execute it via CLI
106 |
107 | ```php
108 | $ php apidoc.php
109 | ```
110 |
111 | ### Available Methods
112 |
113 | Here is the list of methods available so far :
114 |
115 | * @ApiDescription(section="...", description="...")
116 | * @ApiMethod(type="(get|post|put|delete|patch")
117 | * @ApiRoute(name="...")
118 | * @ApiParams(name="...", type="...", nullable=..., description="...", [sample=".."])
119 | * @ApiHeaders(name="...", type="...", nullable=..., description="...")
120 | * @ApiReturnHeaders(sample="...")
121 | * @ApiReturn(type="...", sample="...")
122 | * @ApiBody(sample="...")
123 |
124 | ### Preview
125 |
126 | You can see a dummy generated documentation on http://calinrada.github.io/php-apidoc/
127 |
128 | ### Tips
129 |
130 | To generate complex object sample input, use the ApiParam "type=(object|array(object)|array)":
131 |
132 | ```php
133 | * @ApiParams(name="data", type="object", sample="{'user_id':'int','profile':{'email':'string','age':'integer'}}")
134 | ```
135 |
136 | ### Known issues
137 |
138 | I don't know any, but please tell me if you find something. PS: I have tested it only in Chrome !
139 |
140 | ### TODO
141 |
142 | * Implement options for JSONP
143 | * Implement "add fields" option
144 |
145 |
--------------------------------------------------------------------------------
/Resources/views/default/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{title}}
10 |
11 |
24 |
25 |
26 |
27 |
67 |
68 |
69 |
70 |
71 |
Api URL
72 |
73 |
74 |
75 |
76 |
77 | {{content}}
78 |
79 |
80 |
81 |
89 |
90 |
91 |
92 |
93 |
94 |
297 |
298 |
299 |
--------------------------------------------------------------------------------
/Resources/views/default/partial/main.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {{ description }}
23 |
24 |
25 |
Headers
26 |
27 | {{ headers }}
28 |
29 |
30 |
31 |
Parameters
32 |
33 | {{ parameters }}
34 |
35 |
36 |
37 |
Body
38 |
39 | {{ body }}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | {{ sandbox_form }}
48 |
49 |
50 | Response
51 |
52 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | {{ sample_response_headers }}
64 | {{ sample_response_body }}
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/Resources/views/default/partial/paramContentTpl.html:
--------------------------------------------------------------------------------
1 |
2 | {{ name }} |
3 | {{ type }} |
4 | {{ nullable }} |
5 | {{ description }} |
6 |
7 |
--------------------------------------------------------------------------------
/Resources/views/default/partial/paramSampleBtnTpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Resources/views/default/partial/paramTableTpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Name |
5 | Type |
6 | Required |
7 | Description |
8 |
9 |
10 |
11 | {{ tbody }}
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Resources/views/default/partial/samplePostBodyTpl.html:
--------------------------------------------------------------------------------
1 | {{ body }}
2 |
--------------------------------------------------------------------------------
/Resources/views/default/partial/sampleReponseHeaderTpl.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/views/default/partial/sampleReponseTpl.html:
--------------------------------------------------------------------------------
1 | {{ description }}
2 |
3 | {{ response }}
4 |
--------------------------------------------------------------------------------
/Resources/views/default/partial/sandboxFormInputTpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Resources/views/default/partial/sandboxFormTpl.html:
--------------------------------------------------------------------------------
1 |
2 | Headers
3 |
4 |
7 |
8 |
9 |
16 |
17 |
--------------------------------------------------------------------------------
/Response.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | class Response
12 | {
13 | /**
14 | * Set header
15 | *
16 | * @param string $key
17 | * @param string $value
18 | * @param integer $http_response_code Optional
19 | * @example $response->setHeader('Content-type','application/json')
20 | */
21 | public function setHeader($key, $value, $http_response_code = null)
22 | {
23 | header($key.': '.$value, null, $http_response_code);
24 | }
25 |
26 | /**
27 | * Set content type for the output
28 | *
29 | * @param string $s_contentType
30 | * @see http://www.freeformatter.com/mime-types-list.html
31 | */
32 | public function setContentType($s_contentType)
33 | {
34 | header('Content-type: '.$s_contentType);
35 | }
36 |
37 | /**
38 | * Sends connection: close header
39 | */
40 | public function closeConection()
41 | {
42 | header('Connection: close');
43 | }
44 |
45 | /**
46 | * Wraper for echo
47 | *
48 | * @param $data
49 | */
50 | public function send($data)
51 | {
52 | echo $data;
53 | exit(0);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Template.php:
--------------------------------------------------------------------------------
1 |
10 | *
11 | * @license http://opensource.org/licenses/bsd-license.php The BSD License
12 | * @author Calin Rada
13 | */
14 | class Template {
15 | /**
16 | * Content variables
17 | * @access private
18 | * @var array
19 | */
20 | private $vars = [];
21 |
22 | /**
23 | * Content delimiters
24 | * @access private
25 | * @var string
26 | */
27 | private $l_delim = '{{',
28 | $r_delim = '}}';
29 |
30 | /**
31 | * Set template property in template file
32 | * @access public
33 | * @param string $key property name
34 | * @param string $value property value
35 | */
36 | public function assign( $key, $value )
37 | {
38 | $this->vars[$key] = $value;
39 | }
40 |
41 | /**
42 | * Parse template file
43 | * @access public
44 | * @param string $template_file
45 | */
46 | public function parse( $template_file )
47 | {
48 | if ( file_exists( $template_file ) ) {
49 | $content = file_get_contents($template_file);
50 |
51 | foreach ( $this->vars as $key => $value ) {
52 | if ( is_array( $value ) ) {
53 | $content = $this->parsePair($key, $value, $content);
54 | } else {
55 | $content = $this->parseSingle($key, (string) $value, $content);
56 | }
57 | }
58 |
59 | return $content;
60 | } else {
61 | exit( 'Template error
' );
62 | }
63 | }
64 |
65 | /**
66 | * Parsing content for single varliable
67 | * @access private
68 | * @param string $key property name
69 | * @param string $value property value
70 | * @param string $string content to replace
71 | * @param integer $index index of loop item
72 | * @return string replaced content
73 | */
74 | private function parseSingle( $key, $value, $string, $index = null )
75 | {
76 | if ( isset( $index ) ) {
77 | $string = str_replace( $this->l_delim . '%index%' . $this->r_delim, $index, $string );
78 | }
79 | return str_replace( $this->l_delim . $key . $this->r_delim, $value, $string );
80 | }
81 |
82 | /**
83 | * Parsing content for loop varliable
84 | * @access private
85 | * @param string $variable loop name
86 | * @param string $value loop data
87 | * @param string $string content to replace
88 | * @return string replaced content
89 | */
90 | private function parsePair( $variable, $data, $string )
91 | {
92 | $match = $this->matchPair($string, $variable);
93 | if( $match == false ) return $string;
94 |
95 | $str = '';
96 | foreach ( $data as $k_row => $row ) {
97 | $temp = $match['1'];
98 | foreach( $row as $key => $val ) {
99 | if( !is_array( $val ) ) {
100 | $index = array_search( $k_row, array_keys( $data ) );
101 | $temp = $this->parseSingle( $key, $val, $temp, $index );
102 | } else {
103 | $temp = $this->parsePair( $key, $val, $temp );
104 | }
105 | }
106 | $str .= $temp;
107 | }
108 |
109 | return str_replace( $match['0'], $str, $string );
110 | }
111 |
112 | /**
113 | * Match loop pair
114 | * @access private
115 | * @param string $string content with loop
116 | * @param string $variable loop name
117 | * @return string matched content
118 | */
119 | private function matchPair( $string, $variable )
120 | {
121 | if ( !preg_match("|" . preg_quote($this->l_delim) . 'loop ' . $variable . preg_quote($this->r_delim) . "(.+?)". preg_quote($this->l_delim) . 'end loop' . preg_quote($this->r_delim) . "|s", $string, $match ) ) {
122 | return false;
123 | }
124 |
125 | return $match;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/TestClasses/Article.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class View extends BaseView
14 | {
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/View/BaseView.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class BaseView implements ViewInterface
14 | {
15 | /**
16 | * Template name
17 | *
18 | * @var string
19 | */
20 | protected $s_file;
21 |
22 | /**
23 | * Data array that has to be rendered
24 | *
25 | * @var array
26 | */
27 | protected $st_data = array();
28 |
29 | /**
30 | * Array with config parameters for the View
31 | *
32 | * @var array
33 | */
34 | protected $st_viewConfig;
35 |
36 | /**
37 | * View object
38 | *
39 | * @var object
40 | */
41 | protected $o_view;
42 |
43 | /**
44 | * {@inheritdoc}
45 | */
46 | public function __init() {}
47 |
48 | /**
49 | * Set configuration params from the config file.
50 | *
51 | * @param array $st_config
52 | */
53 | public function setConfig($st_config)
54 | {
55 | $this->st_viewConfig = $st_config;
56 | }
57 |
58 | /**
59 | * Get config param(s)
60 | *
61 | * @param string $s_key
62 | * @return string|array|boolean
63 | */
64 | public function getConfig($s_key = null)
65 | {
66 | if ($s_key) {
67 | if (isset($this->st_viewConfig[$s_key])) {
68 | return $this->st_viewConfig[$s_key];
69 | }
70 |
71 | return false;
72 | }
73 |
74 | return $this->st_viewConfig;
75 | }
76 |
77 | /**
78 | * Set template file
79 | * @param string $file Full path to file
80 | */
81 | public function setTemplate($file)
82 | {
83 | $this->s_file = $file;
84 | }
85 |
86 | /**
87 | * {@inheritdoc}
88 | */
89 | public function getTemplate()
90 | {
91 | return $this->s_file;
92 | }
93 |
94 | /**
95 | * Set parameters to render
96 | * @param string $key
97 | * @param mixed $value Value - can be string / array
98 | * @param boolean $b_skipKey If true, the value will be assign directly to $this->st_data
99 | */
100 | public function set($key, $value, $b_skipKey = false)
101 | {
102 | if (true === $b_skipKey) {
103 | $this->st_data = $value;
104 | } else {
105 | $this->st_data[$key] = $value;
106 | }
107 | }
108 |
109 | /**
110 | * Get a value by key
111 | * @param string $key Key name
112 | */
113 | public function get($key)
114 | {
115 | return $this->st_data[$key];
116 | }
117 |
118 | /**
119 | * {@inheritdoc}
120 | */
121 | public function getAll()
122 | {
123 | return $this->st_data;
124 | }
125 |
126 | /**
127 | * {@inheritdoc}
128 | */
129 | public function render()
130 | {
131 | $file = str_replace('\\','/',$this->getTemplate()).'.php';
132 | if (!file_exists($file)) {
133 | throw new Exception('Template ' . $file . ' does not exist.');
134 | }
135 | extract($this->st_data);
136 | ob_start();
137 | include($file);
138 | $output = ob_get_contents();
139 | ob_end_clean();
140 | echo $output;
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/View/JsonView.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | class JsonView extends BaseView
12 | {
13 | /**
14 | * {@inheritdoc}
15 | */
16 | public function render()
17 | {
18 | $data = json_encode($this->st_data, JSON_FORCE_OBJECT);
19 |
20 | $response = new \Crada\Apidoc\Response();
21 | $response->setContentType('application/json');
22 | $response->closeConection();
23 | $response->send($data);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/View/ViewInterface.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | interface ViewInterface
14 | {
15 | /**
16 | * Act as constructor
17 | */
18 | public function __init();
19 |
20 | /**
21 | * Set template file
22 | * @param string $file Full path to file
23 | */
24 | public function setTemplate($file);
25 |
26 | /**
27 | * Get current template
28 | */
29 | public function getTemplate();
30 |
31 | /**
32 | * Set parameters to render
33 | * @param string $key
34 | * @param mixed $value Value - can be string / array
35 | */
36 | public function set($key, $value);
37 |
38 | /**
39 | * Get a value by key
40 | * @param string $key Key name
41 | */
42 | public function get($key);
43 |
44 | /**
45 | * Get all values
46 | */
47 | public function getAll();
48 |
49 | /**
50 | * Render template
51 | */
52 | public function render();
53 | }
54 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "crada/php-apidoc",
3 | "type": "library",
4 | "description": "Generate documentation for php API based application. No dependency. No framework required.",
5 | "keywords": ["php","api","documentation","generator"],
6 | "homepage": "https://github.com/calinrada/php-apidoc",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Calin Rada",
11 | "email": "rada.calin@gmail.com"
12 | }, {
13 | "name": "Kevin Saliou",
14 | "email": "kevin@saliou.name"
15 | }, {
16 | "name": "Borja Santos-Diez",
17 | "email": "borja@santosdiez.es"
18 | }
19 | ],
20 | "minimum-stability": "dev",
21 | "require": {
22 | "php": ">=5.3.2"
23 | },
24 | "autoload": {
25 | "psr-0": { "Crada": "" }
26 | },
27 | "target-dir": "Crada/Apidoc",
28 | "extra": {
29 | "branch-alias": {
30 | "dev-master": "1.3-dev"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------