├── LICENSE ├── PHPFuncParser.php ├── README.md └── test ├── sourcefile.php └── test.php /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Horst Xu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /PHPFuncParser.php: -------------------------------------------------------------------------------- 1 | contents =& $_contents; 39 | 40 | $this->in_php = false; 41 | $this->state = self::STATE_INIT; 42 | 43 | $this->function_list = array(); 44 | $this->class_name = ""; 45 | $this->function_name = ""; 46 | $this->start_line = -1; 47 | $this->end_line = -1; 48 | 49 | $this->last_line = 0; 50 | $this->left_func_method_brace_cnt = 0; //进入函数或方法内部时统计括号数量 51 | $this->left_class_brace_cnt = 0; //进入到类内部时统计括号数量 52 | $this->left_brace_cnt = 0; //总的括号统计数量 53 | $this->meet_to_in_func_method = 0; //"t_function => 1"; "t_string => 2";"( => 3"; ") => 4"; "{ => 5"; "} => 0" 54 | 55 | $this->prev_token = false; 56 | } 57 | 58 | private function update_last_line($token){ 59 | if (is_array($token)) { 60 | $this->last_line = $token[2]; //记录当前行的行号 61 | return; 62 | } 63 | // is_string 64 | if(is_array($this->prev_token)){ 65 | if($this->prev_token[0] == T_WHITESPACE){ 66 | $count1 = substr_count($this->prev_token[1], "\r"); 67 | $count2 = substr_count($this->prev_token[1], "\n"); 68 | $this->last_line = $this->prev_token[2] + max($count1, $count2); 69 | } else { 70 | $this->last_line = $this->prev_token[2]; 71 | } 72 | } 73 | } 74 | 75 | public function process() 76 | { 77 | $this->tokens = token_get_all($this->contents); 78 | foreach ($this->tokens as &$token) { 79 | $this->update_last_line($token); 80 | switch ($this->state) { 81 | case self::STATE_INIT: 82 | if ($this->should_change_init_to_meet_func($token)) { 83 | $this->change_init_to_meet_func($token); 84 | } else if ($this->should_change_init_to_meet_class($token)) { 85 | $this->change_init_to_meet_class($token); 86 | } else { 87 | $this->update_init_info($token); 88 | } 89 | break; 90 | case self::STATE_MEET_FUNC: 91 | if ($this->should_change_meet_func_to_init($token)) { 92 | $this->change_meet_func_to_init($token); 93 | } else if ($this->should_change_meet_func_to_in_func($token)) { 94 | $this->change_meet_func_to_in_func($token); 95 | } else { 96 | $this->update_meet_func_info($token); 97 | } 98 | break; 99 | case self::STATE_IN_FUNC: 100 | if ($this->should_change_in_func_to_init($token)) { 101 | $this->change_in_func_to_init($token); 102 | } else { 103 | $this->update_in_func_info($token); 104 | } 105 | break; 106 | case self::STATE_MEET_CLASS: 107 | if ($this->should_change_meet_class_to_init($token)) { 108 | $this->change_meet_class_to_init($token); 109 | } else if ($this->should_change_meet_class_to_in_class($token)) { 110 | $this->change_meet_class_to_in_class($token); 111 | } else { 112 | $this->update_meet_class_info($token); 113 | } 114 | break; 115 | case self::STATE_IN_CLASS: 116 | if ($this->should_change_in_class_to_init($token)) { 117 | $this->change_in_class_to_init($token); 118 | } else if ($this->should_change_in_class_to_meet_method($token)) { 119 | $this->change_in_class_to_meet_method($token); 120 | } else { 121 | $this->update_in_class_info($token); 122 | } 123 | break; 124 | case self::STATE_MEET_METHOD: 125 | if ($this->should_change_meet_method_to_in_class($token)) { 126 | $this->change_meet_method_to_in_class($token); 127 | } else if ($this->should_change_meet_method_to_in_method($token)) { 128 | $this->change_meet_method_to_in_method($token); 129 | } else { 130 | $this->update_meet_method_info($token); 131 | } 132 | break; 133 | case self::STATE_IN_METHOD: 134 | if ($this->should_change_in_method_to_in_class($token)) { 135 | $this->change_in_method_to_in_class($token); 136 | } else { 137 | $this->update_in_method_info($token); 138 | } 139 | break; 140 | default: 141 | break; 142 | } 143 | $this->prev_token = $token; 144 | } 145 | 146 | if($this->left_class_brace_cnt != 0 || $this->left_brace_cnt != 0 || $this->left_func_method_brace_cnt != 0){ 147 | throw new RuntimeException($this->error_message("Brace Mismatch!")); 148 | } 149 | 150 | return $this->function_list; 151 | } 152 | 153 | private function should_change_in_method_to_in_class($token) { 154 | if (!$this->in_php || $this->state != self::STATE_IN_METHOD || is_array($token)) { 155 | return false; 156 | } 157 | if (is_string($token) && $token == "}" && $this->left_func_method_brace_cnt == 1 && 158 | $this->left_class_brace_cnt > 0) { 159 | return true; 160 | } 161 | return false; 162 | } 163 | 164 | private function change_in_method_to_in_class($token) { 165 | if($this->state != self::STATE_IN_METHOD){ 166 | return; 167 | } 168 | $this->state = self::STATE_IN_CLASS; 169 | $this->end_line = $this->last_line; 170 | $this->record_information(); //记录当前的数据 171 | $this->function_name = ""; 172 | $this->start_line = -1; 173 | $this->end_line = -1; 174 | $this->left_func_method_brace_cnt = 0; //func内部括号数目归零 175 | $this->left_class_brace_cnt --; //类内部括号数减一 176 | $this->left_brace_cnt --; //总计括号数减一 177 | $this->meet_to_in_func_method = 0; //子状态归零 178 | } 179 | 180 | private function update_in_method_info($token) { 181 | if(is_array($token)){ 182 | switch ($token[0]) { 183 | case T_CLOSE_TAG: 184 | $this->in_php = false; 185 | break; 186 | case T_OPEN_TAG: 187 | case T_OPEN_TAG_WITH_ECHO: 188 | $this->in_php = true; 189 | break; 190 | case T_CURLY_OPEN: 191 | case T_DOLLAR_OPEN_CURLY_BRACES: 192 | case T_STRING_VARNAME: 193 | $this->left_brace_cnt++; 194 | $this->left_func_method_brace_cnt ++; 195 | $this->left_class_brace_cnt ++; 196 | break; 197 | case T_FUNCTION: 198 | case T_CLASS: 199 | break; 200 | default: 201 | break; 202 | } 203 | } else { 204 | switch ($token){ 205 | case "{": 206 | $this->left_brace_cnt ++; 207 | $this->left_func_method_brace_cnt ++; 208 | $this->left_class_brace_cnt ++; 209 | break; 210 | case "}": 211 | $this->left_brace_cnt --; 212 | $this->left_func_method_brace_cnt --; 213 | $this->left_class_brace_cnt --; 214 | break; 215 | default: 216 | break; 217 | } 218 | } 219 | } 220 | 221 | private function update_meet_method_info($token) { 222 | if (is_array($token)) { 223 | switch ($token[0]) { 224 | case T_OPEN_TAG: 225 | case T_OPEN_TAG_WITH_ECHO: 226 | $this->in_php = true; 227 | break; 228 | case T_CLOSE_TAG: 229 | $this->in_php = false; 230 | break; 231 | case T_CURLY_OPEN: 232 | case T_DOLLAR_OPEN_CURLY_BRACES: 233 | case T_STRING_VARNAME: 234 | $this->left_brace_cnt ++; 235 | $this->left_class_brace_cnt ++; 236 | break; 237 | case T_STRING: //函数名称 238 | $this->function_name = $token[1]; 239 | if($this->meet_to_in_func_method == 1) { 240 | $this->meet_to_in_func_method = 2; 241 | } 242 | break; 243 | case T_FUNCTION: 244 | case T_CLASS: 245 | throw new RuntimeException($this->error_message($token)); 246 | break; 247 | default: 248 | break; 249 | } 250 | } else { 251 | switch ($token) { 252 | case "{": 253 | $this->left_brace_cnt ++; 254 | $this->left_class_brace_cnt ++; 255 | break; 256 | case "(": 257 | if ($this->meet_to_in_func_method == 2) { 258 | $this->meet_to_in_func_method = 3; 259 | } 260 | break; 261 | case ")": 262 | if ($this->meet_to_in_func_method == 3) { 263 | $this->meet_to_in_func_method = 4; 264 | } 265 | break; 266 | case "}": 267 | $this->left_brace_cnt --; 268 | $this->left_class_brace_cnt --; 269 | break; 270 | default: 271 | break; 272 | } 273 | } 274 | } 275 | 276 | private function change_meet_method_to_in_class($token){ 277 | if ($this->state != self::STATE_MEET_METHOD) { 278 | return; 279 | } 280 | $this->state = self::STATE_IN_CLASS; 281 | if($token == "{") { 282 | $this->left_brace_cnt ++; 283 | $this->left_class_brace_cnt ++; 284 | } else if($token == "}"){ 285 | $this->left_brace_cnt --; 286 | $this->left_class_brace_cnt --; 287 | } 288 | $this->left_func_method_brace_cnt = 0; 289 | $this->meet_to_in_func_method = 0; 290 | $this->start_line = -1; 291 | $this->end_line = -1; 292 | $this->function_name = ""; 293 | } 294 | 295 | private function should_change_meet_method_to_in_class($token){ 296 | if (!$this->in_php || $this->state != self::STATE_MEET_METHOD || is_array($token)) { 297 | return false; 298 | } 299 | if (is_string($token) && $token == ";") { 300 | return true; 301 | } 302 | if (is_string($token) && ($token == "(" || $token == ")" || $token == "{" || $token == "}") && $this->function_name == ""){ 303 | return true; 304 | } 305 | return false; 306 | } 307 | 308 | private function change_meet_method_to_in_method($token) { 309 | if($this->state != self::STATE_MEET_METHOD){ 310 | return; 311 | } 312 | $this->state = self::STATE_IN_METHOD; 313 | $this->meet_to_in_func_method = 5; 314 | $this->left_brace_cnt ++; 315 | $this->left_class_brace_cnt ++; 316 | $this->left_func_method_brace_cnt ++; 317 | } 318 | 319 | private function should_change_meet_method_to_in_method($token) { 320 | if (!$this->in_php || $this->state != self::STATE_MEET_METHOD || is_array($token)) { 321 | return false; 322 | } 323 | if (is_string($token) && $token == "{" && $this->meet_to_in_func_method == 4) { 324 | return true; 325 | } 326 | return false; 327 | } 328 | 329 | private function update_in_class_info($token) { 330 | if(is_array($token)){ 331 | switch ($token[0]) { 332 | case T_CLOSE_TAG: 333 | $this->in_php = false; 334 | break; 335 | case T_OPEN_TAG: 336 | case T_OPEN_TAG_WITH_ECHO: 337 | $this->in_php = true; 338 | break; 339 | case T_CURLY_OPEN: 340 | case T_DOLLAR_OPEN_CURLY_BRACES: 341 | case T_STRING_VARNAME: 342 | $this->left_brace_cnt++; 343 | $this->left_class_brace_cnt ++; 344 | break; 345 | case T_FUNCTION: 346 | case T_CLASS: 347 | break; 348 | default: 349 | break; 350 | } 351 | } else { 352 | switch ($token){ 353 | case "{": 354 | $this->left_brace_cnt ++; 355 | $this->left_class_brace_cnt ++; 356 | break; 357 | case "}": 358 | $this->left_brace_cnt --; 359 | $this->left_class_brace_cnt --; 360 | break; 361 | default: 362 | break; 363 | } 364 | } 365 | } 366 | 367 | private function change_in_class_to_init($token) { 368 | if($this->state != self::STATE_IN_CLASS){ 369 | return; 370 | } 371 | $this->state = self::STATE_INIT; 372 | $this->left_brace_cnt --; 373 | $this->left_class_brace_cnt --; 374 | $this->class_name = ""; 375 | $this->function_name = ""; 376 | $this->start_line = -1; 377 | $this->end_line = -1; 378 | } 379 | 380 | private function should_change_in_class_to_init($token) { 381 | if(!$this->in_php || $this->state != self::STATE_IN_CLASS || is_array($token)){ 382 | return false; 383 | } 384 | if(is_string($token) && $token == "}" && $this->left_class_brace_cnt == 1 && 385 | $this->left_func_method_brace_cnt == 0 && $this->left_brace_cnt == 1){ 386 | return true; 387 | } 388 | return false; 389 | } 390 | 391 | private function change_in_class_to_meet_method($token) { 392 | if($this->state != self::STATE_IN_CLASS){ 393 | return; 394 | } 395 | $this->state = self::STATE_MEET_METHOD; 396 | $this->start_line = $this->last_line; 397 | $this->meet_to_in_func_method = 1; 398 | } 399 | 400 | private function should_change_in_class_to_meet_method($token) { 401 | if(!$this->in_php || $this->state != self::STATE_IN_CLASS || !is_array($token) || $token[0] != T_FUNCTION){ 402 | return false; 403 | } 404 | if($this->class_name == "" || $this->left_class_brace_cnt < 1 || $this->left_func_method_brace_cnt != 0) { 405 | return false; 406 | } 407 | return true; 408 | } 409 | 410 | private function update_meet_class_info($token) { 411 | if(is_array($token)){ 412 | switch ($token[0]) { 413 | case T_CLOSE_TAG: 414 | $this->in_php = false; 415 | break; 416 | case T_OPEN_TAG: 417 | case T_OPEN_TAG_WITH_ECHO: 418 | $this->in_php = true; 419 | break; 420 | case T_CURLY_OPEN: 421 | case T_DOLLAR_OPEN_CURLY_BRACES: 422 | case T_STRING_VARNAME: 423 | $this->left_brace_cnt++; 424 | break; 425 | case T_STRING: 426 | $this->class_name = $token[1]; 427 | break; 428 | case T_FUNCTION: 429 | case T_CLASS: 430 | break; 431 | default: 432 | break; 433 | } 434 | } else { 435 | switch ($token){ 436 | case "{": 437 | $this->left_brace_cnt ++; 438 | break; 439 | case "}": 440 | $this->left_brace_cnt --; 441 | break; 442 | default: 443 | break; 444 | } 445 | } 446 | } 447 | 448 | private function should_change_meet_class_to_in_class($token){ 449 | if(!$this->in_php || $this->state != self::STATE_MEET_CLASS || is_array($token)){ 450 | return false; 451 | } 452 | if($this->class_name != "" && $token == "{"){ 453 | return true; 454 | } 455 | return false; 456 | } 457 | 458 | private function change_meet_class_to_in_class($token) { 459 | if($this->state != self::STATE_MEET_CLASS){ 460 | return; 461 | } 462 | $this->state = self::STATE_IN_CLASS; 463 | $this->left_brace_cnt ++; //全局左括号数量 464 | $this->left_class_brace_cnt ++; //类中的左括号数量 465 | } 466 | 467 | private function should_change_meet_class_to_init($token) { 468 | if(!$this->in_php || $this->state != self::STATE_MEET_CLASS || is_array($token)){ 469 | return false; 470 | } 471 | if(is_string($token) && $token == ";"){ //碰到分号 472 | return true; 473 | } 474 | if(is_string($token) && ($token == "(" || $token == ")" || $token == "{" || $token == "}") && $this->class_name == ""){ 475 | return true; 476 | } 477 | return false; 478 | } 479 | 480 | private function change_meet_class_to_init($token) { 481 | if($this->state != self::STATE_MEET_CLASS) { 482 | return; 483 | } 484 | if($token == "{"){ 485 | $this->left_brace_cnt ++; 486 | } else if($token == "}"){ 487 | $this->left_brace_cnt --; 488 | } 489 | $this->state = self::STATE_INIT; 490 | $this->class_name = ""; 491 | $this->function_name = ""; 492 | $this->start_line = -1; 493 | $this->end_line = -1; 494 | $this->left_func_method_brace_cnt = 0; 495 | $this->meet_to_in_func_method = 0; 496 | } 497 | 498 | private function update_in_func_info($token){ 499 | if(is_array($token)){ 500 | switch ($token[0]) { 501 | case T_CLOSE_TAG: 502 | $this->in_php = false; 503 | break; 504 | case T_OPEN_TAG: 505 | case T_OPEN_TAG_WITH_ECHO: 506 | $this->in_php = true; 507 | break; 508 | case T_CURLY_OPEN: 509 | case T_DOLLAR_OPEN_CURLY_BRACES: 510 | case T_STRING_VARNAME: 511 | $this->left_brace_cnt++; 512 | $this->left_func_method_brace_cnt ++; 513 | break; 514 | case T_FUNCTION: 515 | case T_CLASS: 516 | break; 517 | default: 518 | break; 519 | } 520 | } else { 521 | switch ($token){ 522 | case "{": 523 | $this->left_brace_cnt ++; 524 | $this->left_func_method_brace_cnt ++; 525 | break; 526 | case "}": 527 | $this->left_brace_cnt --; 528 | $this->left_func_method_brace_cnt --; 529 | break; 530 | default: 531 | break; 532 | } 533 | } 534 | } 535 | 536 | private function change_in_func_to_init($token){ 537 | if($this->state != self::STATE_IN_FUNC){ 538 | return; 539 | } 540 | $this->end_line = $this->last_line; 541 | $this->record_information(); //记录当前的数据 542 | $this->state = self::STATE_INIT; 543 | $this->function_name = ""; 544 | $this->start_line = -1; 545 | $this->end_line = -1; 546 | $this->left_func_method_brace_cnt = 0; //func内部括号数目归零 547 | $this->left_brace_cnt --; 548 | $this->meet_to_in_func_method = 0; //子状态归零 549 | } 550 | 551 | private function should_change_in_func_to_init($token) { 552 | if (!$this->in_php || $this->state != self::STATE_IN_FUNC || is_array($token)) { 553 | return false; 554 | } 555 | if (is_string($token) && $token == "}" && $this->left_func_method_brace_cnt == 1 && 556 | $this->left_class_brace_cnt == 0) { 557 | return true; 558 | } 559 | return false; 560 | } 561 | 562 | private function should_change_init_to_meet_func($token) 563 | { 564 | if (!$this->in_php || $this->state != self::STATE_INIT || !is_array($token) || $token[0] != T_FUNCTION) { 565 | return false; 566 | } 567 | if ($this->left_class_brace_cnt != 0 || $this->left_func_method_brace_cnt != 0) { 568 | return false; 569 | } 570 | return true; 571 | } 572 | 573 | private function change_init_to_meet_func($token) 574 | { 575 | if ($this->state != self::STATE_INIT) { 576 | return; 577 | } 578 | $this->state = self::STATE_MEET_FUNC; 579 | $this->start_line = $this->last_line; 580 | $this->meet_to_in_func_method = 1; 581 | } 582 | 583 | private function should_change_init_to_meet_class($token) 584 | { 585 | if (!$this->in_php || $this->state != self::STATE_INIT || !is_array($token) || $token[0] != T_CLASS) { 586 | return false; 587 | } 588 | if ($this->left_class_brace_cnt != 0 || $this->left_func_method_brace_cnt != 0) { 589 | return false; 590 | } 591 | return true; 592 | } 593 | 594 | private function change_init_to_meet_class($token) 595 | { 596 | if ($this->state != self::STATE_INIT) { 597 | return; 598 | } 599 | $this->state = self::STATE_MEET_CLASS; 600 | } 601 | 602 | private function update_init_info($token) 603 | { 604 | if (is_array($token)) { 605 | switch ($token[0]) { 606 | case T_CLOSE_TAG: 607 | $this->in_php = false; 608 | break; 609 | case T_OPEN_TAG: 610 | case T_OPEN_TAG_WITH_ECHO: 611 | $this->in_php = true; 612 | break; 613 | case T_CURLY_OPEN: 614 | case T_DOLLAR_OPEN_CURLY_BRACES: 615 | case T_STRING_VARNAME: 616 | $this->left_brace_cnt++; 617 | break; 618 | case T_FUNCTION: 619 | case T_CLASS: 620 | break; 621 | default: 622 | break; 623 | } 624 | } else { 625 | switch ($token) { 626 | case "{": 627 | $this->left_brace_cnt++; 628 | break; 629 | case "}": 630 | $this->left_brace_cnt--; 631 | break; 632 | default: 633 | break; 634 | } 635 | } 636 | 637 | } 638 | 639 | private function should_change_meet_func_to_init($token) 640 | { 641 | if (!$this->in_php || $this->state != self::STATE_MEET_FUNC || is_array($token)) { 642 | return false; 643 | } 644 | if (is_string($token) && $token == ";") { 645 | return true; 646 | } 647 | if (is_string($token) && ($token == "(" || $token == ")" || $token == "{" || $token == "}") && $this->function_name == ""){ 648 | return true; 649 | } 650 | return false; 651 | } 652 | 653 | private function change_meet_func_to_init($token) 654 | { 655 | if ($this->state != self::STATE_MEET_FUNC) { 656 | return; 657 | } 658 | $this->state = self::STATE_INIT; 659 | if($token == "{"){ 660 | $this->left_brace_cnt ++; 661 | } else if($token == "}"){ 662 | $this->left_brace_cnt --; 663 | } 664 | $this->left_func_method_brace_cnt = 0; 665 | $this->meet_to_in_func_method = 0; 666 | $this->start_line = -1; 667 | $this->end_line = -1; 668 | $this->function_name = ""; 669 | } 670 | 671 | private function should_change_meet_func_to_in_func($token) 672 | { 673 | if (!$this->in_php || $this->state != self::STATE_MEET_FUNC || is_array($token)) { 674 | return false; 675 | } 676 | if (is_string($token) && $token = "{" && $this->meet_to_in_func_method == 4) { 677 | return true; 678 | } 679 | return false; 680 | } 681 | 682 | private function change_meet_func_to_in_func($token) 683 | { 684 | if ($this->state != self::STATE_MEET_FUNC) { 685 | return; 686 | } 687 | $this->state = self::STATE_IN_FUNC; 688 | $this->meet_to_in_func_method = 5; 689 | $this->left_brace_cnt++; 690 | $this->left_func_method_brace_cnt++; 691 | } 692 | 693 | private function update_meet_func_info($token) 694 | { 695 | if (is_array($token)) { 696 | switch ($token[0]) { 697 | case T_OPEN_TAG: 698 | case T_OPEN_TAG_WITH_ECHO: 699 | $this->in_php = true; 700 | break; 701 | case T_CLOSE_TAG: 702 | $this->in_php = false; 703 | break; 704 | case T_CURLY_OPEN: 705 | case T_DOLLAR_OPEN_CURLY_BRACES: 706 | case T_STRING_VARNAME: 707 | $this->left_brace_cnt++; 708 | break; 709 | case T_STRING: //函数名称 710 | $this->function_name = $token[1]; 711 | if($this->meet_to_in_func_method == 1) { 712 | $this->meet_to_in_func_method = 2; 713 | } 714 | break; 715 | case T_FUNCTION: 716 | case T_CLASS: 717 | throw new RuntimeException($this->error_message($token)); 718 | break; 719 | default: 720 | break; 721 | } 722 | } else { 723 | switch ($token) { 724 | case "{": 725 | $this->left_brace_cnt++; 726 | break; 727 | case "(": 728 | if ($this->meet_to_in_func_method == 2) { 729 | $this->meet_to_in_func_method = 3; 730 | } 731 | break; 732 | case ")": 733 | if ($this->meet_to_in_func_method == 3) { 734 | $this->meet_to_in_func_method = 4; 735 | } 736 | break; 737 | case "}": 738 | $this->left_brace_cnt--; 739 | break; 740 | default: 741 | break; 742 | } 743 | } 744 | } 745 | 746 | private function record_information() 747 | { 748 | if ($this->function_name != "") { 749 | if ($this->class_name == "" && $this->start_line > 0 && $this->end_line > 0) { 750 | $this->function_list[$this->function_name] = array($this->start_line, $this->end_line); 751 | } else if ($this->class_name != "" && $this->start_line > 0 && $this->end_line > 0) { 752 | $full_name = $this->class_name . "::" . $this->function_name; 753 | $this->function_list[$full_name] = array($this->start_line, $this->end_line); 754 | } 755 | } 756 | $this->function_name = ""; 757 | $this->start_line = -1; 758 | $this->end_line = -1; 759 | } 760 | 761 | private function error_message($token) 762 | { 763 | if (is_array($token)) { 764 | return "Error occurs!\n" . 765 | "Token name: " . token_name($token[0]) . "\n" . 766 | "Source code line: " . $token[2] . "\n" . 767 | "Source code: \n" . 768 | "########################" . 769 | $token[1] . 770 | "########################" . "\n"; 771 | } 772 | if (is_string($token)) { 773 | return "Error occurs!\n" . 774 | "Error Infomation (Position): " . $token . "\n"; 775 | } 776 | return false; 777 | } 778 | } 779 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHPFunctionParser 2 | 本项目中提供了一个PHP类,用于静态解析PHP源代码文件,将源代码中所定义的所有函数或类方法解析出来,以数组的形式输出。解析出的内容包括函数名(方法名),起始行号,终止行号。可以很方便的运用在项目中。 3 | 4 | ## 使用方法 5 | 6 | 在test目录下的`test.php`脚本中给出了使用的例子。该类可能会抛出`RuntimeException`,所以使用时记得将其包含在`try catch`语句中。 7 | 8 | 本例中,`test.php`内容如下 9 | 10 | process(); 18 | print_r($result); 19 | } catch (RuntimeException $e) { 20 | print($e->getMessage()); 21 | exit(1); 22 | } 23 | 24 | 运行`test.php`可以得到如下结果 25 | 26 | Array 27 | ( 28 | [A::__construct] => Array 29 | ( 30 | [0] => 9 31 | [1] => 9 32 | ) 33 | 34 | [A::func1] => Array 35 | ( 36 | [0] => 11 37 | [1] => 13 38 | ) 39 | 40 | [A::func2] => Array 41 | ( 42 | [0] => 15 43 | [1] => 19 44 | ) 45 | 46 | [add] => Array 47 | ( 48 | [0] => 25 49 | [1] => 27 50 | ) 51 | ) 52 | 53 | 如果是全局函数,则直接显示为函数名,如果定义在类中,则显示为 `类名::函数名` 的格式。 注意,`interface`和`abstract function`不计算为函数定义,因为其只有声明,没有函数实现。上面的结果可以解释为,该源文件定义了类`A`,其中有类方法`__construct`在第9行定义;有类方法`func1`和`func2`,分别在第11至第13行,第15至第19行定义。源文件还定义了全局函数`add`,它在第25至27行定义。 54 | 55 | ## 原理 56 | 57 | 利用PHP `tokenizer` 将源代码转为token数组,并依次读取每一个token。借鉴有限状态机的原理实现函数识别。读取token过程中根据条件实时转换状态。状态共有7种: 58 | 59 | - 初始状态 (init) 60 | - 碰到函数定义 (meet function) 61 | - 在函数内部 (in function) 62 | - 碰到类定义 (meet class) 63 | - 在类内部 (in class) 64 | - 碰到类方法定义 (meet method) 65 | - 在类方法内部 (in method) 66 | 67 | 本项目中的PHP类主要实现了这一状态机,并通过状态转变来判断函数(方法)定义和函数(方法)位置。 68 | 69 | ## 注意事项 70 | 71 | - 目前暂不支持`namespace`,默认在全局命名空间下。 72 | - 本工具只负责解析源代码中定义的函数,但不负责语法检查。使用前需保证待检查的源代码是没有语法错误的,否则可能会抛出`RuntimeException`或其他错误。 73 | -------------------------------------------------------------------------------- /test/sourcefile.php: -------------------------------------------------------------------------------- 1 | process(); 9 | print_r($result); 10 | } catch(RuntimeException $e){ 11 | print($e->getMessage()); 12 | exit(1); 13 | } 14 | --------------------------------------------------------------------------------