ÿØÿàJFIFÿþ ÿÛC       ÿÛC ÿÀÿÄÿÄ"#QrÿÄÿÄ&1!A"2qQaáÿÚ ?Øy,æ/3JæÝ¹È߲؋5êXw²±ÉyˆR”¾I0ó2—PI¾IÌÚiMö¯–þrìN&"KgX:Šíµ•nTJnLK„…@!‰-ý ùúmë;ºgµŒ&ó±hw’¯Õ@”Ü— 9ñ-ë.²1<yà‚¹ïQÐU„ہ?.’¦èûbß±©Ö«Âw*VŒ) `$‰bØÔŸ’ëXÖ-ËTÜíGÚ3ð«g Ÿ§¯—Jx„–’U/ÂÅv_s(Hÿ@TñJÑãõçn­‚!ÈgfbÓc­:él[ðQe 9ÀPLbÃãCµm[5¿ç'ªjglå‡Ûí_§Úõl-;"PkÞÞÁQâ¼_Ñ^¢SŸx?"¸¦ùY騐ÒOÈ q’`~~ÚtËU¹CڒêV  I1Áß_ÿÙ#!/usr/bin/env php '/phar/phpunit/php-file-iterator/src/Iterator.php', 'file_iterator_facade' => '/phar/phpunit/php-file-iterator/src/Facade.php', 'file_iterator_factory' => '/phar/phpunit/php-file-iterator/src/Factory.php', 'php_codecoverage' => '/phar/phpunit/php-code-coverage/src/CodeCoverage.php', 'php_codecoverage_driver' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Driver.php', 'php_codecoverage_driver_hhvm' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Driver/HHVM.php', 'php_codecoverage_driver_phpdbg' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Driver/PHPDBG.php', 'php_codecoverage_driver_xdebug' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Driver/Xdebug.php', 'php_codecoverage_exception' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Exception.php', 'php_codecoverage_exception_unintentionallycoveredcode' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Exception/UnintentionallyCoveredCode.php', 'php_codecoverage_filter' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Filter.php', 'php_codecoverage_report_clover' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/Clover.php', 'php_codecoverage_report_crap4j' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/Crap4j.php', 'php_codecoverage_report_factory' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/Factory.php', 'php_codecoverage_report_html' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML.php', 'php_codecoverage_report_html_renderer' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML/Renderer.php', 'php_codecoverage_report_html_renderer_dashboard' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML/Renderer/Dashboard.php', 'php_codecoverage_report_html_renderer_directory' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML/Renderer/Directory.php', 'php_codecoverage_report_html_renderer_file' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML/Renderer/File.php', 'php_codecoverage_report_node' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/Node.php', 'php_codecoverage_report_node_directory' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/Node/Directory.php', 'php_codecoverage_report_node_file' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/Node/File.php', 'php_codecoverage_report_node_iterator' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/Node/Iterator.php', 'php_codecoverage_report_php' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/PHP.php', 'php_codecoverage_report_text' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/Text.php', 'php_codecoverage_report_xml' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML.php', 'php_codecoverage_report_xml_directory' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Directory.php', 'php_codecoverage_report_xml_file' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File.php', 'php_codecoverage_report_xml_file_coverage' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File/Coverage.php', 'php_codecoverage_report_xml_file_method' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File/Method.php', 'php_codecoverage_report_xml_file_report' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File/Report.php', 'php_codecoverage_report_xml_file_unit' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File/Unit.php', 'php_codecoverage_report_xml_node' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Node.php', 'php_codecoverage_report_xml_project' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Project.php', 'php_codecoverage_report_xml_tests' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Tests.php', 'php_codecoverage_report_xml_totals' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Totals.php', 'php_codecoverage_util' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Util.php', 'php_codecoverage_util_invalidargumenthelper' => '/phar/phpunit/php-code-coverage/src/CodeCoverage/Util/InvalidArgumentHelper.php', 'php_timer' => '/phar/phpunit/php-timer/src/Timer.php', 'php_token' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_abstract' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_ampersand' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_and_equal' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_array' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_array_cast' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_as' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_async' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_at' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_await' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_backtick' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_bad_character' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_bool_cast' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_boolean_and' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_boolean_or' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_break' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_callable' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_caret' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_case' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_catch' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_character' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_class' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_class_c' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_class_name_constant' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_clone' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_close_bracket' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_close_curly' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_close_square' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_close_tag' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_coalesce' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_colon' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_comma' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_comment' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_compiler_halt_offset' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_concat_equal' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_const' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_constant_encapsed_string' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_continue' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_curly_open' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_dec' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_declare' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_default' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_dir' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_div' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_div_equal' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_dnumber' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_do' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_doc_comment' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_dollar' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_dollar_open_curly_braces' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_dot' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_double_arrow' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_double_cast' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_double_colon' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_double_quotes' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_echo' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_ellipsis' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_else' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_elseif' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_empty' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_encapsed_and_whitespace' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_end_heredoc' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_enddeclare' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_endfor' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_endforeach' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_endif' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_endswitch' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_endwhile' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_enum' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_equal' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_equals' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_eval' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_exclamation_mark' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_exit' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_extends' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_file' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_final' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_finally' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_for' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_foreach' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_func_c' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_function' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_global' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_goto' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_gt' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_halt_compiler' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_if' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_implements' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_in' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_inc' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_include' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_include_once' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_includes' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_inline_html' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_instanceof' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_insteadof' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_int_cast' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_interface' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_is_equal' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_is_greater_or_equal' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_is_identical' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_is_not_equal' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_is_not_identical' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_is_smaller_or_equal' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_isset' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_join' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_lambda_arrow' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_lambda_cp' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_lambda_op' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_line' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_list' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_lnumber' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_logical_and' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_logical_or' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_logical_xor' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_lt' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_method_c' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_minus' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_minus_equal' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_mod_equal' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_mul_equal' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_mult' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_namespace' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_new' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_ns_c' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_ns_separator' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_num_string' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_object_cast' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_object_operator' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_onumber' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_open_bracket' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_open_curly' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_open_square' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_open_tag' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_open_tag_with_echo' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_or_equal' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_paamayim_nekudotayim' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_percent' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_pipe' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_plus' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_plus_equal' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_pow' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_pow_equal' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_print' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_private' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_protected' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_public' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_question_mark' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_require' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_require_once' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_return' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_semicolon' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_shape' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_sl' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_sl_equal' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_spaceship' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_sr' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_sr_equal' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_start_heredoc' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_static' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_stream' => '/phar/phpunit/php-token-stream/src/Token/Stream.php', 'php_token_stream_cachingfactory' => '/phar/phpunit/php-token-stream/src/Token/Stream/CachingFactory.php', 'php_token_string' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_string_cast' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_string_varname' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_switch' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_throw' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_tilde' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_trait' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_trait_c' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_try' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_type' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_typelist_gt' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_typelist_lt' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_unset' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_unset_cast' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_use' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_var' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_variable' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_where' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_while' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_whitespace' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_xhp_attribute' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_xhp_category' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_xhp_category_label' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_xhp_children' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_xhp_label' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_xhp_required' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_xhp_tag_gt' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_xhp_tag_lt' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_xhp_text' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_xor_equal' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_yield' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_token_yield_from' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_tokenwithscope' => '/phar/phpunit/php-token-stream/src/Token.php', 'php_tokenwithscopeandvisibility' => '/phar/phpunit/php-token-stream/src/Token.php', 'phpunit_exception' => '/phar/phpunit/phpunit/src/Exception.php', 'phpunit_extensions_grouptestsuite' => '/phar/phpunit/phpunit/src/Extensions/GroupTestSuite.php', 'phpunit_extensions_phpttestcase' => '/phar/phpunit/phpunit/src/Extensions/PhptTestCase.php', 'phpunit_extensions_phpttestsuite' => '/phar/phpunit/phpunit/src/Extensions/PhptTestSuite.php', 'phpunit_extensions_repeatedtest' => '/phar/phpunit/phpunit/src/Extensions/RepeatedTest.php', 'phpunit_extensions_testdecorator' => '/phar/phpunit/phpunit/src/Extensions/TestDecorator.php', 'phpunit_extensions_ticketlistener' => '/phar/phpunit/phpunit/src/Extensions/TicketListener.php', 'phpunit_framework_assert' => '/phar/phpunit/phpunit/src/Framework/Assert.php', 'phpunit_framework_assertionfailederror' => '/phar/phpunit/phpunit/src/Framework/AssertionFailedError.php', 'phpunit_framework_basetestlistener' => '/phar/phpunit/phpunit/src/Framework/BaseTestListener.php', 'phpunit_framework_codecoverageexception' => '/phar/phpunit/phpunit/src/Framework/CodeCoverageException.php', 'phpunit_framework_constraint' => '/phar/phpunit/phpunit/src/Framework/Constraint.php', 'phpunit_framework_constraint_and' => '/phar/phpunit/phpunit/src/Framework/Constraint/And.php', 'phpunit_framework_constraint_arrayhaskey' => '/phar/phpunit/phpunit/src/Framework/Constraint/ArrayHasKey.php', 'phpunit_framework_constraint_arraysubset' => '/phar/phpunit/phpunit/src/Framework/Constraint/ArraySubset.php', 'phpunit_framework_constraint_attribute' => '/phar/phpunit/phpunit/src/Framework/Constraint/Attribute.php', 'phpunit_framework_constraint_callback' => '/phar/phpunit/phpunit/src/Framework/Constraint/Callback.php', 'phpunit_framework_constraint_classhasattribute' => '/phar/phpunit/phpunit/src/Framework/Constraint/ClassHasAttribute.php', 'phpunit_framework_constraint_classhasstaticattribute' => '/phar/phpunit/phpunit/src/Framework/Constraint/ClassHasStaticAttribute.php', 'phpunit_framework_constraint_composite' => '/phar/phpunit/phpunit/src/Framework/Constraint/Composite.php', 'phpunit_framework_constraint_count' => '/phar/phpunit/phpunit/src/Framework/Constraint/Count.php', 'phpunit_framework_constraint_exception' => '/phar/phpunit/phpunit/src/Framework/Constraint/Exception.php', 'phpunit_framework_constraint_exceptioncode' => '/phar/phpunit/phpunit/src/Framework/Constraint/ExceptionCode.php', 'phpunit_framework_constraint_exceptionmessage' => '/phar/phpunit/phpunit/src/Framework/Constraint/ExceptionMessage.php', 'phpunit_framework_constraint_exceptionmessageregexp' => '/phar/phpunit/phpunit/src/Framework/Constraint/ExceptionMessageRegExp.php', 'phpunit_framework_constraint_fileexists' => '/phar/phpunit/phpunit/src/Framework/Constraint/FileExists.php', 'phpunit_framework_constraint_greaterthan' => '/phar/phpunit/phpunit/src/Framework/Constraint/GreaterThan.php', 'phpunit_framework_constraint_isanything' => '/phar/phpunit/phpunit/src/Framework/Constraint/IsAnything.php', 'phpunit_framework_constraint_isempty' => '/phar/phpunit/phpunit/src/Framework/Constraint/IsEmpty.php', 'phpunit_framework_constraint_isequal' => '/phar/phpunit/phpunit/src/Framework/Constraint/IsEqual.php', 'phpunit_framework_constraint_isfalse' => '/phar/phpunit/phpunit/src/Framework/Constraint/IsFalse.php', 'phpunit_framework_constraint_isidentical' => '/phar/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php', 'phpunit_framework_constraint_isinstanceof' => '/phar/phpunit/phpunit/src/Framework/Constraint/IsInstanceOf.php', 'phpunit_framework_constraint_isjson' => '/phar/phpunit/phpunit/src/Framework/Constraint/IsJson.php', 'phpunit_framework_constraint_isnull' => '/phar/phpunit/phpunit/src/Framework/Constraint/IsNull.php', 'phpunit_framework_constraint_istrue' => '/phar/phpunit/phpunit/src/Framework/Constraint/IsTrue.php', 'phpunit_framework_constraint_istype' => '/phar/phpunit/phpunit/src/Framework/Constraint/IsType.php', 'phpunit_framework_constraint_jsonmatches' => '/phar/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php', 'phpunit_framework_constraint_jsonmatches_errormessageprovider' => '/phar/phpunit/phpunit/src/Framework/Constraint/JsonMatches/ErrorMessageProvider.php', 'phpunit_framework_constraint_lessthan' => '/phar/phpunit/phpunit/src/Framework/Constraint/LessThan.php', 'phpunit_framework_constraint_not' => '/phar/phpunit/phpunit/src/Framework/Constraint/Not.php', 'phpunit_framework_constraint_objecthasattribute' => '/phar/phpunit/phpunit/src/Framework/Constraint/ObjectHasAttribute.php', 'phpunit_framework_constraint_or' => '/phar/phpunit/phpunit/src/Framework/Constraint/Or.php', 'phpunit_framework_constraint_pcrematch' => '/phar/phpunit/phpunit/src/Framework/Constraint/PCREMatch.php', 'phpunit_framework_constraint_samesize' => '/phar/phpunit/phpunit/src/Framework/Constraint/SameSize.php', 'phpunit_framework_constraint_stringcontains' => '/phar/phpunit/phpunit/src/Framework/Constraint/StringContains.php', 'phpunit_framework_constraint_stringendswith' => '/phar/phpunit/phpunit/src/Framework/Constraint/StringEndsWith.php', 'phpunit_framework_constraint_stringmatches' => '/phar/phpunit/phpunit/src/Framework/Constraint/StringMatches.php', 'phpunit_framework_constraint_stringstartswith' => '/phar/phpunit/phpunit/src/Framework/Constraint/StringStartsWith.php', 'phpunit_framework_constraint_traversablecontains' => '/phar/phpunit/phpunit/src/Framework/Constraint/TraversableContains.php', 'phpunit_framework_constraint_traversablecontainsonly' => '/phar/phpunit/phpunit/src/Framework/Constraint/TraversableContainsOnly.php', 'phpunit_framework_constraint_xor' => '/phar/phpunit/phpunit/src/Framework/Constraint/Xor.php', 'phpunit_framework_error' => '/phar/phpunit/phpunit/src/Framework/Error.php', 'phpunit_framework_error_deprecated' => '/phar/phpunit/phpunit/src/Framework/Error/Deprecated.php', 'phpunit_framework_error_notice' => '/phar/phpunit/phpunit/src/Framework/Error/Notice.php', 'phpunit_framework_error_warning' => '/phar/phpunit/phpunit/src/Framework/Error/Warning.php', 'phpunit_framework_exception' => '/phar/phpunit/phpunit/src/Framework/Exception.php', 'phpunit_framework_exceptionwrapper' => '/phar/phpunit/phpunit/src/Framework/ExceptionWrapper.php', 'phpunit_framework_expectationfailedexception' => '/phar/phpunit/phpunit/src/Framework/ExpectationFailedException.php', 'phpunit_framework_incompletetest' => '/phar/phpunit/phpunit/src/Framework/IncompleteTest.php', 'phpunit_framework_incompletetestcase' => '/phar/phpunit/phpunit/src/Framework/IncompleteTestCase.php', 'phpunit_framework_incompletetesterror' => '/phar/phpunit/phpunit/src/Framework/IncompleteTestError.php', 'phpunit_framework_invalidcoverstargeterror' => '/phar/phpunit/phpunit/src/Framework/InvalidCoversTargetError.php', 'phpunit_framework_invalidcoverstargetexception' => '/phar/phpunit/phpunit/src/Framework/InvalidCoversTargetException.php', 'phpunit_framework_mockobject_badmethodcallexception' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/BadMethodCallException.php', 'phpunit_framework_mockobject_builder_identity' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Identity.php', 'phpunit_framework_mockobject_builder_invocationmocker' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/InvocationMocker.php', 'phpunit_framework_mockobject_builder_match' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Match.php', 'phpunit_framework_mockobject_builder_methodnamematch' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/MethodNameMatch.php', 'phpunit_framework_mockobject_builder_namespace' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Namespace.php', 'phpunit_framework_mockobject_builder_parametersmatch' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/ParametersMatch.php', 'phpunit_framework_mockobject_builder_stub' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Stub.php', 'phpunit_framework_mockobject_exception' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/Exception.php', 'phpunit_framework_mockobject_generator' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Generator.php', 'phpunit_framework_mockobject_invocation' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation.php', 'phpunit_framework_mockobject_invocation_object' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation/Object.php', 'phpunit_framework_mockobject_invocation_static' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation/Static.php', 'phpunit_framework_mockobject_invocationmocker' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/InvocationMocker.php', 'phpunit_framework_mockobject_invokable' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invokable.php', 'phpunit_framework_mockobject_matcher' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher.php', 'phpunit_framework_mockobject_matcher_anyinvokedcount' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/AnyInvokedCount.php', 'phpunit_framework_mockobject_matcher_anyparameters' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/AnyParameters.php', 'phpunit_framework_mockobject_matcher_consecutiveparameters' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/ConsecutiveParameters.php', 'phpunit_framework_mockobject_matcher_invocation' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/Invocation.php', 'phpunit_framework_mockobject_matcher_invokedatindex' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtIndex.php', 'phpunit_framework_mockobject_matcher_invokedatleastcount' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtLeastCount.php', 'phpunit_framework_mockobject_matcher_invokedatleastonce' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtLeastOnce.php', 'phpunit_framework_mockobject_matcher_invokedatmostcount' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtMostCount.php', 'phpunit_framework_mockobject_matcher_invokedcount' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedCount.php', 'phpunit_framework_mockobject_matcher_invokedrecorder' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedRecorder.php', 'phpunit_framework_mockobject_matcher_methodname' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/MethodName.php', 'phpunit_framework_mockobject_matcher_parameters' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/Parameters.php', 'phpunit_framework_mockobject_matcher_statelessinvocation' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/StatelessInvocation.php', 'phpunit_framework_mockobject_mockbuilder' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/MockBuilder.php', 'phpunit_framework_mockobject_mockobject' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/MockObject.php', 'phpunit_framework_mockobject_runtimeexception' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/RuntimeException.php', 'phpunit_framework_mockobject_stub' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub.php', 'phpunit_framework_mockobject_stub_consecutivecalls' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ConsecutiveCalls.php', 'phpunit_framework_mockobject_stub_exception' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/Exception.php', 'phpunit_framework_mockobject_stub_matchercollection' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/MatcherCollection.php', 'phpunit_framework_mockobject_stub_return' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/Return.php', 'phpunit_framework_mockobject_stub_returnargument' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnArgument.php', 'phpunit_framework_mockobject_stub_returncallback' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnCallback.php', 'phpunit_framework_mockobject_stub_returnself' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnSelf.php', 'phpunit_framework_mockobject_stub_returnvaluemap' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnValueMap.php', 'phpunit_framework_mockobject_verifiable' => '/phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Verifiable.php', 'phpunit_framework_outputerror' => '/phar/phpunit/phpunit/src/Framework/OutputError.php', 'phpunit_framework_riskytest' => '/phar/phpunit/phpunit/src/Framework/RiskyTest.php', 'phpunit_framework_riskytesterror' => '/phar/phpunit/phpunit/src/Framework/RiskyTestError.php', 'phpunit_framework_selfdescribing' => '/phar/phpunit/phpunit/src/Framework/SelfDescribing.php', 'phpunit_framework_skippedtest' => '/phar/phpunit/phpunit/src/Framework/SkippedTest.php', 'phpunit_framework_skippedtestcase' => '/phar/phpunit/phpunit/src/Framework/SkippedTestCase.php', 'phpunit_framework_skippedtesterror' => '/phar/phpunit/phpunit/src/Framework/SkippedTestError.php', 'phpunit_framework_skippedtestsuiteerror' => '/phar/phpunit/phpunit/src/Framework/SkippedTestSuiteError.php', 'phpunit_framework_syntheticerror' => '/phar/phpunit/phpunit/src/Framework/SyntheticError.php', 'phpunit_framework_test' => '/phar/phpunit/phpunit/src/Framework/Test.php', 'phpunit_framework_testcase' => '/phar/phpunit/phpunit/src/Framework/TestCase.php', 'phpunit_framework_testfailure' => '/phar/phpunit/phpunit/src/Framework/TestFailure.php', 'phpunit_framework_testlistener' => '/phar/phpunit/phpunit/src/Framework/TestListener.php', 'phpunit_framework_testresult' => '/phar/phpunit/phpunit/src/Framework/TestResult.php', 'phpunit_framework_testsuite' => '/phar/phpunit/phpunit/src/Framework/TestSuite.php', 'phpunit_framework_testsuite_dataprovider' => '/phar/phpunit/phpunit/src/Framework/TestSuite/DataProvider.php', 'phpunit_framework_unintentionallycoveredcodeerror' => '/phar/phpunit/phpunit/src/Framework/UnintentionallyCoveredCodeError.php', 'phpunit_framework_warning' => '/phar/phpunit/phpunit/src/Framework/Warning.php', 'phpunit_runner_basetestrunner' => '/phar/phpunit/phpunit/src/Runner/BaseTestRunner.php', 'phpunit_runner_exception' => '/phar/phpunit/phpunit/src/Runner/Exception.php', 'phpunit_runner_filter_factory' => '/phar/phpunit/phpunit/src/Runner/Filter/Factory.php', 'phpunit_runner_filter_group_exclude' => '/phar/phpunit/phpunit/src/Runner/Filter/Group/Exclude.php', 'phpunit_runner_filter_group_include' => '/phar/phpunit/phpunit/src/Runner/Filter/Group/Include.php', 'phpunit_runner_filter_groupfilteriterator' => '/phar/phpunit/phpunit/src/Runner/Filter/Group.php', 'phpunit_runner_filter_test' => '/phar/phpunit/phpunit/src/Runner/Filter/Test.php', 'phpunit_runner_standardtestsuiteloader' => '/phar/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php', 'phpunit_runner_testsuiteloader' => '/phar/phpunit/phpunit/src/Runner/TestSuiteLoader.php', 'phpunit_runner_version' => '/phar/phpunit/phpunit/src/Runner/Version.php', 'phpunit_textui_command' => '/phar/phpunit/phpunit/src/TextUI/Command.php', 'phpunit_textui_resultprinter' => '/phar/phpunit/phpunit/src/TextUI/ResultPrinter.php', 'phpunit_textui_testrunner' => '/phar/phpunit/phpunit/src/TextUI/TestRunner.php', 'phpunit_util_blacklist' => '/phar/phpunit/phpunit/src/Util/Blacklist.php', 'phpunit_util_configuration' => '/phar/phpunit/phpunit/src/Util/Configuration.php', 'phpunit_util_errorhandler' => '/phar/phpunit/phpunit/src/Util/ErrorHandler.php', 'phpunit_util_fileloader' => '/phar/phpunit/phpunit/src/Util/Fileloader.php', 'phpunit_util_filesystem' => '/phar/phpunit/phpunit/src/Util/Filesystem.php', 'phpunit_util_filter' => '/phar/phpunit/phpunit/src/Util/Filter.php', 'phpunit_util_getopt' => '/phar/phpunit/phpunit/src/Util/Getopt.php', 'phpunit_util_globalstate' => '/phar/phpunit/phpunit/src/Util/GlobalState.php', 'phpunit_util_invalidargumenthelper' => '/phar/phpunit/phpunit/src/Util/InvalidArgumentHelper.php', 'phpunit_util_log_json' => '/phar/phpunit/phpunit/src/Util/Log/JSON.php', 'phpunit_util_log_junit' => '/phar/phpunit/phpunit/src/Util/Log/JUnit.php', 'phpunit_util_log_tap' => '/phar/phpunit/phpunit/src/Util/Log/TAP.php', 'phpunit_util_php' => '/phar/phpunit/phpunit/src/Util/PHP.php', 'phpunit_util_php_default' => '/phar/phpunit/phpunit/src/Util/PHP/Default.php', 'phpunit_util_php_windows' => '/phar/phpunit/phpunit/src/Util/PHP/Windows.php', 'phpunit_util_printer' => '/phar/phpunit/phpunit/src/Util/Printer.php', 'phpunit_util_regex' => '/phar/phpunit/phpunit/src/Util/Regex.php', 'phpunit_util_string' => '/phar/phpunit/phpunit/src/Util/String.php', 'phpunit_util_test' => '/phar/phpunit/phpunit/src/Util/Test.php', 'phpunit_util_testdox_nameprettifier' => '/phar/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php', 'phpunit_util_testdox_resultprinter' => '/phar/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php', 'phpunit_util_testdox_resultprinter_html' => '/phar/phpunit/phpunit/src/Util/TestDox/ResultPrinter/HTML.php', 'phpunit_util_testdox_resultprinter_text' => '/phar/phpunit/phpunit/src/Util/TestDox/ResultPrinter/Text.php', 'phpunit_util_testsuiteiterator' => '/phar/phpunit/phpunit/src/Util/TestSuiteIterator.php', 'phpunit_util_type' => '/phar/phpunit/phpunit/src/Util/Type.php', 'phpunit_util_xml' => '/phar/phpunit/phpunit/src/Util/XML.php', 'sebastianbergmann\\comparator\\arraycomparator' => '/phar/sebastian/comparator/src/ArrayComparator.php', 'sebastianbergmann\\comparator\\comparator' => '/phar/sebastian/comparator/src/Comparator.php', 'sebastianbergmann\\comparator\\comparisonfailure' => '/phar/sebastian/comparator/src/ComparisonFailure.php', 'sebastianbergmann\\comparator\\datetimecomparator' => '/phar/sebastian/comparator/src/DateTimeComparator.php', 'sebastianbergmann\\comparator\\domnodecomparator' => '/phar/sebastian/comparator/src/DOMNodeComparator.php', 'sebastianbergmann\\comparator\\doublecomparator' => '/phar/sebastian/comparator/src/DoubleComparator.php', 'sebastianbergmann\\comparator\\exceptioncomparator' => '/phar/sebastian/comparator/src/ExceptionComparator.php', 'sebastianbergmann\\comparator\\factory' => '/phar/sebastian/comparator/src/Factory.php', 'sebastianbergmann\\comparator\\mockobjectcomparator' => '/phar/sebastian/comparator/src/MockObjectComparator.php', 'sebastianbergmann\\comparator\\numericcomparator' => '/phar/sebastian/comparator/src/NumericComparator.php', 'sebastianbergmann\\comparator\\objectcomparator' => '/phar/sebastian/comparator/src/ObjectComparator.php', 'sebastianbergmann\\comparator\\resourcecomparator' => '/phar/sebastian/comparator/src/ResourceComparator.php', 'sebastianbergmann\\comparator\\scalarcomparator' => '/phar/sebastian/comparator/src/ScalarComparator.php', 'sebastianbergmann\\comparator\\splobjectstoragecomparator' => '/phar/sebastian/comparator/src/SplObjectStorageComparator.php', 'sebastianbergmann\\comparator\\typecomparator' => '/phar/sebastian/comparator/src/TypeComparator.php', 'sebastianbergmann\\diff\\chunk' => '/phar/sebastian/diff/src/Chunk.php', 'sebastianbergmann\\diff\\diff' => '/phar/sebastian/diff/src/Diff.php', 'sebastianbergmann\\diff\\differ' => '/phar/sebastian/diff/src/Differ.php', 'sebastianbergmann\\diff\\line' => '/phar/sebastian/diff/src/Line.php', 'sebastianbergmann\\diff\\parser' => '/phar/sebastian/diff/src/Parser.php', 'sebastianbergmann\\environment\\console' => '/phar/sebastian/environment/src/Console.php', 'sebastianbergmann\\environment\\runtime' => '/phar/sebastian/environment/src/Runtime.php', 'sebastianbergmann\\exporter\\exporter' => '/phar/sebastian/exporter/src/Exporter.php', 'sebastianbergmann\\finderfacade\\configuration' => '/phar/sebastian/finder-facade/src/Configuration.php', 'sebastianbergmann\\finderfacade\\finderfacade' => '/phar/sebastian/finder-facade/src/FinderFacade.php', 'sebastianbergmann\\globalstate\\blacklist' => '/phar/sebastian/global-state/src/Blacklist.php', 'sebastianbergmann\\globalstate\\exception' => '/phar/sebastian/global-state/src/Exception.php', 'sebastianbergmann\\globalstate\\restorer' => '/phar/sebastian/global-state/src/Restorer.php', 'sebastianbergmann\\globalstate\\runtimeexception' => '/phar/sebastian/global-state/src/RuntimeException.php', 'sebastianbergmann\\globalstate\\snapshot' => '/phar/sebastian/global-state/src/Snapshot.php', 'sebastianbergmann\\phpcov\\application' => '/phar/src/Application.php', 'sebastianbergmann\\phpcov\\basecommand' => '/phar/src/BaseCommand.php', 'sebastianbergmann\\phpcov\\executecommand' => '/phar/src/ExecuteCommand.php', 'sebastianbergmann\\phpcov\\mergecommand' => '/phar/src/MergeCommand.php', 'sebastianbergmann\\phpcov\\patchcoverage' => '/phar/src/PatchCoverage.php', 'sebastianbergmann\\phpcov\\patchcoveragecommand' => '/phar/src/PatchCoverageCommand.php', 'sebastianbergmann\\recursioncontext\\context' => '/phar/sebastian/recursion-context/src/Context.php', 'sebastianbergmann\\recursioncontext\\exception' => '/phar/sebastian/recursion-context/src/Exception.php', 'sebastianbergmann\\recursioncontext\\invalidargumentexception' => '/phar/sebastian/recursion-context/src/InvalidArgumentException.php', 'sebastianbergmann\\version' => '/phar/sebastian/version/src/Version.php', 'symfony\\component\\console\\application' => '/phar/symfony/console/Application.php', 'symfony\\component\\console\\command\\command' => '/phar/symfony/console/Command/Command.php', 'symfony\\component\\console\\command\\helpcommand' => '/phar/symfony/console/Command/HelpCommand.php', 'symfony\\component\\console\\command\\listcommand' => '/phar/symfony/console/Command/ListCommand.php', 'symfony\\component\\console\\consoleevents' => '/phar/symfony/console/ConsoleEvents.php', 'symfony\\component\\console\\descriptor\\applicationdescription' => '/phar/symfony/console/Descriptor/ApplicationDescription.php', 'symfony\\component\\console\\descriptor\\descriptor' => '/phar/symfony/console/Descriptor/Descriptor.php', 'symfony\\component\\console\\descriptor\\descriptorinterface' => '/phar/symfony/console/Descriptor/DescriptorInterface.php', 'symfony\\component\\console\\descriptor\\jsondescriptor' => '/phar/symfony/console/Descriptor/JsonDescriptor.php', 'symfony\\component\\console\\descriptor\\markdowndescriptor' => '/phar/symfony/console/Descriptor/MarkdownDescriptor.php', 'symfony\\component\\console\\descriptor\\textdescriptor' => '/phar/symfony/console/Descriptor/TextDescriptor.php', 'symfony\\component\\console\\descriptor\\xmldescriptor' => '/phar/symfony/console/Descriptor/XmlDescriptor.php', 'symfony\\component\\console\\event\\consolecommandevent' => '/phar/symfony/console/Event/ConsoleCommandEvent.php', 'symfony\\component\\console\\event\\consoleevent' => '/phar/symfony/console/Event/ConsoleEvent.php', 'symfony\\component\\console\\event\\consoleexceptionevent' => '/phar/symfony/console/Event/ConsoleExceptionEvent.php', 'symfony\\component\\console\\event\\consoleterminateevent' => '/phar/symfony/console/Event/ConsoleTerminateEvent.php', 'symfony\\component\\console\\formatter\\outputformatter' => '/phar/symfony/console/Formatter/OutputFormatter.php', 'symfony\\component\\console\\formatter\\outputformatterinterface' => '/phar/symfony/console/Formatter/OutputFormatterInterface.php', 'symfony\\component\\console\\formatter\\outputformatterstyle' => '/phar/symfony/console/Formatter/OutputFormatterStyle.php', 'symfony\\component\\console\\formatter\\outputformatterstyleinterface' => '/phar/symfony/console/Formatter/OutputFormatterStyleInterface.php', 'symfony\\component\\console\\formatter\\outputformatterstylestack' => '/phar/symfony/console/Formatter/OutputFormatterStyleStack.php', 'symfony\\component\\console\\helper\\debugformatterhelper' => '/phar/symfony/console/Helper/DebugFormatterHelper.php', 'symfony\\component\\console\\helper\\descriptorhelper' => '/phar/symfony/console/Helper/DescriptorHelper.php', 'symfony\\component\\console\\helper\\dialoghelper' => '/phar/symfony/console/Helper/DialogHelper.php', 'symfony\\component\\console\\helper\\formatterhelper' => '/phar/symfony/console/Helper/FormatterHelper.php', 'symfony\\component\\console\\helper\\helper' => '/phar/symfony/console/Helper/Helper.php', 'symfony\\component\\console\\helper\\helperinterface' => '/phar/symfony/console/Helper/HelperInterface.php', 'symfony\\component\\console\\helper\\helperset' => '/phar/symfony/console/Helper/HelperSet.php', 'symfony\\component\\console\\helper\\inputawarehelper' => '/phar/symfony/console/Helper/InputAwareHelper.php', 'symfony\\component\\console\\helper\\processhelper' => '/phar/symfony/console/Helper/ProcessHelper.php', 'symfony\\component\\console\\helper\\progressbar' => '/phar/symfony/console/Helper/ProgressBar.php', 'symfony\\component\\console\\helper\\progresshelper' => '/phar/symfony/console/Helper/ProgressHelper.php', 'symfony\\component\\console\\helper\\questionhelper' => '/phar/symfony/console/Helper/QuestionHelper.php', 'symfony\\component\\console\\helper\\symfonyquestionhelper' => '/phar/symfony/console/Helper/SymfonyQuestionHelper.php', 'symfony\\component\\console\\helper\\table' => '/phar/symfony/console/Helper/Table.php', 'symfony\\component\\console\\helper\\tablecell' => '/phar/symfony/console/Helper/TableCell.php', 'symfony\\component\\console\\helper\\tablehelper' => '/phar/symfony/console/Helper/TableHelper.php', 'symfony\\component\\console\\helper\\tableseparator' => '/phar/symfony/console/Helper/TableSeparator.php', 'symfony\\component\\console\\helper\\tablestyle' => '/phar/symfony/console/Helper/TableStyle.php', 'symfony\\component\\console\\input\\argvinput' => '/phar/symfony/console/Input/ArgvInput.php', 'symfony\\component\\console\\input\\arrayinput' => '/phar/symfony/console/Input/ArrayInput.php', 'symfony\\component\\console\\input\\input' => '/phar/symfony/console/Input/Input.php', 'symfony\\component\\console\\input\\inputargument' => '/phar/symfony/console/Input/InputArgument.php', 'symfony\\component\\console\\input\\inputawareinterface' => '/phar/symfony/console/Input/InputAwareInterface.php', 'symfony\\component\\console\\input\\inputdefinition' => '/phar/symfony/console/Input/InputDefinition.php', 'symfony\\component\\console\\input\\inputinterface' => '/phar/symfony/console/Input/InputInterface.php', 'symfony\\component\\console\\input\\inputoption' => '/phar/symfony/console/Input/InputOption.php', 'symfony\\component\\console\\input\\stringinput' => '/phar/symfony/console/Input/StringInput.php', 'symfony\\component\\console\\logger\\consolelogger' => '/phar/symfony/console/Logger/ConsoleLogger.php', 'symfony\\component\\console\\output\\bufferedoutput' => '/phar/symfony/console/Output/BufferedOutput.php', 'symfony\\component\\console\\output\\consoleoutput' => '/phar/symfony/console/Output/ConsoleOutput.php', 'symfony\\component\\console\\output\\consoleoutputinterface' => '/phar/symfony/console/Output/ConsoleOutputInterface.php', 'symfony\\component\\console\\output\\nulloutput' => '/phar/symfony/console/Output/NullOutput.php', 'symfony\\component\\console\\output\\output' => '/phar/symfony/console/Output/Output.php', 'symfony\\component\\console\\output\\outputinterface' => '/phar/symfony/console/Output/OutputInterface.php', 'symfony\\component\\console\\output\\streamoutput' => '/phar/symfony/console/Output/StreamOutput.php', 'symfony\\component\\console\\question\\choicequestion' => '/phar/symfony/console/Question/ChoiceQuestion.php', 'symfony\\component\\console\\question\\confirmationquestion' => '/phar/symfony/console/Question/ConfirmationQuestion.php', 'symfony\\component\\console\\question\\question' => '/phar/symfony/console/Question/Question.php', 'symfony\\component\\console\\shell' => '/phar/symfony/console/Shell.php', 'symfony\\component\\console\\style\\outputstyle' => '/phar/symfony/console/Style/OutputStyle.php', 'symfony\\component\\console\\style\\styleinterface' => '/phar/symfony/console/Style/StyleInterface.php', 'symfony\\component\\console\\style\\symfonystyle' => '/phar/symfony/console/Style/SymfonyStyle.php', 'symfony\\component\\console\\tester\\applicationtester' => '/phar/symfony/console/Tester/ApplicationTester.php', 'symfony\\component\\console\\tester\\commandtester' => '/phar/symfony/console/Tester/CommandTester.php', 'symfony\\component\\finder\\adapter\\abstractadapter' => '/phar/symfony/finder/Adapter/AbstractAdapter.php', 'symfony\\component\\finder\\adapter\\abstractfindadapter' => '/phar/symfony/finder/Adapter/AbstractFindAdapter.php', 'symfony\\component\\finder\\adapter\\adapterinterface' => '/phar/symfony/finder/Adapter/AdapterInterface.php', 'symfony\\component\\finder\\adapter\\bsdfindadapter' => '/phar/symfony/finder/Adapter/BsdFindAdapter.php', 'symfony\\component\\finder\\adapter\\gnufindadapter' => '/phar/symfony/finder/Adapter/GnuFindAdapter.php', 'symfony\\component\\finder\\adapter\\phpadapter' => '/phar/symfony/finder/Adapter/PhpAdapter.php', 'symfony\\component\\finder\\comparator\\comparator' => '/phar/symfony/finder/Comparator/Comparator.php', 'symfony\\component\\finder\\comparator\\datecomparator' => '/phar/symfony/finder/Comparator/DateComparator.php', 'symfony\\component\\finder\\comparator\\numbercomparator' => '/phar/symfony/finder/Comparator/NumberComparator.php', 'symfony\\component\\finder\\exception\\accessdeniedexception' => '/phar/symfony/finder/Exception/AccessDeniedException.php', 'symfony\\component\\finder\\exception\\adapterfailureexception' => '/phar/symfony/finder/Exception/AdapterFailureException.php', 'symfony\\component\\finder\\exception\\exceptioninterface' => '/phar/symfony/finder/Exception/ExceptionInterface.php', 'symfony\\component\\finder\\exception\\operationnotpermitedexception' => '/phar/symfony/finder/Exception/OperationNotPermitedException.php', 'symfony\\component\\finder\\exception\\shellcommandfailureexception' => '/phar/symfony/finder/Exception/ShellCommandFailureException.php', 'symfony\\component\\finder\\expression\\expression' => '/phar/symfony/finder/Expression/Expression.php', 'symfony\\component\\finder\\expression\\glob' => '/phar/symfony/finder/Expression/Glob.php', 'symfony\\component\\finder\\expression\\regex' => '/phar/symfony/finder/Expression/Regex.php', 'symfony\\component\\finder\\expression\\valueinterface' => '/phar/symfony/finder/Expression/ValueInterface.php', 'symfony\\component\\finder\\finder' => '/phar/symfony/finder/Finder.php', 'symfony\\component\\finder\\glob' => '/phar/symfony/finder/Glob.php', 'symfony\\component\\finder\\iterator\\customfilteriterator' => '/phar/symfony/finder/Iterator/CustomFilterIterator.php', 'symfony\\component\\finder\\iterator\\daterangefilteriterator' => '/phar/symfony/finder/Iterator/DateRangeFilterIterator.php', 'symfony\\component\\finder\\iterator\\depthrangefilteriterator' => '/phar/symfony/finder/Iterator/DepthRangeFilterIterator.php', 'symfony\\component\\finder\\iterator\\excludedirectoryfilteriterator' => '/phar/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php', 'symfony\\component\\finder\\iterator\\filecontentfilteriterator' => '/phar/symfony/finder/Iterator/FilecontentFilterIterator.php', 'symfony\\component\\finder\\iterator\\filenamefilteriterator' => '/phar/symfony/finder/Iterator/FilenameFilterIterator.php', 'symfony\\component\\finder\\iterator\\filepathsiterator' => '/phar/symfony/finder/Iterator/FilePathsIterator.php', 'symfony\\component\\finder\\iterator\\filetypefilteriterator' => '/phar/symfony/finder/Iterator/FileTypeFilterIterator.php', 'symfony\\component\\finder\\iterator\\filteriterator' => '/phar/symfony/finder/Iterator/FilterIterator.php', 'symfony\\component\\finder\\iterator\\multiplepcrefilteriterator' => '/phar/symfony/finder/Iterator/MultiplePcreFilterIterator.php', 'symfony\\component\\finder\\iterator\\pathfilteriterator' => '/phar/symfony/finder/Iterator/PathFilterIterator.php', 'symfony\\component\\finder\\iterator\\recursivedirectoryiterator' => '/phar/symfony/finder/Iterator/RecursiveDirectoryIterator.php', 'symfony\\component\\finder\\iterator\\sizerangefilteriterator' => '/phar/symfony/finder/Iterator/SizeRangeFilterIterator.php', 'symfony\\component\\finder\\iterator\\sortableiterator' => '/phar/symfony/finder/Iterator/SortableIterator.php', 'symfony\\component\\finder\\shell\\command' => '/phar/symfony/finder/Shell/Command.php', 'symfony\\component\\finder\\shell\\shell' => '/phar/symfony/finder/Shell/Shell.php', 'symfony\\component\\finder\\splfileinfo' => '/phar/symfony/finder/SplFileInfo.php', 'symfony\\component\\yaml\\dumper' => '/phar/symfony/yaml/Dumper.php', 'symfony\\component\\yaml\\escaper' => '/phar/symfony/yaml/Escaper.php', 'symfony\\component\\yaml\\exception\\dumpexception' => '/phar/symfony/yaml/Exception/DumpException.php', 'symfony\\component\\yaml\\exception\\exceptioninterface' => '/phar/symfony/yaml/Exception/ExceptionInterface.php', 'symfony\\component\\yaml\\exception\\parseexception' => '/phar/symfony/yaml/Exception/ParseException.php', 'symfony\\component\\yaml\\exception\\runtimeexception' => '/phar/symfony/yaml/Exception/RuntimeException.php', 'symfony\\component\\yaml\\inline' => '/phar/symfony/yaml/Inline.php', 'symfony\\component\\yaml\\parser' => '/phar/symfony/yaml/Parser.php', 'symfony\\component\\yaml\\unescaper' => '/phar/symfony/yaml/Unescaper.php', 'symfony\\component\\yaml\\yaml' => '/phar/symfony/yaml/Yaml.php', 'text_template' => '/phar/phpunit/php-text-template/src/Template.php', 'theseer\\fdom\\css\\dollarequalrule' => '/phar/fdomdocument/css/DollarEqualRule.php', 'theseer\\fdom\\css\\notrule' => '/phar/fdomdocument/css/NotRule.php', 'theseer\\fdom\\css\\nthchildrule' => '/phar/fdomdocument/css/NthChildRule.php', 'theseer\\fdom\\css\\regexrule' => '/phar/fdomdocument/css/RegexRule.php', 'theseer\\fdom\\css\\ruleinterface' => '/phar/fdomdocument/css/RuleInterface.php', 'theseer\\fdom\\css\\translator' => '/phar/fdomdocument/css/Translator.php', 'theseer\\fdom\\fdomdocument' => '/phar/fdomdocument/fDOMDocument.php', 'theseer\\fdom\\fdomdocumentfragment' => '/phar/fdomdocument/fDOMDocumentFragment.php', 'theseer\\fdom\\fdomelement' => '/phar/fdomdocument/fDOMElement.php', 'theseer\\fdom\\fdomexception' => '/phar/fdomdocument/fDOMException.php', 'theseer\\fdom\\fdomnode' => '/phar/fdomdocument/fDOMNode.php', 'theseer\\fdom\\fdomxpath' => '/phar/fdomdocument/fDOMXPath.php', 'theseer\\fdom\\xpathquery' => '/phar/fdomdocument/XPathQuery.php', 'theseer\\fdom\\xpathqueryexception' => '/phar/fdomdocument/XPathQueryException.php' ); } $class = strtolower($class); if (isset($classes[$class])) { require 'phar://phpcov-2.0.2.phar' . $classes[$class]; } } ); Phar::mapPhar('phpcov-2.0.2.phar'); if (isset($_SERVER['argv'][1]) && $_SERVER['argv'][1] == '--manifest') { print file_get_contents(__PHPCOV_PHAR_ROOT__ . '/manifest.txt'); exit; } $application = new SebastianBergmann\PHPCOV\Application; $application->run(); __HALT_COMPILER(); ?> xyphpcov-2.0.2.pharphar/fdomdocument/fDOMNode.phpnBV*!phar/fdomdocument/fDOMElement.phpU7nBVU7yf*phar/fdomdocument/fDOMDocumentFragment.phpnBVx,l)phar/fdomdocument/XPathQueryException.phpnBVֶ"phar/fdomdocument/fDOMDocument.phpXnBVX$O4?phar/fdomdocument/autoload.phpnBV!H phar/fdomdocument/XPathQuery.phpnBV=M!phar/fdomdocument/css/NotRule.php7nBV7ꙶ#phar/fdomdocument/css/RegexRule.phpnBVs'phar/fdomdocument/css/RuleInterface.phpnBV,)phar/fdomdocument/css/DollarEqualRule.phpnBVp&phar/fdomdocument/css/NthChildRule.phpnBV[4$phar/fdomdocument/css/Translator.phpnBVl phar/fdomdocument/fDOMXPath.phpnBV#X#phar/fdomdocument/fDOMException.phpnBV8*phar/sebastian/environment/src/Runtime.php@nBV@Ҏ!˶*phar/sebastian/environment/src/Console.phppnBVp .Q(phar/sebastian/exporter/src/Exporter.php["nBV["T2phar/sebastian/recursion-context/src/Exception.phpJnBVJ0phar/sebastian/recursion-context/src/Context.phpnBV ?Aphar/sebastian/recursion-context/src/InvalidArgumentException.phpnBVmH-phar/sebastian/global-state/src/Exception.phpnBV ,phar/sebastian/global-state/src/Snapshot.php',nBV',5+ ,phar/sebastian/global-state/src/Restorer.phpnBV}FԶ-phar/sebastian/global-state/src/Blacklist.php#nBV#f14phar/sebastian/global-state/src/RuntimeException.php0 nBV0 yv,phar/sebastian/comparator/src/Comparator.phpPnBVP!P=<phar/sebastian/comparator/src/SplObjectStorageComparator.php nBV MQ0phar/sebastian/comparator/src/TypeComparator.phpnBVQ2phar/sebastian/comparator/src/ObjectComparator.phpnBVw4phar/sebastian/comparator/src/ResourceComparator.phpnBV&w1phar/sebastian/comparator/src/ArrayComparator.phpnBVvx4phar/sebastian/comparator/src/DateTimeComparator.php nBV 5phar/sebastian/comparator/src/ExceptionComparator.phpnBVkf3phar/sebastian/comparator/src/DOMNodeComparator.php nBV >%2phar/sebastian/comparator/src/ScalarComparator.php>nBV>B(6phar/sebastian/comparator/src/MockObjectComparator.phpnBVO3phar/sebastian/comparator/src/NumericComparator.php~ nBV~ 7)phar/sebastian/comparator/src/Factory.phpd nBVd ه12phar/sebastian/comparator/src/DoubleComparator.php7nBV7Stٶ3phar/sebastian/comparator/src/ComparisonFailure.php nBV V&phar/sebastian/version/src/Version.php2nBV2BZ1phar/sebastian/finder-facade/src/FinderFacade.php$ nBV$ IGd2phar/sebastian/finder-facade/src/Configuration.php nBV 9 phar/sebastian/diff/src/Line.phpnBVζ"phar/sebastian/diff/src/Differ.phpnBV<6"phar/sebastian/diff/src/Parser.phps nBVs v ^ phar/sebastian/diff/src/Diff.phpnBV!phar/sebastian/diff/src/Chunk.phpnBV9LKphar/manifest.txtnBV#phar/symfony/finder/Shell/Shell.phpnBVq%phar/symfony/finder/Shell/Command.phpnBV_LF/phar/symfony/finder/Adapter/AbstractAdapter.phpnBV=Ķ0phar/symfony/finder/Adapter/AdapterInterface.php nBV j*phar/symfony/finder/Adapter/PhpAdapter.phpy nBVy ]Ķ3phar/symfony/finder/Adapter/AbstractFindAdapter.php)nBV) C.phar/symfony/finder/Adapter/BsdFindAdapter.php nBV kA.phar/symfony/finder/Adapter/GnuFindAdapter.php nBV 3phar/symfony/finder/Comparator/NumberComparator.php nBV ldJ-phar/symfony/finder/Comparator/Comparator.phpnBV1phar/symfony/finder/Comparator/DateComparator.phpnBV 'e1phar/symfony/finder/Iterator/SortableIterator.php nBV pe:phar/symfony/finder/Iterator/FilecontentFilterIterator.phpnBV$l/phar/symfony/finder/Iterator/FilterIterator.phpnBV,G8phar/symfony/finder/Iterator/DateRangeFilterIterator.phpnBV'Q7phar/symfony/finder/Iterator/FileTypeFilterIterator.phpYnBVY]%5phar/symfony/finder/Iterator/CustomFilterIterator.phpnBVЧ7phar/symfony/finder/Iterator/FilenameFilterIterator.phpnBVJc?phar/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php nBV ";phar/symfony/finder/Iterator/RecursiveDirectoryIterator.phpnBV:2phar/symfony/finder/Iterator/FilePathsIterator.php nBV ׷];phar/symfony/finder/Iterator/MultiplePcreFilterIterator.phpnBVW)<8phar/symfony/finder/Iterator/SizeRangeFilterIterator.phpnBV9phar/symfony/finder/Iterator/DepthRangeFilterIterator.phpnBV-,3phar/symfony/finder/Iterator/PathFilterIterator.phpnBVN_?phar/symfony/finder/Exception/OperationNotPermitedException.phpnBVP7phar/symfony/finder/Exception/AccessDeniedException.phpnBVcW޶9phar/symfony/finder/Exception/AdapterFailureException.phpnBV>@4phar/symfony/finder/Exception/ExceptionInterface.phpnBV7>phar/symfony/finder/Exception/ShellCommandFailureException.php9nBV9phar/symfony/finder/Glob.php nBV Sphar/symfony/finder/Finder.phpUnBVU6iv'phar/symfony/finder/Expression/Glob.phpnBV/ݒ-phar/symfony/finder/Expression/Expression.php% nBV% KL(phar/symfony/finder/Expression/Regex.phpnBV;A1phar/symfony/finder/Expression/ValueInterface.phpHnBVH"#phar/symfony/finder/SplFileInfo.php nBV s1*phar/symfony/console/Style/OutputStyle.php0 nBV0 B޿-phar/symfony/console/Style/StyleInterface.php nBV 3Z+phar/symfony/console/Style/SymfonyStyle.php*nBV*նphar/symfony/console/Shell.phpnBVք-phar/symfony/console/Tester/CommandTester.phpKnBVKtI1phar/symfony/console/Tester/ApplicationTester.php[ nBV[ /I.phar/symfony/console/Descriptor/Descriptor.php nBV 8{"$6phar/symfony/console/Descriptor/MarkdownDescriptor.phpnBV;7phar/symfony/console/Descriptor/DescriptorInterface.phpnBVJZ0<1phar/symfony/console/Descriptor/XmlDescriptor.phpS%nBVS%N2phar/symfony/console/Descriptor/TextDescriptor.php'nBV' 2phar/symfony/console/Descriptor/JsonDescriptor.phpnBVt[˶:phar/symfony/console/Descriptor/ApplicationDescription.phpnBV)Ŷ-phar/symfony/console/Output/ConsoleOutput.phpnBV ‹6phar/symfony/console/Output/ConsoleOutputInterface.phpKnBVK0/phar/symfony/console/Output/OutputInterface.php! nBV! 3F,phar/symfony/console/Output/StreamOutput.php nBV Ӎv&phar/symfony/console/Output/Output.php9nBV9*phar/symfony/console/Output/NullOutput.phpnBV8.phar/symfony/console/Output/BufferedOutput.phphnBVht|X4*phar/symfony/console/Helper/TableStyle.php<nBV<ó.phar/symfony/console/Helper/TableSeparator.phpbnBVbZQ.phar/symfony/console/Helper/QuestionHelper.php3nBV3x@ö)phar/symfony/console/Helper/TableCell.phpnBV.b.phar/symfony/console/Helper/ProgressHelper.php0nBV0Wc0phar/symfony/console/Helper/DescriptorHelper.php nBV 9u:+phar/symfony/console/Helper/ProgressBar.phpEnBVEF,phar/symfony/console/Helper/DialogHelper.phpEBnBVEBʶ%phar/symfony/console/Helper/Table.php CnBV CN+phar/symfony/console/Helper/TableHelper.php0nBV0i-phar/symfony/console/Helper/ProcessHelper.phpnBVt&phar/symfony/console/Helper/Helper.phpL nBVL du Z5phar/symfony/console/Helper/SymfonyQuestionHelper.php nBV 64phar/symfony/console/Helper/DebugFormatterHelper.phpVnBVVݔ/phar/symfony/console/Helper/HelperInterface.phpnBVz׶)phar/symfony/console/Helper/HelperSet.phpy nBVy Ej/phar/symfony/console/Helper/FormatterHelper.php nBV "*0phar/symfony/console/Helper/InputAwareHelper.phpnBV˶(phar/symfony/console/Command/Command.phpJnBVJB,phar/symfony/console/Command/ListCommand.phpm nBVm B?,phar/symfony/console/Command/HelpCommand.php nBV {{U6phar/symfony/console/Question/ConfirmationQuestion.php7nBV7&*phar/symfony/console/Question/Question.phpnBV 0phar/symfony/console/Question/ChoiceQuestion.phpnBVF6-phar/symfony/console/Logger/ConsoleLogger.phpnBV&phar/symfony/console/ConsoleEvents.php7nBV7E4phar/symfony/console/Event/ConsoleTerminateEvent.phpnBV{e+phar/symfony/console/Event/ConsoleEvent.phpnBV 4phar/symfony/console/Event/ConsoleExceptionEvent.php=nBV=Ŷ2phar/symfony/console/Event/ConsoleCommandEvent.php=nBV=%mf@phar/symfony/console/Formatter/OutputFormatterStyleInterface.phpnBV5<phar/symfony/console/Formatter/OutputFormatterStyleStack.php nBV 9B۶2phar/symfony/console/Formatter/OutputFormatter.php9nBV97phar/symfony/console/Formatter/OutputFormatterStyle.phpnBVÒN;phar/symfony/console/Formatter/OutputFormatterInterface.phpnBVz,phar/symfony/console/Input/InputArgument.php nBV w*phar/symfony/console/Input/StringInput.php nBV `C(phar/symfony/console/Input/ArgvInput.php)nBV)3\;$phar/symfony/console/Input/Input.phpnBV)phar/symfony/console/Input/ArrayInput.phpKnBVK2phar/symfony/console/Input/InputAwareInterface.php^nBV^9Kh-phar/symfony/console/Input/InputInterface.phpnBV)*phar/symfony/console/Input/InputOption.php:nBV:{P.phar/symfony/console/Input/InputDefinition.php[3nBV[3kF$phar/symfony/console/Application.phpnBVyWphar/symfony/yaml/Inline.phpbMnBVbMoܗphar/symfony/yaml/Unescaper.phpnBV ͏*phar/symfony/yaml/Yaml.php,nBV, .phar/symfony/yaml/Exception/ParseException.phpnBV79-phar/symfony/yaml/Exception/DumpException.phpnBVؙ՚0phar/symfony/yaml/Exception/RuntimeException.phpnBV|-2phar/symfony/yaml/Exception/ExceptionInterface.phpnBV+lphar/symfony/yaml/Parser.phpinBViJ%phar/symfony/yaml/Dumper.php nBV lDphar/symfony/yaml/Escaper.phpnBVKphar/src/ExecuteCommand.php3nBV3S!phar/src/PatchCoverageCommand.php nBV 6phar/src/PatchCoverage.php nBV Ҷphar/src/MergeCommand.php nBV 6aU phar/src/Application.phpnBVphar/src/BaseCommand.phpnBV]ö/phar/phpunit/php-text-template/src/Template.php nBV w4$phar/phpunit/php-timer/src/Timer.phpE nBVE *E/phar/phpunit/php-file-iterator/src/Iterator.phpanBVa-phar/phpunit/php-file-iterator/src/Facade.php nBV 0.phar/phpunit/php-file-iterator/src/Factory.php nBV y6phar/phpunit/php-code-coverage/scripts/auto_append.phptnBVts7phar/phpunit/php-code-coverage/scripts/auto_prepend.phpnBV8phar/phpunit/php-code-coverage/src/CodeCoverage/Util.phpnBVK϶=phar/phpunit/php-code-coverage/src/CodeCoverage/Exception.phpnBVK)Hphar/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML/Renderer.php!nBV!lfRphar/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML/Renderer/Dashboard.php&nBV&b;Mphar/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML/Renderer/File.php,LnBV,L=>Rphar/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML/Renderer/Directory.php| nBV| Aphar/phpunit/php-code-coverage/src/CodeCoverage/Report/Crap4j.php,nBV,G8EDphar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Tests.phpnBV2tCphar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File.php0nBV0ڶCphar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Node.php^nBV^NFphar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Project.php]nBV]h>Ephar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Totals.phpnBVoFHphar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Directory.phpnBVZJphar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File/Method.phpunBVuʶJphar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File/Report.phpCnBVCY{ȶLphar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File/Coverage.php5nBV5(+RHphar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File/Unit.phpB nBVB  kr?phar/phpunit/php-code-coverage/src/CodeCoverage/Report/Node.phpnBV2޶Hphar/phpunit/php-code-coverage/src/CodeCoverage/Report/Node/Iterator.phpnBVMqDphar/phpunit/php-code-coverage/src/CodeCoverage/Report/Node/File.phpJnBVJQcIphar/phpunit/php-code-coverage/src/CodeCoverage/Report/Node/Directory.phpS(nBVS(Aphar/phpunit/php-code-coverage/src/CodeCoverage/Report/Clover.php'nBV'dV?phar/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML.php=nBV=0Bphar/phpunit/php-code-coverage/src/CodeCoverage/Report/Factory.phpknBVkrp2>phar/phpunit/php-code-coverage/src/CodeCoverage/Report/XML.phplnBVls>phar/phpunit/php-code-coverage/src/CodeCoverage/Report/PHP.phpnBV4R}?phar/phpunit/php-code-coverage/src/CodeCoverage/Report/Text.phpx!nBVx!`kXphar/phpunit/php-code-coverage/src/CodeCoverage/Exception/UnintentionallyCoveredCode.phpnBV}:phar/phpunit/php-code-coverage/src/CodeCoverage/Driver.phpnBV0:phar/phpunit/php-code-coverage/src/CodeCoverage/Filter.php-nBV-+XնNphar/phpunit/php-code-coverage/src/CodeCoverage/Util/InvalidArgumentHelper.php=nBV=?phar/phpunit/php-code-coverage/src/CodeCoverage/Driver/HHVM.php^nBV^7bAphar/phpunit/php-code-coverage/src/CodeCoverage/Driver/PHPDBG.php nBV 9Aphar/phpunit/php-code-coverage/src/CodeCoverage/Driver/Xdebug.phpx nBVx 3phar/phpunit/php-code-coverage/src/CodeCoverage.phpsinBVsi]Aphar/phpunit/php-token-stream/src/Token/Stream/CachingFactory.phpnBV_2phar/phpunit/php-token-stream/src/Token/Stream.php@nBV@ܜ+phar/phpunit/php-token-stream/src/Token.php:bnBV:b#0!Fphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher.php nBV 7/Iphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Verifiable.phpnBV'LHphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invokable.php9nBV9~Jphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/MockBuilder.phpnBV\_Rphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/Exception.phpnBV]T_phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/BadMethodCallException.phpnBV).Yphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/RuntimeException.phpnBVYn4Iphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation.phpnBVsIphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/MockObject.php9nBV9COphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/InvocationMocker.phpnBV2׶Qphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/MethodName.php nBV vYphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtLeastOnce.phpnBVcITphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/AnyParameters.phpCnBVCQphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/Parameters.phpnBVPjVphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/AnyInvokedCount.phpnBVDs$Zphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtLeastCount.phpnBV RQphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/Invocation.phpnBVKb\phar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/ConsecutiveParameters.phpnBViOmUphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtIndex.php nBV YSphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedCount.php nBV SGYphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtMostCount.phpnBVVphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedRecorder.php`nBV`!Zphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/StatelessInvocation.phpknBVk϶Hphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Generator.phpnBV Lphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Match.phpWnBVWEAOphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Identity.phpnBV(4Wphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/InvocationMocker.phpvnBVvVphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/ParametersMatch.phpnBVsґζPphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Namespace.phpnBVM쉔Vphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/MethodNameMatch.php)nBV)sKphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Stub.phponBVohrPphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation/Object.phpnBV9Pphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation/Static.phpFnBVFKpCphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub.php\nBV\f+FжMphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/Exception.phpnBV<Jphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/Return.phpnBVLf+Nphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnSelf.phpnBVIRphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnValueMap.php|nBV|uB Tphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ConsecutiveCalls.phpnBV2 Uphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/MatcherCollection.php:nBV:YRphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnCallback.phpnBVg`Rphar/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnArgument.phpnBV9)`&phar/phpunit/phpunit/src/Exception.phpsnBVsy:۶+phar/phpunit/phpunit/src/Framework/Test.phpnBV;0phar/phpunit/phpunit/src/Framework/Exception.php nBV D2phar/phpunit/phpunit/src/Framework/OutputError.phpnBVj̶-phar/phpunit/phpunit/src/Framework/Assert.phpЀnBVЀk<phar/phpunit/phpunit/src/Framework/CodeCoverageException.phpqnBVqE0phar/phpunit/phpunit/src/Framework/TestSuite.phpsnBVs{0phar/phpunit/phpunit/src/Framework/RiskyTest.phpnBV\5phar/phpunit/phpunit/src/Framework/SelfDescribing.phpnBV:|7phar/phpunit/phpunit/src/Framework/Assert/Functions.php!nBV!-0ǂ5phar/phpunit/phpunit/src/Framework/SyntheticError.phpnBVƷ5phar/phpunit/phpunit/src/Framework/RiskyTestError.phpnBV?v3phar/phpunit/phpunit/src/Framework/TestListener.phpR nBVR ݶ7phar/phpunit/phpunit/src/Framework/ExceptionWrapper.phpnBV2phar/phpunit/phpunit/src/Framework/SkippedTest.phpnBV_ $Cphar/phpunit/phpunit/src/Framework/InvalidCoversTargetException.phpnBV%M/phar/phpunit/phpunit/src/Framework/TestCase.phpnBVoҶAphar/phpunit/phpunit/src/Framework/ExpectationFailedException.phpnBV_S;,phar/phpunit/phpunit/src/Framework/Error.php$nBV$X%yW1phar/phpunit/phpunit/src/Framework/Constraint.phpnBVWSǶ;phar/phpunit/phpunit/src/Framework/AssertionFailedError.php{nBV{+9phar/phpunit/phpunit/src/Framework/IncompleteTestCase.php5nBV5.S=phar/phpunit/phpunit/src/Framework/TestSuite/DataProvider.phpnBVphar/phpunit/phpunit/src/Framework/Constraint/IsInstanceOf.phpnBVrȫǶDphar/phpunit/phpunit/src/Framework/Constraint/ObjectHasAttribute.phpnBV{G8phar/phpunit/phpunit/src/Framework/Constraint/IsType.php} nBV} 2t:phar/phpunit/phpunit/src/Framework/Constraint/LessThan.phpnBV=phar/phpunit/phpunit/src/Framework/Constraint/ArraySubset.php>nBV>o8phar/phpunit/phpunit/src/Framework/Constraint/IsTrue.phpknBVk8phar/phpunit/phpunit/src/Framework/Constraint/IsJson.phpnBVC\9phar/phpunit/phpunit/src/Framework/Constraint/IsEqual.phpnBV#!ڶ5phar/phpunit/phpunit/src/Framework/Constraint/Xor.php9 nBV9 suRphar/phpunit/phpunit/src/Framework/Constraint/JsonMatches/ErrorMessageProvider.phpnBVWJ?phar/phpunit/phpunit/src/Framework/Constraint/StringMatches.php nBV %c5phar/phpunit/phpunit/src/Framework/Constraint/Not.phpnBV@ζHphar/phpunit/phpunit/src/Framework/Constraint/ExceptionMessageRegExp.phpUnBVU`]_=phar/phpunit/phpunit/src/Framework/Constraint/GreaterThan.phpnBV ?nBphar/phpunit/phpunit/src/Framework/Constraint/ExceptionMessage.php8nBV8Xz:phar/phpunit/phpunit/src/Framework/Constraint/Callback.php:nBV:+=phar/phpunit/phpunit/src/Framework/Constraint/ArrayHasKey.phpnBVy6@phar/phpunit/phpunit/src/Framework/Constraint/StringContains.php,nBV,GzڶIphar/phpunit/phpunit/src/Framework/Constraint/TraversableContainsOnly.php nBV }=9phar/phpunit/phpunit/src/Framework/Constraint/IsEmpty.phpnBV82;phar/phpunit/phpunit/src/Framework/Constraint/PCREMatch.phpnBV^#8phar/phpunit/phpunit/src/Framework/Constraint/IsNull.phpknBVkK:;phar/phpunit/phpunit/src/Framework/Constraint/Attribute.php nBV LLp9phar/phpunit/phpunit/src/Framework/Constraint/IsFalse.phponBVo?AIphar/phpunit/phpunit/src/Framework/Constraint/ClassHasStaticAttribute.phpwnBVwRQp4phar/phpunit/phpunit/src/Framework/Constraint/Or.php nBV ^|=phar/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php7nBV7ܶ<phar/phpunit/phpunit/src/Framework/Constraint/FileExists.phpnBV I;phar/phpunit/phpunit/src/Framework/Constraint/Composite.php+nBV+IYBphar/phpunit/phpunit/src/Framework/Constraint/StringStartsWith.phpnBV|C}@phar/phpunit/phpunit/src/Framework/Constraint/StringEndsWith.phpnBV^<phar/phpunit/phpunit/src/Framework/Constraint/IsAnything.phpnBV;7phar/phpunit/phpunit/src/Framework/Constraint/Count.php nBV !w5phar/phpunit/phpunit/src/Framework/Constraint/And.php nBV A87phar/phpunit/phpunit/src/Framework/BaseTestListener.phppnBVpMU7phar/phpunit/phpunit/src/Framework/SkippedTestError.php nBV TFphar/phpunit/phpunit/src/Framework/UnintentionallyCoveredCodeError.phpnBVd^S.phar/phpunit/phpunit/src/Framework/Warning.phpnBVS6phar/phpunit/phpunit/src/Extensions/GroupTestSuite.phpnBV65phar/phpunit/phpunit/src/Extensions/PhptTestSuite.phpnBVH4phar/phpunit/phpunit/src/Extensions/RepeatedTest.phpnBV,a6phar/phpunit/phpunit/src/Extensions/TicketListener.phpnBV5phar/phpunit/phpunit/src/Extensions/TestDecorator.php: nBV: 0v-4phar/phpunit/phpunit/src/Extensions/PhptTestCase.php(nBV(*V+phar/phpunit/phpunit/src/TextUI/Command.phpnBVK.phar/phpunit/phpunit/src/TextUI/TestRunner.phphnBVh2"1phar/phpunit/phpunit/src/TextUI/ResultPrinter.phpCnBVC/&phar/phpunit/phpunit/src/Util/Test.phpcnBVcz`&,phar/phpunit/phpunit/src/Util/Filesystem.phpnBV(`O(phar/phpunit/phpunit/src/Util/Getopt.phpnBV}h.phar/phpunit/phpunit/src/Util/ErrorHandler.php nBV Qݶ(phar/phpunit/phpunit/src/Util/String.phpsnBVsU)D7phar/phpunit/phpunit/src/Util/InvalidArgumentHelper.phpnBVW4)phar/phpunit/phpunit/src/Util/Printer.php^nBV^ /phar/phpunit/phpunit/src/Util/Configuration.php؊nBV؊'lX-phar/phpunit/phpunit/src/Util/GlobalState.php/nBV/KU|*phar/phpunit/phpunit/src/Util/Log/JSON.php%nBV%ڶ)phar/phpunit/phpunit/src/Util/Log/TAP.phpnBV|%n+phar/phpunit/phpunit/src/Util/Log/JUnit.php2nBV2, +phar/phpunit/phpunit/src/Util/Blacklist.php nBV X3phar/phpunit/phpunit/src/Util/TestSuiteIterator.phpnBV⍘(phar/phpunit/phpunit/src/Util/Filter.php nBV pĶ<phar/phpunit/phpunit/src/Util/TestDox/ResultPrinter/HTML.phpnBVpfʶ<phar/phpunit/phpunit/src/Util/TestDox/ResultPrinter/Text.phpwnBVw ;7phar/phpunit/phpunit/src/Util/TestDox/ResultPrinter.phpOnBVO l8phar/phpunit/phpunit/src/Util/TestDox/NamePrettifier.phpE nBVE ߤ'phar/phpunit/phpunit/src/Util/Regex.phpnBVPd&phar/phpunit/phpunit/src/Util/Type.phpnBVC!Ҷ-phar/phpunit/phpunit/src/Util/PHP/Default.phpnBV>>L-phar/phpunit/phpunit/src/Util/PHP/Windows.phpx nBVx ",phar/phpunit/phpunit/src/Util/Fileloader.phpnBV)ع%phar/phpunit/phpunit/src/Util/XML.phpsnBVs%phar/phpunit/phpunit/src/Util/PHP.phpnBV]QF-phar/phpunit/phpunit/src/Runner/Exception.phptnBVtd/phar/phpunit/phpunit/src/Runner/Filter/Test.php nBV a8phar/phpunit/phpunit/src/Runner/Filter/Group/Exclude.phpnBVPF8phar/phpunit/phpunit/src/Runner/Filter/Group/Include.phpnBVVe2phar/phpunit/phpunit/src/Runner/Filter/Factory.phpnBVJ0phar/phpunit/phpunit/src/Runner/Filter/Group.phpnBVc2{m3phar/phpunit/phpunit/src/Runner/TestSuiteLoader.phpnBV9ɶ2phar/phpunit/phpunit/src/Runner/BaseTestRunner.phpnBVQ;phar/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php nBV -+phar/phpunit/phpunit/src/Runner/Version.phpnBVM\ö * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of Arne Blankerts nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * * @category PHP * @package TheSeer\fDOM * @author Arne Blankerts * @copyright Arne Blankerts , All rights reserved. * @license http://www.opensource.org/licenses/bsd-license.php BSD License * @link http://github.com/theseer/fdomdocument * */ namespace TheSeer\fDOM { /** * fDomNode * * @category PHP * @package TheSeer\fDOM * @author Arne Blankerts * @access public * @property fDOMDocument $ownerDocument * */ class fDOMNode extends \DOMNode { /** * @return string */ public function __toString() { return $this->C14N(); } /** * Create a new fDOMElement * * @see fDOMDocument::createElement * * @param string $name * @param string $content * @param bool $asTextnode * * @return fDOMElement */ public function createElement($name, $content = NULL, $asTextnode = FALSE) { return $this->ownerDocument->createElement($name, $content, $asTextnode); } /** * Create a new fDOMElement in namespace defined by prefix * * @see fDOMDocument::createElementPrefix * * @param string $prefix * @param string $name * @param string $content * @param bool $asTextNode * * @return fDOMElement */ public function createElementPrefix($prefix, $name, $content = NULL, $asTextNode = FALSE) { return $this->ownerDocument->createElementPrefix($prefix, $name, $content, $asTextNode); } /** * Create a new fDOMElement within given namespace and return it * * @param string $namespace Namespace URI for node to create * @param string $name Name of node to create * @param null $content Content to set (optional) * @param bool $asTextNode Create content as textNode rather then setting nodeValue * * @throws fDOMException * * @return fDOMElement */ public function createElementNS($namespace, $name, $content = NULL, $asTextNode = FALSE) { return $this->ownerDocument->createElementNS($namespace, $name, $content, $asTextNode); } /** * Forward to fDomDocument->query() * * @param string $q XPath to use * @param \DOMNode $ctx \DOMNode to overwrite context * @param boolean $registerNodeNS Register flag pass thru * * @return \DomNodeList */ public function query($q, \DOMNode $ctx = null, $registerNodeNS = true) { return $this->ownerDocument->query($q, $ctx ? $ctx : $this, $registerNodeNS); } /** * Forward to fDomDocument->queryOne() * * @param string $q XPath to use * @param \DOMNode $ctx (optional) \DOMNode to overwrite context * @param boolean $registerNodeNS Register flag pass thru * * @return mixed */ public function queryOne($q, \DOMNode $ctx = null, $registerNodeNS = true) { return $this->ownerDocument->queryOne($q, $ctx ? $ctx : $this, $registerNodeNS); } /** * Forward to fDomDocument->select() * * @param string $selector A CSS Level 3 Selector string * @param \DOMNode $ctx * @param bool $registerNodeNS * * @return \DOMNodeList */ public function select($selector, \DOMNode $ctx = null, $registerNodeNS = true) { return $this->ownerDocument->select($selector, $ctx, $registerNodeNS); } /** * Check if the given node is in the same document * * @param \DomNode $node Node to compare with * * @return boolean true on match, false if they differ * */ public function inSameDocument(\DOMNode $node) { return $this->ownerDocument->inSameDocument($node); } /** * Wrapper to DomDocument::saveXML() with current node as context * * @return string */ public function saveXML() { return $this->ownerDocument->saveXML($this); } /** * Wrapper to DomDocument::saveHTML() with current node as context * * @return string */ public function saveHTML() { return $this->ownerDocument->saveHTML($this); } } // fDOMNode } * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of Arne Blankerts nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * * @category PHP * @package TheSeer\fDOM * @author Arne Blankerts * @copyright Arne Blankerts , All rights reserved. * @license http://www.opensource.org/licenses/bsd-license.php BSD License * @link http://github.com/theseer/fdomdocument * */ namespace TheSeer\fDOM { /** * fDomElement * * @category PHP * @package TheSeer\fDOM * @author Arne Blankerts * @access public * @property fDOMDocument $ownerDocument * */ class fDOMElement extends \DOMElement { /** * @return string */ public function __toString() { return $this->C14N(); } /** * Forward to fDomDocument->query() * * @param string $q XPath to use * @param \DOMNode $ctx \DOMNode to overwrite context * @param boolean $registerNodeNS Register flag pass thru * * @return \DomNodeList */ public function query($q, \DOMNode $ctx = null, $registerNodeNS = true) { return $this->ownerDocument->query($q, $ctx ? $ctx : $this, $registerNodeNS); } /** * Forward to fDomDocument->queryOne() * * @param string $q XPath to use * @param \DOMNode $ctx (optional) \DOMNode to overwrite context * @param boolean $registerNodeNS Register flag pass thru * * @return mixed */ public function queryOne($q, \DOMNode $ctx = null, $registerNodeNS = true) { return $this->ownerDocument->queryOne($q, $ctx ? $ctx : $this, $registerNodeNS); } /** * Forward to fDomDocument->select() * * @param string $selector A CSS Level 3 Selector string * @param \DOMNode $ctx * @param bool $registerNodeNS * * @return \DOMNodeList */ public function select($selector, \DOMNode $ctx = null, $registerNodeNS = true) { return $this->ownerDocument->select($selector, $ctx, $registerNodeNS); } /** * Parse and append XML String to node * * @param String $str string to process * * @return fDomDocumentFragment Reference to the created Fragment */ public function appendXML($str) { $frag = $this->ownerDocument->createDocumentFragment(); $frag->appendXML($str); $this->appendChild($frag); return $frag; } /** * Create a new element and append it * * @param string $name Name of not element to create * @param string $content Optional content to be set * * @return fDOMElement Reference to created fDOMElement */ public function appendElement($name, $content = null) { $node = $this->ownerDocument->createElement($name, $content); $this->appendChild($node); return $node; } /** * Create a new element in given namespace and append it * * @param string $ns Namespace of node to create * @param string $name Name of not element to create * @param string $content Optional content to be set * * @return fDOMElement Reference to created fDOMElement */ public function appendElementNS($ns, $name, $content = null) { $node = $this->ownerDocument->createElementNS($ns, $name, $content); $this->appendChild($node); return $node; } /** * Create a new element in given namespace and append it * * @param string $prefix Namespace prefix for node to create * @param string $name Name of not element to create * @param string $content Optional content to be set * * @return fDOMElement Reference to created fDOMElement */ public function appendElementPrefix($prefix, $name, $content = null) { $node = $this->ownerDocument->createElementPrefix($prefix, $name, $content); $this->appendChild($node); return $node; } /** * Create a new text node and append it * * @param string $content Text content to be added * * @return \DOMText */ public function appendTextNode($content) { $text = $this->ownerDocument->createTextNode($content); $this->appendChild($text); return $text; } /** * Create a new fDOMElement * * @see fDOMDocument::createElement * * @param string $name * @param string $content * @param bool $asTextnode * * @return fDOMElement */ public function createElement($name, $content = NULL, $asTextnode = FALSE) { return $this->ownerDocument->createElement($name, $content, $asTextnode); } /** * Create a new fDOMElement in namespace defined by prefix * * @see fDOMDocument::createElementPrefix * * @param string $prefix * @param string $name * @param string $content * @param bool $asTextNode * * @return fDOMElement */ public function createElementPrefix($prefix, $name, $content = NULL, $asTextNode = FALSE) { return $this->ownerDocument->createElementPrefix($prefix, $name, $content, $asTextNode); } /** * Create a new fDOMElement within given namespace and return it * * @param string $namespace Namespace URI for node to create * @param string $name Name of node to create * @param null $content Content to set (optional) * @param bool $asTextNode Create content as textNode rather then setting nodeValue * * @throws fDOMException * * @return fDOMElement */ public function createElementNS($namespace, $name, $content = NULL, $asTextNode = FALSE) { return $this->ownerDocument->createElementNS($namespace, $name, $content, $asTextNode); } /** * Wrapper to DomElement->getAttribute with default value option * * Note: A set but emptry attribute does NOT trigger use of the default * * @param string $attr Attribute to access * @param string $default Default value to use if the attribute is not set * * @return string */ public function getAttribute($attr, $default='') { return $this->hasAttribute($attr) ? parent::getAttribute($attr) : $default; } /** * Wrapper to DomElement->getAttributeNS with default value option * * Note: A set but empty attribute does NOT trigger use of the default * * @param string $ns Namespace of attribute * @param string $attr Attribute to access * @param string $default Default value to use if the attribute is not set * * @return string */ public function getAttributeNS($ns, $attr, $default='') { return $this->hasAttributeNS($ns, $attr) ? parent::getAttributeNS($ns, $attr) : $default; } /** * Wrapper to DOMElement::setAttribute with additional entities support * * @param string $attr Attribute name to set * @param string $value Value to set attribute to * @param bool $keepEntities Flag to signale if entities should be kept * * @throws fDOMException * * @return \DOMAttr * * @see DOMElement::setAttribute() */ public function setAttribute($attr, $value, $keepEntities=false) { if ($keepEntities === true) { $attrNode = $this->ownerDocument->createAttribute($attr); if (!$attrNode) { throw new fDOMException("Setting attribute '$attr' failed.", fDOMException::SetFailedError); } $attrNode->value = $value; $this->appendChild($attrNode); return $attrNode; } return parent::setAttribute($attr, $value); } /** * Wrapper to namespace aware DOMElement::setAttributeNS with additional entities support * * @param string $ns namespace attribute should be in * @param string $attr Attribute name to set * @param string $value Value to set attribute to * @param bool $keepEntities Flag to signale if entities should be kept * * @throws fDOMException * * @return \DOMAttr|null * @see DOMElement::setAttribute() */ public function setAttributeNS($ns, $attr, $value, $keepEntities=false) { if ($keepEntities === true) { $attrNode = $this->ownerDocument->createAttributeNS($ns, $attr); if (!$attrNode) { throw new fDOMException("Setting attribute '$attr' failed.", fDOMException::SetFailedError); } $attrNode->value = $value; $this->appendChild($attrNode); return $attrNode; } return parent::setAttributeNS($ns, $attr, $value); } /** * Helper to add multiple attributes to an element * * @param array $attr Attributes to add as key-value pair * @param bool $keepEntities Flag wether to keep entities * * @return array List with references to created DOMAttr */ public function setAttributes(array $attr, $keepEntities=false) { $attList = array(); foreach($attr as $name => $value) { $attList[] = $this->setAttribute($name, $value, $keepEntities); } return $attList; } /** * Helper to add multiple attributes with the given namespace and prefix * * @param string $ns Namespace of attribute * @param string $prefix Namespace prefix for attribute to create * @param array $attr Attributes to add * @param bool $keepEntities Flag wether to keep entities * * @return void */ public function setAttributesNS($ns, $prefix, array $attr, $keepEntities=false) { foreach($attr as $name => $value) { $this->setAttributeNS($ns, $prefix.':'.$name, $value, $keepEntities); } } /** * Helper method to get children by name * * @param string $tagName tagname to search for * * @return \DOMNodeList */ public function getChildrenByTagName($tagName) { return $this->query("*[local-name()='$tagName']"); } /** * Helper method to get children by name and namespace * * @param string $ns namespace nodes have to be in * @param string $tagName tagname to search for * * @return \DOMNodeList */ public function getChildrenByTagNameNS($ns, $tagName) { return $this->query("*[local-name()='$tagName' and namespace-uri()='$ns']"); } /** * Check if the given node is in the same document * * @param \DomNode $node Node to compare with * * @return boolean true on match, false if they differ * */ public function inSameDocument(\DomNode $node) { return $this->ownerDocument->inSameDocument($node); } /** * Wrapper to DomDocument::saveXML() with current node as context * * @return string */ public function saveXML() { return $this->ownerDocument->saveXML($this); } /** * Wrapper to DomDocument::saveHTML() with current node as context * * @return string */ public function saveHTML() { return $this->ownerDocument->saveHTML($this); } } // fDOMElement } * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of Arne Blankerts nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * * @category PHP * @package TheSeer\fDOM * @author Arne Blankerts * @copyright Arne Blankerts , All rights reserved. * @license http://www.opensource.org/licenses/bsd-license.php BSD License * @link http://github.com/theseer/fdomdocument * */ namespace TheSeer\fDOM { /** * fDOMDocumentFragment * * @category PHP * @package TheSeer\fDOM * @author Arne Blankerts * @access public * @property fDOMDocument $ownerDocument * */ class fDOMDocumentFragment extends \DOMDocumentFragment { /** * @return string */ public function __toString() { return $this->ownerDocument->saveXML($this); } /** * Wrapper to standard method with exception support * * @param string $str Data string to parse and append * * @throws fDOMException * * @return bool true on success */ public function appendXML($str) { if (!parent::appendXML($str)) { throw new fDOMException('Appending xml string failed', fDOMException::ParseError); } return true; } /** * Create a new element and append it * * @param string $name Name of not element to create * @param string $content Optional content to be set * * @return fDOMElement Reference to created fDOMElement */ public function appendElement($name, $content = null) { $node = $this->ownerDocument->createElement($name, $content); $this->appendChild($node); return $node; } /** * Create a new element in given namespace and append it * * @param string $ns Namespace of node to create * @param string $name Name of not element to create * @param string $content Optional content to be set * * @return fDOMElement Reference to created fDOMElement */ public function appendElementNS($ns, $name, $content = null) { $node = $this->ownerDocument->createElementNS($ns, $name, $content); $this->appendChild($node); return $node; } /** * Create a new element in given namespace and append it * * @param string $prefix Namespace prefix for node to create * @param string $name Name of not element to create * @param string $content Optional content to be set * * @return fDOMElement Reference to created fDOMElement */ public function appendElementPrefix($prefix, $name, $content = null) { $node = $this->ownerDocument->createElementPrefix($prefix, $name, $content); $this->appendChild($node); return $node; } /** * Create a new text node and append it * * @param string $content Text content to be added * * @return \DOMText */ public function appendTextNode($content) { $text = $this->ownerDocument->createTextNode($content); $this->appendChild($text); return $text; } /** * Check if the given node is in the same document * * @param \DOMNode $node Node to compare with * * @return boolean true on match, false if they differ * */ public function inSameDocument(\DOMNode $node) { return $this->ownerDocument->inSameDocument($node); } /** * Forward to fDomDocument->query() * * @param string $q XPath to use * @param \DOMNode $ctx \DOMNode to overwrite context * @param boolean $registerNodeNS Register flag pass thru * * @return \DomNodeList */ public function query($q, \DOMNode $ctx = null, $registerNodeNS = true) { return $this->ownerDocument->query($q, $ctx ? $ctx : $this, $registerNodeNS); } /** * Forward to fDomDocument->queryOne() * * @param string $q XPath to use * @param \DOMNode $ctx (optional) \DOMNode to overwrite context * @param boolean $registerNodeNS Register flag pass thru * * @return mixed */ public function queryOne($q, \DOMNode $ctx = null, $registerNodeNS = true) { return $this->ownerDocument->queryOne($q, $ctx ? $ctx : $this, $registerNodeNS); } /** * Forward to fDomDocument->select() * * @param string $selector A CSS Level 3 Selector string * @param \DOMNode $ctx * @param bool $registerNodeNS * * @return \DOMNodeList */ public function select($selector, \DOMNode $ctx = null, $registerNodeNS = true) { return $this->ownerDocument->select($selector, $ctx ? $ctx : $this, $registerNodeNS); } } // fDOMDocumentFragment } * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of Arne Blankerts nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * * @category PHP * @package TheSeer\fDOM * @author Arne Blankerts * @copyright Arne Blankerts , All rights reserved. * @license http://www.opensource.org/licenses/bsd-license.php BSD License * @link http://github.com/theseer/fdomdocument * */ namespace TheSeer\fDOM { class XPathQueryException extends \Exception { const KeyNotFound = 1; } } * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of Arne Blankerts nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * * @category PHP * @package TheSeer\fDOM * @author Arne Blankerts * @copyright Arne Blankerts , All rights reserved. * @license http://www.opensource.org/licenses/bsd-license.php BSD License * @link http://github.com/theseer/fdomdocument * */ namespace TheSeer\fDOM { use TheSeer\fDOM\CSS\Translator; /** * fDOMDocument extension to PHP's DOMDocument. * This class adds various convenience methods to simplify APIs * It is set to final since further extending it would even more * break the Object structure after use of registerNodeClass. * * @category PHP * @package TheSeer\fDOM * @author Arne Blankerts * @access public * @property fDOMDocument $ownerDocument * */ class fDOMDocument extends \DOMDocument { /** * XPath Object instance * * @var fDOMXPath */ private $xp = NULL; /** * List of registered prefixes and their namespace uri * @var Array */ private $prefixes = array(); /** * Extended DOMDocument constructor * * @param string $version XML Version, should be 1.0 * @param string $encoding Encoding, defaults to utf-8 * @param array $streamOptions optional stream options array * * @return fDOMDocument */ public function __construct($version = '1.0', $encoding = 'utf-8', $streamOptions = NULL) { if (!is_null($streamOptions)) { $this->setStreamContext($streamOptions); } libxml_use_internal_errors(TRUE); $rc = parent::__construct($version, $encoding); $this->registerNodeClasses(); return $rc; } /** * Reset XPath object so the clone gets a new instance when needed */ public function __clone() { $this->registerNodeClasses(); $this->xp = new fDOMXPath($this); foreach($this->prefixes as $prefix => $uri) { $this->xp->registerNamespace($prefix, $uri); } } /** * @return string */ public function __toString() { return $this->C14N(); } /** * Set Stream context options * * @param Array $options Stream context options * * @return boolean true on success, false on failure */ public function setStreamContext(Array $options) { if (!count($options)) { return FALSE; } $context = stream_context_create($options); libxml_set_streams_context($context); return TRUE; } /** * Wrapper to DOMDocument load with exception handling * Returns true on success to satisfy the compatibilty of the original DOM Api * * @param string $fname File to load * @param int|null $options LibXML Flags to pass * * @throws fDOMException * * @return bool|mixed */ public function load($fname, $options = LIBXML_NONET) { $this->xp = NULL; $tmp = parent :: load($fname, $options); if (!$tmp || libxml_get_last_error()) { throw new fDOMException("loading file '$fname' failed.", fDOMException::LoadError); } $this->registerNodeClasses(); return TRUE; } /** * Wrapper to DOMDocument loadXML with exception handling * Returns true on success to satisfy the compatibilty of the original DOM Api * * @param string $source XML source code * @param integer $options LibXML option flags * * @throws fDOMException * * @return boolean */ public function loadXML($source, $options = LIBXML_NONET) { $this->xp = NULL; $tmp = parent :: loadXML($source, $options); if (!$tmp || libxml_get_last_error()) { throw new fDOMException('parsing string failed', fDOMException::ParseError); } $this->registerNodeClasses(); return TRUE; } /** * Wrapper to DOMDocument loadHTMLFile with exception handling. * Returns true on success to satisfy the compatibilty of the original DOM Api * * @param string $fname html file to load * @param integer $options Options bitmask (@see DOMDocument::loadHTMLFile) * * @throws fDOMException * * @return boolean */ public function loadHTMLFile($fname, $options = NULL) { $this->xp = NULL; if (version_compare(PHP_VERSION, '5.4.0', '<')) { if ($options != NULL) { throw new fDOMException('Passing options requires PHP 5.4.0+', fDOMException::LoadError); } $tmp = parent :: loadHTMLFile($fname); } else { $tmp = parent :: loadHTMLFile($fname, $options); } if (!$tmp || libxml_get_last_error()) { throw new fDOMException("loading html file '$fname' failed", fDOMException::LoadError); } $this->registerNodeClasses(); return TRUE; } /** * Wrapper to DOMDocument loadHTML with exception handling * Returns true on success to satisfy the compatibilty of the original DOM Api * * @param string $source html source code * @param integer $options Options bitmask (@see DOMDocument::loadHTML) * * @throws fDOMException * * @return boolean */ public function loadHTML($source, $options = NULL) { $this->xp = NULL; if (version_compare(PHP_VERSION, '5.4.0', '<')) { if ($options != NULL) { throw new fDOMException('Passing options requires PHP 5.4.0+', fDOMException::LoadError); } $tmp = parent :: loadHTML($source); } else { $tmp = parent :: loadHTML($source, $options); } if (!$tmp || libxml_get_last_error()) { throw new fDOMException('parsing html string failed', fDOMException::ParseError); } $this->registerNodeClasses(); return TRUE; } /** * Wrapper to DOMDocument::save with exception handling * * @param string $filename filename to save to * @param integer $options Options bitmask (@see DOMDocument::save) * * @throws fDOMException * * @return integer bytes saved */ public function save($filename, $options = NULL) { $tmp = parent::save($filename, $options); if (!$tmp) { throw new fDOMException("Saving XML to file '$filename' failed", fDOMException::SaveError); } return $tmp; } /** * Wrapper to DOMDocument::saveHTML with exception handling * * @param \DOMNode|null $node Context DOMNode (optional) * * @throws fDOMException * * @return string html content */ public function saveHTML(\DOMNode $node = NULL) { if (version_compare(PHP_VERSION, '5.3.6', '<') && $node != NULL) { throw new fDOMException('Passing a context node requires PHP 5.3.6+', fDOMException::SaveError); } $tmp = parent::saveHTML($node); if (!$tmp) { throw new fDOMException('Serializing to HTML failed', fDOMException::SaveError); } return $tmp; } /** * Wrapper to DOMDocument::saveHTMLfile with exception handling * * @param string $filename filename to save to * @param integer $options Options bitmask (@see DOMDocument::saveHTMLFile) * * @throws fDOMException * * @return integer bytes saved */ public function saveHTMLFile($filename, $options = NULL) { $tmp = parent::saveHTMLFile($filename, $options); if (!$tmp) { throw new fDOMException("Saving HTML to file '$filename' failed", fDOMException::SaveError); } return $tmp; } /** * Wrapper to DOMDocument::saveXML with exception handling * * @param \DOMNode $node node to start serializing at * @param integer $options options flags as bitmask * * @throws fDOMException * * @return string serialized XML */ public function saveXML(\DOMNode $node = NULL, $options = NULL) { try { $tmp = parent::saveXML($node, $options); if (!$tmp) { throw new fDOMException('Serializing to XML failed', fDOMException::SaveError); } return $tmp; } catch (\Exception $e) { if (!$e instanceof fDOMException) { throw new fDOMException($e->getMessage(), fDOMException::SaveError, $e); } throw $e; } } /** * get Instance of DOMXPath Object for current DOM * * @throws fDOMException * * @return fDOMXPath */ public function getDOMXPath() { if (is_null($this->xp)) { $this->xp = new fDOMXPath($this); } if (!$this->xp) { throw new fDOMException('creating DOMXPath object failed.', fDOMException::NoDOMXPath); } return $this->xp; } /** * Convert a given DOMNodeList into a DOMFragment * * @param \DOMNodeList $list The Nodelist to process * @param boolean $move Signale if nodes are to be moved into fragment or not * * @return fDOMDocumentFragment */ public function nodeList2Fragment(\DOMNodeList $list, $move=FALSE) { $frag = $this->createDocumentFragment(); /** @var fDOMNode $node */ foreach($list as $node) { $frag->appendChild($move ? $node : $node->cloneNode(TRUE)); } return $this->ensureIntance($frag); } /** * Perform an xpath query * * @param String $q query string containing xpath * @param \DOMNode|null $ctx (optional) Context DOMNode * @param boolean $registerNodeNS Register flag pass through * * @return \DOMNodeList */ public function query($q, \DOMNode $ctx = NULL, $registerNodeNS = TRUE) { if (is_null($this->xp)) { $this->getDOMXPath(); } return $this->xp->evaluate($q, $ctx, $registerNodeNS); } /** * Perform an xpath query and return only the 1st match * * @param String $q query string containing xpath * @param \DOMNode $ctx (optional) Context DOMNode * @param boolean $registerNodeNS Register flag pass thru * * @return fDOMNode */ public function queryOne($q, \DOMNode $ctx = NULL, $registerNodeNS = TRUE) { if (is_null($this->xp)) { $this->getDOMXPath(); } return $this->xp->queryOne($q, $ctx, $registerNodeNS); } /** * Forwarder to fDOMXPath's prepare method allowing for easy and secure * placeholder replacement comparable to sql's prepared statements * . * @param string $xpath String containing xpath with :placeholder markup * @param array $valueMap Array containing keys (:placeholder) and value pairs to be quoted * * @return string */ public function prepareQuery($xpath, array $valueMap) { if (is_null($this->xp)) { $this->getDOMXPath(); } return $this->xp->prepare($xpath, $valueMap); } /** * Use a CSS Level 3 Selector string to query select nodes * * @param string $selector A CSS Level 3 Selector string * @param \DOMNode $ctx * @param bool $registerNodeNS * * @return \DOMNodeList */ public function select($selector, \DOMNode $ctx = NULL, $registerNodeNS = TRUE) { $translator = new Translator(); $xpath = $translator->translate($selector); if ($ctx != NULL) { $xpath = '.' . $xpath; } return $this->query($xpath, $ctx, $registerNodeNS); } /** * Forward to DOMXPath->registerNamespace() * * @param string $prefix The prefix to use * @param string $uri The uri to assign to this prefix * * @throws fDOMException * * @return void */ public function registerNamespace($prefix, $uri) { if (is_null($this->xp)) { $this->getDOMXPath(); } if (!$this->xp->registerNamespace($prefix, $uri)) { throw new fDOMException("Registering namespace '$uri' with prefix '$prefix' failed.", fDOMException::RegistrationFailed); } $this->prefixes[$prefix] = $uri; } /** * Forward to DOMXPath->registerPHPFunctions() * * @param mixed $restrict Array of function names or string with functionname to restrict callabilty to * * @throws fDOMException * * @return void */ public function registerPHPFunctions($restrict = NULL) { if (is_null($this->xp)) { $this->getDOMXPath(); } $this->xp->registerPHPFunctions($restrict); if (libxml_get_last_error()) { throw new fDOMException("Registering php functions failed.", fDOMException::RegistrationFailed); } } /** * Create a new element in namespace defined by given prefix * * @param string $prefix Namespace prefix for node to create * @param string $name Name of not element to create * @param string $content Optional content to be set * @param bool $asTextNode Create content as textNode rather then setting nodeValue * * @throws fDOMException * * @return fDOMElement Reference to created fDOMElement */ public function createElementPrefix($prefix, $name, $content = NULL, $asTextNode = FALSE) { if (!isset($this->prefixes[$prefix])) { throw new fDOMException("'$prefix' not bound", fDOMException::UnboundPrefix); } return $this->createElementNS($this->prefixes[$prefix], $prefix.':'.$name, $content, $asTextNode); } /** * Create a new fDOMElement and return it, optionally set content * * @param string $name Name of node to create * @param null $content Content to set (optional) * @param bool $asTextnode Create content as textNode rather then setting nodeValue * * @throws fDOMException * * @return fDOMElement Reference to created fDOMElement */ public function createElement($name, $content = NULL, $asTextnode = FALSE) { try { $node = parent::createElement($name); if (!$node) { throw new fDOMException("Creating element with name '$name' failed", fDOMException::NameInvalid); } if ($content !== NULL) { if ($asTextnode) { $node->appendChild($this->createTextnode($content)); } else { $node->nodeValue = $content; } if (libxml_get_errors()) { throw new fDOMException("Setting content value failed", fDOMException::SetFailedError); } } return $this->ensureIntance($node); } catch (\DOMException $e) { throw new fDOMException("Creating elemnt with name '$name' failed", 0, $e); } } /** * Create a new fDOMElement within given namespace and return it * * @param string $namespace Namespace URI for node to create * @param string $name Name of node to create * @param string $content Content to set (optional) * @param bool $asTextNode Create content as textNode rather then setting nodeValue * * @throws fDOMException * * @return fDOMElement */ public function createElementNS($namespace, $name, $content = NULL, $asTextNode = FALSE) { $node = parent::createElementNS($namespace, $name); if (!$node) { throw new fDOMException("Creating element with name '$name' failed", fDOMException::NameInvalid); } if ($content !== NULL) { if ($asTextNode) { $node->appendChild($this->createTextnode($content)); } else { $node->nodeValue = $content; } if (libxml_get_errors()) { throw new fDOMException("Setting content value failed", fDOMException::SetFailedError); } } return $this->ensureIntance($node); } /** * @return fDOMDocumentFragment */ public function createDocumentFragment() { return $this->ensureIntance(parent::createDocumentFragment()); } /** * Check if the given node is in the same document * * @param \DOMNode $node Node to compare with * * @return boolean true on match, false if they differ * */ public function inSameDocument(\DOMNode $node) { if ($node instanceof \DOMDocument) { return $this->isSameNode($node); } return $this->isSameNode($node->ownerDocument); } /** * Create a new element and append it as documentElement * * @param string $name Name of not element to create * @param string $content Optional content to be set * @param bool $asTextNode * * @return fDOMElement Reference to created fDOMElement */ public function appendElement($name, $content = NULL, $asTextNode = FALSE) { return $this->appendChild( $this->createElement($name, $content, $asTextNode) ); } /** * Create a new element in given namespace and append it as documentElement * * @param string $ns Namespace of node to create * @param string $name Name of not element to create * @param string $content Optional content to be set * @param bool $asTextNode * * @return fDOMElement Reference to created fDOMElement */ public function appendElementNS($ns, $name, $content = NULL, $asTextNode = FALSE) { return $this->appendChild( $this->createElementNS($ns, $name, $content, $asTextNode) ); } /** * This is a workaround for hhvm's broken registerNodeClass handling * (https://github.com/facebook/hhvm/issues/1848) * * @param \DOMNode $node * * @return \DOMNode */ private function ensureIntance(\DOMNode $node) { if ($node instanceof fDOMNode || $node instanceof fDOMElement || $node instanceof fDOMDocumentFragment) { return $node; } return $this->importNode($node, TRUE); } /** * Register replacements * * Called from constructor and, as a workaround for (https://github.com/facebook/hhvm/issues/5412), * after load(), loadXML(), loadHTML() and loadHTMLFile() */ private function registerNodeClasses() { $this->registerNodeClass('DOMDocument', get_called_class()); $this->registerNodeClass('DOMNode', 'TheSeer\fDOM\fDOMNode'); $this->registerNodeClass('DOMElement', 'TheSeer\fDOM\fDOMElement'); $this->registerNodeClass('DOMDocumentFragment', 'TheSeer\fDOM\fDOMDocumentFragment'); } } // fDOMDocument } '/css/DollarEqualRule.php', 'theseer\\fdom\\css\\notrule' => '/css/NotRule.php', 'theseer\\fdom\\css\\nthchildrule' => '/css/NthChildRule.php', 'theseer\\fdom\\css\\regexrule' => '/css/RegexRule.php', 'theseer\\fdom\\css\\ruleinterface' => '/css/RuleInterface.php', 'theseer\\fdom\\css\\translator' => '/css/Translator.php', 'theseer\\fdom\\fdomdocument' => '/fDOMDocument.php', 'theseer\\fdom\\fdomdocumentfragment' => '/fDOMDocumentFragment.php', 'theseer\\fdom\\fdomelement' => '/fDOMElement.php', 'theseer\\fdom\\fdomexception' => '/fDOMException.php', 'theseer\\fdom\\fdomnode' => '/fDOMNode.php', 'theseer\\fdom\\fdomxpath' => '/fDOMXPath.php', 'theseer\\fdom\\xpathquery' => '/XPathQuery.php', 'theseer\\fdom\\xpathqueryexception' => '/XPathQueryException.php' ); } $cn = strtolower($class); if (isset($classes[$cn])) { require __DIR__ . $classes[$cn]; } } ); // @codeCoverageIgnoreEnd * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of Arne Blankerts nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * * @category PHP * @package TheSeer\fDOM * @author Arne Blankerts * @copyright Arne Blankerts , All rights reserved. * @license http://www.opensource.org/licenses/bsd-license.php BSD License * @link http://github.com/theseer/fdomdocument * */ namespace TheSeer\fDOM { /** * Class XPathQuery * * @package TheSeer\fDOM */ class XPathQuery { /** * @var string */ private $query; /** * Key-value Map for bound values * * @var array */ private $values = array(); /** * @param string $query */ public function __construct($query) { $this->setQuery($query); } /** * Set Query. * * @param string $query */ private function setQuery($query) { $this->query = $query; $res = preg_match_all('/(:(\w*))/', $query, $matches); if ($res > 0) { $this->values = array_fill_keys($matches[2], ''); } } /** * Returns keys. * * @return array */ public function getKeys() { return array_keys($this->values); } /** * Bind value to key. * * @param string $key * @param string $value * * @throws XPathQueryException */ public function bind($key, $value) { if (!array_key_exists($key, $this->values)) { throw new XPathQueryException("'$key' not found in query'", XPathQueryException::KeyNotFound ); } $this->values[$key] = $value; } /** * Generate query. * * @param \DOMNode $ctx * @param array $values * * @return string */ public function generate(\DOMNode $ctx, array $values = NULL) { return $this->buildQuery($this->getXPathObjectFor($ctx), $values); } /** * Evaluate Query. * * @param \DOMNode $ctx * @param array $values * @param bool $registerNodeNS * * @throws fDOMException * * @return mixed */ public function evaluate(\DOMNode $ctx, array $values = NULL, $registerNodeNS = TRUE) { $xp = $this->getXPathObjectFor($ctx); return $xp->evaluate($this->buildQuery($xp, $values), $ctx, $registerNodeNS); } /** * Execute Query. * * @param \DOMNode $ctx * @param array $values * @param bool $registerNodeNS * * @throws fDOMException * * @return mixed */ public function query(\DOMNode $ctx, array $values = NULL, $registerNodeNS = TRUE) { $xp = $this->getXPathObjectFor($ctx); return $xp->evaluate($this->buildQuery($xp, $values), $ctx, $registerNodeNS); } /** * Execute Query and return first result. * * @param \DOMNode $ctx * @param array $values * @param bool $registerNodeNS * * @return \DOMNode */ public function queryOne(\DOMNode $ctx, array $values = NULL, $registerNodeNS = TRUE) { $xp = $this->getXPathObjectFor($ctx); return $xp->queryOne($this->buildQuery($xp, $values), $ctx, $registerNodeNS); } /** * Return xPath for node * * @param \DOMNode $ctx * * @throws fDOMException * * @return fDOMXPath */ private function getXPathObjectFor(\DOMNode $ctx) { $dom = $ctx instanceof \DOMDocument ? $ctx : $ctx->ownerDocument; if ($dom instanceOf fDOMDocument) { return $dom->getDOMXPath(); } return new fDOMXPath($dom); } /** * Build query using values. * * @param fDOMXPath $xp * @param array $values * * @throws XPathQueryException * * @return string */ private function buildQuery(fDOMXPath $xp, array $values = NULL) { $backup = $this->values; if (count($values) > 0) { foreach($values as $k => $v) { $this->bind($k, $v); } } $query = $xp->prepare($this->query, $this->values); $this->values = $backup; return $query; } } } translator = $translator; } /** * @param $selector * * @return string */ public function apply($selector) { return preg_replace_callback( '/([a-zA-Z0-9\_\-\*]+):not\(([^\)]*)\)/', array($this, 'callback'), $selector ); } /** * @param array $matches * * @return string */ private function callback(array $matches) { $subresult = preg_replace( '/^[^\[]+\[([^\]]*)\].*$/', '$1', $this->translator->translate($matches[2]) ); return $matches[1] . '[not(' . $subresult . ')]'; } } } regex = $regex; $this->replacement = $replacement; } /** * @param $selector * * @return string */ public function apply($selector) { return preg_replace($this->regex, $this->replacement, $selector); } } } =0]/self::' . $matches[1]; } case 'odd': { return $matches[1] . '[(count(preceding-sibling::*) + 1) mod 2=1]'; } default: { $b = !isset($matches[2]) || empty($matches[2]) ? '0' : $matches[2]; $b = preg_replace('/^([0-9]*)n.*?([0-9]*)$/', '$1+$2', $b); $b = explode('+', $b); if (!isset($b[1])) { $b[1] = '0'; } return '*[(position()-' . $b[1] . ') mod ' . $b[0] . '=0 and position()>=' . $b[1] . ']/self::' . $matches[1]; } } } } } * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of Arne Blankerts nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * * @category PHP * @package TheSeer\fDOM * @author Arne Blankerts * @copyright Arne Blankerts , All rights reserved. * @license http://www.opensource.org/licenses/bsd-license.php BSD License * @link http://github.com/theseer/fdomdocument * */ namespace TheSeer\fDOM\CSS { /** * Class Translator * * The regular expressions used in this class are heavily inspired by and mostly adopted from * the css2xpath.js code by Andrea Giammarchi (http://code.google.com/p/css2xpath/). * The JavaScript version (css2xpath.js) is licensed under the MIT License * */ class Translator { /** * @var array */ private $rules; /** * @param string $selector A CSS Selector string * * @return string */ public function translate($selector) { foreach($this->getRules() as $rule) { /** @var RuleInterface $rule */ $selector = $rule->apply($selector); } return '//' . $selector; } /** * @return array */ private function getRules() { if ($this->rules != NULL) { return $this->rules; } $this->rules = array( // prefix|name new RegexRule('/([a-zA-Z0-9\_\-\*]+)\|([a-zA-Z0-9\_\-\*]+)/', '$1:$2'), // add @ for attribs new RegexRule("/\[([^\]~\$\*\^\|\!]+)(=[^\]]+)?\]/", '[@$1$2]'), // multiple queries new RegexRule("/\s*,\s*/", '|'), // , + ~ > new RegexRule("/\s*(\+|~|>)\s*/", '$1'), //* ~ + > new RegexRule("/([a-zA-Z0-9\_\-\*])~([a-zA-Z0-9\_\-\*])/", '$1/following-sibling::$2'), new RegexRule("/([a-zA-Z0-9\_\-\*])\+([a-zA-Z0-9\_\-\*])/", '$1/following-sibling::*[1]/self::$2'), new RegexRule("/([a-zA-Z0-9\_\-\*])>([a-zA-Z0-9\_\-\*])/", '$1/$2'), // all unescaped stuff escaped new RegexRule("/\[([^=]+)=([^'|'][^\]]*)\]/", '[$1="$2"]'), // all descendant or self to // new RegexRule("/(^|[^a-zA-Z0-9\_\-\*])(#|\.)([a-zA-Z0-9\_\-]+)/", '$1*$2$3'), new RegexRule("/([\>\+\|\~\,\s])([a-zA-Z\*]+)/", '$1//$2'), new RegexRule("/\s+\/\//", '//'), // :first-child new RegexRule("/([a-zA-Z0-9\_\-\*]+):first-child/", '*[1]/self::$1'), // :last-child new RegexRule("/([a-zA-Z0-9\_\-\*]+):last-child/", '$1[not(following-sibling::*)]'), // :only-child new RegexRule("/([a-zA-Z0-9\_\-\*]+):only-child/", '*[last()=1]/self::$1'), // :empty new RegexRule("/([a-zA-Z0-9\_\-\*]+):empty/", '$1[not(*) and not(normalize-space())]'), // :not new NotRule($this), // :nth-child new NthChildRule(), // :contains(selectors) new RegexRule('/:contains\(([^\)]*)\)/', '[contains(string(.),"$1")]'), // |= attrib new RegexRule("/\[([a-zA-Z0-9\_\-]+)\|=([^\]]+)\]/", '[@$1=$2 or starts-with(@$1,concat($2,"-"))]'), // *= attrib new RegexRule("/\[([a-zA-Z0-9\_\-]+)\*=([^\]]+)\]/", '[contains(@$1,$2)]'), // ~= attrib new RegexRule("/\[([a-zA-Z0-9\_\-]+)~=([^\]]+)\]/", '[contains(concat(" ",normalize-space(@$1)," "),concat(" ",$2," "))]'), // ^= attrib new RegexRule("/\[([a-zA-Z0-9\_\-]+)\^=([^\]]+)\]/", '[starts-with(@$1,$2)]'), // $= attrib new DollarEqualRule(), // != attrib new RegexRule("/\[([a-zA-Z0-9\_\-]+)\!=([^\]]+)\]/", '[not(@$1) or @$1!=$2]'), // ids and classes new RegexRule("/#([a-zA-Z0-9\_\-]+)/", '[@id="$1"]'), new RegexRule("/\.([a-zA-Z0-9\_\-]+)/", '[contains(concat(" ",normalize-space(@class)," ")," $1 ")]'), // normalize multiple filters new RegexRule("/\]\[([^\]]+)/", ' and ($1)') ); return $this->rules; } } } * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of Arne Blankerts nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * * @category PHP * @package TheSeer\fDOM * @author Arne Blankerts * @copyright Arne Blankerts , All rights reserved. * @license http://www.opensource.org/licenses/bsd-license.php BSD License * @link http://github.com/theseer/fdomdocument * */ namespace TheSeer\fDOM { /** * fDOMXPath extension to PHP's DOMXPath. * * @category PHP * @package TheSeer\fDOM * @author Arne Blankerts * @access public * */ class fDOMXPath extends \DOMXPath { /** * @var \DOMDocument */ protected $doc; /** * @param \DOMDocument $doc */ public function __construct(\DOMDocument $doc) { parent::__construct($doc); $this->doc = $doc; } /** * @param string $xpath * @param array $valueMap * * @return string */ public function prepare($xpath, array $valueMap) { if (count($valueMap)==0) { return $xpath; } foreach($valueMap as $key => $value) { $xpath = str_replace(':'.$key, $this->quote($value), $xpath); } return $xpath; } /** * @param string $q * @param \DOMNode $ctx * @param bool $registerNodeNS * * @throws fDOMException * * @return \DOMNodeList */ public function query($q, \DOMNode $ctx = null, $registerNodeNS = true) { libxml_clear_errors(); if (version_compare(PHP_VERSION, '5.3.3', '<') || strpos(PHP_VERSION, 'hiphop') || strpos(PHP_VERSION, 'hhvm')) { $rc = parent::query($q, ($ctx instanceof \DOMNode) ? $ctx : $this->doc->documentElement); } else { $rc = parent::query($q, ($ctx instanceof \DOMNode) ? $ctx : $this->doc->documentElement, $registerNodeNS); } if (libxml_get_last_error()) { throw new fDOMException('evaluating xpath expression failed.', fDOMException::QueryError); } return $rc; } /** * @param string $q * @param \DOMNode $ctx * @param bool $registerNodeNS * * @throws fDOMException * * @return mixed */ public function evaluate($q, \DOMNode $ctx = null, $registerNodeNS = true) { libxml_clear_errors(); if (version_compare(PHP_VERSION, '5.3.3', '<') || strpos(PHP_VERSION, 'hiphop') || strpos(PHP_VERSION, 'hhvm')) { $rc = parent::evaluate($q, ($ctx instanceof \DOMNode) ? $ctx : $this->doc->documentElement); } else { $rc = parent::evaluate($q, ($ctx instanceof \DOMNode) ? $ctx : $this->doc->documentElement, $registerNodeNS); } if (libxml_get_last_error()) { throw new fDOMException('evaluating xpath expression failed.', fDOMException::QueryError); } return $rc; } /** * @param string $q * @param \DOMNode $ctx * @param bool $registerNodeNS * * @throws fDOMException * * @return \DOMNode|mixed */ public function queryOne($q, \DOMNode $ctx = null, $registerNodeNS = true) { $rc = $this->evaluate($q, $ctx, $registerNodeNS); if ($rc instanceof \DOMNodelist) { return $rc->item(0); } return $rc; } /** * @param string $str * * @return string */ public function quote($str) { if (strpos($str, '"') === false) { return '"'.$str.'"'; } $parts = explode('"', $str); return 'concat("' . join('",\'"\',"', $parts).'")'; } } } * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of Arne Blankerts nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @package fDOM * @author Arne Blankerts * @copyright Arne Blankerts , All rights reserved. * @license BSD License */ namespace TheSeer\fDOM { /** * fDOMException * * @category PHP * @package TheSeer\fDOM * @author Arne Blankerts * @access public * */ class fDOMException extends \Exception { const LoadError = 1; const ParseError = 2; const SaveError = 3; const QueryError = 4; const RegistrationFailed = 5; const NoDOMXPath = 6; const UnboundPrefix = 7; const SetFailedError = 8; const NameInvalid = 9; /** * List of libxml error objects * * @var array */ private $errorList; /** * Full Error message * * @var string */ private $fullMessage = null; /** * Short Error Message * * @var string */ private $shortMessage = null; private static $fullMesageMode = true; /** * Constructor * * @param string $message Exception message * @param integer $code Exception code * @param \Exception $chain optional chained exception * */ public function __construct($message, $code = 0, \Exception $chain = NULL) { $this->shortMessage = $message; $this->errorList = libxml_get_errors(); libxml_clear_errors(); parent :: __construct($message, $code, $chain); $this->fullMessage = $message."\n\n"; foreach ($this->errorList as $error) { // hack, skip "attempt to load external pseudo error" if ($error->code=='1543') { continue; } if (empty($error->file)) { $this->fullMessage .= '[XML-STRING] '; } else { $this->fullMessage .= '['.$error->file.'] '; } $this->fullMessage .= '[Line: '.$error->line.' - Column: '.$error->column.'] '; switch ($error->level) { case LIBXML_ERR_WARNING: $this->fullMessage .= "Warning $error->code: "; break; case LIBXML_ERR_ERROR: $this->fullMessage .= "Error $error->code: "; break; case LIBXML_ERR_FATAL: $this->fullMessage .= "Fatal Error $error->code: "; break; } $this->fullMessage .= str_replace("\n", '', $error->message)."\n"; if (self::$fullMesageMode) { $this->message = $this->fullMessage; } } } /** * Accessor to fullMessage * * @return string */ public function getFullMessage() { return $this->fullMessage; } /** * Access to shortMessage * * @return string */ public function getShortMessage() { return $this->shortMessage; } /** * Accessor to errorList objets * * @return array */ public function getErrorList() { return $this->errorList; } /** * Toggle wehter getMessage() should return full or only exception message * * @param boolean $full Flag to enable or disable full message output * * @return void */ public function toggleFullMessage($full = true) { $this->message = $full ? $this->fullMessage : $this->shortMessage; } /** * Magic method for string context * * @return string */ public function __toString() { return $this->fullMessage; } } // fDOMException } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Environment; /** * Utility class for HHVM/PHP environment handling. */ class Runtime { /** * @var string */ private static $binary; /** * Returns true when Xdebug is supported or * the runtime used is PHPDBG (PHP >= 7.0). * * @return bool */ public function canCollectCodeCoverage() { return $this->hasXdebug() || $this->hasPHPDBGCodeCoverage(); } /** * Returns the path to the binary of the current runtime. * Appends ' --php' to the path when the runtime is HHVM. * * @return string */ public function getBinary() { // HHVM if (self::$binary === null && $this->isHHVM()) { if ((self::$binary = getenv('PHP_BINARY')) === false) { self::$binary = PHP_BINARY; } self::$binary = escapeshellarg(self::$binary) . ' --php'; } // PHP >= 5.4.0 if (self::$binary === null && defined('PHP_BINARY')) { self::$binary = escapeshellarg(PHP_BINARY); } // PHP < 5.4.0 if (self::$binary === null) { if (PHP_SAPI == 'cli' && isset($_SERVER['_'])) { if (strpos($_SERVER['_'], 'phpunit') !== false) { $file = file($_SERVER['_']); if (strpos($file[0], ' ') !== false) { $tmp = explode(' ', $file[0]); self::$binary = escapeshellarg(trim($tmp[1])); } else { self::$binary = escapeshellarg(ltrim(trim($file[0]), '#!')); } } elseif (strpos(basename($_SERVER['_']), 'php') !== false) { self::$binary = escapeshellarg($_SERVER['_']); } } } if (self::$binary === null) { $possibleBinaryLocations = array( PHP_BINDIR . '/php', PHP_BINDIR . '/php-cli.exe', PHP_BINDIR . '/php.exe' ); foreach ($possibleBinaryLocations as $binary) { if (is_readable($binary)) { self::$binary = escapeshellarg($binary); break; } } } if (self::$binary === null) { self::$binary = 'php'; } return self::$binary; } /** * @return string */ public function getNameWithVersion() { return $this->getName() . ' ' . $this->getVersion(); } /** * @return string */ public function getName() { if ($this->isHHVM()) { return 'HHVM'; } elseif ($this->isPHPDBG()) { return 'PHPDBG'; } else { return 'PHP'; } } /** * @return string */ public function getVendorUrl() { if ($this->isHHVM()) { return 'http://hhvm.com/'; } else { return 'http://php.net/'; } } /** * @return string */ public function getVersion() { if ($this->isHHVM()) { return HHVM_VERSION; } else { return PHP_VERSION; } } /** * Returns true when the runtime used is PHP and Xdebug is loaded. * * @return bool */ public function hasXdebug() { return ($this->isPHP() || $this->isHHVM()) && extension_loaded('xdebug'); } /** * Returns true when the runtime used is HHVM. * * @return bool */ public function isHHVM() { return defined('HHVM_VERSION'); } /** * Returns true when the runtime used is PHP without the PHPDBG SAPI. * * @return bool */ public function isPHP() { return !$this->isHHVM() && !$this->isPHPDBG(); } /** * Returns true when the runtime used is PHP with the PHPDBG SAPI. * * @return bool */ public function isPHPDBG() { return PHP_SAPI === 'phpdbg' && !$this->isHHVM(); } /** * Returns true when the runtime used is PHP with the PHPDBG SAPI * and the phpdbg_*_oplog() functions are available (PHP >= 7.0). * * @return bool */ public function hasPHPDBGCodeCoverage() { return $this->isPHPDBG() && function_exists('phpdbg_start_oplog'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Environment; /** */ class Console { const STDIN = 0; const STDOUT = 1; const STDERR = 2; /** * Returns true if STDOUT supports colorization. * * This code has been copied and adapted from * Symfony\Component\Console\Output\OutputStream. * * @return bool */ public function hasColorSupport() { if (DIRECTORY_SEPARATOR == '\\') { return false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI'); } if (!defined('STDOUT')) { return false; } return $this->isInteractive(STDOUT); } /** * Returns the number of columns of the terminal. * * @return int */ public function getNumberOfColumns() { // Windows terminals have a fixed size of 80 // but one column is used for the cursor. if (DIRECTORY_SEPARATOR == '\\') { return 79; } if (!$this->isInteractive(self::STDIN)) { return 80; } if (preg_match('#\d+ (\d+)#', shell_exec('stty size'), $match) === 1) { return (int) $match[1]; } if (preg_match('#columns = (\d+);#', shell_exec('stty'), $match) === 1) { return (int) $match[1]; } return 80; } /** * Returns if the file descriptor is an interactive terminal or not. * * @param int|resource $fileDescriptor * * @return bool */ public function isInteractive($fileDescriptor = self::STDOUT) { return function_exists('posix_isatty') && @posix_isatty($fileDescriptor); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Exporter; use SebastianBergmann\RecursionContext\Context; /** * A nifty utility for visualizing PHP variables. * * * export(new Exception); * */ class Exporter { /** * Exports a value as a string * * The output of this method is similar to the output of print_r(), but * improved in various aspects: * * - NULL is rendered as "null" (instead of "") * - TRUE is rendered as "true" (instead of "1") * - FALSE is rendered as "false" (instead of "") * - Strings are always quoted with single quotes * - Carriage returns and newlines are normalized to \n * - Recursion and repeated rendering is treated properly * * @param mixed $value * @param int $indentation The indentation level of the 2nd+ line * @return string */ public function export($value, $indentation = 0) { return $this->recursiveExport($value, $indentation); } /** * @param mixed $data * @param Context $context * @return string */ public function shortenedRecursiveExport(&$data, Context $context = null) { $result = array(); $exporter = new self(); if (!$context) { $context = new Context; } $context->add($data); foreach ($data as $key => $value) { if (is_array($value)) { if ($context->contains($data[$key]) !== false) { $result[] = '*RECURSION*'; } else { $result[] = sprintf( 'array(%s)', $this->shortenedRecursiveExport($data[$key], $context) ); } } else { $result[] = $exporter->shortenedExport($value); } } return implode(', ', $result); } /** * Exports a value into a single-line string * * The output of this method is similar to the output of * SebastianBergmann\Exporter\Exporter::export. This method guarantees * thought that the result contains now newlines. * * Newlines are replaced by the visible string '\n'. Contents of arrays * and objects (if any) are replaced by '...'. * * @param mixed $value * @return string * @see SebastianBergmann\Exporter\Exporter::export */ public function shortenedExport($value) { if (is_string($value)) { $string = $this->export($value); if (strlen($string) > 40) { $string = substr($string, 0, 30) . '...' . substr($string, -7); } return str_replace("\n", '\n', $string); } if (is_object($value)) { return sprintf( '%s Object (%s)', get_class($value), count($this->toArray($value)) > 0 ? '...' : '' ); } if (is_array($value)) { return sprintf( 'Array (%s)', count($value) > 0 ? '...' : '' ); } return $this->export($value); } /** * Converts an object to an array containing all of its private, protected * and public properties. * * @param mixed $value * @return array */ public function toArray($value) { if (!is_object($value)) { return (array) $value; } $array = array(); foreach ((array) $value as $key => $val) { // properties are transformed to keys in the following way: // private $property => "\0Classname\0property" // protected $property => "\0*\0property" // public $property => "property" if (preg_match('/^\0.+\0(.+)$/', $key, $matches)) { $key = $matches[1]; } // See https://github.com/php/php-src/commit/5721132 if ($key === "\0gcdata") { continue; } $array[$key] = $val; } // Some internal classes like SplObjectStorage don't work with the // above (fast) mechanism nor with reflection in Zend. // Format the output similarly to print_r() in this case if ($value instanceof \SplObjectStorage) { // However, the fast method does work in HHVM, and exposes the // internal implementation. Hide it again. if (property_exists('\SplObjectStorage', '__storage')) { unset($array['__storage']); } elseif (property_exists('\SplObjectStorage', 'storage')) { unset($array['storage']); } if (property_exists('\SplObjectStorage', '__key')) { unset($array['__key']); } foreach ($value as $key => $val) { $array[spl_object_hash($val)] = array( 'obj' => $val, 'inf' => $value->getInfo(), ); } } return $array; } /** * Recursive implementation of export * * @param mixed $value The value to export * @param int $indentation The indentation level of the 2nd+ line * @param \SebastianBergmann\RecursionContext\Context $processed Previously processed objects * @return string * @see SebastianBergmann\Exporter\Exporter::export */ protected function recursiveExport(&$value, $indentation, $processed = null) { if ($value === null) { return 'null'; } if ($value === true) { return 'true'; } if ($value === false) { return 'false'; } if (is_float($value) && floatval(intval($value)) === $value) { return "$value.0"; } if (is_resource($value)) { return sprintf( 'resource(%d) of type (%s)', $value, get_resource_type($value) ); } if (is_string($value)) { // Match for most non printable chars somewhat taking multibyte chars into account if (preg_match('/[^\x09-\x0d\x20-\xff]/', $value)) { return 'Binary String: 0x' . bin2hex($value); } return "'" . str_replace(array("\r\n", "\n\r", "\r"), array("\n", "\n", "\n"), $value) . "'"; } $whitespace = str_repeat(' ', 4 * $indentation); if (!$processed) { $processed = new Context; } if (is_array($value)) { if (($key = $processed->contains($value)) !== false) { return 'Array &' . $key; } $key = $processed->add($value); $values = ''; if (count($value) > 0) { foreach ($value as $k => $v) { $values .= sprintf( '%s %s => %s' . "\n", $whitespace, $this->recursiveExport($k, $indentation), $this->recursiveExport($value[$k], $indentation + 1, $processed) ); } $values = "\n" . $values . $whitespace; } return sprintf('Array &%s (%s)', $key, $values); } if (is_object($value)) { $class = get_class($value); if ($hash = $processed->contains($value)) { return sprintf('%s Object &%s', $class, $hash); } $hash = $processed->add($value); $values = ''; $array = $this->toArray($value); if (count($array) > 0) { foreach ($array as $k => $v) { $values .= sprintf( '%s %s => %s' . "\n", $whitespace, $this->recursiveExport($k, $indentation), $this->recursiveExport($v, $indentation + 1, $processed) ); } $values = "\n" . $values . $whitespace; } return sprintf('%s Object &%s (%s)', $class, $hash, $values); } return var_export($value, true); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\RecursionContext; /** */ interface Exception { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\RecursionContext; /** * A context containing previously processed arrays and objects * when recursively processing a value. */ final class Context { /** * @var array[] */ private $arrays; /** * @var \SplObjectStorage */ private $objects; /** * Initialises the context */ public function __construct() { $this->arrays = array(); $this->objects = new \SplObjectStorage; } /** * Adds a value to the context. * * @param array|object $value The value to add. * @return int|string The ID of the stored value, either as * a string or integer. * @throws InvalidArgumentException Thrown if $value is not an array or * object */ public function add(&$value) { if (is_array($value)) { return $this->addArray($value); } else if (is_object($value)) { return $this->addObject($value); } throw new InvalidArgumentException( 'Only arrays and objects are supported' ); } /** * Checks if the given value exists within the context. * * @param array|object $value The value to check. * @return int|string|false The string or integer ID of the stored * value if it has already been seen, or * false if the value is not stored. * @throws InvalidArgumentException Thrown if $value is not an array or * object */ public function contains(&$value) { if (is_array($value)) { return $this->containsArray($value); } else if (is_object($value)) { return $this->containsObject($value); } throw new InvalidArgumentException( 'Only arrays and objects are supported' ); } /** * @param array $array * @return bool|int */ private function addArray(array &$array) { $key = $this->containsArray($array); if ($key !== false) { return $key; } $this->arrays[] = &$array; return count($this->arrays) - 1; } /** * @param object $object * @return string */ private function addObject($object) { if (!$this->objects->contains($object)) { $this->objects->attach($object); } return spl_object_hash($object); } /** * @param array $array * @return int|false */ private function containsArray(array &$array) { $keys = array_keys($this->arrays, $array, true); $hash = '_Key_' . hash('sha512', microtime(true)); foreach ($keys as $key) { $this->arrays[$key][$hash] = $hash; if (isset($array[$hash]) && $array[$hash] === $hash) { unset($this->arrays[$key][$hash]); return $key; } unset($this->arrays[$key][$hash]); } return false; } /** * @param object $value * @return string|false */ private function containsObject($value) { if ($this->objects->contains($value)) { return spl_object_hash($value); } return false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\RecursionContext; /** */ final class InvalidArgumentException extends \InvalidArgumentException implements Exception { } . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * * Neither the name of Sebastian Bergmann nor the names of his * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @author Sebastian Bergmann * @copyright 2001-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.github.com/sebastianbergmann/global-state */ namespace SebastianBergmann\GlobalState; /** * @author Sebastian Bergmann * @copyright 2001-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.github.com/sebastianbergmann/global-state */ interface Exception { } . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * * Neither the name of Sebastian Bergmann nor the names of his * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @author Sebastian Bergmann * @copyright 2001-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.github.com/sebastianbergmann/global-state */ namespace SebastianBergmann\GlobalState; use Closure; use ReflectionClass; /** * A snapshot of global state. * * @author Sebastian Bergmann * @copyright 2001-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.github.com/sebastianbergmann/global-state */ class Snapshot { /** * @var Blacklist */ private $blacklist; /** * @var array */ private $globalVariables = array(); /** * @var array */ private $superGlobalArrays = array(); /** * @var array */ private $superGlobalVariables = array(); /** * @var array */ private $staticAttributes = array(); /** * @var array */ private $iniSettings = array(); /** * @var array */ private $includedFiles = array(); /** * @var array */ private $constants = array(); /** * @var array */ private $functions = array(); /** * @var array */ private $interfaces = array(); /** * @var array */ private $classes = array(); /** * @var array */ private $traits = array(); /** * Creates a snapshot of the current global state. * * @param Blacklist $blacklist * @param boolean $includeGlobalVariables * @param boolean $includeStaticAttributes * @param boolean $includeConstants * @param boolean $includeFunctions * @param boolean $includeClasses * @param boolean $includeInterfaces * @param boolean $includeTraits * @param boolean $includeIniSettings * @param boolean $includeIncludedFiles */ public function __construct(Blacklist $blacklist = null, $includeGlobalVariables = true, $includeStaticAttributes = true, $includeConstants = true, $includeFunctions = true, $includeClasses = true, $includeInterfaces = true, $includeTraits = true, $includeIniSettings = true, $includeIncludedFiles = true) { if ($blacklist === null) { $blacklist = new Blacklist; } $this->blacklist = $blacklist; if ($includeConstants) { $this->snapshotConstants(); } if ($includeFunctions) { $this->snapshotFunctions(); } if ($includeClasses || $includeStaticAttributes) { $this->snapshotClasses(); } if ($includeInterfaces) { $this->snapshotInterfaces(); } if ($includeGlobalVariables) { $this->setupSuperGlobalArrays(); $this->snapshotGlobals(); } if ($includeStaticAttributes) { $this->snapshotStaticAttributes(); } if ($includeIniSettings) { $this->iniSettings = ini_get_all(null, false); } if ($includeIncludedFiles) { $this->includedFiles = get_included_files(); } if (function_exists('get_declared_traits')) { $this->traits = get_declared_traits(); } } /** * @return Blacklist */ public function blacklist() { return $this->blacklist; } /** * @return array */ public function globalVariables() { return $this->globalVariables; } /** * @return array */ public function superGlobalVariables() { return $this->superGlobalVariables; } /** * Returns a list of all super-global variable arrays. * * @return array */ public function superGlobalArrays() { return $this->superGlobalArrays; } /** * @return array */ public function staticAttributes() { return $this->staticAttributes; } /** * @return array */ public function iniSettings() { return $this->iniSettings; } /** * @return array */ public function includedFiles() { return $this->includedFiles; } /** * @return array */ public function constants() { return $this->constants; } /** * @return array */ public function functions() { return $this->functions; } /** * @return array */ public function interfaces() { return $this->interfaces; } /** * @return array */ public function classes() { return $this->classes; } /** * @return array */ public function traits() { return $this->traits; } /** * Creates a snapshot user-defined constants. */ private function snapshotConstants() { $constants = get_defined_constants(true); if (isset($constants['user'])) { $this->constants = $constants['user']; } } /** * Creates a snapshot user-defined functions. */ private function snapshotFunctions() { $functions = get_defined_functions(); $this->functions = $functions['user']; } /** * Creates a snapshot user-defined classes. */ private function snapshotClasses() { foreach (array_reverse(get_declared_classes()) as $className) { $class = new ReflectionClass($className); if (!$class->isUserDefined()) { break; } $this->classes[] = $className; } $this->classes = array_reverse($this->classes); } /** * Creates a snapshot user-defined interfaces. */ private function snapshotInterfaces() { foreach (array_reverse(get_declared_interfaces()) as $interfaceName) { $class = new ReflectionClass($interfaceName); if (!$class->isUserDefined()) { break; } $this->interfaces[] = $interfaceName; } $this->interfaces = array_reverse($this->interfaces); } /** * Creates a snapshot of all global and super-global variables. */ private function snapshotGlobals() { $superGlobalArrays = $this->superGlobalArrays(); foreach ($superGlobalArrays as $superGlobalArray) { $this->snapshotSuperGlobalArray($superGlobalArray); } foreach (array_keys($GLOBALS) as $key) { if ($key != 'GLOBALS' && !in_array($key, $superGlobalArrays) && $this->canBeSerialized($GLOBALS[$key]) && !$this->blacklist->isGlobalVariableBlacklisted($key)) { $this->globalVariables[$key] = unserialize(serialize($GLOBALS[$key])); } } } /** * Creates a snapshot a super-global variable array. * * @param $superGlobalArray */ private function snapshotSuperGlobalArray($superGlobalArray) { $this->superGlobalVariables[$superGlobalArray] = array(); if (isset($GLOBALS[$superGlobalArray]) && is_array($GLOBALS[$superGlobalArray])) { foreach ($GLOBALS[$superGlobalArray] as $key => $value) { $this->superGlobalVariables[$superGlobalArray][$key] = unserialize(serialize($value)); } } } /** * Creates a snapshot of all static attributes in user-defined classes. */ private function snapshotStaticAttributes() { foreach ($this->classes as $className) { $class = new ReflectionClass($className); $snapshot = array(); foreach ($class->getProperties() as $attribute) { if ($attribute->isStatic()) { $name = $attribute->getName(); if ($this->blacklist->isStaticAttributeBlacklisted($className, $name)) { continue; } $attribute->setAccessible(true); $value = $attribute->getValue(); if ($this->canBeSerialized($value)) { $snapshot[$name] = unserialize(serialize($value)); } } } if (!empty($snapshot)) { $this->staticAttributes[$className] = $snapshot; } } } /** * Returns a list of all super-global variable arrays. * * @return array */ private function setupSuperGlobalArrays() { $this->superGlobalArrays = array( '_ENV', '_POST', '_GET', '_COOKIE', '_SERVER', '_FILES', '_REQUEST' ); if (ini_get('register_long_arrays') == '1') { $this->superGlobalArrays = array_merge( $this->superGlobalArrays, array( 'HTTP_ENV_VARS', 'HTTP_POST_VARS', 'HTTP_GET_VARS', 'HTTP_COOKIE_VARS', 'HTTP_SERVER_VARS', 'HTTP_POST_FILES' ) ); } } /** * @param mixed $variable * @return boolean * @todo Implement this properly */ private function canBeSerialized($variable) { return !$variable instanceof Closure; } } . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * * Neither the name of Sebastian Bergmann nor the names of his * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @author Sebastian Bergmann * @copyright 2001-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.github.com/sebastianbergmann/global-state */ namespace SebastianBergmann\GlobalState; use ReflectionProperty; /** * Restorer of snapshots of global state. * * @author Sebastian Bergmann * @copyright 2001-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.github.com/sebastianbergmann/global-state */ class Restorer { /** * Deletes function definitions that are not defined in a snapshot. * * @param Snapshot $snapshot * @throws RuntimeException when the uopz_delete() function is not available * @see https://github.com/krakjoe/uopz */ public function restoreFunctions(Snapshot $snapshot) { if (!function_exists('uopz_delete')) { throw new RuntimeException('The uopz_delete() function is required for this operation'); } $functions = get_defined_functions(); foreach (array_diff($functions['user'], $snapshot->functions()) as $function) { uopz_delete($function); } } /** * Restores all global and super-global variables from a snapshot. * * @param Snapshot $snapshot */ public function restoreGlobalVariables(Snapshot $snapshot) { $superGlobalArrays = $snapshot->superGlobalArrays(); foreach ($superGlobalArrays as $superGlobalArray) { $this->restoreSuperGlobalArray($snapshot, $superGlobalArray); } $globalVariables = $snapshot->globalVariables(); foreach (array_keys($GLOBALS) as $key) { if ($key != 'GLOBALS' && !in_array($key, $superGlobalArrays) && !$snapshot->blacklist()->isGlobalVariableBlacklisted($key)) { if (isset($globalVariables[$key])) { $GLOBALS[$key] = $globalVariables[$key]; } else { unset($GLOBALS[$key]); } } } } /** * Restores all static attributes in user-defined classes from this snapshot. * * @param Snapshot $snapshot */ public function restoreStaticAttributes(Snapshot $snapshot) { foreach ($snapshot->staticAttributes() as $className => $staticAttributes) { foreach ($staticAttributes as $name => $value) { $reflector = new ReflectionProperty($className, $name); $reflector->setAccessible(true); $reflector->setValue($value); } } } /** * Restores a super-global variable array from this snapshot. * * @param Snapshot $snapshot * @param $superGlobalArray */ private function restoreSuperGlobalArray(Snapshot $snapshot, $superGlobalArray) { $superGlobalVariables = $snapshot->superGlobalVariables(); if (isset($GLOBALS[$superGlobalArray]) && is_array($GLOBALS[$superGlobalArray]) && isset($superGlobalVariables[$superGlobalArray])) { $keys = array_keys( array_merge( $GLOBALS[$superGlobalArray], $superGlobalVariables[$superGlobalArray] ) ); foreach ($keys as $key) { if (isset($superGlobalVariables[$superGlobalArray][$key])) { $GLOBALS[$superGlobalArray][$key] = $superGlobalVariables[$superGlobalArray][$key]; } else { unset($GLOBALS[$superGlobalArray][$key]); } } } } } . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * * Neither the name of Sebastian Bergmann nor the names of his * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @author Sebastian Bergmann * @copyright 2001-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.github.com/sebastianbergmann/global-state */ namespace SebastianBergmann\GlobalState; use ReflectionClass; /** * A blacklist for global state elements that should not be snapshotted. * * @author Sebastian Bergmann * @copyright 2001-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.github.com/sebastianbergmann/global-state */ class Blacklist { /** * @var array */ private $globalVariables = array(); /** * @var array */ private $classes = array(); /** * @var array */ private $classNamePrefixes = array(); /** * @var array */ private $parentClasses = array(); /** * @var array */ private $interfaces = array(); /** * @var array */ private $staticAttributes = array(); /** * @param string $variableName */ public function addGlobalVariable($variableName) { $this->globalVariables[$variableName] = true; } /** * @param string $className */ public function addClass($className) { $this->classes[] = $className; } /** * @param string $className */ public function addSubclassesOf($className) { $this->parentClasses[] = $className; } /** * @param string $interfaceName */ public function addImplementorsOf($interfaceName) { $this->interfaces[] = $interfaceName; } /** * @param string $classNamePrefix */ public function addClassNamePrefix($classNamePrefix) { $this->classNamePrefixes[] = $classNamePrefix; } /** * @param string $className * @param string $attributeName */ public function addStaticAttribute($className, $attributeName) { if (!isset($this->staticAttributes[$className])) { $this->staticAttributes[$className] = array(); } $this->staticAttributes[$className][$attributeName] = true; } /** * @param string $variableName * @return boolean */ public function isGlobalVariableBlacklisted($variableName) { return isset($this->globalVariables[$variableName]); } /** * @param string $className * @param string $attributeName * @return boolean */ public function isStaticAttributeBlacklisted($className, $attributeName) { if (in_array($className, $this->classes)) { return true; } foreach ($this->classNamePrefixes as $prefix) { if (strpos($className, $prefix) === 0) { return true; } } $class = new ReflectionClass($className); foreach ($this->parentClasses as $type) { if ($class->isSubclassOf($type)) { return true; } } foreach ($this->interfaces as $type) { if ($class->implementsInterface($type)) { return true; } } if (isset($this->staticAttributes[$className][$attributeName])) { return true; } return false; } } . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * * Neither the name of Sebastian Bergmann nor the names of his * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @author Sebastian Bergmann * @copyright 2001-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.github.com/sebastianbergmann/global-state */ namespace SebastianBergmann\GlobalState; /** * @author Sebastian Bergmann * @copyright 2001-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.github.com/sebastianbergmann/global-state */ class RuntimeException extends \RuntimeException implements Exception { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Comparator; use SebastianBergmann\Exporter\Exporter; /** * Abstract base class for comparators which compare values for equality. */ abstract class Comparator { /** * @var Factory */ protected $factory; /** * @var Exporter */ protected $exporter; public function __construct() { $this->exporter = new Exporter; } /** * @param Factory $factory */ public function setFactory(Factory $factory) { $this->factory = $factory; } /** * Returns whether the comparator can compare two values. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @return bool */ abstract public function accepts($expected, $actual); /** * Asserts that two values are equal. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @param float $delta The allowed numerical distance between two values to * consider them equal * @param bool $canonicalize If set to TRUE, arrays are sorted before * comparison * @param bool $ignoreCase If set to TRUE, upper- and lowercasing is * ignored when comparing string values * @throws ComparisonFailure Thrown when the comparison * fails. Contains information about the * specific errors that lead to the failure. */ abstract public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Comparator; /** * Compares \SplObjectStorage instances for equality. */ class SplObjectStorageComparator extends Comparator { /** * Returns whether the comparator can compare two values. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @return bool */ public function accepts($expected, $actual) { return $expected instanceof \SplObjectStorage && $actual instanceof \SplObjectStorage; } /** * Asserts that two values are equal. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @param float $delta The allowed numerical distance between two values to * consider them equal * @param bool $canonicalize If set to TRUE, arrays are sorted before * comparison * @param bool $ignoreCase If set to TRUE, upper- and lowercasing is * ignored when comparing string values * @throws ComparisonFailure Thrown when the comparison * fails. Contains information about the * specific errors that lead to the failure. */ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false) { foreach ($actual as $object) { if (!$expected->contains($object)) { throw new ComparisonFailure( $expected, $actual, $this->exporter->export($expected), $this->exporter->export($actual), false, 'Failed asserting that two objects are equal.' ); } } foreach ($expected as $object) { if (!$actual->contains($object)) { throw new ComparisonFailure( $expected, $actual, $this->exporter->export($expected), $this->exporter->export($actual), false, 'Failed asserting that two objects are equal.' ); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Comparator; /** * Compares values for type equality. */ class TypeComparator extends Comparator { /** * Returns whether the comparator can compare two values. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @return bool */ public function accepts($expected, $actual) { return true; } /** * Asserts that two values are equal. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @param float $delta The allowed numerical distance between two values to * consider them equal * @param bool $canonicalize If set to TRUE, arrays are sorted before * comparison * @param bool $ignoreCase If set to TRUE, upper- and lowercasing is * ignored when comparing string values * @throws ComparisonFailure Thrown when the comparison * fails. Contains information about the * specific errors that lead to the failure. */ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false) { if (gettype($expected) != gettype($actual)) { throw new ComparisonFailure( $expected, $actual, // we don't need a diff '', '', false, sprintf( '%s does not match expected type "%s".', $this->exporter->shortenedExport($actual), gettype($expected) ) ); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Comparator; /** * Compares objects for equality. */ class ObjectComparator extends ArrayComparator { /** * Returns whether the comparator can compare two values. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @return bool */ public function accepts($expected, $actual) { return is_object($expected) && is_object($actual); } /** * Asserts that two values are equal. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @param float $delta The allowed numerical distance between two values to * consider them equal * @param bool $canonicalize If set to TRUE, arrays are sorted before * comparison * @param bool $ignoreCase If set to TRUE, upper- and lowercasing is * ignored when comparing string values * @param array $processed * @throws ComparisonFailure Thrown when the comparison * fails. Contains information about the * specific errors that lead to the failure. */ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false, array &$processed = array()) { if (get_class($actual) !== get_class($expected)) { throw new ComparisonFailure( $expected, $actual, $this->exporter->export($expected), $this->exporter->export($actual), false, sprintf( '%s is not instance of expected class "%s".', $this->exporter->export($actual), get_class($expected) ) ); } // don't compare twice to allow for cyclic dependencies if (in_array(array($actual, $expected), $processed, true) || in_array(array($expected, $actual), $processed, true)) { return; } $processed[] = array($actual, $expected); // don't compare objects if they are identical // this helps to avoid the error "maximum function nesting level reached" // CAUTION: this conditional clause is not tested if ($actual !== $expected) { try { parent::assertEquals( $this->toArray($expected), $this->toArray($actual), $delta, $canonicalize, $ignoreCase, $processed ); } catch (ComparisonFailure $e) { throw new ComparisonFailure( $expected, $actual, // replace "Array" with "MyClass object" substr_replace($e->getExpectedAsString(), get_class($expected) . ' Object', 0, 5), substr_replace($e->getActualAsString(), get_class($actual) . ' Object', 0, 5), false, 'Failed asserting that two objects are equal.' ); } } } /** * Converts an object to an array containing all of its private, protected * and public properties. * * @param object $object * @return array */ protected function toArray($object) { return $this->exporter->toArray($object); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Comparator; /** * Compares resources for equality. */ class ResourceComparator extends Comparator { /** * Returns whether the comparator can compare two values. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @return bool */ public function accepts($expected, $actual) { return is_resource($expected) && is_resource($actual); } /** * Asserts that two values are equal. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @param float $delta The allowed numerical distance between two values to * consider them equal * @param bool $canonicalize If set to TRUE, arrays are sorted before * comparison * @param bool $ignoreCase If set to TRUE, upper- and lowercasing is * ignored when comparing string values * @throws ComparisonFailure Thrown when the comparison * fails. Contains information about the * specific errors that lead to the failure. */ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false) { if ($actual != $expected) { throw new ComparisonFailure( $expected, $actual, $this->exporter->export($expected), $this->exporter->export($actual) ); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Comparator; /** * Compares arrays for equality. */ class ArrayComparator extends Comparator { /** * Returns whether the comparator can compare two values. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @return bool */ public function accepts($expected, $actual) { return is_array($expected) && is_array($actual); } /** * Asserts that two values are equal. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @param float $delta The allowed numerical distance between two values to * consider them equal * @param bool $canonicalize If set to TRUE, arrays are sorted before * comparison * @param bool $ignoreCase If set to TRUE, upper- and lowercasing is * ignored when comparing string values * @param array $processed * @throws ComparisonFailure Thrown when the comparison * fails. Contains information about the * specific errors that lead to the failure. */ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false, array &$processed = array()) { if ($canonicalize) { sort($expected); sort($actual); } $remaining = $actual; $expString = $actString = "Array (\n"; $equal = true; foreach ($expected as $key => $value) { unset($remaining[$key]); if (!array_key_exists($key, $actual)) { $expString .= sprintf( " %s => %s\n", $this->exporter->export($key), $this->exporter->shortenedExport($value) ); $equal = false; continue; } try { $comparator = $this->factory->getComparatorFor($value, $actual[$key]); $comparator->assertEquals($value, $actual[$key], $delta, $canonicalize, $ignoreCase, $processed); $expString .= sprintf( " %s => %s\n", $this->exporter->export($key), $this->exporter->shortenedExport($value) ); $actString .= sprintf( " %s => %s\n", $this->exporter->export($key), $this->exporter->shortenedExport($actual[$key]) ); } catch (ComparisonFailure $e) { $expString .= sprintf( " %s => %s\n", $this->exporter->export($key), $e->getExpectedAsString() ? $this->indent($e->getExpectedAsString()) : $this->exporter->shortenedExport($e->getExpected()) ); $actString .= sprintf( " %s => %s\n", $this->exporter->export($key), $e->getActualAsString() ? $this->indent($e->getActualAsString()) : $this->exporter->shortenedExport($e->getActual()) ); $equal = false; } } foreach ($remaining as $key => $value) { $actString .= sprintf( " %s => %s\n", $this->exporter->export($key), $this->exporter->shortenedExport($value) ); $equal = false; } $expString .= ')'; $actString .= ')'; if (!$equal) { throw new ComparisonFailure( $expected, $actual, $expString, $actString, false, 'Failed asserting that two arrays are equal.' ); } } protected function indent($lines) { return trim(str_replace("\n", "\n ", $lines)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Comparator; /** * Compares DateTimeInterface instances for equality. */ class DateTimeComparator extends ObjectComparator { /** * Returns whether the comparator can compare two values. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @return bool */ public function accepts($expected, $actual) { return ($expected instanceof \DateTime || $expected instanceof \DateTimeInterface) && ($actual instanceof \DateTime || $actual instanceof \DateTimeInterface); } /** * Asserts that two values are equal. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @param float $delta The allowed numerical distance between two values to * consider them equal * @param bool $canonicalize If set to TRUE, arrays are sorted before * comparison * @param bool $ignoreCase If set to TRUE, upper- and lowercasing is * ignored when comparing string values * @throws ComparisonFailure Thrown when the comparison * fails. Contains information about the * specific errors that lead to the failure. */ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false) { $delta = new \DateInterval(sprintf('PT%sS', abs($delta))); $expectedLower = clone $expected; $expectedUpper = clone $expected; if ($actual < $expectedLower->sub($delta) || $actual > $expectedUpper->add($delta)) { throw new ComparisonFailure( $expected, $actual, $this->dateTimeToString($expected), $this->dateTimeToString($actual), false, 'Failed asserting that two DateTime objects are equal.' ); } } /** * Returns an ISO 8601 formatted string representation of a datetime or * 'Invalid DateTimeInterface object' if the provided DateTimeInterface was not properly * initialized. * * @param \DateTimeInterface $datetime * @return string */ protected function dateTimeToString($datetime) { $string = $datetime->format(\DateTime::ISO8601); return $string ? $string : 'Invalid DateTimeInterface object'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Comparator; /** * Compares Exception instances for equality. */ class ExceptionComparator extends ObjectComparator { /** * Returns whether the comparator can compare two values. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @return bool */ public function accepts($expected, $actual) { return $expected instanceof \Exception && $actual instanceof \Exception; } /** * Converts an object to an array containing all of its private, protected * and public properties. * * @param object $object * @return array */ protected function toArray($object) { $array = parent::toArray($object); unset( $array['file'], $array['line'], $array['trace'], $array['string'], $array['xdebug_message'] ); return $array; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Comparator; use DOMDocument; use DOMNode; /** * Compares DOMNode instances for equality. */ class DOMNodeComparator extends ObjectComparator { /** * Returns whether the comparator can compare two values. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @return bool */ public function accepts($expected, $actual) { return $expected instanceof DOMNode && $actual instanceof DOMNode; } /** * Asserts that two values are equal. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @param float $delta The allowed numerical distance between two values to * consider them equal * @param bool $canonicalize If set to TRUE, arrays are sorted before * comparison * @param bool $ignoreCase If set to TRUE, upper- and lowercasing is * ignored when comparing string values * @throws ComparisonFailure Thrown when the comparison * fails. Contains information about the * specific errors that lead to the failure. */ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false) { $expectedAsString = $this->nodeToText($expected, true, $ignoreCase); $actualAsString = $this->nodeToText($actual, true, $ignoreCase); if ($expectedAsString !== $actualAsString) { if ($expected instanceof DOMDocument) { $type = 'documents'; } else { $type = 'nodes'; } throw new ComparisonFailure( $expected, $actual, $expectedAsString, $actualAsString, false, sprintf("Failed asserting that two DOM %s are equal.\n", $type) ); } } /** * Returns the normalized, whitespace-cleaned, and indented textual * representation of a DOMNode. * * @param DOMNode $node * @param bool $canonicalize * @param bool $ignoreCase * @return string */ private function nodeToText(DOMNode $node, $canonicalize, $ignoreCase) { if ($canonicalize) { $document = new DOMDocument; $document->loadXML($node->C14N()); $node = $document; } if ($node instanceof DOMDocument) { $document = $node; } else { $document = $node->ownerDocument; } $document->formatOutput = true; $document->normalizeDocument(); if ($node instanceof DOMDocument) { $text = $node->saveXML(); } else { $text = $document->saveXML($node); } if ($ignoreCase) { $text = strtolower($text); } return $text; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Comparator; /** * Compares scalar or NULL values for equality. */ class ScalarComparator extends Comparator { /** * Returns whether the comparator can compare two values. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @return bool * @since Method available since Release 3.6.0 */ public function accepts($expected, $actual) { return ((is_scalar($expected) xor null === $expected) && (is_scalar($actual) xor null === $actual)) // allow comparison between strings and objects featuring __toString() || (is_string($expected) && is_object($actual) && method_exists($actual, '__toString')) || (is_object($expected) && method_exists($expected, '__toString') && is_string($actual)); } /** * Asserts that two values are equal. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @param float $delta The allowed numerical distance between two values to * consider them equal * @param bool $canonicalize If set to TRUE, arrays are sorted before * comparison * @param bool $ignoreCase If set to TRUE, upper- and lowercasing is * ignored when comparing string values * @throws ComparisonFailure Thrown when the comparison * fails. Contains information about the * specific errors that lead to the failure. */ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false) { $expectedToCompare = $expected; $actualToCompare = $actual; // always compare as strings to avoid strange behaviour // otherwise 0 == 'Foobar' if (is_string($expected) || is_string($actual)) { $expectedToCompare = (string) $expectedToCompare; $actualToCompare = (string) $actualToCompare; if ($ignoreCase) { $expectedToCompare = strtolower($expectedToCompare); $actualToCompare = strtolower($actualToCompare); } } if ($expectedToCompare != $actualToCompare) { if (is_string($expected) && is_string($actual)) { throw new ComparisonFailure( $expected, $actual, $this->exporter->export($expected), $this->exporter->export($actual), false, 'Failed asserting that two strings are equal.' ); } throw new ComparisonFailure( $expected, $actual, // no diff is required '', '', false, sprintf( 'Failed asserting that %s matches expected %s.', $this->exporter->export($actual), $this->exporter->export($expected) ) ); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Comparator; /** * Compares PHPUnit_Framework_MockObject_MockObject instances for equality. */ class MockObjectComparator extends ObjectComparator { /** * Returns whether the comparator can compare two values. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @return bool */ public function accepts($expected, $actual) { return $expected instanceof \PHPUnit_Framework_MockObject_MockObject && $actual instanceof \PHPUnit_Framework_MockObject_MockObject; } /** * Converts an object to an array containing all of its private, protected * and public properties. * * @param object $object * @return array */ protected function toArray($object) { $array = parent::toArray($object); unset($array['__phpunit_invocationMocker']); return $array; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Comparator; /** * Compares numerical values for equality. */ class NumericComparator extends ScalarComparator { /** * Returns whether the comparator can compare two values. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @return bool */ public function accepts($expected, $actual) { // all numerical values, but not if one of them is a double // or both of them are strings return is_numeric($expected) && is_numeric($actual) && !(is_double($expected) || is_double($actual)) && !(is_string($expected) && is_string($actual)); } /** * Asserts that two values are equal. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @param float $delta The allowed numerical distance between two values to * consider them equal * @param bool $canonicalize If set to TRUE, arrays are sorted before * comparison * @param bool $ignoreCase If set to TRUE, upper- and lowercasing is * ignored when comparing string values * @throws ComparisonFailure Thrown when the comparison * fails. Contains information about the * specific errors that lead to the failure. */ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false) { if (is_infinite($actual) && is_infinite($expected)) { return; } if ((is_infinite($actual) xor is_infinite($expected)) || (is_nan($actual) or is_nan($expected)) || abs($actual - $expected) > $delta) { throw new ComparisonFailure( $expected, $actual, '', '', false, sprintf( 'Failed asserting that %s matches expected %s.', $this->exporter->export($actual), $this->exporter->export($expected) ) ); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Comparator; /** * Factory for comparators which compare values for equality. */ class Factory { /** * @var Comparator[] */ private $comparators = array(); /** * @var Factory */ private static $instance; /** * Constructs a new factory. */ public function __construct() { $this->register(new TypeComparator); $this->register(new ScalarComparator); $this->register(new NumericComparator); $this->register(new DoubleComparator); $this->register(new ArrayComparator); $this->register(new ResourceComparator); $this->register(new ObjectComparator); $this->register(new ExceptionComparator); $this->register(new SplObjectStorageComparator); $this->register(new DOMNodeComparator); $this->register(new MockObjectComparator); $this->register(new DateTimeComparator); } /** * @return Factory */ public static function getInstance() { if (self::$instance === null) { self::$instance = new self; } return self::$instance; } /** * Returns the correct comparator for comparing two values. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @return Comparator */ public function getComparatorFor($expected, $actual) { foreach ($this->comparators as $comparator) { if ($comparator->accepts($expected, $actual)) { return $comparator; } } } /** * Registers a new comparator. * * This comparator will be returned by getInstance() if its accept() method * returns TRUE for the compared values. It has higher priority than the * existing comparators, meaning that its accept() method will be tested * before those of the other comparators. * * @param Comparator $comparator The registered comparator */ public function register(Comparator $comparator) { array_unshift($this->comparators, $comparator); $comparator->setFactory($this); } /** * Unregisters a comparator. * * This comparator will no longer be returned by getInstance(). * * @param Comparator $comparator The unregistered comparator */ public function unregister(Comparator $comparator) { foreach ($this->comparators as $key => $_comparator) { if ($comparator === $_comparator) { unset($this->comparators[$key]); } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Comparator; /** * Compares doubles for equality. */ class DoubleComparator extends NumericComparator { /** * Smallest value available in PHP. * * @var float */ const EPSILON = 0.0000000001; /** * Returns whether the comparator can compare two values. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @return bool */ public function accepts($expected, $actual) { return (is_double($expected) || is_double($actual)) && is_numeric($expected) && is_numeric($actual); } /** * Asserts that two values are equal. * * @param mixed $expected The first value to compare * @param mixed $actual The second value to compare * @param float $delta The allowed numerical distance between two values to * consider them equal * @param bool $canonicalize If set to TRUE, arrays are sorted before * comparison * @param bool $ignoreCase If set to TRUE, upper- and lowercasing is * ignored when comparing string values * @throws ComparisonFailure Thrown when the comparison * fails. Contains information about the * specific errors that lead to the failure. */ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false) { if ($delta == 0) { $delta = self::EPSILON; } parent::assertEquals($expected, $actual, $delta, $canonicalize, $ignoreCase); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Comparator; use SebastianBergmann\Diff\Differ; /** * Thrown when an assertion for string equality failed. */ class ComparisonFailure extends \RuntimeException { /** * Expected value of the retrieval which does not match $actual. * @var mixed */ protected $expected; /** * Actually retrieved value which does not match $expected. * @var mixed */ protected $actual; /** * The string representation of the expected value * @var string */ protected $expectedAsString; /** * The string representation of the actual value * @var string */ protected $actualAsString; /** * @var bool */ protected $identical; /** * Optional message which is placed in front of the first line * returned by toString(). * @var string */ protected $message; /** * Initialises with the expected value and the actual value. * * @param mixed $expected Expected value retrieved. * @param mixed $actual Actual value retrieved. * @param string $expectedAsString * @param string $actualAsString * @param bool $identical * @param string $message A string which is prefixed on all returned lines * in the difference output. */ public function __construct($expected, $actual, $expectedAsString, $actualAsString, $identical = false, $message = '') { $this->expected = $expected; $this->actual = $actual; $this->expectedAsString = $expectedAsString; $this->actualAsString = $actualAsString; $this->message = $message; } /** * @return mixed */ public function getActual() { return $this->actual; } /** * @return mixed */ public function getExpected() { return $this->expected; } /** * @return string */ public function getActualAsString() { return $this->actualAsString; } /** * @return string */ public function getExpectedAsString() { return $this->expectedAsString; } /** * @return string */ public function getDiff() { if (!$this->actualAsString && !$this->expectedAsString) { return ''; } $differ = new Differ("\n--- Expected\n+++ Actual\n"); return $differ->diff($this->expectedAsString, $this->actualAsString); } /** * @return string */ public function toString() { return $this->message . $this->getDiff(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann; /** * @since Class available since Release 1.0.0 */ class Version { private $path; private $release; private $version; /** * @param string $release * @param string $path */ public function __construct($release, $path) { $this->release = $release; $this->path = $path; } /** * @return string */ public function getVersion() { if ($this->version === null) { if (count(explode('.', $this->release)) == 3) { $this->version = $this->release; } else { $this->version = $this->release . '-dev'; } $git = $this->getGitInformation($this->path); if ($git) { if (count(explode('.', $this->release)) == 3) { $this->version = $git; } else { $git = explode('-', $git); $this->version = $this->release . '-' . end($git); } } } return $this->version; } /** * @param string $path * @return bool|string */ private function getGitInformation($path) { if (!is_dir($path . DIRECTORY_SEPARATOR . '.git')) { return false; } $dir = getcwd(); chdir($path); $returnCode = 1; $result = @exec('git describe --tags 2>&1', $output, $returnCode); chdir($dir); if ($returnCode !== 0) { return false; } return $result; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\FinderFacade; use Symfony\Component\Finder\Finder; /** * Convenience wrapper for Symfony's Finder component. * * @author Sebastian Bergmann * @copyright Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://github.com/sebastianbergmann/finder-facade/tree * @since Class available since Release 1.0.0 */ class FinderFacade { /** * @var array */ protected $items = array(); /** * @var array */ protected $excludes = array(); /** * @var array */ protected $names = array(); /** * @var array */ protected $notNames = array(); /** * @var array */ protected $regularExpressionsExcludes = array(); /** * @param array $items * @param array $excludes * @param array $names * @param array $notNames * @param array $regularExpressionsExcludes */ public function __construct(array $items = array(), array $excludes = array(), array $names = array(), array $notNames = array(), $regularExpressionsExcludes = array()) { $this->items = $items; $this->excludes = $excludes; $this->names = $names; $this->notNames = $notNames; $this->regularExpressionsExcludes = $regularExpressionsExcludes; } /** * @return array */ public function findFiles() { $files = array(); $finder = new Finder; $iterate = false; $finder->ignoreUnreadableDirs(); foreach ($this->items as $item) { if (!is_file($item)) { $finder->in($item); $iterate = true; } else { $files[] = realpath($item); } } foreach ($this->excludes as $exclude) { $finder->exclude($exclude); } foreach ($this->names as $name) { $finder->name($name); } foreach ($this->notNames as $notName) { $finder->notName($notName); } foreach ($this->regularExpressionsExcludes as $regularExpressionExclude) { $finder->notPath($regularExpressionExclude); } if ($iterate) { foreach ($finder as $file) { $files[] = $file->getRealpath(); } } return $files; } /** * @param string $file */ public function loadConfiguration($file) { $configuration = new Configuration($file); $configuration = $configuration->parse(); $this->items = $configuration['items']; $this->excludes = $configuration['excludes']; $this->names = $configuration['names']; $this->notNames = $configuration['notNames']; $this->regularExpressionsExcludes = $configuration['regularExpressionExcludes']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\FinderFacade; use TheSeer\fDOM\fDOMDocument; /** * * * * /path/to/directory * /path/to/file * * /path/to/directory * *.php * * * * @author Sebastian Bergmann * @copyright Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://github.com/sebastianbergmann/finder-facade/tree * @since Class available since Release 1.0.0 */ class Configuration { /** * @var string */ protected $basePath; /** * @var fDOMDocument */ protected $xml; /** * @param string $file */ public function __construct($file) { $this->basePath = dirname($file); $this->xml = new fDOMDocument; $this->xml->load($file); } /** * @param string $xpath * @return array */ public function parse($xpath = '') { $result = array( 'items' => array(), 'excludes' => array(), 'names' => array(), 'notNames' => array(), 'regularExpressionExcludes' => array() ); foreach ($this->xml->getDOMXPath()->query($xpath . 'include/directory') as $item) { $result['items'][] = $this->toAbsolutePath($item->nodeValue); } foreach ($this->xml->getDOMXPath()->query($xpath . 'include/file') as $item) { $result['items'][] = $this->toAbsolutePath($item->nodeValue); } foreach ($this->xml->getDOMXPath()->query($xpath . 'exclude') as $exclude) { $result['excludes'][] = $exclude->nodeValue; } foreach ($this->xml->getDOMXPath()->query($xpath . 'name') as $name) { $result['names'][] = $name->nodeValue; } foreach ($this->xml->getDOMXPath()->query($xpath . 'notName') as $notName) { $result['notNames'][] = $notName->nodeValue; } foreach ($this->xml->getDOMXPath()->query($xpath . 'regularExpressionExcludes') as $regularExpressionExclude) { $result['regularExpressionExcludes'][] = $regularExpressionExclude->nodeValue; } return $result; } /** * @param string $path * @return string */ protected function toAbsolutePath($path) { // Check whether the path is already absolute. if ($path[0] === '/' || $path[0] === '\\' || (strlen($path) > 3 && ctype_alpha($path[0]) && $path[1] === ':' && ($path[2] === '\\' || $path[2] === '/'))) { return $path; } // Check whether a stream is used. if (strpos($path, '://') !== false) { return $path; } return $this->basePath . DIRECTORY_SEPARATOR . $path; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; /** * @package Diff * @author Sebastian Bergmann * @author Kore Nordmann * @copyright Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.github.com/sebastianbergmann/diff */ class Line { const ADDED = 1; const REMOVED = 2; const UNCHANGED = 3; /** * @var int */ private $type; /** * @var string */ private $content; /** * @param int $type * @param string $content */ public function __construct($type = self::UNCHANGED, $content = '') { $this->type = $type; $this->content = $content; } /** * @return string */ public function getContent() { return $this->content; } /** * @return int */ public function getType() { return $this->type; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; use SebastianBergmann\Diff\LCS\LongestCommonSubsequence; use SebastianBergmann\Diff\LCS\TimeEfficientImplementation; use SebastianBergmann\Diff\LCS\MemoryEfficientImplementation; /** * Diff implementation. * * @package Diff * @author Sebastian Bergmann * @author Kore Nordmann * @copyright Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.github.com/sebastianbergmann/diff */ class Differ { /** * @var string */ private $header; /** * @param string $header */ public function __construct($header = "--- Original\n+++ New\n") { $this->header = $header; } /** * Returns the diff between two arrays or strings as string. * * @param array|string $from * @param array|string $to * @param LongestCommonSubsequence $lcs * @return string */ public function diff($from, $to, LongestCommonSubsequence $lcs = null) { if (!is_array($from) && !is_string($from)) { $from = (string) $from; } if (!is_array($to) && !is_string($to)) { $to = (string) $to; } $buffer = $this->header; $diff = $this->diffToArray($from, $to, $lcs); $inOld = false; $i = 0; $old = array(); foreach ($diff as $line) { if ($line[1] === 0 /* OLD */) { if ($inOld === false) { $inOld = $i; } } elseif ($inOld !== false) { if (($i - $inOld) > 5) { $old[$inOld] = $i - 1; } $inOld = false; } ++$i; } $start = isset($old[0]) ? $old[0] : 0; $end = count($diff); if ($tmp = array_search($end, $old)) { $end = $tmp; } $newChunk = true; for ($i = $start; $i < $end; $i++) { if (isset($old[$i])) { $buffer .= "\n"; $newChunk = true; $i = $old[$i]; } if ($newChunk) { $buffer .= "@@ @@\n"; $newChunk = false; } if ($diff[$i][1] === 1 /* ADDED */) { $buffer .= '+' . $diff[$i][0] . "\n"; } elseif ($diff[$i][1] === 2 /* REMOVED */) { $buffer .= '-' . $diff[$i][0] . "\n"; } else { $buffer .= ' ' . $diff[$i][0] . "\n"; } } return $buffer; } /** * Returns the diff between two arrays or strings as array. * * Each array element contains two elements: * - [0] => string $token * - [1] => 2|1|0 * * - 2: REMOVED: $token was removed from $from * - 1: ADDED: $token was added to $from * - 0: OLD: $token is not changed in $to * * @param array|string $from * @param array|string $to * @param LongestCommonSubsequence $lcs * @return array */ public function diffToArray($from, $to, LongestCommonSubsequence $lcs = null) { preg_match_all('(\r\n|\r|\n)', $from, $fromMatches); preg_match_all('(\r\n|\r|\n)', $to, $toMatches); if (is_string($from)) { $from = preg_split('(\r\n|\r|\n)', $from); } if (is_string($to)) { $to = preg_split('(\r\n|\r|\n)', $to); } $start = array(); $end = array(); $fromLength = count($from); $toLength = count($to); $length = min($fromLength, $toLength); for ($i = 0; $i < $length; ++$i) { if ($from[$i] === $to[$i]) { $start[] = $from[$i]; unset($from[$i], $to[$i]); } else { break; } } $length -= $i; for ($i = 1; $i < $length; ++$i) { if ($from[$fromLength - $i] === $to[$toLength - $i]) { array_unshift($end, $from[$fromLength - $i]); unset($from[$fromLength - $i], $to[$toLength - $i]); } else { break; } } if ($lcs === null) { $lcs = $this->selectLcsImplementation($from, $to); } $common = $lcs->calculate(array_values($from), array_values($to)); $diff = array(); if (isset($fromMatches[0]) && $toMatches[0] && count($fromMatches[0]) === count($toMatches[0]) && $fromMatches[0] !== $toMatches[0]) { $diff[] = array( '#Warning: Strings contain different line endings!', 0 ); } foreach ($start as $token) { $diff[] = array($token, 0 /* OLD */); } reset($from); reset($to); foreach ($common as $token) { while ((($fromToken = reset($from)) !== $token)) { $diff[] = array(array_shift($from), 2 /* REMOVED */); } while ((($toToken = reset($to)) !== $token)) { $diff[] = array(array_shift($to), 1 /* ADDED */); } $diff[] = array($token, 0 /* OLD */); array_shift($from); array_shift($to); } while (($token = array_shift($from)) !== null) { $diff[] = array($token, 2 /* REMOVED */); } while (($token = array_shift($to)) !== null) { $diff[] = array($token, 1 /* ADDED */); } foreach ($end as $token) { $diff[] = array($token, 0 /* OLD */); } return $diff; } /** * @param array $from * @param array $to * @return LongestCommonSubsequence */ private function selectLcsImplementation(array $from, array $to) { // We do not want to use the time-efficient implementation if its memory // footprint will probably exceed this value. Note that the footprint // calculation is only an estimation for the matrix and the LCS method // will typically allocate a bit more memory than this. $memoryLimit = 100 * 1024 * 1024; if ($this->calculateEstimatedFootprint($from, $to) > $memoryLimit) { return new MemoryEfficientImplementation; } return new TimeEfficientImplementation; } /** * Calculates the estimated memory footprint for the DP-based method. * * @param array $from * @param array $to * @return integer */ private function calculateEstimatedFootprint(array $from, array $to) { $itemSize = PHP_INT_SIZE == 4 ? 76 : 144; return $itemSize * pow(min(count($from), count($to)), 2); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; /** * Unified diff parser. * * @package Diff * @author Sebastian Bergmann * @author Kore Nordmann * @copyright Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.github.com/sebastianbergmann/diff */ class Parser { /** * @param string $string * @return Diff[] */ public function parse($string) { $lines = preg_split('(\r\n|\r|\n)', $string); $lineCount = count($lines); $diffs = array(); $diff = null; $collected = array(); for ($i = 0; $i < $lineCount; ++$i) { if (preg_match('(^---\\s+(?P\\S+))', $lines[$i], $fromMatch) && preg_match('(^\\+\\+\\+\\s+(?P\\S+))', $lines[$i + 1], $toMatch)) { if ($diff !== null) { $this->parseFileDiff($diff, $collected); $diffs[] = $diff; $collected = array(); } $diff = new Diff($fromMatch['file'], $toMatch['file']); ++$i; } else { if (preg_match('/^(?:diff --git |index [\da-f\.]+|[+-]{3} [ab])/', $lines[$i])) { continue; } $collected[] = $lines[$i]; } } if (count($collected) && ($diff !== null)) { $this->parseFileDiff($diff, $collected); $diffs[] = $diff; } return $diffs; } /** * @param Diff $diff * @param array $lines */ private function parseFileDiff(Diff $diff, array $lines) { $chunks = array(); foreach ($lines as $line) { if (preg_match('/^@@\s+-(?P\d+)(?:,\s*(?P\d+))?\s+\+(?P\d+)(?:,\s*(?P\d+))?\s+@@/', $line, $match)) { $chunk = new Chunk( $match['start'], isset($match['startrange']) ? max(1, $match['startrange']) : 1, $match['end'], isset($match['endrange']) ? max(1, $match['endrange']) : 1 ); $chunks[] = $chunk; $diffLines = array(); continue; } if (preg_match('/^(?P[+ -])?(?P.*)/', $line, $match)) { $type = Line::UNCHANGED; if ($match['type'] == '+') { $type = Line::ADDED; } elseif ($match['type'] == '-') { $type = Line::REMOVED; } $diffLines[] = new Line($type, $match['line']); if (isset($chunk)) { $chunk->setLines($diffLines); } } } $diff->setChunks($chunks); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; /** * @package Diff * @author Sebastian Bergmann * @author Kore Nordmann * @copyright Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.github.com/sebastianbergmann/diff */ class Diff { /** * @var string */ private $from; /** * @var string */ private $to; /** * @var Chunk[] */ private $chunks; /** * @param string $from * @param string $to * @param Chunk[] $chunks */ public function __construct($from, $to, array $chunks = array()) { $this->from = $from; $this->to = $to; $this->chunks = $chunks; } /** * @return string */ public function getFrom() { return $this->from; } /** * @return string */ public function getTo() { return $this->to; } /** * @return Chunk[] */ public function getChunks() { return $this->chunks; } /** * @param Chunk[] $chunks */ public function setChunks(array $chunks) { $this->chunks = $chunks; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; /** * @package Diff * @author Sebastian Bergmann * @author Kore Nordmann * @copyright Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.github.com/sebastianbergmann/diff */ class Chunk { /** * @var int */ private $start; /** * @var int */ private $startRange; /** * @var int */ private $end; /** * @var int */ private $endRange; /** * @var array */ private $lines; /** * @param int $start * @param int $startRange * @param int $end * @param int $endRange * @param array $lines */ public function __construct($start = 0, $startRange = 1, $end = 0, $endRange = 1, array $lines = array()) { $this->start = (int) $start; $this->startRange = (int) $startRange; $this->end = (int) $end; $this->endRange = (int) $endRange; $this->lines = $lines; } /** * @return int */ public function getStart() { return $this->start; } /** * @return int */ public function getStartRange() { return $this->startRange; } /** * @return int */ public function getEnd() { return $this->end; } /** * @return int */ public function getEndRange() { return $this->endRange; } /** * @return array */ public function getLines() { return $this->lines; } /** * @param array $lines */ public function setLines(array $lines) { $this->lines = $lines; } } phpunit/phpcov: 2.0.2 doctrine/instantiator: 1.0.5 phpdocumentor/reflection-docblock: 2.0.4 phpspec/prophecy: v1.5.0 phpunit/php-code-coverage: 2.2.3 phpunit/php-file-iterator: 1.4.1 phpunit/php-text-template: 1.2.1 phpunit/php-timer: 1.0.7 phpunit/php-token-stream: 1.4.8 phpunit/phpunit: 4.8.10 phpunit/phpunit-mock-objects: 2.3.8 sebastian/comparator: 1.2.0 sebastian/diff: 1.3.0 sebastian/environment: 1.3.2 sebastian/exporter: 1.2.1 sebastian/finder-facade: 1.2.0 sebastian/global-state: 1.0.0 sebastian/recursion-context: 1.0.1 sebastian/version: 1.0.6 symfony/console: v2.7.5 symfony/finder: v2.7.5 symfony/yaml: v2.7.5 theseer/fdomdocument: 1.6.1 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Shell; /** * @author Jean-François Simon */ class Shell { const TYPE_UNIX = 1; const TYPE_DARWIN = 2; const TYPE_CYGWIN = 3; const TYPE_WINDOWS = 4; const TYPE_BSD = 5; /** * @var string|null */ private $type; /** * Returns guessed OS type. * * @return int */ public function getType() { if (null === $this->type) { $this->type = $this->guessType(); } return $this->type; } /** * Tests if a command is available. * * @param string $command * * @return bool */ public function testCommand($command) { if (!function_exists('exec')) { return false; } // todo: find a better way (command could not be available) $testCommand = 'which '; if (self::TYPE_WINDOWS === $this->type) { $testCommand = 'where '; } $command = escapeshellcmd($command); exec($testCommand.$command, $output, $code); return 0 === $code && count($output) > 0; } /** * Guesses OS type. * * @return int */ private function guessType() { $os = strtolower(PHP_OS); if (false !== strpos($os, 'cygwin')) { return self::TYPE_CYGWIN; } if (false !== strpos($os, 'darwin')) { return self::TYPE_DARWIN; } if (false !== strpos($os, 'bsd')) { return self::TYPE_BSD; } if (0 === strpos($os, 'win')) { return self::TYPE_WINDOWS; } return self::TYPE_UNIX; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Shell; /** * @author Jean-François Simon */ class Command { /** * @var Command|null */ private $parent; /** * @var array */ private $bits = array(); /** * @var array */ private $labels = array(); /** * @var \Closure|null */ private $errorHandler; /** * Constructor. * * @param Command|null $parent Parent command */ public function __construct(Command $parent = null) { $this->parent = $parent; } /** * Returns command as string. * * @return string */ public function __toString() { return $this->join(); } /** * Creates a new Command instance. * * @param Command|null $parent Parent command * * @return Command New Command instance */ public static function create(Command $parent = null) { return new self($parent); } /** * Escapes special chars from input. * * @param string $input A string to escape * * @return string The escaped string */ public static function escape($input) { return escapeshellcmd($input); } /** * Quotes input. * * @param string $input An argument string * * @return string The quoted string */ public static function quote($input) { return escapeshellarg($input); } /** * Appends a string or a Command instance. * * @param string|Command $bit * * @return Command The current Command instance */ public function add($bit) { $this->bits[] = $bit; return $this; } /** * Prepends a string or a command instance. * * @param string|Command $bit * * @return Command The current Command instance */ public function top($bit) { array_unshift($this->bits, $bit); foreach ($this->labels as $label => $index) { $this->labels[$label] += 1; } return $this; } /** * Appends an argument, will be quoted. * * @param string $arg * * @return Command The current Command instance */ public function arg($arg) { $this->bits[] = self::quote($arg); return $this; } /** * Appends escaped special command chars. * * @param string $esc * * @return Command The current Command instance */ public function cmd($esc) { $this->bits[] = self::escape($esc); return $this; } /** * Inserts a labeled command to feed later. * * @param string $label The unique label * * @return Command The current Command instance * * @throws \RuntimeException If label already exists */ public function ins($label) { if (isset($this->labels[$label])) { throw new \RuntimeException(sprintf('Label "%s" already exists.', $label)); } $this->bits[] = self::create($this); $this->labels[$label] = count($this->bits) - 1; return $this->bits[$this->labels[$label]]; } /** * Retrieves a previously labeled command. * * @param string $label * * @return Command The labeled command * * @throws \RuntimeException */ public function get($label) { if (!isset($this->labels[$label])) { throw new \RuntimeException(sprintf('Label "%s" does not exist.', $label)); } return $this->bits[$this->labels[$label]]; } /** * Returns parent command (if any). * * @return Command Parent command * * @throws \RuntimeException If command has no parent */ public function end() { if (null === $this->parent) { throw new \RuntimeException('Calling end on root command doesn\'t make sense.'); } return $this->parent; } /** * Counts bits stored in command. * * @return int The bits count */ public function length() { return count($this->bits); } /** * @param \Closure $errorHandler * * @return Command */ public function setErrorHandler(\Closure $errorHandler) { $this->errorHandler = $errorHandler; return $this; } /** * @return \Closure|null */ public function getErrorHandler() { return $this->errorHandler; } /** * Executes current command. * * @return array The command result * * @throws \RuntimeException */ public function execute() { if (null === $errorHandler = $this->errorHandler) { exec($this->join(), $output); } else { $process = proc_open($this->join(), array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $pipes); $output = preg_split('~(\r\n|\r|\n)~', stream_get_contents($pipes[1]), -1, PREG_SPLIT_NO_EMPTY); if ($error = stream_get_contents($pipes[2])) { $errorHandler($error); } proc_close($process); } return $output ?: array(); } /** * Joins bits. * * @return string */ public function join() { return implode(' ', array_filter( array_map(function ($bit) { return $bit instanceof Command ? $bit->join() : ($bit ?: null); }, $this->bits), function ($bit) { return null !== $bit; } )); } /** * Insert a string or a Command instance before the bit at given position $index (index starts from 0). * * @param string|Command $bit * @param int $index * * @return Command The current Command instance */ public function addAtIndex($bit, $index) { array_splice($this->bits, $index, 0, $bit instanceof self ? array($bit) : $bit); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Adapter; /** * Interface for finder engine implementations. * * @author Jean-François Simon */ abstract class AbstractAdapter implements AdapterInterface { protected $followLinks = false; protected $mode = 0; protected $minDepth = 0; protected $maxDepth = PHP_INT_MAX; protected $exclude = array(); protected $names = array(); protected $notNames = array(); protected $contains = array(); protected $notContains = array(); protected $sizes = array(); protected $dates = array(); protected $filters = array(); protected $sort = false; protected $paths = array(); protected $notPaths = array(); protected $ignoreUnreadableDirs = false; private static $areSupported = array(); /** * {@inheritdoc} */ public function isSupported() { $name = $this->getName(); if (!array_key_exists($name, self::$areSupported)) { self::$areSupported[$name] = $this->canBeUsed(); } return self::$areSupported[$name]; } /** * {@inheritdoc} */ public function setFollowLinks($followLinks) { $this->followLinks = $followLinks; return $this; } /** * {@inheritdoc} */ public function setMode($mode) { $this->mode = $mode; return $this; } /** * {@inheritdoc} */ public function setDepths(array $depths) { $this->minDepth = 0; $this->maxDepth = PHP_INT_MAX; foreach ($depths as $comparator) { switch ($comparator->getOperator()) { case '>': $this->minDepth = $comparator->getTarget() + 1; break; case '>=': $this->minDepth = $comparator->getTarget(); break; case '<': $this->maxDepth = $comparator->getTarget() - 1; break; case '<=': $this->maxDepth = $comparator->getTarget(); break; default: $this->minDepth = $this->maxDepth = $comparator->getTarget(); } } return $this; } /** * {@inheritdoc} */ public function setExclude(array $exclude) { $this->exclude = $exclude; return $this; } /** * {@inheritdoc} */ public function setNames(array $names) { $this->names = $names; return $this; } /** * {@inheritdoc} */ public function setNotNames(array $notNames) { $this->notNames = $notNames; return $this; } /** * {@inheritdoc} */ public function setContains(array $contains) { $this->contains = $contains; return $this; } /** * {@inheritdoc} */ public function setNotContains(array $notContains) { $this->notContains = $notContains; return $this; } /** * {@inheritdoc} */ public function setSizes(array $sizes) { $this->sizes = $sizes; return $this; } /** * {@inheritdoc} */ public function setDates(array $dates) { $this->dates = $dates; return $this; } /** * {@inheritdoc} */ public function setFilters(array $filters) { $this->filters = $filters; return $this; } /** * {@inheritdoc} */ public function setSort($sort) { $this->sort = $sort; return $this; } /** * {@inheritdoc} */ public function setPath(array $paths) { $this->paths = $paths; return $this; } /** * {@inheritdoc} */ public function setNotPath(array $notPaths) { $this->notPaths = $notPaths; return $this; } /** * {@inheritdoc} */ public function ignoreUnreadableDirs($ignore = true) { $this->ignoreUnreadableDirs = (bool) $ignore; return $this; } /** * Returns whether the adapter is supported in the current environment. * * This method should be implemented in all adapters. Do not implement * isSupported in the adapters as the generic implementation provides a cache * layer. * * @see isSupported() * * @return bool Whether the adapter is supported */ abstract protected function canBeUsed(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Adapter; /** * @author Jean-François Simon */ interface AdapterInterface { /** * @param bool $followLinks * * @return AdapterInterface Current instance */ public function setFollowLinks($followLinks); /** * @param int $mode * * @return AdapterInterface Current instance */ public function setMode($mode); /** * @param array $exclude * * @return AdapterInterface Current instance */ public function setExclude(array $exclude); /** * @param array $depths * * @return AdapterInterface Current instance */ public function setDepths(array $depths); /** * @param array $names * * @return AdapterInterface Current instance */ public function setNames(array $names); /** * @param array $notNames * * @return AdapterInterface Current instance */ public function setNotNames(array $notNames); /** * @param array $contains * * @return AdapterInterface Current instance */ public function setContains(array $contains); /** * @param array $notContains * * @return AdapterInterface Current instance */ public function setNotContains(array $notContains); /** * @param array $sizes * * @return AdapterInterface Current instance */ public function setSizes(array $sizes); /** * @param array $dates * * @return AdapterInterface Current instance */ public function setDates(array $dates); /** * @param array $filters * * @return AdapterInterface Current instance */ public function setFilters(array $filters); /** * @param \Closure|int $sort * * @return AdapterInterface Current instance */ public function setSort($sort); /** * @param array $paths * * @return AdapterInterface Current instance */ public function setPath(array $paths); /** * @param array $notPaths * * @return AdapterInterface Current instance */ public function setNotPath(array $notPaths); /** * @param bool $ignore * * @return AdapterInterface Current instance */ public function ignoreUnreadableDirs($ignore = true); /** * @param string $dir * * @return \Iterator Result iterator */ public function searchInDirectory($dir); /** * Tests adapter support for current platform. * * @return bool */ public function isSupported(); /** * Returns adapter name. * * @return string */ public function getName(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Adapter; use Symfony\Component\Finder\Iterator; /** * PHP finder engine implementation. * * @author Jean-François Simon */ class PhpAdapter extends AbstractAdapter { /** * {@inheritdoc} */ public function searchInDirectory($dir) { $flags = \RecursiveDirectoryIterator::SKIP_DOTS; if ($this->followLinks) { $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS; } $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs); if ($this->exclude) { $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); } $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); if ($this->minDepth > 0 || $this->maxDepth < PHP_INT_MAX) { $iterator = new Iterator\DepthRangeFilterIterator($iterator, $this->minDepth, $this->maxDepth); } if ($this->mode) { $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode); } if ($this->names || $this->notNames) { $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); } if ($this->contains || $this->notContains) { $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); } if ($this->sizes) { $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); } if ($this->dates) { $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); } if ($this->filters) { $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); } if ($this->paths || $this->notPaths) { $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $this->notPaths); } if ($this->sort) { $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort); $iterator = $iteratorAggregate->getIterator(); } return $iterator; } /** * {@inheritdoc} */ public function getName() { return 'php'; } /** * {@inheritdoc} */ protected function canBeUsed() { return true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Adapter; use Symfony\Component\Finder\Exception\AccessDeniedException; use Symfony\Component\Finder\Iterator; use Symfony\Component\Finder\Shell\Shell; use Symfony\Component\Finder\Expression\Expression; use Symfony\Component\Finder\Shell\Command; use Symfony\Component\Finder\Comparator\NumberComparator; use Symfony\Component\Finder\Comparator\DateComparator; /** * Shell engine implementation using GNU find command. * * @author Jean-François Simon */ abstract class AbstractFindAdapter extends AbstractAdapter { /** * @var Shell */ protected $shell; /** * Constructor. */ public function __construct() { $this->shell = new Shell(); } /** * {@inheritdoc} */ public function searchInDirectory($dir) { // having "/../" in path make find fail $dir = realpath($dir); // searching directories containing or not containing strings leads to no result if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode && ($this->contains || $this->notContains)) { return new Iterator\FilePathsIterator(array(), $dir); } $command = Command::create(); $find = $this->buildFindCommand($command, $dir); if ($this->followLinks) { $find->add('-follow'); } $find->add('-mindepth')->add($this->minDepth + 1); if (PHP_INT_MAX !== $this->maxDepth) { $find->add('-maxdepth')->add($this->maxDepth + 1); } if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode) { $find->add('-type d'); } elseif (Iterator\FileTypeFilterIterator::ONLY_FILES === $this->mode) { $find->add('-type f'); } $this->buildNamesFiltering($find, $this->names); $this->buildNamesFiltering($find, $this->notNames, true); $this->buildPathsFiltering($find, $dir, $this->paths); $this->buildPathsFiltering($find, $dir, $this->notPaths, true); $this->buildSizesFiltering($find, $this->sizes); $this->buildDatesFiltering($find, $this->dates); $useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs'); $useSort = is_int($this->sort) && $this->shell->testCommand('sort') && $this->shell->testCommand('cut'); if ($useGrep && ($this->contains || $this->notContains)) { $grep = $command->ins('grep'); $this->buildContentFiltering($grep, $this->contains); $this->buildContentFiltering($grep, $this->notContains, true); } if ($useSort) { $this->buildSorting($command, $this->sort); } $command->setErrorHandler( $this->ignoreUnreadableDirs // If directory is unreadable and finder is set to ignore it, `stderr` is ignored. ? function ($stderr) { return; } : function ($stderr) { throw new AccessDeniedException($stderr); } ); $paths = $this->shell->testCommand('uniq') ? $command->add('| uniq')->execute() : array_unique($command->execute()); $iterator = new Iterator\FilePathsIterator($paths, $dir); if ($this->exclude) { $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); } if (!$useGrep && ($this->contains || $this->notContains)) { $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); } if ($this->filters) { $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); } if (!$useSort && $this->sort) { $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort); $iterator = $iteratorAggregate->getIterator(); } return $iterator; } /** * {@inheritdoc} */ protected function canBeUsed() { return $this->shell->testCommand('find'); } /** * @param Command $command * @param string $dir * * @return Command */ protected function buildFindCommand(Command $command, $dir) { return $command ->ins('find') ->add('find ') ->arg($dir) ->add('-noleaf'); // the -noleaf option is required for filesystems that don't follow the '.' and '..' conventions } /** * @param Command $command * @param string[] $names * @param bool $not */ private function buildNamesFiltering(Command $command, array $names, $not = false) { if (0 === count($names)) { return; } $command->add($not ? '-not' : null)->cmd('('); foreach ($names as $i => $name) { $expr = Expression::create($name); // Find does not support expandable globs ("*.{a,b}" syntax). if ($expr->isGlob() && $expr->getGlob()->isExpandable()) { $expr = Expression::create($expr->getGlob()->toRegex(false)); } // Fixes 'not search' and 'full path matching' regex problems. // - Jokers '.' are replaced by [^/]. // - We add '[^/]*' before and after regex (if no ^|$ flags are present). if ($expr->isRegex()) { $regex = $expr->getRegex(); $regex->prepend($regex->hasStartFlag() ? '/' : '/[^/]*') ->setStartFlag(false) ->setStartJoker(true) ->replaceJokers('[^/]'); if (!$regex->hasEndFlag() || $regex->hasEndJoker()) { $regex->setEndJoker(false)->append('[^/]*'); } } $command ->add($i > 0 ? '-or' : null) ->add($expr->isRegex() ? ($expr->isCaseSensitive() ? '-regex' : '-iregex') : ($expr->isCaseSensitive() ? '-name' : '-iname') ) ->arg($expr->renderPattern()); } $command->cmd(')'); } /** * @param Command $command * @param string $dir * @param string[] $paths * @param bool $not */ private function buildPathsFiltering(Command $command, $dir, array $paths, $not = false) { if (0 === count($paths)) { return; } $command->add($not ? '-not' : null)->cmd('('); foreach ($paths as $i => $path) { $expr = Expression::create($path); // Find does not support expandable globs ("*.{a,b}" syntax). if ($expr->isGlob() && $expr->getGlob()->isExpandable()) { $expr = Expression::create($expr->getGlob()->toRegex(false)); } // Fixes 'not search' regex problems. if ($expr->isRegex()) { $regex = $expr->getRegex(); $regex->prepend($regex->hasStartFlag() ? preg_quote($dir).DIRECTORY_SEPARATOR : '.*')->setEndJoker(!$regex->hasEndFlag()); } else { $expr->prepend('*')->append('*'); } $command ->add($i > 0 ? '-or' : null) ->add($expr->isRegex() ? ($expr->isCaseSensitive() ? '-regex' : '-iregex') : ($expr->isCaseSensitive() ? '-path' : '-ipath') ) ->arg($expr->renderPattern()); } $command->cmd(')'); } /** * @param Command $command * @param NumberComparator[] $sizes */ private function buildSizesFiltering(Command $command, array $sizes) { foreach ($sizes as $i => $size) { $command->add($i > 0 ? '-and' : null); switch ($size->getOperator()) { case '<=': $command->add('-size -'.($size->getTarget() + 1).'c'); break; case '>=': $command->add('-size +'.($size->getTarget() - 1).'c'); break; case '>': $command->add('-size +'.$size->getTarget().'c'); break; case '!=': $command->add('-size -'.$size->getTarget().'c'); $command->add('-size +'.$size->getTarget().'c'); break; case '<': default: $command->add('-size -'.$size->getTarget().'c'); } } } /** * @param Command $command * @param DateComparator[] $dates */ private function buildDatesFiltering(Command $command, array $dates) { foreach ($dates as $i => $date) { $command->add($i > 0 ? '-and' : null); $mins = (int) round((time() - $date->getTarget()) / 60); if (0 > $mins) { // mtime is in the future $command->add(' -mmin -0'); // we will have no result so we don't need to continue return; } switch ($date->getOperator()) { case '<=': $command->add('-mmin +'.($mins - 1)); break; case '>=': $command->add('-mmin -'.($mins + 1)); break; case '>': $command->add('-mmin -'.$mins); break; case '!=': $command->add('-mmin +'.$mins.' -or -mmin -'.$mins); break; case '<': default: $command->add('-mmin +'.$mins); } } } /** * @param Command $command * @param string $sort * * @throws \InvalidArgumentException */ private function buildSorting(Command $command, $sort) { $this->buildFormatSorting($command, $sort); } /** * @param Command $command * @param string $sort */ abstract protected function buildFormatSorting(Command $command, $sort); /** * @param Command $command * @param array $contains * @param bool $not */ abstract protected function buildContentFiltering(Command $command, array $contains, $not = false); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Adapter; use Symfony\Component\Finder\Shell\Shell; use Symfony\Component\Finder\Shell\Command; use Symfony\Component\Finder\Iterator\SortableIterator; use Symfony\Component\Finder\Expression\Expression; /** * Shell engine implementation using BSD find command. * * @author Jean-François Simon */ class BsdFindAdapter extends AbstractFindAdapter { /** * {@inheritdoc} */ public function getName() { return 'bsd_find'; } /** * {@inheritdoc} */ protected function canBeUsed() { return in_array($this->shell->getType(), array(Shell::TYPE_BSD, Shell::TYPE_DARWIN)) && parent::canBeUsed(); } /** * {@inheritdoc} */ protected function buildFormatSorting(Command $command, $sort) { switch ($sort) { case SortableIterator::SORT_BY_NAME: $command->ins('sort')->add('| sort'); return; case SortableIterator::SORT_BY_TYPE: $format = '%HT'; break; case SortableIterator::SORT_BY_ACCESSED_TIME: $format = '%a'; break; case SortableIterator::SORT_BY_CHANGED_TIME: $format = '%c'; break; case SortableIterator::SORT_BY_MODIFIED_TIME: $format = '%m'; break; default: throw new \InvalidArgumentException(sprintf('Unknown sort options: %s.', $sort)); } $command ->add('-print0 | xargs -0 stat -f') ->arg($format.'%t%N') ->add('| sort | cut -f 2'); } /** * {@inheritdoc} */ protected function buildFindCommand(Command $command, $dir) { parent::buildFindCommand($command, $dir)->addAtIndex('-E', 1); return $command; } /** * {@inheritdoc} */ protected function buildContentFiltering(Command $command, array $contains, $not = false) { foreach ($contains as $contain) { $expr = Expression::create($contain); // todo: avoid forking process for each $pattern by using multiple -e options $command ->add('| grep -v \'^$\'') ->add('| xargs -I{} grep -I') ->add($expr->isCaseSensitive() ? null : '-i') ->add($not ? '-L' : '-l') ->add('-Ee')->arg($expr->renderPattern()) ->add('{}') ; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Adapter; use Symfony\Component\Finder\Shell\Shell; use Symfony\Component\Finder\Shell\Command; use Symfony\Component\Finder\Iterator\SortableIterator; use Symfony\Component\Finder\Expression\Expression; /** * Shell engine implementation using GNU find command. * * @author Jean-François Simon */ class GnuFindAdapter extends AbstractFindAdapter { /** * {@inheritdoc} */ public function getName() { return 'gnu_find'; } /** * {@inheritdoc} */ protected function buildFormatSorting(Command $command, $sort) { switch ($sort) { case SortableIterator::SORT_BY_NAME: $command->ins('sort')->add('| sort'); return; case SortableIterator::SORT_BY_TYPE: $format = '%y'; break; case SortableIterator::SORT_BY_ACCESSED_TIME: $format = '%A@'; break; case SortableIterator::SORT_BY_CHANGED_TIME: $format = '%C@'; break; case SortableIterator::SORT_BY_MODIFIED_TIME: $format = '%T@'; break; default: throw new \InvalidArgumentException(sprintf('Unknown sort options: %s.', $sort)); } $command ->get('find') ->add('-printf') ->arg($format.' %h/%f\\n') ->add('| sort | cut') ->arg('-d ') ->arg('-f2-') ; } /** * {@inheritdoc} */ protected function canBeUsed() { return $this->shell->getType() === Shell::TYPE_UNIX && parent::canBeUsed(); } /** * {@inheritdoc} */ protected function buildFindCommand(Command $command, $dir) { return parent::buildFindCommand($command, $dir)->add('-regextype posix-extended'); } /** * {@inheritdoc} */ protected function buildContentFiltering(Command $command, array $contains, $not = false) { foreach ($contains as $contain) { $expr = Expression::create($contain); // todo: avoid forking process for each $pattern by using multiple -e options $command ->add('| xargs -I{} -r grep -I') ->add($expr->isCaseSensitive() ? null : '-i') ->add($not ? '-L' : '-l') ->add('-Ee')->arg($expr->renderPattern()) ->add('{}') ; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Comparator; /** * NumberComparator compiles a simple comparison to an anonymous * subroutine, which you can call with a value to be tested again. * * Now this would be very pointless, if NumberCompare didn't understand * magnitudes. * * The target value may use magnitudes of kilobytes (k, ki), * megabytes (m, mi), or gigabytes (g, gi). Those suffixed * with an i use the appropriate 2**n version in accordance with the * IEC standard: http://physics.nist.gov/cuu/Units/binary.html * * Based on the Perl Number::Compare module. * * @author Fabien Potencier PHP port * @author Richard Clamp Perl version * @copyright 2004-2005 Fabien Potencier * @copyright 2002 Richard Clamp * * @see http://physics.nist.gov/cuu/Units/binary.html */ class NumberComparator extends Comparator { /** * Constructor. * * @param string $test A comparison string * * @throws \InvalidArgumentException If the test is not understood */ public function __construct($test) { if (!preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) { throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test)); } $target = $matches[2]; if (!is_numeric($target)) { throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target)); } if (isset($matches[3])) { // magnitude switch (strtolower($matches[3])) { case 'k': $target *= 1000; break; case 'ki': $target *= 1024; break; case 'm': $target *= 1000000; break; case 'mi': $target *= 1024 * 1024; break; case 'g': $target *= 1000000000; break; case 'gi': $target *= 1024 * 1024 * 1024; break; } } $this->setTarget($target); $this->setOperator(isset($matches[1]) ? $matches[1] : '=='); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Comparator; /** * Comparator. * * @author Fabien Potencier */ class Comparator { private $target; private $operator = '=='; /** * Gets the target value. * * @return string The target value */ public function getTarget() { return $this->target; } /** * Sets the target value. * * @param string $target The target value */ public function setTarget($target) { $this->target = $target; } /** * Gets the comparison operator. * * @return string The operator */ public function getOperator() { return $this->operator; } /** * Sets the comparison operator. * * @param string $operator A valid operator * * @throws \InvalidArgumentException */ public function setOperator($operator) { if (!$operator) { $operator = '=='; } if (!in_array($operator, array('>', '<', '>=', '<=', '==', '!='))) { throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); } $this->operator = $operator; } /** * Tests against the target. * * @param mixed $test A test value * * @return bool */ public function test($test) { switch ($this->operator) { case '>': return $test > $this->target; case '>=': return $test >= $this->target; case '<': return $test < $this->target; case '<=': return $test <= $this->target; case '!=': return $test != $this->target; } return $test == $this->target; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Comparator; /** * DateCompare compiles date comparisons. * * @author Fabien Potencier */ class DateComparator extends Comparator { /** * Constructor. * * @param string $test A comparison string * * @throws \InvalidArgumentException If the test is not understood */ public function __construct($test) { if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) { throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test)); } try { $date = new \DateTime($matches[2]); $target = $date->format('U'); } catch (\Exception $e) { throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2])); } $operator = isset($matches[1]) ? $matches[1] : '=='; if ('since' === $operator || 'after' === $operator) { $operator = '>'; } if ('until' === $operator || 'before' === $operator) { $operator = '<'; } $this->setOperator($operator); $this->setTarget($target); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; /** * SortableIterator applies a sort on a given Iterator. * * @author Fabien Potencier */ class SortableIterator implements \IteratorAggregate { const SORT_BY_NAME = 1; const SORT_BY_TYPE = 2; const SORT_BY_ACCESSED_TIME = 3; const SORT_BY_CHANGED_TIME = 4; const SORT_BY_MODIFIED_TIME = 5; private $iterator; private $sort; /** * Constructor. * * @param \Traversable $iterator The Iterator to filter * @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback) * * @throws \InvalidArgumentException */ public function __construct(\Traversable $iterator, $sort) { $this->iterator = $iterator; if (self::SORT_BY_NAME === $sort) { $this->sort = function ($a, $b) { return strcmp($a->getRealpath(), $b->getRealpath()); }; } elseif (self::SORT_BY_TYPE === $sort) { $this->sort = function ($a, $b) { if ($a->isDir() && $b->isFile()) { return -1; } elseif ($a->isFile() && $b->isDir()) { return 1; } return strcmp($a->getRealpath(), $b->getRealpath()); }; } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { $this->sort = function ($a, $b) { return ($a->getATime() - $b->getATime()); }; } elseif (self::SORT_BY_CHANGED_TIME === $sort) { $this->sort = function ($a, $b) { return ($a->getCTime() - $b->getCTime()); }; } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { $this->sort = function ($a, $b) { return ($a->getMTime() - $b->getMTime()); }; } elseif (is_callable($sort)) { $this->sort = $sort; } else { throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); } } public function getIterator() { $array = iterator_to_array($this->iterator, true); uasort($array, $this->sort); return new \ArrayIterator($array); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; /** * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings). * * @author Fabien Potencier * @author Włodzimierz Gajda */ class FilecontentFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. * * @return bool true if the value should be kept, false otherwise */ public function accept() { if (!$this->matchRegexps && !$this->noMatchRegexps) { return true; } $fileinfo = $this->current(); if ($fileinfo->isDir() || !$fileinfo->isReadable()) { return false; } $content = $fileinfo->getContents(); if (!$content) { return false; } // should at least not match one rule to exclude foreach ($this->noMatchRegexps as $regex) { if (preg_match($regex, $content)) { return false; } } // should at least match one rule $match = true; if ($this->matchRegexps) { $match = false; foreach ($this->matchRegexps as $regex) { if (preg_match($regex, $content)) { return true; } } } return $match; } /** * Converts string to regexp if necessary. * * @param string $str Pattern: string or regexp * * @return string regexp corresponding to a given string or regexp */ protected function toRegex($str) { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; /** * This iterator just overrides the rewind method in order to correct a PHP bug. * * @see https://bugs.php.net/bug.php?id=49104 * * @author Alex Bogomazov */ abstract class FilterIterator extends \FilterIterator { /** * This is a workaround for the problem with \FilterIterator leaving inner \FilesystemIterator in wrong state after * rewind in some cases. * * @see FilterIterator::rewind() */ public function rewind() { $iterator = $this; while ($iterator instanceof \OuterIterator) { $innerIterator = $iterator->getInnerIterator(); if ($innerIterator instanceof RecursiveDirectoryIterator) { if ($innerIterator->isRewindable()) { $innerIterator->next(); $innerIterator->rewind(); } } elseif ($iterator->getInnerIterator() instanceof \FilesystemIterator) { $iterator->getInnerIterator()->next(); $iterator->getInnerIterator()->rewind(); } $iterator = $iterator->getInnerIterator(); } parent::rewind(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; use Symfony\Component\Finder\Comparator\DateComparator; /** * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates). * * @author Fabien Potencier */ class DateRangeFilterIterator extends FilterIterator { private $comparators = array(); /** * Constructor. * * @param \Iterator $iterator The Iterator to filter * @param DateComparator[] $comparators An array of DateComparator instances */ public function __construct(\Iterator $iterator, array $comparators) { $this->comparators = $comparators; parent::__construct($iterator); } /** * Filters the iterator values. * * @return bool true if the value should be kept, false otherwise */ public function accept() { $fileinfo = $this->current(); if (!file_exists($fileinfo->getRealPath())) { return false; } $filedate = $fileinfo->getMTime(); foreach ($this->comparators as $compare) { if (!$compare->test($filedate)) { return false; } } return true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; /** * FileTypeFilterIterator only keeps files, directories, or both. * * @author Fabien Potencier */ class FileTypeFilterIterator extends FilterIterator { const ONLY_FILES = 1; const ONLY_DIRECTORIES = 2; private $mode; /** * Constructor. * * @param \Iterator $iterator The Iterator to filter * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) */ public function __construct(\Iterator $iterator, $mode) { $this->mode = $mode; parent::__construct($iterator); } /** * Filters the iterator values. * * @return bool true if the value should be kept, false otherwise */ public function accept() { $fileinfo = $this->current(); if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) { return false; } elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) { return false; } return true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; /** * CustomFilterIterator filters files by applying anonymous functions. * * The anonymous function receives a \SplFileInfo and must return false * to remove files. * * @author Fabien Potencier */ class CustomFilterIterator extends FilterIterator { private $filters = array(); /** * Constructor. * * @param \Iterator $iterator The Iterator to filter * @param array $filters An array of PHP callbacks * * @throws \InvalidArgumentException */ public function __construct(\Iterator $iterator, array $filters) { foreach ($filters as $filter) { if (!is_callable($filter)) { throw new \InvalidArgumentException('Invalid PHP callback.'); } } $this->filters = $filters; parent::__construct($iterator); } /** * Filters the iterator values. * * @return bool true if the value should be kept, false otherwise */ public function accept() { $fileinfo = $this->current(); foreach ($this->filters as $filter) { if (false === call_user_func($filter, $fileinfo)) { return false; } } return true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; use Symfony\Component\Finder\Expression\Expression; /** * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string). * * @author Fabien Potencier */ class FilenameFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. * * @return bool true if the value should be kept, false otherwise */ public function accept() { $filename = $this->current()->getFilename(); // should at least not match one rule to exclude foreach ($this->noMatchRegexps as $regex) { if (preg_match($regex, $filename)) { return false; } } // should at least match one rule $match = true; if ($this->matchRegexps) { $match = false; foreach ($this->matchRegexps as $regex) { if (preg_match($regex, $filename)) { return true; } } } return $match; } /** * Converts glob to regexp. * * PCRE patterns are left unchanged. * Glob strings are transformed with Glob::toRegex(). * * @param string $str Pattern: glob or regexp * * @return string regexp corresponding to a given glob or regexp */ protected function toRegex($str) { return Expression::create($str)->getRegex()->render(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; /** * ExcludeDirectoryFilterIterator filters out directories. * * @author Fabien Potencier */ class ExcludeDirectoryFilterIterator extends FilterIterator implements \RecursiveIterator { private $iterator; private $isRecursive; private $excludedDirs = array(); private $excludedPattern; /** * Constructor. * * @param \Iterator $iterator The Iterator to filter * @param array $directories An array of directories to exclude */ public function __construct(\Iterator $iterator, array $directories) { $this->iterator = $iterator; $this->isRecursive = $iterator instanceof \RecursiveIterator; $patterns = array(); foreach ($directories as $directory) { if (!$this->isRecursive || false !== strpos($directory, '/')) { $patterns[] = preg_quote($directory, '#'); } else { $this->excludedDirs[$directory] = true; } } if ($patterns) { $this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#'; } parent::__construct($iterator); } /** * Filters the iterator values. * * @return bool true if the value should be kept, false otherwise */ public function accept() { if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) { return false; } if ($this->excludedPattern) { $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath(); $path = str_replace('\\', '/', $path); return !preg_match($this->excludedPattern, $path); } return true; } public function hasChildren() { return $this->isRecursive && $this->iterator->hasChildren(); } public function getChildren() { $children = new self($this->iterator->getChildren(), array()); $children->excludedDirs = $this->excludedDirs; $children->excludedPattern = $this->excludedPattern; return $children; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; use Symfony\Component\Finder\Exception\AccessDeniedException; use Symfony\Component\Finder\SplFileInfo; /** * Extends the \RecursiveDirectoryIterator to support relative paths. * * @author Victor Berchet */ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator { /** * @var bool */ private $ignoreUnreadableDirs; /** * @var bool */ private $rewindable; // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations private $rootPath; private $subPath; private $directorySeparator = '/'; /** * Constructor. * * @param string $path * @param int $flags * @param bool $ignoreUnreadableDirs * * @throws \RuntimeException */ public function __construct($path, $flags, $ignoreUnreadableDirs = false) { if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) { throw new \RuntimeException('This iterator only support returning current as fileinfo.'); } parent::__construct($path, $flags); $this->ignoreUnreadableDirs = $ignoreUnreadableDirs; $this->rootPath = (string) $path; if ('/' !== DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) { $this->directorySeparator = DIRECTORY_SEPARATOR; } } /** * Return an instance of SplFileInfo with support for relative paths. * * @return SplFileInfo File information */ public function current() { // the logic here avoids redoing the same work in all iterations if (null === $subPathname = $this->subPath) { $subPathname = $this->subPath = (string) $this->getSubPath(); } if ('' !== $subPathname) { $subPathname .= $this->directorySeparator; } $subPathname .= $this->getFilename(); return new SplFileInfo($this->rootPath.$this->directorySeparator.$subPathname, $this->subPath, $subPathname); } /** * @return \RecursiveIterator * * @throws AccessDeniedException */ public function getChildren() { try { $children = parent::getChildren(); if ($children instanceof self) { // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; // performance optimization to avoid redoing the same work in all children $children->rewindable = &$this->rewindable; $children->rootPath = $this->rootPath; } return $children; } catch (\UnexpectedValueException $e) { if ($this->ignoreUnreadableDirs) { // If directory is unreadable and finder is set to ignore it, a fake empty content is returned. return new \RecursiveArrayIterator(array()); } else { throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); } } } /** * Do nothing for non rewindable stream. */ public function rewind() { if (false === $this->isRewindable()) { return; } // @see https://bugs.php.net/bug.php?id=49104 parent::next(); parent::rewind(); } /** * Checks if the stream is rewindable. * * @return bool true when the stream is rewindable, false otherwise */ public function isRewindable() { if (null !== $this->rewindable) { return $this->rewindable; } if (false !== $stream = @opendir($this->getPath())) { $infos = stream_get_meta_data($stream); closedir($stream); if ($infos['seekable']) { return $this->rewindable = true; } } return $this->rewindable = false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; use Symfony\Component\Finder\SplFileInfo; /** * Iterate over shell command result. * * @author Jean-François Simon */ class FilePathsIterator extends \ArrayIterator { /** * @var string */ private $baseDir; /** * @var int */ private $baseDirLength; /** * @var string */ private $subPath; /** * @var string */ private $subPathname; /** * @var SplFileInfo */ private $current; /** * @param array $paths List of paths returned by shell command * @param string $baseDir Base dir for relative path building */ public function __construct(array $paths, $baseDir) { $this->baseDir = $baseDir; $this->baseDirLength = strlen($baseDir); parent::__construct($paths); } /** * @param string $name * @param array $arguments * * @return mixed */ public function __call($name, array $arguments) { return call_user_func_array(array($this->current(), $name), $arguments); } /** * Return an instance of SplFileInfo with support for relative paths. * * @return SplFileInfo File information */ public function current() { return $this->current; } /** * @return string */ public function key() { return $this->current->getPathname(); } public function next() { parent::next(); $this->buildProperties(); } public function rewind() { parent::rewind(); $this->buildProperties(); } /** * @return string */ public function getSubPath() { return $this->subPath; } /** * @return string */ public function getSubPathname() { return $this->subPathname; } private function buildProperties() { $absolutePath = parent::current(); if ($this->baseDir === substr($absolutePath, 0, $this->baseDirLength)) { $this->subPathname = ltrim(substr($absolutePath, $this->baseDirLength), '/\\'); $dir = dirname($this->subPathname); $this->subPath = '.' === $dir ? '' : $dir; } else { $this->subPath = $this->subPathname = ''; } $this->current = new SplFileInfo(parent::current(), $this->subPath, $this->subPathname); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; use Symfony\Component\Finder\Expression\Expression; /** * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings). * * @author Fabien Potencier */ abstract class MultiplePcreFilterIterator extends FilterIterator { protected $matchRegexps = array(); protected $noMatchRegexps = array(); /** * Constructor. * * @param \Iterator $iterator The Iterator to filter * @param array $matchPatterns An array of patterns that need to match * @param array $noMatchPatterns An array of patterns that need to not match */ public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns) { foreach ($matchPatterns as $pattern) { $this->matchRegexps[] = $this->toRegex($pattern); } foreach ($noMatchPatterns as $pattern) { $this->noMatchRegexps[] = $this->toRegex($pattern); } parent::__construct($iterator); } /** * Checks whether the string is a regex. * * @param string $str * * @return bool Whether the given string is a regex */ protected function isRegex($str) { return Expression::create($str)->isRegex(); } /** * Converts string into regexp. * * @param string $str Pattern * * @return string regexp corresponding to a given string */ abstract protected function toRegex($str); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; use Symfony\Component\Finder\Comparator\NumberComparator; /** * SizeRangeFilterIterator filters out files that are not in the given size range. * * @author Fabien Potencier */ class SizeRangeFilterIterator extends FilterIterator { private $comparators = array(); /** * Constructor. * * @param \Iterator $iterator The Iterator to filter * @param NumberComparator[] $comparators An array of NumberComparator instances */ public function __construct(\Iterator $iterator, array $comparators) { $this->comparators = $comparators; parent::__construct($iterator); } /** * Filters the iterator values. * * @return bool true if the value should be kept, false otherwise */ public function accept() { $fileinfo = $this->current(); if (!$fileinfo->isFile()) { return true; } $filesize = $fileinfo->getSize(); foreach ($this->comparators as $compare) { if (!$compare->test($filesize)) { return false; } } return true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; /** * DepthRangeFilterIterator limits the directory depth. * * @author Fabien Potencier */ class DepthRangeFilterIterator extends FilterIterator { private $minDepth = 0; /** * Constructor. * * @param \RecursiveIteratorIterator $iterator The Iterator to filter * @param int $minDepth The min depth * @param int $maxDepth The max depth */ public function __construct(\RecursiveIteratorIterator $iterator, $minDepth = 0, $maxDepth = PHP_INT_MAX) { $this->minDepth = $minDepth; $iterator->setMaxDepth(PHP_INT_MAX === $maxDepth ? -1 : $maxDepth); parent::__construct($iterator); } /** * Filters the iterator values. * * @return bool true if the value should be kept, false otherwise */ public function accept() { return $this->getInnerIterator()->getDepth() >= $this->minDepth; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Iterator; /** * PathFilterIterator filters files by path patterns (e.g. some/special/dir). * * @author Fabien Potencier * @author Włodzimierz Gajda */ class PathFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. * * @return bool true if the value should be kept, false otherwise */ public function accept() { $filename = $this->current()->getRelativePathname(); if ('\\' === DIRECTORY_SEPARATOR) { $filename = str_replace('\\', '/', $filename); } // should at least not match one rule to exclude foreach ($this->noMatchRegexps as $regex) { if (preg_match($regex, $filename)) { return false; } } // should at least match one rule $match = true; if ($this->matchRegexps) { $match = false; foreach ($this->matchRegexps as $regex) { if (preg_match($regex, $filename)) { return true; } } } return $match; } /** * Converts strings to regexp. * * PCRE patterns are left unchanged. * * Default conversion: * 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/' * * Use only / as directory separator (on Windows also). * * @param string $str Pattern: regexp or dirname. * * @return string regexp corresponding to a given string or regexp */ protected function toRegex($str) { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Exception; /** * @author Jean-François Simon */ class OperationNotPermitedException extends AdapterFailureException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Exception; /** * @author Jean-François Simon */ class AccessDeniedException extends \UnexpectedValueException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Exception; use Symfony\Component\Finder\Adapter\AdapterInterface; /** * Base exception for all adapter failures. * * @author Jean-François Simon */ class AdapterFailureException extends \RuntimeException implements ExceptionInterface { /** * @var \Symfony\Component\Finder\Adapter\AdapterInterface */ private $adapter; /** * @param AdapterInterface $adapter * @param string|null $message * @param \Exception|null $previous */ public function __construct(AdapterInterface $adapter, $message = null, \Exception $previous = null) { $this->adapter = $adapter; parent::__construct($message ?: 'Search failed with "'.$adapter->getName().'" adapter.', $previous); } /** * {@inheritdoc} */ public function getAdapter() { return $this->adapter; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Exception; /** * @author Jean-François Simon */ interface ExceptionInterface { /** * @return \Symfony\Component\Finder\Adapter\AdapterInterface */ public function getAdapter(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Exception; use Symfony\Component\Finder\Adapter\AdapterInterface; use Symfony\Component\Finder\Shell\Command; /** * @author Jean-François Simon */ class ShellCommandFailureException extends AdapterFailureException { /** * @var Command */ private $command; /** * @param AdapterInterface $adapter * @param Command $command * @param \Exception|null $previous */ public function __construct(AdapterInterface $adapter, Command $command, \Exception $previous = null) { $this->command = $command; parent::__construct($adapter, 'Shell command failed: "'.$command->join().'".', $previous); } /** * @return Command */ public function getCommand() { return $this->command; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder; /** * Glob matches globbing patterns against text. * * if match_glob("foo.*", "foo.bar") echo "matched\n"; * * // prints foo.bar and foo.baz * $regex = glob_to_regex("foo.*"); * for (array('foo.bar', 'foo.baz', 'foo', 'bar') as $t) * { * if (/$regex/) echo "matched: $car\n"; * } * * Glob implements glob(3) style matching that can be used to match * against text, rather than fetching names from a filesystem. * * Based on the Perl Text::Glob module. * * @author Fabien Potencier PHP port * @author Richard Clamp Perl version * @copyright 2004-2005 Fabien Potencier * @copyright 2002 Richard Clamp */ class Glob { /** * Returns a regexp which is the equivalent of the glob pattern. * * @param string $glob The glob pattern * @param bool $strictLeadingDot * @param bool $strictWildcardSlash * @param string $delimiter Optional delimiter * * @return string regex The regexp */ public static function toRegex($glob, $strictLeadingDot = true, $strictWildcardSlash = true, $delimiter = '#') { $firstByte = true; $escaping = false; $inCurlies = 0; $regex = ''; $sizeGlob = strlen($glob); for ($i = 0; $i < $sizeGlob; ++$i) { $car = $glob[$i]; if ($firstByte) { if ($strictLeadingDot && '.' !== $car) { $regex .= '(?=[^\.])'; } $firstByte = false; } if ('/' === $car) { $firstByte = true; } if ('.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) { $regex .= "\\$car"; } elseif ('*' === $car) { $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*'); } elseif ('?' === $car) { $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.'); } elseif ('{' === $car) { $regex .= $escaping ? '\\{' : '('; if (!$escaping) { ++$inCurlies; } } elseif ('}' === $car && $inCurlies) { $regex .= $escaping ? '}' : ')'; if (!$escaping) { --$inCurlies; } } elseif (',' === $car && $inCurlies) { $regex .= $escaping ? ',' : '|'; } elseif ('\\' === $car) { if ($escaping) { $regex .= '\\\\'; $escaping = false; } else { $escaping = true; } continue; } else { $regex .= $car; } $escaping = false; } return $delimiter.'^'.$regex.'$'.$delimiter; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder; use Symfony\Component\Finder\Adapter\AdapterInterface; use Symfony\Component\Finder\Adapter\GnuFindAdapter; use Symfony\Component\Finder\Adapter\BsdFindAdapter; use Symfony\Component\Finder\Adapter\PhpAdapter; use Symfony\Component\Finder\Comparator\DateComparator; use Symfony\Component\Finder\Comparator\NumberComparator; use Symfony\Component\Finder\Exception\ExceptionInterface; use Symfony\Component\Finder\Iterator\CustomFilterIterator; use Symfony\Component\Finder\Iterator\DateRangeFilterIterator; use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator; use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator; use Symfony\Component\Finder\Iterator\FilecontentFilterIterator; use Symfony\Component\Finder\Iterator\FilenameFilterIterator; use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator; use Symfony\Component\Finder\Iterator\SortableIterator; /** * Finder allows to build rules to find files and directories. * * It is a thin wrapper around several specialized iterator classes. * * All rules may be invoked several times. * * All methods return the current Finder object to allow easy chaining: * * $finder = Finder::create()->files()->name('*.php')->in(__DIR__); * * @author Fabien Potencier * * @api */ class Finder implements \IteratorAggregate, \Countable { const IGNORE_VCS_FILES = 1; const IGNORE_DOT_FILES = 2; private $mode = 0; private $names = array(); private $notNames = array(); private $exclude = array(); private $filters = array(); private $depths = array(); private $sizes = array(); private $followLinks = false; private $sort = false; private $ignore = 0; private $dirs = array(); private $dates = array(); private $iterators = array(); private $contains = array(); private $notContains = array(); private $adapters = array(); private $paths = array(); private $notPaths = array(); private $ignoreUnreadableDirs = false; private static $vcsPatterns = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'); /** * Constructor. */ public function __construct() { $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; $this ->addAdapter(new GnuFindAdapter()) ->addAdapter(new BsdFindAdapter()) ->addAdapter(new PhpAdapter(), -50) ->setAdapter('php') ; } /** * Creates a new Finder. * * @return Finder A new Finder instance * * @api */ public static function create() { return new static(); } /** * Registers a finder engine implementation. * * @param AdapterInterface $adapter An adapter instance * @param int $priority Highest is selected first * * @return Finder The current Finder instance */ public function addAdapter(AdapterInterface $adapter, $priority = 0) { $this->adapters[$adapter->getName()] = array( 'adapter' => $adapter, 'priority' => $priority, 'selected' => false, ); return $this->sortAdapters(); } /** * Sets the selected adapter to the best one according to the current platform the code is run on. * * @return Finder The current Finder instance */ public function useBestAdapter() { $this->resetAdapterSelection(); return $this->sortAdapters(); } /** * Selects the adapter to use. * * @param string $name * * @throws \InvalidArgumentException * * @return Finder The current Finder instance */ public function setAdapter($name) { if (!isset($this->adapters[$name])) { throw new \InvalidArgumentException(sprintf('Adapter "%s" does not exist.', $name)); } $this->resetAdapterSelection(); $this->adapters[$name]['selected'] = true; return $this->sortAdapters(); } /** * Removes all adapters registered in the finder. * * @return Finder The current Finder instance */ public function removeAdapters() { $this->adapters = array(); return $this; } /** * Returns registered adapters ordered by priority without extra information. * * @return AdapterInterface[] */ public function getAdapters() { return array_values(array_map(function (array $adapter) { return $adapter['adapter']; }, $this->adapters)); } /** * Restricts the matching to directories only. * * @return Finder The current Finder instance * * @api */ public function directories() { $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; return $this; } /** * Restricts the matching to files only. * * @return Finder The current Finder instance * * @api */ public function files() { $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; return $this; } /** * Adds tests for the directory depth. * * Usage: * * $finder->depth('> 1') // the Finder will start matching at level 1. * $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point. * * @param int $level The depth level expression * * @return Finder The current Finder instance * * @see DepthRangeFilterIterator * @see NumberComparator * * @api */ public function depth($level) { $this->depths[] = new Comparator\NumberComparator($level); return $this; } /** * Adds tests for file dates (last modified). * * The date must be something that strtotime() is able to parse: * * $finder->date('since yesterday'); * $finder->date('until 2 days ago'); * $finder->date('> now - 2 hours'); * $finder->date('>= 2005-10-15'); * * @param string $date A date range string * * @return Finder The current Finder instance * * @see strtotime * @see DateRangeFilterIterator * @see DateComparator * * @api */ public function date($date) { $this->dates[] = new Comparator\DateComparator($date); return $this; } /** * Adds rules that files must match. * * You can use patterns (delimited with / sign), globs or simple strings. * * $finder->name('*.php') * $finder->name('/\.php$/') // same as above * $finder->name('test.php') * * @param string $pattern A pattern (a regexp, a glob, or a string) * * @return Finder The current Finder instance * * @see FilenameFilterIterator * * @api */ public function name($pattern) { $this->names[] = $pattern; return $this; } /** * Adds rules that files must not match. * * @param string $pattern A pattern (a regexp, a glob, or a string) * * @return Finder The current Finder instance * * @see FilenameFilterIterator * * @api */ public function notName($pattern) { $this->notNames[] = $pattern; return $this; } /** * Adds tests that file contents must match. * * Strings or PCRE patterns can be used: * * $finder->contains('Lorem ipsum') * $finder->contains('/Lorem ipsum/i') * * @param string $pattern A pattern (string or regexp) * * @return Finder The current Finder instance * * @see FilecontentFilterIterator */ public function contains($pattern) { $this->contains[] = $pattern; return $this; } /** * Adds tests that file contents must not match. * * Strings or PCRE patterns can be used: * * $finder->notContains('Lorem ipsum') * $finder->notContains('/Lorem ipsum/i') * * @param string $pattern A pattern (string or regexp) * * @return Finder The current Finder instance * * @see FilecontentFilterIterator */ public function notContains($pattern) { $this->notContains[] = $pattern; return $this; } /** * Adds rules that filenames must match. * * You can use patterns (delimited with / sign) or simple strings. * * $finder->path('some/special/dir') * $finder->path('/some\/special\/dir/') // same as above * * Use only / as dirname separator. * * @param string $pattern A pattern (a regexp or a string) * * @return Finder The current Finder instance * * @see FilenameFilterIterator */ public function path($pattern) { $this->paths[] = $pattern; return $this; } /** * Adds rules that filenames must not match. * * You can use patterns (delimited with / sign) or simple strings. * * $finder->notPath('some/special/dir') * $finder->notPath('/some\/special\/dir/') // same as above * * Use only / as dirname separator. * * @param string $pattern A pattern (a regexp or a string) * * @return Finder The current Finder instance * * @see FilenameFilterIterator */ public function notPath($pattern) { $this->notPaths[] = $pattern; return $this; } /** * Adds tests for file sizes. * * $finder->size('> 10K'); * $finder->size('<= 1Ki'); * $finder->size(4); * * @param string $size A size range string * * @return Finder The current Finder instance * * @see SizeRangeFilterIterator * @see NumberComparator * * @api */ public function size($size) { $this->sizes[] = new Comparator\NumberComparator($size); return $this; } /** * Excludes directories. * * @param string|array $dirs A directory path or an array of directories * * @return Finder The current Finder instance * * @see ExcludeDirectoryFilterIterator * * @api */ public function exclude($dirs) { $this->exclude = array_merge($this->exclude, (array) $dirs); return $this; } /** * Excludes "hidden" directories and files (starting with a dot). * * @param bool $ignoreDotFiles Whether to exclude "hidden" files or not * * @return Finder The current Finder instance * * @see ExcludeDirectoryFilterIterator * * @api */ public function ignoreDotFiles($ignoreDotFiles) { if ($ignoreDotFiles) { $this->ignore |= static::IGNORE_DOT_FILES; } else { $this->ignore &= ~static::IGNORE_DOT_FILES; } return $this; } /** * Forces the finder to ignore version control directories. * * @param bool $ignoreVCS Whether to exclude VCS files or not * * @return Finder The current Finder instance * * @see ExcludeDirectoryFilterIterator * * @api */ public function ignoreVCS($ignoreVCS) { if ($ignoreVCS) { $this->ignore |= static::IGNORE_VCS_FILES; } else { $this->ignore &= ~static::IGNORE_VCS_FILES; } return $this; } /** * Adds VCS patterns. * * @see ignoreVCS() * * @param string|string[] $pattern VCS patterns to ignore */ public static function addVCSPattern($pattern) { foreach ((array) $pattern as $p) { self::$vcsPatterns[] = $p; } self::$vcsPatterns = array_unique(self::$vcsPatterns); } /** * Sorts files and directories by an anonymous function. * * The anonymous function receives two \SplFileInfo instances to compare. * * This can be slow as all the matching files and directories must be retrieved for comparison. * * @param \Closure $closure An anonymous function * * @return Finder The current Finder instance * * @see SortableIterator * * @api */ public function sort(\Closure $closure) { $this->sort = $closure; return $this; } /** * Sorts files and directories by name. * * This can be slow as all the matching files and directories must be retrieved for comparison. * * @return Finder The current Finder instance * * @see SortableIterator * * @api */ public function sortByName() { $this->sort = Iterator\SortableIterator::SORT_BY_NAME; return $this; } /** * Sorts files and directories by type (directories before files), then by name. * * This can be slow as all the matching files and directories must be retrieved for comparison. * * @return Finder The current Finder instance * * @see SortableIterator * * @api */ public function sortByType() { $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; return $this; } /** * Sorts files and directories by the last accessed time. * * This is the time that the file was last accessed, read or written to. * * This can be slow as all the matching files and directories must be retrieved for comparison. * * @return Finder The current Finder instance * * @see SortableIterator * * @api */ public function sortByAccessedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; return $this; } /** * Sorts files and directories by the last inode changed time. * * This is the time that the inode information was last modified (permissions, owner, group or other metadata). * * On Windows, since inode is not available, changed time is actually the file creation time. * * This can be slow as all the matching files and directories must be retrieved for comparison. * * @return Finder The current Finder instance * * @see SortableIterator * * @api */ public function sortByChangedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; return $this; } /** * Sorts files and directories by the last modified time. * * This is the last time the actual contents of the file were last modified. * * This can be slow as all the matching files and directories must be retrieved for comparison. * * @return Finder The current Finder instance * * @see SortableIterator * * @api */ public function sortByModifiedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; return $this; } /** * Filters the iterator with an anonymous function. * * The anonymous function receives a \SplFileInfo and must return false * to remove files. * * @param \Closure $closure An anonymous function * * @return Finder The current Finder instance * * @see CustomFilterIterator * * @api */ public function filter(\Closure $closure) { $this->filters[] = $closure; return $this; } /** * Forces the following of symlinks. * * @return Finder The current Finder instance * * @api */ public function followLinks() { $this->followLinks = true; return $this; } /** * Tells finder to ignore unreadable directories. * * By default, scanning unreadable directories content throws an AccessDeniedException. * * @param bool $ignore * * @return Finder The current Finder instance */ public function ignoreUnreadableDirs($ignore = true) { $this->ignoreUnreadableDirs = (bool) $ignore; return $this; } /** * Searches files and directories which match defined rules. * * @param string|array $dirs A directory path or an array of directories * * @return Finder The current Finder instance * * @throws \InvalidArgumentException if one of the directories does not exist * * @api */ public function in($dirs) { $resolvedDirs = array(); foreach ((array) $dirs as $dir) { if (is_dir($dir)) { $resolvedDirs[] = $dir; } elseif ($glob = glob($dir, (defined('GLOB_BRACE') ? GLOB_BRACE : 0) | GLOB_ONLYDIR)) { $resolvedDirs = array_merge($resolvedDirs, $glob); } else { throw new \InvalidArgumentException(sprintf('The "%s" directory does not exist.', $dir)); } } $this->dirs = array_merge($this->dirs, $resolvedDirs); return $this; } /** * Returns an Iterator for the current Finder configuration. * * This method implements the IteratorAggregate interface. * * @return \Iterator An iterator * * @throws \LogicException if the in() method has not been called */ public function getIterator() { if (0 === count($this->dirs) && 0 === count($this->iterators)) { throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.'); } if (1 === count($this->dirs) && 0 === count($this->iterators)) { return $this->searchInDirectory($this->dirs[0]); } $iterator = new \AppendIterator(); foreach ($this->dirs as $dir) { $iterator->append($this->searchInDirectory($dir)); } foreach ($this->iterators as $it) { $iterator->append($it); } return $iterator; } /** * Appends an existing set of files/directories to the finder. * * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array. * * @param mixed $iterator * * @return Finder The finder * * @throws \InvalidArgumentException When the given argument is not iterable. */ public function append($iterator) { if ($iterator instanceof \IteratorAggregate) { $this->iterators[] = $iterator->getIterator(); } elseif ($iterator instanceof \Iterator) { $this->iterators[] = $iterator; } elseif ($iterator instanceof \Traversable || is_array($iterator)) { $it = new \ArrayIterator(); foreach ($iterator as $file) { $it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file)); } $this->iterators[] = $it; } else { throw new \InvalidArgumentException('Finder::append() method wrong argument type.'); } return $this; } /** * Counts all the results collected by the iterators. * * @return int */ public function count() { return iterator_count($this->getIterator()); } /** * @return Finder The current Finder instance */ private function sortAdapters() { uasort($this->adapters, function (array $a, array $b) { if ($a['selected'] || $b['selected']) { return $a['selected'] ? -1 : 1; } return $a['priority'] > $b['priority'] ? -1 : 1; }); return $this; } /** * @param $dir * * @return \Iterator * * @throws \RuntimeException When none of the adapters are supported */ private function searchInDirectory($dir) { if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { $this->exclude = array_merge($this->exclude, self::$vcsPatterns); } if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) { $this->notPaths[] = '#(^|/)\..+(/|$)#'; } foreach ($this->adapters as $adapter) { if ($adapter['adapter']->isSupported()) { try { return $this ->buildAdapter($adapter['adapter']) ->searchInDirectory($dir); } catch (ExceptionInterface $e) { } } } throw new \RuntimeException('No supported adapter found.'); } /** * @param AdapterInterface $adapter * * @return AdapterInterface */ private function buildAdapter(AdapterInterface $adapter) { return $adapter ->setFollowLinks($this->followLinks) ->setDepths($this->depths) ->setMode($this->mode) ->setExclude($this->exclude) ->setNames($this->names) ->setNotNames($this->notNames) ->setContains($this->contains) ->setNotContains($this->notContains) ->setSizes($this->sizes) ->setDates($this->dates) ->setFilters($this->filters) ->setSort($this->sort) ->setPath($this->paths) ->setNotPath($this->notPaths) ->ignoreUnreadableDirs($this->ignoreUnreadableDirs); } /** * Unselects all adapters. */ private function resetAdapterSelection() { $this->adapters = array_map(function (array $properties) { $properties['selected'] = false; return $properties; }, $this->adapters); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Expression; use Symfony\Component\Finder\Glob as FinderGlob; /** * @author Jean-François Simon */ class Glob implements ValueInterface { /** * @var string */ private $pattern; /** * @param string $pattern */ public function __construct($pattern) { $this->pattern = $pattern; } /** * {@inheritdoc} */ public function render() { return $this->pattern; } /** * {@inheritdoc} */ public function renderPattern() { return $this->pattern; } /** * {@inheritdoc} */ public function getType() { return Expression::TYPE_GLOB; } /** * {@inheritdoc} */ public function isCaseSensitive() { return true; } /** * {@inheritdoc} */ public function prepend($expr) { $this->pattern = $expr.$this->pattern; return $this; } /** * {@inheritdoc} */ public function append($expr) { $this->pattern .= $expr; return $this; } /** * Tests if glob is expandable ("*.{a,b}" syntax). * * @return bool */ public function isExpandable() { return false !== strpos($this->pattern, '{') && false !== strpos($this->pattern, '}'); } /** * @param bool $strictLeadingDot * @param bool $strictWildcardSlash * * @return Regex */ public function toRegex($strictLeadingDot = true, $strictWildcardSlash = true) { $regex = FinderGlob::toRegex($this->pattern, $strictLeadingDot, $strictWildcardSlash, ''); return new Regex($regex); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Expression; /** * @author Jean-François Simon */ class Expression implements ValueInterface { const TYPE_REGEX = 1; const TYPE_GLOB = 2; /** * @var ValueInterface */ private $value; /** * @param string $expr * * @return Expression */ public static function create($expr) { return new self($expr); } /** * @param string $expr */ public function __construct($expr) { try { $this->value = Regex::create($expr); } catch (\InvalidArgumentException $e) { $this->value = new Glob($expr); } } /** * @return string */ public function __toString() { return $this->render(); } /** * {@inheritdoc} */ public function render() { return $this->value->render(); } /** * {@inheritdoc} */ public function renderPattern() { return $this->value->renderPattern(); } /** * @return bool */ public function isCaseSensitive() { return $this->value->isCaseSensitive(); } /** * @return int */ public function getType() { return $this->value->getType(); } /** * {@inheritdoc} */ public function prepend($expr) { $this->value->prepend($expr); return $this; } /** * {@inheritdoc} */ public function append($expr) { $this->value->append($expr); return $this; } /** * @return bool */ public function isRegex() { return self::TYPE_REGEX === $this->value->getType(); } /** * @return bool */ public function isGlob() { return self::TYPE_GLOB === $this->value->getType(); } /** * @throws \LogicException * * @return Glob */ public function getGlob() { if (self::TYPE_GLOB !== $this->value->getType()) { throw new \LogicException('Regex can\'t be transformed to glob.'); } return $this->value; } /** * @return Regex */ public function getRegex() { return self::TYPE_REGEX === $this->value->getType() ? $this->value : $this->value->toRegex(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Expression; /** * @author Jean-François Simon */ class Regex implements ValueInterface { const START_FLAG = '^'; const END_FLAG = '$'; const BOUNDARY = '~'; const JOKER = '.*'; const ESCAPING = '\\'; /** * @var string */ private $pattern; /** * @var array */ private $options; /** * @var bool */ private $startFlag; /** * @var bool */ private $endFlag; /** * @var bool */ private $startJoker; /** * @var bool */ private $endJoker; /** * @param string $expr * * @return Regex * * @throws \InvalidArgumentException */ public static function create($expr) { if (preg_match('/^(.{3,}?)([imsxuADU]*)$/', $expr, $m)) { $start = substr($m[1], 0, 1); $end = substr($m[1], -1); if ( ($start === $end && !preg_match('/[*?[:alnum:] \\\\]/', $start)) || ($start === '{' && $end === '}') || ($start === '(' && $end === ')') ) { return new self(substr($m[1], 1, -1), $m[2], $end); } } throw new \InvalidArgumentException('Given expression is not a regex.'); } /** * @param string $pattern * @param string $options * @param string $delimiter */ public function __construct($pattern, $options = '', $delimiter = null) { if (null !== $delimiter) { // removes delimiter escaping $pattern = str_replace('\\'.$delimiter, $delimiter, $pattern); } $this->parsePattern($pattern); $this->options = $options; } /** * @return string */ public function __toString() { return $this->render(); } /** * {@inheritdoc} */ public function render() { return self::BOUNDARY .$this->renderPattern() .self::BOUNDARY .$this->options; } /** * {@inheritdoc} */ public function renderPattern() { return ($this->startFlag ? self::START_FLAG : '') .($this->startJoker ? self::JOKER : '') .str_replace(self::BOUNDARY, '\\'.self::BOUNDARY, $this->pattern) .($this->endJoker ? self::JOKER : '') .($this->endFlag ? self::END_FLAG : ''); } /** * {@inheritdoc} */ public function isCaseSensitive() { return !$this->hasOption('i'); } /** * {@inheritdoc} */ public function getType() { return Expression::TYPE_REGEX; } /** * {@inheritdoc} */ public function prepend($expr) { $this->pattern = $expr.$this->pattern; return $this; } /** * {@inheritdoc} */ public function append($expr) { $this->pattern .= $expr; return $this; } /** * @param string $option * * @return bool */ public function hasOption($option) { return false !== strpos($this->options, $option); } /** * @param string $option * * @return Regex */ public function addOption($option) { if (!$this->hasOption($option)) { $this->options .= $option; } return $this; } /** * @param string $option * * @return Regex */ public function removeOption($option) { $this->options = str_replace($option, '', $this->options); return $this; } /** * @param bool $startFlag * * @return Regex */ public function setStartFlag($startFlag) { $this->startFlag = $startFlag; return $this; } /** * @return bool */ public function hasStartFlag() { return $this->startFlag; } /** * @param bool $endFlag * * @return Regex */ public function setEndFlag($endFlag) { $this->endFlag = (bool) $endFlag; return $this; } /** * @return bool */ public function hasEndFlag() { return $this->endFlag; } /** * @param bool $startJoker * * @return Regex */ public function setStartJoker($startJoker) { $this->startJoker = $startJoker; return $this; } /** * @return bool */ public function hasStartJoker() { return $this->startJoker; } /** * @param bool $endJoker * * @return Regex */ public function setEndJoker($endJoker) { $this->endJoker = (bool) $endJoker; return $this; } /** * @return bool */ public function hasEndJoker() { return $this->endJoker; } /** * @param array $replacement * * @return Regex */ public function replaceJokers($replacement) { $replace = function ($subject) use ($replacement) { $subject = $subject[0]; $replace = 0 === substr_count($subject, '\\') % 2; return $replace ? str_replace('.', $replacement, $subject) : $subject; }; $this->pattern = preg_replace_callback('~[\\\\]*\\.~', $replace, $this->pattern); return $this; } /** * @param string $pattern */ private function parsePattern($pattern) { if ($this->startFlag = self::START_FLAG === substr($pattern, 0, 1)) { $pattern = substr($pattern, 1); } if ($this->startJoker = self::JOKER === substr($pattern, 0, 2)) { $pattern = substr($pattern, 2); } if ($this->endFlag = (self::END_FLAG === substr($pattern, -1) && self::ESCAPING !== substr($pattern, -2, -1))) { $pattern = substr($pattern, 0, -1); } if ($this->endJoker = (self::JOKER === substr($pattern, -2) && self::ESCAPING !== substr($pattern, -3, -2))) { $pattern = substr($pattern, 0, -2); } $this->pattern = $pattern; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder\Expression; /** * @author Jean-François Simon */ interface ValueInterface { /** * Renders string representation of expression. * * @return string */ public function render(); /** * Renders string representation of pattern. * * @return string */ public function renderPattern(); /** * Returns value case sensitivity. * * @return bool */ public function isCaseSensitive(); /** * Returns expression type. * * @return int */ public function getType(); /** * @param string $expr * * @return ValueInterface */ public function prepend($expr); /** * @param string $expr * * @return ValueInterface */ public function append($expr); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Finder; /** * Extends \SplFileInfo to support relative paths. * * @author Fabien Potencier */ class SplFileInfo extends \SplFileInfo { private $relativePath; private $relativePathname; /** * Constructor. * * @param string $file The file name * @param string $relativePath The relative path * @param string $relativePathname The relative path name */ public function __construct($file, $relativePath, $relativePathname) { parent::__construct($file); $this->relativePath = $relativePath; $this->relativePathname = $relativePathname; } /** * Returns the relative path. * * @return string the relative path */ public function getRelativePath() { return $this->relativePath; } /** * Returns the relative path name. * * @return string the relative path name */ public function getRelativePathname() { return $this->relativePathname; } /** * Returns the contents of the file. * * @return string the contents of the file * * @throws \RuntimeException */ public function getContents() { $level = error_reporting(0); $content = file_get_contents($this->getPathname()); error_reporting($level); if (false === $content) { $error = error_get_last(); throw new \RuntimeException($error['message']); } return $content; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Style; use Symfony\Component\Console\Formatter\OutputFormatterInterface; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Output\OutputInterface; /** * Decorates output to add console style guide helpers. * * @author Kevin Bond */ abstract class OutputStyle implements OutputInterface, StyleInterface { private $output; /** * @param OutputInterface $output */ public function __construct(OutputInterface $output) { $this->output = $output; } /** * {@inheritdoc} */ public function newLine($count = 1) { $this->output->write(str_repeat(PHP_EOL, $count)); } /** * @param int $max * * @return ProgressBar */ public function createProgressBar($max = 0) { return new ProgressBar($this->output, $max); } /** * {@inheritdoc} */ public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) { $this->output->write($messages, $newline, $type); } /** * {@inheritdoc} */ public function writeln($messages, $type = self::OUTPUT_NORMAL) { $this->output->writeln($messages, $type); } /** * {@inheritdoc} */ public function setVerbosity($level) { $this->output->setVerbosity($level); } /** * {@inheritdoc} */ public function getVerbosity() { return $this->output->getVerbosity(); } /** * {@inheritdoc} */ public function setDecorated($decorated) { $this->output->setDecorated($decorated); } /** * {@inheritdoc} */ public function isDecorated() { return $this->output->isDecorated(); } /** * {@inheritdoc} */ public function setFormatter(OutputFormatterInterface $formatter) { $this->output->setFormatter($formatter); } /** * {@inheritdoc} */ public function getFormatter() { return $this->output->getFormatter(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Style; /** * Output style helpers. * * @author Kevin Bond */ interface StyleInterface { /** * Formats a command title. * * @param string $message */ public function title($message); /** * Formats a section title. * * @param string $message */ public function section($message); /** * Formats a list. * * @param array $elements */ public function listing(array $elements); /** * Formats informational text. * * @param string|array $message */ public function text($message); /** * Formats a success result bar. * * @param string|array $message */ public function success($message); /** * Formats an error result bar. * * @param string|array $message */ public function error($message); /** * Formats an warning result bar. * * @param string|array $message */ public function warning($message); /** * Formats a note admonition. * * @param string|array $message */ public function note($message); /** * Formats a caution admonition. * * @param string|array $message */ public function caution($message); /** * Formats a table. * * @param array $headers * @param array $rows */ public function table(array $headers, array $rows); /** * Asks a question. * * @param string $question * @param string|null $default * @param callable|null $validator * * @return string */ public function ask($question, $default = null, $validator = null); /** * Asks a question with the user input hidden. * * @param string $question * @param callable|null $validator * * @return string */ public function askHidden($question, $validator = null); /** * Asks for confirmation. * * @param string $question * @param bool $default * * @return bool */ public function confirm($question, $default = true); /** * Asks a choice question. * * @param string $question * @param array $choices * @param string|int|null $default * * @return string */ public function choice($question, array $choices, $default = null); /** * Add newline(s). * * @param int $count The number of newlines */ public function newLine($count = 1); /** * Starts the progress output. * * @param int $max Maximum steps (0 if unknown) */ public function progressStart($max = 0); /** * Advances the progress output X steps. * * @param int $step Number of steps to advance */ public function progressAdvance($step = 1); /** * Finishes the progress output. */ public function progressFinish(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Style; use Symfony\Component\Console\Application; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Helper\Helper; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Helper\SymfonyQuestionHelper; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\Question; /** * Output decorator helpers for the Symfony Style Guide. * * @author Kevin Bond */ class SymfonyStyle extends OutputStyle { const MAX_LINE_LENGTH = 120; private $input; private $questionHelper; private $progressBar; private $lineLength; private $bufferedOutput; /** * @param InputInterface $input * @param OutputInterface $output */ public function __construct(InputInterface $input, OutputInterface $output) { $this->input = $input; $this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter()); // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. $this->lineLength = min($this->getTerminalWidth() - (int) (DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); parent::__construct($output); } /** * Formats a message as a block of text. * * @param string|array $messages The message to write in the block * @param string|null $type The block type (added in [] on first line) * @param string|null $style The style to apply to the whole block * @param string $prefix The prefix for the block * @param bool $padding Whether to add vertical padding */ public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false) { $this->autoPrependBlock(); $messages = is_array($messages) ? array_values($messages) : array($messages); $lines = array(); // add type if (null !== $type) { $messages[0] = sprintf('[%s] %s', $type, $messages[0]); } // wrap and add newlines for each element foreach ($messages as $key => $message) { $message = OutputFormatter::escape($message); $lines = array_merge($lines, explode(PHP_EOL, wordwrap($message, $this->lineLength - Helper::strlen($prefix), PHP_EOL, true))); if (count($messages) > 1 && $key < count($messages) - 1) { $lines[] = ''; } } if ($padding && $this->isDecorated()) { array_unshift($lines, ''); $lines[] = ''; } foreach ($lines as &$line) { $line = sprintf('%s%s', $prefix, $line); $line .= str_repeat(' ', $this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line)); if ($style) { $line = sprintf('<%s>%s', $style, $line); } } $this->writeln($lines); $this->newLine(); } /** * {@inheritdoc} */ public function title($message) { $this->autoPrependBlock(); $this->writeln(array( sprintf('%s', $message), sprintf('%s', str_repeat('=', strlen($message))), )); $this->newLine(); } /** * {@inheritdoc} */ public function section($message) { $this->autoPrependBlock(); $this->writeln(array( sprintf('%s', $message), sprintf('%s', str_repeat('-', strlen($message))), )); $this->newLine(); } /** * {@inheritdoc} */ public function listing(array $elements) { $this->autoPrependText(); $elements = array_map(function ($element) { return sprintf(' * %s', $element); }, $elements); $this->writeln($elements); $this->newLine(); } /** * {@inheritdoc} */ public function text($message) { $this->autoPrependText(); if (!is_array($message)) { $this->writeln(sprintf(' // %s', $message)); return; } foreach ($message as $element) { $this->text($element); } } /** * {@inheritdoc} */ public function success($message) { $this->block($message, 'OK', 'fg=white;bg=green', ' ', true); } /** * {@inheritdoc} */ public function error($message) { $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); } /** * {@inheritdoc} */ public function warning($message) { $this->block($message, 'WARNING', 'fg=white;bg=red', ' ', true); } /** * {@inheritdoc} */ public function note($message) { $this->block($message, 'NOTE', 'fg=yellow', ' ! '); } /** * {@inheritdoc} */ public function caution($message) { $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); } /** * {@inheritdoc} */ public function table(array $headers, array $rows) { $headers = array_map(function ($value) { return sprintf('%s', $value); }, $headers); $table = new Table($this); $table->setHeaders($headers); $table->setRows($rows); $table->setStyle('symfony-style-guide'); $table->render(); $this->newLine(); } /** * {@inheritdoc} */ public function ask($question, $default = null, $validator = null) { $question = new Question($question, $default); $question->setValidator($validator); return $this->askQuestion($question); } /** * {@inheritdoc} */ public function askHidden($question, $validator = null) { $question = new Question($question); $question->setHidden(true); $question->setValidator($validator); return $this->askQuestion($question); } /** * {@inheritdoc} */ public function confirm($question, $default = true) { return $this->askQuestion(new ConfirmationQuestion($question, $default)); } /** * {@inheritdoc} */ public function choice($question, array $choices, $default = null) { if (null !== $default) { $values = array_flip($choices); $default = $values[$default]; } return $this->askQuestion(new ChoiceQuestion($question, $choices, $default)); } /** * {@inheritdoc} */ public function progressStart($max = 0) { $this->progressBar = $this->createProgressBar($max); $this->progressBar->start(); } /** * {@inheritdoc} */ public function progressAdvance($step = 1) { $this->getProgressBar()->advance($step); } /** * {@inheritdoc} */ public function progressFinish() { $this->getProgressBar()->finish(); $this->newLine(2); $this->progressBar = null; } /** * {@inheritdoc} */ public function createProgressBar($max = 0) { $progressBar = parent::createProgressBar($max); if ('\\' === DIRECTORY_SEPARATOR) { $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591 $progressBar->setProgressCharacter(''); $progressBar->setBarCharacter('▓'); // dark shade character \u2593 } return $progressBar; } /** * @param Question $question * * @return string */ public function askQuestion(Question $question) { if ($this->input->isInteractive()) { $this->autoPrependBlock(); } if (!$this->questionHelper) { $this->questionHelper = new SymfonyQuestionHelper(); } $answer = $this->questionHelper->ask($this->input, $this, $question); if ($this->input->isInteractive()) { $this->newLine(); $this->bufferedOutput->write("\n"); } return $answer; } /** * {@inheritdoc} */ public function writeln($messages, $type = self::OUTPUT_NORMAL) { parent::writeln($messages, $type); $this->bufferedOutput->writeln($this->reduceBuffer($messages), $type); } /** * {@inheritdoc} */ public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) { parent::write($messages, $newline, $type); $this->bufferedOutput->write($this->reduceBuffer($messages), $newline, $type); } /** * {@inheritdoc} */ public function newLine($count = 1) { parent::newLine($count); $this->bufferedOutput->write(str_repeat("\n", $count)); } /** * @return ProgressBar */ private function getProgressBar() { if (!$this->progressBar) { throw new \RuntimeException('The ProgressBar is not started.'); } return $this->progressBar; } private function getTerminalWidth() { $application = new Application(); $dimensions = $application->getTerminalDimensions(); return $dimensions[0] ?: self::MAX_LINE_LENGTH; } private function autoPrependBlock() { $chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2); if (!isset($chars[0])) { return $this->newLine(); //empty history, so we should start with a new line. } //Prepend new line for each non LF chars (This means no blank line was output before) $this->newLine(2 - substr_count($chars, "\n")); } private function autoPrependText() { $fetched = $this->bufferedOutput->fetch(); //Prepend new line if last char isn't EOL: if ("\n" !== substr($fetched, -1)) { $this->newLine(); } } private function reduceBuffer($messages) { // We need to know if the two last chars are PHP_EOL // Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer return array_map(function ($value) { return substr($value, -4); }, array_merge(array($this->bufferedOutput->fetch()), (array) $messages)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Process\ProcessBuilder; use Symfony\Component\Process\PhpExecutableFinder; /** * A Shell wraps an Application to add shell capabilities to it. * * Support for history and completion only works with a PHP compiled * with readline support (either --with-readline or --with-libedit) * * @author Fabien Potencier * @author Martin Hasoň */ class Shell { private $application; private $history; private $output; private $hasReadline; private $processIsolation = false; /** * Constructor. * * If there is no readline support for the current PHP executable * a \RuntimeException exception is thrown. * * @param Application $application An application instance */ public function __construct(Application $application) { $this->hasReadline = function_exists('readline'); $this->application = $application; $this->history = getenv('HOME').'/.history_'.$application->getName(); $this->output = new ConsoleOutput(); } /** * Runs the shell. */ public function run() { $this->application->setAutoExit(false); $this->application->setCatchExceptions(true); if ($this->hasReadline) { readline_read_history($this->history); readline_completion_function(array($this, 'autocompleter')); } $this->output->writeln($this->getHeader()); $php = null; if ($this->processIsolation) { $finder = new PhpExecutableFinder(); $php = $finder->find(); $this->output->writeln(<<Running with process isolation, you should consider this: * each command is executed as separate process, * commands don't support interactivity, all params must be passed explicitly, * commands output is not colorized. EOF ); } while (true) { $command = $this->readline(); if (false === $command) { $this->output->writeln("\n"); break; } if ($this->hasReadline) { readline_add_history($command); readline_write_history($this->history); } if ($this->processIsolation) { $pb = new ProcessBuilder(); $process = $pb ->add($php) ->add($_SERVER['argv'][0]) ->add($command) ->inheritEnvironmentVariables(true) ->getProcess() ; $output = $this->output; $process->run(function ($type, $data) use ($output) { $output->writeln($data); }); $ret = $process->getExitCode(); } else { $ret = $this->application->run(new StringInput($command), $this->output); } if (0 !== $ret) { $this->output->writeln(sprintf('The command terminated with an error status (%s)', $ret)); } } } /** * Returns the shell header. * * @return string The header string */ protected function getHeader() { return <<{$this->application->getName()} shell ({$this->application->getVersion()}). At the prompt, type help for some help, or list to get a list of available commands. To exit the shell, type ^D. EOF; } /** * Renders a prompt. * * @return string The prompt */ protected function getPrompt() { // using the formatter here is required when using readline return $this->output->getFormatter()->format($this->application->getName().' > '); } protected function getOutput() { return $this->output; } protected function getApplication() { return $this->application; } /** * Tries to return autocompletion for the current entered text. * * @param string $text The last segment of the entered text * * @return bool|array A list of guessed strings or true */ private function autocompleter($text) { $info = readline_info(); $text = substr($info['line_buffer'], 0, $info['end']); if ($info['point'] !== $info['end']) { return true; } // task name? if (false === strpos($text, ' ') || !$text) { return array_keys($this->application->all()); } // options and arguments? try { $command = $this->application->find(substr($text, 0, strpos($text, ' '))); } catch (\Exception $e) { return true; } $list = array('--help'); foreach ($command->getDefinition()->getOptions() as $option) { $list[] = '--'.$option->getName(); } return $list; } /** * Reads a single line from standard input. * * @return string The single line from standard input */ private function readline() { if ($this->hasReadline) { $line = readline($this->getPrompt()); } else { $this->output->write($this->getPrompt()); $line = fgets(STDIN, 1024); $line = (false === $line || '' === $line) ? false : rtrim($line); } return $line; } public function getProcessIsolation() { return $this->processIsolation; } public function setProcessIsolation($processIsolation) { $this->processIsolation = (bool) $processIsolation; if ($this->processIsolation && !class_exists('Symfony\\Component\\Process\\Process')) { throw new \RuntimeException('Unable to isolate processes as the Symfony Process Component is not installed.'); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Tester; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Eases the testing of console commands. * * @author Fabien Potencier */ class CommandTester { private $command; private $input; private $output; private $statusCode; /** * Constructor. * * @param Command $command A Command instance to test. */ public function __construct(Command $command) { $this->command = $command; } /** * Executes the command. * * Available execution options: * * * interactive: Sets the input interactive flag * * decorated: Sets the output decorated flag * * verbosity: Sets the output verbosity flag * * @param array $input An array of command arguments and options * @param array $options An array of execution options * * @return int The command exit code */ public function execute(array $input, array $options = array()) { // set the command name automatically if the application requires // this argument and no command name was passed if (!isset($input['command']) && (null !== $application = $this->command->getApplication()) && $application->getDefinition()->hasArgument('command') ) { $input = array_merge(array('command' => $this->command->getName()), $input); } $this->input = new ArrayInput($input); if (isset($options['interactive'])) { $this->input->setInteractive($options['interactive']); } $this->output = new StreamOutput(fopen('php://memory', 'w', false)); if (isset($options['decorated'])) { $this->output->setDecorated($options['decorated']); } if (isset($options['verbosity'])) { $this->output->setVerbosity($options['verbosity']); } return $this->statusCode = $this->command->run($this->input, $this->output); } /** * Gets the display returned by the last execution of the command. * * @param bool $normalize Whether to normalize end of lines to \n or not * * @return string The display */ public function getDisplay($normalize = false) { rewind($this->output->getStream()); $display = stream_get_contents($this->output->getStream()); if ($normalize) { $display = str_replace(PHP_EOL, "\n", $display); } return $display; } /** * Gets the input instance used by the last execution of the command. * * @return InputInterface The current input instance */ public function getInput() { return $this->input; } /** * Gets the output instance used by the last execution of the command. * * @return OutputInterface The current output instance */ public function getOutput() { return $this->output; } /** * Gets the status code returned by the last execution of the application. * * @return int The status code */ public function getStatusCode() { return $this->statusCode; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Tester; use Symfony\Component\Console\Application; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\StreamOutput; /** * Eases the testing of console applications. * * When testing an application, don't forget to disable the auto exit flag: * * $application = new Application(); * $application->setAutoExit(false); * * @author Fabien Potencier */ class ApplicationTester { private $application; private $input; private $output; private $statusCode; /** * Constructor. * * @param Application $application An Application instance to test. */ public function __construct(Application $application) { $this->application = $application; } /** * Executes the application. * * Available options: * * * interactive: Sets the input interactive flag * * decorated: Sets the output decorated flag * * verbosity: Sets the output verbosity flag * * @param array $input An array of arguments and options * @param array $options An array of options * * @return int The command exit code */ public function run(array $input, $options = array()) { $this->input = new ArrayInput($input); if (isset($options['interactive'])) { $this->input->setInteractive($options['interactive']); } $this->output = new StreamOutput(fopen('php://memory', 'w', false)); if (isset($options['decorated'])) { $this->output->setDecorated($options['decorated']); } if (isset($options['verbosity'])) { $this->output->setVerbosity($options['verbosity']); } return $this->statusCode = $this->application->run($this->input, $this->output); } /** * Gets the display returned by the last execution of the application. * * @param bool $normalize Whether to normalize end of lines to \n or not * * @return string The display */ public function getDisplay($normalize = false) { rewind($this->output->getStream()); $display = stream_get_contents($this->output->getStream()); if ($normalize) { $display = str_replace(PHP_EOL, "\n", $display); } return $display; } /** * Gets the input instance used by the last execution of the application. * * @return InputInterface The current input instance */ public function getInput() { return $this->input; } /** * Gets the output instance used by the last execution of the application. * * @return OutputInterface The current output instance */ public function getOutput() { return $this->output; } /** * Gets the status code returned by the last execution of the application. * * @return int The status code */ public function getStatusCode() { return $this->statusCode; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * @author Jean-François Simon * * @internal */ abstract class Descriptor implements DescriptorInterface { /** * @var OutputInterface */ private $output; /** * {@inheritdoc} */ public function describe(OutputInterface $output, $object, array $options = array()) { $this->output = $output; switch (true) { case $object instanceof InputArgument: $this->describeInputArgument($object, $options); break; case $object instanceof InputOption: $this->describeInputOption($object, $options); break; case $object instanceof InputDefinition: $this->describeInputDefinition($object, $options); break; case $object instanceof Command: $this->describeCommand($object, $options); break; case $object instanceof Application: $this->describeApplication($object, $options); break; default: throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object))); } } /** * Writes content to output. * * @param string $content * @param bool $decorated */ protected function write($content, $decorated = false) { $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); } /** * Describes an InputArgument instance. * * @param InputArgument $argument * @param array $options * * @return string|mixed */ abstract protected function describeInputArgument(InputArgument $argument, array $options = array()); /** * Describes an InputOption instance. * * @param InputOption $option * @param array $options * * @return string|mixed */ abstract protected function describeInputOption(InputOption $option, array $options = array()); /** * Describes an InputDefinition instance. * * @param InputDefinition $definition * @param array $options * * @return string|mixed */ abstract protected function describeInputDefinition(InputDefinition $definition, array $options = array()); /** * Describes a Command instance. * * @param Command $command * @param array $options * * @return string|mixed */ abstract protected function describeCommand(Command $command, array $options = array()); /** * Describes an Application instance. * * @param Application $application * @param array $options * * @return string|mixed */ abstract protected function describeApplication(Application $application, array $options = array()); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; /** * Markdown descriptor. * * @author Jean-François Simon * * @internal */ class MarkdownDescriptor extends Descriptor { /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = array()) { $this->write( '**'.$argument->getName().':**'."\n\n" .'* Name: '.($argument->getName() ?: '')."\n" .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n" .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n" .'* Description: '.preg_replace('/\s*[\r\n]\s*/', "\n ", $argument->getDescription() ?: '')."\n" .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`' ); } /** * {@inheritdoc} */ protected function describeInputOption(InputOption $option, array $options = array()) { $this->write( '**'.$option->getName().':**'."\n\n" .'* Name: `--'.$option->getName().'`'."\n" .'* Shortcut: '.($option->getShortcut() ? '`-'.implode('|-', explode('|', $option->getShortcut())).'`' : '')."\n" .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n" .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n" .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n" .'* Description: '.preg_replace('/\s*[\r\n]\s*/', "\n ", $option->getDescription() ?: '')."\n" .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`' ); } /** * {@inheritdoc} */ protected function describeInputDefinition(InputDefinition $definition, array $options = array()) { if ($showArguments = count($definition->getArguments()) > 0) { $this->write('### Arguments:'); foreach ($definition->getArguments() as $argument) { $this->write("\n\n"); $this->write($this->describeInputArgument($argument)); } } if (count($definition->getOptions()) > 0) { if ($showArguments) { $this->write("\n\n"); } $this->write('### Options:'); foreach ($definition->getOptions() as $option) { $this->write("\n\n"); $this->write($this->describeInputOption($option)); } } } /** * {@inheritdoc} */ protected function describeCommand(Command $command, array $options = array()) { $command->getSynopsis(); $command->mergeApplicationDefinition(false); $this->write( $command->getName()."\n" .str_repeat('-', strlen($command->getName()))."\n\n" .'* Description: '.($command->getDescription() ?: '')."\n" .'* Usage:'."\n\n" .array_reduce(array_merge(array($command->getSynopsis()), $command->getAliases(), $command->getUsages()), function ($carry, $usage) { return $carry .= ' * `'.$usage.'`'."\n"; }) ); if ($help = $command->getProcessedHelp()) { $this->write("\n"); $this->write($help); } if ($command->getNativeDefinition()) { $this->write("\n\n"); $this->describeInputDefinition($command->getNativeDefinition()); } } /** * {@inheritdoc} */ protected function describeApplication(Application $application, array $options = array()) { $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; $description = new ApplicationDescription($application, $describedNamespace); $this->write($application->getName()."\n".str_repeat('=', strlen($application->getName()))); foreach ($description->getNamespaces() as $namespace) { if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { $this->write("\n\n"); $this->write('**'.$namespace['id'].':**'); } $this->write("\n\n"); $this->write(implode("\n", array_map(function ($commandName) { return '* '.$commandName; }, $namespace['commands']))); } foreach ($description->getCommands() as $command) { $this->write("\n\n"); $this->write($this->describeCommand($command)); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Output\OutputInterface; /** * Descriptor interface. * * @author Jean-François Simon */ interface DescriptorInterface { /** * Describes an InputArgument instance. * * @param OutputInterface $output * @param object $object * @param array $options */ public function describe(OutputInterface $output, $object, array $options = array()); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; /** * XML descriptor. * * @author Jean-François Simon * * @internal */ class XmlDescriptor extends Descriptor { /** * @param InputDefinition $definition * * @return \DOMDocument */ public function getInputDefinitionDocument(InputDefinition $definition) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($definitionXML = $dom->createElement('definition')); $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments')); foreach ($definition->getArguments() as $argument) { $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument)); } $definitionXML->appendChild($optionsXML = $dom->createElement('options')); foreach ($definition->getOptions() as $option) { $this->appendDocument($optionsXML, $this->getInputOptionDocument($option)); } return $dom; } /** * @param Command $command * * @return \DOMDocument */ public function getCommandDocument(Command $command) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($commandXML = $dom->createElement('command')); $command->getSynopsis(); $command->mergeApplicationDefinition(false); $commandXML->setAttribute('id', $command->getName()); $commandXML->setAttribute('name', $command->getName()); $commandXML->appendChild($usagesXML = $dom->createElement('usages')); foreach (array_merge(array($command->getSynopsis()), $command->getAliases(), $command->getUsages()) as $usage) { $usagesXML->appendChild($dom->createElement('usage', $usage)); } $commandXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription()))); $commandXML->appendChild($helpXML = $dom->createElement('help')); $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp()))); $definitionXML = $this->getInputDefinitionDocument($command->getNativeDefinition()); $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0)); return $dom; } /** * @param Application $application * @param string|null $namespace * * @return \DOMDocument */ public function getApplicationDocument(Application $application, $namespace = null) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($rootXml = $dom->createElement('symfony')); if ($application->getName() !== 'UNKNOWN') { $rootXml->setAttribute('name', $application->getName()); if ($application->getVersion() !== 'UNKNOWN') { $rootXml->setAttribute('version', $application->getVersion()); } } $rootXml->appendChild($commandsXML = $dom->createElement('commands')); $description = new ApplicationDescription($application, $namespace); if ($namespace) { $commandsXML->setAttribute('namespace', $namespace); } foreach ($description->getCommands() as $command) { $this->appendDocument($commandsXML, $this->getCommandDocument($command)); } if (!$namespace) { $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces')); foreach ($description->getNamespaces() as $namespaceDescription) { $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace')); $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']); foreach ($namespaceDescription['commands'] as $name) { $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command')); $commandXML->appendChild($dom->createTextNode($name)); } } } return $dom; } /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = array()) { $this->writeDocument($this->getInputArgumentDocument($argument)); } /** * {@inheritdoc} */ protected function describeInputOption(InputOption $option, array $options = array()) { $this->writeDocument($this->getInputOptionDocument($option)); } /** * {@inheritdoc} */ protected function describeInputDefinition(InputDefinition $definition, array $options = array()) { $this->writeDocument($this->getInputDefinitionDocument($definition)); } /** * {@inheritdoc} */ protected function describeCommand(Command $command, array $options = array()) { $this->writeDocument($this->getCommandDocument($command)); } /** * {@inheritdoc} */ protected function describeApplication(Application $application, array $options = array()) { $this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null)); } /** * Appends document children to parent node. * * @param \DOMNode $parentNode * @param \DOMNode $importedParent */ private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent) { foreach ($importedParent->childNodes as $childNode) { $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); } } /** * Writes DOM document. * * @param \DOMDocument $dom * * @return \DOMDocument|string */ private function writeDocument(\DOMDocument $dom) { $dom->formatOutput = true; $this->write($dom->saveXML()); } /** * @param InputArgument $argument * * @return \DOMDocument */ private function getInputArgumentDocument(InputArgument $argument) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($objectXML = $dom->createElement('argument')); $objectXML->setAttribute('name', $argument->getName()); $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0); $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0); $objectXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode($argument->getDescription())); $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); $defaults = is_array($argument->getDefault()) ? $argument->getDefault() : (is_bool($argument->getDefault()) ? array(var_export($argument->getDefault(), true)) : ($argument->getDefault() ? array($argument->getDefault()) : array())); foreach ($defaults as $default) { $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); $defaultXML->appendChild($dom->createTextNode($default)); } return $dom; } /** * @param InputOption $option * * @return \DOMDocument */ private function getInputOptionDocument(InputOption $option) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($objectXML = $dom->createElement('option')); $objectXML->setAttribute('name', '--'.$option->getName()); $pos = strpos($option->getShortcut(), '|'); if (false !== $pos) { $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos)); $objectXML->setAttribute('shortcuts', '-'.implode('|-', explode('|', $option->getShortcut()))); } else { $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : ''); } $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0); $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0); $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0); $objectXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode($option->getDescription())); if ($option->acceptValue()) { $defaults = is_array($option->getDefault()) ? $option->getDefault() : (is_bool($option->getDefault()) ? array(var_export($option->getDefault(), true)) : ($option->getDefault() ? array($option->getDefault()) : array())); $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); if (!empty($defaults)) { foreach ($defaults as $default) { $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); $defaultXML->appendChild($dom->createTextNode($default)); } } } return $dom; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; /** * Text descriptor. * * @author Jean-François Simon * * @internal */ class TextDescriptor extends Descriptor { /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = array()) { if (null !== $argument->getDefault() && (!is_array($argument->getDefault()) || count($argument->getDefault()))) { $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); } else { $default = ''; } $totalWidth = isset($options['total_width']) ? $options['total_width'] : strlen($argument->getName()); $spacingWidth = $totalWidth - strlen($argument->getName()) + 2; $this->writeText(sprintf(' %s%s%s%s', $argument->getName(), str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + + + 2 spaces preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 17), $argument->getDescription()), $default ), $options); } /** * {@inheritdoc} */ protected function describeInputOption(InputOption $option, array $options = array()) { if ($option->acceptValue() && null !== $option->getDefault() && (!is_array($option->getDefault()) || count($option->getDefault()))) { $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); } else { $default = ''; } $value = ''; if ($option->acceptValue()) { $value = '='.strtoupper($option->getName()); if ($option->isValueOptional()) { $value = '['.$value.']'; } } $totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions(array($option)); $synopsis = sprintf('%s%s', $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', sprintf('--%s%s', $option->getName(), $value) ); $spacingWidth = $totalWidth - strlen($synopsis) + 2; $this->writeText(sprintf(' %s%s%s%s%s', $synopsis, str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + + + 2 spaces preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 17), $option->getDescription()), $default, $option->isArray() ? ' (multiple values allowed)' : '' ), $options); } /** * {@inheritdoc} */ protected function describeInputDefinition(InputDefinition $definition, array $options = array()) { $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); foreach ($definition->getArguments() as $argument) { $totalWidth = max($totalWidth, strlen($argument->getName())); } if ($definition->getArguments()) { $this->writeText('Arguments:', $options); $this->writeText("\n"); foreach ($definition->getArguments() as $argument) { $this->describeInputArgument($argument, array_merge($options, array('total_width' => $totalWidth))); $this->writeText("\n"); } } if ($definition->getArguments() && $definition->getOptions()) { $this->writeText("\n"); } if ($definition->getOptions()) { $laterOptions = array(); $this->writeText('Options:', $options); foreach ($definition->getOptions() as $option) { if (strlen($option->getShortcut()) > 1) { $laterOptions[] = $option; continue; } $this->writeText("\n"); $this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth))); } foreach ($laterOptions as $option) { $this->writeText("\n"); $this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth))); } } } /** * {@inheritdoc} */ protected function describeCommand(Command $command, array $options = array()) { $command->getSynopsis(true); $command->getSynopsis(false); $command->mergeApplicationDefinition(false); $this->writeText('Usage:', $options); foreach (array_merge(array($command->getSynopsis(true)), $command->getAliases(), $command->getUsages()) as $usage) { $this->writeText("\n"); $this->writeText(' '.$usage, $options); } $this->writeText("\n"); $definition = $command->getNativeDefinition(); if ($definition->getOptions() || $definition->getArguments()) { $this->writeText("\n"); $this->describeInputDefinition($definition, $options); $this->writeText("\n"); } if ($help = $command->getProcessedHelp()) { $this->writeText("\n"); $this->writeText('Help:', $options); $this->writeText("\n"); $this->writeText(' '.str_replace("\n", "\n ", $help), $options); $this->writeText("\n"); } } /** * {@inheritdoc} */ protected function describeApplication(Application $application, array $options = array()) { $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; $description = new ApplicationDescription($application, $describedNamespace); if (isset($options['raw_text']) && $options['raw_text']) { $width = $this->getColumnWidth($description->getCommands()); foreach ($description->getCommands() as $command) { $this->writeText(sprintf("%-${width}s %s", $command->getName(), $command->getDescription()), $options); $this->writeText("\n"); } } else { if ('' != $help = $application->getHelp()) { $this->writeText("$help\n\n", $options); } $this->writeText("Usage:\n", $options); $this->writeText(" command [options] [arguments]\n\n", $options); $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options); $this->writeText("\n"); $this->writeText("\n"); $width = $this->getColumnWidth($description->getCommands()); if ($describedNamespace) { $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); } else { $this->writeText('Available commands:', $options); } // add commands by namespace foreach ($description->getNamespaces() as $namespace) { if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { $this->writeText("\n"); $this->writeText(' '.$namespace['id'].'', $options); } foreach ($namespace['commands'] as $name) { $this->writeText("\n"); $spacingWidth = $width - strlen($name); $this->writeText(sprintf(' %s%s%s', $name, str_repeat(' ', $spacingWidth), $description->getCommand($name)->getDescription()), $options); } } $this->writeText("\n"); } } /** * {@inheritdoc} */ private function writeText($content, array $options = array()) { $this->write( isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : true ); } /** * Formats input option/argument default value. * * @param mixed $default * * @return string */ private function formatDefaultValue($default) { if (PHP_VERSION_ID < 50400) { return str_replace('\/', '/', json_encode($default)); } return json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } /** * @param Command[] $commands * * @return int */ private function getColumnWidth(array $commands) { $widths = array(); foreach ($commands as $command) { $widths[] = strlen($command->getName()); foreach ($command->getAliases() as $alias) { $widths[] = strlen($alias); } } return max($widths) + 2; } /** * @param InputOption[] $options * * @return int */ private function calculateTotalWidthForOptions($options) { $totalWidth = 0; foreach ($options as $option) { // "-" + shortcut + ", --" + name $nameLength = 1 + max(strlen($option->getShortcut()), 1) + 4 + strlen($option->getName()); if ($option->acceptValue()) { $valueLength = 1 + strlen($option->getName()); // = + value $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] $nameLength += $valueLength; } $totalWidth = max($totalWidth, $nameLength); } return $totalWidth; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; /** * JSON descriptor. * * @author Jean-François Simon * * @internal */ class JsonDescriptor extends Descriptor { /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = array()) { $this->writeData($this->getInputArgumentData($argument), $options); } /** * {@inheritdoc} */ protected function describeInputOption(InputOption $option, array $options = array()) { $this->writeData($this->getInputOptionData($option), $options); } /** * {@inheritdoc} */ protected function describeInputDefinition(InputDefinition $definition, array $options = array()) { $this->writeData($this->getInputDefinitionData($definition), $options); } /** * {@inheritdoc} */ protected function describeCommand(Command $command, array $options = array()) { $this->writeData($this->getCommandData($command), $options); } /** * {@inheritdoc} */ protected function describeApplication(Application $application, array $options = array()) { $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; $description = new ApplicationDescription($application, $describedNamespace); $commands = array(); foreach ($description->getCommands() as $command) { $commands[] = $this->getCommandData($command); } $data = $describedNamespace ? array('commands' => $commands, 'namespace' => $describedNamespace) : array('commands' => $commands, 'namespaces' => array_values($description->getNamespaces())); $this->writeData($data, $options); } /** * Writes data as json. * * @param array $data * @param array $options * * @return array|string */ private function writeData(array $data, array $options) { $this->write(json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0)); } /** * @param InputArgument $argument * * @return array */ private function getInputArgumentData(InputArgument $argument) { return array( 'name' => $argument->getName(), 'is_required' => $argument->isRequired(), 'is_array' => $argument->isArray(), 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()), 'default' => $argument->getDefault(), ); } /** * @param InputOption $option * * @return array */ private function getInputOptionData(InputOption $option) { return array( 'name' => '--'.$option->getName(), 'shortcut' => $option->getShortcut() ? '-'.implode('|-', explode('|', $option->getShortcut())) : '', 'accept_value' => $option->acceptValue(), 'is_value_required' => $option->isValueRequired(), 'is_multiple' => $option->isArray(), 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()), 'default' => $option->getDefault(), ); } /** * @param InputDefinition $definition * * @return array */ private function getInputDefinitionData(InputDefinition $definition) { $inputArguments = array(); foreach ($definition->getArguments() as $name => $argument) { $inputArguments[$name] = $this->getInputArgumentData($argument); } $inputOptions = array(); foreach ($definition->getOptions() as $name => $option) { $inputOptions[$name] = $this->getInputOptionData($option); } return array('arguments' => $inputArguments, 'options' => $inputOptions); } /** * @param Command $command * * @return array */ private function getCommandData(Command $command) { $command->getSynopsis(); $command->mergeApplicationDefinition(false); return array( 'name' => $command->getName(), 'usage' => array_merge(array($command->getSynopsis()), $command->getUsages(), $command->getAliases()), 'description' => $command->getDescription(), 'help' => $command->getProcessedHelp(), 'definition' => $this->getInputDefinitionData($command->getNativeDefinition()), ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; /** * @author Jean-François Simon * * @internal */ class ApplicationDescription { const GLOBAL_NAMESPACE = '_global'; /** * @var Application */ private $application; /** * @var null|string */ private $namespace; /** * @var array */ private $namespaces; /** * @var Command[] */ private $commands; /** * @var Command[] */ private $aliases; /** * Constructor. * * @param Application $application * @param string|null $namespace */ public function __construct(Application $application, $namespace = null) { $this->application = $application; $this->namespace = $namespace; } /** * @return array */ public function getNamespaces() { if (null === $this->namespaces) { $this->inspectApplication(); } return $this->namespaces; } /** * @return Command[] */ public function getCommands() { if (null === $this->commands) { $this->inspectApplication(); } return $this->commands; } /** * @param string $name * * @return Command * * @throws \InvalidArgumentException */ public function getCommand($name) { if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name)); } return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name]; } private function inspectApplication() { $this->commands = array(); $this->namespaces = array(); $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null); foreach ($this->sortCommands($all) as $namespace => $commands) { $names = array(); /** @var Command $command */ foreach ($commands as $name => $command) { if (!$command->getName()) { continue; } if ($command->getName() === $name) { $this->commands[$name] = $command; } else { $this->aliases[$name] = $command; } $names[] = $name; } $this->namespaces[$namespace] = array('id' => $namespace, 'commands' => $names); } } /** * @param array $commands * * @return array */ private function sortCommands(array $commands) { $namespacedCommands = array(); foreach ($commands as $name => $command) { $key = $this->application->extractNamespace($name, 1); if (!$key) { $key = '_global'; } $namespacedCommands[$key][$name] = $command; } ksort($namespacedCommands); foreach ($namespacedCommands as &$commandsSet) { ksort($commandsSet); } // unset reference to keep scope clear unset($commandsSet); return $namespacedCommands; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * ConsoleOutput is the default class for all CLI output. It uses STDOUT. * * This class is a convenient wrapper around `StreamOutput`. * * $output = new ConsoleOutput(); * * This is equivalent to: * * $output = new StreamOutput(fopen('php://stdout', 'w')); * * @author Fabien Potencier * * @api */ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface { /** * @var StreamOutput */ private $stderr; /** * Constructor. * * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) * * @api */ public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null) { parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter); $actualDecorated = $this->isDecorated(); $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter()); if (null === $decorated) { $this->setDecorated($actualDecorated && $this->stderr->isDecorated()); } } /** * {@inheritdoc} */ public function setDecorated($decorated) { parent::setDecorated($decorated); $this->stderr->setDecorated($decorated); } /** * {@inheritdoc} */ public function setFormatter(OutputFormatterInterface $formatter) { parent::setFormatter($formatter); $this->stderr->setFormatter($formatter); } /** * {@inheritdoc} */ public function setVerbosity($level) { parent::setVerbosity($level); $this->stderr->setVerbosity($level); } /** * {@inheritdoc} */ public function getErrorOutput() { return $this->stderr; } /** * {@inheritdoc} */ public function setErrorOutput(OutputInterface $error) { $this->stderr = $error; } /** * Returns true if current environment supports writing console output to * STDOUT. * * @return bool */ protected function hasStdoutSupport() { return false === $this->isRunningOS400(); } /** * Returns true if current environment supports writing console output to * STDERR. * * @return bool */ protected function hasStderrSupport() { return false === $this->isRunningOS400(); } /** * Checks if current executing environment is IBM iSeries (OS400), which * doesn't properly convert character-encodings between ASCII to EBCDIC. * * @return bool */ private function isRunningOS400() { return 'OS400' === php_uname('s'); } /** * @return resource */ private function openOutputStream() { $outputStream = $this->hasStdoutSupport() ? 'php://stdout' : 'php://output'; return @fopen($outputStream, 'w') ?: fopen('php://output', 'w'); } /** * @return resource */ private function openErrorStream() { $errorStream = $this->hasStderrSupport() ? 'php://stderr' : 'php://output'; return fopen($errorStream, 'w'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; /** * ConsoleOutputInterface is the interface implemented by ConsoleOutput class. * This adds information about stderr output stream. * * @author Dariusz Górecki */ interface ConsoleOutputInterface extends OutputInterface { /** * Gets the OutputInterface for errors. * * @return OutputInterface */ public function getErrorOutput(); /** * Sets the OutputInterface used for errors. * * @param OutputInterface $error */ public function setErrorOutput(OutputInterface $error); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * OutputInterface is the interface implemented by all Output classes. * * @author Fabien Potencier * * @api */ interface OutputInterface { const VERBOSITY_QUIET = 0; const VERBOSITY_NORMAL = 1; const VERBOSITY_VERBOSE = 2; const VERBOSITY_VERY_VERBOSE = 3; const VERBOSITY_DEBUG = 4; const OUTPUT_NORMAL = 0; const OUTPUT_RAW = 1; const OUTPUT_PLAIN = 2; /** * Writes a message to the output. * * @param string|array $messages The message as an array of lines or a single string * @param bool $newline Whether to add a newline * @param int $type The type of output (one of the OUTPUT constants) * * @throws \InvalidArgumentException When unknown output type is given * * @api */ public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL); /** * Writes a message to the output and adds a newline at the end. * * @param string|array $messages The message as an array of lines or a single string * @param int $type The type of output (one of the OUTPUT constants) * * @throws \InvalidArgumentException When unknown output type is given * * @api */ public function writeln($messages, $type = self::OUTPUT_NORMAL); /** * Sets the verbosity of the output. * * @param int $level The level of verbosity (one of the VERBOSITY constants) * * @api */ public function setVerbosity($level); /** * Gets the current verbosity of the output. * * @return int The current level of verbosity (one of the VERBOSITY constants) * * @api */ public function getVerbosity(); /** * Sets the decorated flag. * * @param bool $decorated Whether to decorate the messages * * @api */ public function setDecorated($decorated); /** * Gets the decorated flag. * * @return bool true if the output will decorate messages, false otherwise * * @api */ public function isDecorated(); /** * Sets output formatter. * * @param OutputFormatterInterface $formatter * * @api */ public function setFormatter(OutputFormatterInterface $formatter); /** * Returns current output formatter instance. * * @return OutputFormatterInterface * * @api */ public function getFormatter(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * StreamOutput writes the output to a given stream. * * Usage: * * $output = new StreamOutput(fopen('php://stdout', 'w')); * * As `StreamOutput` can use any stream, you can also use a file: * * $output = new StreamOutput(fopen('/path/to/output.log', 'a', false)); * * @author Fabien Potencier * * @api */ class StreamOutput extends Output { private $stream; /** * Constructor. * * @param resource $stream A stream resource * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) * * @throws \InvalidArgumentException When first argument is not a real stream * * @api */ public function __construct($stream, $verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null) { if (!is_resource($stream) || 'stream' !== get_resource_type($stream)) { throw new \InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); } $this->stream = $stream; if (null === $decorated) { $decorated = $this->hasColorSupport(); } parent::__construct($verbosity, $decorated, $formatter); } /** * Gets the stream attached to this StreamOutput instance. * * @return resource A stream resource */ public function getStream() { return $this->stream; } /** * {@inheritdoc} */ protected function doWrite($message, $newline) { if (false === @fwrite($this->stream, $message.($newline ? PHP_EOL : ''))) { // should never happen throw new \RuntimeException('Unable to write output.'); } fflush($this->stream); } /** * Returns true if the stream supports colorization. * * Colorization is disabled if not supported by the stream: * * - Windows without Ansicon and ConEmu * - non tty consoles * * @return bool true if the stream supports colorization, false otherwise */ protected function hasColorSupport() { if (DIRECTORY_SEPARATOR === '\\') { return false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI'); } return function_exists('posix_isatty') && @posix_isatty($this->stream); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Formatter\OutputFormatterInterface; use Symfony\Component\Console\Formatter\OutputFormatter; /** * Base class for output classes. * * There are five levels of verbosity: * * * normal: no option passed (normal output) * * verbose: -v (more output) * * very verbose: -vv (highly extended output) * * debug: -vvv (all debug output) * * quiet: -q (no output) * * @author Fabien Potencier * * @api */ abstract class Output implements OutputInterface { private $verbosity; private $formatter; /** * Constructor. * * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) * @param bool $decorated Whether to decorate messages * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) * * @api */ public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = false, OutputFormatterInterface $formatter = null) { $this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity; $this->formatter = $formatter ?: new OutputFormatter(); $this->formatter->setDecorated($decorated); } /** * {@inheritdoc} */ public function setFormatter(OutputFormatterInterface $formatter) { $this->formatter = $formatter; } /** * {@inheritdoc} */ public function getFormatter() { return $this->formatter; } /** * {@inheritdoc} */ public function setDecorated($decorated) { $this->formatter->setDecorated($decorated); } /** * {@inheritdoc} */ public function isDecorated() { return $this->formatter->isDecorated(); } /** * {@inheritdoc} */ public function setVerbosity($level) { $this->verbosity = (int) $level; } /** * {@inheritdoc} */ public function getVerbosity() { return $this->verbosity; } public function isQuiet() { return self::VERBOSITY_QUIET === $this->verbosity; } public function isVerbose() { return self::VERBOSITY_VERBOSE <= $this->verbosity; } public function isVeryVerbose() { return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; } public function isDebug() { return self::VERBOSITY_DEBUG <= $this->verbosity; } /** * {@inheritdoc} */ public function writeln($messages, $type = self::OUTPUT_NORMAL) { $this->write($messages, true, $type); } /** * {@inheritdoc} */ public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) { if (self::VERBOSITY_QUIET === $this->verbosity) { return; } $messages = (array) $messages; foreach ($messages as $message) { switch ($type) { case OutputInterface::OUTPUT_NORMAL: $message = $this->formatter->format($message); break; case OutputInterface::OUTPUT_RAW: break; case OutputInterface::OUTPUT_PLAIN: $message = strip_tags($this->formatter->format($message)); break; default: throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type)); } $this->doWrite($message, $newline); } } /** * Writes a message to the output. * * @param string $message A message to write to the output * @param bool $newline Whether to add a newline or not */ abstract protected function doWrite($message, $newline); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * NullOutput suppresses all output. * * $output = new NullOutput(); * * @author Fabien Potencier * @author Tobias Schultze * * @api */ class NullOutput implements OutputInterface { /** * {@inheritdoc} */ public function setFormatter(OutputFormatterInterface $formatter) { // do nothing } /** * {@inheritdoc} */ public function getFormatter() { // to comply with the interface we must return a OutputFormatterInterface return new OutputFormatter(); } /** * {@inheritdoc} */ public function setDecorated($decorated) { // do nothing } /** * {@inheritdoc} */ public function isDecorated() { return false; } /** * {@inheritdoc} */ public function setVerbosity($level) { // do nothing } /** * {@inheritdoc} */ public function getVerbosity() { return self::VERBOSITY_QUIET; } public function isQuiet() { return true; } public function isVerbose() { return false; } public function isVeryVerbose() { return false; } public function isDebug() { return false; } /** * {@inheritdoc} */ public function writeln($messages, $type = self::OUTPUT_NORMAL) { // do nothing } /** * {@inheritdoc} */ public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) { // do nothing } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; /** * @author Jean-François Simon */ class BufferedOutput extends Output { /** * @var string */ private $buffer = ''; /** * Empties buffer and returns its content. * * @return string */ public function fetch() { $content = $this->buffer; $this->buffer = ''; return $content; } /** * {@inheritdoc} */ protected function doWrite($message, $newline) { $this->buffer .= $message; if ($newline) { $this->buffer .= "\n"; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; /** * Defines the styles for a Table. * * @author Fabien Potencier * @author Саша Стаменковић */ class TableStyle { private $paddingChar = ' '; private $horizontalBorderChar = '-'; private $verticalBorderChar = '|'; private $crossingChar = '+'; private $cellHeaderFormat = '%s'; private $cellRowFormat = '%s'; private $cellRowContentFormat = ' %s '; private $borderFormat = '%s'; private $padType = STR_PAD_RIGHT; /** * Sets padding character, used for cell padding. * * @param string $paddingChar * * @return TableStyle */ public function setPaddingChar($paddingChar) { if (!$paddingChar) { throw new \LogicException('The padding char must not be empty'); } $this->paddingChar = $paddingChar; return $this; } /** * Gets padding character, used for cell padding. * * @return string */ public function getPaddingChar() { return $this->paddingChar; } /** * Sets horizontal border character. * * @param string $horizontalBorderChar * * @return TableStyle */ public function setHorizontalBorderChar($horizontalBorderChar) { $this->horizontalBorderChar = $horizontalBorderChar; return $this; } /** * Gets horizontal border character. * * @return string */ public function getHorizontalBorderChar() { return $this->horizontalBorderChar; } /** * Sets vertical border character. * * @param string $verticalBorderChar * * @return TableStyle */ public function setVerticalBorderChar($verticalBorderChar) { $this->verticalBorderChar = $verticalBorderChar; return $this; } /** * Gets vertical border character. * * @return string */ public function getVerticalBorderChar() { return $this->verticalBorderChar; } /** * Sets crossing character. * * @param string $crossingChar * * @return TableStyle */ public function setCrossingChar($crossingChar) { $this->crossingChar = $crossingChar; return $this; } /** * Gets crossing character. * * @return string $crossingChar */ public function getCrossingChar() { return $this->crossingChar; } /** * Sets header cell format. * * @param string $cellHeaderFormat * * @return TableStyle */ public function setCellHeaderFormat($cellHeaderFormat) { $this->cellHeaderFormat = $cellHeaderFormat; return $this; } /** * Gets header cell format. * * @return string */ public function getCellHeaderFormat() { return $this->cellHeaderFormat; } /** * Sets row cell format. * * @param string $cellRowFormat * * @return TableStyle */ public function setCellRowFormat($cellRowFormat) { $this->cellRowFormat = $cellRowFormat; return $this; } /** * Gets row cell format. * * @return string */ public function getCellRowFormat() { return $this->cellRowFormat; } /** * Sets row cell content format. * * @param string $cellRowContentFormat * * @return TableStyle */ public function setCellRowContentFormat($cellRowContentFormat) { $this->cellRowContentFormat = $cellRowContentFormat; return $this; } /** * Gets row cell content format. * * @return string */ public function getCellRowContentFormat() { return $this->cellRowContentFormat; } /** * Sets table border format. * * @param string $borderFormat * * @return TableStyle */ public function setBorderFormat($borderFormat) { $this->borderFormat = $borderFormat; return $this; } /** * Gets table border format. * * @return string */ public function getBorderFormat() { return $this->borderFormat; } /** * Sets cell padding type. * * @param int $padType STR_PAD_* * * @return TableStyle */ public function setPadType($padType) { if (!in_array($padType, array(STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH), true)) { throw new \InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).'); } $this->padType = $padType; return $this; } /** * Gets cell padding type. * * @return int */ public function getPadType() { return $this->padType; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; /** * Marks a row as being a separator. * * @author Fabien Potencier */ class TableSeparator extends TableCell { /** * @param string $value * @param array $options */ public function __construct(array $options = array()) { parent::__construct('', $options); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Question\Question; use Symfony\Component\Console\Question\ChoiceQuestion; /** * The QuestionHelper class provides helpers to interact with the user. * * @author Fabien Potencier */ class QuestionHelper extends Helper { private $inputStream; private static $shell; private static $stty; /** * Asks a question to the user. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance * @param Question $question The question to ask * * @return string The user answer * * @throws \RuntimeException If there is no data to read in the input stream */ public function ask(InputInterface $input, OutputInterface $output, Question $question) { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } if (!$input->isInteractive()) { return $question->getDefault(); } if (!$question->getValidator()) { return $this->doAsk($output, $question); } $that = $this; $interviewer = function () use ($output, $question, $that) { return $that->doAsk($output, $question); }; return $this->validateAttempts($interviewer, $output, $question); } /** * Sets the input stream to read from when interacting with the user. * * This is mainly useful for testing purpose. * * @param resource $stream The input stream * * @throws \InvalidArgumentException In case the stream is not a resource */ public function setInputStream($stream) { if (!is_resource($stream)) { throw new \InvalidArgumentException('Input stream must be a valid resource.'); } $this->inputStream = $stream; } /** * Returns the helper's input stream. * * @return resource */ public function getInputStream() { return $this->inputStream; } /** * {@inheritdoc} */ public function getName() { return 'question'; } /** * Asks the question to the user. * * This method is public for PHP 5.3 compatibility, it should be private. * * @param OutputInterface $output * @param Question $question * * @return bool|mixed|null|string * * @throws \Exception * @throws \RuntimeException */ public function doAsk(OutputInterface $output, Question $question) { $this->writePrompt($output, $question); $inputStream = $this->inputStream ?: STDIN; $autocomplete = $question->getAutocompleterValues(); if (null === $autocomplete || !$this->hasSttyAvailable()) { $ret = false; if ($question->isHidden()) { try { $ret = trim($this->getHiddenResponse($output, $inputStream)); } catch (\RuntimeException $e) { if (!$question->isHiddenFallback()) { throw $e; } } } if (false === $ret) { $ret = fgets($inputStream, 4096); if (false === $ret) { throw new \RuntimeException('Aborted'); } $ret = trim($ret); } } else { $ret = trim($this->autocomplete($output, $question, $inputStream)); } $ret = strlen($ret) > 0 ? $ret : $question->getDefault(); if ($normalizer = $question->getNormalizer()) { return $normalizer($ret); } return $ret; } /** * Outputs the question prompt. * * @param OutputInterface $output * @param Question $question */ protected function writePrompt(OutputInterface $output, Question $question) { $message = $question->getQuestion(); if ($question instanceof ChoiceQuestion) { $width = max(array_map('strlen', array_keys($question->getChoices()))); $messages = (array) $question->getQuestion(); foreach ($question->getChoices() as $key => $value) { $messages[] = sprintf(" [%-${width}s] %s", $key, $value); } $output->writeln($messages); $message = $question->getPrompt(); } $output->write($message); } /** * Outputs an error message. * * @param OutputInterface $output * @param \Exception $error */ protected function writeError(OutputInterface $output, \Exception $error) { if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) { $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'); } else { $message = ''.$error->getMessage().''; } $output->writeln($message); } /** * Autocompletes a question. * * @param OutputInterface $output * @param Question $question * * @return string */ private function autocomplete(OutputInterface $output, Question $question, $inputStream) { $autocomplete = $question->getAutocompleterValues(); $ret = ''; $i = 0; $ofs = -1; $matches = $autocomplete; $numMatches = count($matches); $sttyMode = shell_exec('stty -g'); // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) shell_exec('stty -icanon -echo'); // Add highlighted text style $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); // Read a keypress while (!feof($inputStream)) { $c = fread($inputStream, 1); // Backspace Character if ("\177" === $c) { if (0 === $numMatches && 0 !== $i) { --$i; // Move cursor backwards $output->write("\033[1D"); } if ($i === 0) { $ofs = -1; $matches = $autocomplete; $numMatches = count($matches); } else { $numMatches = 0; } // Pop the last character off the end of our string $ret = substr($ret, 0, $i); } elseif ("\033" === $c) { // Did we read an escape sequence? $c .= fread($inputStream, 2); // A = Up Arrow. B = Down Arrow if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { if ('A' === $c[2] && -1 === $ofs) { $ofs = 0; } if (0 === $numMatches) { continue; } $ofs += ('A' === $c[2]) ? -1 : 1; $ofs = ($numMatches + $ofs) % $numMatches; } } elseif (ord($c) < 32) { if ("\t" === $c || "\n" === $c) { if ($numMatches > 0 && -1 !== $ofs) { $ret = $matches[$ofs]; // Echo out remaining chars for current match $output->write(substr($ret, $i)); $i = strlen($ret); } if ("\n" === $c) { $output->write($c); break; } $numMatches = 0; } continue; } else { $output->write($c); $ret .= $c; ++$i; $numMatches = 0; $ofs = 0; foreach ($autocomplete as $value) { // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) if (0 === strpos($value, $ret) && $i !== strlen($value)) { $matches[$numMatches++] = $value; } } } // Erase characters from cursor to end of line $output->write("\033[K"); if ($numMatches > 0 && -1 !== $ofs) { // Save cursor position $output->write("\0337"); // Write highlighted text $output->write(''.substr($matches[$ofs], $i).''); // Restore cursor position $output->write("\0338"); } } // Reset stty so it behaves normally again shell_exec(sprintf('stty %s', $sttyMode)); return $ret; } /** * Gets a hidden response from user. * * @param OutputInterface $output An Output instance * * @return string The answer * * @throws \RuntimeException In case the fallback is deactivated and the response cannot be hidden */ private function getHiddenResponse(OutputInterface $output, $inputStream) { if ('\\' === DIRECTORY_SEPARATOR) { $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; // handle code running from a phar if ('phar:' === substr(__FILE__, 0, 5)) { $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; copy($exe, $tmpExe); $exe = $tmpExe; } $value = rtrim(shell_exec($exe)); $output->writeln(''); if (isset($tmpExe)) { unlink($tmpExe); } return $value; } if ($this->hasSttyAvailable()) { $sttyMode = shell_exec('stty -g'); shell_exec('stty -echo'); $value = fgets($inputStream, 4096); shell_exec(sprintf('stty %s', $sttyMode)); if (false === $value) { throw new \RuntimeException('Aborted'); } $value = trim($value); $output->writeln(''); return $value; } if (false !== $shell = $this->getShell()) { $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword'; $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); $value = rtrim(shell_exec($command)); $output->writeln(''); return $value; } throw new \RuntimeException('Unable to hide the response.'); } /** * Validates an attempt. * * @param callable $interviewer A callable that will ask for a question and return the result * @param OutputInterface $output An Output instance * @param Question $question A Question instance * * @return string The validated response * * @throws \Exception In case the max number of attempts has been reached and no valid response has been given */ private function validateAttempts($interviewer, OutputInterface $output, Question $question) { $error = null; $attempts = $question->getMaxAttempts(); while (null === $attempts || $attempts--) { if (null !== $error) { $this->writeError($output, $error); } try { return call_user_func($question->getValidator(), $interviewer()); } catch (\Exception $error) { } } throw $error; } /** * Returns a valid unix shell. * * @return string|bool The valid shell name, false in case no valid shell is found */ private function getShell() { if (null !== self::$shell) { return self::$shell; } self::$shell = false; if (file_exists('/usr/bin/env')) { // handle other OSs with bash/zsh/ksh/csh if available to hide the answer $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) { if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { self::$shell = $sh; break; } } } return self::$shell; } /** * Returns whether Stty is available or not. * * @return bool */ private function hasSttyAvailable() { if (null !== self::$stty) { return self::$stty; } exec('stty 2>&1', $output, $exitcode); return self::$stty = $exitcode === 0; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; /** * @author Abdellatif Ait boudad */ class TableCell { /** * @var string */ private $value; /** * @var array */ private $options = array( 'rowspan' => 1, 'colspan' => 1, ); /** * @param string $value * @param array $options */ public function __construct($value = '', array $options = array()) { $this->value = $value; // check option names if ($diff = array_diff(array_keys($options), array_keys($this->options))) { throw new \InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff))); } $this->options = array_merge($this->options, $options); } /** * Returns the cell value. * * @return string */ public function __toString() { return $this->value; } /** * Gets number of colspan. * * @return int */ public function getColspan() { return (int) $this->options['colspan']; } /** * Gets number of rowspan. * * @return int */ public function getRowspan() { return (int) $this->options['rowspan']; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Output\OutputInterface; /** * The Progress class provides helpers to display progress output. * * @author Chris Jones * @author Fabien Potencier * * @deprecated since version 2.5, to be removed in 3.0 * Use {@link ProgressBar} instead. */ class ProgressHelper extends Helper { const FORMAT_QUIET = ' %percent%%'; const FORMAT_NORMAL = ' %current%/%max% [%bar%] %percent%%'; const FORMAT_VERBOSE = ' %current%/%max% [%bar%] %percent%% Elapsed: %elapsed%'; const FORMAT_QUIET_NOMAX = ' %current%'; const FORMAT_NORMAL_NOMAX = ' %current% [%bar%]'; const FORMAT_VERBOSE_NOMAX = ' %current% [%bar%] Elapsed: %elapsed%'; // options private $barWidth = 28; private $barChar = '='; private $emptyBarChar = '-'; private $progressChar = '>'; private $format = null; private $redrawFreq = 1; private $lastMessagesLength; private $barCharOriginal; /** * @var OutputInterface */ private $output; /** * Current step. * * @var int */ private $current; /** * Maximum number of steps. * * @var int */ private $max; /** * Start time of the progress bar. * * @var int */ private $startTime; /** * List of formatting variables. * * @var array */ private $defaultFormatVars = array( 'current', 'max', 'bar', 'percent', 'elapsed', ); /** * Available formatting variables. * * @var array */ private $formatVars; /** * Stored format part widths (used for padding). * * @var array */ private $widths = array( 'current' => 4, 'max' => 4, 'percent' => 3, 'elapsed' => 6, ); /** * Various time formats. * * @var array */ private $timeFormats = array( array(0, '???'), array(2, '1 sec'), array(59, 'secs', 1), array(60, '1 min'), array(3600, 'mins', 60), array(5400, '1 hr'), array(86400, 'hrs', 3600), array(129600, '1 day'), array(604800, 'days', 86400), ); public function __construct($triggerDeprecationError = true) { if ($triggerDeprecationError) { @trigger_error('The '.__CLASS__.' class is deprecated since version 2.5 and will be removed in 3.0. Use the Symfony\Component\Console\Helper\ProgressBar class instead.', E_USER_DEPRECATED); } } /** * Sets the progress bar width. * * @param int $size The progress bar size */ public function setBarWidth($size) { $this->barWidth = (int) $size; } /** * Sets the bar character. * * @param string $char A character */ public function setBarCharacter($char) { $this->barChar = $char; } /** * Sets the empty bar character. * * @param string $char A character */ public function setEmptyBarCharacter($char) { $this->emptyBarChar = $char; } /** * Sets the progress bar character. * * @param string $char A character */ public function setProgressCharacter($char) { $this->progressChar = $char; } /** * Sets the progress bar format. * * @param string $format The format */ public function setFormat($format) { $this->format = $format; } /** * Sets the redraw frequency. * * @param int $freq The frequency in steps */ public function setRedrawFrequency($freq) { $this->redrawFreq = (int) $freq; } /** * Starts the progress output. * * @param OutputInterface $output An Output instance * @param int|null $max Maximum steps */ public function start(OutputInterface $output, $max = null) { $this->startTime = time(); $this->current = 0; $this->max = (int) $max; // Disabling output when it does not support ANSI codes as it would result in a broken display anyway. $this->output = $output->isDecorated() ? $output : new NullOutput(); $this->lastMessagesLength = 0; $this->barCharOriginal = ''; if (null === $this->format) { switch ($output->getVerbosity()) { case OutputInterface::VERBOSITY_QUIET: $this->format = self::FORMAT_QUIET_NOMAX; if ($this->max > 0) { $this->format = self::FORMAT_QUIET; } break; case OutputInterface::VERBOSITY_VERBOSE: case OutputInterface::VERBOSITY_VERY_VERBOSE: case OutputInterface::VERBOSITY_DEBUG: $this->format = self::FORMAT_VERBOSE_NOMAX; if ($this->max > 0) { $this->format = self::FORMAT_VERBOSE; } break; default: $this->format = self::FORMAT_NORMAL_NOMAX; if ($this->max > 0) { $this->format = self::FORMAT_NORMAL; } break; } } $this->initialize(); } /** * Advances the progress output X steps. * * @param int $step Number of steps to advance * @param bool $redraw Whether to redraw or not * * @throws \LogicException */ public function advance($step = 1, $redraw = false) { $this->setCurrent($this->current + $step, $redraw); } /** * Sets the current progress. * * @param int $current The current progress * @param bool $redraw Whether to redraw or not * * @throws \LogicException */ public function setCurrent($current, $redraw = false) { if (null === $this->startTime) { throw new \LogicException('You must start the progress bar before calling setCurrent().'); } $current = (int) $current; if ($current < $this->current) { throw new \LogicException('You can\'t regress the progress bar'); } if (0 === $this->current) { $redraw = true; } $prevPeriod = (int) ($this->current / $this->redrawFreq); $this->current = $current; $currPeriod = (int) ($this->current / $this->redrawFreq); if ($redraw || $prevPeriod !== $currPeriod || $this->max === $this->current) { $this->display(); } } /** * Outputs the current progress string. * * @param bool $finish Forces the end result * * @throws \LogicException */ public function display($finish = false) { if (null === $this->startTime) { throw new \LogicException('You must start the progress bar before calling display().'); } $message = $this->format; foreach ($this->generate($finish) as $name => $value) { $message = str_replace("%{$name}%", $value, $message); } $this->overwrite($this->output, $message); } /** * Removes the progress bar from the current line. * * This is useful if you wish to write some output * while a progress bar is running. * Call display() to show the progress bar again. */ public function clear() { $this->overwrite($this->output, ''); } /** * Finishes the progress output. */ public function finish() { if (null === $this->startTime) { throw new \LogicException('You must start the progress bar before calling finish().'); } if (null !== $this->startTime) { if (!$this->max) { $this->barChar = $this->barCharOriginal; $this->display(true); } $this->startTime = null; $this->output->writeln(''); $this->output = null; } } /** * Initializes the progress helper. */ private function initialize() { $this->formatVars = array(); foreach ($this->defaultFormatVars as $var) { if (false !== strpos($this->format, "%{$var}%")) { $this->formatVars[$var] = true; } } if ($this->max > 0) { $this->widths['max'] = $this->strlen($this->max); $this->widths['current'] = $this->widths['max']; } else { $this->barCharOriginal = $this->barChar; $this->barChar = $this->emptyBarChar; } } /** * Generates the array map of format variables to values. * * @param bool $finish Forces the end result * * @return array Array of format vars and values */ private function generate($finish = false) { $vars = array(); $percent = 0; if ($this->max > 0) { $percent = (float) $this->current / $this->max; } if (isset($this->formatVars['bar'])) { $completeBars = 0; if ($this->max > 0) { $completeBars = floor($percent * $this->barWidth); } else { if (!$finish) { $completeBars = floor($this->current % $this->barWidth); } else { $completeBars = $this->barWidth; } } $emptyBars = $this->barWidth - $completeBars - $this->strlen($this->progressChar); $bar = str_repeat($this->barChar, $completeBars); if ($completeBars < $this->barWidth) { $bar .= $this->progressChar; $bar .= str_repeat($this->emptyBarChar, $emptyBars); } $vars['bar'] = $bar; } if (isset($this->formatVars['elapsed'])) { $elapsed = time() - $this->startTime; $vars['elapsed'] = str_pad($this->humaneTime($elapsed), $this->widths['elapsed'], ' ', STR_PAD_LEFT); } if (isset($this->formatVars['current'])) { $vars['current'] = str_pad($this->current, $this->widths['current'], ' ', STR_PAD_LEFT); } if (isset($this->formatVars['max'])) { $vars['max'] = $this->max; } if (isset($this->formatVars['percent'])) { $vars['percent'] = str_pad(floor($percent * 100), $this->widths['percent'], ' ', STR_PAD_LEFT); } return $vars; } /** * Converts seconds into human-readable format. * * @param int $secs Number of seconds * * @return string Time in readable format */ private function humaneTime($secs) { $text = ''; foreach ($this->timeFormats as $format) { if ($secs < $format[0]) { if (count($format) == 2) { $text = $format[1]; break; } else { $text = ceil($secs / $format[2]).' '.$format[1]; break; } } } return $text; } /** * Overwrites a previous message to the output. * * @param OutputInterface $output An Output instance * @param string $message The message */ private function overwrite(OutputInterface $output, $message) { $length = $this->strlen($message); // append whitespace to match the last line's length if (null !== $this->lastMessagesLength && $this->lastMessagesLength > $length) { $message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT); } // carriage return $output->write("\x0D"); $output->write($message); $this->lastMessagesLength = $this->strlen($message); } /** * {@inheritdoc} */ public function getName() { return 'progress'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Descriptor\DescriptorInterface; use Symfony\Component\Console\Descriptor\JsonDescriptor; use Symfony\Component\Console\Descriptor\MarkdownDescriptor; use Symfony\Component\Console\Descriptor\TextDescriptor; use Symfony\Component\Console\Descriptor\XmlDescriptor; use Symfony\Component\Console\Output\OutputInterface; /** * This class adds helper method to describe objects in various formats. * * @author Jean-François Simon */ class DescriptorHelper extends Helper { /** * @var DescriptorInterface[] */ private $descriptors = array(); /** * Constructor. */ public function __construct() { $this ->register('txt', new TextDescriptor()) ->register('xml', new XmlDescriptor()) ->register('json', new JsonDescriptor()) ->register('md', new MarkdownDescriptor()) ; } /** * Describes an object if supported. * * Available options are: * * format: string, the output format name * * raw_text: boolean, sets output type as raw * * @param OutputInterface $output * @param object $object * @param array $options * * @throws \InvalidArgumentException when the given format is not supported */ public function describe(OutputInterface $output, $object, array $options = array()) { $options = array_merge(array( 'raw_text' => false, 'format' => 'txt', ), $options); if (!isset($this->descriptors[$options['format']])) { throw new \InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format'])); } $descriptor = $this->descriptors[$options['format']]; $descriptor->describe($output, $object, $options); } /** * Registers a descriptor. * * @param string $format * @param DescriptorInterface $descriptor * * @return DescriptorHelper */ public function register($format, DescriptorInterface $descriptor) { $this->descriptors[$format] = $descriptor; return $this; } /** * {@inheritdoc} */ public function getName() { return 'descriptor'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * The ProgressBar provides helpers to display progress output. * * @author Fabien Potencier * @author Chris Jones */ class ProgressBar { // options private $barWidth = 28; private $barChar; private $emptyBarChar = '-'; private $progressChar = '>'; private $format = null; private $redrawFreq = 1; /** * @var OutputInterface */ private $output; private $step = 0; private $max; private $startTime; private $stepWidth; private $percent = 0.0; private $lastMessagesLength = 0; private $formatLineCount; private $messages; private $overwrite = true; private static $formatters; private static $formats; /** * Constructor. * * @param OutputInterface $output An OutputInterface instance * @param int $max Maximum steps (0 if unknown) */ public function __construct(OutputInterface $output, $max = 0) { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $this->output = $output; $this->setMaxSteps($max); if (!$this->output->isDecorated()) { // disable overwrite when output does not support ANSI codes. $this->overwrite = false; if ($this->max > 10) { // set a reasonable redraw frequency so output isn't flooded $this->setRedrawFrequency($max / 10); } } $this->setFormat($this->determineBestFormat()); $this->startTime = time(); } /** * Sets a placeholder formatter for a given name. * * This method also allow you to override an existing placeholder. * * @param string $name The placeholder name (including the delimiter char like %) * @param callable $callable A PHP callable */ public static function setPlaceholderFormatterDefinition($name, $callable) { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } self::$formatters[$name] = $callable; } /** * Gets the placeholder formatter for a given name. * * @param string $name The placeholder name (including the delimiter char like %) * * @return callable|null A PHP callable */ public static function getPlaceholderFormatterDefinition($name) { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } return isset(self::$formatters[$name]) ? self::$formatters[$name] : null; } /** * Sets a format for a given name. * * This method also allow you to override an existing format. * * @param string $name The format name * @param string $format A format string */ public static function setFormatDefinition($name, $format) { if (!self::$formats) { self::$formats = self::initFormats(); } self::$formats[$name] = $format; } /** * Gets the format for a given name. * * @param string $name The format name * * @return string|null A format string */ public static function getFormatDefinition($name) { if (!self::$formats) { self::$formats = self::initFormats(); } return isset(self::$formats[$name]) ? self::$formats[$name] : null; } public function setMessage($message, $name = 'message') { $this->messages[$name] = $message; } public function getMessage($name = 'message') { return $this->messages[$name]; } /** * Gets the progress bar start time. * * @return int The progress bar start time */ public function getStartTime() { return $this->startTime; } /** * Gets the progress bar maximal steps. * * @return int The progress bar max steps */ public function getMaxSteps() { return $this->max; } /** * Gets the progress bar step. * * @deprecated since version 2.6, to be removed in 3.0. Use {@link getProgress()} instead. * * @return int The progress bar step */ public function getStep() { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the getProgress() method instead.', E_USER_DEPRECATED); return $this->getProgress(); } /** * Gets the current step position. * * @return int The progress bar step */ public function getProgress() { return $this->step; } /** * Gets the progress bar step width. * * @internal This method is public for PHP 5.3 compatibility, it should not be used. * * @return int The progress bar step width */ public function getStepWidth() { return $this->stepWidth; } /** * Gets the current progress bar percent. * * @return float The current progress bar percent */ public function getProgressPercent() { return $this->percent; } /** * Sets the progress bar width. * * @param int $size The progress bar size */ public function setBarWidth($size) { $this->barWidth = (int) $size; } /** * Gets the progress bar width. * * @return int The progress bar size */ public function getBarWidth() { return $this->barWidth; } /** * Sets the bar character. * * @param string $char A character */ public function setBarCharacter($char) { $this->barChar = $char; } /** * Gets the bar character. * * @return string A character */ public function getBarCharacter() { if (null === $this->barChar) { return $this->max ? '=' : $this->emptyBarChar; } return $this->barChar; } /** * Sets the empty bar character. * * @param string $char A character */ public function setEmptyBarCharacter($char) { $this->emptyBarChar = $char; } /** * Gets the empty bar character. * * @return string A character */ public function getEmptyBarCharacter() { return $this->emptyBarChar; } /** * Sets the progress bar character. * * @param string $char A character */ public function setProgressCharacter($char) { $this->progressChar = $char; } /** * Gets the progress bar character. * * @return string A character */ public function getProgressCharacter() { return $this->progressChar; } /** * Sets the progress bar format. * * @param string $format The format */ public function setFormat($format) { // try to use the _nomax variant if available if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) { $this->format = self::getFormatDefinition($format.'_nomax'); } elseif (null !== self::getFormatDefinition($format)) { $this->format = self::getFormatDefinition($format); } else { $this->format = $format; } $this->formatLineCount = substr_count($this->format, "\n"); } /** * Sets the redraw frequency. * * @param int $freq The frequency in steps */ public function setRedrawFrequency($freq) { $this->redrawFreq = (int) $freq; } /** * Starts the progress output. * * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged */ public function start($max = null) { $this->startTime = time(); $this->step = 0; $this->percent = 0.0; if (null !== $max) { $this->setMaxSteps($max); } $this->display(); } /** * Advances the progress output X steps. * * @param int $step Number of steps to advance * * @throws \LogicException */ public function advance($step = 1) { $this->setProgress($this->step + $step); } /** * Sets the current progress. * * @deprecated since version 2.6, to be removed in 3.0. Use {@link setProgress()} instead. * * @param int $step The current progress * * @throws \LogicException */ public function setCurrent($step) { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the setProgress() method instead.', E_USER_DEPRECATED); $this->setProgress($step); } /** * Sets whether to overwrite the progressbar, false for new line. * * @param bool $overwrite */ public function setOverwrite($overwrite) { $this->overwrite = (bool) $overwrite; } /** * Sets the current progress. * * @param int $step The current progress * * @throws \LogicException */ public function setProgress($step) { $step = (int) $step; if ($step < $this->step) { throw new \LogicException('You can\'t regress the progress bar.'); } if ($this->max && $step > $this->max) { $this->max = $step; } $prevPeriod = (int) ($this->step / $this->redrawFreq); $currPeriod = (int) ($step / $this->redrawFreq); $this->step = $step; $this->percent = $this->max ? (float) $this->step / $this->max : 0; if ($prevPeriod !== $currPeriod || $this->max === $step) { $this->display(); } } /** * Finishes the progress output. */ public function finish() { if (!$this->max) { $this->max = $this->step; } if ($this->step === $this->max && !$this->overwrite) { // prevent double 100% output return; } $this->setProgress($this->max); } /** * Outputs the current progress string. */ public function display() { if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { return; } // these 3 variables can be removed in favor of using $this in the closure when support for PHP 5.3 will be dropped. $self = $this; $output = $this->output; $messages = $this->messages; $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self, $output, $messages) { if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) { $text = call_user_func($formatter, $self, $output); } elseif (isset($messages[$matches[1]])) { $text = $messages[$matches[1]]; } else { return $matches[0]; } if (isset($matches[2])) { $text = sprintf('%'.$matches[2], $text); } return $text; }, $this->format)); } /** * Removes the progress bar from the current line. * * This is useful if you wish to write some output * while a progress bar is running. * Call display() to show the progress bar again. */ public function clear() { if (!$this->overwrite) { return; } $this->overwrite(str_repeat("\n", $this->formatLineCount)); } /** * Sets the progress bar maximal steps. * * @param int $max The progress bar max steps */ private function setMaxSteps($max) { $this->max = max(0, (int) $max); $this->stepWidth = $this->max ? Helper::strlen($this->max) : 4; } /** * Overwrites a previous message to the output. * * @param string $message The message */ private function overwrite($message) { $lines = explode("\n", $message); // append whitespace to match the line's length if (null !== $this->lastMessagesLength) { foreach ($lines as $i => $line) { if ($this->lastMessagesLength > Helper::strlenWithoutDecoration($this->output->getFormatter(), $line)) { $lines[$i] = str_pad($line, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT); } } } if ($this->overwrite) { // move back to the beginning of the progress bar before redrawing it $this->output->write("\x0D"); } elseif ($this->step > 0) { // move to new line $this->output->writeln(''); } if ($this->formatLineCount) { $this->output->write(sprintf("\033[%dA", $this->formatLineCount)); } $this->output->write(implode("\n", $lines)); $this->lastMessagesLength = 0; foreach ($lines as $line) { $len = Helper::strlenWithoutDecoration($this->output->getFormatter(), $line); if ($len > $this->lastMessagesLength) { $this->lastMessagesLength = $len; } } } private function determineBestFormat() { switch ($this->output->getVerbosity()) { // OutputInterface::VERBOSITY_QUIET: display is disabled anyway case OutputInterface::VERBOSITY_VERBOSE: return $this->max ? 'verbose' : 'verbose_nomax'; case OutputInterface::VERBOSITY_VERY_VERBOSE: return $this->max ? 'very_verbose' : 'very_verbose_nomax'; case OutputInterface::VERBOSITY_DEBUG: return $this->max ? 'debug' : 'debug_nomax'; default: return $this->max ? 'normal' : 'normal_nomax'; } } private static function initPlaceholderFormatters() { return array( 'bar' => function (ProgressBar $bar, OutputInterface $output) { $completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth()); $display = str_repeat($bar->getBarCharacter(), $completeBars); if ($completeBars < $bar->getBarWidth()) { $emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter()); $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars); } return $display; }, 'elapsed' => function (ProgressBar $bar) { return Helper::formatTime(time() - $bar->getStartTime()); }, 'remaining' => function (ProgressBar $bar) { if (!$bar->getMaxSteps()) { throw new \LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); } if (!$bar->getProgress()) { $remaining = 0; } else { $remaining = round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress())); } return Helper::formatTime($remaining); }, 'estimated' => function (ProgressBar $bar) { if (!$bar->getMaxSteps()) { throw new \LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); } if (!$bar->getProgress()) { $estimated = 0; } else { $estimated = round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps()); } return Helper::formatTime($estimated); }, 'memory' => function (ProgressBar $bar) { return Helper::formatMemory(memory_get_usage(true)); }, 'current' => function (ProgressBar $bar) { return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT); }, 'max' => function (ProgressBar $bar) { return $bar->getMaxSteps(); }, 'percent' => function (ProgressBar $bar) { return floor($bar->getProgressPercent() * 100); }, ); } private static function initFormats() { return array( 'normal' => ' %current%/%max% [%bar%] %percent:3s%%', 'normal_nomax' => ' %current% [%bar%]', 'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%', 'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', 'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%', 'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', 'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%', 'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%', ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Formatter\OutputFormatterStyle; /** * The Dialog class provides helpers to interact with the user. * * @author Fabien Potencier * * @deprecated since version 2.5, to be removed in 3.0. * Use {@link \Symfony\Component\Console\Helper\QuestionHelper} instead. */ class DialogHelper extends InputAwareHelper { private $inputStream; private static $shell; private static $stty; public function __construct($triggerDeprecationError = true) { if ($triggerDeprecationError) { @trigger_error('"Symfony\Component\Console\Helper\DialogHelper" is deprecated since version 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\QuestionHelper" instead.', E_USER_DEPRECATED); } } /** * Asks the user to select a value. * * @param OutputInterface $output An Output instance * @param string|array $question The question to ask * @param array $choices List of choices to pick from * @param bool|string $default The default answer if the user enters nothing * @param bool|int $attempts Max number of times to ask before giving up (false by default, which means infinite) * @param string $errorMessage Message which will be shown if invalid value from choice list would be picked * @param bool $multiselect Select more than one value separated by comma * * @return int|string|array The selected value or values (the key of the choices array) * * @throws \InvalidArgumentException */ public function select(OutputInterface $output, $question, $choices, $default = null, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false) { $width = max(array_map('strlen', array_keys($choices))); $messages = (array) $question; foreach ($choices as $key => $value) { $messages[] = sprintf(" [%-${width}s] %s", $key, $value); } $output->writeln($messages); $result = $this->askAndValidate($output, '> ', function ($picked) use ($choices, $errorMessage, $multiselect) { // Collapse all spaces. $selectedChoices = str_replace(' ', '', $picked); if ($multiselect) { // Check for a separated comma values if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) { throw new \InvalidArgumentException(sprintf($errorMessage, $picked)); } $selectedChoices = explode(',', $selectedChoices); } else { $selectedChoices = array($picked); } $multiselectChoices = array(); foreach ($selectedChoices as $value) { if (empty($choices[$value])) { throw new \InvalidArgumentException(sprintf($errorMessage, $value)); } $multiselectChoices[] = $value; } if ($multiselect) { return $multiselectChoices; } return $picked; }, $attempts, $default); return $result; } /** * Asks a question to the user. * * @param OutputInterface $output An Output instance * @param string|array $question The question to ask * @param string $default The default answer if none is given by the user * @param array $autocomplete List of values to autocomplete * * @return string The user answer * * @throws \RuntimeException If there is no data to read in the input stream */ public function ask(OutputInterface $output, $question, $default = null, array $autocomplete = null) { if ($this->input && !$this->input->isInteractive()) { return $default; } $output->write($question); $inputStream = $this->inputStream ?: STDIN; if (null === $autocomplete || !$this->hasSttyAvailable()) { $ret = fgets($inputStream, 4096); if (false === $ret) { throw new \RuntimeException('Aborted'); } $ret = trim($ret); } else { $ret = ''; $i = 0; $ofs = -1; $matches = $autocomplete; $numMatches = count($matches); $sttyMode = shell_exec('stty -g'); // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) shell_exec('stty -icanon -echo'); // Add highlighted text style $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); // Read a keypress while (!feof($inputStream)) { $c = fread($inputStream, 1); // Backspace Character if ("\177" === $c) { if (0 === $numMatches && 0 !== $i) { --$i; // Move cursor backwards $output->write("\033[1D"); } if ($i === 0) { $ofs = -1; $matches = $autocomplete; $numMatches = count($matches); } else { $numMatches = 0; } // Pop the last character off the end of our string $ret = substr($ret, 0, $i); } elseif ("\033" === $c) { // Did we read an escape sequence? $c .= fread($inputStream, 2); // A = Up Arrow. B = Down Arrow if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { if ('A' === $c[2] && -1 === $ofs) { $ofs = 0; } if (0 === $numMatches) { continue; } $ofs += ('A' === $c[2]) ? -1 : 1; $ofs = ($numMatches + $ofs) % $numMatches; } } elseif (ord($c) < 32) { if ("\t" === $c || "\n" === $c) { if ($numMatches > 0 && -1 !== $ofs) { $ret = $matches[$ofs]; // Echo out remaining chars for current match $output->write(substr($ret, $i)); $i = strlen($ret); } if ("\n" === $c) { $output->write($c); break; } $numMatches = 0; } continue; } else { $output->write($c); $ret .= $c; ++$i; $numMatches = 0; $ofs = 0; foreach ($autocomplete as $value) { // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) if (0 === strpos($value, $ret) && $i !== strlen($value)) { $matches[$numMatches++] = $value; } } } // Erase characters from cursor to end of line $output->write("\033[K"); if ($numMatches > 0 && -1 !== $ofs) { // Save cursor position $output->write("\0337"); // Write highlighted text $output->write(''.substr($matches[$ofs], $i).''); // Restore cursor position $output->write("\0338"); } } // Reset stty so it behaves normally again shell_exec(sprintf('stty %s', $sttyMode)); } return strlen($ret) > 0 ? $ret : $default; } /** * Asks a confirmation to the user. * * The question will be asked until the user answers by nothing, yes, or no. * * @param OutputInterface $output An Output instance * @param string|array $question The question to ask * @param bool $default The default answer if the user enters nothing * * @return bool true if the user has confirmed, false otherwise */ public function askConfirmation(OutputInterface $output, $question, $default = true) { $answer = 'z'; while ($answer && !in_array(strtolower($answer[0]), array('y', 'n'))) { $answer = $this->ask($output, $question); } if (false === $default) { return $answer && 'y' == strtolower($answer[0]); } return !$answer || 'y' == strtolower($answer[0]); } /** * Asks a question to the user, the response is hidden. * * @param OutputInterface $output An Output instance * @param string|array $question The question * @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not * * @return string The answer * * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden */ public function askHiddenResponse(OutputInterface $output, $question, $fallback = true) { if ('\\' === DIRECTORY_SEPARATOR) { $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; // handle code running from a phar if ('phar:' === substr(__FILE__, 0, 5)) { $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; copy($exe, $tmpExe); $exe = $tmpExe; } $output->write($question); $value = rtrim(shell_exec($exe)); $output->writeln(''); if (isset($tmpExe)) { unlink($tmpExe); } return $value; } if ($this->hasSttyAvailable()) { $output->write($question); $sttyMode = shell_exec('stty -g'); shell_exec('stty -echo'); $value = fgets($this->inputStream ?: STDIN, 4096); shell_exec(sprintf('stty %s', $sttyMode)); if (false === $value) { throw new \RuntimeException('Aborted'); } $value = trim($value); $output->writeln(''); return $value; } if (false !== $shell = $this->getShell()) { $output->write($question); $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword'; $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); $value = rtrim(shell_exec($command)); $output->writeln(''); return $value; } if ($fallback) { return $this->ask($output, $question); } throw new \RuntimeException('Unable to hide the response'); } /** * Asks for a value and validates the response. * * The validator receives the data to validate. It must return the * validated data when the data is valid and throw an exception * otherwise. * * @param OutputInterface $output An Output instance * @param string|array $question The question to ask * @param callable $validator A PHP callback * @param int|false $attempts Max number of times to ask before giving up (false by default, which means infinite) * @param string $default The default answer if none is given by the user * @param array $autocomplete List of values to autocomplete * * @return mixed * * @throws \Exception When any of the validators return an error */ public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $default = null, array $autocomplete = null) { $that = $this; $interviewer = function () use ($output, $question, $default, $autocomplete, $that) { return $that->ask($output, $question, $default, $autocomplete); }; return $this->validateAttempts($interviewer, $output, $validator, $attempts); } /** * Asks for a value, hide and validates the response. * * The validator receives the data to validate. It must return the * validated data when the data is valid and throw an exception * otherwise. * * @param OutputInterface $output An Output instance * @param string|array $question The question to ask * @param callable $validator A PHP callback * @param int|false $attempts Max number of times to ask before giving up (false by default, which means infinite) * @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not * * @return string The response * * @throws \Exception When any of the validators return an error * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden */ public function askHiddenResponseAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $fallback = true) { $that = $this; $interviewer = function () use ($output, $question, $fallback, $that) { return $that->askHiddenResponse($output, $question, $fallback); }; return $this->validateAttempts($interviewer, $output, $validator, $attempts); } /** * Sets the input stream to read from when interacting with the user. * * This is mainly useful for testing purpose. * * @param resource $stream The input stream */ public function setInputStream($stream) { $this->inputStream = $stream; } /** * Returns the helper's input stream. * * @return resource|null The input stream or null if the default STDIN is used */ public function getInputStream() { return $this->inputStream; } /** * {@inheritdoc} */ public function getName() { return 'dialog'; } /** * Return a valid Unix shell. * * @return string|bool The valid shell name, false in case no valid shell is found */ private function getShell() { if (null !== self::$shell) { return self::$shell; } self::$shell = false; if (file_exists('/usr/bin/env')) { // handle other OSs with bash/zsh/ksh/csh if available to hide the answer $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) { if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { self::$shell = $sh; break; } } } return self::$shell; } private function hasSttyAvailable() { if (null !== self::$stty) { return self::$stty; } exec('stty 2>&1', $output, $exitcode); return self::$stty = $exitcode === 0; } /** * Validate an attempt. * * @param callable $interviewer A callable that will ask for a question and return the result * @param OutputInterface $output An Output instance * @param callable $validator A PHP callback * @param int|false $attempts Max number of times to ask before giving up ; false will ask infinitely * * @return string The validated response * * @throws \Exception In case the max number of attempts has been reached and no valid response has been given */ private function validateAttempts($interviewer, OutputInterface $output, $validator, $attempts) { $e = null; while (false === $attempts || $attempts--) { if (null !== $e) { $output->writeln($this->getHelperSet()->get('formatter')->formatBlock($e->getMessage(), 'error')); } try { return call_user_func($validator, $interviewer()); } catch (\Exception $e) { } } throw $e; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Output\OutputInterface; /** * Provides helpers to display a table. * * @author Fabien Potencier * @author Саша Стаменковић * @author Abdellatif Ait boudad */ class Table { /** * Table headers. * * @var array */ private $headers = array(); /** * Table rows. * * @var array */ private $rows = array(); /** * Column widths cache. * * @var array */ private $columnWidths = array(); /** * Number of columns cache. * * @var array */ private $numberOfColumns; /** * @var OutputInterface */ private $output; /** * @var TableStyle */ private $style; private static $styles; public function __construct(OutputInterface $output) { $this->output = $output; if (!self::$styles) { self::$styles = self::initStyles(); } $this->setStyle('default'); } /** * Sets a style definition. * * @param string $name The style name * @param TableStyle $style A TableStyle instance */ public static function setStyleDefinition($name, TableStyle $style) { if (!self::$styles) { self::$styles = self::initStyles(); } self::$styles[$name] = $style; } /** * Gets a style definition by name. * * @param string $name The style name * * @return TableStyle A TableStyle instance */ public static function getStyleDefinition($name) { if (!self::$styles) { self::$styles = self::initStyles(); } if (!self::$styles[$name]) { throw new \InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); } return self::$styles[$name]; } /** * Sets table style. * * @param TableStyle|string $name The style name or a TableStyle instance * * @return Table */ public function setStyle($name) { if ($name instanceof TableStyle) { $this->style = $name; } elseif (isset(self::$styles[$name])) { $this->style = self::$styles[$name]; } else { throw new \InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); } return $this; } /** * Gets the current table style. * * @return TableStyle */ public function getStyle() { return $this->style; } public function setHeaders(array $headers) { $headers = array_values($headers); if (!empty($headers) && !is_array($headers[0])) { $headers = array($headers); } $this->headers = $headers; return $this; } public function setRows(array $rows) { $this->rows = array(); return $this->addRows($rows); } public function addRows(array $rows) { foreach ($rows as $row) { $this->addRow($row); } return $this; } public function addRow($row) { if ($row instanceof TableSeparator) { $this->rows[] = $row; return $this; } if (!is_array($row)) { throw new \InvalidArgumentException('A row must be an array or a TableSeparator instance.'); } $this->rows[] = array_values($row); return $this; } public function setRow($column, array $row) { $this->rows[$column] = $row; return $this; } /** * Renders table to output. * * Example: * +---------------+-----------------------+------------------+ * | ISBN | Title | Author | * +---------------+-----------------------+------------------+ * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | * +---------------+-----------------------+------------------+ */ public function render() { $this->calculateNumberOfColumns(); $this->rows = $this->buildTableRows($this->rows); $this->headers = $this->buildTableRows($this->headers); $this->renderRowSeparator(); if (!empty($this->headers)) { foreach ($this->headers as $header) { $this->renderRow($header, $this->style->getCellHeaderFormat()); $this->renderRowSeparator(); } } foreach ($this->rows as $row) { if ($row instanceof TableSeparator) { $this->renderRowSeparator(); } else { $this->renderRow($row, $this->style->getCellRowFormat()); } } if (!empty($this->rows)) { $this->renderRowSeparator(); } $this->cleanup(); } /** * Renders horizontal header separator. * * Example: +-----+-----------+-------+ */ private function renderRowSeparator() { if (0 === $count = $this->numberOfColumns) { return; } if (!$this->style->getHorizontalBorderChar() && !$this->style->getCrossingChar()) { return; } $markup = $this->style->getCrossingChar(); for ($column = 0; $column < $count; ++$column) { $markup .= str_repeat($this->style->getHorizontalBorderChar(), $this->getColumnWidth($column)).$this->style->getCrossingChar(); } $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup)); } /** * Renders vertical column separator. */ private function renderColumnSeparator() { $this->output->write(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar())); } /** * Renders table row. * * Example: | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | * * @param array $row * @param string $cellFormat */ private function renderRow(array $row, $cellFormat) { if (empty($row)) { return; } $this->renderColumnSeparator(); foreach ($this->getRowColumns($row) as $column) { $this->renderCell($row, $column, $cellFormat); $this->renderColumnSeparator(); } $this->output->writeln(''); } /** * Renders table cell with padding. * * @param array $row * @param int $column * @param string $cellFormat */ private function renderCell(array $row, $column, $cellFormat) { $cell = isset($row[$column]) ? $row[$column] : ''; $width = $this->getColumnWidth($column); if ($cell instanceof TableCell && $cell->getColspan() > 1) { // add the width of the following columns(numbers of colspan). foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) { $width += $this->getColumnSeparatorWidth() + $this->getColumnWidth($nextColumn); } } // str_pad won't work properly with multi-byte strings, we need to fix the padding if (function_exists('mb_strwidth') && false !== $encoding = mb_detect_encoding($cell)) { $width += strlen($cell) - mb_strwidth($cell, $encoding); } if ($cell instanceof TableSeparator) { $this->output->write(sprintf($this->style->getBorderFormat(), str_repeat($this->style->getHorizontalBorderChar(), $width))); } else { $width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); $content = sprintf($this->style->getCellRowContentFormat(), $cell); $this->output->write(sprintf($cellFormat, str_pad($content, $width, $this->style->getPaddingChar(), $this->style->getPadType()))); } } /** * Calculate number of columns for this table. */ private function calculateNumberOfColumns() { if (null !== $this->numberOfColumns) { return; } $columns = array(0); foreach (array_merge($this->headers, $this->rows) as $row) { if ($row instanceof TableSeparator) { continue; } $columns[] = $this->getNumberOfColumns($row); } return $this->numberOfColumns = max($columns); } private function buildTableRows($rows) { $unmergedRows = array(); for ($rowKey = 0; $rowKey < count($rows); ++$rowKey) { $rows = $this->fillNextRows($rows, $rowKey); // Remove any new line breaks and replace it with a new line foreach ($rows[$rowKey] as $column => $cell) { $rows[$rowKey] = $this->fillCells($rows[$rowKey], $column); if (!strstr($cell, "\n")) { continue; } $lines = explode("\n", $cell); foreach ($lines as $lineKey => $line) { if ($cell instanceof TableCell) { $line = new TableCell($line, array('colspan' => $cell->getColspan())); } if (0 === $lineKey) { $rows[$rowKey][$column] = $line; } else { $unmergedRows[$rowKey][$lineKey][$column] = $line; } } } } $tableRows = array(); foreach ($rows as $rowKey => $row) { $tableRows[] = $row; if (isset($unmergedRows[$rowKey])) { $tableRows = array_merge($tableRows, $unmergedRows[$rowKey]); } } return $tableRows; } /** * fill rows that contains rowspan > 1. * * @param array $rows * @param int $line * * @return array */ private function fillNextRows($rows, $line) { $unmergedRows = array(); foreach ($rows[$line] as $column => $cell) { if ($cell instanceof TableCell && $cell->getRowspan() > 1) { $nbLines = $cell->getRowspan() - 1; $lines = array($cell); if (strstr($cell, "\n")) { $lines = explode("\n", $cell); $nbLines = count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines; $rows[$line][$column] = new TableCell($lines[0], array('colspan' => $cell->getColspan())); unset($lines[0]); } // create a two dimensional array (rowspan x colspan) $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, ''), $unmergedRows); foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { $value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : ''; $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, array('colspan' => $cell->getColspan())); } } } foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { // we need to know if $unmergedRow will be merged or inserted into $rows if (isset($rows[$unmergedRowKey]) && is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { foreach ($unmergedRow as $cellKey => $cell) { // insert cell into row at cellKey position array_splice($rows[$unmergedRowKey], $cellKey, 0, array($cell)); } } else { $row = $this->copyRow($rows, $unmergedRowKey - 1); foreach ($unmergedRow as $column => $cell) { if (!empty($cell)) { $row[$column] = $unmergedRow[$column]; } } array_splice($rows, $unmergedRowKey, 0, array($row)); } } return $rows; } /** * fill cells for a row that contains colspan > 1. * * @param array $row * @param int $column * * @return array */ private function fillCells($row, $column) { $cell = $row[$column]; if ($cell instanceof TableCell && $cell->getColspan() > 1) { foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) { // insert empty value into rows at column position array_splice($row, $position, 0, ''); } } return $row; } /** * @param array $rows * @param int $line * * @return array */ private function copyRow($rows, $line) { $row = $rows[$line]; foreach ($row as $cellKey => $cellValue) { $row[$cellKey] = ''; if ($cellValue instanceof TableCell) { $row[$cellKey] = new TableCell('', array('colspan' => $cellValue->getColspan())); } } return $row; } /** * Gets number of columns by row. * * @param array $row * * @return int */ private function getNumberOfColumns(array $row) { $columns = count($row); foreach ($row as $column) { $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0; } return $columns; } /** * Gets list of columns for the given row. * * @param array $row * * @return array() */ private function getRowColumns($row) { $columns = range(0, $this->numberOfColumns - 1); foreach ($row as $cellKey => $cell) { if ($cell instanceof TableCell && $cell->getColspan() > 1) { // exclude grouped columns. $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1)); } } return $columns; } /** * Gets column width. * * @param int $column * * @return int */ private function getColumnWidth($column) { if (isset($this->columnWidths[$column])) { return $this->columnWidths[$column]; } foreach (array_merge($this->headers, $this->rows) as $row) { if ($row instanceof TableSeparator) { continue; } $lengths[] = $this->getCellWidth($row, $column); } return $this->columnWidths[$column] = max($lengths) + strlen($this->style->getCellRowContentFormat()) - 2; } /** * Gets column width. * * @param int $column * * @return int */ private function getColumnSeparatorWidth() { return strlen(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar())); } /** * Gets cell width. * * @param array $row * @param int $column * * @return int */ private function getCellWidth(array $row, $column) { if (isset($row[$column])) { $cell = $row[$column]; $cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); if ($cell instanceof TableCell && $cell->getColspan() > 1) { // we assume that cell value will be across more than one column. $cellWidth = $cellWidth / $cell->getColspan(); } return $cellWidth; } return 0; } /** * Called after rendering to cleanup cache data. */ private function cleanup() { $this->columnWidths = array(); $this->numberOfColumns = null; } private static function initStyles() { $borderless = new TableStyle(); $borderless ->setHorizontalBorderChar('=') ->setVerticalBorderChar(' ') ->setCrossingChar(' ') ; $compact = new TableStyle(); $compact ->setHorizontalBorderChar('') ->setVerticalBorderChar(' ') ->setCrossingChar('') ->setCellRowContentFormat('%s') ; $styleGuide = new TableStyle(); $styleGuide ->setHorizontalBorderChar('-') ->setVerticalBorderChar(' ') ->setCrossingChar(' ') ->setCellHeaderFormat('%s') ; return array( 'default' => new TableStyle(), 'borderless' => $borderless, 'compact' => $compact, 'symfony-style-guide' => $styleGuide, ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\NullOutput; /** * Provides helpers to display table output. * * @author Саша Стаменковић * @author Fabien Potencier * * @deprecated since version 2.5, to be removed in 3.0 * Use {@link Table} instead. */ class TableHelper extends Helper { const LAYOUT_DEFAULT = 0; const LAYOUT_BORDERLESS = 1; const LAYOUT_COMPACT = 2; /** * @var Table */ private $table; public function __construct($triggerDeprecationError = true) { if ($triggerDeprecationError) { @trigger_error('The '.__CLASS__.' class is deprecated since version 2.5 and will be removed in 3.0. Use the Symfony\Component\Console\Helper\Table class instead.', E_USER_DEPRECATED); } $this->table = new Table(new NullOutput()); } /** * Sets table layout type. * * @param int $layout self::LAYOUT_* * * @return TableHelper * * @throws \InvalidArgumentException when the table layout is not known */ public function setLayout($layout) { switch ($layout) { case self::LAYOUT_BORDERLESS: $this->table->setStyle('borderless'); break; case self::LAYOUT_COMPACT: $this->table->setStyle('compact'); break; case self::LAYOUT_DEFAULT: $this->table->setStyle('default'); break; default: throw new \InvalidArgumentException(sprintf('Invalid table layout "%s".', $layout)); }; return $this; } public function setHeaders(array $headers) { $this->table->setHeaders($headers); return $this; } public function setRows(array $rows) { $this->table->setRows($rows); return $this; } public function addRows(array $rows) { $this->table->addRows($rows); return $this; } public function addRow(array $row) { $this->table->addRow($row); return $this; } public function setRow($column, array $row) { $this->table->setRow($column, $row); return $this; } /** * Sets padding character, used for cell padding. * * @param string $paddingChar * * @return TableHelper */ public function setPaddingChar($paddingChar) { $this->table->getStyle()->setPaddingChar($paddingChar); return $this; } /** * Sets horizontal border character. * * @param string $horizontalBorderChar * * @return TableHelper */ public function setHorizontalBorderChar($horizontalBorderChar) { $this->table->getStyle()->setHorizontalBorderChar($horizontalBorderChar); return $this; } /** * Sets vertical border character. * * @param string $verticalBorderChar * * @return TableHelper */ public function setVerticalBorderChar($verticalBorderChar) { $this->table->getStyle()->setVerticalBorderChar($verticalBorderChar); return $this; } /** * Sets crossing character. * * @param string $crossingChar * * @return TableHelper */ public function setCrossingChar($crossingChar) { $this->table->getStyle()->setCrossingChar($crossingChar); return $this; } /** * Sets header cell format. * * @param string $cellHeaderFormat * * @return TableHelper */ public function setCellHeaderFormat($cellHeaderFormat) { $this->table->getStyle()->setCellHeaderFormat($cellHeaderFormat); return $this; } /** * Sets row cell format. * * @param string $cellRowFormat * * @return TableHelper */ public function setCellRowFormat($cellRowFormat) { $this->table->getStyle()->setCellHeaderFormat($cellRowFormat); return $this; } /** * Sets row cell content format. * * @param string $cellRowContentFormat * * @return TableHelper */ public function setCellRowContentFormat($cellRowContentFormat) { $this->table->getStyle()->setCellRowContentFormat($cellRowContentFormat); return $this; } /** * Sets table border format. * * @param string $borderFormat * * @return TableHelper */ public function setBorderFormat($borderFormat) { $this->table->getStyle()->setBorderFormat($borderFormat); return $this; } /** * Sets cell padding type. * * @param int $padType STR_PAD_* * * @return TableHelper */ public function setPadType($padType) { $this->table->getStyle()->setPadType($padType); return $this; } /** * Renders table to output. * * Example: * +---------------+-----------------------+------------------+ * | ISBN | Title | Author | * +---------------+-----------------------+------------------+ * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | * +---------------+-----------------------+------------------+ * * @param OutputInterface $output */ public function render(OutputInterface $output) { $p = new \ReflectionProperty($this->table, 'output'); $p->setAccessible(true); $p->setValue($this->table, $output); $this->table->render(); } /** * {@inheritdoc} */ public function getName() { return 'table'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Component\Process\Process; use Symfony\Component\Process\ProcessBuilder; /** * The ProcessHelper class provides helpers to run external processes. * * @author Fabien Potencier */ class ProcessHelper extends Helper { /** * Runs an external process. * * @param OutputInterface $output An OutputInterface instance * @param string|array|Process $cmd An instance of Process or an array of arguments to escape and run or a command to run * @param string|null $error An error message that must be displayed if something went wrong * @param callable|null $callback A PHP callback to run whenever there is some * output available on STDOUT or STDERR * @param int $verbosity The threshold for verbosity * * @return Process The process that ran */ public function run(OutputInterface $output, $cmd, $error = null, $callback = null, $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE) { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $formatter = $this->getHelperSet()->get('debug_formatter'); if (is_array($cmd)) { $process = ProcessBuilder::create($cmd)->getProcess(); } elseif ($cmd instanceof Process) { $process = $cmd; } else { $process = new Process($cmd); } if ($verbosity <= $output->getVerbosity()) { $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine()))); } if ($output->isDebug()) { $callback = $this->wrapCallback($output, $process, $callback); } $process->run($callback); if ($verbosity <= $output->getVerbosity()) { $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode()); $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful())); } if (!$process->isSuccessful() && null !== $error) { $output->writeln(sprintf('%s', $this->escapeString($error))); } return $process; } /** * Runs the process. * * This is identical to run() except that an exception is thrown if the process * exits with a non-zero exit code. * * @param OutputInterface $output An OutputInterface instance * @param string|Process $cmd An instance of Process or a command to run * @param string|null $error An error message that must be displayed if something went wrong * @param callable|null $callback A PHP callback to run whenever there is some * output available on STDOUT or STDERR * * @return Process The process that ran * * @throws ProcessFailedException * * @see run() */ public function mustRun(OutputInterface $output, $cmd, $error = null, $callback = null) { $process = $this->run($output, $cmd, $error, $callback); if (!$process->isSuccessful()) { throw new ProcessFailedException($process); } return $process; } /** * Wraps a Process callback to add debugging output. * * @param OutputInterface $output An OutputInterface interface * @param Process $process The Process * @param callable|null $callback A PHP callable * * @return callable */ public function wrapCallback(OutputInterface $output, Process $process, $callback = null) { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $formatter = $this->getHelperSet()->get('debug_formatter'); $that = $this; return function ($type, $buffer) use ($output, $process, $callback, $formatter, $that) { $output->write($formatter->progress(spl_object_hash($process), $that->escapeString($buffer), Process::ERR === $type)); if (null !== $callback) { call_user_func($callback, $type, $buffer); } }; } /** * This method is public for PHP 5.3 compatibility, it should be private. * * @internal */ public function escapeString($str) { return str_replace('<', '\\<', $str); } /** * {@inheritdoc} */ public function getName() { return 'process'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * Helper is the base class for all helper classes. * * @author Fabien Potencier */ abstract class Helper implements HelperInterface { protected $helperSet = null; /** * Sets the helper set associated with this helper. * * @param HelperSet $helperSet A HelperSet instance */ public function setHelperSet(HelperSet $helperSet = null) { $this->helperSet = $helperSet; } /** * Gets the helper set associated with this helper. * * @return HelperSet A HelperSet instance */ public function getHelperSet() { return $this->helperSet; } /** * Returns the length of a string, using mb_strwidth if it is available. * * @param string $string The string to check its length * * @return int The length of the string */ public static function strlen($string) { if (!function_exists('mb_strwidth')) { return strlen($string); } if (false === $encoding = mb_detect_encoding($string)) { return strlen($string); } return mb_strwidth($string, $encoding); } public static function formatTime($secs) { static $timeFormats = array( array(0, '< 1 sec'), array(2, '1 sec'), array(59, 'secs', 1), array(60, '1 min'), array(3600, 'mins', 60), array(5400, '1 hr'), array(86400, 'hrs', 3600), array(129600, '1 day'), array(604800, 'days', 86400), ); foreach ($timeFormats as $format) { if ($secs >= $format[0]) { continue; } if (2 == count($format)) { return $format[1]; } return ceil($secs / $format[2]).' '.$format[1]; } } public static function formatMemory($memory) { if ($memory >= 1024 * 1024 * 1024) { return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024); } if ($memory >= 1024 * 1024) { return sprintf('%.1f MiB', $memory / 1024 / 1024); } if ($memory >= 1024) { return sprintf('%d KiB', $memory / 1024); } return sprintf('%d B', $memory); } public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, $string) { $isDecorated = $formatter->isDecorated(); $formatter->setDecorated(false); // remove <...> formatting $string = $formatter->format($string); // remove already formatted characters $string = preg_replace("/\033\[[^m]*m/", '', $string); $formatter->setDecorated($isDecorated); return self::strlen($string); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\Question; use Symfony\Component\Console\Style\SymfonyStyle; /** * Symfony Style Guide compliant question helper. * * @author Kevin Bond */ class SymfonyQuestionHelper extends QuestionHelper { /** * {@inheritdoc} */ public function ask(InputInterface $input, OutputInterface $output, Question $question) { $validator = $question->getValidator(); $question->setValidator(function ($value) use ($validator) { if (null !== $validator && is_callable($validator)) { $value = $validator($value); } // make required if (!is_array($value) && !is_bool($value) && 0 === strlen($value)) { throw new \Exception('A value is required.'); } return $value; }); return parent::ask($input, $output, $question); } /** * {@inheritdoc} */ protected function writePrompt(OutputInterface $output, Question $question) { $text = $question->getQuestion(); $default = $question->getDefault(); switch (true) { case null === $default: $text = sprintf(' %s:', $text); break; case $question instanceof ConfirmationQuestion: $text = sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no'); break; case $question instanceof ChoiceQuestion: $choices = $question->getChoices(); $text = sprintf(' %s [%s]:', $text, $choices[$default]); break; default: $text = sprintf(' %s [%s]:', $text, $default); } $output->writeln($text); if ($question instanceof ChoiceQuestion) { $width = max(array_map('strlen', array_keys($question->getChoices()))); foreach ($question->getChoices() as $key => $value) { $output->writeln(sprintf(" [%-${width}s] %s", $key, $value)); } } $output->write(' > '); } /** * {@inheritdoc} */ protected function writeError(OutputInterface $output, \Exception $error) { if ($output instanceof SymfonyStyle) { $output->newLine(); $output->error($error->getMessage()); return; } parent::writeError($output, $error); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; /** * Helps outputting debug information when running an external program from a command. * * An external program can be a Process, an HTTP request, or anything else. * * @author Fabien Potencier */ class DebugFormatterHelper extends Helper { private $colors = array('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default'); private $started = array(); private $count = -1; /** * Starts a debug formatting session. * * @param string $id The id of the formatting session * @param string $message The message to display * @param string $prefix The prefix to use * * @return string */ public function start($id, $message, $prefix = 'RUN') { $this->started[$id] = array('border' => ++$this->count % count($this->colors)); return sprintf("%s %s %s\n", $this->getBorder($id), $prefix, $message); } /** * Adds progress to a formatting session. * * @param string $id The id of the formatting session * @param string $buffer The message to display * @param bool $error Whether to consider the buffer as error * @param string $prefix The prefix for output * @param string $errorPrefix The prefix for error output * * @return string */ public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR') { $message = ''; if ($error) { if (isset($this->started[$id]['out'])) { $message .= "\n"; unset($this->started[$id]['out']); } if (!isset($this->started[$id]['err'])) { $message .= sprintf('%s %s ', $this->getBorder($id), $errorPrefix); $this->started[$id]['err'] = true; } $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $errorPrefix), $buffer); } else { if (isset($this->started[$id]['err'])) { $message .= "\n"; unset($this->started[$id]['err']); } if (!isset($this->started[$id]['out'])) { $message .= sprintf('%s %s ', $this->getBorder($id), $prefix); $this->started[$id]['out'] = true; } $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $prefix), $buffer); } return $message; } /** * Stops a formatting session. * * @param string $id The id of the formatting session * @param string $message The message to display * @param bool $successful Whether to consider the result as success * @param string $prefix The prefix for the end output * * @return string */ public function stop($id, $message, $successful, $prefix = 'RES') { $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; if ($successful) { return sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); } $message = sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); unset($this->started[$id]['out'], $this->started[$id]['err']); return $message; } /** * @param string $id The id of the formatting session * * @return string */ private function getBorder($id) { return sprintf(' ', $this->colors[$this->started[$id]['border']]); } /** * {@inheritdoc} */ public function getName() { return 'debug_formatter'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; /** * HelperInterface is the interface all helpers must implement. * * @author Fabien Potencier * * @api */ interface HelperInterface { /** * Sets the helper set associated with this helper. * * @param HelperSet $helperSet A HelperSet instance * * @api */ public function setHelperSet(HelperSet $helperSet = null); /** * Gets the helper set associated with this helper. * * @return HelperSet A HelperSet instance * * @api */ public function getHelperSet(); /** * Returns the canonical name of this helper. * * @return string The canonical name * * @api */ public function getName(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Command\Command; /** * HelperSet represents a set of helpers to be used with a command. * * @author Fabien Potencier */ class HelperSet implements \IteratorAggregate { private $helpers = array(); private $command; /** * Constructor. * * @param Helper[] $helpers An array of helper. */ public function __construct(array $helpers = array()) { foreach ($helpers as $alias => $helper) { $this->set($helper, is_int($alias) ? null : $alias); } } /** * Sets a helper. * * @param HelperInterface $helper The helper instance * @param string $alias An alias */ public function set(HelperInterface $helper, $alias = null) { $this->helpers[$helper->getName()] = $helper; if (null !== $alias) { $this->helpers[$alias] = $helper; } $helper->setHelperSet($this); } /** * Returns true if the helper if defined. * * @param string $name The helper name * * @return bool true if the helper is defined, false otherwise */ public function has($name) { return isset($this->helpers[$name]); } /** * Gets a helper value. * * @param string $name The helper name * * @return HelperInterface The helper instance * * @throws \InvalidArgumentException if the helper is not defined */ public function get($name) { if (!$this->has($name)) { throw new \InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); } if ('dialog' === $name && $this->helpers[$name] instanceof DialogHelper) { @trigger_error('"Symfony\Component\Console\Helper\DialogHelper" is deprecated since version 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\QuestionHelper" instead.', E_USER_DEPRECATED); } elseif ('progress' === $name && $this->helpers[$name] instanceof ProgressHelper) { @trigger_error('"Symfony\Component\Console\Helper\ProgressHelper" is deprecated since version 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\ProgressBar" instead.', E_USER_DEPRECATED); } elseif ('table' === $name && $this->helpers[$name] instanceof TableHelper) { @trigger_error('"Symfony\Component\Console\Helper\TableHelper" is deprecated since version 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\Table" instead.', E_USER_DEPRECATED); } return $this->helpers[$name]; } /** * Sets the command associated with this helper set. * * @param Command $command A Command instance */ public function setCommand(Command $command = null) { $this->command = $command; } /** * Gets the command associated with this helper set. * * @return Command A Command instance */ public function getCommand() { return $this->command; } public function getIterator() { return new \ArrayIterator($this->helpers); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Formatter\OutputFormatter; /** * The Formatter class provides helpers to format messages. * * @author Fabien Potencier */ class FormatterHelper extends Helper { /** * Formats a message within a section. * * @param string $section The section name * @param string $message The message * @param string $style The style to apply to the section * * @return string The format section */ public function formatSection($section, $message, $style = 'info') { return sprintf('<%s>[%s] %s', $style, $section, $style, $message); } /** * Formats a message as a block of text. * * @param string|array $messages The message to write in the block * @param string $style The style to apply to the whole block * @param bool $large Whether to return a large block * * @return string The formatter message */ public function formatBlock($messages, $style, $large = false) { if (!is_array($messages)) { $messages = array($messages); } $len = 0; $lines = array(); foreach ($messages as $message) { $message = OutputFormatter::escape($message); $lines[] = sprintf($large ? ' %s ' : ' %s ', $message); $len = max($this->strlen($message) + ($large ? 4 : 2), $len); } $messages = $large ? array(str_repeat(' ', $len)) : array(); for ($i = 0; isset($lines[$i]); ++$i) { $messages[] = $lines[$i].str_repeat(' ', $len - $this->strlen($lines[$i])); } if ($large) { $messages[] = str_repeat(' ', $len); } for ($i = 0; isset($messages[$i]); ++$i) { $messages[$i] = sprintf('<%s>%s', $style, $messages[$i], $style); } return implode("\n", $messages); } /** * {@inheritdoc} */ public function getName() { return 'formatter'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputAwareInterface; /** * An implementation of InputAwareInterface for Helpers. * * @author Wouter J */ abstract class InputAwareHelper extends Helper implements InputAwareInterface { protected $input; /** * {@inheritdoc} */ public function setInput(InputInterface $input) { $this->input = $input; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Command; use Symfony\Component\Console\Descriptor\TextDescriptor; use Symfony\Component\Console\Descriptor\XmlDescriptor; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Application; use Symfony\Component\Console\Helper\HelperSet; /** * Base class for all commands. * * @author Fabien Potencier * * @api */ class Command { private $application; private $name; private $processTitle; private $aliases = array(); private $definition; private $help; private $description; private $ignoreValidationErrors = false; private $applicationDefinitionMerged = false; private $applicationDefinitionMergedWithArgs = false; private $code; private $synopsis = array(); private $usages = array(); private $helperSet; /** * Constructor. * * @param string|null $name The name of the command; passing null means it must be set in configure() * * @throws \LogicException When the command name is empty * * @api */ public function __construct($name = null) { $this->definition = new InputDefinition(); if (null !== $name) { $this->setName($name); } $this->configure(); if (!$this->name) { throw new \LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this))); } } /** * Ignores validation errors. * * This is mainly useful for the help command. */ public function ignoreValidationErrors() { $this->ignoreValidationErrors = true; } /** * Sets the application instance for this command. * * @param Application $application An Application instance * * @api */ public function setApplication(Application $application = null) { $this->application = $application; if ($application) { $this->setHelperSet($application->getHelperSet()); } else { $this->helperSet = null; } } /** * Sets the helper set. * * @param HelperSet $helperSet A HelperSet instance */ public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; } /** * Gets the helper set. * * @return HelperSet A HelperSet instance */ public function getHelperSet() { return $this->helperSet; } /** * Gets the application instance for this command. * * @return Application An Application instance * * @api */ public function getApplication() { return $this->application; } /** * Checks whether the command is enabled or not in the current environment. * * Override this to check for x or y and return false if the command can not * run properly under the current conditions. * * @return bool */ public function isEnabled() { return true; } /** * Configures the current command. */ protected function configure() { } /** * Executes the current command. * * This method is not abstract because you can use this class * as a concrete class. In this case, instead of defining the * execute() method, you set the code to execute by passing * a Closure to the setCode() method. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance * * @return null|int null or 0 if everything went fine, or an error code * * @throws \LogicException When this abstract method is not implemented * * @see setCode() */ protected function execute(InputInterface $input, OutputInterface $output) { throw new \LogicException('You must override the execute() method in the concrete command class.'); } /** * Interacts with the user. * * This method is executed before the InputDefinition is validated. * This means that this is the only place where the command can * interactively ask for values of missing required arguments. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance */ protected function interact(InputInterface $input, OutputInterface $output) { } /** * Initializes the command just after the input has been validated. * * This is mainly useful when a lot of commands extends one main command * where some things need to be initialized based on the input arguments and options. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance */ protected function initialize(InputInterface $input, OutputInterface $output) { } /** * Runs the command. * * The code to execute is either defined directly with the * setCode() method or by overriding the execute() method * in a sub-class. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance * * @return int The command exit code * * @throws \Exception * * @see setCode() * @see execute() * * @api */ public function run(InputInterface $input, OutputInterface $output) { // force the creation of the synopsis before the merge with the app definition $this->getSynopsis(true); $this->getSynopsis(false); // add the application arguments and options $this->mergeApplicationDefinition(); // bind the input against the command specific arguments/options try { $input->bind($this->definition); } catch (\Exception $e) { if (!$this->ignoreValidationErrors) { throw $e; } } $this->initialize($input, $output); if (null !== $this->processTitle) { if (function_exists('cli_set_process_title')) { cli_set_process_title($this->processTitle); } elseif (function_exists('setproctitle')) { setproctitle($this->processTitle); } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) { $output->writeln('Install the proctitle PECL to be able to change the process title.'); } } if ($input->isInteractive()) { $this->interact($input, $output); } $input->validate(); if ($this->code) { $statusCode = call_user_func($this->code, $input, $output); } else { $statusCode = $this->execute($input, $output); } return is_numeric($statusCode) ? (int) $statusCode : 0; } /** * Sets the code to execute when running this command. * * If this method is used, it overrides the code defined * in the execute() method. * * @param callable $code A callable(InputInterface $input, OutputInterface $output) * * @return Command The current instance * * @throws \InvalidArgumentException * * @see execute() * * @api */ public function setCode($code) { if (!is_callable($code)) { throw new \InvalidArgumentException('Invalid callable provided to Command::setCode.'); } $this->code = $code; return $this; } /** * Merges the application definition with the command definition. * * This method is not part of public API and should not be used directly. * * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments */ public function mergeApplicationDefinition($mergeArgs = true) { if (null === $this->application || (true === $this->applicationDefinitionMerged && ($this->applicationDefinitionMergedWithArgs || !$mergeArgs))) { return; } if ($mergeArgs) { $currentArguments = $this->definition->getArguments(); $this->definition->setArguments($this->application->getDefinition()->getArguments()); $this->definition->addArguments($currentArguments); } $this->definition->addOptions($this->application->getDefinition()->getOptions()); $this->applicationDefinitionMerged = true; if ($mergeArgs) { $this->applicationDefinitionMergedWithArgs = true; } } /** * Sets an array of argument and option instances. * * @param array|InputDefinition $definition An array of argument and option instances or a definition instance * * @return Command The current instance * * @api */ public function setDefinition($definition) { if ($definition instanceof InputDefinition) { $this->definition = $definition; } else { $this->definition->setDefinition($definition); } $this->applicationDefinitionMerged = false; return $this; } /** * Gets the InputDefinition attached to this Command. * * @return InputDefinition An InputDefinition instance * * @api */ public function getDefinition() { return $this->definition; } /** * Gets the InputDefinition to be used to create XML and Text representations of this Command. * * Can be overridden to provide the original command representation when it would otherwise * be changed by merging with the application InputDefinition. * * This method is not part of public API and should not be used directly. * * @return InputDefinition An InputDefinition instance */ public function getNativeDefinition() { return $this->getDefinition(); } /** * Adds an argument. * * @param string $name The argument name * @param int $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL * @param string $description A description text * @param mixed $default The default value (for InputArgument::OPTIONAL mode only) * * @return Command The current instance * * @api */ public function addArgument($name, $mode = null, $description = '', $default = null) { $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); return $this; } /** * Adds an option. * * @param string $name The option name * @param string $shortcut The shortcut (can be null) * @param int $mode The option mode: One of the InputOption::VALUE_* constants * @param string $description A description text * @param mixed $default The default value (must be null for InputOption::VALUE_REQUIRED or InputOption::VALUE_NONE) * * @return Command The current instance * * @api */ public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) { $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); return $this; } /** * Sets the name of the command. * * This method can set both the namespace and the name if * you separate them by a colon (:) * * $command->setName('foo:bar'); * * @param string $name The command name * * @return Command The current instance * * @throws \InvalidArgumentException When the name is invalid * * @api */ public function setName($name) { $this->validateName($name); $this->name = $name; return $this; } /** * Sets the process title of the command. * * This feature should be used only when creating a long process command, * like a daemon. * * PHP 5.5+ or the proctitle PECL library is required * * @param string $title The process title * * @return Command The current instance */ public function setProcessTitle($title) { $this->processTitle = $title; return $this; } /** * Returns the command name. * * @return string The command name * * @api */ public function getName() { return $this->name; } /** * Sets the description for the command. * * @param string $description The description for the command * * @return Command The current instance * * @api */ public function setDescription($description) { $this->description = $description; return $this; } /** * Returns the description for the command. * * @return string The description for the command * * @api */ public function getDescription() { return $this->description; } /** * Sets the help for the command. * * @param string $help The help for the command * * @return Command The current instance * * @api */ public function setHelp($help) { $this->help = $help; return $this; } /** * Returns the help for the command. * * @return string The help for the command * * @api */ public function getHelp() { return $this->help ?: $this->description; } /** * Returns the processed help for the command replacing the %command.name% and * %command.full_name% patterns with the real values dynamically. * * @return string The processed help for the command */ public function getProcessedHelp() { $name = $this->name; $placeholders = array( '%command.name%', '%command.full_name%', ); $replacements = array( $name, $_SERVER['PHP_SELF'].' '.$name, ); return str_replace($placeholders, $replacements, $this->getHelp()); } /** * Sets the aliases for the command. * * @param string[] $aliases An array of aliases for the command * * @return Command The current instance * * @throws \InvalidArgumentException When an alias is invalid * * @api */ public function setAliases($aliases) { if (!is_array($aliases) && !$aliases instanceof \Traversable) { throw new \InvalidArgumentException('$aliases must be an array or an instance of \Traversable'); } foreach ($aliases as $alias) { $this->validateName($alias); } $this->aliases = $aliases; return $this; } /** * Returns the aliases for the command. * * @return array An array of aliases for the command * * @api */ public function getAliases() { return $this->aliases; } /** * Returns the synopsis for the command. * * @param bool $short Whether to show the short version of the synopsis (with options folded) or not * * @return string The synopsis */ public function getSynopsis($short = false) { $key = $short ? 'short' : 'long'; if (!isset($this->synopsis[$key])) { $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); } return $this->synopsis[$key]; } /** * Add a command usage example. * * @param string $usage The usage, it'll be prefixed with the command name */ public function addUsage($usage) { if (0 !== strpos($usage, $this->name)) { $usage = sprintf('%s %s', $this->name, $usage); } $this->usages[] = $usage; return $this; } /** * Returns alternative usages of the command. * * @return array */ public function getUsages() { return $this->usages; } /** * Gets a helper instance by name. * * @param string $name The helper name * * @return mixed The helper value * * @throws \InvalidArgumentException if the helper is not defined * * @api */ public function getHelper($name) { return $this->helperSet->get($name); } /** * Returns a text representation of the command. * * @return string A string representing the command * * @deprecated since version 2.3, to be removed in 3.0. */ public function asText() { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); $descriptor = new TextDescriptor(); $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); $descriptor->describe($output, $this, array('raw_output' => true)); return $output->fetch(); } /** * Returns an XML representation of the command. * * @param bool $asDom Whether to return a DOM or an XML string * * @return string|\DOMDocument An XML string representing the command * * @deprecated since version 2.3, to be removed in 3.0. */ public function asXml($asDom = false) { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); $descriptor = new XmlDescriptor(); if ($asDom) { return $descriptor->getCommandDocument($this); } $output = new BufferedOutput(); $descriptor->describe($output, $this); return $output->fetch(); } /** * Validates a command name. * * It must be non-empty and parts can optionally be separated by ":". * * @param string $name * * @throws \InvalidArgumentException When the name is invalid */ private function validateName($name) { if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Command; use Symfony\Component\Console\Helper\DescriptorHelper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputDefinition; /** * ListCommand displays the list of all available commands for the application. * * @author Fabien Potencier */ class ListCommand extends Command { /** * {@inheritdoc} */ protected function configure() { $this ->setName('list') ->setDefinition($this->createDefinition()) ->setDescription('Lists commands') ->setHelp(<<%command.name% command lists all commands: php %command.full_name% You can also display the commands for a specific namespace: php %command.full_name% test You can also output the information in other formats by using the --format option: php %command.full_name% --format=xml It's also possible to get raw list of commands (useful for embedding command runner): php %command.full_name% --raw EOF ) ; } /** * {@inheritdoc} */ public function getNativeDefinition() { return $this->createDefinition(); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { if ($input->getOption('xml')) { @trigger_error('The --xml option was deprecated in version 2.7 and will be removed in version 3.0. Use the --format option instead.', E_USER_DEPRECATED); $input->setOption('format', 'xml'); } $helper = new DescriptorHelper(); $helper->describe($output, $this->getApplication(), array( 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), 'namespace' => $input->getArgument('namespace'), )); } /** * {@inheritdoc} */ private function createDefinition() { return new InputDefinition(array( new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), new InputOption('xml', null, InputOption::VALUE_NONE, 'To output list as XML'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), )); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Command; use Symfony\Component\Console\Helper\DescriptorHelper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * HelpCommand displays the help for a given command. * * @author Fabien Potencier */ class HelpCommand extends Command { private $command; /** * {@inheritdoc} */ protected function configure() { $this->ignoreValidationErrors(); $this ->setName('help') ->setDefinition(array( new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), )) ->setDescription('Displays help for a command') ->setHelp(<<%command.name% command displays help for a given command: php %command.full_name% list You can also output the help in other formats by using the --format option: php %command.full_name% --format=xml list To display the list of available commands, please use the list command. EOF ) ; } /** * Sets the command. * * @param Command $command The command to set */ public function setCommand(Command $command) { $this->command = $command; } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { if (null === $this->command) { $this->command = $this->getApplication()->find($input->getArgument('command_name')); } if ($input->getOption('xml')) { @trigger_error('The --xml option was deprecated in version 2.7 and will be removed in version 3.0. Use the --format option instead.', E_USER_DEPRECATED); $input->setOption('format', 'xml'); } $helper = new DescriptorHelper(); $helper->describe($output, $this->command, array( 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), )); $this->command = null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Question; /** * Represents a yes/no question. * * @author Fabien Potencier */ class ConfirmationQuestion extends Question { private $trueAnswerRegex; /** * Constructor. * * @param string $question The question to ask to the user * @param bool $default The default answer to return, true or false * @param string $trueAnswerRegex A regex to match the "yes" answer */ public function __construct($question, $default = true, $trueAnswerRegex = '/^y/i') { parent::__construct($question, (bool) $default); $this->trueAnswerRegex = $trueAnswerRegex; $this->setNormalizer($this->getDefaultNormalizer()); } /** * Returns the default answer normalizer. * * @return callable */ private function getDefaultNormalizer() { $default = $this->getDefault(); $regex = $this->trueAnswerRegex; return function ($answer) use ($default, $regex) { if (is_bool($answer)) { return $answer; } $answerIsTrue = (bool) preg_match($regex, $answer); if (false === $default) { return $answer && $answerIsTrue; } return !$answer || $answerIsTrue; }; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Question; /** * Represents a Question. * * @author Fabien Potencier */ class Question { private $question; private $attempts; private $hidden = false; private $hiddenFallback = true; private $autocompleterValues; private $validator; private $default; private $normalizer; /** * Constructor. * * @param string $question The question to ask to the user * @param mixed $default The default answer to return if the user enters nothing */ public function __construct($question, $default = null) { $this->question = $question; $this->default = $default; } /** * Returns the question. * * @return string */ public function getQuestion() { return $this->question; } /** * Returns the default answer. * * @return mixed */ public function getDefault() { return $this->default; } /** * Returns whether the user response must be hidden. * * @return bool */ public function isHidden() { return $this->hidden; } /** * Sets whether the user response must be hidden or not. * * @param bool $hidden * * @return Question The current instance * * @throws \LogicException In case the autocompleter is also used */ public function setHidden($hidden) { if ($this->autocompleterValues) { throw new \LogicException('A hidden question cannot use the autocompleter.'); } $this->hidden = (bool) $hidden; return $this; } /** * In case the response can not be hidden, whether to fallback on non-hidden question or not. * * @return bool */ public function isHiddenFallback() { return $this->hiddenFallback; } /** * Sets whether to fallback on non-hidden question if the response can not be hidden. * * @param bool $fallback * * @return Question The current instance */ public function setHiddenFallback($fallback) { $this->hiddenFallback = (bool) $fallback; return $this; } /** * Gets values for the autocompleter. * * @return null|array|\Traversable */ public function getAutocompleterValues() { return $this->autocompleterValues; } /** * Sets values for the autocompleter. * * @param null|array|\Traversable $values * * @return Question The current instance * * @throws \InvalidArgumentException * @throws \LogicException */ public function setAutocompleterValues($values) { if (is_array($values) && $this->isAssoc($values)) { $values = array_merge(array_keys($values), array_values($values)); } if (null !== $values && !is_array($values)) { if (!$values instanceof \Traversable || $values instanceof \Countable) { throw new \InvalidArgumentException('Autocompleter values can be either an array, `null` or an object implementing both `Countable` and `Traversable` interfaces.'); } } if ($this->hidden) { throw new \LogicException('A hidden question cannot use the autocompleter.'); } $this->autocompleterValues = $values; return $this; } /** * Sets a validator for the question. * * @param null|callable $validator * * @return Question The current instance */ public function setValidator($validator) { $this->validator = $validator; return $this; } /** * Gets the validator for the question. * * @return null|callable */ public function getValidator() { return $this->validator; } /** * Sets the maximum number of attempts. * * Null means an unlimited number of attempts. * * @param null|int $attempts * * @return Question The current instance * * @throws \InvalidArgumentException In case the number of attempts is invalid. */ public function setMaxAttempts($attempts) { if (null !== $attempts && $attempts < 1) { throw new \InvalidArgumentException('Maximum number of attempts must be a positive value.'); } $this->attempts = $attempts; return $this; } /** * Gets the maximum number of attempts. * * Null means an unlimited number of attempts. * * @return null|int */ public function getMaxAttempts() { return $this->attempts; } /** * Sets a normalizer for the response. * * The normalizer can be a callable (a string), a closure or a class implementing __invoke. * * @param string|\Closure $normalizer * * @return Question The current instance */ public function setNormalizer($normalizer) { $this->normalizer = $normalizer; return $this; } /** * Gets the normalizer for the response. * * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. * * @return string|\Closure */ public function getNormalizer() { return $this->normalizer; } protected function isAssoc($array) { return (bool) count(array_filter(array_keys($array), 'is_string')); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Question; /** * Represents a choice question. * * @author Fabien Potencier */ class ChoiceQuestion extends Question { private $choices; private $multiselect = false; private $prompt = ' > '; private $errorMessage = 'Value "%s" is invalid'; /** * Constructor. * * @param string $question The question to ask to the user * @param array $choices The list of available choices * @param mixed $default The default answer to return */ public function __construct($question, array $choices, $default = null) { parent::__construct($question, $default); $this->choices = $choices; $this->setValidator($this->getDefaultValidator()); $this->setAutocompleterValues($choices); } /** * Returns available choices. * * @return array */ public function getChoices() { return $this->choices; } /** * Sets multiselect option. * * When multiselect is set to true, multiple choices can be answered. * * @param bool $multiselect * * @return ChoiceQuestion The current instance */ public function setMultiselect($multiselect) { $this->multiselect = $multiselect; $this->setValidator($this->getDefaultValidator()); return $this; } /** * Gets the prompt for choices. * * @return string */ public function getPrompt() { return $this->prompt; } /** * Sets the prompt for choices. * * @param string $prompt * * @return ChoiceQuestion The current instance */ public function setPrompt($prompt) { $this->prompt = $prompt; return $this; } /** * Sets the error message for invalid values. * * The error message has a string placeholder (%s) for the invalid value. * * @param string $errorMessage * * @return ChoiceQuestion The current instance */ public function setErrorMessage($errorMessage) { $this->errorMessage = $errorMessage; $this->setValidator($this->getDefaultValidator()); return $this; } /** * Returns the default answer validator. * * @return callable */ private function getDefaultValidator() { $choices = $this->choices; $errorMessage = $this->errorMessage; $multiselect = $this->multiselect; $isAssoc = $this->isAssoc($choices); return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { // Collapse all spaces. $selectedChoices = str_replace(' ', '', $selected); if ($multiselect) { // Check for a separated comma values if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) { throw new \InvalidArgumentException(sprintf($errorMessage, $selected)); } $selectedChoices = explode(',', $selectedChoices); } else { $selectedChoices = array($selected); } $multiselectChoices = array(); foreach ($selectedChoices as $value) { $results = array(); foreach ($choices as $key => $choice) { if ($choice === $value) { $results[] = $key; } } if (count($results) > 1) { throw new \InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results))); } $result = array_search($value, $choices); if (!$isAssoc) { if (false !== $result) { $result = $choices[$result]; } elseif (isset($choices[$value])) { $result = $choices[$value]; } } elseif (false === $result && isset($choices[$value])) { $result = $value; } if (false === $result) { throw new \InvalidArgumentException(sprintf($errorMessage, $value)); } $multiselectChoices[] = (string) $result; } if ($multiselect) { return $multiselectChoices; } return current($multiselectChoices); }; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Logger; use Psr\Log\AbstractLogger; use Psr\Log\InvalidArgumentException; use Psr\Log\LogLevel; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\ConsoleOutputInterface; /** * PSR-3 compliant console logger. * * @author Kévin Dunglas * * @link http://www.php-fig.org/psr/psr-3/ */ class ConsoleLogger extends AbstractLogger { const INFO = 'info'; const ERROR = 'error'; /** * @var OutputInterface */ private $output; /** * @var array */ private $verbosityLevelMap = array( LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL, LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE, LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE, LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG, ); /** * @var array */ private $formatLevelMap = array( LogLevel::EMERGENCY => self::ERROR, LogLevel::ALERT => self::ERROR, LogLevel::CRITICAL => self::ERROR, LogLevel::ERROR => self::ERROR, LogLevel::WARNING => self::INFO, LogLevel::NOTICE => self::INFO, LogLevel::INFO => self::INFO, LogLevel::DEBUG => self::INFO, ); /** * @param OutputInterface $output * @param array $verbosityLevelMap * @param array $formatLevelMap */ public function __construct(OutputInterface $output, array $verbosityLevelMap = array(), array $formatLevelMap = array()) { $this->output = $output; $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; } /** * {@inheritdoc} */ public function log($level, $message, array $context = array()) { if (!isset($this->verbosityLevelMap[$level])) { throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); } // Write to the error output if necessary and available if ($this->formatLevelMap[$level] === self::ERROR && $this->output instanceof ConsoleOutputInterface) { $output = $this->output->getErrorOutput(); } else { $output = $this->output; } if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) { $output->writeln(sprintf('<%1$s>[%2$s] %3$s', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context))); } } /** * Interpolates context values into the message placeholders. * * @author PHP Framework Interoperability Group * * @param string $message * @param array $context * * @return string */ private function interpolate($message, array $context) { // build a replacement array with braces around the context keys $replace = array(); foreach ($context as $key => $val) { if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) { $replace[sprintf('{%s}', $key)] = $val; } } // interpolate replacement values into the message and return return strtr($message, $replace); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console; /** * Contains all events dispatched by an Application. * * @author Francesco Levorato */ final class ConsoleEvents { /** * The COMMAND event allows you to attach listeners before any command is * executed by the console. It also allows you to modify the command, input and output * before they are handled to the command. * * The event listener method receives a Symfony\Component\Console\Event\ConsoleCommandEvent * instance. * * @Event * * @var string */ const COMMAND = 'console.command'; /** * The TERMINATE event allows you to attach listeners after a command is * executed by the console. * * The event listener method receives a Symfony\Component\Console\Event\ConsoleTerminateEvent * instance. * * @Event * * @var string */ const TERMINATE = 'console.terminate'; /** * The EXCEPTION event occurs when an uncaught exception appears. * * This event allows you to deal with the exception or * to modify the thrown exception. The event listener method receives * a Symfony\Component\Console\Event\ConsoleExceptionEvent * instance. * * @Event * * @var string */ const EXCEPTION = 'console.exception'; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Event; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Allows to manipulate the exit code of a command after its execution. * * @author Francesco Levorato */ class ConsoleTerminateEvent extends ConsoleEvent { /** * The exit code of the command. * * @var int */ private $exitCode; public function __construct(Command $command, InputInterface $input, OutputInterface $output, $exitCode) { parent::__construct($command, $input, $output); $this->setExitCode($exitCode); } /** * Sets the exit code. * * @param int $exitCode The command exit code */ public function setExitCode($exitCode) { $this->exitCode = (int) $exitCode; } /** * Gets the exit code. * * @return int The command exit code */ public function getExitCode() { return $this->exitCode; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Event; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\EventDispatcher\Event; /** * Allows to inspect input and output of a command. * * @author Francesco Levorato */ class ConsoleEvent extends Event { protected $command; private $input; private $output; public function __construct(Command $command, InputInterface $input, OutputInterface $output) { $this->command = $command; $this->input = $input; $this->output = $output; } /** * Gets the command that is executed. * * @return Command A Command instance */ public function getCommand() { return $this->command; } /** * Gets the input instance. * * @return InputInterface An InputInterface instance */ public function getInput() { return $this->input; } /** * Gets the output instance. * * @return OutputInterface An OutputInterface instance */ public function getOutput() { return $this->output; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Event; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Allows to handle exception thrown in a command. * * @author Fabien Potencier */ class ConsoleExceptionEvent extends ConsoleEvent { private $exception; private $exitCode; public function __construct(Command $command, InputInterface $input, OutputInterface $output, \Exception $exception, $exitCode) { parent::__construct($command, $input, $output); $this->setException($exception); $this->exitCode = (int) $exitCode; } /** * Returns the thrown exception. * * @return \Exception The thrown exception */ public function getException() { return $this->exception; } /** * Replaces the thrown exception. * * This exception will be thrown if no response is set in the event. * * @param \Exception $exception The thrown exception */ public function setException(\Exception $exception) { $this->exception = $exception; } /** * Gets the exit code. * * @return int The command exit code */ public function getExitCode() { return $this->exitCode; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Event; /** * Allows to do things before the command is executed, like skipping the command or changing the input. * * @author Fabien Potencier */ class ConsoleCommandEvent extends ConsoleEvent { /** * The return code for skipped commands, this will also be passed into the terminate event. */ const RETURN_CODE_DISABLED = 113; /** * Indicates if the command should be run or skipped. * * @var bool */ private $commandShouldRun = true; /** * Disables the command, so it won't be run. * * @return bool */ public function disableCommand() { return $this->commandShouldRun = false; } /** * Enables the command. * * @return bool */ public function enableCommand() { return $this->commandShouldRun = true; } /** * Returns true if the command is runnable, false otherwise. * * @return bool */ public function commandShouldRun() { return $this->commandShouldRun; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; /** * Formatter style interface for defining styles. * * @author Konstantin Kudryashov * * @api */ interface OutputFormatterStyleInterface { /** * Sets style foreground color. * * @param string $color The color name * * @api */ public function setForeground($color = null); /** * Sets style background color. * * @param string $color The color name * * @api */ public function setBackground($color = null); /** * Sets some specific style option. * * @param string $option The option name * * @api */ public function setOption($option); /** * Unsets some specific style option. * * @param string $option The option name */ public function unsetOption($option); /** * Sets multiple style options at once. * * @param array $options */ public function setOptions(array $options); /** * Applies the style to a given text. * * @param string $text The text to style * * @return string */ public function apply($text); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; /** * @author Jean-François Simon */ class OutputFormatterStyleStack { /** * @var OutputFormatterStyleInterface[] */ private $styles; /** * @var OutputFormatterStyleInterface */ private $emptyStyle; /** * Constructor. * * @param OutputFormatterStyleInterface|null $emptyStyle */ public function __construct(OutputFormatterStyleInterface $emptyStyle = null) { $this->emptyStyle = $emptyStyle ?: new OutputFormatterStyle(); $this->reset(); } /** * Resets stack (ie. empty internal arrays). */ public function reset() { $this->styles = array(); } /** * Pushes a style in the stack. * * @param OutputFormatterStyleInterface $style */ public function push(OutputFormatterStyleInterface $style) { $this->styles[] = $style; } /** * Pops a style from the stack. * * @param OutputFormatterStyleInterface|null $style * * @return OutputFormatterStyleInterface * * @throws \InvalidArgumentException When style tags incorrectly nested */ public function pop(OutputFormatterStyleInterface $style = null) { if (empty($this->styles)) { return $this->emptyStyle; } if (null === $style) { return array_pop($this->styles); } foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { if ($style->apply('') === $stackedStyle->apply('')) { $this->styles = array_slice($this->styles, 0, $index); return $stackedStyle; } } throw new \InvalidArgumentException('Incorrectly nested style tag found.'); } /** * Computes current style with stacks top codes. * * @return OutputFormatterStyle */ public function getCurrent() { if (empty($this->styles)) { return $this->emptyStyle; } return $this->styles[count($this->styles) - 1]; } /** * @param OutputFormatterStyleInterface $emptyStyle * * @return OutputFormatterStyleStack */ public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle) { $this->emptyStyle = $emptyStyle; return $this; } /** * @return OutputFormatterStyleInterface */ public function getEmptyStyle() { return $this->emptyStyle; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; /** * Formatter class for console output. * * @author Konstantin Kudryashov * * @api */ class OutputFormatter implements OutputFormatterInterface { private $decorated; private $styles = array(); private $styleStack; /** * Escapes "<" special char in given text. * * @param string $text Text to escape * * @return string Escaped text */ public static function escape($text) { return preg_replace('/([^\\\\]?) FormatterStyle" instances * * @api */ public function __construct($decorated = false, array $styles = array()) { $this->decorated = (bool) $decorated; $this->setStyle('error', new OutputFormatterStyle('white', 'red')); $this->setStyle('info', new OutputFormatterStyle('green')); $this->setStyle('comment', new OutputFormatterStyle('yellow')); $this->setStyle('question', new OutputFormatterStyle('black', 'cyan')); foreach ($styles as $name => $style) { $this->setStyle($name, $style); } $this->styleStack = new OutputFormatterStyleStack(); } /** * Sets the decorated flag. * * @param bool $decorated Whether to decorate the messages or not * * @api */ public function setDecorated($decorated) { $this->decorated = (bool) $decorated; } /** * Gets the decorated flag. * * @return bool true if the output will decorate messages, false otherwise * * @api */ public function isDecorated() { return $this->decorated; } /** * Sets a new style. * * @param string $name The style name * @param OutputFormatterStyleInterface $style The style instance * * @api */ public function setStyle($name, OutputFormatterStyleInterface $style) { $this->styles[strtolower($name)] = $style; } /** * Checks if output formatter has style with specified name. * * @param string $name * * @return bool * * @api */ public function hasStyle($name) { return isset($this->styles[strtolower($name)]); } /** * Gets style options from style with specified name. * * @param string $name * * @return OutputFormatterStyleInterface * * @throws \InvalidArgumentException When style isn't defined * * @api */ public function getStyle($name) { if (!$this->hasStyle($name)) { throw new \InvalidArgumentException(sprintf('Undefined style: %s', $name)); } return $this->styles[strtolower($name)]; } /** * Formats a message according to the given styles. * * @param string $message The message to style * * @return string The styled message * * @api */ public function format($message) { $message = (string) $message; $offset = 0; $output = ''; $tagRegex = '[a-z][a-z0-9_=;-]*'; preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, PREG_OFFSET_CAPTURE); foreach ($matches[0] as $i => $match) { $pos = $match[1]; $text = $match[0]; if (0 != $pos && '\\' == $message[$pos - 1]) { continue; } // add the text up to the next tag $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset)); $offset = $pos + strlen($text); // opening tag? if ($open = '/' != $text[1]) { $tag = $matches[1][$i][0]; } else { $tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : ''; } if (!$open && !$tag) { // $this->styleStack->pop(); } elseif (false === $style = $this->createStyleFromString(strtolower($tag))) { $output .= $this->applyCurrentStyle($text); } elseif ($open) { $this->styleStack->push($style); } else { $this->styleStack->pop($style); } } $output .= $this->applyCurrentStyle(substr($message, $offset)); return str_replace('\\<', '<', $output); } /** * @return OutputFormatterStyleStack */ public function getStyleStack() { return $this->styleStack; } /** * Tries to create new style instance from string. * * @param string $string * * @return OutputFormatterStyle|bool false if string is not format string */ private function createStyleFromString($string) { if (isset($this->styles[$string])) { return $this->styles[$string]; } if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($string), $matches, PREG_SET_ORDER)) { return false; } $style = new OutputFormatterStyle(); foreach ($matches as $match) { array_shift($match); if ('fg' == $match[0]) { $style->setForeground($match[1]); } elseif ('bg' == $match[0]) { $style->setBackground($match[1]); } else { try { $style->setOption($match[1]); } catch (\InvalidArgumentException $e) { return false; } } } return $style; } /** * Applies current style from stack to text, if must be applied. * * @param string $text Input text * * @return string Styled text */ private function applyCurrentStyle($text) { return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; /** * Formatter style class for defining styles. * * @author Konstantin Kudryashov * * @api */ class OutputFormatterStyle implements OutputFormatterStyleInterface { private static $availableForegroundColors = array( 'black' => array('set' => 30, 'unset' => 39), 'red' => array('set' => 31, 'unset' => 39), 'green' => array('set' => 32, 'unset' => 39), 'yellow' => array('set' => 33, 'unset' => 39), 'blue' => array('set' => 34, 'unset' => 39), 'magenta' => array('set' => 35, 'unset' => 39), 'cyan' => array('set' => 36, 'unset' => 39), 'white' => array('set' => 37, 'unset' => 39), 'default' => array('set' => 39, 'unset' => 39), ); private static $availableBackgroundColors = array( 'black' => array('set' => 40, 'unset' => 49), 'red' => array('set' => 41, 'unset' => 49), 'green' => array('set' => 42, 'unset' => 49), 'yellow' => array('set' => 43, 'unset' => 49), 'blue' => array('set' => 44, 'unset' => 49), 'magenta' => array('set' => 45, 'unset' => 49), 'cyan' => array('set' => 46, 'unset' => 49), 'white' => array('set' => 47, 'unset' => 49), 'default' => array('set' => 49, 'unset' => 49), ); private static $availableOptions = array( 'bold' => array('set' => 1, 'unset' => 22), 'underscore' => array('set' => 4, 'unset' => 24), 'blink' => array('set' => 5, 'unset' => 25), 'reverse' => array('set' => 7, 'unset' => 27), 'conceal' => array('set' => 8, 'unset' => 28), ); private $foreground; private $background; private $options = array(); /** * Initializes output formatter style. * * @param string|null $foreground The style foreground color name * @param string|null $background The style background color name * @param array $options The style options * * @api */ public function __construct($foreground = null, $background = null, array $options = array()) { if (null !== $foreground) { $this->setForeground($foreground); } if (null !== $background) { $this->setBackground($background); } if (count($options)) { $this->setOptions($options); } } /** * Sets style foreground color. * * @param string|null $color The color name * * @throws \InvalidArgumentException When the color name isn't defined * * @api */ public function setForeground($color = null) { if (null === $color) { $this->foreground = null; return; } if (!isset(static::$availableForegroundColors[$color])) { throw new \InvalidArgumentException(sprintf( 'Invalid foreground color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableForegroundColors)) )); } $this->foreground = static::$availableForegroundColors[$color]; } /** * Sets style background color. * * @param string|null $color The color name * * @throws \InvalidArgumentException When the color name isn't defined * * @api */ public function setBackground($color = null) { if (null === $color) { $this->background = null; return; } if (!isset(static::$availableBackgroundColors[$color])) { throw new \InvalidArgumentException(sprintf( 'Invalid background color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableBackgroundColors)) )); } $this->background = static::$availableBackgroundColors[$color]; } /** * Sets some specific style option. * * @param string $option The option name * * @throws \InvalidArgumentException When the option name isn't defined * * @api */ public function setOption($option) { if (!isset(static::$availableOptions[$option])) { throw new \InvalidArgumentException(sprintf( 'Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)) )); } if (!in_array(static::$availableOptions[$option], $this->options)) { $this->options[] = static::$availableOptions[$option]; } } /** * Unsets some specific style option. * * @param string $option The option name * * @throws \InvalidArgumentException When the option name isn't defined */ public function unsetOption($option) { if (!isset(static::$availableOptions[$option])) { throw new \InvalidArgumentException(sprintf( 'Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)) )); } $pos = array_search(static::$availableOptions[$option], $this->options); if (false !== $pos) { unset($this->options[$pos]); } } /** * Sets multiple style options at once. * * @param array $options */ public function setOptions(array $options) { $this->options = array(); foreach ($options as $option) { $this->setOption($option); } } /** * Applies the style to a given text. * * @param string $text The text to style * * @return string */ public function apply($text) { $setCodes = array(); $unsetCodes = array(); if (null !== $this->foreground) { $setCodes[] = $this->foreground['set']; $unsetCodes[] = $this->foreground['unset']; } if (null !== $this->background) { $setCodes[] = $this->background['set']; $unsetCodes[] = $this->background['unset']; } if (count($this->options)) { foreach ($this->options as $option) { $setCodes[] = $option['set']; $unsetCodes[] = $option['unset']; } } if (0 === count($setCodes)) { return $text; } return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; /** * Formatter interface for console output. * * @author Konstantin Kudryashov * * @api */ interface OutputFormatterInterface { /** * Sets the decorated flag. * * @param bool $decorated Whether to decorate the messages or not * * @api */ public function setDecorated($decorated); /** * Gets the decorated flag. * * @return bool true if the output will decorate messages, false otherwise * * @api */ public function isDecorated(); /** * Sets a new style. * * @param string $name The style name * @param OutputFormatterStyleInterface $style The style instance * * @api */ public function setStyle($name, OutputFormatterStyleInterface $style); /** * Checks if output formatter has style with specified name. * * @param string $name * * @return bool * * @api */ public function hasStyle($name); /** * Gets style options from style with specified name. * * @param string $name * * @return OutputFormatterStyleInterface * * @api */ public function getStyle($name); /** * Formats a message according to the given styles. * * @param string $message The message to style * * @return string The styled message * * @api */ public function format($message); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; /** * Represents a command line argument. * * @author Fabien Potencier * * @api */ class InputArgument { const REQUIRED = 1; const OPTIONAL = 2; const IS_ARRAY = 4; private $name; private $mode; private $default; private $description; /** * Constructor. * * @param string $name The argument name * @param int $mode The argument mode: self::REQUIRED or self::OPTIONAL * @param string $description A description text * @param mixed $default The default value (for self::OPTIONAL mode only) * * @throws \InvalidArgumentException When argument mode is not valid * * @api */ public function __construct($name, $mode = null, $description = '', $default = null) { if (null === $mode) { $mode = self::OPTIONAL; } elseif (!is_int($mode) || $mode > 7 || $mode < 1) { throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); } $this->name = $name; $this->mode = $mode; $this->description = $description; $this->setDefault($default); } /** * Returns the argument name. * * @return string The argument name */ public function getName() { return $this->name; } /** * Returns true if the argument is required. * * @return bool true if parameter mode is self::REQUIRED, false otherwise */ public function isRequired() { return self::REQUIRED === (self::REQUIRED & $this->mode); } /** * Returns true if the argument can take multiple values. * * @return bool true if mode is self::IS_ARRAY, false otherwise */ public function isArray() { return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); } /** * Sets the default value. * * @param mixed $default The default value * * @throws \LogicException When incorrect default value is given */ public function setDefault($default = null) { if (self::REQUIRED === $this->mode && null !== $default) { throw new \LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); } if ($this->isArray()) { if (null === $default) { $default = array(); } elseif (!is_array($default)) { throw new \LogicException('A default value for an array argument must be an array.'); } } $this->default = $default; } /** * Returns the default value. * * @return mixed The default value */ public function getDefault() { return $this->default; } /** * Returns the description text. * * @return string The description text */ public function getDescription() { return $this->description; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; /** * StringInput represents an input provided as a string. * * Usage: * * $input = new StringInput('foo --bar="foobar"'); * * @author Fabien Potencier * * @api */ class StringInput extends ArgvInput { const REGEX_STRING = '([^\s]+?)(?:\s|(?setTokens($this->tokenize($input)); if (null !== $definition) { $this->bind($definition); } } /** * Tokenizes a string. * * @param string $input The input to tokenize * * @return array An array of tokens * * @throws \InvalidArgumentException When unable to parse input (should never happen) */ private function tokenize($input) { $tokens = array(); $length = strlen($input); $cursor = 0; while ($cursor < $length) { if (preg_match('/\s+/A', $input, $match, null, $cursor)) { } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor)) { $tokens[] = $match[1].$match[2].stripcslashes(str_replace(array('"\'', '\'"', '\'\'', '""'), '', substr($match[3], 1, strlen($match[3]) - 2))); } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) { $tokens[] = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2)); } elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) { $tokens[] = stripcslashes($match[1]); } else { // should never happen throw new \InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10))); } $cursor += strlen($match[0]); } return $tokens; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; /** * ArgvInput represents an input coming from the CLI arguments. * * Usage: * * $input = new ArgvInput(); * * By default, the `$_SERVER['argv']` array is used for the input values. * * This can be overridden by explicitly passing the input values in the constructor: * * $input = new ArgvInput($_SERVER['argv']); * * If you pass it yourself, don't forget that the first element of the array * is the name of the running application. * * When passing an argument to the constructor, be sure that it respects * the same rules as the argv one. It's almost always better to use the * `StringInput` when you want to provide your own input. * * @author Fabien Potencier * * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02 * * @api */ class ArgvInput extends Input { private $tokens; private $parsed; /** * Constructor. * * @param array $argv An array of parameters from the CLI (in the argv format) * @param InputDefinition $definition A InputDefinition instance * * @api */ public function __construct(array $argv = null, InputDefinition $definition = null) { if (null === $argv) { $argv = $_SERVER['argv']; } // strip the application name array_shift($argv); $this->tokens = $argv; parent::__construct($definition); } protected function setTokens(array $tokens) { $this->tokens = $tokens; } /** * Processes command line arguments. */ protected function parse() { $parseOptions = true; $this->parsed = $this->tokens; while (null !== $token = array_shift($this->parsed)) { if ($parseOptions && '' == $token) { $this->parseArgument($token); } elseif ($parseOptions && '--' == $token) { $parseOptions = false; } elseif ($parseOptions && 0 === strpos($token, '--')) { $this->parseLongOption($token); } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { $this->parseShortOption($token); } else { $this->parseArgument($token); } } } /** * Parses a short option. * * @param string $token The current token. */ private function parseShortOption($token) { $name = substr($token, 1); if (strlen($name) > 1) { if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) { // an option with a value (with no space) $this->addShortOption($name[0], substr($name, 1)); } else { $this->parseShortOptionSet($name); } } else { $this->addShortOption($name, null); } } /** * Parses a short option set. * * @param string $name The current token * * @throws \RuntimeException When option given doesn't exist */ private function parseShortOptionSet($name) { $len = strlen($name); for ($i = 0; $i < $len; ++$i) { if (!$this->definition->hasShortcut($name[$i])) { throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i])); } $option = $this->definition->getOptionForShortcut($name[$i]); if ($option->acceptValue()) { $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); break; } else { $this->addLongOption($option->getName(), null); } } } /** * Parses a long option. * * @param string $token The current token */ private function parseLongOption($token) { $name = substr($token, 2); if (false !== $pos = strpos($name, '=')) { $this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1)); } else { $this->addLongOption($name, null); } } /** * Parses an argument. * * @param string $token The current token * * @throws \RuntimeException When too many arguments are given */ private function parseArgument($token) { $c = count($this->arguments); // if input is expecting another argument, add it if ($this->definition->hasArgument($c)) { $arg = $this->definition->getArgument($c); $this->arguments[$arg->getName()] = $arg->isArray() ? array($token) : $token; // if last argument isArray(), append token to last argument } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { $arg = $this->definition->getArgument($c - 1); $this->arguments[$arg->getName()][] = $token; // unexpected argument } else { throw new \RuntimeException('Too many arguments.'); } } /** * Adds a short option value. * * @param string $shortcut The short option key * @param mixed $value The value for the option * * @throws \RuntimeException When option given doesn't exist */ private function addShortOption($shortcut, $value) { if (!$this->definition->hasShortcut($shortcut)) { throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); } $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); } /** * Adds a long option value. * * @param string $name The long option key * @param mixed $value The value for the option * * @throws \RuntimeException When option given doesn't exist */ private function addLongOption($name, $value) { if (!$this->definition->hasOption($name)) { throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name)); } $option = $this->definition->getOption($name); // Convert empty values to null if (!isset($value[0])) { $value = null; } if (null !== $value && !$option->acceptValue()) { throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); } if (null === $value && $option->acceptValue() && count($this->parsed)) { // if option accepts an optional or mandatory argument // let's see if there is one provided $next = array_shift($this->parsed); if (isset($next[0]) && '-' !== $next[0]) { $value = $next; } elseif (empty($next)) { $value = ''; } else { array_unshift($this->parsed, $next); } } if (null === $value) { if ($option->isValueRequired()) { throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name)); } if (!$option->isArray()) { $value = $option->isValueOptional() ? $option->getDefault() : true; } } if ($option->isArray()) { $this->options[$name][] = $value; } else { $this->options[$name] = $value; } } /** * Returns the first argument from the raw parameters (not parsed). * * @return string The value of the first argument or null otherwise */ public function getFirstArgument() { foreach ($this->tokens as $token) { if ($token && '-' === $token[0]) { continue; } return $token; } } /** * Returns true if the raw parameters (not parsed) contain a value. * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. * * @param string|array $values The value(s) to look for in the raw parameters (can be an array) * * @return bool true if the value is contained in the raw parameters */ public function hasParameterOption($values) { $values = (array) $values; foreach ($this->tokens as $token) { foreach ($values as $value) { if ($token === $value || 0 === strpos($token, $value.'=')) { return true; } } } return false; } /** * Returns the value of a raw option (not parsed). * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. * * @param string|array $values The value(s) to look for in the raw parameters (can be an array) * @param mixed $default The default value to return if no result is found * * @return mixed The option value */ public function getParameterOption($values, $default = false) { $values = (array) $values; $tokens = $this->tokens; while (0 < count($tokens)) { $token = array_shift($tokens); foreach ($values as $value) { if ($token === $value || 0 === strpos($token, $value.'=')) { if (false !== $pos = strpos($token, '=')) { return substr($token, $pos + 1); } return array_shift($tokens); } } } return $default; } /** * Returns a stringified representation of the args passed to the command. * * @return string */ public function __toString() { $self = $this; $tokens = array_map(function ($token) use ($self) { if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { return $match[1].$self->escapeToken($match[2]); } if ($token && $token[0] !== '-') { return $self->escapeToken($token); } return $token; }, $this->tokens); return implode(' ', $tokens); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; /** * Input is the base class for all concrete Input classes. * * Three concrete classes are provided by default: * * * `ArgvInput`: The input comes from the CLI arguments (argv) * * `StringInput`: The input is provided as a string * * `ArrayInput`: The input is provided as an array * * @author Fabien Potencier */ abstract class Input implements InputInterface { /** * @var InputDefinition */ protected $definition; protected $options = array(); protected $arguments = array(); protected $interactive = true; /** * Constructor. * * @param InputDefinition $definition A InputDefinition instance */ public function __construct(InputDefinition $definition = null) { if (null === $definition) { $this->definition = new InputDefinition(); } else { $this->bind($definition); $this->validate(); } } /** * Binds the current Input instance with the given arguments and options. * * @param InputDefinition $definition A InputDefinition instance */ public function bind(InputDefinition $definition) { $this->arguments = array(); $this->options = array(); $this->definition = $definition; $this->parse(); } /** * Processes command line arguments. */ abstract protected function parse(); /** * Validates the input. * * @throws \RuntimeException When not enough arguments are given */ public function validate() { if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) { throw new \RuntimeException('Not enough arguments.'); } } /** * Checks if the input is interactive. * * @return bool Returns true if the input is interactive */ public function isInteractive() { return $this->interactive; } /** * Sets the input interactivity. * * @param bool $interactive If the input should be interactive */ public function setInteractive($interactive) { $this->interactive = (bool) $interactive; } /** * Returns the argument values. * * @return array An array of argument values */ public function getArguments() { return array_merge($this->definition->getArgumentDefaults(), $this->arguments); } /** * Returns the argument value for a given argument name. * * @param string $name The argument name * * @return mixed The argument value * * @throws \InvalidArgumentException When argument given doesn't exist */ public function getArgument($name) { if (!$this->definition->hasArgument($name)) { throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault(); } /** * Sets an argument value by name. * * @param string $name The argument name * @param string $value The argument value * * @throws \InvalidArgumentException When argument given doesn't exist */ public function setArgument($name, $value) { if (!$this->definition->hasArgument($name)) { throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } $this->arguments[$name] = $value; } /** * Returns true if an InputArgument object exists by name or position. * * @param string|int $name The InputArgument name or position * * @return bool true if the InputArgument object exists, false otherwise */ public function hasArgument($name) { return $this->definition->hasArgument($name); } /** * Returns the options values. * * @return array An array of option values */ public function getOptions() { return array_merge($this->definition->getOptionDefaults(), $this->options); } /** * Returns the option value for a given option name. * * @param string $name The option name * * @return mixed The option value * * @throws \InvalidArgumentException When option given doesn't exist */ public function getOption($name) { if (!$this->definition->hasOption($name)) { throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); } return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); } /** * Sets an option value by name. * * @param string $name The option name * @param string|bool $value The option value * * @throws \InvalidArgumentException When option given doesn't exist */ public function setOption($name, $value) { if (!$this->definition->hasOption($name)) { throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); } $this->options[$name] = $value; } /** * Returns true if an InputOption object exists by name. * * @param string $name The InputOption name * * @return bool true if the InputOption object exists, false otherwise */ public function hasOption($name) { return $this->definition->hasOption($name); } /** * Escapes a token through escapeshellarg if it contains unsafe chars. * * @param string $token * * @return string */ public function escapeToken($token) { return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; /** * ArrayInput represents an input provided as an array. * * Usage: * * $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar')); * * @author Fabien Potencier * * @api */ class ArrayInput extends Input { private $parameters; /** * Constructor. * * @param array $parameters An array of parameters * @param InputDefinition $definition A InputDefinition instance * * @api */ public function __construct(array $parameters, InputDefinition $definition = null) { $this->parameters = $parameters; parent::__construct($definition); } /** * Returns the first argument from the raw parameters (not parsed). * * @return string The value of the first argument or null otherwise */ public function getFirstArgument() { foreach ($this->parameters as $key => $value) { if ($key && '-' === $key[0]) { continue; } return $value; } } /** * Returns true if the raw parameters (not parsed) contain a value. * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. * * @param string|array $values The values to look for in the raw parameters (can be an array) * * @return bool true if the value is contained in the raw parameters */ public function hasParameterOption($values) { $values = (array) $values; foreach ($this->parameters as $k => $v) { if (!is_int($k)) { $v = $k; } if (in_array($v, $values)) { return true; } } return false; } /** * Returns the value of a raw option (not parsed). * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. * * @param string|array $values The value(s) to look for in the raw parameters (can be an array) * @param mixed $default The default value to return if no result is found * * @return mixed The option value */ public function getParameterOption($values, $default = false) { $values = (array) $values; foreach ($this->parameters as $k => $v) { if (is_int($k)) { if (in_array($v, $values)) { return true; } } elseif (in_array($k, $values)) { return $v; } } return $default; } /** * Returns a stringified representation of the args passed to the command. * * @return string */ public function __toString() { $params = array(); foreach ($this->parameters as $param => $val) { if ($param && '-' === $param[0]) { $params[] = $param.('' != $val ? '='.$this->escapeToken($val) : ''); } else { $params[] = $this->escapeToken($val); } } return implode(' ', $params); } /** * Processes command line arguments. */ protected function parse() { foreach ($this->parameters as $key => $value) { if (0 === strpos($key, '--')) { $this->addLongOption(substr($key, 2), $value); } elseif ('-' === $key[0]) { $this->addShortOption(substr($key, 1), $value); } else { $this->addArgument($key, $value); } } } /** * Adds a short option value. * * @param string $shortcut The short option key * @param mixed $value The value for the option * * @throws \InvalidArgumentException When option given doesn't exist */ private function addShortOption($shortcut, $value) { if (!$this->definition->hasShortcut($shortcut)) { throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); } $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); } /** * Adds a long option value. * * @param string $name The long option key * @param mixed $value The value for the option * * @throws \InvalidArgumentException When option given doesn't exist * @throws \InvalidArgumentException When a required value is missing */ private function addLongOption($name, $value) { if (!$this->definition->hasOption($name)) { throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); } $option = $this->definition->getOption($name); if (null === $value) { if ($option->isValueRequired()) { throw new \InvalidArgumentException(sprintf('The "--%s" option requires a value.', $name)); } $value = $option->isValueOptional() ? $option->getDefault() : true; } $this->options[$name] = $value; } /** * Adds an argument value. * * @param string $name The argument name * @param mixed $value The value for the argument * * @throws \InvalidArgumentException When argument given doesn't exist */ private function addArgument($name, $value) { if (!$this->definition->hasArgument($name)) { throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } $this->arguments[$name] = $value; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; /** * InputAwareInterface should be implemented by classes that depends on the * Console Input. * * @author Wouter J */ interface InputAwareInterface { /** * Sets the Console Input. * * @param InputInterface */ public function setInput(InputInterface $input); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; /** * InputInterface is the interface implemented by all input classes. * * @author Fabien Potencier */ interface InputInterface { /** * Returns the first argument from the raw parameters (not parsed). * * @return string The value of the first argument or null otherwise */ public function getFirstArgument(); /** * Returns true if the raw parameters (not parsed) contain a value. * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. * * @param string|array $values The values to look for in the raw parameters (can be an array) * * @return bool true if the value is contained in the raw parameters */ public function hasParameterOption($values); /** * Returns the value of a raw option (not parsed). * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. * * @param string|array $values The value(s) to look for in the raw parameters (can be an array) * @param mixed $default The default value to return if no result is found * * @return mixed The option value */ public function getParameterOption($values, $default = false); /** * Binds the current Input instance with the given arguments and options. * * @param InputDefinition $definition A InputDefinition instance */ public function bind(InputDefinition $definition); /** * Validates if arguments given are correct. * * Throws an exception when not enough arguments are given. * * @throws \RuntimeException */ public function validate(); /** * Returns all the given arguments merged with the default values. * * @return array */ public function getArguments(); /** * Gets argument by name. * * @param string $name The name of the argument * * @return mixed */ public function getArgument($name); /** * Sets an argument value by name. * * @param string $name The argument name * @param string $value The argument value * * @throws \InvalidArgumentException When argument given doesn't exist */ public function setArgument($name, $value); /** * Returns true if an InputArgument object exists by name or position. * * @param string|int $name The InputArgument name or position * * @return bool true if the InputArgument object exists, false otherwise */ public function hasArgument($name); /** * Returns all the given options merged with the default values. * * @return array */ public function getOptions(); /** * Gets an option by name. * * @param string $name The name of the option * * @return mixed */ public function getOption($name); /** * Sets an option value by name. * * @param string $name The option name * @param string|bool $value The option value * * @throws \InvalidArgumentException When option given doesn't exist */ public function setOption($name, $value); /** * Returns true if an InputOption object exists by name. * * @param string $name The InputOption name * * @return bool true if the InputOption object exists, false otherwise */ public function hasOption($name); /** * Is this input means interactive? * * @return bool */ public function isInteractive(); /** * Sets the input interactivity. * * @param bool $interactive If the input should be interactive */ public function setInteractive($interactive); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; /** * Represents a command line option. * * @author Fabien Potencier * * @api */ class InputOption { const VALUE_NONE = 1; const VALUE_REQUIRED = 2; const VALUE_OPTIONAL = 4; const VALUE_IS_ARRAY = 8; private $name; private $shortcut; private $mode; private $default; private $description; /** * Constructor. * * @param string $name The option name * @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts * @param int $mode The option mode: One of the VALUE_* constants * @param string $description A description text * @param mixed $default The default value (must be null for self::VALUE_REQUIRED or self::VALUE_NONE) * * @throws \InvalidArgumentException If option mode is invalid or incompatible * * @api */ public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null) { if (0 === strpos($name, '--')) { $name = substr($name, 2); } if (empty($name)) { throw new \InvalidArgumentException('An option name cannot be empty.'); } if (empty($shortcut)) { $shortcut = null; } if (null !== $shortcut) { if (is_array($shortcut)) { $shortcut = implode('|', $shortcut); } $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); $shortcuts = array_filter($shortcuts); $shortcut = implode('|', $shortcuts); if (empty($shortcut)) { throw new \InvalidArgumentException('An option shortcut cannot be empty.'); } } if (null === $mode) { $mode = self::VALUE_NONE; } elseif (!is_int($mode) || $mode > 15 || $mode < 1) { throw new \InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); } $this->name = $name; $this->shortcut = $shortcut; $this->mode = $mode; $this->description = $description; if ($this->isArray() && !$this->acceptValue()) { throw new \InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); } $this->setDefault($default); } /** * Returns the option shortcut. * * @return string The shortcut */ public function getShortcut() { return $this->shortcut; } /** * Returns the option name. * * @return string The name */ public function getName() { return $this->name; } /** * Returns true if the option accepts a value. * * @return bool true if value mode is not self::VALUE_NONE, false otherwise */ public function acceptValue() { return $this->isValueRequired() || $this->isValueOptional(); } /** * Returns true if the option requires a value. * * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise */ public function isValueRequired() { return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); } /** * Returns true if the option takes an optional value. * * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise */ public function isValueOptional() { return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); } /** * Returns true if the option can take multiple values. * * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise */ public function isArray() { return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); } /** * Sets the default value. * * @param mixed $default The default value * * @throws \LogicException When incorrect default value is given */ public function setDefault($default = null) { if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { throw new \LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); } if ($this->isArray()) { if (null === $default) { $default = array(); } elseif (!is_array($default)) { throw new \LogicException('A default value for an array option must be an array.'); } } $this->default = $this->acceptValue() ? $default : false; } /** * Returns the default value. * * @return mixed The default value */ public function getDefault() { return $this->default; } /** * Returns the description text. * * @return string The description text */ public function getDescription() { return $this->description; } /** * Checks whether the given option equals this one. * * @param InputOption $option option to compare * * @return bool */ public function equals(InputOption $option) { return $option->getName() === $this->getName() && $option->getShortcut() === $this->getShortcut() && $option->getDefault() === $this->getDefault() && $option->isArray() === $this->isArray() && $option->isValueRequired() === $this->isValueRequired() && $option->isValueOptional() === $this->isValueOptional() ; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Descriptor\TextDescriptor; use Symfony\Component\Console\Descriptor\XmlDescriptor; use Symfony\Component\Console\Output\BufferedOutput; /** * A InputDefinition represents a set of valid command line arguments and options. * * Usage: * * $definition = new InputDefinition(array( * new InputArgument('name', InputArgument::REQUIRED), * new InputOption('foo', 'f', InputOption::VALUE_REQUIRED), * )); * * @author Fabien Potencier * * @api */ class InputDefinition { private $arguments; private $requiredCount; private $hasAnArrayArgument = false; private $hasOptional; private $options; private $shortcuts; /** * Constructor. * * @param array $definition An array of InputArgument and InputOption instance * * @api */ public function __construct(array $definition = array()) { $this->setDefinition($definition); } /** * Sets the definition of the input. * * @param array $definition The definition array * * @api */ public function setDefinition(array $definition) { $arguments = array(); $options = array(); foreach ($definition as $item) { if ($item instanceof InputOption) { $options[] = $item; } else { $arguments[] = $item; } } $this->setArguments($arguments); $this->setOptions($options); } /** * Sets the InputArgument objects. * * @param InputArgument[] $arguments An array of InputArgument objects * * @api */ public function setArguments($arguments = array()) { $this->arguments = array(); $this->requiredCount = 0; $this->hasOptional = false; $this->hasAnArrayArgument = false; $this->addArguments($arguments); } /** * Adds an array of InputArgument objects. * * @param InputArgument[] $arguments An array of InputArgument objects * * @api */ public function addArguments($arguments = array()) { if (null !== $arguments) { foreach ($arguments as $argument) { $this->addArgument($argument); } } } /** * Adds an InputArgument object. * * @param InputArgument $argument An InputArgument object * * @throws \LogicException When incorrect argument is given * * @api */ public function addArgument(InputArgument $argument) { if (isset($this->arguments[$argument->getName()])) { throw new \LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); } if ($this->hasAnArrayArgument) { throw new \LogicException('Cannot add an argument after an array argument.'); } if ($argument->isRequired() && $this->hasOptional) { throw new \LogicException('Cannot add a required argument after an optional one.'); } if ($argument->isArray()) { $this->hasAnArrayArgument = true; } if ($argument->isRequired()) { ++$this->requiredCount; } else { $this->hasOptional = true; } $this->arguments[$argument->getName()] = $argument; } /** * Returns an InputArgument by name or by position. * * @param string|int $name The InputArgument name or position * * @return InputArgument An InputArgument object * * @throws \InvalidArgumentException When argument given doesn't exist * * @api */ public function getArgument($name) { if (!$this->hasArgument($name)) { throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; return $arguments[$name]; } /** * Returns true if an InputArgument object exists by name or position. * * @param string|int $name The InputArgument name or position * * @return bool true if the InputArgument object exists, false otherwise * * @api */ public function hasArgument($name) { $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; return isset($arguments[$name]); } /** * Gets the array of InputArgument objects. * * @return InputArgument[] An array of InputArgument objects * * @api */ public function getArguments() { return $this->arguments; } /** * Returns the number of InputArguments. * * @return int The number of InputArguments */ public function getArgumentCount() { return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); } /** * Returns the number of required InputArguments. * * @return int The number of required InputArguments */ public function getArgumentRequiredCount() { return $this->requiredCount; } /** * Gets the default values. * * @return array An array of default values */ public function getArgumentDefaults() { $values = array(); foreach ($this->arguments as $argument) { $values[$argument->getName()] = $argument->getDefault(); } return $values; } /** * Sets the InputOption objects. * * @param InputOption[] $options An array of InputOption objects * * @api */ public function setOptions($options = array()) { $this->options = array(); $this->shortcuts = array(); $this->addOptions($options); } /** * Adds an array of InputOption objects. * * @param InputOption[] $options An array of InputOption objects * * @api */ public function addOptions($options = array()) { foreach ($options as $option) { $this->addOption($option); } } /** * Adds an InputOption object. * * @param InputOption $option An InputOption object * * @throws \LogicException When option given already exist * * @api */ public function addOption(InputOption $option) { if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { throw new \LogicException(sprintf('An option named "%s" already exists.', $option->getName())); } if ($option->getShortcut()) { foreach (explode('|', $option->getShortcut()) as $shortcut) { if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) { throw new \LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); } } } $this->options[$option->getName()] = $option; if ($option->getShortcut()) { foreach (explode('|', $option->getShortcut()) as $shortcut) { $this->shortcuts[$shortcut] = $option->getName(); } } } /** * Returns an InputOption by name. * * @param string $name The InputOption name * * @return InputOption A InputOption object * * @throws \InvalidArgumentException When option given doesn't exist * * @api */ public function getOption($name) { if (!$this->hasOption($name)) { throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); } return $this->options[$name]; } /** * Returns true if an InputOption object exists by name. * * @param string $name The InputOption name * * @return bool true if the InputOption object exists, false otherwise * * @api */ public function hasOption($name) { return isset($this->options[$name]); } /** * Gets the array of InputOption objects. * * @return InputOption[] An array of InputOption objects * * @api */ public function getOptions() { return $this->options; } /** * Returns true if an InputOption object exists by shortcut. * * @param string $name The InputOption shortcut * * @return bool true if the InputOption object exists, false otherwise */ public function hasShortcut($name) { return isset($this->shortcuts[$name]); } /** * Gets an InputOption by shortcut. * * @param string $shortcut the Shortcut name * * @return InputOption An InputOption object */ public function getOptionForShortcut($shortcut) { return $this->getOption($this->shortcutToName($shortcut)); } /** * Gets an array of default values. * * @return array An array of all default values */ public function getOptionDefaults() { $values = array(); foreach ($this->options as $option) { $values[$option->getName()] = $option->getDefault(); } return $values; } /** * Returns the InputOption name given a shortcut. * * @param string $shortcut The shortcut * * @return string The InputOption name * * @throws \InvalidArgumentException When option given does not exist */ private function shortcutToName($shortcut) { if (!isset($this->shortcuts[$shortcut])) { throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); } return $this->shortcuts[$shortcut]; } /** * Gets the synopsis. * * @param bool $short Whether to return the short version (with options folded) or not * * @return string The synopsis */ public function getSynopsis($short = false) { $elements = array(); if ($short && $this->getOptions()) { $elements[] = '[options]'; } elseif (!$short) { foreach ($this->getOptions() as $option) { $value = ''; if ($option->acceptValue()) { $value = sprintf( ' %s%s%s', $option->isValueOptional() ? '[' : '', strtoupper($option->getName()), $option->isValueOptional() ? ']' : '' ); } $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value); } } if (count($elements) && $this->getArguments()) { $elements[] = '[--]'; } foreach ($this->getArguments() as $argument) { $element = '<'.$argument->getName().'>'; if (!$argument->isRequired()) { $element = '['.$element.']'; } elseif ($argument->isArray()) { $element = $element.' ('.$element.')'; } if ($argument->isArray()) { $element .= '...'; } $elements[] = $element; } return implode(' ', $elements); } /** * Returns a textual representation of the InputDefinition. * * @return string A string representing the InputDefinition * * @deprecated since version 2.3, to be removed in 3.0. */ public function asText() { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); $descriptor = new TextDescriptor(); $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); $descriptor->describe($output, $this, array('raw_output' => true)); return $output->fetch(); } /** * Returns an XML representation of the InputDefinition. * * @param bool $asDom Whether to return a DOM or an XML string * * @return string|\DOMDocument An XML string representing the InputDefinition * * @deprecated since version 2.3, to be removed in 3.0. */ public function asXml($asDom = false) { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); $descriptor = new XmlDescriptor(); if ($asDom) { return $descriptor->getInputDefinitionDocument($this); } $output = new BufferedOutput(); $descriptor->describe($output, $this); return $output->fetch(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console; use Symfony\Component\Console\Descriptor\TextDescriptor; use Symfony\Component\Console\Descriptor\XmlDescriptor; use Symfony\Component\Console\Helper\DebugFormatterHelper; use Symfony\Component\Console\Helper\ProcessHelper; use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputAwareInterface; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\HelpCommand; use Symfony\Component\Console\Command\ListCommand; use Symfony\Component\Console\Helper\HelperSet; use Symfony\Component\Console\Helper\FormatterHelper; use Symfony\Component\Console\Helper\DialogHelper; use Symfony\Component\Console\Helper\ProgressHelper; use Symfony\Component\Console\Helper\TableHelper; use Symfony\Component\Console\Event\ConsoleCommandEvent; use Symfony\Component\Console\Event\ConsoleExceptionEvent; use Symfony\Component\Console\Event\ConsoleTerminateEvent; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * An Application is the container for a collection of commands. * * It is the main entry point of a Console application. * * This class is optimized for a standard CLI environment. * * Usage: * * $app = new Application('myapp', '1.0 (stable)'); * $app->add(new SimpleCommand()); * $app->run(); * * @author Fabien Potencier * * @api */ class Application { private $commands = array(); private $wantHelps = false; private $runningCommand; private $name; private $version; private $catchExceptions = true; private $autoExit = true; private $definition; private $helperSet; private $dispatcher; private $terminalDimensions; private $defaultCommand; /** * Constructor. * * @param string $name The name of the application * @param string $version The version of the application * * @api */ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') { $this->name = $name; $this->version = $version; $this->defaultCommand = 'list'; $this->helperSet = $this->getDefaultHelperSet(); $this->definition = $this->getDefaultInputDefinition(); foreach ($this->getDefaultCommands() as $command) { $this->add($command); } } public function setDispatcher(EventDispatcherInterface $dispatcher) { $this->dispatcher = $dispatcher; } /** * Runs the current application. * * @param InputInterface $input An Input instance * @param OutputInterface $output An Output instance * * @return int 0 if everything went fine, or an error code * * @throws \Exception When doRun returns Exception * * @api */ public function run(InputInterface $input = null, OutputInterface $output = null) { if (null === $input) { $input = new ArgvInput(); } if (null === $output) { $output = new ConsoleOutput(); } $this->configureIO($input, $output); try { $exitCode = $this->doRun($input, $output); } catch (\Exception $e) { if (!$this->catchExceptions) { throw $e; } if ($output instanceof ConsoleOutputInterface) { $this->renderException($e, $output->getErrorOutput()); } else { $this->renderException($e, $output); } $exitCode = $e->getCode(); if (is_numeric($exitCode)) { $exitCode = (int) $exitCode; if (0 === $exitCode) { $exitCode = 1; } } else { $exitCode = 1; } } if ($this->autoExit) { if ($exitCode > 255) { $exitCode = 255; } exit($exitCode); } return $exitCode; } /** * Runs the current application. * * @param InputInterface $input An Input instance * @param OutputInterface $output An Output instance * * @return int 0 if everything went fine, or an error code */ public function doRun(InputInterface $input, OutputInterface $output) { if (true === $input->hasParameterOption(array('--version', '-V'))) { $output->writeln($this->getLongVersion()); return 0; } $name = $this->getCommandName($input); if (true === $input->hasParameterOption(array('--help', '-h'))) { if (!$name) { $name = 'help'; $input = new ArrayInput(array('command' => 'help')); } else { $this->wantHelps = true; } } if (!$name) { $name = $this->defaultCommand; $input = new ArrayInput(array('command' => $this->defaultCommand)); } // the command name MUST be the first element of the input $command = $this->find($name); $this->runningCommand = $command; $exitCode = $this->doRunCommand($command, $input, $output); $this->runningCommand = null; return $exitCode; } /** * Set a helper set to be used with the command. * * @param HelperSet $helperSet The helper set * * @api */ public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; } /** * Get the helper set associated with the command. * * @return HelperSet The HelperSet instance associated with this command * * @api */ public function getHelperSet() { return $this->helperSet; } /** * Set an input definition set to be used with this application. * * @param InputDefinition $definition The input definition * * @api */ public function setDefinition(InputDefinition $definition) { $this->definition = $definition; } /** * Gets the InputDefinition related to this Application. * * @return InputDefinition The InputDefinition instance */ public function getDefinition() { return $this->definition; } /** * Gets the help message. * * @return string A help message. */ public function getHelp() { return $this->getLongVersion(); } /** * Sets whether to catch exceptions or not during commands execution. * * @param bool $boolean Whether to catch exceptions or not during commands execution * * @api */ public function setCatchExceptions($boolean) { $this->catchExceptions = (bool) $boolean; } /** * Sets whether to automatically exit after a command execution or not. * * @param bool $boolean Whether to automatically exit after a command execution or not * * @api */ public function setAutoExit($boolean) { $this->autoExit = (bool) $boolean; } /** * Gets the name of the application. * * @return string The application name * * @api */ public function getName() { return $this->name; } /** * Sets the application name. * * @param string $name The application name * * @api */ public function setName($name) { $this->name = $name; } /** * Gets the application version. * * @return string The application version * * @api */ public function getVersion() { return $this->version; } /** * Sets the application version. * * @param string $version The application version * * @api */ public function setVersion($version) { $this->version = $version; } /** * Returns the long version of the application. * * @return string The long application version * * @api */ public function getLongVersion() { if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) { return sprintf('%s version %s', $this->getName(), $this->getVersion()); } return 'Console Tool'; } /** * Registers a new command. * * @param string $name The command name * * @return Command The newly created command * * @api */ public function register($name) { return $this->add(new Command($name)); } /** * Adds an array of command objects. * * @param Command[] $commands An array of commands * * @api */ public function addCommands(array $commands) { foreach ($commands as $command) { $this->add($command); } } /** * Adds a command object. * * If a command with the same name already exists, it will be overridden. * * @param Command $command A Command object * * @return Command The registered command * * @api */ public function add(Command $command) { $command->setApplication($this); if (!$command->isEnabled()) { $command->setApplication(null); return; } if (null === $command->getDefinition()) { throw new \LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command))); } $this->commands[$command->getName()] = $command; foreach ($command->getAliases() as $alias) { $this->commands[$alias] = $command; } return $command; } /** * Returns a registered command by name or alias. * * @param string $name The command name or alias * * @return Command A Command object * * @throws \InvalidArgumentException When command name given does not exist * * @api */ public function get($name) { if (!isset($this->commands[$name])) { throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name)); } $command = $this->commands[$name]; if ($this->wantHelps) { $this->wantHelps = false; $helpCommand = $this->get('help'); $helpCommand->setCommand($command); return $helpCommand; } return $command; } /** * Returns true if the command exists, false otherwise. * * @param string $name The command name or alias * * @return bool true if the command exists, false otherwise * * @api */ public function has($name) { return isset($this->commands[$name]); } /** * Returns an array of all unique namespaces used by currently registered commands. * * It does not returns the global namespace which always exists. * * @return array An array of namespaces */ public function getNamespaces() { $namespaces = array(); foreach ($this->commands as $command) { $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); foreach ($command->getAliases() as $alias) { $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias)); } } return array_values(array_unique(array_filter($namespaces))); } /** * Finds a registered namespace by a name or an abbreviation. * * @param string $namespace A namespace or abbreviation to search for * * @return string A registered namespace * * @throws \InvalidArgumentException When namespace is incorrect or ambiguous */ public function findNamespace($namespace) { $allNamespaces = $this->getNamespaces(); $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace); $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces); if (empty($namespaces)) { $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { if (1 == count($alternatives)) { $message .= "\n\nDid you mean this?\n "; } else { $message .= "\n\nDid you mean one of these?\n "; } $message .= implode("\n ", $alternatives); } throw new \InvalidArgumentException($message); } $exact = in_array($namespace, $namespaces, true); if (count($namespaces) > 1 && !$exact) { throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces)))); } return $exact ? $namespace : reset($namespaces); } /** * Finds a command by name or alias. * * Contrary to get, this command tries to find the best * match if you give it an abbreviation of a name or alias. * * @param string $name A command name or a command alias * * @return Command A Command instance * * @throws \InvalidArgumentException When command name is incorrect or ambiguous * * @api */ public function find($name) { $allCommands = array_keys($this->commands); $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name); $commands = preg_grep('{^'.$expr.'}', $allCommands); if (empty($commands) || count(preg_grep('{^'.$expr.'$}', $commands)) < 1) { if (false !== $pos = strrpos($name, ':')) { // check if a namespace exists and contains commands $this->findNamespace(substr($name, 0, $pos)); } $message = sprintf('Command "%s" is not defined.', $name); if ($alternatives = $this->findAlternatives($name, $allCommands)) { if (1 == count($alternatives)) { $message .= "\n\nDid you mean this?\n "; } else { $message .= "\n\nDid you mean one of these?\n "; } $message .= implode("\n ", $alternatives); } throw new \InvalidArgumentException($message); } // filter out aliases for commands which are already on the list if (count($commands) > 1) { $commandList = $this->commands; $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) { $commandName = $commandList[$nameOrAlias]->getName(); return $commandName === $nameOrAlias || !in_array($commandName, $commands); }); } $exact = in_array($name, $commands, true); if (count($commands) > 1 && !$exact) { $suggestions = $this->getAbbreviationSuggestions(array_values($commands)); throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions)); } return $this->get($exact ? $name : reset($commands)); } /** * Gets the commands (registered in the given namespace if provided). * * The array keys are the full names and the values the command instances. * * @param string $namespace A namespace name * * @return Command[] An array of Command instances * * @api */ public function all($namespace = null) { if (null === $namespace) { return $this->commands; } $commands = array(); foreach ($this->commands as $name => $command) { if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) { $commands[$name] = $command; } } return $commands; } /** * Returns an array of possible abbreviations given a set of names. * * @param array $names An array of names * * @return array An array of abbreviations */ public static function getAbbreviations($names) { $abbrevs = array(); foreach ($names as $name) { for ($len = strlen($name); $len > 0; --$len) { $abbrev = substr($name, 0, $len); $abbrevs[$abbrev][] = $name; } } return $abbrevs; } /** * Returns a text representation of the Application. * * @param string $namespace An optional namespace name * @param bool $raw Whether to return raw command list * * @return string A string representing the Application * * @deprecated since version 2.3, to be removed in 3.0. */ public function asText($namespace = null, $raw = false) { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); $descriptor = new TextDescriptor(); $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, !$raw); $descriptor->describe($output, $this, array('namespace' => $namespace, 'raw_output' => true)); return $output->fetch(); } /** * Returns an XML representation of the Application. * * @param string $namespace An optional namespace name * @param bool $asDom Whether to return a DOM or an XML string * * @return string|\DOMDocument An XML string representing the Application * * @deprecated since version 2.3, to be removed in 3.0. */ public function asXml($namespace = null, $asDom = false) { @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); $descriptor = new XmlDescriptor(); if ($asDom) { return $descriptor->getApplicationDocument($this, $namespace); } $output = new BufferedOutput(); $descriptor->describe($output, $this, array('namespace' => $namespace)); return $output->fetch(); } /** * Renders a caught exception. * * @param \Exception $e An exception instance * @param OutputInterface $output An OutputInterface instance */ public function renderException($e, $output) { do { $title = sprintf(' [%s] ', get_class($e)); $len = $this->stringWidth($title); $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX; // HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327 if (defined('HHVM_VERSION') && $width > 1 << 31) { $width = 1 << 31; } $formatter = $output->getFormatter(); $lines = array(); foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { foreach ($this->splitStringByWidth($line, $width - 4) as $line) { // pre-format lines to get the right string length $lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $formatter->format($line))) + 4; $lines[] = array($line, $lineLength); $len = max($lineLength, $len); } } $messages = array('', ''); $messages[] = $emptyLine = $formatter->format(sprintf('%s', str_repeat(' ', $len))); $messages[] = $formatter->format(sprintf('%s%s', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title))))); foreach ($lines as $line) { $messages[] = $formatter->format(sprintf(' %s %s', $line[0], str_repeat(' ', $len - $line[1]))); } $messages[] = $emptyLine; $messages[] = ''; $messages[] = ''; $output->writeln($messages, OutputInterface::OUTPUT_RAW); if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { $output->writeln('Exception trace:'); // exception related properties $trace = $e->getTrace(); array_unshift($trace, array( 'function' => '', 'file' => $e->getFile() !== null ? $e->getFile() : 'n/a', 'line' => $e->getLine() !== null ? $e->getLine() : 'n/a', 'args' => array(), )); for ($i = 0, $count = count($trace); $i < $count; ++$i) { $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; $function = $trace[$i]['function']; $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; $output->writeln(sprintf(' %s%s%s() at %s:%s', $class, $type, $function, $file, $line)); } $output->writeln(''); $output->writeln(''); } } while ($e = $e->getPrevious()); if (null !== $this->runningCommand) { $output->writeln(sprintf('%s', sprintf($this->runningCommand->getSynopsis(), $this->getName()))); $output->writeln(''); $output->writeln(''); } } /** * Tries to figure out the terminal width in which this application runs. * * @return int|null */ protected function getTerminalWidth() { $dimensions = $this->getTerminalDimensions(); return $dimensions[0]; } /** * Tries to figure out the terminal height in which this application runs. * * @return int|null */ protected function getTerminalHeight() { $dimensions = $this->getTerminalDimensions(); return $dimensions[1]; } /** * Tries to figure out the terminal dimensions based on the current environment. * * @return array Array containing width and height */ public function getTerminalDimensions() { if ($this->terminalDimensions) { return $this->terminalDimensions; } if ('\\' === DIRECTORY_SEPARATOR) { // extract [w, H] from "wxh (WxH)" if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) { return array((int) $matches[1], (int) $matches[2]); } // extract [w, h] from "wxh" if (preg_match('/^(\d+)x(\d+)$/', $this->getConsoleMode(), $matches)) { return array((int) $matches[1], (int) $matches[2]); } } if ($sttyString = $this->getSttyColumns()) { // extract [w, h] from "rows h; columns w;" if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { return array((int) $matches[2], (int) $matches[1]); } // extract [w, h] from "; h rows; w columns" if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { return array((int) $matches[2], (int) $matches[1]); } } return array(null, null); } /** * Sets terminal dimensions. * * Can be useful to force terminal dimensions for functional tests. * * @param int $width The width * @param int $height The height * * @return Application The current application */ public function setTerminalDimensions($width, $height) { $this->terminalDimensions = array($width, $height); return $this; } /** * Configures the input and output instances based on the user arguments and options. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance */ protected function configureIO(InputInterface $input, OutputInterface $output) { if (true === $input->hasParameterOption(array('--ansi'))) { $output->setDecorated(true); } elseif (true === $input->hasParameterOption(array('--no-ansi'))) { $output->setDecorated(false); } if (true === $input->hasParameterOption(array('--no-interaction', '-n'))) { $input->setInteractive(false); } elseif (function_exists('posix_isatty') && $this->getHelperSet()->has('question')) { $inputStream = $this->getHelperSet()->get('question')->getInputStream(); if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) { $input->setInteractive(false); } } if (true === $input->hasParameterOption(array('--quiet', '-q'))) { $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); } else { if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) { $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) { $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) { $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); } } } /** * Runs the current command. * * If an event dispatcher has been attached to the application, * events are also dispatched during the life-cycle of the command. * * @param Command $command A Command instance * @param InputInterface $input An Input instance * @param OutputInterface $output An Output instance * * @return int 0 if everything went fine, or an error code * * @throws \Exception when the command being run threw an exception */ protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) { foreach ($command->getHelperSet() as $helper) { if ($helper instanceof InputAwareInterface) { $helper->setInput($input); } } if (null === $this->dispatcher) { return $command->run($input, $output); } $event = new ConsoleCommandEvent($command, $input, $output); $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event); if ($event->commandShouldRun()) { try { $exitCode = $command->run($input, $output); } catch (\Exception $e) { $event = new ConsoleExceptionEvent($command, $input, $output, $e, $e->getCode()); $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event); $e = $event->getException(); $event = new ConsoleTerminateEvent($command, $input, $output, $e->getCode()); $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); throw $e; } } else { $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED; } $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); return $event->getExitCode(); } /** * Gets the name of the command based on input. * * @param InputInterface $input The input interface * * @return string The command name */ protected function getCommandName(InputInterface $input) { return $input->getFirstArgument(); } /** * Gets the default input definition. * * @return InputDefinition An InputDefinition instance */ protected function getDefaultInputDefinition() { return new InputDefinition(array( new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'), new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'), new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'), new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), )); } /** * Gets the default commands that should always be available. * * @return Command[] An array of default Command instances */ protected function getDefaultCommands() { return array(new HelpCommand(), new ListCommand()); } /** * Gets the default helper set with the helpers that should always be available. * * @return HelperSet A HelperSet instance */ protected function getDefaultHelperSet() { return new HelperSet(array( new FormatterHelper(), new DialogHelper(false), new ProgressHelper(false), new TableHelper(false), new DebugFormatterHelper(), new ProcessHelper(), new QuestionHelper(), )); } /** * Runs and parses stty -a if it's available, suppressing any error output. * * @return string */ private function getSttyColumns() { if (!function_exists('proc_open')) { return; } $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')); $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, array('suppress_errors' => true)); if (is_resource($process)) { $info = stream_get_contents($pipes[1]); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); return $info; } } /** * Runs and parses mode CON if it's available, suppressing any error output. * * @return string x or null if it could not be parsed */ private function getConsoleMode() { if (!function_exists('proc_open')) { return; } $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')); $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, array('suppress_errors' => true)); if (is_resource($process)) { $info = stream_get_contents($pipes[1]); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { return $matches[2].'x'.$matches[1]; } } } /** * Returns abbreviated suggestions in string format. * * @param array $abbrevs Abbreviated suggestions to convert * * @return string A formatted string of abbreviated suggestions */ private function getAbbreviationSuggestions($abbrevs) { return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : ''); } /** * Returns the namespace part of the command name. * * This method is not part of public API and should not be used directly. * * @param string $name The full name of the command * @param string $limit The maximum number of parts of the namespace * * @return string The namespace of the command */ public function extractNamespace($name, $limit = null) { $parts = explode(':', $name); array_pop($parts); return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit)); } /** * Finds alternative of $name among $collection, * if nothing is found in $collection, try in $abbrevs. * * @param string $name The string * @param array|\Traversable $collection The collection * * @return array A sorted array of similar string */ private function findAlternatives($name, $collection) { $threshold = 1e3; $alternatives = array(); $collectionParts = array(); foreach ($collection as $item) { $collectionParts[$item] = explode(':', $item); } foreach (explode(':', $name) as $i => $subname) { foreach ($collectionParts as $collectionName => $parts) { $exists = isset($alternatives[$collectionName]); if (!isset($parts[$i]) && $exists) { $alternatives[$collectionName] += $threshold; continue; } elseif (!isset($parts[$i])) { continue; } $lev = levenshtein($subname, $parts[$i]); if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) { $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; } elseif ($exists) { $alternatives[$collectionName] += $threshold; } } } foreach ($collection as $item) { $lev = levenshtein($name, $item); if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; } } $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); asort($alternatives); return array_keys($alternatives); } /** * Sets the default Command name. * * @param string $commandName The Command name */ public function setDefaultCommand($commandName) { $this->defaultCommand = $commandName; } private function stringWidth($string) { if (!function_exists('mb_strwidth')) { return strlen($string); } if (false === $encoding = mb_detect_encoding($string)) { return strlen($string); } return mb_strwidth($string, $encoding); } private function splitStringByWidth($string, $width) { // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly. // additionally, array_slice() is not enough as some character has doubled width. // we need a function to split string not by character count but by string width if (!function_exists('mb_strwidth')) { return str_split($string, $width); } if (false === $encoding = mb_detect_encoding($string)) { return str_split($string, $width); } $utf8String = mb_convert_encoding($string, 'utf8', $encoding); $lines = array(); $line = ''; foreach (preg_split('//u', $utf8String) as $char) { // test if $char could be appended to current line if (mb_strwidth($line.$char, 'utf8') <= $width) { $line .= $char; continue; } // if not, push current line to array and make new line $lines[] = str_pad($line, $width); $line = $char; } if ('' !== $line) { $lines[] = count($lines) ? str_pad($line, $width) : $line; } mb_convert_variables($encoding, 'utf8', $lines); return $lines; } /** * Returns all namespaces of the command name. * * @param string $name The full name of the command * * @return array The namespaces of the command */ private function extractAllNamespaces($name) { // -1 as third argument is needed to skip the command short name when exploding $parts = explode(':', $name, -1); $namespaces = array(); foreach ($parts as $part) { if (count($namespaces)) { $namespaces[] = end($namespaces).':'.$part; } else { $namespaces[] = $part; } } return $namespaces; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml; use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Exception\DumpException; /** * Inline implements a YAML parser/dumper for the YAML inline syntax. * * @author Fabien Potencier */ class Inline { const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\']*(?:\'\'[^\']*)*)\')'; private static $exceptionOnInvalidType = false; private static $objectSupport = false; private static $objectForMap = false; /** * Converts a YAML string to a PHP array. * * @param string $value A YAML string * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise * @param bool $objectSupport true if object support is enabled, false otherwise * @param bool $objectForMap true if maps should return a stdClass instead of array() * @param array $references Mapping of variable names to values * * @return array A PHP array representing the YAML string * * @throws ParseException */ public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false, $references = array()) { self::$exceptionOnInvalidType = $exceptionOnInvalidType; self::$objectSupport = $objectSupport; self::$objectForMap = $objectForMap; $value = trim($value); if ('' === $value) { return ''; } if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { $mbEncoding = mb_internal_encoding(); mb_internal_encoding('ASCII'); } $i = 0; switch ($value[0]) { case '[': $result = self::parseSequence($value, $i, $references); ++$i; break; case '{': $result = self::parseMapping($value, $i, $references); ++$i; break; default: $result = self::parseScalar($value, null, array('"', "'"), $i, true, $references); } // some comments are allowed at the end if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) { throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i))); } if (isset($mbEncoding)) { mb_internal_encoding($mbEncoding); } return $result; } /** * Dumps a given PHP variable to a YAML string. * * @param mixed $value The PHP variable to convert * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise * @param bool $objectSupport true if object support is enabled, false otherwise * * @return string The YAML string representing the PHP array * * @throws DumpException When trying to dump PHP resource */ public static function dump($value, $exceptionOnInvalidType = false, $objectSupport = false) { switch (true) { case is_resource($value): if ($exceptionOnInvalidType) { throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); } return 'null'; case is_object($value): if ($objectSupport) { return '!!php/object:'.serialize($value); } if ($exceptionOnInvalidType) { throw new DumpException('Object support when dumping a YAML file has been disabled.'); } return 'null'; case is_array($value): return self::dumpArray($value, $exceptionOnInvalidType, $objectSupport); case null === $value: return 'null'; case true === $value: return 'true'; case false === $value: return 'false'; case ctype_digit($value): return is_string($value) ? "'$value'" : (int) $value; case is_numeric($value): $locale = setlocale(LC_NUMERIC, 0); if (false !== $locale) { setlocale(LC_NUMERIC, 'C'); } if (is_float($value)) { $repr = (string) $value; if (is_infinite($value)) { $repr = str_ireplace('INF', '.Inf', $repr); } elseif (floor($value) == $value && $repr == $value) { // Preserve float data type since storing a whole number will result in integer value. $repr = '!!float '.$repr; } } else { $repr = is_string($value) ? "'$value'" : (string) $value; } if (false !== $locale) { setlocale(LC_NUMERIC, $locale); } return $repr; case '' == $value: return "''"; case Escaper::requiresDoubleQuoting($value): return Escaper::escapeWithDoubleQuotes($value); case Escaper::requiresSingleQuoting($value): case preg_match(self::getHexRegex(), $value): case preg_match(self::getTimestampRegex(), $value): return Escaper::escapeWithSingleQuotes($value); default: return $value; } } /** * Dumps a PHP array to a YAML string. * * @param array $value The PHP array to dump * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise * @param bool $objectSupport true if object support is enabled, false otherwise * * @return string The YAML string representing the PHP array */ private static function dumpArray($value, $exceptionOnInvalidType, $objectSupport) { // array $keys = array_keys($value); $keysCount = count($keys); if ((1 === $keysCount && '0' == $keys[0]) || ($keysCount > 1 && array_reduce($keys, function ($v, $w) { return (int) $v + $w; }, 0) === $keysCount * ($keysCount - 1) / 2) ) { $output = array(); foreach ($value as $val) { $output[] = self::dump($val, $exceptionOnInvalidType, $objectSupport); } return sprintf('[%s]', implode(', ', $output)); } // mapping $output = array(); foreach ($value as $key => $val) { $output[] = sprintf('%s: %s', self::dump($key, $exceptionOnInvalidType, $objectSupport), self::dump($val, $exceptionOnInvalidType, $objectSupport)); } return sprintf('{ %s }', implode(', ', $output)); } /** * Parses a scalar to a YAML string. * * @param string $scalar * @param string $delimiters * @param array $stringDelimiters * @param int &$i * @param bool $evaluate * @param array $references * * @return string A YAML string * * @throws ParseException When malformed inline YAML string is parsed */ public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array()) { if (in_array($scalar[$i], $stringDelimiters)) { // quoted scalar $output = self::parseQuotedScalar($scalar, $i); if (null !== $delimiters) { $tmp = ltrim(substr($scalar, $i), ' '); if (!in_array($tmp[0], $delimiters)) { throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i))); } } } else { // "normal" string if (!$delimiters) { $output = substr($scalar, $i); $i += strlen($output); // remove comments if (false !== $strpos = strpos($output, ' #')) { $output = rtrim(substr($output, 0, $strpos)); } } elseif (preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { $output = $match[1]; $i += strlen($output); } else { throw new ParseException(sprintf('Malformed inline YAML string (%s).', $scalar)); } if ($evaluate) { $output = self::evaluateScalar($output, $references); } } return $output; } /** * Parses a quoted scalar to YAML. * * @param string $scalar * @param int &$i * * @return string A YAML string * * @throws ParseException When malformed inline YAML string is parsed */ private static function parseQuotedScalar($scalar, &$i) { if (!preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) { throw new ParseException(sprintf('Malformed inline YAML string (%s).', substr($scalar, $i))); } $output = substr($match[0], 1, strlen($match[0]) - 2); $unescaper = new Unescaper(); if ('"' == $scalar[$i]) { $output = $unescaper->unescapeDoubleQuotedString($output); } else { $output = $unescaper->unescapeSingleQuotedString($output); } $i += strlen($match[0]); return $output; } /** * Parses a sequence to a YAML string. * * @param string $sequence * @param int &$i * @param array $references * * @return string A YAML string * * @throws ParseException When malformed inline YAML string is parsed */ private static function parseSequence($sequence, &$i = 0, $references = array()) { $output = array(); $len = strlen($sequence); ++$i; // [foo, bar, ...] while ($i < $len) { switch ($sequence[$i]) { case '[': // nested sequence $output[] = self::parseSequence($sequence, $i, $references); break; case '{': // nested mapping $output[] = self::parseMapping($sequence, $i, $references); break; case ']': return $output; case ',': case ' ': break; default: $isQuoted = in_array($sequence[$i], array('"', "'")); $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references); // the value can be an array if a reference has been resolved to an array var if (!is_array($value) && !$isQuoted && false !== strpos($value, ': ')) { // embedded mapping? try { $pos = 0; $value = self::parseMapping('{'.$value.'}', $pos, $references); } catch (\InvalidArgumentException $e) { // no, it's not } } $output[] = $value; --$i; } ++$i; } throw new ParseException(sprintf('Malformed inline YAML string %s', $sequence)); } /** * Parses a mapping to a YAML string. * * @param string $mapping * @param int &$i * @param array $references * * @return string A YAML string * * @throws ParseException When malformed inline YAML string is parsed */ private static function parseMapping($mapping, &$i = 0, $references = array()) { $output = array(); $len = strlen($mapping); ++$i; // {foo: bar, bar:foo, ...} while ($i < $len) { switch ($mapping[$i]) { case ' ': case ',': ++$i; continue 2; case '}': if (self::$objectForMap) { return (object) $output; } return $output; } // key $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false); // value $done = false; while ($i < $len) { switch ($mapping[$i]) { case '[': // nested sequence $value = self::parseSequence($mapping, $i, $references); // Spec: Keys MUST be unique; first one wins. // Parser cannot abort this mapping earlier, since lines // are processed sequentially. if (!isset($output[$key])) { $output[$key] = $value; } $done = true; break; case '{': // nested mapping $value = self::parseMapping($mapping, $i, $references); // Spec: Keys MUST be unique; first one wins. // Parser cannot abort this mapping earlier, since lines // are processed sequentially. if (!isset($output[$key])) { $output[$key] = $value; } $done = true; break; case ':': case ' ': break; default: $value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references); // Spec: Keys MUST be unique; first one wins. // Parser cannot abort this mapping earlier, since lines // are processed sequentially. if (!isset($output[$key])) { $output[$key] = $value; } $done = true; --$i; } ++$i; if ($done) { continue 2; } } } throw new ParseException(sprintf('Malformed inline YAML string %s', $mapping)); } /** * Evaluates scalars and replaces magic values. * * @param string $scalar * @param array $references * * @return string A YAML string * * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved */ private static function evaluateScalar($scalar, $references = array()) { $scalar = trim($scalar); $scalarLower = strtolower($scalar); if (0 === strpos($scalar, '*')) { if (false !== $pos = strpos($scalar, '#')) { $value = substr($scalar, 1, $pos - 2); } else { $value = substr($scalar, 1); } // an unquoted * if (false === $value || '' === $value) { throw new ParseException('A reference must contain at least one character.'); } if (!array_key_exists($value, $references)) { throw new ParseException(sprintf('Reference "%s" does not exist.', $value)); } return $references[$value]; } switch (true) { case 'null' === $scalarLower: case '' === $scalar: case '~' === $scalar: return; case 'true' === $scalarLower: return true; case 'false' === $scalarLower: return false; // Optimise for returning strings. case $scalar[0] === '+' || $scalar[0] === '-' || $scalar[0] === '.' || $scalar[0] === '!' || is_numeric($scalar[0]): switch (true) { case 0 === strpos($scalar, '!str'): return (string) substr($scalar, 5); case 0 === strpos($scalar, '! '): return (int) self::parseScalar(substr($scalar, 2)); case 0 === strpos($scalar, '!!php/object:'): if (self::$objectSupport) { return unserialize(substr($scalar, 13)); } if (self::$exceptionOnInvalidType) { throw new ParseException('Object support when parsing a YAML file has been disabled.'); } return; case 0 === strpos($scalar, '!!float '): return (float) substr($scalar, 8); case ctype_digit($scalar): $raw = $scalar; $cast = (int) $scalar; return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): $raw = $scalar; $cast = (int) $scalar; return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw); case is_numeric($scalar): case preg_match(self::getHexRegex(), $scalar): return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar; case '.inf' === $scalarLower: case '.nan' === $scalarLower: return -log(0); case '-.inf' === $scalarLower: return log(0); case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar): return (float) str_replace(',', '', $scalar); case preg_match(self::getTimestampRegex(), $scalar): return strtotime($scalar); } default: return (string) $scalar; } } /** * Gets a regex that matches a YAML date. * * @return string The regular expression * * @see http://www.yaml.org/spec/1.2/spec.html#id2761573 */ private static function getTimestampRegex() { return <<[0-9][0-9][0-9][0-9]) -(?P[0-9][0-9]?) -(?P[0-9][0-9]?) (?:(?:[Tt]|[ \t]+) (?P[0-9][0-9]?) :(?P[0-9][0-9]) :(?P[0-9][0-9]) (?:\.(?P[0-9]*))? (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) (?::(?P[0-9][0-9]))?))?)? $~x EOF; } /** * Gets a regex that matches a YAML number in hexadecimal notation. * * @return string */ private static function getHexRegex() { return '~^0x[0-9a-f]++$~i'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml; /** * Unescaper encapsulates unescaping rules for single and double-quoted * YAML strings. * * @author Matthew Lewinski */ class Unescaper { /** * Parser and Inline assume UTF-8 encoding, so escaped Unicode characters * must be converted to that encoding. * * @deprecated since version 2.5, to be removed in 3.0 * * @internal */ const ENCODING = 'UTF-8'; /** * Regex fragment that matches an escaped character in a double quoted string. */ const REGEX_ESCAPED_CHARACTER = "\\\\([0abt\tnvfre \\\"\\/\\\\N_LP]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})"; /** * Unescapes a single quoted string. * * @param string $value A single quoted string. * * @return string The unescaped string. */ public function unescapeSingleQuotedString($value) { return str_replace('\'\'', '\'', $value); } /** * Unescapes a double quoted string. * * @param string $value A double quoted string. * * @return string The unescaped string. */ public function unescapeDoubleQuotedString($value) { $self = $this; $callback = function ($match) use ($self) { return $self->unescapeCharacter($match[0]); }; // evaluate the string return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value); } /** * Unescapes a character that was found in a double-quoted string. * * @param string $value An escaped character * * @return string The unescaped character */ public function unescapeCharacter($value) { switch ($value{1}) { case '0': return "\x0"; case 'a': return "\x7"; case 'b': return "\x8"; case 't': return "\t"; case "\t": return "\t"; case 'n': return "\n"; case 'v': return "\xB"; case 'f': return "\xC"; case 'r': return "\r"; case 'e': return "\x1B"; case ' ': return ' '; case '"': return '"'; case '/': return '/'; case '\\': return '\\'; case 'N': // U+0085 NEXT LINE return "\xC2\x85"; case '_': // U+00A0 NO-BREAK SPACE return "\xC2\xA0"; case 'L': // U+2028 LINE SEPARATOR return "\xE2\x80\xA8"; case 'P': // U+2029 PARAGRAPH SEPARATOR return "\xE2\x80\xA9"; case 'x': return self::utf8chr(hexdec(substr($value, 2, 2))); case 'u': return self::utf8chr(hexdec(substr($value, 2, 4))); case 'U': return self::utf8chr(hexdec(substr($value, 2, 8))); } } /** * Get the UTF-8 character for the given code point. * * @param int $c The unicode code point * * @return string The corresponding UTF-8 character */ private static function utf8chr($c) { if (0x80 > $c %= 0x200000) { return chr($c); } if (0x800 > $c) { return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F); } if (0x10000 > $c) { return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F); } return chr(0xF0 | $c >> 18).chr(0x80 | $c >> 12 & 0x3F).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml; use Symfony\Component\Yaml\Exception\ParseException; /** * Yaml offers convenience methods to load and dump YAML. * * @author Fabien Potencier * * @api */ class Yaml { /** * Parses YAML into a PHP array. * * The parse method, when supplied with a YAML stream (string or file), * will do its best to convert YAML in a file into a PHP array. * * Usage: * * $array = Yaml::parse(file_get_contents('config.yml')); * print_r($array); * * * As this method accepts both plain strings and file names as an input, * you must validate the input before calling this method. Passing a file * as an input is a deprecated feature and will be removed in 3.0. * * Note: the ability to pass file names to the Yaml::parse method is deprecated since version 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead. * * @param string $input Path to a YAML file or a string containing YAML * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise * @param bool $objectSupport True if object support is enabled, false otherwise * @param bool $objectForMap True if maps should return a stdClass instead of array() * * @return array The YAML converted to a PHP array * * @throws ParseException If the YAML is not valid * * @api */ public static function parse($input, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false) { // if input is a file, process it $file = ''; if (strpos($input, "\n") === false && is_file($input)) { @trigger_error('The ability to pass file names to the '.__METHOD__.' method is deprecated since version 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead.', E_USER_DEPRECATED); if (false === is_readable($input)) { throw new ParseException(sprintf('Unable to parse "%s" as the file is not readable.', $input)); } $file = $input; $input = file_get_contents($file); } $yaml = new Parser(); try { return $yaml->parse($input, $exceptionOnInvalidType, $objectSupport, $objectForMap); } catch (ParseException $e) { if ($file) { $e->setParsedFile($file); } throw $e; } } /** * Dumps a PHP array to a YAML string. * * The dump method, when supplied with an array, will do its best * to convert the array into friendly YAML. * * @param array $array PHP array * @param int $inline The level where you switch to inline YAML * @param int $indent The amount of spaces to use for indentation of nested nodes. * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise * @param bool $objectSupport true if object support is enabled, false otherwise * * @return string A YAML string representing the original PHP array * * @api */ public static function dump($array, $inline = 2, $indent = 4, $exceptionOnInvalidType = false, $objectSupport = false) { $yaml = new Dumper(); $yaml->setIndentation($indent); return $yaml->dump($array, $inline, 0, $exceptionOnInvalidType, $objectSupport); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml\Exception; /** * Exception class thrown when an error occurs during parsing. * * @author Fabien Potencier * * @api */ class ParseException extends RuntimeException { private $parsedFile; private $parsedLine; private $snippet; private $rawMessage; /** * Constructor. * * @param string $message The error message * @param int $parsedLine The line where the error occurred * @param int $snippet The snippet of code near the problem * @param string $parsedFile The file name where the error occurred * @param \Exception $previous The previous exception */ public function __construct($message, $parsedLine = -1, $snippet = null, $parsedFile = null, \Exception $previous = null) { $this->parsedFile = $parsedFile; $this->parsedLine = $parsedLine; $this->snippet = $snippet; $this->rawMessage = $message; $this->updateRepr(); parent::__construct($this->message, 0, $previous); } /** * Gets the snippet of code near the error. * * @return string The snippet of code */ public function getSnippet() { return $this->snippet; } /** * Sets the snippet of code near the error. * * @param string $snippet The code snippet */ public function setSnippet($snippet) { $this->snippet = $snippet; $this->updateRepr(); } /** * Gets the filename where the error occurred. * * This method returns null if a string is parsed. * * @return string The filename */ public function getParsedFile() { return $this->parsedFile; } /** * Sets the filename where the error occurred. * * @param string $parsedFile The filename */ public function setParsedFile($parsedFile) { $this->parsedFile = $parsedFile; $this->updateRepr(); } /** * Gets the line where the error occurred. * * @return int The file line */ public function getParsedLine() { return $this->parsedLine; } /** * Sets the line where the error occurred. * * @param int $parsedLine The file line */ public function setParsedLine($parsedLine) { $this->parsedLine = $parsedLine; $this->updateRepr(); } private function updateRepr() { $this->message = $this->rawMessage; $dot = false; if ('.' === substr($this->message, -1)) { $this->message = substr($this->message, 0, -1); $dot = true; } if (null !== $this->parsedFile) { if (PHP_VERSION_ID >= 50400) { $jsonOptions = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; } else { $jsonOptions = 0; } $this->message .= sprintf(' in %s', json_encode($this->parsedFile, $jsonOptions)); } if ($this->parsedLine >= 0) { $this->message .= sprintf(' at line %d', $this->parsedLine); } if ($this->snippet) { $this->message .= sprintf(' (near "%s")', $this->snippet); } if ($dot) { $this->message .= '.'; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml\Exception; /** * Exception class thrown when an error occurs during dumping. * * @author Fabien Potencier * * @api */ class DumpException extends RuntimeException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml\Exception; /** * Exception class thrown when an error occurs during parsing. * * @author Romain Neutron * * @api */ class RuntimeException extends \RuntimeException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml\Exception; /** * Exception interface for all exceptions thrown by the component. * * @author Fabien Potencier * * @api */ interface ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml; use Symfony\Component\Yaml\Exception\ParseException; /** * Parser parses YAML strings to convert them to PHP arrays. * * @author Fabien Potencier */ class Parser { const FOLDED_SCALAR_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?'; private $offset = 0; private $lines = array(); private $currentLineNb = -1; private $currentLine = ''; private $refs = array(); /** * Constructor. * * @param int $offset The offset of YAML document (used for line numbers in error messages) */ public function __construct($offset = 0) { $this->offset = $offset; } /** * Parses a YAML string to a PHP value. * * @param string $value A YAML string * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise * @param bool $objectSupport true if object support is enabled, false otherwise * @param bool $objectForMap true if maps should return a stdClass instead of array() * * @return mixed A PHP value * * @throws ParseException If the YAML is not valid */ public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false) { if (!preg_match('//u', $value)) { throw new ParseException('The YAML value does not appear to be valid UTF-8.'); } $this->currentLineNb = -1; $this->currentLine = ''; $value = $this->cleanup($value); $this->lines = explode("\n", $value); if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { $mbEncoding = mb_internal_encoding(); mb_internal_encoding('UTF-8'); } $data = array(); $context = null; $allowOverwrite = false; while ($this->moveToNextLine()) { if ($this->isCurrentLineEmpty()) { continue; } // tab? if ("\t" === $this->currentLine[0]) { throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } $isRef = $mergeNode = false; if (preg_match('#^\-((?P\s+)(?P.+?))?\s*$#u', $this->currentLine, $values)) { if ($context && 'mapping' == $context) { throw new ParseException('You cannot define a sequence item when in a mapping'); } $context = 'sequence'; if (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) { $isRef = $matches['ref']; $values['value'] = $matches['value']; } // array if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { $c = $this->getRealCurrentLineNb() + 1; $parser = new self($c); $parser->refs = &$this->refs; $data[] = $parser->parse($this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap); } else { if (isset($values['leadspaces']) && preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P.+?))?\s*$#u', $values['value'], $matches) ) { // this is a compact notation element, add to next block and parse $c = $this->getRealCurrentLineNb(); $parser = new self($c); $parser->refs = &$this->refs; $block = $values['value']; if ($this->isNextLineIndented()) { $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + strlen($values['leadspaces']) + 1); } $data[] = $parser->parse($block, $exceptionOnInvalidType, $objectSupport, $objectForMap); } else { $data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap); } } if ($isRef) { $this->refs[$isRef] = end($data); } } elseif (preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P.+?))?\s*$#u', $this->currentLine, $values) && (false === strpos($values['key'], ' #') || in_array($values['key'][0], array('"', "'")))) { if ($context && 'sequence' == $context) { throw new ParseException('You cannot define a mapping item when in a sequence'); } $context = 'mapping'; // force correct settings Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); try { $key = Inline::parseScalar($values['key']); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } // Convert float keys to strings, to avoid being converted to integers by PHP if (is_float($key)) { $key = (string) $key; } if ('<<' === $key) { $mergeNode = true; $allowOverwrite = true; if (isset($values['value']) && 0 === strpos($values['value'], '*')) { $refName = substr($values['value'], 1); if (!array_key_exists($refName, $this->refs)) { throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine); } $refValue = $this->refs[$refName]; if (!is_array($refValue)) { throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } foreach ($refValue as $key => $value) { if (!isset($data[$key])) { $data[$key] = $value; } } } else { if (isset($values['value']) && $values['value'] !== '') { $value = $values['value']; } else { $value = $this->getNextEmbedBlock(); } $c = $this->getRealCurrentLineNb() + 1; $parser = new self($c); $parser->refs = &$this->refs; $parsed = $parser->parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap); if (!is_array($parsed)) { throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } if (isset($parsed[0])) { // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier // in the sequence override keys specified in later mapping nodes. foreach ($parsed as $parsedItem) { if (!is_array($parsedItem)) { throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem); } foreach ($parsedItem as $key => $value) { if (!isset($data[$key])) { $data[$key] = $value; } } } } else { // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the // current mapping, unless the key already exists in it. foreach ($parsed as $key => $value) { if (!isset($data[$key])) { $data[$key] = $value; } } } } } elseif (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) { $isRef = $matches['ref']; $values['value'] = $matches['value']; } if ($mergeNode) { // Merge keys } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { // hash // if next line is less indented or equal, then it means that the current value is null if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) { // Spec: Keys MUST be unique; first one wins. // But overwriting is allowed when a merge node is used in current block. if ($allowOverwrite || !isset($data[$key])) { $data[$key] = null; } } else { $c = $this->getRealCurrentLineNb() + 1; $parser = new self($c); $parser->refs = &$this->refs; $value = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap); // Spec: Keys MUST be unique; first one wins. // But overwriting is allowed when a merge node is used in current block. if ($allowOverwrite || !isset($data[$key])) { $data[$key] = $value; } } } else { $value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap); // Spec: Keys MUST be unique; first one wins. // But overwriting is allowed when a merge node is used in current block. if ($allowOverwrite || !isset($data[$key])) { $data[$key] = $value; } } if ($isRef) { $this->refs[$isRef] = $data[$key]; } } else { // multiple documents are not supported if ('---' === $this->currentLine) { throw new ParseException('Multiple documents are not supported.'); } // 1-liner optionally followed by newline(s) if (is_string($value) && $this->lines[0] === trim($value)) { try { $value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } if (is_array($value)) { $first = reset($value); if (is_string($first) && 0 === strpos($first, '*')) { $data = array(); foreach ($value as $alias) { $data[] = $this->refs[substr($alias, 1)]; } $value = $data; } } if (isset($mbEncoding)) { mb_internal_encoding($mbEncoding); } return $value; } switch (preg_last_error()) { case PREG_INTERNAL_ERROR: $error = 'Internal PCRE error.'; break; case PREG_BACKTRACK_LIMIT_ERROR: $error = 'pcre.backtrack_limit reached.'; break; case PREG_RECURSION_LIMIT_ERROR: $error = 'pcre.recursion_limit reached.'; break; case PREG_BAD_UTF8_ERROR: $error = 'Malformed UTF-8 data.'; break; case PREG_BAD_UTF8_OFFSET_ERROR: $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.'; break; default: $error = 'Unable to parse.'; } throw new ParseException($error, $this->getRealCurrentLineNb() + 1, $this->currentLine); } } if (isset($mbEncoding)) { mb_internal_encoding($mbEncoding); } return empty($data) ? null : $data; } /** * Returns the current line number (takes the offset into account). * * @return int The current line number */ private function getRealCurrentLineNb() { return $this->currentLineNb + $this->offset; } /** * Returns the current line indentation. * * @return int The current line indentation */ private function getCurrentLineIndentation() { return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' ')); } /** * Returns the next embed block of YAML. * * @param int $indentation The indent level at which the block is to be read, or null for default * @param bool $inSequence True if the enclosing data structure is a sequence * * @return string A YAML string * * @throws ParseException When indentation problem are detected */ private function getNextEmbedBlock($indentation = null, $inSequence = false) { $oldLineIndentation = $this->getCurrentLineIndentation(); if (!$this->moveToNextLine()) { return; } if (null === $indentation) { $newIndent = $this->getCurrentLineIndentation(); $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem($this->currentLine); if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } } else { $newIndent = $indentation; } $data = array(); if ($this->getCurrentLineIndentation() >= $newIndent) { $data[] = substr($this->currentLine, $newIndent); } else { $this->moveToPreviousLine(); return; } if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) { // the previous line contained a dash but no item content, this line is a sequence item with the same indentation // and therefore no nested list or mapping $this->moveToPreviousLine(); return; } $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem($this->currentLine); // Comments must not be removed inside a string block (ie. after a line ending with "|") $removeCommentsPattern = '~'.self::FOLDED_SCALAR_PATTERN.'$~'; $removeComments = !preg_match($removeCommentsPattern, $this->currentLine); while ($this->moveToNextLine()) { $indent = $this->getCurrentLineIndentation(); if ($indent === $newIndent) { $removeComments = !preg_match($removeCommentsPattern, $this->currentLine); } if ($isItUnindentedCollection && !$this->isStringUnIndentedCollectionItem($this->currentLine) && $newIndent === $indent) { $this->moveToPreviousLine(); break; } if ($this->isCurrentLineBlank()) { $data[] = substr($this->currentLine, $newIndent); continue; } if ($removeComments && $this->isCurrentLineComment()) { continue; } if ($indent >= $newIndent) { $data[] = substr($this->currentLine, $newIndent); } elseif (0 == $indent) { $this->moveToPreviousLine(); break; } else { throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } } return implode("\n", $data); } /** * Moves the parser to the next line. * * @return bool */ private function moveToNextLine() { if ($this->currentLineNb >= count($this->lines) - 1) { return false; } $this->currentLine = $this->lines[++$this->currentLineNb]; return true; } /** * Moves the parser to the previous line. */ private function moveToPreviousLine() { $this->currentLine = $this->lines[--$this->currentLineNb]; } /** * Parses a YAML value. * * @param string $value A YAML value * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise * @param bool $objectSupport True if object support is enabled, false otherwise * @param bool $objectForMap true if maps should return a stdClass instead of array() * * @return mixed A PHP value * * @throws ParseException When reference does not exist */ private function parseValue($value, $exceptionOnInvalidType, $objectSupport, $objectForMap) { if (0 === strpos($value, '*')) { if (false !== $pos = strpos($value, '#')) { $value = substr($value, 1, $pos - 2); } else { $value = substr($value, 1); } if (!array_key_exists($value, $this->refs)) { throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLine); } return $this->refs[$value]; } if (preg_match('/^'.self::FOLDED_SCALAR_PATTERN.'$/', $value, $matches)) { $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : ''; return $this->parseFoldedScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers)); } try { return Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } } /** * Parses a folded scalar. * * @param string $separator The separator that was used to begin this folded scalar (| or >) * @param string $indicator The indicator that was used to begin this folded scalar (+ or -) * @param int $indentation The indentation that was used to begin this folded scalar * * @return string The text value */ private function parseFoldedScalar($separator, $indicator = '', $indentation = 0) { $notEOF = $this->moveToNextLine(); if (!$notEOF) { return ''; } $isCurrentLineBlank = $this->isCurrentLineBlank(); $text = ''; // leading blank lines are consumed before determining indentation while ($notEOF && $isCurrentLineBlank) { // newline only if not EOF if ($notEOF = $this->moveToNextLine()) { $text .= "\n"; $isCurrentLineBlank = $this->isCurrentLineBlank(); } } // determine indentation if not specified if (0 === $indentation) { if (preg_match('/^ +/', $this->currentLine, $matches)) { $indentation = strlen($matches[0]); } } if ($indentation > 0) { $pattern = sprintf('/^ {%d}(.*)$/', $indentation); while ( $notEOF && ( $isCurrentLineBlank || preg_match($pattern, $this->currentLine, $matches) ) ) { if ($isCurrentLineBlank) { $text .= substr($this->currentLine, $indentation); } else { $text .= $matches[1]; } // newline only if not EOF if ($notEOF = $this->moveToNextLine()) { $text .= "\n"; $isCurrentLineBlank = $this->isCurrentLineBlank(); } } } elseif ($notEOF) { $text .= "\n"; } if ($notEOF) { $this->moveToPreviousLine(); } // replace all non-trailing single newlines with spaces in folded blocks if ('>' === $separator) { preg_match('/(\n*)$/', $text, $matches); $text = preg_replace('/(?getCurrentLineIndentation(); $EOF = !$this->moveToNextLine(); while (!$EOF && $this->isCurrentLineEmpty()) { $EOF = !$this->moveToNextLine(); } if ($EOF) { return false; } $ret = false; if ($this->getCurrentLineIndentation() > $currentIndentation) { $ret = true; } $this->moveToPreviousLine(); return $ret; } /** * Returns true if the current line is blank or if it is a comment line. * * @return bool Returns true if the current line is empty or if it is a comment line, false otherwise */ private function isCurrentLineEmpty() { return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); } /** * Returns true if the current line is blank. * * @return bool Returns true if the current line is blank, false otherwise */ private function isCurrentLineBlank() { return '' == trim($this->currentLine, ' '); } /** * Returns true if the current line is a comment line. * * @return bool Returns true if the current line is a comment line, false otherwise */ private function isCurrentLineComment() { //checking explicitly the first char of the trim is faster than loops or strpos $ltrimmedLine = ltrim($this->currentLine, ' '); return $ltrimmedLine[0] === '#'; } /** * Cleanups a YAML string to be parsed. * * @param string $value The input YAML string * * @return string A cleaned up YAML string */ private function cleanup($value) { $value = str_replace(array("\r\n", "\r"), "\n", $value); // strip YAML header $count = 0; $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count); $this->offset += $count; // remove leading comments $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count); if ($count == 1) { // items have been removed, update the offset $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); $value = $trimmedValue; } // remove start of the document marker (---) $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count); if ($count == 1) { // items have been removed, update the offset $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); $value = $trimmedValue; // remove end of the document marker (...) $value = preg_replace('#\.\.\.\s*$#', '', $value); } return $value; } /** * Returns true if the next line starts unindented collection. * * @return bool Returns true if the next line starts unindented collection, false otherwise */ private function isNextLineUnIndentedCollection() { $currentIndentation = $this->getCurrentLineIndentation(); $notEOF = $this->moveToNextLine(); while ($notEOF && $this->isCurrentLineEmpty()) { $notEOF = $this->moveToNextLine(); } if (false === $notEOF) { return false; } $ret = false; if ( $this->getCurrentLineIndentation() == $currentIndentation && $this->isStringUnIndentedCollectionItem($this->currentLine) ) { $ret = true; } $this->moveToPreviousLine(); return $ret; } /** * Returns true if the string is un-indented collection item. * * @return bool Returns true if the string is un-indented collection item, false otherwise */ private function isStringUnIndentedCollectionItem() { return (0 === strpos($this->currentLine, '- ')); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml; /** * Dumper dumps PHP variables to YAML strings. * * @author Fabien Potencier */ class Dumper { /** * The amount of spaces to use for indentation of nested nodes. * * @var int */ protected $indentation = 4; /** * Sets the indentation. * * @param int $num The amount of spaces to use for indentation of nested nodes. */ public function setIndentation($num) { $this->indentation = (int) $num; } /** * Dumps a PHP value to YAML. * * @param mixed $input The PHP value * @param int $inline The level where you switch to inline YAML * @param int $indent The level of indentation (used internally) * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise * @param bool $objectSupport true if object support is enabled, false otherwise * * @return string The YAML representation of the PHP value */ public function dump($input, $inline = 0, $indent = 0, $exceptionOnInvalidType = false, $objectSupport = false) { $output = ''; $prefix = $indent ? str_repeat(' ', $indent) : ''; if ($inline <= 0 || !is_array($input) || empty($input)) { $output .= $prefix.Inline::dump($input, $exceptionOnInvalidType, $objectSupport); } else { $isAHash = array_keys($input) !== range(0, count($input) - 1); foreach ($input as $key => $value) { $willBeInlined = $inline - 1 <= 0 || !is_array($value) || empty($value); $output .= sprintf('%s%s%s%s', $prefix, $isAHash ? Inline::dump($key, $exceptionOnInvalidType, $objectSupport).':' : '-', $willBeInlined ? ' ' : "\n", $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $exceptionOnInvalidType, $objectSupport) ).($willBeInlined ? "\n" : ''); } } return $output; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml; /** * Escaper encapsulates escaping rules for single and double-quoted * YAML strings. * * @author Matthew Lewinski */ class Escaper { // Characters that would cause a dumped string to require double quoting. const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9"; // Mapping arrays for escaping a double quoted string. The backslash is // first to ensure proper escaping because str_replace operates iteratively // on the input arrays. This ordering of the characters avoids the use of strtr, // which performs more slowly. private static $escapees = array('\\', '\\\\', '\\"', '"', "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9",); private static $escaped = array('\\\\', '\\"', '\\\\', '\\"', '\\0', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\a', '\\b', '\\t', '\\n', '\\v', '\\f', '\\r', '\\x0e', '\\x0f', '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', '\\x18', '\\x19', '\\x1a', '\\e', '\\x1c', '\\x1d', '\\x1e', '\\x1f', '\\N', '\\_', '\\L', '\\P',); /** * Determines if a PHP value would require double quoting in YAML. * * @param string $value A PHP value * * @return bool True if the value would require double quotes. */ public static function requiresDoubleQuoting($value) { return preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', $value); } /** * Escapes and surrounds a PHP value with double quotes. * * @param string $value A PHP value * * @return string The quoted, escaped string */ public static function escapeWithDoubleQuotes($value) { return sprintf('"%s"', str_replace(self::$escapees, self::$escaped, $value)); } /** * Determines if a PHP value would require single quoting in YAML. * * @param string $value A PHP value * * @return bool True if the value would require single quotes. */ public static function requiresSingleQuoting($value) { // Determines if a PHP value is entirely composed of a value that would // require single quoting in YAML. if (in_array(strtolower($value), array('null', '~', 'true', 'false', 'y', 'n', 'yes', 'no', 'on', 'off'))) { return true; } // Determines if the PHP value contains any single characters that would // cause it to require single quoting in YAML. return preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ \- ? | < > = ! % @ ` ]/x', $value); } /** * Escapes and surrounds a PHP value with single quotes. * * @param string $value A PHP value * * @return string The quoted, escaped string */ public static function escapeWithSingleQuotes($value) { return sprintf("'%s'", str_replace('\'', '\'\'', $value)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCOV; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use PHP_CodeCoverage; /** * @since Class available since Release 2.0.0 */ class ExecuteCommand extends BaseCommand { /** * Configures the current command. */ protected function configure() { $this->setName('execute') ->addArgument( 'script', InputArgument::REQUIRED, 'Script to execute' ) ->addOption( 'configuration', null, InputOption::VALUE_REQUIRED, 'Read configuration from XML file' ) ->addOption( 'blacklist', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add directory or file to the blacklist' ) ->addOption( 'whitelist', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add directory or file to the whitelist' ) ->addOption( 'add-uncovered', null, InputOption::VALUE_NONE, 'Add whitelisted files that are not covered' ) ->addOption( 'process-uncovered', null, InputOption::VALUE_NONE, 'Process whitelisted files that are not covered' ) ->addOption( 'clover', null, InputOption::VALUE_REQUIRED, 'Generate code coverage report in Clover XML format' ) ->addOption( 'crap4j', null, InputOption::VALUE_REQUIRED, 'Generate code coverage report in Crap4J XML format' ) ->addOption( 'html', null, InputOption::VALUE_REQUIRED, 'Generate code coverage report in HTML format' ) ->addOption( 'php', null, InputOption::VALUE_REQUIRED, 'Export PHP_CodeCoverage object to file' ) ->addOption( 'text', null, InputOption::VALUE_REQUIRED, 'Generate code coverage report in text format' ); } /** * Executes the current command. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance * * @return null|int null or 0 if everything went fine, or an error code */ protected function execute(InputInterface $input, OutputInterface $output) { $coverage = new PHP_CodeCoverage; $this->handleConfiguration($coverage, $input); $this->handleFilter($coverage, $input); $coverage->start('phpcov'); require $input->getArgument('script'); $coverage->stop(); $this->handleReports($coverage, $input, $output); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCOV; use Symfony\Component\Console\Command\Command as AbstractCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use PHP_CodeCoverage_Util; /** * @since Class available since Release 2.0.0 */ class PatchCoverageCommand extends AbstractCommand { /** * Configures the current command. */ protected function configure() { $this->setName('patch-coverage') ->addArgument( 'coverage', InputArgument::REQUIRED, 'Exported PHP_CodeCoverage object' ) ->addOption( 'patch', null, InputOption::VALUE_REQUIRED, 'Unified diff to be analysed for patch coverage' ) ->addOption( 'path-prefix', null, InputOption::VALUE_REQUIRED, 'Prefix that needs to be stripped from paths in the diff' ); } /** * Executes the current command. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance * * @return null|int null or 0 if everything went fine, or an error code */ protected function execute(InputInterface $input, OutputInterface $output) { $pc = new PatchCoverage; $pc = $pc->execute( $input->getArgument('coverage'), $input->getOption('patch'), $input->getOption('path-prefix') ); $output->writeln( sprintf( '%d / %d changed executable lines covered (%s)', $pc['numChangedLinesThatWereExecuted'], $pc['numChangedLinesThatAreExecutable'], PHP_CodeCoverage_Util::percent( $pc['numChangedLinesThatWereExecuted'], $pc['numChangedLinesThatAreExecutable'], true ) ) ); if (!empty($pc['changedLinesThatWereNotExecuted'])) { $output->writeln("\nChanged executable lines that are not covered:\n"); foreach ($pc['changedLinesThatWereNotExecuted'] as $file => $lines) { foreach ($lines as $line) { $output->writeln( sprintf( ' %s:%d', $file, $line ) ); } } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCOV; use SebastianBergmann\Diff\Line; use SebastianBergmann\Diff\Parser as DiffParser; /** * @since Class available since Release 2.0.0 */ class PatchCoverage { /** * @param string $coverage * @param string $patch * @param string $prefix * @return array */ public function execute($coverage, $patch, $prefix) { $result = array( 'numChangedLinesThatAreExecutable' => 0, 'numChangedLinesThatWereExecuted' => 0, 'changedLinesThatWereNotExecuted' => array() ); $coverage = include($coverage); $coverage = $coverage->getData(); $parser = new DiffParser; $patch = $parser->parse(file_get_contents($patch)); $changes = array(); foreach ($patch as $diff) { $file = substr($diff->getFrom(), 2); $changes[$file] = array(); foreach ($diff->getChunks() as $chunk) { $lineNr = $chunk->getStart(); foreach ($chunk->getLines() as $line) { if ($line->getType() == Line::ADDED) { $changes[$file][] = $lineNr; } if ($line->getType() != Line::REMOVED) { $lineNr++; } } } } foreach ($changes as $file => $lines) { $key = $prefix . $file; foreach ($lines as $line) { if (isset($coverage[$key][$line]) && is_array($coverage[$key][$line])) { $result['numChangedLinesThatAreExecutable']++; if (empty($coverage[$key][$line])) { if (!isset($result['changedLinesThatWereNotExecuted'][$file])) { $result['changedLinesThatWereNotExecuted'][$file] = array(); } $result['changedLinesThatWereNotExecuted'][$file][] = $line; } else { $result['numChangedLinesThatWereExecuted']++; } } } } return $result; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCOV; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use SebastianBergmann\FinderFacade\FinderFacade; use PHP_CodeCoverage; /** * @since Class available since Release 2.0.0 */ class MergeCommand extends BaseCommand { /** * Configures the current command. */ protected function configure() { $this->setName('merge') ->addArgument( 'directory', InputArgument::REQUIRED, 'Directory to scan for exported PHP_CodeCoverage objects stored in .cov files' ) ->addOption( 'clover', null, InputOption::VALUE_REQUIRED, 'Generate code coverage report in Clover XML format' ) ->addOption( 'crap4j', null, InputOption::VALUE_REQUIRED, 'Generate code coverage report in Crap4J XML format' ) ->addOption( 'html', null, InputOption::VALUE_REQUIRED, 'Generate code coverage report in HTML format' ) ->addOption( 'php', null, InputOption::VALUE_REQUIRED, 'Export PHP_CodeCoverage object to file' ) ->addOption( 'text', null, InputOption::VALUE_REQUIRED, 'Generate code coverage report in text format' ); } /** * Executes the current command. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance * * @return null|int null or 0 if everything went fine, or an error code */ protected function execute(InputInterface $input, OutputInterface $output) { $mergedCoverage = new PHP_CodeCoverage; $finder = new FinderFacade( array($input->getArgument('directory')), array(), array('*.cov') ); foreach ($finder->findFiles() as $file) { $_coverage = include($file); $mergedCoverage->merge($_coverage); unset($_coverage); } $this->handleReports($mergedCoverage, $input, $output); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCOV; use SebastianBergmann\Version; use Symfony\Component\Console\Application as AbstractApplication; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * TextUI frontend for PHP_CodeCoverage. * * @since Class available since Release 2.0.0 */ class Application extends AbstractApplication { public function __construct() { $version = new Version('2.0.2', dirname(__DIR__)); parent::__construct('phpcov', $version->getVersion()); $this->add(new ExecuteCommand); $this->add(new MergeCommand); $this->add(new PatchCoverageCommand); } /** * Runs the current application. * * @param InputInterface $input An Input instance * @param OutputInterface $output An Output instance * * @return int 0 if everything went fine, or an error code */ public function doRun(InputInterface $input, OutputInterface $output) { if (!$input->hasParameterOption('--quiet')) { $output->write( sprintf( "phpcov %s by Sebastian Bergmann.\n\n", $this->getVersion() ) ); } if ($input->hasParameterOption('--version') || $input->hasParameterOption('-V')) { exit; } parent::doRun($input, $output); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\PHPCOV; use Symfony\Component\Console\Command\Command as AbstractCommand; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use PHP_CodeCoverage; use PHP_CodeCoverage_Report_Clover; use PHP_CodeCoverage_Report_Crap4j; use PHP_CodeCoverage_Report_HTML; use PHP_CodeCoverage_Report_PHP; use PHP_CodeCoverage_Report_Text; use PHPUnit_Util_Configuration; use ReflectionClass; /** * @since Class available since Release 2.0.0 */ abstract class BaseCommand extends AbstractCommand { protected function handleConfiguration(PHP_CodeCoverage $coverage, InputInterface $input) { $configuration = $input->getOption('configuration'); if (!$configuration) { return; } $filter = $coverage->filter(); $configuration = PHPUnit_Util_Configuration::getInstance($configuration); $filterConfiguration = $configuration->getFilterConfiguration(); $coverage->setAddUncoveredFilesFromWhitelist( $filterConfiguration['whitelist']['addUncoveredFilesFromWhitelist'] ); $coverage->setProcessUncoveredFilesFromWhitelist( $filterConfiguration['whitelist']['processUncoveredFilesFromWhitelist'] ); foreach ($filterConfiguration['blacklist']['include']['directory'] as $dir) { $filter->addDirectoryToBlacklist( $dir['path'], $dir['suffix'], $dir['prefix'], $dir['group'] ); } foreach ($filterConfiguration['blacklist']['include']['file'] as $file) { $filter->addFileToBlacklist($file); } foreach ($filterConfiguration['blacklist']['exclude']['directory'] as $dir) { $filter->removeDirectoryFromBlacklist( $dir['path'], $dir['suffix'], $dir['prefix'], $dir['group'] ); } foreach ($filterConfiguration['blacklist']['exclude']['file'] as $file) { $filter->removeFileFromBlacklist($file); } foreach ($filterConfiguration['whitelist']['include']['directory'] as $dir) { $filter->addDirectoryToWhitelist( $dir['path'], $dir['suffix'], $dir['prefix'] ); } foreach ($filterConfiguration['whitelist']['include']['file'] as $file) { $filter->addFileToWhitelist($file); } foreach ($filterConfiguration['whitelist']['exclude']['directory'] as $dir) { $filter->removeDirectoryFromWhitelist( $dir['path'], $dir['suffix'], $dir['prefix'] ); } foreach ($filterConfiguration['whitelist']['exclude']['file'] as $file) { $filter->removeFileFromWhitelist($file); } } protected function handleFilter(PHP_CodeCoverage $coverage, InputInterface $input) { $filter = $coverage->filter(); $whitelist = $input->getOption('whitelist'); if (empty($whitelist)) { $classes = array( 'SebastianBergmann\PHPCOV\Application', 'SebastianBergmann\FinderFacade\FinderFacade', 'SebastianBergmann\Version', 'Symfony\Component\Console\Application', 'Symfony\Component\Finder\Finder' ); foreach ($classes as $class) { $c = new ReflectionClass($class); $filter->addDirectoryToBlacklist(dirname($c->getFileName())); } foreach ($input->getOption('blacklist') as $item) { if (is_dir($item)) { $filter->addDirectoryToBlacklist($item); } elseif (is_file($item)) { $filter->addFileToBlacklist($item); } } } else { $coverage->setAddUncoveredFilesFromWhitelist( $input->getOption('add-uncovered') ); $coverage->setProcessUncoveredFilesFromWhitelist( $input->getOption('process-uncovered') ); foreach ($input->getOption('whitelist') as $item) { if (is_dir($item)) { $filter->addDirectoryToWhitelist($item); } elseif (is_file($item)) { $filter->addFileToWhitelist($item); } } } } protected function handleReports(PHP_CodeCoverage $coverage, InputInterface $input, OutputInterface $output) { if ($input->getOption('clover')) { $output->write( "\nGenerating code coverage report in Clover XML format ..." ); $writer = new PHP_CodeCoverage_Report_Clover; $writer->process($coverage, $input->getOption('clover')); $output->write(" done\n"); } if ($input->getOption('crap4j')) { $output->write( "\nGenerating code coverage report in Crap4J XML format..." ); $writer = new PHP_CodeCoverage_Report_Crap4j; $writer->process($coverage, $input->getOption('crap4j')); $output->write(" done\n"); } if ($input->getOption('html')) { $output->write( "\nGenerating code coverage report in HTML format ..." ); $writer = new PHP_CodeCoverage_Report_HTML; $writer->process($coverage, $input->getOption('html')); $output->write(" done\n"); } if ($input->getOption('php')) { $output->write( "\nGenerating code coverage report in PHP format ..." ); $writer = new PHP_CodeCoverage_Report_PHP; $writer->process($coverage, $input->getOption('php')); $output->write(" done\n"); } if ($input->getOption('text')) { $writer = new PHP_CodeCoverage_Report_Text; $writer->process($coverage, $input->getOption('text')); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A simple template engine. * * @since Class available since Release 1.0.0 */ class Text_Template { /** * @var string */ protected $template = ''; /** * @var string */ protected $openDelimiter = '{'; /** * @var string */ protected $closeDelimiter = '}'; /** * @var array */ protected $values = array(); /** * Constructor. * * @param string $file * @throws InvalidArgumentException */ public function __construct($file = '', $openDelimiter = '{', $closeDelimiter = '}') { $this->setFile($file); $this->openDelimiter = $openDelimiter; $this->closeDelimiter = $closeDelimiter; } /** * Sets the template file. * * @param string $file * @throws InvalidArgumentException */ public function setFile($file) { $distFile = $file . '.dist'; if (file_exists($file)) { $this->template = file_get_contents($file); } else if (file_exists($distFile)) { $this->template = file_get_contents($distFile); } else { throw new InvalidArgumentException( 'Template file could not be loaded.' ); } } /** * Sets one or more template variables. * * @param array $values * @param bool $merge */ public function setVar(array $values, $merge = TRUE) { if (!$merge || empty($this->values)) { $this->values = $values; } else { $this->values = array_merge($this->values, $values); } } /** * Renders the template and returns the result. * * @return string */ public function render() { $keys = array(); foreach ($this->values as $key => $value) { $keys[] = $this->openDelimiter . $key . $this->closeDelimiter; } return str_replace($keys, $this->values, $this->template); } /** * Renders the template and writes the result to a file. * * @param string $target */ public function renderTo($target) { $fp = @fopen($target, 'wt'); if ($fp) { fwrite($fp, $this->render()); fclose($fp); } else { $error = error_get_last(); throw new RuntimeException( sprintf( 'Could not write to %s: %s', $target, substr( $error['message'], strpos($error['message'], ':') + 2 ) ) ); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Utility class for timing. * * @since Class available since Release 1.0.0 */ class PHP_Timer { /** * @var array */ private static $times = array( 'hour' => 3600000, 'minute' => 60000, 'second' => 1000 ); /** * @var array */ private static $startTimes = array(); /** * @var float */ public static $requestTime; /** * Starts the timer. */ public static function start() { array_push(self::$startTimes, microtime(true)); } /** * Stops the timer and returns the elapsed time. * * @return float */ public static function stop() { return microtime(true) - array_pop(self::$startTimes); } /** * Formats the elapsed time as a string. * * @param float $time * @return string */ public static function secondsToTimeString($time) { $ms = round($time * 1000); foreach (self::$times as $unit => $value) { if ($ms >= $value) { $time = floor($ms / $value * 100.0) / 100.0; return $time . ' ' . ($time == 1 ? $unit : $unit . 's'); } } return $ms . ' ms'; } /** * Formats the elapsed time since the start of the request as a string. * * @return string */ public static function timeSinceStartOfRequest() { return self::secondsToTimeString(microtime(true) - self::$requestTime); } /** * Returns the resources (time, memory) of the request as a string. * * @return string */ public static function resourceUsage() { return sprintf( 'Time: %s, Memory: %4.2fMb', self::timeSinceStartOfRequest(), memory_get_peak_usage(true) / 1048576 ); } } if (isset($_SERVER['REQUEST_TIME_FLOAT'])) { PHP_Timer::$requestTime = $_SERVER['REQUEST_TIME_FLOAT']; } elseif (isset($_SERVER['REQUEST_TIME'])) { PHP_Timer::$requestTime = $_SERVER['REQUEST_TIME']; } else { PHP_Timer::$requestTime = microtime(true); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * FilterIterator implementation that filters files based on prefix(es) and/or * suffix(es). Hidden files and files from hidden directories are also filtered. * * @since Class available since Release 1.0.0 */ class File_Iterator extends FilterIterator { const PREFIX = 0; const SUFFIX = 1; /** * @var array */ protected $suffixes = array(); /** * @var array */ protected $prefixes = array(); /** * @var array */ protected $exclude = array(); /** * @var string */ protected $basepath; /** * @param Iterator $iterator * @param array $suffixes * @param array $prefixes * @param array $exclude * @param string $basepath */ public function __construct(Iterator $iterator, array $suffixes = array(), array $prefixes = array(), array $exclude = array(), $basepath = NULL) { $exclude = array_filter(array_map('realpath', $exclude)); if ($basepath !== NULL) { $basepath = realpath($basepath); } if ($basepath === FALSE) { $basepath = NULL; } else { foreach ($exclude as &$_exclude) { $_exclude = str_replace($basepath, '', $_exclude); } } $this->prefixes = $prefixes; $this->suffixes = $suffixes; $this->exclude = $exclude; $this->basepath = $basepath; parent::__construct($iterator); } /** * @return bool */ public function accept() { $current = $this->getInnerIterator()->current(); $filename = $current->getFilename(); $realpath = $current->getRealPath(); if ($this->basepath !== NULL) { $realpath = str_replace($this->basepath, '', $realpath); } // Filter files in hidden directories. if (preg_match('=/\.[^/]*/=', $realpath)) { return FALSE; } return $this->acceptPath($realpath) && $this->acceptPrefix($filename) && $this->acceptSuffix($filename); } /** * @param string $path * @return bool * @since Method available since Release 1.1.0 */ protected function acceptPath($path) { foreach ($this->exclude as $exclude) { if (strpos($path, $exclude) === 0) { return FALSE; } } return TRUE; } /** * @param string $filename * @return bool * @since Method available since Release 1.1.0 */ protected function acceptPrefix($filename) { return $this->acceptSubString($filename, $this->prefixes, self::PREFIX); } /** * @param string $filename * @return bool * @since Method available since Release 1.1.0 */ protected function acceptSuffix($filename) { return $this->acceptSubString($filename, $this->suffixes, self::SUFFIX); } /** * @param string $filename * @param array $subString * @param int $type * @return bool * @since Method available since Release 1.1.0 */ protected function acceptSubString($filename, array $subStrings, $type) { if (empty($subStrings)) { return TRUE; } $matched = FALSE; foreach ($subStrings as $string) { if (($type == self::PREFIX && strpos($filename, $string) === 0) || ($type == self::SUFFIX && substr($filename, -1 * strlen($string)) == $string)) { $matched = TRUE; break; } } return $matched; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Façade implementation that uses File_Iterator_Factory to create a * File_Iterator that operates on an AppendIterator that contains an * RecursiveDirectoryIterator for each given path. The list of unique * files is returned as an array. * * @since Class available since Release 1.3.0 */ class File_Iterator_Facade { /** * @param array|string $paths * @param array|string $suffixes * @param array|string $prefixes * @param array $exclude * @param bool $commonPath * @return array */ public function getFilesAsArray($paths, $suffixes = '', $prefixes = '', array $exclude = array(), $commonPath = FALSE) { if (is_string($paths)) { $paths = array($paths); } $factory = new File_Iterator_Factory; $iterator = $factory->getFileIterator( $paths, $suffixes, $prefixes, $exclude ); $files = array(); foreach ($iterator as $file) { $file = $file->getRealPath(); if ($file) { $files[] = $file; } } foreach ($paths as $path) { if (is_file($path)) { $files[] = realpath($path); } } $files = array_unique($files); sort($files); if ($commonPath) { return array( 'commonPath' => $this->getCommonPath($files), 'files' => $files ); } else { return $files; } } /** * Returns the common path of a set of files. * * @param array $files * @return string */ protected function getCommonPath(array $files) { $count = count($files); if ($count == 0) { return ''; } if ($count == 1) { return dirname($files[0]) . DIRECTORY_SEPARATOR; } $_files = array(); foreach ($files as $file) { $_files[] = $_fileParts = explode(DIRECTORY_SEPARATOR, $file); if (empty($_fileParts[0])) { $_fileParts[0] = DIRECTORY_SEPARATOR; } } $common = ''; $done = FALSE; $j = 0; $count--; while (!$done) { for ($i = 0; $i < $count; $i++) { if ($_files[$i][$j] != $_files[$i+1][$j]) { $done = TRUE; break; } } if (!$done) { $common .= $_files[0][$j]; if ($j > 0) { $common .= DIRECTORY_SEPARATOR; } } $j++; } return DIRECTORY_SEPARATOR . $common; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Factory Method implementation that creates a File_Iterator that operates on * an AppendIterator that contains an RecursiveDirectoryIterator for each given * path. * * @since Class available since Release 1.1.0 */ class File_Iterator_Factory { /** * @param array|string $paths * @param array|string $suffixes * @param array|string $prefixes * @param array $exclude * @return AppendIterator */ public function getFileIterator($paths, $suffixes = '', $prefixes = '', array $exclude = array()) { if (is_string($paths)) { $paths = array($paths); } $paths = $this->getPathsAfterResolvingWildcards($paths); $exclude = $this->getPathsAfterResolvingWildcards($exclude); if (is_string($prefixes)) { if ($prefixes != '') { $prefixes = array($prefixes); } else { $prefixes = array(); } } if (is_string($suffixes)) { if ($suffixes != '') { $suffixes = array($suffixes); } else { $suffixes = array(); } } $iterator = new AppendIterator; foreach ($paths as $path) { if (is_dir($path)) { $iterator->append( new File_Iterator( new RecursiveIteratorIterator( new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS) ), $suffixes, $prefixes, $exclude, $path ) ); } } return $iterator; } /** * @param array $paths * @return array */ protected function getPathsAfterResolvingWildcards(array $paths) { $_paths = array(); foreach ($paths as $path) { if ($locals = glob($path, GLOB_ONLYDIR)) { $_paths = array_merge($_paths, $locals); } else { $_paths[] = $path; } } return $_paths; } } stop(); $writer = new PHP_CodeCoverage_Report_HTML; $writer->process($coverage, '/tmp/coverage'); filter(); $filter->addFileToBlacklist(__FILE__); $filter->addFileToBlacklist(dirname(__FILE__) . '/auto_append.php'); $coverage->start($_SERVER['SCRIPT_FILENAME']); * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Utility methods. * * @since Class available since Release 1.0.0 */ class PHP_CodeCoverage_Util { /** * @param float $a * @param float $b * @return float ($a / $b) * 100 */ public static function percent($a, $b, $asString = false, $fixedWidth = false) { if ($asString && $b == 0) { return ''; } if ($b > 0) { $percent = ($a / $b) * 100; } else { $percent = 100; } if ($asString) { if ($fixedWidth) { return sprintf('%6.2F%%', $percent); } return sprintf('%01.2F%%', $percent); } else { return $percent; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Exception class for PHP_CodeCoverage component. * * @since Class available since Release 1.1.0 */ class PHP_CodeCoverage_Exception extends RuntimeException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use SebastianBergmann\Environment\Runtime; /** * Base class for PHP_CodeCoverage_Report_Node renderers. * * @since Class available since Release 1.1.0 */ abstract class PHP_CodeCoverage_Report_HTML_Renderer { /** * @var string */ protected $templatePath; /** * @var string */ protected $generator; /** * @var string */ protected $date; /** * @var int */ protected $lowUpperBound; /** * @var int */ protected $highLowerBound; /** * @var string */ protected $version; /** * Constructor. * * @param string $templatePath * @param string $generator * @param string $date * @param int $lowUpperBound * @param int $highLowerBound */ public function __construct($templatePath, $generator, $date, $lowUpperBound, $highLowerBound) { $version = new SebastianBergmann\Version('2.2.3', dirname(dirname(dirname(dirname(__DIR__))))); $this->templatePath = $templatePath; $this->generator = $generator; $this->date = $date; $this->lowUpperBound = $lowUpperBound; $this->highLowerBound = $highLowerBound; $this->version = $version->getVersion(); } /** * @param Text_Template $template * @param array $data * @return string */ protected function renderItemTemplate(Text_Template $template, array $data) { $numSeparator = ' / '; if (isset($data['numClasses']) && $data['numClasses'] > 0) { $classesLevel = $this->getColorLevel($data['testedClassesPercent']); $classesNumber = $data['numTestedClasses'] . $numSeparator . $data['numClasses']; $classesBar = $this->getCoverageBar( $data['testedClassesPercent'] ); } else { $classesLevel = 'success'; $classesNumber = '0' . $numSeparator . '0'; $classesBar = $this->getCoverageBar(100); } if ($data['numMethods'] > 0) { $methodsLevel = $this->getColorLevel($data['testedMethodsPercent']); $methodsNumber = $data['numTestedMethods'] . $numSeparator . $data['numMethods']; $methodsBar = $this->getCoverageBar( $data['testedMethodsPercent'] ); } else { $methodsLevel = 'success'; $methodsNumber = '0' . $numSeparator . '0'; $methodsBar = $this->getCoverageBar(100); $data['testedMethodsPercentAsString'] = '100.00%'; } if ($data['numExecutableLines'] > 0) { $linesLevel = $this->getColorLevel($data['linesExecutedPercent']); $linesNumber = $data['numExecutedLines'] . $numSeparator . $data['numExecutableLines']; $linesBar = $this->getCoverageBar( $data['linesExecutedPercent'] ); } else { $linesLevel = 'success'; $linesNumber = '0' . $numSeparator . '0'; $linesBar = $this->getCoverageBar(100); $data['linesExecutedPercentAsString'] = '100.00%'; } $template->setVar( array( 'icon' => isset($data['icon']) ? $data['icon'] : '', 'crap' => isset($data['crap']) ? $data['crap'] : '', 'name' => $data['name'], 'lines_bar' => $linesBar, 'lines_executed_percent' => $data['linesExecutedPercentAsString'], 'lines_level' => $linesLevel, 'lines_number' => $linesNumber, 'methods_bar' => $methodsBar, 'methods_tested_percent' => $data['testedMethodsPercentAsString'], 'methods_level' => $methodsLevel, 'methods_number' => $methodsNumber, 'classes_bar' => $classesBar, 'classes_tested_percent' => isset($data['testedClassesPercentAsString']) ? $data['testedClassesPercentAsString'] : '', 'classes_level' => $classesLevel, 'classes_number' => $classesNumber ) ); return $template->render(); } /** * @param Text_Template $template * @param PHP_CodeCoverage_Report_Node $node */ protected function setCommonTemplateVariables(Text_Template $template, PHP_CodeCoverage_Report_Node $node) { $runtime = new Runtime; $template->setVar( array( 'id' => $node->getId(), 'full_path' => $node->getPath(), 'path_to_root' => $this->getPathToRoot($node), 'breadcrumbs' => $this->getBreadcrumbs($node), 'date' => $this->date, 'version' => $this->version, 'runtime_name' => $runtime->getName(), 'runtime_version' => $runtime->getVersion(), 'runtime_link' => $runtime->getVendorUrl(), 'generator' => $this->generator, 'low_upper_bound' => $this->lowUpperBound, 'high_lower_bound' => $this->highLowerBound ) ); } protected function getBreadcrumbs(PHP_CodeCoverage_Report_Node $node) { $breadcrumbs = ''; $path = $node->getPathAsArray(); $pathToRoot = array(); $max = count($path); if ($node instanceof PHP_CodeCoverage_Report_Node_File) { $max--; } for ($i = 0; $i < $max; $i++) { $pathToRoot[] = str_repeat('../', $i); } foreach ($path as $step) { if ($step !== $node) { $breadcrumbs .= $this->getInactiveBreadcrumb( $step, array_pop($pathToRoot) ); } else { $breadcrumbs .= $this->getActiveBreadcrumb($step); } } return $breadcrumbs; } protected function getActiveBreadcrumb(PHP_CodeCoverage_Report_Node $node) { $buffer = sprintf( '
  • %s
  • ' . "\n", $node->getName() ); if ($node instanceof PHP_CodeCoverage_Report_Node_Directory) { $buffer .= '
  • (Dashboard)
  • ' . "\n"; } return $buffer; } protected function getInactiveBreadcrumb(PHP_CodeCoverage_Report_Node $node, $pathToRoot) { return sprintf( '
  • %s
  • ' . "\n", $pathToRoot, $node->getName() ); } protected function getPathToRoot(PHP_CodeCoverage_Report_Node $node) { $id = $node->getId(); $depth = substr_count($id, '/'); if ($id != 'index' && $node instanceof PHP_CodeCoverage_Report_Node_Directory) { $depth++; } return str_repeat('../', $depth); } protected function getCoverageBar($percent) { $level = $this->getColorLevel($percent); $template = new Text_Template( $this->templatePath . 'coverage_bar.html', '{{', '}}' ); $template->setVar(array('level' => $level, 'percent' => sprintf('%.2F', $percent))); return $template->render(); } /** * @param int $percent * @return string */ protected function getColorLevel($percent) { if ($percent <= $this->lowUpperBound) { return 'danger'; } elseif ($percent > $this->lowUpperBound && $percent < $this->highLowerBound) { return 'warning'; } else { return 'success'; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Renders the dashboard for a PHP_CodeCoverage_Report_Node_Directory node. * * @since Class available since Release 1.1.0 */ class PHP_CodeCoverage_Report_HTML_Renderer_Dashboard extends PHP_CodeCoverage_Report_HTML_Renderer { /** * @param PHP_CodeCoverage_Report_Node_Directory $node * @param string $file */ public function render(PHP_CodeCoverage_Report_Node_Directory $node, $file) { $classes = $node->getClassesAndTraits(); $template = new Text_Template( $this->templatePath . 'dashboard.html', '{{', '}}' ); $this->setCommonTemplateVariables($template, $node); $baseLink = $node->getId() . '/'; $complexity = $this->complexity($classes, $baseLink); $coverageDistribution = $this->coverageDistribution($classes); $insufficientCoverage = $this->insufficientCoverage($classes, $baseLink); $projectRisks = $this->projectRisks($classes, $baseLink); $template->setVar( array( 'insufficient_coverage_classes' => $insufficientCoverage['class'], 'insufficient_coverage_methods' => $insufficientCoverage['method'], 'project_risks_classes' => $projectRisks['class'], 'project_risks_methods' => $projectRisks['method'], 'complexity_class' => $complexity['class'], 'complexity_method' => $complexity['method'], 'class_coverage_distribution' => $coverageDistribution['class'], 'method_coverage_distribution' => $coverageDistribution['method'] ) ); $template->renderTo($file); } /** * Returns the data for the Class/Method Complexity charts. * * @param array $classes * @param string $baseLink * @return array */ protected function complexity(array $classes, $baseLink) { $result = array('class' => array(), 'method' => array()); foreach ($classes as $className => $class) { foreach ($class['methods'] as $methodName => $method) { if ($className != '*') { $methodName = $className . '::' . $methodName; } $result['method'][] = array( $method['coverage'], $method['ccn'], sprintf( '%s', str_replace($baseLink, '', $method['link']), $methodName ) ); } $result['class'][] = array( $class['coverage'], $class['ccn'], sprintf( '%s', str_replace($baseLink, '', $class['link']), $className ) ); } return array( 'class' => json_encode($result['class']), 'method' => json_encode($result['method']) ); } /** * Returns the data for the Class / Method Coverage Distribution chart. * * @param array $classes * @return array */ protected function coverageDistribution(array $classes) { $result = array( 'class' => array( '0%' => 0, '0-10%' => 0, '10-20%' => 0, '20-30%' => 0, '30-40%' => 0, '40-50%' => 0, '50-60%' => 0, '60-70%' => 0, '70-80%' => 0, '80-90%' => 0, '90-100%' => 0, '100%' => 0 ), 'method' => array( '0%' => 0, '0-10%' => 0, '10-20%' => 0, '20-30%' => 0, '30-40%' => 0, '40-50%' => 0, '50-60%' => 0, '60-70%' => 0, '70-80%' => 0, '80-90%' => 0, '90-100%' => 0, '100%' => 0 ) ); foreach ($classes as $class) { foreach ($class['methods'] as $methodName => $method) { if ($method['coverage'] == 0) { $result['method']['0%']++; } elseif ($method['coverage'] == 100) { $result['method']['100%']++; } else { $key = floor($method['coverage'] / 10) * 10; $key = $key . '-' . ($key + 10) . '%'; $result['method'][$key]++; } } if ($class['coverage'] == 0) { $result['class']['0%']++; } elseif ($class['coverage'] == 100) { $result['class']['100%']++; } else { $key = floor($class['coverage'] / 10) * 10; $key = $key . '-' . ($key + 10) . '%'; $result['class'][$key]++; } } return array( 'class' => json_encode(array_values($result['class'])), 'method' => json_encode(array_values($result['method'])) ); } /** * Returns the classes / methods with insufficient coverage. * * @param array $classes * @param string $baseLink * @return array */ protected function insufficientCoverage(array $classes, $baseLink) { $leastTestedClasses = array(); $leastTestedMethods = array(); $result = array('class' => '', 'method' => ''); foreach ($classes as $className => $class) { foreach ($class['methods'] as $methodName => $method) { if ($method['coverage'] < $this->highLowerBound) { if ($className != '*') { $key = $className . '::' . $methodName; } else { $key = $methodName; } $leastTestedMethods[$key] = $method['coverage']; } } if ($class['coverage'] < $this->highLowerBound) { $leastTestedClasses[$className] = $class['coverage']; } } asort($leastTestedClasses); asort($leastTestedMethods); foreach ($leastTestedClasses as $className => $coverage) { $result['class'] .= sprintf( ' %s%d%%' . "\n", str_replace($baseLink, '', $classes[$className]['link']), $className, $coverage ); } foreach ($leastTestedMethods as $methodName => $coverage) { list($class, $method) = explode('::', $methodName); $result['method'] .= sprintf( ' %s%d%%' . "\n", str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), $methodName, $method, $coverage ); } return $result; } /** * Returns the project risks according to the CRAP index. * * @param array $classes * @param string $baseLink * @return array */ protected function projectRisks(array $classes, $baseLink) { $classRisks = array(); $methodRisks = array(); $result = array('class' => '', 'method' => ''); foreach ($classes as $className => $class) { foreach ($class['methods'] as $methodName => $method) { if ($method['coverage'] < $this->highLowerBound && $method['ccn'] > 1) { if ($className != '*') { $key = $className . '::' . $methodName; } else { $key = $methodName; } $methodRisks[$key] = $method['crap']; } } if ($class['coverage'] < $this->highLowerBound && $class['ccn'] > count($class['methods'])) { $classRisks[$className] = $class['crap']; } } arsort($classRisks); arsort($methodRisks); foreach ($classRisks as $className => $crap) { $result['class'] .= sprintf( ' %s%d' . "\n", str_replace($baseLink, '', $classes[$className]['link']), $className, $crap ); } foreach ($methodRisks as $methodName => $crap) { list($class, $method) = explode('::', $methodName); $result['method'] .= sprintf( ' %s%d' . "\n", str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), $methodName, $method, $crap ); } return $result; } protected function getActiveBreadcrumb(PHP_CodeCoverage_Report_Node $node) { return sprintf( '
  • %s
  • ' . "\n" . '
  • (Dashboard)
  • ' . "\n", $node->getName() ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ // @codeCoverageIgnoreStart if (!defined('T_TRAIT')) { define('T_TRAIT', 1001); } if (!defined('T_INSTEADOF')) { define('T_INSTEADOF', 1002); } if (!defined('T_CALLABLE')) { define('T_CALLABLE', 1003); } if (!defined('T_FINALLY')) { define('T_FINALLY', 1004); } if (!defined('T_YIELD')) { define('T_YIELD', 1005); } // @codeCoverageIgnoreEnd /** * Renders a PHP_CodeCoverage_Report_Node_File node. * * @since Class available since Release 1.1.0 */ class PHP_CodeCoverage_Report_HTML_Renderer_File extends PHP_CodeCoverage_Report_HTML_Renderer { /** * @var int */ private $htmlspecialcharsFlags; /** * Constructor. * * @param string $templatePath * @param string $generator * @param string $date * @param int $lowUpperBound * @param int $highLowerBound */ public function __construct($templatePath, $generator, $date, $lowUpperBound, $highLowerBound) { parent::__construct( $templatePath, $generator, $date, $lowUpperBound, $highLowerBound ); $this->htmlspecialcharsFlags = ENT_COMPAT; if (PHP_VERSION_ID >= 50400 && defined('ENT_SUBSTITUTE')) { $this->htmlspecialcharsFlags = $this->htmlspecialcharsFlags | ENT_HTML401 | ENT_SUBSTITUTE; } } /** * @param PHP_CodeCoverage_Report_Node_File $node * @param string $file */ public function render(PHP_CodeCoverage_Report_Node_File $node, $file) { $template = new Text_Template($this->templatePath . 'file.html', '{{', '}}'); $template->setVar( array( 'items' => $this->renderItems($node), 'lines' => $this->renderSource($node) ) ); $this->setCommonTemplateVariables($template, $node); $template->renderTo($file); } /** * @param PHP_CodeCoverage_Report_Node_File $node * @return string */ protected function renderItems(PHP_CodeCoverage_Report_Node_File $node) { $template = new Text_Template($this->templatePath . 'file_item.html', '{{', '}}'); $methodItemTemplate = new Text_Template( $this->templatePath . 'method_item.html', '{{', '}}' ); $items = $this->renderItemTemplate( $template, array( 'name' => 'Total', 'numClasses' => $node->getNumClassesAndTraits(), 'numTestedClasses' => $node->getNumTestedClassesAndTraits(), 'numMethods' => $node->getNumMethods(), 'numTestedMethods' => $node->getNumTestedMethods(), 'linesExecutedPercent' => $node->getLineExecutedPercent(false), 'linesExecutedPercentAsString' => $node->getLineExecutedPercent(), 'numExecutedLines' => $node->getNumExecutedLines(), 'numExecutableLines' => $node->getNumExecutableLines(), 'testedMethodsPercent' => $node->getTestedMethodsPercent(false), 'testedMethodsPercentAsString' => $node->getTestedMethodsPercent(), 'testedClassesPercent' => $node->getTestedClassesAndTraitsPercent(false), 'testedClassesPercentAsString' => $node->getTestedClassesAndTraitsPercent(), 'crap' => 'CRAP' ) ); $items .= $this->renderFunctionItems( $node->getFunctions(), $methodItemTemplate ); $items .= $this->renderTraitOrClassItems( $node->getTraits(), $template, $methodItemTemplate ); $items .= $this->renderTraitOrClassItems( $node->getClasses(), $template, $methodItemTemplate ); return $items; } /** * @param array $items * @param Text_Template $template * @param Text_Template $methodItemTemplate * @return string */ protected function renderTraitOrClassItems(array $items, Text_Template $template, Text_Template $methodItemTemplate) { if (empty($items)) { return ''; } $buffer = ''; foreach ($items as $name => $item) { $numMethods = count($item['methods']); $numTestedMethods = 0; foreach ($item['methods'] as $method) { if ($method['executedLines'] == $method['executableLines']) { $numTestedMethods++; } } $buffer .= $this->renderItemTemplate( $template, array( 'name' => $name, 'numClasses' => 1, 'numTestedClasses' => $numTestedMethods == $numMethods ? 1 : 0, 'numMethods' => $numMethods, 'numTestedMethods' => $numTestedMethods, 'linesExecutedPercent' => PHP_CodeCoverage_Util::percent( $item['executedLines'], $item['executableLines'], false ), 'linesExecutedPercentAsString' => PHP_CodeCoverage_Util::percent( $item['executedLines'], $item['executableLines'], true ), 'numExecutedLines' => $item['executedLines'], 'numExecutableLines' => $item['executableLines'], 'testedMethodsPercent' => PHP_CodeCoverage_Util::percent( $numTestedMethods, $numMethods, false ), 'testedMethodsPercentAsString' => PHP_CodeCoverage_Util::percent( $numTestedMethods, $numMethods, true ), 'testedClassesPercent' => PHP_CodeCoverage_Util::percent( $numTestedMethods == $numMethods ? 1 : 0, 1, false ), 'testedClassesPercentAsString' => PHP_CodeCoverage_Util::percent( $numTestedMethods == $numMethods ? 1 : 0, 1, true ), 'crap' => $item['crap'] ) ); foreach ($item['methods'] as $method) { $buffer .= $this->renderFunctionOrMethodItem( $methodItemTemplate, $method, ' ' ); } } return $buffer; } /** * @param array $functions * @param Text_Template $template * @return string */ protected function renderFunctionItems(array $functions, Text_Template $template) { if (empty($functions)) { return ''; } $buffer = ''; foreach ($functions as $function) { $buffer .= $this->renderFunctionOrMethodItem( $template, $function ); } return $buffer; } /** * @param Text_Template $template * @return string */ protected function renderFunctionOrMethodItem(Text_Template $template, array $item, $indent = '') { $numTestedItems = $item['executedLines'] == $item['executableLines'] ? 1 : 0; return $this->renderItemTemplate( $template, array( 'name' => sprintf( '%s%s', $indent, $item['startLine'], htmlspecialchars($item['signature']), isset($item['functionName']) ? $item['functionName'] : $item['methodName'] ), 'numMethods' => 1, 'numTestedMethods' => $numTestedItems, 'linesExecutedPercent' => PHP_CodeCoverage_Util::percent( $item['executedLines'], $item['executableLines'], false ), 'linesExecutedPercentAsString' => PHP_CodeCoverage_Util::percent( $item['executedLines'], $item['executableLines'], true ), 'numExecutedLines' => $item['executedLines'], 'numExecutableLines' => $item['executableLines'], 'testedMethodsPercent' => PHP_CodeCoverage_Util::percent( $numTestedItems, 1, false ), 'testedMethodsPercentAsString' => PHP_CodeCoverage_Util::percent( $numTestedItems, 1, true ), 'crap' => $item['crap'] ) ); } /** * @param PHP_CodeCoverage_Report_Node_File $node * @return string */ protected function renderSource(PHP_CodeCoverage_Report_Node_File $node) { $coverageData = $node->getCoverageData(); $testData = $node->getTestData(); $codeLines = $this->loadFile($node->getPath()); $lines = ''; $i = 1; foreach ($codeLines as $line) { $trClass = ''; $popoverContent = ''; $popoverTitle = ''; if (array_key_exists($i, $coverageData)) { $numTests = count($coverageData[$i]); if ($coverageData[$i] === null) { $trClass = ' class="warning"'; } elseif ($numTests == 0) { $trClass = ' class="danger"'; } else { $lineCss = 'covered-by-large-tests'; $popoverContent = '
      '; if ($numTests > 1) { $popoverTitle = $numTests . ' tests cover line ' . $i; } else { $popoverTitle = '1 test covers line ' . $i; } foreach ($coverageData[$i] as $test) { if ($lineCss == 'covered-by-large-tests' && $testData[$test]['size'] == 'medium') { $lineCss = 'covered-by-medium-tests'; } elseif ($testData[$test]['size'] == 'small') { $lineCss = 'covered-by-small-tests'; } switch ($testData[$test]['status']) { case 0: switch ($testData[$test]['size']) { case 'small': $testCSS = ' class="covered-by-small-tests"'; break; case 'medium': $testCSS = ' class="covered-by-medium-tests"'; break; default: $testCSS = ' class="covered-by-large-tests"'; break; } break; case 1: case 2: $testCSS = ' class="warning"'; break; case 3: $testCSS = ' class="danger"'; break; case 4: $testCSS = ' class="danger"'; break; default: $testCSS = ''; } $popoverContent .= sprintf( '%s', $testCSS, htmlspecialchars($test) ); } $popoverContent .= '
    '; $trClass = ' class="' . $lineCss . ' popin"'; } } if (!empty($popoverTitle)) { $popover = sprintf( ' data-title="%s" data-content="%s" data-placement="bottom" data-html="true"', $popoverTitle, htmlspecialchars($popoverContent) ); } else { $popover = ''; } $lines .= sprintf( ' %s' . "\n", $trClass, $popover, $i, $i, $i, $line ); $i++; } return $lines; } /** * @param string $file * @return array */ protected function loadFile($file) { $buffer = file_get_contents($file); $tokens = token_get_all($buffer); $result = array(''); $i = 0; $stringFlag = false; $fileEndsWithNewLine = substr($buffer, -1) == "\n"; unset($buffer); foreach ($tokens as $j => $token) { if (is_string($token)) { if ($token === '"' && $tokens[$j - 1] !== '\\') { $result[$i] .= sprintf( '%s', htmlspecialchars($token) ); $stringFlag = !$stringFlag; } else { $result[$i] .= sprintf( '%s', htmlspecialchars($token) ); } continue; } list($token, $value) = $token; $value = str_replace( array("\t", ' '), array('    ', ' '), htmlspecialchars($value, $this->htmlspecialcharsFlags) ); if ($value === "\n") { $result[++$i] = ''; } else { $lines = explode("\n", $value); foreach ($lines as $jj => $line) { $line = trim($line); if ($line !== '') { if ($stringFlag) { $colour = 'string'; } else { switch ($token) { case T_INLINE_HTML: $colour = 'html'; break; case T_COMMENT: case T_DOC_COMMENT: $colour = 'comment'; break; case T_ABSTRACT: case T_ARRAY: case T_AS: case T_BREAK: case T_CALLABLE: case T_CASE: case T_CATCH: case T_CLASS: case T_CLONE: case T_CONTINUE: case T_DEFAULT: case T_ECHO: case T_ELSE: case T_ELSEIF: case T_EMPTY: case T_ENDDECLARE: case T_ENDFOR: case T_ENDFOREACH: case T_ENDIF: case T_ENDSWITCH: case T_ENDWHILE: case T_EXIT: case T_EXTENDS: case T_FINAL: case T_FINALLY: case T_FOREACH: case T_FUNCTION: case T_GLOBAL: case T_IF: case T_IMPLEMENTS: case T_INCLUDE: case T_INCLUDE_ONCE: case T_INSTANCEOF: case T_INSTEADOF: case T_INTERFACE: case T_ISSET: case T_LOGICAL_AND: case T_LOGICAL_OR: case T_LOGICAL_XOR: case T_NAMESPACE: case T_NEW: case T_PRIVATE: case T_PROTECTED: case T_PUBLIC: case T_REQUIRE: case T_REQUIRE_ONCE: case T_RETURN: case T_STATIC: case T_THROW: case T_TRAIT: case T_TRY: case T_UNSET: case T_USE: case T_VAR: case T_WHILE: case T_YIELD: $colour = 'keyword'; break; default: $colour = 'default'; } } $result[$i] .= sprintf( '%s', $colour, $line ); } if (isset($lines[$jj + 1])) { $result[++$i] = ''; } } } } if ($fileEndsWithNewLine) { unset($result[count($result)-1]); } return $result; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Renders a PHP_CodeCoverage_Report_Node_Directory node. * * @since Class available since Release 1.1.0 */ class PHP_CodeCoverage_Report_HTML_Renderer_Directory extends PHP_CodeCoverage_Report_HTML_Renderer { /** * @param PHP_CodeCoverage_Report_Node_Directory $node * @param string $file */ public function render(PHP_CodeCoverage_Report_Node_Directory $node, $file) { $template = new Text_Template($this->templatePath . 'directory.html', '{{', '}}'); $this->setCommonTemplateVariables($template, $node); $items = $this->renderItem($node, true); foreach ($node->getDirectories() as $item) { $items .= $this->renderItem($item); } foreach ($node->getFiles() as $item) { $items .= $this->renderItem($item); } $template->setVar( array( 'id' => $node->getId(), 'items' => $items ) ); $template->renderTo($file); } /** * @param PHP_CodeCoverage_Report_Node $item * @param bool $total * @return string */ protected function renderItem(PHP_CodeCoverage_Report_Node $item, $total = false) { $data = array( 'numClasses' => $item->getNumClassesAndTraits(), 'numTestedClasses' => $item->getNumTestedClassesAndTraits(), 'numMethods' => $item->getNumMethods(), 'numTestedMethods' => $item->getNumTestedMethods(), 'linesExecutedPercent' => $item->getLineExecutedPercent(false), 'linesExecutedPercentAsString' => $item->getLineExecutedPercent(), 'numExecutedLines' => $item->getNumExecutedLines(), 'numExecutableLines' => $item->getNumExecutableLines(), 'testedMethodsPercent' => $item->getTestedMethodsPercent(false), 'testedMethodsPercentAsString' => $item->getTestedMethodsPercent(), 'testedClassesPercent' => $item->getTestedClassesAndTraitsPercent(false), 'testedClassesPercentAsString' => $item->getTestedClassesAndTraitsPercent() ); if ($total) { $data['name'] = 'Total'; } else { if ($item instanceof PHP_CodeCoverage_Report_Node_Directory) { $data['name'] = sprintf( '%s', $item->getName(), $item->getName() ); $data['icon'] = ' '; } else { $data['name'] = sprintf( '%s', $item->getName(), $item->getName() ); $data['icon'] = ' '; } } return $this->renderItemTemplate( new Text_Template($this->templatePath . 'directory_item.html', '{{', '}}'), $data ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 2.0.0 */ class PHP_CodeCoverage_Report_Crap4j { /** * @var int */ private $threshold; /** * @param int $threshold */ public function __construct($threshold = 30) { if (!is_int($threshold)) { throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 1, 'integer' ); } $this->threshold = $threshold; } /** * @param PHP_CodeCoverage $coverage * @param string $target * @param string $name * @return string */ public function process(PHP_CodeCoverage $coverage, $target = null, $name = null) { $document = new DOMDocument('1.0', 'UTF-8'); $document->formatOutput = true; $root = $document->createElement('crap_result'); $document->appendChild($root); $project = $document->createElement('project', is_string($name) ? $name : ''); $root->appendChild($project); $root->appendChild($document->createElement('timestamp', date('Y-m-d H:i:s', (int) $_SERVER['REQUEST_TIME']))); $stats = $document->createElement('stats'); $methodsNode = $document->createElement('methods'); $report = $coverage->getReport(); unset($coverage); $fullMethodCount = 0; $fullCrapMethodCount = 0; $fullCrapLoad = 0; $fullCrap = 0; foreach ($report as $item) { $namespace = 'global'; if (!$item instanceof PHP_CodeCoverage_Report_Node_File) { continue; } $file = $document->createElement('file'); $file->setAttribute('name', $item->getPath()); $classes = $item->getClassesAndTraits(); foreach ($classes as $className => $class) { foreach ($class['methods'] as $methodName => $method) { $crapLoad = $this->getCrapLoad($method['crap'], $method['ccn'], $method['coverage']); $fullCrap += $method['crap']; $fullCrapLoad += $crapLoad; $fullMethodCount++; if ($method['crap'] >= $this->threshold) { $fullCrapMethodCount++; } $methodNode = $document->createElement('method'); if (!empty($class['package']['namespace'])) { $namespace = $class['package']['namespace']; } $methodNode->appendChild($document->createElement('package', $namespace)); $methodNode->appendChild($document->createElement('className', $className)); $methodNode->appendChild($document->createElement('methodName', $methodName)); $methodNode->appendChild($document->createElement('methodSignature', htmlspecialchars($method['signature']))); $methodNode->appendChild($document->createElement('fullMethod', htmlspecialchars($method['signature']))); $methodNode->appendChild($document->createElement('crap', $this->roundValue($method['crap']))); $methodNode->appendChild($document->createElement('complexity', $method['ccn'])); $methodNode->appendChild($document->createElement('coverage', $this->roundValue($method['coverage']))); $methodNode->appendChild($document->createElement('crapLoad', round($crapLoad))); $methodsNode->appendChild($methodNode); } } } $stats->appendChild($document->createElement('name', 'Method Crap Stats')); $stats->appendChild($document->createElement('methodCount', $fullMethodCount)); $stats->appendChild($document->createElement('crapMethodCount', $fullCrapMethodCount)); $stats->appendChild($document->createElement('crapLoad', round($fullCrapLoad))); $stats->appendChild($document->createElement('totalCrap', $fullCrap)); if ($fullMethodCount > 0) { $crapMethodPercent = $this->roundValue((100 * $fullCrapMethodCount) / $fullMethodCount); } else { $crapMethodPercent = 0; } $stats->appendChild($document->createElement('crapMethodPercent', $crapMethodPercent)); $root->appendChild($stats); $root->appendChild($methodsNode); if ($target !== null) { if (!is_dir(dirname($target))) { mkdir(dirname($target), 0777, true); } return $document->save($target); } else { return $document->saveXML(); } } /** * @param float $crapValue * @param int $cyclomaticComplexity * @param float $coveragePercent * @return float */ private function getCrapLoad($crapValue, $cyclomaticComplexity, $coveragePercent) { $crapLoad = 0; if ($crapValue >= $this->threshold) { $crapLoad += $cyclomaticComplexity * (1.0 - $coveragePercent / 100); $crapLoad += $cyclomaticComplexity / $this->threshold; } return $crapLoad; } /** * @param float $value * @return float */ private function roundValue($value) { return round($value, 2); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 2.0.0 */ class PHP_CodeCoverage_Report_XML_Tests { private $contextNode; private $codeMap = array( 0 => 'PASSED', // PHPUnit_Runner_BaseTestRunner::STATUS_PASSED 1 => 'SKIPPED', // PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED 2 => 'INCOMPLETE', // PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE 3 => 'FAILURE', // PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE 4 => 'ERROR', // PHPUnit_Runner_BaseTestRunner::STATUS_ERROR 5 => 'RISKY' // PHPUnit_Runner_BaseTestRunner::STATUS_RISKY ); public function __construct(DOMElement $context) { $this->contextNode = $context; } public function addTest($test, array $result) { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'test' ) ); $node->setAttribute('name', $test); $node->setAttribute('size', $result['size']); $node->setAttribute('result', (int) $result['status']); $node->setAttribute('status', $this->codeMap[(int) $result['status']]); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 2.0.0 */ class PHP_CodeCoverage_Report_XML_File { /** * @var DOMDocument */ protected $dom; /** * @var DOMElement */ protected $contextNode; public function __construct(DOMElement $context) { $this->dom = $context->ownerDocument; $this->contextNode = $context; } public function getTotals() { $totalsContainer = $this->contextNode->firstChild; if (!$totalsContainer) { $totalsContainer = $this->contextNode->appendChild( $this->dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'totals' ) ); } return new PHP_CodeCoverage_Report_XML_Totals($totalsContainer); } public function getLineCoverage($line) { $coverage = $this->contextNode->getElementsByTagNameNS( 'http://schema.phpunit.de/coverage/1.0', 'coverage' )->item(0); if (!$coverage) { $coverage = $this->contextNode->appendChild( $this->dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'coverage' ) ); } $lineNode = $coverage->appendChild( $this->dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'line' ) ); return new PHP_CodeCoverage_Report_XML_File_Coverage($lineNode, $line); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 2.0.0 */ class PHP_CodeCoverage_Report_XML_Node { /** * @var DOMDocument */ private $dom; /** * @var DOMElement */ private $contextNode; public function __construct(DOMElement $context) { $this->setContextNode($context); } protected function setContextNode(DOMElement $context) { $this->dom = $context->ownerDocument; $this->contextNode = $context; } public function getDom() { return $this->dom; } protected function getContextNode() { return $this->contextNode; } public function getTotals() { $totalsContainer = $this->getContextNode()->firstChild; if (!$totalsContainer) { $totalsContainer = $this->getContextNode()->appendChild( $this->dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'totals' ) ); } return new PHP_CodeCoverage_Report_XML_Totals($totalsContainer); } public function addDirectory($name) { $dirNode = $this->getDom()->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'directory' ); $dirNode->setAttribute('name', $name); $this->getContextNode()->appendChild($dirNode); return new PHP_CodeCoverage_Report_XML_Directory($dirNode); } public function addFile($name, $href) { $fileNode = $this->getDom()->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'file' ); $fileNode->setAttribute('name', $name); $fileNode->setAttribute('href', $href); $this->getContextNode()->appendChild($fileNode); return new PHP_CodeCoverage_Report_XML_File($fileNode); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 2.0.0 */ class PHP_CodeCoverage_Report_XML_Project extends PHP_CodeCoverage_Report_XML_Node { public function __construct($name) { $this->init(); $this->setProjectName($name); } private function init() { $dom = new DOMDocument; $dom->loadXML(''); $this->setContextNode( $dom->getElementsByTagNameNS( 'http://schema.phpunit.de/coverage/1.0', 'project' )->item(0) ); } private function setProjectName($name) { $this->getContextNode()->setAttribute('name', $name); } public function getTests() { $testsNode = $this->getContextNode()->getElementsByTagNameNS( 'http://schema.phpunit.de/coverage/1.0', 'tests' )->item(0); if (!$testsNode) { $testsNode = $this->getContextNode()->appendChild( $this->getDom()->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'tests' ) ); } return new PHP_CodeCoverage_Report_XML_Tests($testsNode); } public function asDom() { return $this->getDom(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 2.0.0 */ class PHP_CodeCoverage_Report_XML_Totals { /** * @var DOMNode */ private $container; /** * @var DOMElement */ private $linesNode; /** * @var DOMElement */ private $methodsNode; /** * @var DOMElement */ private $functionsNode; /** * @var DOMElement */ private $classesNode; /** * @var DOMElement */ private $traitsNode; public function __construct(DOMElement $container) { $this->container = $container; $dom = $container->ownerDocument; $this->linesNode = $dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'lines' ); $this->methodsNode = $dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'methods' ); $this->functionsNode = $dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'functions' ); $this->classesNode = $dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'classes' ); $this->traitsNode = $dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'traits' ); $container->appendChild($this->linesNode); $container->appendChild($this->methodsNode); $container->appendChild($this->functionsNode); $container->appendChild($this->classesNode); $container->appendChild($this->traitsNode); } public function getContainer() { return $this->container; } public function setNumLines($loc, $cloc, $ncloc, $executable, $executed) { $this->linesNode->setAttribute('total', $loc); $this->linesNode->setAttribute('comments', $cloc); $this->linesNode->setAttribute('code', $ncloc); $this->linesNode->setAttribute('executable', $executable); $this->linesNode->setAttribute('executed', $executed); $this->linesNode->setAttribute( 'percent', PHP_CodeCoverage_Util::percent($executed, $executable, true) ); } public function setNumClasses($count, $tested) { $this->classesNode->setAttribute('count', $count); $this->classesNode->setAttribute('tested', $tested); $this->classesNode->setAttribute( 'percent', PHP_CodeCoverage_Util::percent($tested, $count, true) ); } public function setNumTraits($count, $tested) { $this->traitsNode->setAttribute('count', $count); $this->traitsNode->setAttribute('tested', $tested); $this->traitsNode->setAttribute( 'percent', PHP_CodeCoverage_Util::percent($tested, $count, true) ); } public function setNumMethods($count, $tested) { $this->methodsNode->setAttribute('count', $count); $this->methodsNode->setAttribute('tested', $tested); $this->methodsNode->setAttribute( 'percent', PHP_CodeCoverage_Util::percent($tested, $count, true) ); } public function setNumFunctions($count, $tested) { $this->functionsNode->setAttribute('count', $count); $this->functionsNode->setAttribute('tested', $tested); $this->functionsNode->setAttribute( 'percent', PHP_CodeCoverage_Util::percent($tested, $count, true) ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 2.0.0 */ class PHP_CodeCoverage_Report_XML_Directory extends PHP_CodeCoverage_Report_XML_Node { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 2.0.0 */ class PHP_CodeCoverage_Report_XML_File_Method { /** * @var DOMElement */ private $contextNode; public function __construct(DOMElement $context, $name) { $this->contextNode = $context; $this->setName($name); } private function setName($name) { $this->contextNode->setAttribute('name', $name); } public function setSignature($signature) { $this->contextNode->setAttribute('signature', $signature); } public function setLines($start, $end = null) { $this->contextNode->setAttribute('start', $start); if ($end !== null) { $this->contextNode->setAttribute('end', $end); } } public function setTotals($executable, $executed, $coverage) { $this->contextNode->setAttribute('executable', $executable); $this->contextNode->setAttribute('executed', $executed); $this->contextNode->setAttribute('coverage', $coverage); } public function setCrap($crap) { $this->contextNode->setAttribute('crap', $crap); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 2.0.0 */ class PHP_CodeCoverage_Report_XML_File_Report extends PHP_CodeCoverage_Report_XML_File { public function __construct($name) { $this->dom = new DOMDocument; $this->dom->loadXML(''); $this->contextNode = $this->dom->getElementsByTagNameNS( 'http://schema.phpunit.de/coverage/1.0', 'file' )->item(0); $this->setName($name); } private function setName($name) { $this->contextNode->setAttribute('name', $name); } public function asDom() { return $this->dom; } public function getFunctionObject($name) { $node = $this->contextNode->appendChild( $this->dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'function' ) ); return new PHP_CodeCoverage_Report_XML_File_Method($node, $name); } public function getClassObject($name) { return $this->getUnitObject('class', $name); } public function getTraitObject($name) { return $this->getUnitObject('trait', $name); } private function getUnitObject($tagName, $name) { $node = $this->contextNode->appendChild( $this->dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', $tagName ) ); return new PHP_CodeCoverage_Report_XML_File_Unit($node, $name); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 2.0.0 */ class PHP_CodeCoverage_Report_XML_File_Coverage { /** * @var XMLWriter */ private $writer; /** * @var DOMElement */ private $contextNode; /** * @var bool */ private $finalized = false; public function __construct(DOMElement $context, $line) { $this->contextNode = $context; $this->writer = new XMLWriter(); $this->writer->openMemory(); $this->writer->startElementNs(null, $context->nodeName, 'http://schema.phpunit.de/coverage/1.0'); $this->writer->writeAttribute('nr', $line); } public function addTest($test) { if ($this->finalized) { throw new PHP_CodeCoverage_Exception('Coverage Report already finalized'); } $this->writer->startElement('covered'); $this->writer->writeAttribute('by', $test); $this->writer->endElement(); } public function finalize() { $this->writer->endElement(); $fragment = $this->contextNode->ownerDocument->createDocumentFragment(); $fragment->appendXML($this->writer->outputMemory()); $this->contextNode->parentNode->replaceChild( $fragment, $this->contextNode ); $this->finalized = true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 2.0.0 */ class PHP_CodeCoverage_Report_XML_File_Unit { /** * @var DOMElement */ private $contextNode; public function __construct(DOMElement $context, $name) { $this->contextNode = $context; $this->setName($name); } private function setName($name) { $this->contextNode->setAttribute('name', $name); } public function setLines($start, $executable, $executed) { $this->contextNode->setAttribute('start', $start); $this->contextNode->setAttribute('executable', $executable); $this->contextNode->setAttribute('executed', $executed); } public function setCrap($crap) { $this->contextNode->setAttribute('crap', $crap); } public function setPackage($full, $package, $sub, $category) { $node = $this->contextNode->getElementsByTagNameNS( 'http://schema.phpunit.de/coverage/1.0', 'package' )->item(0); if (!$node) { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'package' ) ); } $node->setAttribute('full', $full); $node->setAttribute('name', $package); $node->setAttribute('sub', $sub); $node->setAttribute('category', $category); } public function setNamespace($namespace) { $node = $this->contextNode->getElementsByTagNameNS( 'http://schema.phpunit.de/coverage/1.0', 'namespace' )->item(0); if (!$node) { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'namespace' ) ); } $node->setAttribute('name', $namespace); } public function addMethod($name) { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'method' ) ); return new PHP_CodeCoverage_Report_XML_File_Method($node, $name); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Base class for nodes in the code coverage information tree. * * @since Class available since Release 1.1.0 */ abstract class PHP_CodeCoverage_Report_Node implements Countable { /** * @var string */ protected $name; /** * @var string */ protected $path; /** * @var array */ protected $pathArray; /** * @var PHP_CodeCoverage_Report_Node */ protected $parent; /** * @var string */ protected $id; /** * Constructor. * * @param string $name * @param PHP_CodeCoverage_Report_Node $parent */ public function __construct($name, PHP_CodeCoverage_Report_Node $parent = null) { if (substr($name, -1) == '/') { $name = substr($name, 0, -1); } $this->name = $name; $this->parent = $parent; } /** * @return string */ public function getName() { return $this->name; } /** * @return string */ public function getId() { if ($this->id === null) { $parent = $this->getParent(); if ($parent === null) { $this->id = 'index'; } else { $parentId = $parent->getId(); if ($parentId == 'index') { $this->id = str_replace(':', '_', $this->name); } else { $this->id = $parentId . '/' . $this->name; } } } return $this->id; } /** * @return string */ public function getPath() { if ($this->path === null) { if ($this->parent === null || $this->parent->getPath() === null || $this->parent->getPath() === false) { $this->path = $this->name; } else { $this->path = $this->parent->getPath() . '/' . $this->name; } } return $this->path; } /** * @return array */ public function getPathAsArray() { if ($this->pathArray === null) { if ($this->parent === null) { $this->pathArray = array(); } else { $this->pathArray = $this->parent->getPathAsArray(); } $this->pathArray[] = $this; } return $this->pathArray; } /** * @return PHP_CodeCoverage_Report_Node */ public function getParent() { return $this->parent; } /** * Returns the percentage of classes that has been tested. * * @param bool $asString * @return int */ public function getTestedClassesPercent($asString = true) { return PHP_CodeCoverage_Util::percent( $this->getNumTestedClasses(), $this->getNumClasses(), $asString ); } /** * Returns the percentage of traits that has been tested. * * @param bool $asString * @return int */ public function getTestedTraitsPercent($asString = true) { return PHP_CodeCoverage_Util::percent( $this->getNumTestedTraits(), $this->getNumTraits(), $asString ); } /** * Returns the percentage of traits that has been tested. * * @param bool $asString * @return int * @since Method available since Release 1.2.0 */ public function getTestedClassesAndTraitsPercent($asString = true) { return PHP_CodeCoverage_Util::percent( $this->getNumTestedClassesAndTraits(), $this->getNumClassesAndTraits(), $asString ); } /** * Returns the percentage of methods that has been tested. * * @param bool $asString * @return int */ public function getTestedMethodsPercent($asString = true) { return PHP_CodeCoverage_Util::percent( $this->getNumTestedMethods(), $this->getNumMethods(), $asString ); } /** * Returns the percentage of executed lines. * * @param bool $asString * @return int */ public function getLineExecutedPercent($asString = true) { return PHP_CodeCoverage_Util::percent( $this->getNumExecutedLines(), $this->getNumExecutableLines(), $asString ); } /** * Returns the number of classes and traits. * * @return int * @since Method available since Release 1.2.0 */ public function getNumClassesAndTraits() { return $this->getNumClasses() + $this->getNumTraits(); } /** * Returns the number of tested classes and traits. * * @return int * @since Method available since Release 1.2.0 */ public function getNumTestedClassesAndTraits() { return $this->getNumTestedClasses() + $this->getNumTestedTraits(); } /** * Returns the classes and traits of this node. * * @return array * @since Method available since Release 1.2.0 */ public function getClassesAndTraits() { return array_merge($this->getClasses(), $this->getTraits()); } /** * Returns the classes of this node. * * @return array */ abstract public function getClasses(); /** * Returns the traits of this node. * * @return array */ abstract public function getTraits(); /** * Returns the functions of this node. * * @return array */ abstract public function getFunctions(); /** * Returns the LOC/CLOC/NCLOC of this node. * * @return array */ abstract public function getLinesOfCode(); /** * Returns the number of executable lines. * * @return int */ abstract public function getNumExecutableLines(); /** * Returns the number of executed lines. * * @return int */ abstract public function getNumExecutedLines(); /** * Returns the number of classes. * * @return int */ abstract public function getNumClasses(); /** * Returns the number of tested classes. * * @return int */ abstract public function getNumTestedClasses(); /** * Returns the number of traits. * * @return int */ abstract public function getNumTraits(); /** * Returns the number of tested traits. * * @return int */ abstract public function getNumTestedTraits(); /** * Returns the number of methods. * * @return int */ abstract public function getNumMethods(); /** * Returns the number of tested methods. * * @return int */ abstract public function getNumTestedMethods(); /** * Returns the number of functions. * * @return int */ abstract public function getNumFunctions(); /** * Returns the number of tested functions. * * @return int */ abstract public function getNumTestedFunctions(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Recursive iterator for PHP_CodeCoverage_Report_Node object graphs. * * @since Class available since Release 1.1.0 */ class PHP_CodeCoverage_Report_Node_Iterator implements RecursiveIterator { /** * @var int */ protected $position; /** * @var PHP_CodeCoverage_Report_Node[] */ protected $nodes; /** * Constructor. * * @param PHP_CodeCoverage_Report_Node_Directory $node */ public function __construct(PHP_CodeCoverage_Report_Node_Directory $node) { $this->nodes = $node->getChildNodes(); } /** * Rewinds the Iterator to the first element. */ public function rewind() { $this->position = 0; } /** * Checks if there is a current element after calls to rewind() or next(). * * @return bool */ public function valid() { return $this->position < count($this->nodes); } /** * Returns the key of the current element. * * @return int */ public function key() { return $this->position; } /** * Returns the current element. * * @return PHPUnit_Framework_Test */ public function current() { return $this->valid() ? $this->nodes[$this->position] : null; } /** * Moves forward to next element. */ public function next() { $this->position++; } /** * Returns the sub iterator for the current element. * * @return PHP_CodeCoverage_Report_Node_Iterator */ public function getChildren() { return new self( $this->nodes[$this->position] ); } /** * Checks whether the current element has children. * * @return bool */ public function hasChildren() { return $this->nodes[$this->position] instanceof PHP_CodeCoverage_Report_Node_Directory; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Represents a file in the code coverage information tree. * * @since Class available since Release 1.1.0 */ class PHP_CodeCoverage_Report_Node_File extends PHP_CodeCoverage_Report_Node { /** * @var array */ protected $coverageData; /** * @var array */ protected $testData; /** * @var int */ protected $numExecutableLines = 0; /** * @var int */ protected $numExecutedLines = 0; /** * @var array */ protected $classes = array(); /** * @var array */ protected $traits = array(); /** * @var array */ protected $functions = array(); /** * @var array */ protected $linesOfCode = array(); /** * @var int */ protected $numTestedTraits = 0; /** * @var int */ protected $numTestedClasses = 0; /** * @var int */ protected $numMethods = null; /** * @var int */ protected $numTestedMethods = null; /** * @var int */ protected $numTestedFunctions = null; /** * @var array */ protected $startLines = array(); /** * @var array */ protected $endLines = array(); /** * @var bool */ protected $cacheTokens; /** * Constructor. * * @param string $name * @param PHP_CodeCoverage_Report_Node $parent * @param array $coverageData * @param array $testData * @param bool $cacheTokens * @throws PHP_CodeCoverage_Exception */ public function __construct($name, PHP_CodeCoverage_Report_Node $parent, array $coverageData, array $testData, $cacheTokens) { if (!is_bool($cacheTokens)) { throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 1, 'boolean' ); } parent::__construct($name, $parent); $this->coverageData = $coverageData; $this->testData = $testData; $this->cacheTokens = $cacheTokens; $this->calculateStatistics(); } /** * Returns the number of files in/under this node. * * @return int */ public function count() { return 1; } /** * Returns the code coverage data of this node. * * @return array */ public function getCoverageData() { return $this->coverageData; } /** * Returns the test data of this node. * * @return array */ public function getTestData() { return $this->testData; } /** * Returns the classes of this node. * * @return array */ public function getClasses() { return $this->classes; } /** * Returns the traits of this node. * * @return array */ public function getTraits() { return $this->traits; } /** * Returns the functions of this node. * * @return array */ public function getFunctions() { return $this->functions; } /** * Returns the LOC/CLOC/NCLOC of this node. * * @return array */ public function getLinesOfCode() { return $this->linesOfCode; } /** * Returns the number of executable lines. * * @return int */ public function getNumExecutableLines() { return $this->numExecutableLines; } /** * Returns the number of executed lines. * * @return int */ public function getNumExecutedLines() { return $this->numExecutedLines; } /** * Returns the number of classes. * * @return int */ public function getNumClasses() { return count($this->classes); } /** * Returns the number of tested classes. * * @return int */ public function getNumTestedClasses() { return $this->numTestedClasses; } /** * Returns the number of traits. * * @return int */ public function getNumTraits() { return count($this->traits); } /** * Returns the number of tested traits. * * @return int */ public function getNumTestedTraits() { return $this->numTestedTraits; } /** * Returns the number of methods. * * @return int */ public function getNumMethods() { if ($this->numMethods === null) { $this->numMethods = 0; foreach ($this->classes as $class) { foreach ($class['methods'] as $method) { if ($method['executableLines'] > 0) { $this->numMethods++; } } } foreach ($this->traits as $trait) { foreach ($trait['methods'] as $method) { if ($method['executableLines'] > 0) { $this->numMethods++; } } } } return $this->numMethods; } /** * Returns the number of tested methods. * * @return int */ public function getNumTestedMethods() { if ($this->numTestedMethods === null) { $this->numTestedMethods = 0; foreach ($this->classes as $class) { foreach ($class['methods'] as $method) { if ($method['executableLines'] > 0 && $method['coverage'] == 100) { $this->numTestedMethods++; } } } foreach ($this->traits as $trait) { foreach ($trait['methods'] as $method) { if ($method['executableLines'] > 0 && $method['coverage'] == 100) { $this->numTestedMethods++; } } } } return $this->numTestedMethods; } /** * Returns the number of functions. * * @return int */ public function getNumFunctions() { return count($this->functions); } /** * Returns the number of tested functions. * * @return int */ public function getNumTestedFunctions() { if ($this->numTestedFunctions === null) { $this->numTestedFunctions = 0; foreach ($this->functions as $function) { if ($function['executableLines'] > 0 && $function['coverage'] == 100) { $this->numTestedFunctions++; } } } return $this->numTestedFunctions; } /** * Calculates coverage statistics for the file. */ protected function calculateStatistics() { $classStack = $functionStack = array(); if ($this->cacheTokens) { $tokens = PHP_Token_Stream_CachingFactory::get($this->getPath()); } else { $tokens = new PHP_Token_Stream($this->getPath()); } $this->processClasses($tokens); $this->processTraits($tokens); $this->processFunctions($tokens); $this->linesOfCode = $tokens->getLinesOfCode(); unset($tokens); for ($lineNumber = 1; $lineNumber <= $this->linesOfCode['loc']; $lineNumber++) { if (isset($this->startLines[$lineNumber])) { // Start line of a class. if (isset($this->startLines[$lineNumber]['className'])) { if (isset($currentClass)) { $classStack[] = &$currentClass; } $currentClass = &$this->startLines[$lineNumber]; } // Start line of a trait. elseif (isset($this->startLines[$lineNumber]['traitName'])) { $currentTrait = &$this->startLines[$lineNumber]; } // Start line of a method. elseif (isset($this->startLines[$lineNumber]['methodName'])) { $currentMethod = &$this->startLines[$lineNumber]; } // Start line of a function. elseif (isset($this->startLines[$lineNumber]['functionName'])) { if (isset($currentFunction)) { $functionStack[] = &$currentFunction; } $currentFunction = &$this->startLines[$lineNumber]; } } if (isset($this->coverageData[$lineNumber])) { if (isset($currentClass)) { $currentClass['executableLines']++; } if (isset($currentTrait)) { $currentTrait['executableLines']++; } if (isset($currentMethod)) { $currentMethod['executableLines']++; } if (isset($currentFunction)) { $currentFunction['executableLines']++; } $this->numExecutableLines++; if (count($this->coverageData[$lineNumber]) > 0) { if (isset($currentClass)) { $currentClass['executedLines']++; } if (isset($currentTrait)) { $currentTrait['executedLines']++; } if (isset($currentMethod)) { $currentMethod['executedLines']++; } if (isset($currentFunction)) { $currentFunction['executedLines']++; } $this->numExecutedLines++; } } if (isset($this->endLines[$lineNumber])) { // End line of a class. if (isset($this->endLines[$lineNumber]['className'])) { unset($currentClass); if ($classStack) { end($classStack); $key = key($classStack); $currentClass = &$classStack[$key]; unset($classStack[$key]); } } // End line of a trait. elseif (isset($this->endLines[$lineNumber]['traitName'])) { unset($currentTrait); } // End line of a method. elseif (isset($this->endLines[$lineNumber]['methodName'])) { unset($currentMethod); } // End line of a function. elseif (isset($this->endLines[$lineNumber]['functionName'])) { unset($currentFunction); if ($functionStack) { end($functionStack); $key = key($functionStack); $currentFunction = &$functionStack[$key]; unset($functionStack[$key]); } } } } foreach ($this->traits as &$trait) { foreach ($trait['methods'] as &$method) { if ($method['executableLines'] > 0) { $method['coverage'] = ($method['executedLines'] / $method['executableLines']) * 100; } else { $method['coverage'] = 100; } $method['crap'] = $this->crap( $method['ccn'], $method['coverage'] ); $trait['ccn'] += $method['ccn']; } if ($trait['executableLines'] > 0) { $trait['coverage'] = ($trait['executedLines'] / $trait['executableLines']) * 100; } else { $trait['coverage'] = 100; } if ($trait['coverage'] == 100) { $this->numTestedClasses++; } $trait['crap'] = $this->crap( $trait['ccn'], $trait['coverage'] ); } foreach ($this->classes as &$class) { foreach ($class['methods'] as &$method) { if ($method['executableLines'] > 0) { $method['coverage'] = ($method['executedLines'] / $method['executableLines']) * 100; } else { $method['coverage'] = 100; } $method['crap'] = $this->crap( $method['ccn'], $method['coverage'] ); $class['ccn'] += $method['ccn']; } if ($class['executableLines'] > 0) { $class['coverage'] = ($class['executedLines'] / $class['executableLines']) * 100; } else { $class['coverage'] = 100; } if ($class['coverage'] == 100) { $this->numTestedClasses++; } $class['crap'] = $this->crap( $class['ccn'], $class['coverage'] ); } } /** * @param PHP_Token_Stream $tokens */ protected function processClasses(PHP_Token_Stream $tokens) { $classes = $tokens->getClasses(); unset($tokens); $link = $this->getId() . '.html#'; foreach ($classes as $className => $class) { $this->classes[$className] = array( 'className' => $className, 'methods' => array(), 'startLine' => $class['startLine'], 'executableLines' => 0, 'executedLines' => 0, 'ccn' => 0, 'coverage' => 0, 'crap' => 0, 'package' => $class['package'], 'link' => $link . $class['startLine'] ); $this->startLines[$class['startLine']] = &$this->classes[$className]; $this->endLines[$class['endLine']] = &$this->classes[$className]; foreach ($class['methods'] as $methodName => $method) { $this->classes[$className]['methods'][$methodName] = array( 'methodName' => $methodName, 'signature' => $method['signature'], 'startLine' => $method['startLine'], 'endLine' => $method['endLine'], 'executableLines' => 0, 'executedLines' => 0, 'ccn' => $method['ccn'], 'coverage' => 0, 'crap' => 0, 'link' => $link . $method['startLine'] ); $this->startLines[$method['startLine']] = &$this->classes[$className]['methods'][$methodName]; $this->endLines[$method['endLine']] = &$this->classes[$className]['methods'][$methodName]; } } } /** * @param PHP_Token_Stream $tokens */ protected function processTraits(PHP_Token_Stream $tokens) { $traits = $tokens->getTraits(); unset($tokens); $link = $this->getId() . '.html#'; foreach ($traits as $traitName => $trait) { $this->traits[$traitName] = array( 'traitName' => $traitName, 'methods' => array(), 'startLine' => $trait['startLine'], 'executableLines' => 0, 'executedLines' => 0, 'ccn' => 0, 'coverage' => 0, 'crap' => 0, 'package' => $trait['package'], 'link' => $link . $trait['startLine'] ); $this->startLines[$trait['startLine']] = &$this->traits[$traitName]; $this->endLines[$trait['endLine']] = &$this->traits[$traitName]; foreach ($trait['methods'] as $methodName => $method) { $this->traits[$traitName]['methods'][$methodName] = array( 'methodName' => $methodName, 'signature' => $method['signature'], 'startLine' => $method['startLine'], 'endLine' => $method['endLine'], 'executableLines' => 0, 'executedLines' => 0, 'ccn' => $method['ccn'], 'coverage' => 0, 'crap' => 0, 'link' => $link . $method['startLine'] ); $this->startLines[$method['startLine']] = &$this->traits[$traitName]['methods'][$methodName]; $this->endLines[$method['endLine']] = &$this->traits[$traitName]['methods'][$methodName]; } } } /** * @param PHP_Token_Stream $tokens */ protected function processFunctions(PHP_Token_Stream $tokens) { $functions = $tokens->getFunctions(); unset($tokens); $link = $this->getId() . '.html#'; foreach ($functions as $functionName => $function) { $this->functions[$functionName] = array( 'functionName' => $functionName, 'signature' => $function['signature'], 'startLine' => $function['startLine'], 'executableLines' => 0, 'executedLines' => 0, 'ccn' => $function['ccn'], 'coverage' => 0, 'crap' => 0, 'link' => $link . $function['startLine'] ); $this->startLines[$function['startLine']] = &$this->functions[$functionName]; $this->endLines[$function['endLine']] = &$this->functions[$functionName]; } } /** * Calculates the Change Risk Anti-Patterns (CRAP) index for a unit of code * based on its cyclomatic complexity and percentage of code coverage. * * @param int $ccn * @param float $coverage * @return string * @since Method available since Release 1.2.0 */ protected function crap($ccn, $coverage) { if ($coverage == 0) { return (string) (pow($ccn, 2) + $ccn); } if ($coverage >= 95) { return (string) $ccn; } return sprintf( '%01.2F', pow($ccn, 2) * pow(1 - $coverage/100, 3) + $ccn ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Represents a directory in the code coverage information tree. * * @since Class available since Release 1.1.0 */ class PHP_CodeCoverage_Report_Node_Directory extends PHP_CodeCoverage_Report_Node implements IteratorAggregate { /** * @var PHP_CodeCoverage_Report_Node[] */ protected $children = array(); /** * @var PHP_CodeCoverage_Report_Node_Directory[] */ protected $directories = array(); /** * @var PHP_CodeCoverage_Report_Node_File[] */ protected $files = array(); /** * @var array */ protected $classes; /** * @var array */ protected $traits; /** * @var array */ protected $functions; /** * @var array */ protected $linesOfCode = null; /** * @var int */ protected $numFiles = -1; /** * @var int */ protected $numExecutableLines = -1; /** * @var int */ protected $numExecutedLines = -1; /** * @var int */ protected $numClasses = -1; /** * @var int */ protected $numTestedClasses = -1; /** * @var int */ protected $numTraits = -1; /** * @var int */ protected $numTestedTraits = -1; /** * @var int */ protected $numMethods = -1; /** * @var int */ protected $numTestedMethods = -1; /** * @var int */ protected $numFunctions = -1; /** * @var int */ protected $numTestedFunctions = -1; /** * Returns the number of files in/under this node. * * @return int */ public function count() { if ($this->numFiles == -1) { $this->numFiles = 0; foreach ($this->children as $child) { $this->numFiles += count($child); } } return $this->numFiles; } /** * Returns an iterator for this node. * * @return RecursiveIteratorIterator */ public function getIterator() { return new RecursiveIteratorIterator( new PHP_CodeCoverage_Report_Node_Iterator($this), RecursiveIteratorIterator::SELF_FIRST ); } /** * Adds a new directory. * * @param string $name * @return PHP_CodeCoverage_Report_Node_Directory */ public function addDirectory($name) { $directory = new self($name, $this); $this->children[] = $directory; $this->directories[] = &$this->children[count($this->children) - 1]; return $directory; } /** * Adds a new file. * * @param string $name * @param array $coverageData * @param array $testData * @param bool $cacheTokens * @return PHP_CodeCoverage_Report_Node_File * @throws PHP_CodeCoverage_Exception */ public function addFile($name, array $coverageData, array $testData, $cacheTokens) { $file = new PHP_CodeCoverage_Report_Node_File( $name, $this, $coverageData, $testData, $cacheTokens ); $this->children[] = $file; $this->files[] = &$this->children[count($this->children) - 1]; $this->numExecutableLines = -1; $this->numExecutedLines = -1; return $file; } /** * Returns the directories in this directory. * * @return array */ public function getDirectories() { return $this->directories; } /** * Returns the files in this directory. * * @return array */ public function getFiles() { return $this->files; } /** * Returns the child nodes of this node. * * @return array */ public function getChildNodes() { return $this->children; } /** * Returns the classes of this node. * * @return array */ public function getClasses() { if ($this->classes === null) { $this->classes = array(); foreach ($this->children as $child) { $this->classes = array_merge( $this->classes, $child->getClasses() ); } } return $this->classes; } /** * Returns the traits of this node. * * @return array */ public function getTraits() { if ($this->traits === null) { $this->traits = array(); foreach ($this->children as $child) { $this->traits = array_merge( $this->traits, $child->getTraits() ); } } return $this->traits; } /** * Returns the functions of this node. * * @return array */ public function getFunctions() { if ($this->functions === null) { $this->functions = array(); foreach ($this->children as $child) { $this->functions = array_merge( $this->functions, $child->getFunctions() ); } } return $this->functions; } /** * Returns the LOC/CLOC/NCLOC of this node. * * @return array */ public function getLinesOfCode() { if ($this->linesOfCode === null) { $this->linesOfCode = array('loc' => 0, 'cloc' => 0, 'ncloc' => 0); foreach ($this->children as $child) { $linesOfCode = $child->getLinesOfCode(); $this->linesOfCode['loc'] += $linesOfCode['loc']; $this->linesOfCode['cloc'] += $linesOfCode['cloc']; $this->linesOfCode['ncloc'] += $linesOfCode['ncloc']; } } return $this->linesOfCode; } /** * Returns the number of executable lines. * * @return int */ public function getNumExecutableLines() { if ($this->numExecutableLines == -1) { $this->numExecutableLines = 0; foreach ($this->children as $child) { $this->numExecutableLines += $child->getNumExecutableLines(); } } return $this->numExecutableLines; } /** * Returns the number of executed lines. * * @return int */ public function getNumExecutedLines() { if ($this->numExecutedLines == -1) { $this->numExecutedLines = 0; foreach ($this->children as $child) { $this->numExecutedLines += $child->getNumExecutedLines(); } } return $this->numExecutedLines; } /** * Returns the number of classes. * * @return int */ public function getNumClasses() { if ($this->numClasses == -1) { $this->numClasses = 0; foreach ($this->children as $child) { $this->numClasses += $child->getNumClasses(); } } return $this->numClasses; } /** * Returns the number of tested classes. * * @return int */ public function getNumTestedClasses() { if ($this->numTestedClasses == -1) { $this->numTestedClasses = 0; foreach ($this->children as $child) { $this->numTestedClasses += $child->getNumTestedClasses(); } } return $this->numTestedClasses; } /** * Returns the number of traits. * * @return int */ public function getNumTraits() { if ($this->numTraits == -1) { $this->numTraits = 0; foreach ($this->children as $child) { $this->numTraits += $child->getNumTraits(); } } return $this->numTraits; } /** * Returns the number of tested traits. * * @return int */ public function getNumTestedTraits() { if ($this->numTestedTraits == -1) { $this->numTestedTraits = 0; foreach ($this->children as $child) { $this->numTestedTraits += $child->getNumTestedTraits(); } } return $this->numTestedTraits; } /** * Returns the number of methods. * * @return int */ public function getNumMethods() { if ($this->numMethods == -1) { $this->numMethods = 0; foreach ($this->children as $child) { $this->numMethods += $child->getNumMethods(); } } return $this->numMethods; } /** * Returns the number of tested methods. * * @return int */ public function getNumTestedMethods() { if ($this->numTestedMethods == -1) { $this->numTestedMethods = 0; foreach ($this->children as $child) { $this->numTestedMethods += $child->getNumTestedMethods(); } } return $this->numTestedMethods; } /** * Returns the number of functions. * * @return int */ public function getNumFunctions() { if ($this->numFunctions == -1) { $this->numFunctions = 0; foreach ($this->children as $child) { $this->numFunctions += $child->getNumFunctions(); } } return $this->numFunctions; } /** * Returns the number of tested functions. * * @return int */ public function getNumTestedFunctions() { if ($this->numTestedFunctions == -1) { $this->numTestedFunctions = 0; foreach ($this->children as $child) { $this->numTestedFunctions += $child->getNumTestedFunctions(); } } return $this->numTestedFunctions; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Generates a Clover XML logfile from an PHP_CodeCoverage object. * * @since Class available since Release 1.0.0 */ class PHP_CodeCoverage_Report_Clover { /** * @param PHP_CodeCoverage $coverage * @param string $target * @param string $name * @return string */ public function process(PHP_CodeCoverage $coverage, $target = null, $name = null) { $xmlDocument = new DOMDocument('1.0', 'UTF-8'); $xmlDocument->formatOutput = true; $xmlCoverage = $xmlDocument->createElement('coverage'); $xmlCoverage->setAttribute('generated', (int) $_SERVER['REQUEST_TIME']); $xmlDocument->appendChild($xmlCoverage); $xmlProject = $xmlDocument->createElement('project'); $xmlProject->setAttribute('timestamp', (int) $_SERVER['REQUEST_TIME']); if (is_string($name)) { $xmlProject->setAttribute('name', $name); } $xmlCoverage->appendChild($xmlProject); $packages = array(); $report = $coverage->getReport(); unset($coverage); foreach ($report as $item) { $namespace = 'global'; if (!$item instanceof PHP_CodeCoverage_Report_Node_File) { continue; } $xmlFile = $xmlDocument->createElement('file'); $xmlFile->setAttribute('name', $item->getPath()); $classes = $item->getClassesAndTraits(); $coverage = $item->getCoverageData(); $lines = array(); foreach ($classes as $className => $class) { $classStatements = 0; $coveredClassStatements = 0; $coveredMethods = 0; $classMethods = 0; foreach ($class['methods'] as $methodName => $method) { if ($method['executableLines'] == 0) { continue; } $classMethods++; $classStatements += $method['executableLines']; $coveredClassStatements += $method['executedLines']; if ($method['coverage'] == 100) { $coveredMethods++; } $methodCount = 0; for ($i = $method['startLine']; $i <= $method['endLine']; $i++) { if (isset($coverage[$i]) && ($coverage[$i] !== null)) { $methodCount = max($methodCount, count($coverage[$i])); } } $lines[$method['startLine']] = array( 'count' => $methodCount, 'crap' => $method['crap'], 'type' => 'method', 'name' => $methodName ); } if (!empty($class['package']['namespace'])) { $namespace = $class['package']['namespace']; } $xmlClass = $xmlDocument->createElement('class'); $xmlClass->setAttribute('name', $className); $xmlClass->setAttribute('namespace', $namespace); if (!empty($class['package']['fullPackage'])) { $xmlClass->setAttribute( 'fullPackage', $class['package']['fullPackage'] ); } if (!empty($class['package']['category'])) { $xmlClass->setAttribute( 'category', $class['package']['category'] ); } if (!empty($class['package']['package'])) { $xmlClass->setAttribute( 'package', $class['package']['package'] ); } if (!empty($class['package']['subpackage'])) { $xmlClass->setAttribute( 'subpackage', $class['package']['subpackage'] ); } $xmlFile->appendChild($xmlClass); $xmlMetrics = $xmlDocument->createElement('metrics'); $xmlMetrics->setAttribute('methods', $classMethods); $xmlMetrics->setAttribute('coveredmethods', $coveredMethods); $xmlMetrics->setAttribute('conditionals', 0); $xmlMetrics->setAttribute('coveredconditionals', 0); $xmlMetrics->setAttribute('statements', $classStatements); $xmlMetrics->setAttribute( 'coveredstatements', $coveredClassStatements ); $xmlMetrics->setAttribute( 'elements', $classMethods + $classStatements /* + conditionals */ ); $xmlMetrics->setAttribute( 'coveredelements', $coveredMethods + $coveredClassStatements /* + coveredconditionals */ ); $xmlClass->appendChild($xmlMetrics); } foreach ($coverage as $line => $data) { if ($data === null || isset($lines[$line])) { continue; } $lines[$line] = array( 'count' => count($data), 'type' => 'stmt' ); } ksort($lines); foreach ($lines as $line => $data) { $xmlLine = $xmlDocument->createElement('line'); $xmlLine->setAttribute('num', $line); $xmlLine->setAttribute('type', $data['type']); if (isset($data['name'])) { $xmlLine->setAttribute('name', $data['name']); } if (isset($data['crap'])) { $xmlLine->setAttribute('crap', $data['crap']); } $xmlLine->setAttribute('count', $data['count']); $xmlFile->appendChild($xmlLine); } $linesOfCode = $item->getLinesOfCode(); $xmlMetrics = $xmlDocument->createElement('metrics'); $xmlMetrics->setAttribute('loc', $linesOfCode['loc']); $xmlMetrics->setAttribute('ncloc', $linesOfCode['ncloc']); $xmlMetrics->setAttribute('classes', $item->getNumClassesAndTraits()); $xmlMetrics->setAttribute('methods', $item->getNumMethods()); $xmlMetrics->setAttribute( 'coveredmethods', $item->getNumTestedMethods() ); $xmlMetrics->setAttribute('conditionals', 0); $xmlMetrics->setAttribute('coveredconditionals', 0); $xmlMetrics->setAttribute( 'statements', $item->getNumExecutableLines() ); $xmlMetrics->setAttribute( 'coveredstatements', $item->getNumExecutedLines() ); $xmlMetrics->setAttribute( 'elements', $item->getNumMethods() + $item->getNumExecutableLines() /* + conditionals */ ); $xmlMetrics->setAttribute( 'coveredelements', $item->getNumTestedMethods() + $item->getNumExecutedLines() /* + coveredconditionals */ ); $xmlFile->appendChild($xmlMetrics); if ($namespace == 'global') { $xmlProject->appendChild($xmlFile); } else { if (!isset($packages[$namespace])) { $packages[$namespace] = $xmlDocument->createElement( 'package' ); $packages[$namespace]->setAttribute('name', $namespace); $xmlProject->appendChild($packages[$namespace]); } $packages[$namespace]->appendChild($xmlFile); } } $linesOfCode = $report->getLinesOfCode(); $xmlMetrics = $xmlDocument->createElement('metrics'); $xmlMetrics->setAttribute('files', count($report)); $xmlMetrics->setAttribute('loc', $linesOfCode['loc']); $xmlMetrics->setAttribute('ncloc', $linesOfCode['ncloc']); $xmlMetrics->setAttribute( 'classes', $report->getNumClassesAndTraits() ); $xmlMetrics->setAttribute('methods', $report->getNumMethods()); $xmlMetrics->setAttribute( 'coveredmethods', $report->getNumTestedMethods() ); $xmlMetrics->setAttribute('conditionals', 0); $xmlMetrics->setAttribute('coveredconditionals', 0); $xmlMetrics->setAttribute( 'statements', $report->getNumExecutableLines() ); $xmlMetrics->setAttribute( 'coveredstatements', $report->getNumExecutedLines() ); $xmlMetrics->setAttribute( 'elements', $report->getNumMethods() + $report->getNumExecutableLines() /* + conditionals */ ); $xmlMetrics->setAttribute( 'coveredelements', $report->getNumTestedMethods() + $report->getNumExecutedLines() /* + coveredconditionals */ ); $xmlProject->appendChild($xmlMetrics); if ($target !== null) { if (!is_dir(dirname($target))) { mkdir(dirname($target), 0777, true); } return $xmlDocument->save($target); } else { return $xmlDocument->saveXML(); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Generates an HTML report from an PHP_CodeCoverage object. * * @since Class available since Release 1.0.0 */ class PHP_CodeCoverage_Report_HTML { /** * @var string */ private $templatePath; /** * @var string */ private $generator; /** * @var int */ private $lowUpperBound; /** * @var int */ private $highLowerBound; /** * Constructor. * * @param int $lowUpperBound * @param int $highLowerBound * @param string $generator */ public function __construct($lowUpperBound = 50, $highLowerBound = 90, $generator = '') { $this->generator = $generator; $this->highLowerBound = $highLowerBound; $this->lowUpperBound = $lowUpperBound; $this->templatePath = sprintf( '%s%sHTML%sRenderer%sTemplate%s', dirname(__FILE__), DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR ); } /** * @param PHP_CodeCoverage $coverage * @param string $target */ public function process(PHP_CodeCoverage $coverage, $target) { $target = $this->getDirectory($target); $report = $coverage->getReport(); unset($coverage); if (!isset($_SERVER['REQUEST_TIME'])) { $_SERVER['REQUEST_TIME'] = time(); } $date = date('D M j G:i:s T Y', $_SERVER['REQUEST_TIME']); $dashboard = new PHP_CodeCoverage_Report_HTML_Renderer_Dashboard( $this->templatePath, $this->generator, $date, $this->lowUpperBound, $this->highLowerBound ); $directory = new PHP_CodeCoverage_Report_HTML_Renderer_Directory( $this->templatePath, $this->generator, $date, $this->lowUpperBound, $this->highLowerBound ); $file = new PHP_CodeCoverage_Report_HTML_Renderer_File( $this->templatePath, $this->generator, $date, $this->lowUpperBound, $this->highLowerBound ); $directory->render($report, $target . 'index.html'); $dashboard->render($report, $target . 'dashboard.html'); foreach ($report as $node) { $id = $node->getId(); if ($node instanceof PHP_CodeCoverage_Report_Node_Directory) { if (!file_exists($target . $id)) { mkdir($target . $id, 0777, true); } $directory->render($node, $target . $id . '/index.html'); $dashboard->render($node, $target . $id . '/dashboard.html'); } else { $dir = dirname($target . $id); if (!file_exists($dir)) { mkdir($dir, 0777, true); } $file->render($node, $target . $id . '.html'); } } $this->copyFiles($target); } /** * @param string $target */ private function copyFiles($target) { $dir = $this->getDirectory($target . 'css'); copy($this->templatePath . 'css/bootstrap.min.css', $dir . 'bootstrap.min.css'); copy($this->templatePath . 'css/nv.d3.min.css', $dir . 'nv.d3.min.css'); copy($this->templatePath . 'css/style.css', $dir . 'style.css'); $dir = $this->getDirectory($target . 'fonts'); copy($this->templatePath . 'fonts/glyphicons-halflings-regular.eot', $dir . 'glyphicons-halflings-regular.eot'); copy($this->templatePath . 'fonts/glyphicons-halflings-regular.svg', $dir . 'glyphicons-halflings-regular.svg'); copy($this->templatePath . 'fonts/glyphicons-halflings-regular.ttf', $dir . 'glyphicons-halflings-regular.ttf'); copy($this->templatePath . 'fonts/glyphicons-halflings-regular.woff', $dir . 'glyphicons-halflings-regular.woff'); copy($this->templatePath . 'fonts/glyphicons-halflings-regular.woff2', $dir . 'glyphicons-halflings-regular.woff2'); $dir = $this->getDirectory($target . 'js'); copy($this->templatePath . 'js/bootstrap.min.js', $dir . 'bootstrap.min.js'); copy($this->templatePath . 'js/d3.min.js', $dir . 'd3.min.js'); copy($this->templatePath . 'js/holder.min.js', $dir . 'holder.min.js'); copy($this->templatePath . 'js/html5shiv.min.js', $dir . 'html5shiv.min.js'); copy($this->templatePath . 'js/jquery.min.js', $dir . 'jquery.min.js'); copy($this->templatePath . 'js/nv.d3.min.js', $dir . 'nv.d3.min.js'); copy($this->templatePath . 'js/respond.min.js', $dir . 'respond.min.js'); } /** * @param string $directory * @return string * @throws PHP_CodeCoverage_Exception * @since Method available since Release 1.2.0 */ private function getDirectory($directory) { if (substr($directory, -1, 1) != DIRECTORY_SEPARATOR) { $directory .= DIRECTORY_SEPARATOR; } if (is_dir($directory)) { return $directory; } if (@mkdir($directory, 0777, true)) { return $directory; } throw new PHP_CodeCoverage_Exception( sprintf( 'Directory "%s" does not exist.', $directory ) ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Factory for PHP_CodeCoverage_Report_Node_* object graphs. * * @since Class available since Release 1.1.0 */ class PHP_CodeCoverage_Report_Factory { /** * @param PHP_CodeCoverage $coverage * @return PHP_CodeCoverage_Report_Node_Directory */ public function create(PHP_CodeCoverage $coverage) { $files = $coverage->getData(); $commonPath = $this->reducePaths($files); $root = new PHP_CodeCoverage_Report_Node_Directory( $commonPath, null ); $this->addItems( $root, $this->buildDirectoryStructure($files), $coverage->getTests(), $coverage->getCacheTokens() ); return $root; } /** * @param PHP_CodeCoverage_Report_Node_Directory $root * @param array $items * @param array $tests * @param bool $cacheTokens */ private function addItems(PHP_CodeCoverage_Report_Node_Directory $root, array $items, array $tests, $cacheTokens) { foreach ($items as $key => $value) { if (substr($key, -2) == '/f') { $key = substr($key, 0, -2); if (file_exists($root->getPath() . DIRECTORY_SEPARATOR . $key)) { $root->addFile($key, $value, $tests, $cacheTokens); } } else { $child = $root->addDirectory($key); $this->addItems($child, $value, $tests, $cacheTokens); } } } /** * Builds an array representation of the directory structure. * * For instance, * * * Array * ( * [Money.php] => Array * ( * ... * ) * * [MoneyBag.php] => Array * ( * ... * ) * ) * * * is transformed into * * * Array * ( * [.] => Array * ( * [Money.php] => Array * ( * ... * ) * * [MoneyBag.php] => Array * ( * ... * ) * ) * ) * * * @param array $files * @return array */ private function buildDirectoryStructure($files) { $result = array(); foreach ($files as $path => $file) { $path = explode('/', $path); $pointer = &$result; $max = count($path); for ($i = 0; $i < $max; $i++) { if ($i == ($max - 1)) { $type = '/f'; } else { $type = ''; } $pointer = &$pointer[$path[$i] . $type]; } $pointer = $file; } return $result; } /** * Reduces the paths by cutting the longest common start path. * * For instance, * * * Array * ( * [/home/sb/Money/Money.php] => Array * ( * ... * ) * * [/home/sb/Money/MoneyBag.php] => Array * ( * ... * ) * ) * * * is reduced to * * * Array * ( * [Money.php] => Array * ( * ... * ) * * [MoneyBag.php] => Array * ( * ... * ) * ) * * * @param array $files * @return string */ private function reducePaths(&$files) { if (empty($files)) { return '.'; } $commonPath = ''; $paths = array_keys($files); if (count($files) == 1) { $commonPath = dirname($paths[0]) . '/'; $files[basename($paths[0])] = $files[$paths[0]]; unset($files[$paths[0]]); return $commonPath; } $max = count($paths); for ($i = 0; $i < $max; $i++) { // strip phar:// prefixes if (strpos($paths[$i], 'phar://') === 0) { $paths[$i] = substr($paths[$i], 7); $paths[$i] = strtr($paths[$i], '/', DIRECTORY_SEPARATOR); } $paths[$i] = explode(DIRECTORY_SEPARATOR, $paths[$i]); if (empty($paths[$i][0])) { $paths[$i][0] = DIRECTORY_SEPARATOR; } } $done = false; $max = count($paths); while (!$done) { for ($i = 0; $i < $max - 1; $i++) { if (!isset($paths[$i][0]) || !isset($paths[$i+1][0]) || $paths[$i][0] != $paths[$i+1][0]) { $done = true; break; } } if (!$done) { $commonPath .= $paths[0][0]; if ($paths[0][0] != DIRECTORY_SEPARATOR) { $commonPath .= DIRECTORY_SEPARATOR; } for ($i = 0; $i < $max; $i++) { array_shift($paths[$i]); } } } $original = array_keys($files); $max = count($original); for ($i = 0; $i < $max; $i++) { $files[implode('/', $paths[$i])] = $files[$original[$i]]; unset($files[$original[$i]]); } ksort($files); return substr($commonPath, 0, -1); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 2.0.0 */ class PHP_CodeCoverage_Report_XML { /** * @var string */ private $target; /** * @var PHP_CodeCoverage_Report_XML_Project */ private $project; public function process(PHP_CodeCoverage $coverage, $target) { if (substr($target, -1, 1) != DIRECTORY_SEPARATOR) { $target .= DIRECTORY_SEPARATOR; } $this->target = $target; $this->initTargetDirectory($target); $report = $coverage->getReport(); $this->project = new PHP_CodeCoverage_Report_XML_Project( $coverage->getReport()->getName() ); $this->processTests($coverage->getTests()); $this->processDirectory($report, $this->project); $index = $this->project->asDom(); $index->formatOutput = true; $index->preserveWhiteSpace = false; $index->save($target . '/index.xml'); } private function initTargetDirectory($dir) { if (file_exists($dir)) { if (!is_dir($dir)) { throw new PHP_CodeCoverage_Exception( "'$dir' exists but is not a directory." ); } if (!is_writable($dir)) { throw new PHP_CodeCoverage_Exception( "'$dir' exists but is not writable." ); } } elseif (!@mkdir($dir, 0777, true)) { throw new PHP_CodeCoverage_Exception( "'$dir' could not be created." ); } } private function processDirectory(PHP_CodeCoverage_Report_Node_Directory $directory, PHP_CodeCoverage_Report_XML_Node $context) { $dirObject = $context->addDirectory($directory->getName()); $this->setTotals($directory, $dirObject->getTotals()); foreach ($directory as $node) { if ($node instanceof PHP_CodeCoverage_Report_Node_Directory) { $this->processDirectory($node, $dirObject); continue; } if ($node instanceof PHP_CodeCoverage_Report_Node_File) { $this->processFile($node, $dirObject); continue; } throw new PHP_CodeCoverage_Exception( 'Unknown node type for XML report' ); } } private function processFile(PHP_CodeCoverage_Report_Node_File $file, PHP_CodeCoverage_Report_XML_Directory $context) { $fileObject = $context->addFile( $file->getName(), $file->getId() . '.xml' ); $this->setTotals($file, $fileObject->getTotals()); $fileReport = new PHP_CodeCoverage_Report_XML_File_Report( $file->getName() ); $this->setTotals($file, $fileReport->getTotals()); foreach ($file->getClassesAndTraits() as $unit) { $this->processUnit($unit, $fileReport); } foreach ($file->getFunctions() as $function) { $this->processFunction($function, $fileReport); } foreach ($file->getCoverageData() as $line => $tests) { if (!is_array($tests) || count($tests) == 0) { continue; } $coverage = $fileReport->getLineCoverage($line); foreach ($tests as $test) { $coverage->addTest($test); } $coverage->finalize(); } $this->initTargetDirectory( $this->target . dirname($file->getId()) . '/' ); $fileDom = $fileReport->asDom(); $fileDom->formatOutput = true; $fileDom->preserveWhiteSpace = false; $fileDom->save($this->target . $file->getId() . '.xml'); } private function processUnit($unit, PHP_CodeCoverage_Report_XML_File_Report $report) { if (isset($unit['className'])) { $unitObject = $report->getClassObject($unit['className']); } else { $unitObject = $report->getTraitObject($unit['traitName']); } $unitObject->setLines( $unit['startLine'], $unit['executableLines'], $unit['executedLines'] ); $unitObject->setCrap($unit['crap']); $unitObject->setPackage( $unit['package']['fullPackage'], $unit['package']['package'], $unit['package']['subpackage'], $unit['package']['category'] ); $unitObject->setNamespace($unit['package']['namespace']); foreach ($unit['methods'] as $method) { $methodObject = $unitObject->addMethod($method['methodName']); $methodObject->setSignature($method['signature']); $methodObject->setLines($method['startLine'], $method['endLine']); $methodObject->setCrap($method['crap']); $methodObject->setTotals( $method['executableLines'], $method['executedLines'], $method['coverage'] ); } } private function processFunction($function, PHP_CodeCoverage_Report_XML_File_Report $report) { $functionObject = $report->getFunctionObject($function['functionName']); $functionObject->setSignature($function['signature']); $functionObject->setLines($function['startLine']); $functionObject->setCrap($function['crap']); $functionObject->setTotals($function['executableLines'], $function['executedLines'], $function['coverage']); } private function processTests(array $tests) { $testsObject = $this->project->getTests(); foreach ($tests as $test => $result) { if ($test == 'UNCOVERED_FILES_FROM_WHITELIST') { continue; } $testsObject->addTest($test, $result); } } private function setTotals(PHP_CodeCoverage_Report_Node $node, PHP_CodeCoverage_Report_XML_Totals $totals) { $loc = $node->getLinesOfCode(); $totals->setNumLines( $loc['loc'], $loc['cloc'], $loc['ncloc'], $node->getNumExecutableLines(), $node->getNumExecutedLines() ); $totals->setNumClasses( $node->getNumClasses(), $node->getNumTestedClasses() ); $totals->setNumTraits( $node->getNumTraits(), $node->getNumTestedTraits() ); $totals->setNumMethods( $node->getNumMethods(), $node->getNumTestedMethods() ); $totals->setNumFunctions( $node->getNumFunctions(), $node->getNumTestedFunctions() ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Uses var_export() to write a PHP_CodeCoverage object to a file. * * @since Class available since Release 1.1.0 */ class PHP_CodeCoverage_Report_PHP { /** * @param PHP_CodeCoverage $coverage * @param string $target * @return string */ public function process(PHP_CodeCoverage $coverage, $target = null) { $filter = $coverage->filter(); $output = sprintf( 'setData(%s); $coverage->setTests(%s); $filter = $coverage->filter(); $filter->setBlacklistedFiles(%s); $filter->setWhitelistedFiles(%s); return $coverage;', var_export($coverage->getData(true), 1), var_export($coverage->getTests(), 1), var_export($filter->getBlacklistedFiles(), 1), var_export($filter->getWhitelistedFiles(), 1) ); if ($target !== null) { return file_put_contents($target, $output); } else { return $output; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Generates human readable output from an PHP_CodeCoverage object. * * The output gets put into a text file our written to the CLI. * * @since Class available since Release 1.1.0 */ class PHP_CodeCoverage_Report_Text { protected $lowUpperBound; protected $highLowerBound; protected $showUncoveredFiles; protected $showOnlySummary; protected $colors = array( 'green' => "\x1b[30;42m", 'yellow' => "\x1b[30;43m", 'red' => "\x1b[37;41m", 'header' => "\x1b[1;37;40m", 'reset' => "\x1b[0m", 'eol' => "\x1b[2K", ); public function __construct($lowUpperBound, $highLowerBound, $showUncoveredFiles, $showOnlySummary) { $this->lowUpperBound = $lowUpperBound; $this->highLowerBound = $highLowerBound; $this->showUncoveredFiles = $showUncoveredFiles; $this->showOnlySummary = $showOnlySummary; } /** * @param PHP_CodeCoverage $coverage * @param bool $showColors * @return string */ public function process(PHP_CodeCoverage $coverage, $showColors = false) { $output = PHP_EOL . PHP_EOL; $report = $coverage->getReport(); unset($coverage); $colors = array( 'header' => '', 'classes' => '', 'methods' => '', 'lines' => '', 'reset' => '', 'eol' => '' ); if ($showColors) { $colors['classes'] = $this->getCoverageColor( $report->getNumTestedClassesAndTraits(), $report->getNumClassesAndTraits() ); $colors['methods'] = $this->getCoverageColor( $report->getNumTestedMethods(), $report->getNumMethods() ); $colors['lines'] = $this->getCoverageColor( $report->getNumExecutedLines(), $report->getNumExecutableLines() ); $colors['reset'] = $this->colors['reset']; $colors['header'] = $this->colors['header']; $colors['eol'] = $this->colors['eol']; } $classes = sprintf( ' Classes: %6s (%d/%d)', PHP_CodeCoverage_Util::percent( $report->getNumTestedClassesAndTraits(), $report->getNumClassesAndTraits(), true ), $report->getNumTestedClassesAndTraits(), $report->getNumClassesAndTraits() ); $methods = sprintf( ' Methods: %6s (%d/%d)', PHP_CodeCoverage_Util::percent( $report->getNumTestedMethods(), $report->getNumMethods(), true ), $report->getNumTestedMethods(), $report->getNumMethods() ); $lines = sprintf( ' Lines: %6s (%d/%d)', PHP_CodeCoverage_Util::percent( $report->getNumExecutedLines(), $report->getNumExecutableLines(), true ), $report->getNumExecutedLines(), $report->getNumExecutableLines() ); $padding = max(array_map('strlen', array($classes, $methods, $lines))); if ($this->showOnlySummary) { $title = 'Code Coverage Report Summary:'; $padding = max($padding, strlen($title)); $output .= $this->format($colors['header'], $padding, $title); } else { $date = date(' Y-m-d H:i:s', $_SERVER['REQUEST_TIME']); $title = 'Code Coverage Report:'; $output .= $this->format($colors['header'], $padding, $title); $output .= $this->format($colors['header'], $padding, $date); $output .= $this->format($colors['header'], $padding, ''); $output .= $this->format($colors['header'], $padding, ' Summary:'); } $output .= $this->format($colors['classes'], $padding, $classes); $output .= $this->format($colors['methods'], $padding, $methods); $output .= $this->format($colors['lines'], $padding, $lines); if ($this->showOnlySummary) { return $output . PHP_EOL; } $classCoverage = array(); foreach ($report as $item) { if (!$item instanceof PHP_CodeCoverage_Report_Node_File) { continue; } $classes = $item->getClassesAndTraits(); foreach ($classes as $className => $class) { $classStatements = 0; $coveredClassStatements = 0; $coveredMethods = 0; $classMethods = 0; foreach ($class['methods'] as $method) { if ($method['executableLines'] == 0) { continue; } $classMethods++; $classStatements += $method['executableLines']; $coveredClassStatements += $method['executedLines']; if ($method['coverage'] == 100) { $coveredMethods++; } } if (!empty($class['package']['namespace'])) { $namespace = '\\' . $class['package']['namespace'] . '::'; } elseif (!empty($class['package']['fullPackage'])) { $namespace = '@' . $class['package']['fullPackage'] . '::'; } else { $namespace = ''; } $classCoverage[$namespace . $className] = array( 'namespace' => $namespace, 'className ' => $className, 'methodsCovered' => $coveredMethods, 'methodCount' => $classMethods, 'statementsCovered' => $coveredClassStatements, 'statementCount' => $classStatements, ); } } ksort($classCoverage); $methodColor = ''; $linesColor = ''; $resetColor = ''; foreach ($classCoverage as $fullQualifiedPath => $classInfo) { if ($classInfo['statementsCovered'] != 0 || $this->showUncoveredFiles) { if ($showColors) { $methodColor = $this->getCoverageColor($classInfo['methodsCovered'], $classInfo['methodCount']); $linesColor = $this->getCoverageColor($classInfo['statementsCovered'], $classInfo['statementCount']); $resetColor = $colors['reset']; } $output .= PHP_EOL . $fullQualifiedPath . PHP_EOL . ' ' . $methodColor . 'Methods: ' . $this->printCoverageCounts($classInfo['methodsCovered'], $classInfo['methodCount'], 2) . $resetColor . ' ' . ' ' . $linesColor . 'Lines: ' . $this->printCoverageCounts($classInfo['statementsCovered'], $classInfo['statementCount'], 3) . $resetColor ; } } return $output . PHP_EOL; } protected function getCoverageColor($numberOfCoveredElements, $totalNumberOfElements) { $coverage = PHP_CodeCoverage_Util::percent( $numberOfCoveredElements, $totalNumberOfElements ); if ($coverage >= $this->highLowerBound) { return $this->colors['green']; } elseif ($coverage > $this->lowUpperBound) { return $this->colors['yellow']; } return $this->colors['red']; } protected function printCoverageCounts($numberOfCoveredElements, $totalNumberOfElements, $presicion) { $format = '%' . $presicion . 's'; return PHP_CodeCoverage_Util::percent( $numberOfCoveredElements, $totalNumberOfElements, true, true ) . ' (' . sprintf($format, $numberOfCoveredElements) . '/' . sprintf($format, $totalNumberOfElements) . ')'; } private function format($color, $padding, $string) { $reset = $color ? $this->colors['reset'] : ''; return $color . str_pad($string, $padding) . $reset . PHP_EOL; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Exception that is raised when code is unintentionally covered. * * @since Class available since Release 2.0.0 */ class PHP_CodeCoverage_Exception_UnintentionallyCoveredCode extends PHP_CodeCoverage_Exception { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Interface for code coverage drivers. * * @since Class available since Release 1.0.0 */ interface PHP_CodeCoverage_Driver { /** * @var int * @see http://xdebug.org/docs/code_coverage */ const LINE_EXECUTED = 1; /** * @var int * @see http://xdebug.org/docs/code_coverage */ const LINE_NOT_EXECUTED = -1; /** * @var int * @see http://xdebug.org/docs/code_coverage */ const LINE_NOT_EXECUTABLE = -2; /** * Start collection of code coverage information. */ public function start(); /** * Stop collection of code coverage information. * * @return array */ public function stop(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Filter for blacklisting and whitelisting of code coverage information. * * @since Class available since Release 1.0.0 */ class PHP_CodeCoverage_Filter { /** * Source files that are blacklisted. * * @var array */ private $blacklistedFiles = array(); /** * Source files that are whitelisted. * * @var array */ private $whitelistedFiles = array(); /** * Adds a directory to the blacklist (recursively). * * @param string $directory * @param string $suffix * @param string $prefix */ public function addDirectoryToBlacklist($directory, $suffix = '.php', $prefix = '') { $facade = new File_Iterator_Facade; $files = $facade->getFilesAsArray($directory, $suffix, $prefix); foreach ($files as $file) { $this->addFileToBlacklist($file); } } /** * Adds a file to the blacklist. * * @param string $filename */ public function addFileToBlacklist($filename) { $this->blacklistedFiles[realpath($filename)] = true; } /** * Adds files to the blacklist. * * @param array $files */ public function addFilesToBlacklist(array $files) { foreach ($files as $file) { $this->addFileToBlacklist($file); } } /** * Removes a directory from the blacklist (recursively). * * @param string $directory * @param string $suffix * @param string $prefix */ public function removeDirectoryFromBlacklist($directory, $suffix = '.php', $prefix = '') { $facade = new File_Iterator_Facade; $files = $facade->getFilesAsArray($directory, $suffix, $prefix); foreach ($files as $file) { $this->removeFileFromBlacklist($file); } } /** * Removes a file from the blacklist. * * @param string $filename */ public function removeFileFromBlacklist($filename) { $filename = realpath($filename); if (isset($this->blacklistedFiles[$filename])) { unset($this->blacklistedFiles[$filename]); } } /** * Adds a directory to the whitelist (recursively). * * @param string $directory * @param string $suffix * @param string $prefix */ public function addDirectoryToWhitelist($directory, $suffix = '.php', $prefix = '') { $facade = new File_Iterator_Facade; $files = $facade->getFilesAsArray($directory, $suffix, $prefix); foreach ($files as $file) { $this->addFileToWhitelist($file); } } /** * Adds a file to the whitelist. * * @param string $filename */ public function addFileToWhitelist($filename) { $this->whitelistedFiles[realpath($filename)] = true; } /** * Adds files to the whitelist. * * @param array $files */ public function addFilesToWhitelist(array $files) { foreach ($files as $file) { $this->addFileToWhitelist($file); } } /** * Removes a directory from the whitelist (recursively). * * @param string $directory * @param string $suffix * @param string $prefix */ public function removeDirectoryFromWhitelist($directory, $suffix = '.php', $prefix = '') { $facade = new File_Iterator_Facade; $files = $facade->getFilesAsArray($directory, $suffix, $prefix); foreach ($files as $file) { $this->removeFileFromWhitelist($file); } } /** * Removes a file from the whitelist. * * @param string $filename */ public function removeFileFromWhitelist($filename) { $filename = realpath($filename); if (isset($this->whitelistedFiles[$filename])) { unset($this->whitelistedFiles[$filename]); } } /** * Checks whether a filename is a real filename. * * @param string $filename * @return bool */ public function isFile($filename) { if ($filename == '-' || strpos($filename, 'vfs://') === 0 || strpos($filename, 'xdebug://debug-eval') !== false || strpos($filename, 'eval()\'d code') !== false || strpos($filename, 'runtime-created function') !== false || strpos($filename, 'runkit created function') !== false || strpos($filename, 'assert code') !== false || strpos($filename, 'regexp code') !== false) { return false; } return file_exists($filename); } /** * Checks whether or not a file is filtered. * * When the whitelist is empty (default), blacklisting is used. * When the whitelist is not empty, whitelisting is used. * * @param string $filename * @return bool * @throws PHP_CodeCoverage_Exception */ public function isFiltered($filename) { if (!$this->isFile($filename)) { return true; } $filename = realpath($filename); if (!empty($this->whitelistedFiles)) { return !isset($this->whitelistedFiles[$filename]); } return isset($this->blacklistedFiles[$filename]); } /** * Returns the list of blacklisted files. * * @return array */ public function getBlacklist() { return array_keys($this->blacklistedFiles); } /** * Returns the list of whitelisted files. * * @return array */ public function getWhitelist() { return array_keys($this->whitelistedFiles); } /** * Returns whether this filter has a whitelist. * * @return bool * @since Method available since Release 1.1.0 */ public function hasWhitelist() { return !empty($this->whitelistedFiles); } /** * Returns the blacklisted files. * * @return array * @since Method available since Release 2.0.0 */ public function getBlacklistedFiles() { return $this->blacklistedFiles; } /** * Sets the blacklisted files. * * @param array $blacklistedFiles * @since Method available since Release 2.0.0 */ public function setBlacklistedFiles($blacklistedFiles) { $this->blacklistedFiles = $blacklistedFiles; } /** * Returns the whitelisted files. * * @return array * @since Method available since Release 2.0.0 */ public function getWhitelistedFiles() { return $this->whitelistedFiles; } /** * Sets the whitelisted files. * * @param array $whitelistedFiles * @since Method available since Release 2.0.0 */ public function setWhitelistedFiles($whitelistedFiles) { $this->whitelistedFiles = $whitelistedFiles; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Factory for PHP_CodeCoverage_Exception objects that are used to describe * invalid arguments passed to a function or method. * * @since Class available since Release 1.2.0 */ class PHP_CodeCoverage_Util_InvalidArgumentHelper { /** * @param int $argument * @param string $type * @param mixed $value */ public static function factory($argument, $type, $value = null) { $stack = debug_backtrace(false); return new PHP_CodeCoverage_Exception( sprintf( 'Argument #%d%sof %s::%s() must be a %s', $argument, $value !== null ? ' (' . gettype($value) . '#' . $value . ')' : ' (No Value) ', $stack[1]['class'], $stack[1]['function'], $type ) ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Driver for HHVM's code coverage functionality. * * @since Class available since Release 2.2.2 * @codeCoverageIgnore */ class PHP_CodeCoverage_Driver_HHVM extends PHP_CodeCoverage_Driver_Xdebug { /** * Start collection of code coverage information. */ public function start() { xdebug_start_code_coverage(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Driver for PHPDBG's code coverage functionality. * * @since Class available since Release 2.2.0 * @codeCoverageIgnore */ class PHP_CodeCoverage_Driver_PHPDBG implements PHP_CodeCoverage_Driver { /** * Constructor. */ public function __construct() { if (PHP_SAPI !== 'phpdbg') { throw new PHP_CodeCoverage_Exception( 'This driver requires the PHPDBG SAPI' ); } if (!function_exists('phpdbg_start_oplog')) { throw new PHP_CodeCoverage_Exception( 'This build of PHPDBG does not support code coverage' ); } } /** * Start collection of code coverage information. */ public function start() { phpdbg_start_oplog(); } /** * Stop collection of code coverage information. * * @return array */ public function stop() { static $fetchedLines = array(); $dbgData = phpdbg_end_oplog(); if ($fetchedLines == array()) { $sourceLines = phpdbg_get_executable(); } else { $newFiles = array_diff( get_included_files(), array_keys($fetchedLines) ); if ($newFiles) { $sourceLines = phpdbg_get_executable( array('files' => $newFiles) ); } else { $sourceLines = array(); } } foreach ($sourceLines as $file => $lines) { foreach ($lines as $lineNo => $numExecuted) { $sourceLines[$file][$lineNo] = self::LINE_NOT_EXECUTED; } } $fetchedLines = array_merge($fetchedLines, $sourceLines); return $this->detectExecutedLines($fetchedLines, $dbgData); } /** * Convert phpdbg based data into the format CodeCoverage expects * * @param array $sourceLines * @param array $dbgData * @return array */ private function detectExecutedLines(array $sourceLines, array $dbgData) { foreach ($dbgData as $file => $coveredLines) { foreach ($coveredLines as $lineNo => $numExecuted) { // phpdbg also reports $lineNo=0 when e.g. exceptions get thrown. // make sure we only mark lines executed which are actually executable. if (isset($sourceLines[$file][$lineNo])) { $sourceLines[$file][$lineNo] = self::LINE_EXECUTED; } } } return $sourceLines; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Driver for Xdebug's code coverage functionality. * * @since Class available since Release 1.0.0 * @codeCoverageIgnore */ class PHP_CodeCoverage_Driver_Xdebug implements PHP_CodeCoverage_Driver { /** * Constructor. */ public function __construct() { if (!extension_loaded('xdebug')) { throw new PHP_CodeCoverage_Exception('This driver requires Xdebug'); } if (version_compare(phpversion('xdebug'), '2.2.0-dev', '>=') && !ini_get('xdebug.coverage_enable')) { throw new PHP_CodeCoverage_Exception( 'xdebug.coverage_enable=On has to be set in php.ini' ); } } /** * Start collection of code coverage information. */ public function start() { xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE); } /** * Stop collection of code coverage information. * * @return array */ public function stop() { $data = xdebug_get_code_coverage(); xdebug_stop_code_coverage(); return $this->cleanup($data); } /** * @param array $data * @return array * @since Method available since Release 2.0.0 */ private function cleanup(array $data) { foreach (array_keys($data) as $file) { unset($data[$file][0]); if ($file != 'xdebug://debug-eval' && file_exists($file)) { $numLines = $this->getNumberOfLinesInFile($file); foreach (array_keys($data[$file]) as $line) { if (isset($data[$file][$line]) && $line > $numLines) { unset($data[$file][$line]); } } } } return $data; } /** * @param string $file * @return int * @since Method available since Release 2.0.0 */ private function getNumberOfLinesInFile($file) { $buffer = file_get_contents($file); $lines = substr_count($buffer, "\n"); if (substr($buffer, -1) !== "\n") { $lines++; } return $lines; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use SebastianBergmann\Environment\Runtime; /** * Provides collection functionality for PHP code coverage information. * * @since Class available since Release 1.0.0 */ class PHP_CodeCoverage { /** * @var PHP_CodeCoverage_Driver */ private $driver; /** * @var PHP_CodeCoverage_Filter */ private $filter; /** * @var bool */ private $cacheTokens = false; /** * @var bool */ private $checkForUnintentionallyCoveredCode = false; /** * @var bool */ private $forceCoversAnnotation = false; /** * @var bool */ private $mapTestClassNameToCoveredClassName = false; /** * @var bool */ private $addUncoveredFilesFromWhitelist = true; /** * @var bool */ private $processUncoveredFilesFromWhitelist = false; /** * @var mixed */ private $currentId; /** * Code coverage data. * * @var array */ private $data = array(); /** * @var array */ private $ignoredLines = array(); /** * @var bool */ private $disableIgnoredLines = false; /** * Test data. * * @var array */ private $tests = array(); /** * Constructor. * * @param PHP_CodeCoverage_Driver $driver * @param PHP_CodeCoverage_Filter $filter * @throws PHP_CodeCoverage_Exception */ public function __construct(PHP_CodeCoverage_Driver $driver = null, PHP_CodeCoverage_Filter $filter = null) { if ($driver === null) { $driver = $this->selectDriver(); } if ($filter === null) { $filter = new PHP_CodeCoverage_Filter; } $this->driver = $driver; $this->filter = $filter; } /** * Returns the PHP_CodeCoverage_Report_Node_* object graph * for this PHP_CodeCoverage object. * * @return PHP_CodeCoverage_Report_Node_Directory * @since Method available since Release 1.1.0 */ public function getReport() { $factory = new PHP_CodeCoverage_Report_Factory; return $factory->create($this); } /** * Clears collected code coverage data. */ public function clear() { $this->currentId = null; $this->data = array(); $this->tests = array(); } /** * Returns the PHP_CodeCoverage_Filter used. * * @return PHP_CodeCoverage_Filter */ public function filter() { return $this->filter; } /** * Returns the collected code coverage data. * Set $raw = true to bypass all filters. * * @param bool $raw * @return array * @since Method available since Release 1.1.0 */ public function getData($raw = false) { if (!$raw && $this->addUncoveredFilesFromWhitelist) { $this->addUncoveredFilesFromWhitelist(); } // We need to apply the blacklist filter a second time // when no whitelist is used. if (!$raw && !$this->filter->hasWhitelist()) { $this->applyListsFilter($this->data); } return $this->data; } /** * Sets the coverage data. * * @param array $data * @since Method available since Release 2.0.0 */ public function setData(array $data) { $this->data = $data; } /** * Returns the test data. * * @return array * @since Method available since Release 1.1.0 */ public function getTests() { return $this->tests; } /** * Sets the test data. * * @param array $tests * @since Method available since Release 2.0.0 */ public function setTests(array $tests) { $this->tests = $tests; } /** * Start collection of code coverage information. * * @param mixed $id * @param bool $clear * @throws PHP_CodeCoverage_Exception */ public function start($id, $clear = false) { if (!is_bool($clear)) { throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 1, 'boolean' ); } if ($clear) { $this->clear(); } $this->currentId = $id; $this->driver->start(); } /** * Stop collection of code coverage information. * * @param bool $append * @param mixed $linesToBeCovered * @param array $linesToBeUsed * @return array * @throws PHP_CodeCoverage_Exception */ public function stop($append = true, $linesToBeCovered = array(), array $linesToBeUsed = array()) { if (!is_bool($append)) { throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 1, 'boolean' ); } if (!is_array($linesToBeCovered) && $linesToBeCovered !== false) { throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 2, 'array or false' ); } $data = $this->driver->stop(); $this->append($data, null, $append, $linesToBeCovered, $linesToBeUsed); $this->currentId = null; return $data; } /** * Appends code coverage data. * * @param array $data * @param mixed $id * @param bool $append * @param mixed $linesToBeCovered * @param array $linesToBeUsed * @throws PHP_CodeCoverage_Exception */ public function append(array $data, $id = null, $append = true, $linesToBeCovered = array(), array $linesToBeUsed = array()) { if ($id === null) { $id = $this->currentId; } if ($id === null) { throw new PHP_CodeCoverage_Exception; } $this->applyListsFilter($data); $this->applyIgnoredLinesFilter($data); $this->initializeFilesThatAreSeenTheFirstTime($data); if (!$append) { return; } if ($id != 'UNCOVERED_FILES_FROM_WHITELIST') { $this->applyCoversAnnotationFilter( $data, $linesToBeCovered, $linesToBeUsed ); } if (empty($data)) { return; } $size = 'unknown'; $status = null; if ($id instanceof PHPUnit_Framework_TestCase) { $_size = $id->getSize(); if ($_size == PHPUnit_Util_Test::SMALL) { $size = 'small'; } elseif ($_size == PHPUnit_Util_Test::MEDIUM) { $size = 'medium'; } elseif ($_size == PHPUnit_Util_Test::LARGE) { $size = 'large'; } $status = $id->getStatus(); $id = get_class($id) . '::' . $id->getName(); } elseif ($id instanceof PHPUnit_Extensions_PhptTestCase) { $size = 'large'; $id = $id->getName(); } $this->tests[$id] = array('size' => $size, 'status' => $status); foreach ($data as $file => $lines) { if (!$this->filter->isFile($file)) { continue; } foreach ($lines as $k => $v) { if ($v == PHP_CodeCoverage_Driver::LINE_EXECUTED) { if (empty($this->data[$file][$k]) || !in_array($id, $this->data[$file][$k])) { $this->data[$file][$k][] = $id; } } } } } /** * Merges the data from another instance of PHP_CodeCoverage. * * @param PHP_CodeCoverage $that */ public function merge(PHP_CodeCoverage $that) { $this->filter->setBlacklistedFiles( array_merge($this->filter->getBlacklistedFiles(), $that->filter()->getBlacklistedFiles()) ); $this->filter->setWhitelistedFiles( array_merge($this->filter->getWhitelistedFiles(), $that->filter()->getWhitelistedFiles()) ); foreach ($that->data as $file => $lines) { if (!isset($this->data[$file])) { if (!$this->filter->isFiltered($file)) { $this->data[$file] = $lines; } continue; } foreach ($lines as $line => $data) { if ($data !== null) { if (!isset($this->data[$file][$line])) { $this->data[$file][$line] = $data; } else { $this->data[$file][$line] = array_unique( array_merge($this->data[$file][$line], $data) ); } } } } $this->tests = array_merge($this->tests, $that->getTests()); } /** * @param bool $flag * @throws PHP_CodeCoverage_Exception * @since Method available since Release 1.1.0 */ public function setCacheTokens($flag) { if (!is_bool($flag)) { throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 1, 'boolean' ); } $this->cacheTokens = $flag; } /** * @since Method available since Release 1.1.0 */ public function getCacheTokens() { return $this->cacheTokens; } /** * @param bool $flag * @throws PHP_CodeCoverage_Exception * @since Method available since Release 2.0.0 */ public function setCheckForUnintentionallyCoveredCode($flag) { if (!is_bool($flag)) { throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 1, 'boolean' ); } $this->checkForUnintentionallyCoveredCode = $flag; } /** * @param bool $flag * @throws PHP_CodeCoverage_Exception */ public function setForceCoversAnnotation($flag) { if (!is_bool($flag)) { throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 1, 'boolean' ); } $this->forceCoversAnnotation = $flag; } /** * @param bool $flag * @throws PHP_CodeCoverage_Exception */ public function setMapTestClassNameToCoveredClassName($flag) { if (!is_bool($flag)) { throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 1, 'boolean' ); } $this->mapTestClassNameToCoveredClassName = $flag; } /** * @param bool $flag * @throws PHP_CodeCoverage_Exception */ public function setAddUncoveredFilesFromWhitelist($flag) { if (!is_bool($flag)) { throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 1, 'boolean' ); } $this->addUncoveredFilesFromWhitelist = $flag; } /** * @param bool $flag * @throws PHP_CodeCoverage_Exception */ public function setProcessUncoveredFilesFromWhitelist($flag) { if (!is_bool($flag)) { throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 1, 'boolean' ); } $this->processUncoveredFilesFromWhitelist = $flag; } /** * @param bool $flag * @throws PHP_CodeCoverage_Exception */ public function setDisableIgnoredLines($flag) { if (!is_bool($flag)) { throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 1, 'boolean' ); } $this->disableIgnoredLines = $flag; } /** * Applies the @covers annotation filtering. * * @param array $data * @param mixed $linesToBeCovered * @param array $linesToBeUsed * @throws PHP_CodeCoverage_Exception_UnintentionallyCoveredCode */ private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, array $linesToBeUsed) { if ($linesToBeCovered === false || ($this->forceCoversAnnotation && empty($linesToBeCovered))) { $data = array(); return; } if (empty($linesToBeCovered)) { return; } if ($this->checkForUnintentionallyCoveredCode) { $this->performUnintentionallyCoveredCodeCheck( $data, $linesToBeCovered, $linesToBeUsed ); } $data = array_intersect_key($data, $linesToBeCovered); foreach (array_keys($data) as $filename) { $_linesToBeCovered = array_flip($linesToBeCovered[$filename]); $data[$filename] = array_intersect_key( $data[$filename], $_linesToBeCovered ); } } /** * Applies the blacklist/whitelist filtering. * * @param array $data */ private function applyListsFilter(array &$data) { foreach (array_keys($data) as $filename) { if ($this->filter->isFiltered($filename)) { unset($data[$filename]); } } } /** * Applies the "ignored lines" filtering. * * @param array $data */ private function applyIgnoredLinesFilter(array &$data) { foreach (array_keys($data) as $filename) { if (!$this->filter->isFile($filename)) { continue; } foreach ($this->getLinesToBeIgnored($filename) as $line) { unset($data[$filename][$line]); } } } /** * @param array $data * @since Method available since Release 1.1.0 */ private function initializeFilesThatAreSeenTheFirstTime(array $data) { foreach ($data as $file => $lines) { if ($this->filter->isFile($file) && !isset($this->data[$file])) { $this->data[$file] = array(); foreach ($lines as $k => $v) { $this->data[$file][$k] = $v == -2 ? null : array(); } } } } /** * Processes whitelisted files that are not covered. */ private function addUncoveredFilesFromWhitelist() { $data = array(); $uncoveredFiles = array_diff( $this->filter->getWhitelist(), array_keys($this->data) ); foreach ($uncoveredFiles as $uncoveredFile) { if (!file_exists($uncoveredFile)) { continue; } if ($this->processUncoveredFilesFromWhitelist) { $this->processUncoveredFileFromWhitelist( $uncoveredFile, $data, $uncoveredFiles ); } else { $data[$uncoveredFile] = array(); $lines = count(file($uncoveredFile)); for ($i = 1; $i <= $lines; $i++) { $data[$uncoveredFile][$i] = PHP_CodeCoverage_Driver::LINE_NOT_EXECUTED; } } } $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST'); } /** * @param string $uncoveredFile * @param array $data * @param array $uncoveredFiles */ private function processUncoveredFileFromWhitelist($uncoveredFile, array &$data, array $uncoveredFiles) { $this->driver->start(); include_once $uncoveredFile; $coverage = $this->driver->stop(); foreach ($coverage as $file => $fileCoverage) { if (!isset($data[$file]) && in_array($file, $uncoveredFiles)) { foreach (array_keys($fileCoverage) as $key) { if ($fileCoverage[$key] == PHP_CodeCoverage_Driver::LINE_EXECUTED) { $fileCoverage[$key] = PHP_CodeCoverage_Driver::LINE_NOT_EXECUTED; } } $data[$file] = $fileCoverage; } } } /** * Returns the lines of a source file that should be ignored. * * @param string $filename * @return array * @throws PHP_CodeCoverage_Exception * @since Method available since Release 2.0.0 */ private function getLinesToBeIgnored($filename) { if (!is_string($filename)) { throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 1, 'string' ); } if (!isset($this->ignoredLines[$filename])) { $this->ignoredLines[$filename] = array(); if ($this->disableIgnoredLines) { return $this->ignoredLines[$filename]; } $ignore = false; $stop = false; $lines = file($filename); $numLines = count($lines); foreach ($lines as $index => $line) { if (!trim($line)) { $this->ignoredLines[$filename][] = $index + 1; } } if ($this->cacheTokens) { $tokens = PHP_Token_Stream_CachingFactory::get($filename); } else { $tokens = new PHP_Token_Stream($filename); } $classes = array_merge($tokens->getClasses(), $tokens->getTraits()); $tokens = $tokens->tokens(); foreach ($tokens as $token) { switch (get_class($token)) { case 'PHP_Token_COMMENT': case 'PHP_Token_DOC_COMMENT': $_token = trim($token); $_line = trim($lines[$token->getLine() - 1]); if ($_token == '// @codeCoverageIgnore' || $_token == '//@codeCoverageIgnore') { $ignore = true; $stop = true; } elseif ($_token == '// @codeCoverageIgnoreStart' || $_token == '//@codeCoverageIgnoreStart') { $ignore = true; } elseif ($_token == '// @codeCoverageIgnoreEnd' || $_token == '//@codeCoverageIgnoreEnd') { $stop = true; } if (!$ignore) { $start = $token->getLine(); $end = $start + substr_count($token, "\n"); // Do not ignore the first line when there is a token // before the comment if (0 !== strpos($_token, $_line)) { $start++; } for ($i = $start; $i < $end; $i++) { $this->ignoredLines[$filename][] = $i; } // A DOC_COMMENT token or a COMMENT token starting with "/*" // does not contain the final \n character in its text if (isset($lines[$i-1]) && 0 === strpos($_token, '/*') && '*/' === substr(trim($lines[$i-1]), -2)) { $this->ignoredLines[$filename][] = $i; } } break; case 'PHP_Token_INTERFACE': case 'PHP_Token_TRAIT': case 'PHP_Token_CLASS': case 'PHP_Token_FUNCTION': $docblock = $token->getDocblock(); $this->ignoredLines[$filename][] = $token->getLine(); if (strpos($docblock, '@codeCoverageIgnore') || strpos($docblock, '@deprecated')) { $endLine = $token->getEndLine(); for ($i = $token->getLine(); $i <= $endLine; $i++) { $this->ignoredLines[$filename][] = $i; } } elseif ($token instanceof PHP_Token_INTERFACE || $token instanceof PHP_Token_TRAIT || $token instanceof PHP_Token_CLASS) { if (empty($classes[$token->getName()]['methods'])) { for ($i = $token->getLine(); $i <= $token->getEndLine(); $i++) { $this->ignoredLines[$filename][] = $i; } } else { $firstMethod = array_shift( $classes[$token->getName()]['methods'] ); do { $lastMethod = array_pop( $classes[$token->getName()]['methods'] ); } while ($lastMethod !== null && substr($lastMethod['signature'], 0, 18) == 'anonymous function'); if ($lastMethod === null) { $lastMethod = $firstMethod; } for ($i = $token->getLine(); $i < $firstMethod['startLine']; $i++) { $this->ignoredLines[$filename][] = $i; } for ($i = $token->getEndLine(); $i > $lastMethod['endLine']; $i--) { $this->ignoredLines[$filename][] = $i; } } } break; case 'PHP_Token_NAMESPACE': $this->ignoredLines[$filename][] = $token->getEndLine(); // Intentional fallthrough case 'PHP_Token_OPEN_TAG': case 'PHP_Token_CLOSE_TAG': case 'PHP_Token_USE': $this->ignoredLines[$filename][] = $token->getLine(); break; } if ($ignore) { $this->ignoredLines[$filename][] = $token->getLine(); if ($stop) { $ignore = false; $stop = false; } } } $this->ignoredLines[$filename][] = $numLines + 1; $this->ignoredLines[$filename] = array_unique( $this->ignoredLines[$filename] ); sort($this->ignoredLines[$filename]); } return $this->ignoredLines[$filename]; } /** * @param array $data * @param array $linesToBeCovered * @param array $linesToBeUsed * @throws PHP_CodeCoverage_Exception_UnintentionallyCoveredCode * @since Method available since Release 2.0.0 */ private function performUnintentionallyCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed) { $allowedLines = $this->getAllowedLines( $linesToBeCovered, $linesToBeUsed ); $message = ''; foreach ($data as $file => $_data) { foreach ($_data as $line => $flag) { if ($flag == 1 && (!isset($allowedLines[$file]) || !isset($allowedLines[$file][$line]))) { $message .= sprintf( '- %s:%d' . PHP_EOL, $file, $line ); } } } if (!empty($message)) { throw new PHP_CodeCoverage_Exception_UnintentionallyCoveredCode( $message ); } } /** * @param array $linesToBeCovered * @param array $linesToBeUsed * @return array * @since Method available since Release 2.0.0 */ private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed) { $allowedLines = array(); foreach (array_keys($linesToBeCovered) as $file) { if (!isset($allowedLines[$file])) { $allowedLines[$file] = array(); } $allowedLines[$file] = array_merge( $allowedLines[$file], $linesToBeCovered[$file] ); } foreach (array_keys($linesToBeUsed) as $file) { if (!isset($allowedLines[$file])) { $allowedLines[$file] = array(); } $allowedLines[$file] = array_merge( $allowedLines[$file], $linesToBeUsed[$file] ); } foreach (array_keys($allowedLines) as $file) { $allowedLines[$file] = array_flip( array_unique($allowedLines[$file]) ); } return $allowedLines; } /** * @return PHP_CodeCoverage_Driver * @throws PHP_CodeCoverage_Exception */ private function selectDriver() { $runtime = new Runtime; if (!$runtime->canCollectCodeCoverage()) { throw new PHP_CodeCoverage_Exception('No code coverage driver available'); } if ($runtime->isHHVM()) { return new PHP_CodeCoverage_Driver_HHVM; } elseif ($runtime->isPHPDBG()) { return new PHP_CodeCoverage_Driver_PHPDBG; } else { return new PHP_CodeCoverage_Driver_Xdebug; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A caching factory for token stream objects. * * @author Sebastian Bergmann * @copyright Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://github.com/sebastianbergmann/php-token-stream/tree * @since Class available since Release 1.0.0 */ class PHP_Token_Stream_CachingFactory { /** * @var array */ protected static $cache = array(); /** * @param string $filename * @return PHP_Token_Stream */ public static function get($filename) { if (!isset(self::$cache[$filename])) { self::$cache[$filename] = new PHP_Token_Stream($filename); } return self::$cache[$filename]; } /** * @param string $filename */ public static function clear($filename = null) { if (is_string($filename)) { unset(self::$cache[$filename]); } else { self::$cache = array(); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A stream of PHP tokens. * * @author Sebastian Bergmann * @copyright Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://github.com/sebastianbergmann/php-token-stream/tree * @since Class available since Release 1.0.0 */ class PHP_Token_Stream implements ArrayAccess, Countable, SeekableIterator { /** * @var array */ protected static $customTokens = array( '(' => 'PHP_Token_OPEN_BRACKET', ')' => 'PHP_Token_CLOSE_BRACKET', '[' => 'PHP_Token_OPEN_SQUARE', ']' => 'PHP_Token_CLOSE_SQUARE', '{' => 'PHP_Token_OPEN_CURLY', '}' => 'PHP_Token_CLOSE_CURLY', ';' => 'PHP_Token_SEMICOLON', '.' => 'PHP_Token_DOT', ',' => 'PHP_Token_COMMA', '=' => 'PHP_Token_EQUAL', '<' => 'PHP_Token_LT', '>' => 'PHP_Token_GT', '+' => 'PHP_Token_PLUS', '-' => 'PHP_Token_MINUS', '*' => 'PHP_Token_MULT', '/' => 'PHP_Token_DIV', '?' => 'PHP_Token_QUESTION_MARK', '!' => 'PHP_Token_EXCLAMATION_MARK', ':' => 'PHP_Token_COLON', '"' => 'PHP_Token_DOUBLE_QUOTES', '@' => 'PHP_Token_AT', '&' => 'PHP_Token_AMPERSAND', '%' => 'PHP_Token_PERCENT', '|' => 'PHP_Token_PIPE', '$' => 'PHP_Token_DOLLAR', '^' => 'PHP_Token_CARET', '~' => 'PHP_Token_TILDE', '`' => 'PHP_Token_BACKTICK' ); /** * @var string */ protected $filename; /** * @var array */ protected $tokens = array(); /** * @var integer */ protected $position = 0; /** * @var array */ protected $linesOfCode = array('loc' => 0, 'cloc' => 0, 'ncloc' => 0); /** * @var array */ protected $classes; /** * @var array */ protected $functions; /** * @var array */ protected $includes; /** * @var array */ protected $interfaces; /** * @var array */ protected $traits; /** * @var array */ protected $lineToFunctionMap = array(); /** * Constructor. * * @param string $sourceCode */ public function __construct($sourceCode) { if (is_file($sourceCode)) { $this->filename = $sourceCode; $sourceCode = file_get_contents($sourceCode); } $this->scan($sourceCode); } /** * Destructor. */ public function __destruct() { $this->tokens = array(); } /** * @return string */ public function __toString() { $buffer = ''; foreach ($this as $token) { $buffer .= $token; } return $buffer; } /** * @return string * @since Method available since Release 1.1.0 */ public function getFilename() { return $this->filename; } /** * Scans the source for sequences of characters and converts them into a * stream of tokens. * * @param string $sourceCode */ protected function scan($sourceCode) { $line = 1; $tokens = token_get_all($sourceCode); $numTokens = count($tokens); $lastNonWhitespaceTokenWasDoubleColon = false; for ($i = 0; $i < $numTokens; ++$i) { $token = $tokens[$i]; unset($tokens[$i]); if (is_array($token)) { $name = substr(token_name($token[0]), 2); $text = $token[1]; if ($lastNonWhitespaceTokenWasDoubleColon && $name == 'CLASS') { $name = 'CLASS_NAME_CONSTANT'; } $tokenClass = 'PHP_Token_' . $name; } else { $text = $token; $tokenClass = self::$customTokens[$token]; } $this->tokens[] = new $tokenClass($text, $line, $this, $i); $lines = substr_count($text, "\n"); $line += $lines; if ($tokenClass == 'PHP_Token_HALT_COMPILER') { break; } elseif ($tokenClass == 'PHP_Token_COMMENT' || $tokenClass == 'PHP_Token_DOC_COMMENT') { $this->linesOfCode['cloc'] += $lines + 1; } if ($name == 'DOUBLE_COLON') { $lastNonWhitespaceTokenWasDoubleColon = true; } elseif ($name != 'WHITESPACE') { $lastNonWhitespaceTokenWasDoubleColon = false; } } $this->linesOfCode['loc'] = substr_count($sourceCode, "\n"); $this->linesOfCode['ncloc'] = $this->linesOfCode['loc'] - $this->linesOfCode['cloc']; } /** * @return integer */ public function count() { return count($this->tokens); } /** * @return PHP_Token[] */ public function tokens() { return $this->tokens; } /** * @return array */ public function getClasses() { if ($this->classes !== null) { return $this->classes; } $this->parse(); return $this->classes; } /** * @return array */ public function getFunctions() { if ($this->functions !== null) { return $this->functions; } $this->parse(); return $this->functions; } /** * @return array */ public function getInterfaces() { if ($this->interfaces !== null) { return $this->interfaces; } $this->parse(); return $this->interfaces; } /** * @return array * @since Method available since Release 1.1.0 */ public function getTraits() { if ($this->traits !== null) { return $this->traits; } $this->parse(); return $this->traits; } /** * Gets the names of all files that have been included * using include(), include_once(), require() or require_once(). * * Parameter $categorize set to TRUE causing this function to return a * multi-dimensional array with categories in the keys of the first dimension * and constants and their values in the second dimension. * * Parameter $category allow to filter following specific inclusion type * * @param bool $categorize OPTIONAL * @param string $category OPTIONAL Either 'require_once', 'require', * 'include_once', 'include'. * @return array * @since Method available since Release 1.1.0 */ public function getIncludes($categorize = false, $category = null) { if ($this->includes === null) { $this->includes = array( 'require_once' => array(), 'require' => array(), 'include_once' => array(), 'include' => array() ); foreach ($this->tokens as $token) { switch (get_class($token)) { case 'PHP_Token_REQUIRE_ONCE': case 'PHP_Token_REQUIRE': case 'PHP_Token_INCLUDE_ONCE': case 'PHP_Token_INCLUDE': $this->includes[$token->getType()][] = $token->getName(); break; } } } if (isset($this->includes[$category])) { $includes = $this->includes[$category]; } elseif ($categorize === false) { $includes = array_merge( $this->includes['require_once'], $this->includes['require'], $this->includes['include_once'], $this->includes['include'] ); } else { $includes = $this->includes; } return $includes; } /** * Returns the name of the function or method a line belongs to. * * @return string or null if the line is not in a function or method * @since Method available since Release 1.2.0 */ public function getFunctionForLine($line) { $this->parse(); if (isset($this->lineToFunctionMap[$line])) { return $this->lineToFunctionMap[$line]; } } protected function parse() { $this->interfaces = array(); $this->classes = array(); $this->traits = array(); $this->functions = array(); $class = array(); $classEndLine = array(); $trait = false; $traitEndLine = false; $interface = false; $interfaceEndLine = false; foreach ($this->tokens as $token) { switch (get_class($token)) { case 'PHP_Token_HALT_COMPILER': return; case 'PHP_Token_INTERFACE': $interface = $token->getName(); $interfaceEndLine = $token->getEndLine(); $this->interfaces[$interface] = array( 'methods' => array(), 'parent' => $token->getParent(), 'keywords' => $token->getKeywords(), 'docblock' => $token->getDocblock(), 'startLine' => $token->getLine(), 'endLine' => $interfaceEndLine, 'package' => $token->getPackage(), 'file' => $this->filename ); break; case 'PHP_Token_CLASS': case 'PHP_Token_TRAIT': $tmp = array( 'methods' => array(), 'parent' => $token->getParent(), 'interfaces'=> $token->getInterfaces(), 'keywords' => $token->getKeywords(), 'docblock' => $token->getDocblock(), 'startLine' => $token->getLine(), 'endLine' => $token->getEndLine(), 'package' => $token->getPackage(), 'file' => $this->filename ); if ($token instanceof PHP_Token_CLASS) { $class[] = $token->getName(); $classEndLine[] = $token->getEndLine(); if ($class[count($class)-1] != 'anonymous class') { $this->classes[$class[count($class)-1]] = $tmp; } } else { $trait = $token->getName(); $traitEndLine = $token->getEndLine(); $this->traits[$trait] = $tmp; } break; case 'PHP_Token_FUNCTION': $name = $token->getName(); $tmp = array( 'docblock' => $token->getDocblock(), 'keywords' => $token->getKeywords(), 'visibility'=> $token->getVisibility(), 'signature' => $token->getSignature(), 'startLine' => $token->getLine(), 'endLine' => $token->getEndLine(), 'ccn' => $token->getCCN(), 'file' => $this->filename ); if (empty($class) && $trait === false && $interface === false) { $this->functions[$name] = $tmp; $this->addFunctionToMap( $name, $tmp['startLine'], $tmp['endLine'] ); } elseif (!empty($class) && $class[count($class)-1] != 'anonymous class') { $this->classes[$class[count($class)-1]]['methods'][$name] = $tmp; $this->addFunctionToMap( $class[count($class)-1] . '::' . $name, $tmp['startLine'], $tmp['endLine'] ); } elseif ($trait !== false) { $this->traits[$trait]['methods'][$name] = $tmp; $this->addFunctionToMap( $trait . '::' . $name, $tmp['startLine'], $tmp['endLine'] ); } else { $this->interfaces[$interface]['methods'][$name] = $tmp; } break; case 'PHP_Token_CLOSE_CURLY': if (!empty($classEndLine) && $classEndLine[count($classEndLine)-1] == $token->getLine()) { array_pop($classEndLine); array_pop($class); } elseif ($traitEndLine !== false && $traitEndLine == $token->getLine()) { $trait = false; $traitEndLine = false; } elseif ($interfaceEndLine !== false && $interfaceEndLine == $token->getLine()) { $interface = false; $interfaceEndLine = false; } break; } } } /** * @return array */ public function getLinesOfCode() { return $this->linesOfCode; } /** */ public function rewind() { $this->position = 0; } /** * @return boolean */ public function valid() { return isset($this->tokens[$this->position]); } /** * @return integer */ public function key() { return $this->position; } /** * @return PHP_Token */ public function current() { return $this->tokens[$this->position]; } /** */ public function next() { $this->position++; } /** * @param integer $offset * @return boolean */ public function offsetExists($offset) { return isset($this->tokens[$offset]); } /** * @param integer $offset * @return mixed * @throws OutOfBoundsException */ public function offsetGet($offset) { if (!$this->offsetExists($offset)) { throw new OutOfBoundsException( sprintf( 'No token at position "%s"', $offset ) ); } return $this->tokens[$offset]; } /** * @param integer $offset * @param mixed $value */ public function offsetSet($offset, $value) { $this->tokens[$offset] = $value; } /** * @param integer $offset * @throws OutOfBoundsException */ public function offsetUnset($offset) { if (!$this->offsetExists($offset)) { throw new OutOfBoundsException( sprintf( 'No token at position "%s"', $offset ) ); } unset($this->tokens[$offset]); } /** * Seek to an absolute position. * * @param integer $position * @throws OutOfBoundsException */ public function seek($position) { $this->position = $position; if (!$this->valid()) { throw new OutOfBoundsException( sprintf( 'No token at position "%s"', $this->position ) ); } } /** * @param string $name * @param integer $startLine * @param integer $endLine */ private function addFunctionToMap($name, $startLine, $endLine) { for ($line = $startLine; $line <= $endLine; $line++) { $this->lineToFunctionMap[$line] = $name; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A PHP token. * * @author Sebastian Bergmann * @copyright Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://github.com/sebastianbergmann/php-token-stream/tree * @since Class available since Release 1.0.0 */ abstract class PHP_Token { /** * @var string */ protected $text; /** * @var integer */ protected $line; /** * @var PHP_Token_Stream */ protected $tokenStream; /** * @var integer */ protected $id; /** * Constructor. * * @param string $text * @param integer $line * @param PHP_Token_Stream $tokenStream * @param integer $id */ public function __construct($text, $line, PHP_Token_Stream $tokenStream, $id) { $this->text = $text; $this->line = $line; $this->tokenStream = $tokenStream; $this->id = $id; } /** * @return string */ public function __toString() { return $this->text; } /** * @return integer */ public function getLine() { return $this->line; } } abstract class PHP_TokenWithScope extends PHP_Token { /** * @var integer */ protected $endTokenId; /** * Get the docblock for this token * * This method will fetch the docblock belonging to the current token. The * docblock must be placed on the line directly above the token to be * recognized. * * @return string|null Returns the docblock as a string if found */ public function getDocblock() { $tokens = $this->tokenStream->tokens(); $currentLineNumber = $tokens[$this->id]->getLine(); $prevLineNumber = $currentLineNumber - 1; for ($i = $this->id - 1; $i; $i--) { if (!isset($tokens[$i])) { return; } if ($tokens[$i] instanceof PHP_Token_FUNCTION || $tokens[$i] instanceof PHP_Token_CLASS || $tokens[$i] instanceof PHP_Token_TRAIT) { // Some other trait, class or function, no docblock can be // used for the current token break; } $line = $tokens[$i]->getLine(); if ($line == $currentLineNumber || ($line == $prevLineNumber && $tokens[$i] instanceof PHP_Token_WHITESPACE)) { continue; } if ($line < $currentLineNumber && !$tokens[$i] instanceof PHP_Token_DOC_COMMENT) { break; } return (string)$tokens[$i]; } } /** * @return integer */ public function getEndTokenId() { $block = 0; $i = $this->id; $tokens = $this->tokenStream->tokens(); while ($this->endTokenId === null && isset($tokens[$i])) { if ($tokens[$i] instanceof PHP_Token_OPEN_CURLY || $tokens[$i] instanceof PHP_Token_CURLY_OPEN) { $block++; } elseif ($tokens[$i] instanceof PHP_Token_CLOSE_CURLY) { $block--; if ($block === 0) { $this->endTokenId = $i; } } elseif (($this instanceof PHP_Token_FUNCTION || $this instanceof PHP_Token_NAMESPACE) && $tokens[$i] instanceof PHP_Token_SEMICOLON) { if ($block === 0) { $this->endTokenId = $i; } } $i++; } if ($this->endTokenId === null) { $this->endTokenId = $this->id; } return $this->endTokenId; } /** * @return integer */ public function getEndLine() { return $this->tokenStream[$this->getEndTokenId()]->getLine(); } } abstract class PHP_TokenWithScopeAndVisibility extends PHP_TokenWithScope { /** * @return string */ public function getVisibility() { $tokens = $this->tokenStream->tokens(); for ($i = $this->id - 2; $i > $this->id - 7; $i -= 2) { if (isset($tokens[$i]) && ($tokens[$i] instanceof PHP_Token_PRIVATE || $tokens[$i] instanceof PHP_Token_PROTECTED || $tokens[$i] instanceof PHP_Token_PUBLIC)) { return strtolower( str_replace('PHP_Token_', '', get_class($tokens[$i])) ); } if (isset($tokens[$i]) && !($tokens[$i] instanceof PHP_Token_STATIC || $tokens[$i] instanceof PHP_Token_FINAL || $tokens[$i] instanceof PHP_Token_ABSTRACT)) { // no keywords; stop visibility search break; } } } /** * @return string */ public function getKeywords() { $keywords = array(); $tokens = $this->tokenStream->tokens(); for ($i = $this->id - 2; $i > $this->id - 7; $i -= 2) { if (isset($tokens[$i]) && ($tokens[$i] instanceof PHP_Token_PRIVATE || $tokens[$i] instanceof PHP_Token_PROTECTED || $tokens[$i] instanceof PHP_Token_PUBLIC)) { continue; } if (isset($tokens[$i]) && ($tokens[$i] instanceof PHP_Token_STATIC || $tokens[$i] instanceof PHP_Token_FINAL || $tokens[$i] instanceof PHP_Token_ABSTRACT)) { $keywords[] = strtolower( str_replace('PHP_Token_', '', get_class($tokens[$i])) ); } } return implode(',', $keywords); } } abstract class PHP_Token_Includes extends PHP_Token { /** * @var string */ protected $name; /** * @var string */ protected $type; /** * @return string */ public function getName() { if ($this->name === null) { $this->process(); } return $this->name; } /** * @return string */ public function getType() { if ($this->type === null) { $this->process(); } return $this->type; } private function process() { $tokens = $this->tokenStream->tokens(); if ($tokens[$this->id+2] instanceof PHP_Token_CONSTANT_ENCAPSED_STRING) { $this->name = trim($tokens[$this->id+2], "'\""); $this->type = strtolower( str_replace('PHP_Token_', '', get_class($tokens[$this->id])) ); } } } class PHP_Token_FUNCTION extends PHP_TokenWithScopeAndVisibility { /** * @var array */ protected $arguments; /** * @var integer */ protected $ccn; /** * @var string */ protected $name; /** * @var string */ protected $signature; /** * @return array */ public function getArguments() { if ($this->arguments !== null) { return $this->arguments; } $this->arguments = array(); $tokens = $this->tokenStream->tokens(); $typeDeclaration = null; // Search for first token inside brackets $i = $this->id + 2; while (!$tokens[$i-1] instanceof PHP_Token_OPEN_BRACKET) { $i++; } while (!$tokens[$i] instanceof PHP_Token_CLOSE_BRACKET) { if ($tokens[$i] instanceof PHP_Token_STRING) { $typeDeclaration = (string)$tokens[$i]; } elseif ($tokens[$i] instanceof PHP_Token_VARIABLE) { $this->arguments[(string)$tokens[$i]] = $typeDeclaration; $typeDeclaration = null; } $i++; } return $this->arguments; } /** * @return string */ public function getName() { if ($this->name !== null) { return $this->name; } $tokens = $this->tokenStream->tokens(); for ($i = $this->id + 1; $i < count($tokens); $i++) { if ($tokens[$i] instanceof PHP_Token_STRING) { $this->name = (string)$tokens[$i]; break; } elseif ($tokens[$i] instanceof PHP_Token_AMPERSAND && $tokens[$i+1] instanceof PHP_Token_STRING) { $this->name = (string)$tokens[$i+1]; break; } elseif ($tokens[$i] instanceof PHP_Token_OPEN_BRACKET) { $this->name = 'anonymous function'; break; } } if ($this->name != 'anonymous function') { for ($i = $this->id; $i; --$i) { if ($tokens[$i] instanceof PHP_Token_NAMESPACE) { $this->name = $tokens[$i]->getName() . '\\' . $this->name; break; } if ($tokens[$i] instanceof PHP_Token_INTERFACE) { break; } } } return $this->name; } /** * @return integer */ public function getCCN() { if ($this->ccn !== null) { return $this->ccn; } $this->ccn = 1; $end = $this->getEndTokenId(); $tokens = $this->tokenStream->tokens(); for ($i = $this->id; $i <= $end; $i++) { switch (get_class($tokens[$i])) { case 'PHP_Token_IF': case 'PHP_Token_ELSEIF': case 'PHP_Token_FOR': case 'PHP_Token_FOREACH': case 'PHP_Token_WHILE': case 'PHP_Token_CASE': case 'PHP_Token_CATCH': case 'PHP_Token_BOOLEAN_AND': case 'PHP_Token_LOGICAL_AND': case 'PHP_Token_BOOLEAN_OR': case 'PHP_Token_LOGICAL_OR': case 'PHP_Token_QUESTION_MARK': $this->ccn++; break; } } return $this->ccn; } /** * @return string */ public function getSignature() { if ($this->signature !== null) { return $this->signature; } if ($this->getName() == 'anonymous function') { $this->signature = 'anonymous function'; $i = $this->id + 1; } else { $this->signature = ''; $i = $this->id + 2; } $tokens = $this->tokenStream->tokens(); while (isset($tokens[$i]) && !$tokens[$i] instanceof PHP_Token_OPEN_CURLY && !$tokens[$i] instanceof PHP_Token_SEMICOLON) { $this->signature .= $tokens[$i++]; } $this->signature = trim($this->signature); return $this->signature; } } class PHP_Token_INTERFACE extends PHP_TokenWithScopeAndVisibility { /** * @var array */ protected $interfaces; /** * @return string */ public function getName() { return (string)$this->tokenStream[$this->id + 2]; } /** * @return boolean */ public function hasParent() { return $this->tokenStream[$this->id + 4] instanceof PHP_Token_EXTENDS; } /** * @return array */ public function getPackage() { $className = $this->getName(); $docComment = $this->getDocblock(); $result = array( 'namespace' => '', 'fullPackage' => '', 'category' => '', 'package' => '', 'subpackage' => '' ); for ($i = $this->id; $i; --$i) { if ($this->tokenStream[$i] instanceof PHP_Token_NAMESPACE) { $result['namespace'] = $this->tokenStream[$i]->getName(); break; } } if (preg_match('/@category[\s]+([\.\w]+)/', $docComment, $matches)) { $result['category'] = $matches[1]; } if (preg_match('/@package[\s]+([\.\w]+)/', $docComment, $matches)) { $result['package'] = $matches[1]; $result['fullPackage'] = $matches[1]; } if (preg_match('/@subpackage[\s]+([\.\w]+)/', $docComment, $matches)) { $result['subpackage'] = $matches[1]; $result['fullPackage'] .= '.' . $matches[1]; } if (empty($result['fullPackage'])) { $result['fullPackage'] = $this->arrayToName( explode('_', str_replace('\\', '_', $className)), '.' ); } return $result; } /** * @param array $parts * @param string $join * @return string */ protected function arrayToName(array $parts, $join = '\\') { $result = ''; if (count($parts) > 1) { array_pop($parts); $result = join($join, $parts); } return $result; } /** * @return boolean|string */ public function getParent() { if (!$this->hasParent()) { return false; } $i = $this->id + 6; $tokens = $this->tokenStream->tokens(); $className = (string)$tokens[$i]; while (isset($tokens[$i+1]) && !$tokens[$i+1] instanceof PHP_Token_WHITESPACE) { $className .= (string)$tokens[++$i]; } return $className; } /** * @return boolean */ public function hasInterfaces() { return (isset($this->tokenStream[$this->id + 4]) && $this->tokenStream[$this->id + 4] instanceof PHP_Token_IMPLEMENTS) || (isset($this->tokenStream[$this->id + 8]) && $this->tokenStream[$this->id + 8] instanceof PHP_Token_IMPLEMENTS); } /** * @return array|boolean */ public function getInterfaces() { if ($this->interfaces !== null) { return $this->interfaces; } if (!$this->hasInterfaces()) { return ($this->interfaces = false); } if ($this->tokenStream[$this->id + 4] instanceof PHP_Token_IMPLEMENTS) { $i = $this->id + 3; } else { $i = $this->id + 7; } $tokens = $this->tokenStream->tokens(); while (!$tokens[$i+1] instanceof PHP_Token_OPEN_CURLY) { $i++; if ($tokens[$i] instanceof PHP_Token_STRING) { $this->interfaces[] = (string)$tokens[$i]; } } return $this->interfaces; } } class PHP_Token_ABSTRACT extends PHP_Token {} class PHP_Token_AMPERSAND extends PHP_Token {} class PHP_Token_AND_EQUAL extends PHP_Token {} class PHP_Token_ARRAY extends PHP_Token {} class PHP_Token_ARRAY_CAST extends PHP_Token {} class PHP_Token_AS extends PHP_Token {} class PHP_Token_AT extends PHP_Token {} class PHP_Token_BACKTICK extends PHP_Token {} class PHP_Token_BAD_CHARACTER extends PHP_Token {} class PHP_Token_BOOLEAN_AND extends PHP_Token {} class PHP_Token_BOOLEAN_OR extends PHP_Token {} class PHP_Token_BOOL_CAST extends PHP_Token {} class PHP_Token_BREAK extends PHP_Token {} class PHP_Token_CARET extends PHP_Token {} class PHP_Token_CASE extends PHP_Token {} class PHP_Token_CATCH extends PHP_Token {} class PHP_Token_CHARACTER extends PHP_Token {} class PHP_Token_CLASS extends PHP_Token_INTERFACE { /** * @return string */ public function getName() { $next = $this->tokenStream[$this->id + 1]; if ($next instanceof PHP_Token_WHITESPACE) { $next = $this->tokenStream[$this->id + 2]; } if ($next instanceof PHP_Token_STRING) { return (string) $next; } if ($next instanceof PHP_Token_OPEN_CURLY || $next instanceof PHP_Token_EXTENDS || $next instanceof PHP_Token_IMPLEMENTS) { return 'anonymous class'; } } } class PHP_Token_CLASS_C extends PHP_Token {} class PHP_Token_CLASS_NAME_CONSTANT extends PHP_Token {} class PHP_Token_CLONE extends PHP_Token {} class PHP_Token_CLOSE_BRACKET extends PHP_Token {} class PHP_Token_CLOSE_CURLY extends PHP_Token {} class PHP_Token_CLOSE_SQUARE extends PHP_Token {} class PHP_Token_CLOSE_TAG extends PHP_Token {} class PHP_Token_COLON extends PHP_Token {} class PHP_Token_COMMA extends PHP_Token {} class PHP_Token_COMMENT extends PHP_Token {} class PHP_Token_CONCAT_EQUAL extends PHP_Token {} class PHP_Token_CONST extends PHP_Token {} class PHP_Token_CONSTANT_ENCAPSED_STRING extends PHP_Token {} class PHP_Token_CONTINUE extends PHP_Token {} class PHP_Token_CURLY_OPEN extends PHP_Token {} class PHP_Token_DEC extends PHP_Token {} class PHP_Token_DECLARE extends PHP_Token {} class PHP_Token_DEFAULT extends PHP_Token {} class PHP_Token_DIV extends PHP_Token {} class PHP_Token_DIV_EQUAL extends PHP_Token {} class PHP_Token_DNUMBER extends PHP_Token {} class PHP_Token_DO extends PHP_Token {} class PHP_Token_DOC_COMMENT extends PHP_Token {} class PHP_Token_DOLLAR extends PHP_Token {} class PHP_Token_DOLLAR_OPEN_CURLY_BRACES extends PHP_Token {} class PHP_Token_DOT extends PHP_Token {} class PHP_Token_DOUBLE_ARROW extends PHP_Token {} class PHP_Token_DOUBLE_CAST extends PHP_Token {} class PHP_Token_DOUBLE_COLON extends PHP_Token {} class PHP_Token_DOUBLE_QUOTES extends PHP_Token {} class PHP_Token_ECHO extends PHP_Token {} class PHP_Token_ELSE extends PHP_Token {} class PHP_Token_ELSEIF extends PHP_Token {} class PHP_Token_EMPTY extends PHP_Token {} class PHP_Token_ENCAPSED_AND_WHITESPACE extends PHP_Token {} class PHP_Token_ENDDECLARE extends PHP_Token {} class PHP_Token_ENDFOR extends PHP_Token {} class PHP_Token_ENDFOREACH extends PHP_Token {} class PHP_Token_ENDIF extends PHP_Token {} class PHP_Token_ENDSWITCH extends PHP_Token {} class PHP_Token_ENDWHILE extends PHP_Token {} class PHP_Token_END_HEREDOC extends PHP_Token {} class PHP_Token_EQUAL extends PHP_Token {} class PHP_Token_EVAL extends PHP_Token {} class PHP_Token_EXCLAMATION_MARK extends PHP_Token {} class PHP_Token_EXIT extends PHP_Token {} class PHP_Token_EXTENDS extends PHP_Token {} class PHP_Token_FILE extends PHP_Token {} class PHP_Token_FINAL extends PHP_Token {} class PHP_Token_FOR extends PHP_Token {} class PHP_Token_FOREACH extends PHP_Token {} class PHP_Token_FUNC_C extends PHP_Token {} class PHP_Token_GLOBAL extends PHP_Token {} class PHP_Token_GT extends PHP_Token {} class PHP_Token_IF extends PHP_Token {} class PHP_Token_IMPLEMENTS extends PHP_Token {} class PHP_Token_INC extends PHP_Token {} class PHP_Token_INCLUDE extends PHP_Token_Includes {} class PHP_Token_INCLUDE_ONCE extends PHP_Token_Includes {} class PHP_Token_INLINE_HTML extends PHP_Token {} class PHP_Token_INSTANCEOF extends PHP_Token {} class PHP_Token_INT_CAST extends PHP_Token {} class PHP_Token_ISSET extends PHP_Token {} class PHP_Token_IS_EQUAL extends PHP_Token {} class PHP_Token_IS_GREATER_OR_EQUAL extends PHP_Token {} class PHP_Token_IS_IDENTICAL extends PHP_Token {} class PHP_Token_IS_NOT_EQUAL extends PHP_Token {} class PHP_Token_IS_NOT_IDENTICAL extends PHP_Token {} class PHP_Token_IS_SMALLER_OR_EQUAL extends PHP_Token {} class PHP_Token_LINE extends PHP_Token {} class PHP_Token_LIST extends PHP_Token {} class PHP_Token_LNUMBER extends PHP_Token {} class PHP_Token_LOGICAL_AND extends PHP_Token {} class PHP_Token_LOGICAL_OR extends PHP_Token {} class PHP_Token_LOGICAL_XOR extends PHP_Token {} class PHP_Token_LT extends PHP_Token {} class PHP_Token_METHOD_C extends PHP_Token {} class PHP_Token_MINUS extends PHP_Token {} class PHP_Token_MINUS_EQUAL extends PHP_Token {} class PHP_Token_MOD_EQUAL extends PHP_Token {} class PHP_Token_MULT extends PHP_Token {} class PHP_Token_MUL_EQUAL extends PHP_Token {} class PHP_Token_NEW extends PHP_Token {} class PHP_Token_NUM_STRING extends PHP_Token {} class PHP_Token_OBJECT_CAST extends PHP_Token {} class PHP_Token_OBJECT_OPERATOR extends PHP_Token {} class PHP_Token_OPEN_BRACKET extends PHP_Token {} class PHP_Token_OPEN_CURLY extends PHP_Token {} class PHP_Token_OPEN_SQUARE extends PHP_Token {} class PHP_Token_OPEN_TAG extends PHP_Token {} class PHP_Token_OPEN_TAG_WITH_ECHO extends PHP_Token {} class PHP_Token_OR_EQUAL extends PHP_Token {} class PHP_Token_PAAMAYIM_NEKUDOTAYIM extends PHP_Token {} class PHP_Token_PERCENT extends PHP_Token {} class PHP_Token_PIPE extends PHP_Token {} class PHP_Token_PLUS extends PHP_Token {} class PHP_Token_PLUS_EQUAL extends PHP_Token {} class PHP_Token_PRINT extends PHP_Token {} class PHP_Token_PRIVATE extends PHP_Token {} class PHP_Token_PROTECTED extends PHP_Token {} class PHP_Token_PUBLIC extends PHP_Token {} class PHP_Token_QUESTION_MARK extends PHP_Token {} class PHP_Token_REQUIRE extends PHP_Token_Includes {} class PHP_Token_REQUIRE_ONCE extends PHP_Token_Includes {} class PHP_Token_RETURN extends PHP_Token {} class PHP_Token_SEMICOLON extends PHP_Token {} class PHP_Token_SL extends PHP_Token {} class PHP_Token_SL_EQUAL extends PHP_Token {} class PHP_Token_SR extends PHP_Token {} class PHP_Token_SR_EQUAL extends PHP_Token {} class PHP_Token_START_HEREDOC extends PHP_Token {} class PHP_Token_STATIC extends PHP_Token {} class PHP_Token_STRING extends PHP_Token {} class PHP_Token_STRING_CAST extends PHP_Token {} class PHP_Token_STRING_VARNAME extends PHP_Token {} class PHP_Token_SWITCH extends PHP_Token {} class PHP_Token_THROW extends PHP_Token {} class PHP_Token_TILDE extends PHP_Token {} class PHP_Token_TRY extends PHP_Token {} class PHP_Token_UNSET extends PHP_Token {} class PHP_Token_UNSET_CAST extends PHP_Token {} class PHP_Token_USE extends PHP_Token {} class PHP_Token_VAR extends PHP_Token {} class PHP_Token_VARIABLE extends PHP_Token {} class PHP_Token_WHILE extends PHP_Token {} class PHP_Token_WHITESPACE extends PHP_Token {} class PHP_Token_XOR_EQUAL extends PHP_Token {} // Tokens introduced in PHP 5.1 class PHP_Token_HALT_COMPILER extends PHP_Token {} // Tokens introduced in PHP 5.3 class PHP_Token_DIR extends PHP_Token {} class PHP_Token_GOTO extends PHP_Token {} class PHP_Token_NAMESPACE extends PHP_TokenWithScope { /** * @return string */ public function getName() { $tokens = $this->tokenStream->tokens(); $namespace = (string)$tokens[$this->id+2]; for ($i = $this->id + 3;; $i += 2) { if (isset($tokens[$i]) && $tokens[$i] instanceof PHP_Token_NS_SEPARATOR) { $namespace .= '\\' . $tokens[$i+1]; } else { break; } } return $namespace; } } class PHP_Token_NS_C extends PHP_Token {} class PHP_Token_NS_SEPARATOR extends PHP_Token {} // Tokens introduced in PHP 5.4 class PHP_Token_CALLABLE extends PHP_Token {} class PHP_Token_INSTEADOF extends PHP_Token {} class PHP_Token_TRAIT extends PHP_Token_INTERFACE {} class PHP_Token_TRAIT_C extends PHP_Token {} // Tokens introduced in PHP 5.5 class PHP_Token_FINALLY extends PHP_Token {} class PHP_Token_YIELD extends PHP_Token {} // Tokens introduced in PHP 5.6 class PHP_Token_ELLIPSIS extends PHP_Token {} class PHP_Token_POW extends PHP_Token {} class PHP_Token_POW_EQUAL extends PHP_Token {} // Tokens introduced in PHP 7.0 class PHP_Token_COALESCE extends PHP_Token {} class PHP_Token_SPACESHIP extends PHP_Token {} class PHP_Token_YIELD_FROM extends PHP_Token {} // Tokens introduced in HackLang / HHVM class PHP_Token_ASYNC extends PHP_Token {} class PHP_Token_AWAIT extends PHP_Token {} class PHP_Token_COMPILER_HALT_OFFSET extends PHP_Token {} class PHP_Token_ENUM extends PHP_Token {} class PHP_Token_EQUALS extends PHP_Token {} class PHP_Token_IN extends PHP_Token {} class PHP_Token_JOIN extends PHP_Token {} class PHP_Token_LAMBDA_ARROW extends PHP_Token {} class PHP_Token_LAMBDA_CP extends PHP_Token {} class PHP_Token_LAMBDA_OP extends PHP_Token {} class PHP_Token_ONUMBER extends PHP_Token {} class PHP_Token_SHAPE extends PHP_Token {} class PHP_Token_TYPE extends PHP_Token {} class PHP_Token_TYPELIST_GT extends PHP_Token {} class PHP_Token_TYPELIST_LT extends PHP_Token {} class PHP_Token_WHERE extends PHP_Token {} class PHP_Token_XHP_ATTRIBUTE extends PHP_Token {} class PHP_Token_XHP_CATEGORY extends PHP_Token {} class PHP_Token_XHP_CATEGORY_LABEL extends PHP_Token {} class PHP_Token_XHP_CHILDREN extends PHP_Token {} class PHP_Token_XHP_LABEL extends PHP_Token {} class PHP_Token_XHP_REQUIRED extends PHP_Token {} class PHP_Token_XHP_TAG_GT extends PHP_Token {} class PHP_Token_XHP_TAG_LT extends PHP_Token {} class PHP_Token_XHP_TEXT extends PHP_Token {} * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Main matcher which defines a full expectation using method, parameter and * invocation matchers. * This matcher encapsulates all the other matchers and allows the builder to * set the specific matchers when the appropriate methods are called (once(), * where() etc.). * * All properties are public so that they can easily be accessed by the builder. * * @since Class available since Release 1.0.0 */ class PHPUnit_Framework_MockObject_Matcher implements PHPUnit_Framework_MockObject_Matcher_Invocation { /** * @var PHPUnit_Framework_MockObject_Matcher_Invocation */ public $invocationMatcher; /** * @var mixed */ public $afterMatchBuilderId = null; /** * @var bool */ public $afterMatchBuilderIsInvoked = false; /** * @var PHPUnit_Framework_MockObject_Matcher_MethodName */ public $methodNameMatcher = null; /** * @var PHPUnit_Framework_MockObject_Matcher_Parameters */ public $parametersMatcher = null; /** * @var PHPUnit_Framework_MockObject_Stub */ public $stub = null; /** * @param PHPUnit_Framework_MockObject_Matcher_Invocation $invocationMatcher */ public function __construct(PHPUnit_Framework_MockObject_Matcher_Invocation $invocationMatcher) { $this->invocationMatcher = $invocationMatcher; } /** * @return string */ public function toString() { $list = array(); if ($this->invocationMatcher !== null) { $list[] = $this->invocationMatcher->toString(); } if ($this->methodNameMatcher !== null) { $list[] = 'where ' . $this->methodNameMatcher->toString(); } if ($this->parametersMatcher !== null) { $list[] = 'and ' . $this->parametersMatcher->toString(); } if ($this->afterMatchBuilderId !== null) { $list[] = 'after ' . $this->afterMatchBuilderId; } if ($this->stub !== null) { $list[] = 'will ' . $this->stub->toString(); } return implode(' ', $list); } /** * @param PHPUnit_Framework_MockObject_Invocation $invocation * @return mixed */ public function invoked(PHPUnit_Framework_MockObject_Invocation $invocation) { if ($this->invocationMatcher === null) { throw new PHPUnit_Framework_Exception( 'No invocation matcher is set' ); } if ($this->methodNameMatcher === null) { throw new PHPUnit_Framework_Exception('No method matcher is set'); } if ($this->afterMatchBuilderId !== null) { $builder = $invocation->object ->__phpunit_getInvocationMocker() ->lookupId($this->afterMatchBuilderId); if (!$builder) { throw new PHPUnit_Framework_Exception( sprintf( 'No builder found for match builder identification <%s>', $this->afterMatchBuilderId ) ); } $matcher = $builder->getMatcher(); if ($matcher && $matcher->invocationMatcher->hasBeenInvoked()) { $this->afterMatchBuilderIsInvoked = true; } } $this->invocationMatcher->invoked($invocation); try { if ($this->parametersMatcher !== null && !$this->parametersMatcher->matches($invocation)) { $this->parametersMatcher->verify(); } } catch (PHPUnit_Framework_ExpectationFailedException $e) { throw new PHPUnit_Framework_ExpectationFailedException( sprintf( "Expectation failed for %s when %s\n%s", $this->methodNameMatcher->toString(), $this->invocationMatcher->toString(), $e->getMessage() ), $e->getComparisonFailure() ); } if ($this->stub) { return $this->stub->invoke($invocation); } return; } /** * @param PHPUnit_Framework_MockObject_Invocation $invocation * @return bool */ public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) { if ($this->afterMatchBuilderId !== null) { $builder = $invocation->object ->__phpunit_getInvocationMocker() ->lookupId($this->afterMatchBuilderId); if (!$builder) { throw new PHPUnit_Framework_Exception( sprintf( 'No builder found for match builder identification <%s>', $this->afterMatchBuilderId ) ); } $matcher = $builder->getMatcher(); if (!$matcher) { return false; } if (!$matcher->invocationMatcher->hasBeenInvoked()) { return false; } } if ($this->invocationMatcher === null) { throw new PHPUnit_Framework_Exception( 'No invocation matcher is set' ); } if ($this->methodNameMatcher === null) { throw new PHPUnit_Framework_Exception('No method matcher is set'); } if (!$this->invocationMatcher->matches($invocation)) { return false; } try { if (!$this->methodNameMatcher->matches($invocation)) { return false; } } catch (PHPUnit_Framework_ExpectationFailedException $e) { throw new PHPUnit_Framework_ExpectationFailedException( sprintf( "Expectation failed for %s when %s\n%s", $this->methodNameMatcher->toString(), $this->invocationMatcher->toString(), $e->getMessage() ), $e->getComparisonFailure() ); } return true; } /** * @throws PHPUnit_Framework_Exception * @throws PHPUnit_Framework_ExpectationFailedException */ public function verify() { if ($this->invocationMatcher === null) { throw new PHPUnit_Framework_Exception( 'No invocation matcher is set' ); } if ($this->methodNameMatcher === null) { throw new PHPUnit_Framework_Exception('No method matcher is set'); } try { $this->invocationMatcher->verify(); if ($this->parametersMatcher === null) { $this->parametersMatcher = new PHPUnit_Framework_MockObject_Matcher_AnyParameters; } $invocationIsAny = get_class($this->invocationMatcher) === 'PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount'; $invocationIsNever = get_class($this->invocationMatcher) === 'PHPUnit_Framework_MockObject_Matcher_InvokedCount' && $this->invocationMatcher->isNever(); if (!$invocationIsAny && !$invocationIsNever) { $this->parametersMatcher->verify(); } } catch (PHPUnit_Framework_ExpectationFailedException $e) { throw new PHPUnit_Framework_ExpectationFailedException( sprintf( "Expectation failed for %s when %s.\n%s", $this->methodNameMatcher->toString(), $this->invocationMatcher->toString(), PHPUnit_Framework_TestFailure::exceptionToString($e) ) ); } } /** * @since Method available since Release 1.2.4 */ public function hasMatchers() { if ($this->invocationMatcher !== null && !$this->invocationMatcher instanceof PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount) { return true; } return false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Interface for classes which must verify a given expectation. * * @since Interface available since Release 1.0.0 */ interface PHPUnit_Framework_MockObject_Verifiable { /** * Verifies that the current expectation is valid. If everything is OK the * code should just return, if not it must throw an exception. * * @throws PHPUnit_Framework_ExpectationFailedException */ public function verify(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Interface for classes which can be invoked. * * The invocation will be taken from a mock object and passed to an object * of this class. * * @since Interface available since Release 1.0.0 */ interface PHPUnit_Framework_MockObject_Invokable extends PHPUnit_Framework_MockObject_Verifiable { /** * Invokes the invocation object $invocation so that it can be checked for * expectations or matched against stubs. * * @param PHPUnit_Framework_MockObject_Invocation $invocation * The invocation object passed from mock object. * @return object */ public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation); /** * Checks if the invocation matches. * * @param PHPUnit_Framework_MockObject_Invocation $invocation * The invocation object passed from mock object. * @return bool */ public function matches(PHPUnit_Framework_MockObject_Invocation $invocation); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Implementation of the Builder pattern for Mock objects. * * @since File available since Release 1.0.0 */ class PHPUnit_Framework_MockObject_MockBuilder { /** * @var PHPUnit_Framework_TestCase */ private $testCase; /** * @var string */ private $type; /** * @var array */ private $methods = array(); /** * @var string */ private $mockClassName = ''; /** * @var array */ private $constructorArgs = array(); /** * @var bool */ private $originalConstructor = true; /** * @var bool */ private $originalClone = true; /** * @var bool */ private $autoload = true; /** * @var bool */ private $cloneArguments = false; /** * @var bool */ private $callOriginalMethods = false; /** * @var object */ private $proxyTarget = null; /** * @param PHPUnit_Framework_TestCase $testCase * @param array|string $type */ public function __construct(PHPUnit_Framework_TestCase $testCase, $type) { $this->testCase = $testCase; $this->type = $type; } /** * Creates a mock object using a fluent interface. * * @return PHPUnit_Framework_MockObject_MockObject */ public function getMock() { return $this->testCase->getMock( $this->type, $this->methods, $this->constructorArgs, $this->mockClassName, $this->originalConstructor, $this->originalClone, $this->autoload, $this->cloneArguments, $this->callOriginalMethods, $this->proxyTarget ); } /** * Creates a mock object for an abstract class using a fluent interface. * * @return PHPUnit_Framework_MockObject_MockObject */ public function getMockForAbstractClass() { return $this->testCase->getMockForAbstractClass( $this->type, $this->constructorArgs, $this->mockClassName, $this->originalConstructor, $this->originalClone, $this->autoload, $this->methods, $this->cloneArguments ); } /** * Creates a mock object for a trait using a fluent interface. * * @return PHPUnit_Framework_MockObject_MockObject */ public function getMockForTrait() { return $this->testCase->getMockForTrait( $this->type, $this->constructorArgs, $this->mockClassName, $this->originalConstructor, $this->originalClone, $this->autoload, $this->methods, $this->cloneArguments ); } /** * Specifies the subset of methods to mock. Default is to mock all of them. * * @param array|null $methods * @return PHPUnit_Framework_MockObject_MockBuilder */ public function setMethods($methods) { $this->methods = $methods; return $this; } /** * Specifies the arguments for the constructor. * * @param array $args * @return PHPUnit_Framework_MockObject_MockBuilder */ public function setConstructorArgs(array $args) { $this->constructorArgs = $args; return $this; } /** * Specifies the name for the mock class. * * @param string $name * @return PHPUnit_Framework_MockObject_MockBuilder */ public function setMockClassName($name) { $this->mockClassName = $name; return $this; } /** * Disables the invocation of the original constructor. * * @return PHPUnit_Framework_MockObject_MockBuilder */ public function disableOriginalConstructor() { $this->originalConstructor = false; return $this; } /** * Enables the invocation of the original constructor. * * @return PHPUnit_Framework_MockObject_MockBuilder * @since Method available since Release 1.2.0 */ public function enableOriginalConstructor() { $this->originalConstructor = true; return $this; } /** * Disables the invocation of the original clone constructor. * * @return PHPUnit_Framework_MockObject_MockBuilder */ public function disableOriginalClone() { $this->originalClone = false; return $this; } /** * Enables the invocation of the original clone constructor. * * @return PHPUnit_Framework_MockObject_MockBuilder * @since Method available since Release 1.2.0 */ public function enableOriginalClone() { $this->originalClone = true; return $this; } /** * Disables the use of class autoloading while creating the mock object. * * @return PHPUnit_Framework_MockObject_MockBuilder */ public function disableAutoload() { $this->autoload = false; return $this; } /** * Enables the use of class autoloading while creating the mock object. * * @return PHPUnit_Framework_MockObject_MockBuilder * @since Method available since Release 1.2.0 */ public function enableAutoload() { $this->autoload = true; return $this; } /** * Disables the cloning of arguments passed to mocked methods. * * @return PHPUnit_Framework_MockObject_MockBuilder * @since Method available since Release 1.2.0 */ public function disableArgumentCloning() { $this->cloneArguments = false; return $this; } /** * Enables the cloning of arguments passed to mocked methods. * * @return PHPUnit_Framework_MockObject_MockBuilder * @since Method available since Release 1.2.0 */ public function enableArgumentCloning() { $this->cloneArguments = true; return $this; } /** * Enables the invocation of the original methods. * * @return PHPUnit_Framework_MockObject_MockBuilder * @since Method available since Release 2.0.0 */ public function enableProxyingToOriginalMethods() { $this->callOriginalMethods = true; return $this; } /** * Disables the invocation of the original methods. * * @return PHPUnit_Framework_MockObject_MockBuilder * @since Method available since Release 2.0.0 */ public function disableProxyingToOriginalMethods() { $this->callOriginalMethods = false; $this->proxyTarget = null; return $this; } /** * Sets the proxy target. * * @param object $object * @return PHPUnit_Framework_MockObject_MockBuilder * @since Method available since Release 2.0.0 */ public function setProxyTarget($object) { $this->proxyTarget = $object; return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Interface for exceptions used by PHPUnit_MockObject. * * @since Interface available since Release 2.0.6 */ interface PHPUnit_Framework_MockObject_Exception { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 2.0.6 */ class PHPUnit_Framework_MockObject_BadMethodCallException extends BadMethodCallException implements PHPUnit_Framework_MockObject_Exception { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 2.0.6 */ class PHPUnit_Framework_MockObject_RuntimeException extends RuntimeException implements PHPUnit_Framework_MockObject_Exception { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Interface for invocations. * * @since Interface available since Release 1.0.0 */ interface PHPUnit_Framework_MockObject_Invocation { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Interface for all mock objects which are generated by * PHPUnit_Framework_MockObject_MockBuilder. * * @method PHPUnit_Framework_MockObject_Builder_InvocationMocker method($constraint) * @since Interface available since Release 1.0.0 */ interface PHPUnit_Framework_MockObject_MockObject /*extends PHPUnit_Framework_MockObject_Verifiable*/ { /** * Registers a new expectation in the mock object and returns the match * object which can be infused with further details. * * @param PHPUnit_Framework_MockObject_Matcher_Invocation $matcher * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker */ public function expects(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher); /** * @return PHPUnit_Framework_MockObject_InvocationMocker * @since Method available since Release 2.0.0 */ public function __phpunit_setOriginalObject($originalObject); /** * @return PHPUnit_Framework_MockObject_InvocationMocker */ public function __phpunit_getInvocationMocker(); /** * Verifies that the current expectation is valid. If everything is OK the * code should just return, if not it must throw an exception. * * @throws PHPUnit_Framework_ExpectationFailedException */ public function __phpunit_verify(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Mocker for invocations which are sent from * PHPUnit_Framework_MockObject_MockObject objects. * * Keeps track of all expectations and stubs as well as registering * identifications for builders. * * @since Class available since Release 1.0.0 */ class PHPUnit_Framework_MockObject_InvocationMocker implements PHPUnit_Framework_MockObject_Stub_MatcherCollection, PHPUnit_Framework_MockObject_Invokable, PHPUnit_Framework_MockObject_Builder_Namespace { /** * @var PHPUnit_Framework_MockObject_Matcher_Invocation[] */ protected $matchers = array(); /** * @var PHPUnit_Framework_MockObject_Builder_Match[] */ protected $builderMap = array(); /** * @param PHPUnit_Framework_MockObject_Matcher_Invocation $matcher */ public function addMatcher(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher) { $this->matchers[] = $matcher; } /** * @since Method available since Release 1.1.0 */ public function hasMatchers() { foreach ($this->matchers as $matcher) { if ($matcher->hasMatchers()) { return true; } } return false; } /** * @param mixed $id * @return bool|null */ public function lookupId($id) { if (isset($this->builderMap[$id])) { return $this->builderMap[$id]; } return; } /** * @param mixed $id * @param PHPUnit_Framework_MockObject_Builder_Match $builder * @throws PHPUnit_Framework_Exception */ public function registerId($id, PHPUnit_Framework_MockObject_Builder_Match $builder) { if (isset($this->builderMap[$id])) { throw new PHPUnit_Framework_Exception( 'Match builder with id <' . $id . '> is already registered.' ); } $this->builderMap[$id] = $builder; } /** * @param PHPUnit_Framework_MockObject_Matcher_Invocation $matcher * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker */ public function expects(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher) { return new PHPUnit_Framework_MockObject_Builder_InvocationMocker( $this, $matcher ); } /** * @param PHPUnit_Framework_MockObject_Invocation $invocation * @return mixed */ public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation) { $exception = null; $hasReturnValue = false; if (strtolower($invocation->methodName) == '__tostring') { $returnValue = ''; } else { $returnValue = null; } foreach ($this->matchers as $match) { try { if ($match->matches($invocation)) { $value = $match->invoked($invocation); if (!$hasReturnValue) { $returnValue = $value; $hasReturnValue = true; } } } catch (Exception $e) { $exception = $e; } } if ($exception !== null) { throw $exception; } return $returnValue; } /** * @param PHPUnit_Framework_MockObject_Invocation $invocation * @return bool */ public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) { foreach ($this->matchers as $matcher) { if (!$matcher->matches($invocation)) { return false; } } return true; } /** * @return bool */ public function verify() { foreach ($this->matchers as $matcher) { $matcher->verify(); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Invocation matcher which looks for a specific method name in the invocations. * * Checks the method name all incoming invocations, the name is checked against * the defined constraint $constraint. If the constraint is met it will return * true in matches(). * * @since Class available since Release 1.0.0 */ class PHPUnit_Framework_MockObject_Matcher_MethodName extends PHPUnit_Framework_MockObject_Matcher_StatelessInvocation { /** * @var PHPUnit_Framework_Constraint */ protected $constraint; /** * @param PHPUnit_Framework_Constraint|string * @throws PHPUnit_Framework_Constraint */ public function __construct($constraint) { if (!$constraint instanceof PHPUnit_Framework_Constraint) { if (!is_string($constraint)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } $constraint = new PHPUnit_Framework_Constraint_IsEqual( $constraint, 0, 10, false, true ); } $this->constraint = $constraint; } /** * @return string */ public function toString() { return 'method name ' . $this->constraint->toString(); } /** * @param PHPUnit_Framework_MockObject_Invocation $invocation * @return bool */ public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) { return $this->constraint->evaluate($invocation->methodName, '', true); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Invocation matcher which checks if a method has been invoked at least one * time. * * If the number of invocations is 0 it will throw an exception in verify. * * @since Class available since Release 1.0.0 */ class PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce extends PHPUnit_Framework_MockObject_Matcher_InvokedRecorder { /** * @return string */ public function toString() { return 'invoked at least once'; } /** * Verifies that the current expectation is valid. If everything is OK the * code should just return, if not it must throw an exception. * * @throws PHPUnit_Framework_ExpectationFailedException */ public function verify() { $count = $this->getInvocationCount(); if ($count < 1) { throw new PHPUnit_Framework_ExpectationFailedException( 'Expected invocation at least once but it never occured.' ); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Invocation matcher which allows any parameters to a method. * * @since Class available since Release 1.0.0 */ class PHPUnit_Framework_MockObject_Matcher_AnyParameters extends PHPUnit_Framework_MockObject_Matcher_StatelessInvocation { /** * @return string */ public function toString() { return 'with any parameters'; } /** * @param PHPUnit_Framework_MockObject_Invocation $invocation * @return bool */ public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) { return true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Invocation matcher which looks for specific parameters in the invocations. * * Checks the parameters of all incoming invocations, the parameter list is * checked against the defined constraints in $parameters. If the constraint * is met it will return true in matches(). * * @since Class available since Release 1.0.0 */ class PHPUnit_Framework_MockObject_Matcher_Parameters extends PHPUnit_Framework_MockObject_Matcher_StatelessInvocation { /** * @var PHPUnit_Framework_Constraint[] */ protected $parameters = array(); /** * @var PHPUnit_Framework_MockObject_Invocation */ protected $invocation; /** * @param array $parameters */ public function __construct(array $parameters) { foreach ($parameters as $parameter) { if (!($parameter instanceof PHPUnit_Framework_Constraint)) { $parameter = new PHPUnit_Framework_Constraint_IsEqual( $parameter ); } $this->parameters[] = $parameter; } } /** * @return string */ public function toString() { $text = 'with parameter'; foreach ($this->parameters as $index => $parameter) { if ($index > 0) { $text .= ' and'; } $text .= ' ' . $index . ' ' . $parameter->toString(); } return $text; } /** * @param PHPUnit_Framework_MockObject_Invocation $invocation * @return bool */ public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) { $this->invocation = $invocation; return $this->verify(); } /** * Checks if the invocation $invocation matches the current rules. If it * does the matcher will get the invoked() method called which should check * if an expectation is met. * * @param PHPUnit_Framework_MockObject_Invocation $invocation * Object containing information on a mocked or stubbed method which * was invoked. * @return bool * @throws PHPUnit_Framework_ExpectationFailedException */ public function verify() { if ($this->invocation === null) { throw new PHPUnit_Framework_ExpectationFailedException( 'Mocked method does not exist.' ); } if (count($this->invocation->parameters) < count($this->parameters)) { $message = 'Parameter count for invocation %s is too low.'; // The user called `->with($this->anything())`, but may have meant // `->withAnyParameters()`. // // @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/199 if (count($this->parameters) === 1 && get_class($this->parameters[0]) === 'PHPUnit_Framework_Constraint_IsAnything') { $message .= "\nTo allow 0 or more parameters with any value, omit ->with() or use ->withAnyParameters() instead."; } throw new PHPUnit_Framework_ExpectationFailedException( sprintf($message, $this->invocation->toString()) ); } foreach ($this->parameters as $i => $parameter) { $parameter->evaluate( $this->invocation->parameters[$i], sprintf( 'Parameter %s for invocation %s does not match expected ' . 'value.', $i, $this->invocation->toString() ) ); } return true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Invocation matcher which checks if a method has been invoked zero or more * times. This matcher will always match. * * @since Class available since Release 1.0.0 */ class PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount extends PHPUnit_Framework_MockObject_Matcher_InvokedRecorder { /** * @return string */ public function toString() { return 'invoked zero or more times'; } /** */ public function verify() { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Invocation matcher which checks if a method has been invoked at least * N times. * * @since Class available since Release 2.2.0 */ class PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastCount extends PHPUnit_Framework_MockObject_Matcher_InvokedRecorder { /** * @var int */ private $requiredInvocations; /** * @param int $requiredInvocations */ public function __construct($requiredInvocations) { $this->requiredInvocations = $requiredInvocations; } /** * @return string */ public function toString() { return 'invoked at least ' . $this->requiredInvocations . ' times'; } /** * Verifies that the current expectation is valid. If everything is OK the * code should just return, if not it must throw an exception. * * @throws PHPUnit_Framework_ExpectationFailedException */ public function verify() { $count = $this->getInvocationCount(); if ($count < $this->requiredInvocations) { throw new PHPUnit_Framework_ExpectationFailedException( 'Expected invocation at least ' . $this->requiredInvocations . ' times but it occured ' . $count . ' time(s).' ); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Interface for classes which matches an invocation based on its * method name, argument, order or call count. * * @since Interface available since Release 1.0.0 */ interface PHPUnit_Framework_MockObject_Matcher_Invocation extends PHPUnit_Framework_SelfDescribing, PHPUnit_Framework_MockObject_Verifiable { /** * Registers the invocation $invocation in the object as being invoked. * This will only occur after matches() returns true which means the * current invocation is the correct one. * * The matcher can store information from the invocation which can later * be checked in verify(), or it can check the values directly and throw * and exception if an expectation is not met. * * If the matcher is a stub it will also have a return value. * * @param PHPUnit_Framework_MockObject_Invocation $invocation * Object containing information on a mocked or stubbed method which * was invoked. * @return mixed */ public function invoked(PHPUnit_Framework_MockObject_Invocation $invocation); /** * Checks if the invocation $invocation matches the current rules. If it does * the matcher will get the invoked() method called which should check if an * expectation is met. * * @param PHPUnit_Framework_MockObject_Invocation $invocation * Object containing information on a mocked or stubbed method which * was invoked. * @return bool */ public function matches(PHPUnit_Framework_MockObject_Invocation $invocation); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Invocation matcher which looks for sets of specific parameters in the invocations. * * Checks the parameters of the incoming invocations, the parameter list is * checked against the defined constraints in $parameters. If the constraint * is met it will return true in matches(). * * It takes a list of match groups and and increases a call index after each invocation. * So the first invocation uses the first group of constraints, the second the next and so on. */ class PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters extends PHPUnit_Framework_MockObject_Matcher_StatelessInvocation { /** * @var array */ private $_parameterGroups = array(); /** * @var array */ private $_invocations = array(); /** * @param array $parameterGroups */ public function __construct(array $parameterGroups) { foreach ($parameterGroups as $index => $parameters) { foreach ($parameters as $parameter) { if (!($parameter instanceof \PHPUnit_Framework_Constraint)) { $parameter = new \PHPUnit_Framework_Constraint_IsEqual($parameter); } $this->_parameterGroups[$index][] = $parameter; } } } /** * @return string */ public function toString() { $text = 'with consecutive parameters'; return $text; } /** * @param PHPUnit_Framework_MockObject_Invocation $invocation * @return bool */ public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) { $this->_invocations[] = $invocation; $callIndex = count($this->_invocations) - 1; $this->verifyInvocation($invocation, $callIndex); return false; } public function verify() { foreach ($this->_invocations as $callIndex => $invocation) { $this->verifyInvocation($invocation, $callIndex); } } /** * Verify a single invocation * * @param PHPUnit_Framework_MockObject_Invocation $invocation * @param int $callIndex * @throws PHPUnit_Framework_ExpectationFailedException */ private function verifyInvocation(PHPUnit_Framework_MockObject_Invocation $invocation, $callIndex) { if (isset($this->_parameterGroups[$callIndex])) { $parameters = $this->_parameterGroups[$callIndex]; } else { // no parameter assertion for this call index return; } if ($invocation === null) { throw new PHPUnit_Framework_ExpectationFailedException( 'Mocked method does not exist.' ); } if (count($invocation->parameters) < count($parameters)) { throw new PHPUnit_Framework_ExpectationFailedException( sprintf( 'Parameter count for invocation %s is too low.', $invocation->toString() ) ); } foreach ($parameters as $i => $parameter) { $parameter->evaluate( $invocation->parameters[$i], sprintf( 'Parameter %s for invocation #%d %s does not match expected ' . 'value.', $i, $callIndex, $invocation->toString() ) ); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Invocation matcher which checks if a method was invoked at a certain index. * * If the expected index number does not match the current invocation index it * will not match which means it skips all method and parameter matching. Only * once the index is reached will the method and parameter start matching and * verifying. * * If the index is never reached it will throw an exception in index. * * @since Class available since Release 1.0.0 */ class PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex implements PHPUnit_Framework_MockObject_Matcher_Invocation { /** * @var int */ protected $sequenceIndex; /** * @var int */ protected $currentIndex = -1; /** * @param int $sequenceIndex */ public function __construct($sequenceIndex) { $this->sequenceIndex = $sequenceIndex; } /** * @return string */ public function toString() { return 'invoked at sequence index ' . $this->sequenceIndex; } /** * @param PHPUnit_Framework_MockObject_Invocation $invocation * @return bool */ public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) { $this->currentIndex++; return $this->currentIndex == $this->sequenceIndex; } /** * @param PHPUnit_Framework_MockObject_Invocation $invocation */ public function invoked(PHPUnit_Framework_MockObject_Invocation $invocation) { } /** * Verifies that the current expectation is valid. If everything is OK the * code should just return, if not it must throw an exception. * * @throws PHPUnit_Framework_ExpectationFailedException */ public function verify() { if ($this->currentIndex < $this->sequenceIndex) { throw new PHPUnit_Framework_ExpectationFailedException( sprintf( 'The expected invocation at index %s was never reached.', $this->sequenceIndex ) ); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Invocation matcher which checks if a method has been invoked a certain amount * of times. * If the number of invocations exceeds the value it will immediately throw an * exception, * If the number is less it will later be checked in verify() and also throw an * exception. * * @since Class available since Release 1.0.0 */ class PHPUnit_Framework_MockObject_Matcher_InvokedCount extends PHPUnit_Framework_MockObject_Matcher_InvokedRecorder { /** * @var int */ protected $expectedCount; /** * @param int $expectedCount */ public function __construct($expectedCount) { $this->expectedCount = $expectedCount; } /** * @return bool */ public function isNever() { return $this->expectedCount == 0; } /** * @return string */ public function toString() { return 'invoked ' . $this->expectedCount . ' time(s)'; } /** * @param PHPUnit_Framework_MockObject_Invocation $invocation * @throws PHPUnit_Framework_ExpectationFailedException */ public function invoked(PHPUnit_Framework_MockObject_Invocation $invocation) { parent::invoked($invocation); $count = $this->getInvocationCount(); if ($count > $this->expectedCount) { $message = $invocation->toString() . ' '; switch ($this->expectedCount) { case 0: { $message .= 'was not expected to be called.'; } break; case 1: { $message .= 'was not expected to be called more than once.'; } break; default: { $message .= sprintf( 'was not expected to be called more than %d times.', $this->expectedCount ); } } throw new PHPUnit_Framework_ExpectationFailedException($message); } } /** * Verifies that the current expectation is valid. If everything is OK the * code should just return, if not it must throw an exception. * * @throws PHPUnit_Framework_ExpectationFailedException */ public function verify() { $count = $this->getInvocationCount(); if ($count !== $this->expectedCount) { throw new PHPUnit_Framework_ExpectationFailedException( sprintf( 'Method was expected to be called %d times, ' . 'actually called %d times.', $this->expectedCount, $count ) ); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Invocation matcher which checks if a method has been invoked at least * N times. * * @since Class available since Release 2.2.0 */ class PHPUnit_Framework_MockObject_Matcher_InvokedAtMostCount extends PHPUnit_Framework_MockObject_Matcher_InvokedRecorder { /** * @var int */ private $allowedInvocations; /** * @param int $allowedInvocations */ public function __construct($allowedInvocations) { $this->allowedInvocations = $allowedInvocations; } /** * @return string */ public function toString() { return 'invoked at most ' . $this->allowedInvocations . ' times'; } /** * Verifies that the current expectation is valid. If everything is OK the * code should just return, if not it must throw an exception. * * @throws PHPUnit_Framework_ExpectationFailedException */ public function verify() { $count = $this->getInvocationCount(); if ($count > $this->allowedInvocations) { throw new PHPUnit_Framework_ExpectationFailedException( 'Expected invocation at most ' . $this->allowedInvocations . ' times but it occured ' . $count . ' time(s).' ); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Records invocations and provides convenience methods for checking them later * on. * This abstract class can be implemented by matchers which needs to check the * number of times an invocation has occured. * * @since Class available since Release 1.0.0 * @abstract */ abstract class PHPUnit_Framework_MockObject_Matcher_InvokedRecorder implements PHPUnit_Framework_MockObject_Matcher_Invocation { /** * @var PHPUnit_Framework_MockObject_Invocation[] */ protected $invocations = array(); /** * @return int */ public function getInvocationCount() { return count($this->invocations); } /** * @return PHPUnit_Framework_MockObject_Invocation[] */ public function getInvocations() { return $this->invocations; } /** * @return bool */ public function hasBeenInvoked() { return count($this->invocations) > 0; } /** * @param PHPUnit_Framework_MockObject_Invocation $invocation */ public function invoked(PHPUnit_Framework_MockObject_Invocation $invocation) { $this->invocations[] = $invocation; } /** * @param PHPUnit_Framework_MockObject_Invocation $invocation * @return bool */ public function matches(PHPUnit_Framework_MockObject_Invocation $invocation) { return true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Invocation matcher which does not care about previous state from earlier * invocations. * * This abstract class can be implemented by matchers which does not care about * state but only the current run-time value of the invocation itself. * * @since Class available since Release 1.0.0 * @abstract */ abstract class PHPUnit_Framework_MockObject_Matcher_StatelessInvocation implements PHPUnit_Framework_MockObject_Matcher_Invocation { /** * Registers the invocation $invocation in the object as being invoked. * This will only occur after matches() returns true which means the * current invocation is the correct one. * * The matcher can store information from the invocation which can later * be checked in verify(), or it can check the values directly and throw * and exception if an expectation is not met. * * If the matcher is a stub it will also have a return value. * * @param PHPUnit_Framework_MockObject_Invocation $invocation * Object containing information on a mocked or stubbed method which * was invoked. * @return mixed */ public function invoked(PHPUnit_Framework_MockObject_Invocation $invocation) { } /** * Checks if the invocation $invocation matches the current rules. If it does * the matcher will get the invoked() method called which should check if an * expectation is met. * * @param PHPUnit_Framework_MockObject_Invocation $invocation * Object containing information on a mocked or stubbed method which * was invoked. * @return bool */ public function verify() { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Doctrine\Instantiator\Instantiator; use Doctrine\Instantiator\Exception\InvalidArgumentException as InstantiatorInvalidArgumentException; use Doctrine\Instantiator\Exception\UnexpectedValueException as InstantiatorUnexpectedValueException; if (!function_exists('trait_exists')) { function trait_exists($traitname, $autoload = true) { return false; } } /** * Mock Object Code Generator * * @since Class available since Release 1.0.0 */ class PHPUnit_Framework_MockObject_Generator { /** * @var array */ private static $cache = array(); /** * @var array */ protected $blacklistedMethodNames = array( '__CLASS__' => true, '__DIR__' => true, '__FILE__' => true, '__FUNCTION__' => true, '__LINE__' => true, '__METHOD__' => true, '__NAMESPACE__' => true, '__TRAIT__' => true, '__clone' => true, '__halt_compiler' => true, 'abstract' => true, 'and' => true, 'array' => true, 'as' => true, 'break' => true, 'callable' => true, 'case' => true, 'catch' => true, 'class' => true, 'clone' => true, 'const' => true, 'continue' => true, 'declare' => true, 'default' => true, 'die' => true, 'do' => true, 'echo' => true, 'else' => true, 'elseif' => true, 'empty' => true, 'enddeclare' => true, 'endfor' => true, 'endforeach' => true, 'endif' => true, 'endswitch' => true, 'endwhile' => true, 'eval' => true, 'exit' => true, 'expects' => true, 'extends' => true, 'final' => true, 'for' => true, 'foreach' => true, 'function' => true, 'global' => true, 'goto' => true, 'if' => true, 'implements' => true, 'include' => true, 'include_once' => true, 'instanceof' => true, 'insteadof' => true, 'interface' => true, 'isset' => true, 'list' => true, 'namespace' => true, 'new' => true, 'or' => true, 'print' => true, 'private' => true, 'protected' => true, 'public' => true, 'require' => true, 'require_once' => true, 'return' => true, 'static' => true, 'switch' => true, 'throw' => true, 'trait' => true, 'try' => true, 'unset' => true, 'use' => true, 'var' => true, 'while' => true, 'xor' => true ); /** * Returns a mock object for the specified class. * * @param array|string $type * @param array $methods * @param array $arguments * @param string $mockClassName * @param bool $callOriginalConstructor * @param bool $callOriginalClone * @param bool $callAutoload * @param bool $cloneArguments * @param bool $callOriginalMethods * @param object $proxyTarget * @return object * @throws InvalidArgumentException * @throws PHPUnit_Framework_Exception * @throws PHPUnit_Framework_MockObject_RuntimeException * @since Method available since Release 1.0.0 */ public function getMock($type, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false, $proxyTarget = null) { if (!is_array($type) && !is_string($type)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'array or string'); } if (!is_string($mockClassName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(4, 'string'); } if (!is_array($methods) && !is_null($methods)) { throw new InvalidArgumentException; } if ($type === 'Traversable' || $type === '\\Traversable') { $type = 'Iterator'; } if (is_array($type)) { $type = array_unique(array_map( function ($type) { if ($type === 'Traversable' || $type === '\\Traversable' || $type === '\\Iterator') { return 'Iterator'; } return $type; }, $type )); } if (null !== $methods) { foreach ($methods as $method) { if (!preg_match('~[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*~', $method)) { throw new PHPUnit_Framework_Exception( sprintf( 'Cannot stub or mock method with invalid name "%s"', $method ) ); } } if ($methods != array_unique($methods)) { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf( 'Cannot stub or mock using a method list that contains duplicates: "%s"', implode(', ', $methods) ) ); } } if ($mockClassName != '' && class_exists($mockClassName, false)) { $reflect = new ReflectionClass($mockClassName); if (!$reflect->implementsInterface('PHPUnit_Framework_MockObject_MockObject')) { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf( 'Class "%s" already exists.', $mockClassName ) ); } } $mock = $this->generate( $type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods ); return $this->getObject( $mock['code'], $mock['mockClassName'], $type, $callOriginalConstructor, $callAutoload, $arguments, $callOriginalMethods, $proxyTarget ); } /** * @param string $code * @param string $className * @param array|string $type * @param bool $callOriginalConstructor * @param bool $callAutoload * @param array $arguments * @param bool $callOriginalMethods * @param object $proxyTarget * @return object */ protected function getObject($code, $className, $type = '', $callOriginalConstructor = false, $callAutoload = false, array $arguments = array(), $callOriginalMethods = false, $proxyTarget = null) { $this->evalClass($code, $className); if ($callOriginalConstructor && is_string($type) && !interface_exists($type, $callAutoload)) { if (count($arguments) == 0) { $object = new $className; } else { $class = new ReflectionClass($className); $object = $class->newInstanceArgs($arguments); } } else { try { $instantiator = new Instantiator; $object = $instantiator->instantiate($className); } catch (InstantiatorUnexpectedValueException $exception) { if ($exception->getPrevious()) { $exception = $exception->getPrevious(); } throw new PHPUnit_Framework_MockObject_RuntimeException( $exception->getMessage() ); } catch (InstantiatorInvalidArgumentException $exception) { throw new PHPUnit_Framework_MockObject_RuntimeException( $exception->getMessage() ); } } if ($callOriginalMethods) { if (!is_object($proxyTarget)) { if (count($arguments) == 0) { $proxyTarget = new $type; } else { $class = new ReflectionClass($type); $proxyTarget = $class->newInstanceArgs($arguments); } } $object->__phpunit_setOriginalObject($proxyTarget); } return $object; } /** * @param string $code * @param string $className */ protected function evalClass($code, $className) { if (!class_exists($className, false)) { eval($code); } } /** * Returns a mock object for the specified abstract class with all abstract * methods of the class mocked. Concrete methods to mock can be specified with * the last parameter * * @param string $originalClassName * @param array $arguments * @param string $mockClassName * @param bool $callOriginalConstructor * @param bool $callOriginalClone * @param bool $callAutoload * @param array $mockedMethods * @param bool $cloneArguments * @return object * @since Method available since Release 1.0.0 * @throws PHPUnit_Framework_MockObject_RuntimeException * @throws PHPUnit_Framework_Exception */ public function getMockForAbstractClass($originalClassName, array $arguments = array(), $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = array(), $cloneArguments = true) { if (!is_string($originalClassName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (!is_string($mockClassName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'string'); } if (class_exists($originalClassName, $callAutoload) || interface_exists($originalClassName, $callAutoload)) { $reflector = new ReflectionClass($originalClassName); $methods = $mockedMethods; foreach ($reflector->getMethods() as $method) { if ($method->isAbstract() && !in_array($method->getName(), $methods)) { $methods[] = $method->getName(); } } if (empty($methods)) { $methods = null; } return $this->getMock( $originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $cloneArguments ); } else { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf('Class "%s" does not exist.', $originalClassName) ); } } /** * Returns a mock object for the specified trait with all abstract methods * of the trait mocked. Concrete methods to mock can be specified with the * `$mockedMethods` parameter. * * @param string $traitName * @param array $arguments * @param string $mockClassName * @param bool $callOriginalConstructor * @param bool $callOriginalClone * @param bool $callAutoload * @param array $mockedMethods * @param bool $cloneArguments * @return object * @since Method available since Release 1.2.3 * @throws PHPUnit_Framework_MockObject_RuntimeException * @throws PHPUnit_Framework_Exception */ public function getMockForTrait($traitName, array $arguments = array(), $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = array(), $cloneArguments = true) { if (!is_string($traitName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (!is_string($mockClassName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'string'); } if (!trait_exists($traitName, $callAutoload)) { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf( 'Trait "%s" does not exist.', $traitName ) ); } $className = $this->generateClassName( $traitName, '', 'Trait_' ); $templateDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR; $classTemplate = new Text_Template( $templateDir . 'trait_class.tpl' ); $classTemplate->setVar( array( 'prologue' => 'abstract ', 'class_name' => $className['className'], 'trait_name' => $traitName ) ); $this->evalClass( $classTemplate->render(), $className['className'] ); return $this->getMockForAbstractClass($className['className'], $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); } /** * Returns an object for the specified trait. * * @param string $traitName * @param array $arguments * @param string $traitClassName * @param bool $callOriginalConstructor * @param bool $callOriginalClone * @param bool $callAutoload * @return object * @since Method available since Release 1.1.0 * @throws PHPUnit_Framework_MockObject_RuntimeException * @throws PHPUnit_Framework_Exception */ public function getObjectForTrait($traitName, array $arguments = array(), $traitClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true) { if (!is_string($traitName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (!is_string($traitClassName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'string'); } if (!trait_exists($traitName, $callAutoload)) { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf( 'Trait "%s" does not exist.', $traitName ) ); } $className = $this->generateClassName( $traitName, $traitClassName, 'Trait_' ); $templateDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR; $classTemplate = new Text_Template( $templateDir . 'trait_class.tpl' ); $classTemplate->setVar( array( 'prologue' => '', 'class_name' => $className['className'], 'trait_name' => $traitName ) ); return $this->getObject( $classTemplate->render(), $className['className'] ); } /** * @param array|string $type * @param array $methods * @param string $mockClassName * @param bool $callOriginalClone * @param bool $callAutoload * @param bool $cloneArguments * @param bool $callOriginalMethods * @return array */ public function generate($type, array $methods = null, $mockClassName = '', $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false) { if (is_array($type)) { sort($type); } if ($mockClassName == '') { $key = md5( is_array($type) ? implode('_', $type) : $type . serialize($methods) . serialize($callOriginalClone) . serialize($cloneArguments) . serialize($callOriginalMethods) ); if (isset(self::$cache[$key])) { return self::$cache[$key]; } } $mock = $this->generateMock( $type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods ); if (isset($key)) { self::$cache[$key] = $mock; } return $mock; } /** * @param string $wsdlFile * @param string $className * @param array $methods * @param array $options * @return string * @throws PHPUnit_Framework_MockObject_RuntimeException */ public function generateClassFromWsdl($wsdlFile, $className, array $methods = array(), array $options = array()) { if (!extension_loaded('soap')) { throw new PHPUnit_Framework_MockObject_RuntimeException( 'The SOAP extension is required to generate a mock object from WSDL.' ); } $options = array_merge($options, array('cache_wsdl' => WSDL_CACHE_NONE)); $client = new SoapClient($wsdlFile, $options); $_methods = array_unique($client->__getFunctions()); unset($client); sort($_methods); $templateDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR; $methodTemplate = new Text_Template($templateDir . 'wsdl_method.tpl'); $methodsBuffer = ''; foreach ($_methods as $method) { $nameStart = strpos($method, ' ') + 1; $nameEnd = strpos($method, '('); $name = substr($method, $nameStart, $nameEnd - $nameStart); if (empty($methods) || in_array($name, $methods)) { $args = explode( ',', substr( $method, $nameEnd + 1, strpos($method, ')') - $nameEnd - 1 ) ); $numArgs = count($args); for ($i = 0; $i < $numArgs; $i++) { $args[$i] = substr($args[$i], strpos($args[$i], '$')); } $methodTemplate->setVar( array( 'method_name' => $name, 'arguments' => implode(', ', $args) ) ); $methodsBuffer .= $methodTemplate->render(); } } $optionsBuffer = 'array('; foreach ($options as $key => $value) { $optionsBuffer .= $key . ' => ' . $value; } $optionsBuffer .= ')'; $classTemplate = new Text_Template($templateDir . 'wsdl_class.tpl'); $namespace = ''; if (strpos($className, '\\') !== false) { $parts = explode('\\', $className); $className = array_pop($parts); $namespace = 'namespace ' . implode('\\', $parts) . ';' . "\n\n"; } $classTemplate->setVar( array( 'namespace' => $namespace, 'class_name' => $className, 'wsdl' => $wsdlFile, 'options' => $optionsBuffer, 'methods' => $methodsBuffer ) ); return $classTemplate->render(); } /** * @param array|string $type * @param array|null $methods * @param string $mockClassName * @param bool $callOriginalClone * @param bool $callAutoload * @param bool $cloneArguments * @param bool $callOriginalMethods * @return array * @throws PHPUnit_Framework_Exception */ protected function generateMock($type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods) { $templateDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR; $classTemplate = new Text_Template( $templateDir . 'mocked_class.tpl' ); $additionalInterfaces = array(); $cloneTemplate = ''; $isClass = false; $isInterface = false; $mockClassName = $this->generateClassName( $type, $mockClassName, 'Mock_' ); if (is_array($type)) { foreach ($type as $_type) { if (!interface_exists($_type, $callAutoload)) { throw new PHPUnit_Framework_Exception( sprintf( 'Interface "%s" does not exist.', $_type ) ); } $additionalInterfaces[] = $_type; foreach ($this->getClassMethods($_type) as $method) { if (in_array($method, $methods)) { throw new PHPUnit_Framework_Exception( sprintf( 'Duplicate method "%s" not allowed.', $method ) ); } $methods[] = $method; } } } if (class_exists($mockClassName['fullClassName'], $callAutoload)) { $isClass = true; } else { if (interface_exists($mockClassName['fullClassName'], $callAutoload)) { $isInterface = true; } } if (!class_exists($mockClassName['fullClassName'], $callAutoload) && !interface_exists($mockClassName['fullClassName'], $callAutoload)) { $prologue = 'class ' . $mockClassName['originalClassName'] . "\n{\n}\n\n"; if (!empty($mockClassName['namespaceName'])) { $prologue = 'namespace ' . $mockClassName['namespaceName'] . " {\n\n" . $prologue . "}\n\n" . "namespace {\n\n"; $epilogue = "\n\n}"; } $cloneTemplate = new Text_Template( $templateDir . 'mocked_clone.tpl' ); } else { $class = new ReflectionClass($mockClassName['fullClassName']); if ($class->isFinal()) { throw new PHPUnit_Framework_Exception( sprintf( 'Class "%s" is declared "final" and cannot be mocked.', $mockClassName['fullClassName'] ) ); } if ($class->hasMethod('__clone')) { $cloneMethod = $class->getMethod('__clone'); if (!$cloneMethod->isFinal()) { if ($callOriginalClone && !$isInterface) { $cloneTemplate = new Text_Template( $templateDir . 'unmocked_clone.tpl' ); } else { $cloneTemplate = new Text_Template( $templateDir . 'mocked_clone.tpl' ); } } } else { $cloneTemplate = new Text_Template( $templateDir . 'mocked_clone.tpl' ); } } if (is_object($cloneTemplate)) { $cloneTemplate = $cloneTemplate->render(); } if (is_array($methods) && empty($methods) && ($isClass || $isInterface)) { $methods = $this->getClassMethods($mockClassName['fullClassName']); } if (!is_array($methods)) { $methods = array(); } $mockedMethods = ''; if (isset($class)) { // https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103 if ($isInterface && $class->implementsInterface('Traversable') && !$class->implementsInterface('Iterator') && !$class->implementsInterface('IteratorAggregate')) { $additionalInterfaces[] = 'Iterator'; $methods = array_merge($methods, $this->getClassMethods('Iterator')); } foreach ($methods as $methodName) { try { $method = $class->getMethod($methodName); if ($this->canMockMethod($method)) { $mockedMethods .= $this->generateMockedMethodDefinitionFromExisting( $templateDir, $method, $cloneArguments, $callOriginalMethods ); } } catch (ReflectionException $e) { $mockedMethods .= $this->generateMockedMethodDefinition( $templateDir, $mockClassName['fullClassName'], $methodName, $cloneArguments ); } } } else { foreach ($methods as $methodName) { $mockedMethods .= $this->generateMockedMethodDefinition( $templateDir, $mockClassName['fullClassName'], $methodName, $cloneArguments ); } } $method = ''; if (!in_array('method', $methods)) { $methodTemplate = new Text_Template( $templateDir . 'mocked_class_method.tpl' ); $method = $methodTemplate->render(); } $classTemplate->setVar( array( 'prologue' => isset($prologue) ? $prologue : '', 'epilogue' => isset($epilogue) ? $epilogue : '', 'class_declaration' => $this->generateMockClassDeclaration( $mockClassName, $isInterface, $additionalInterfaces ), 'clone' => $cloneTemplate, 'mock_class_name' => $mockClassName['className'], 'mocked_methods' => $mockedMethods, 'method' => $method ) ); return array( 'code' => $classTemplate->render(), 'mockClassName' => $mockClassName['className'] ); } /** * @param array|string $type * @param string $className * @param string $prefix * @return array */ protected function generateClassName($type, $className, $prefix) { if (is_array($type)) { $type = implode('_', $type); } if ($type[0] == '\\') { $type = substr($type, 1); } $classNameParts = explode('\\', $type); if (count($classNameParts) > 1) { $type = array_pop($classNameParts); $namespaceName = implode('\\', $classNameParts); $fullClassName = $namespaceName . '\\' . $type; } else { $namespaceName = ''; $fullClassName = $type; } if ($className == '') { do { $className = $prefix . $type . '_' . substr(md5(microtime()), 0, 8); } while (class_exists($className, false)); } return array( 'className' => $className, 'originalClassName' => $type, 'fullClassName' => $fullClassName, 'namespaceName' => $namespaceName ); } /** * @param array $mockClassName * @param bool $isInterface * @param array $additionalInterfaces * @return array */ protected function generateMockClassDeclaration(array $mockClassName, $isInterface, array $additionalInterfaces = array()) { $buffer = 'class '; $additionalInterfaces[] = 'PHPUnit_Framework_MockObject_MockObject'; $interfaces = implode(', ', $additionalInterfaces); if ($isInterface) { $buffer .= sprintf( '%s implements %s', $mockClassName['className'], $interfaces ); if (!in_array($mockClassName['originalClassName'], $additionalInterfaces)) { $buffer .= ', '; if (!empty($mockClassName['namespaceName'])) { $buffer .= $mockClassName['namespaceName'] . '\\'; } $buffer .= $mockClassName['originalClassName']; } } else { $buffer .= sprintf( '%s extends %s%s implements %s', $mockClassName['className'], !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '', $mockClassName['originalClassName'], $interfaces ); } return $buffer; } /** * @param string $templateDir * @param ReflectionMethod $method * @param bool $cloneArguments * @param bool $callOriginalMethods * @return string */ protected function generateMockedMethodDefinitionFromExisting($templateDir, ReflectionMethod $method, $cloneArguments, $callOriginalMethods) { if ($method->isPrivate()) { $modifier = 'private'; } elseif ($method->isProtected()) { $modifier = 'protected'; } else { $modifier = 'public'; } if ($method->isStatic()) { $modifier .= ' static'; } if ($method->returnsReference()) { $reference = '&'; } else { $reference = ''; } return $this->generateMockedMethodDefinition( $templateDir, $method->getDeclaringClass()->getName(), $method->getName(), $cloneArguments, $modifier, $this->getMethodParameters($method), $this->getMethodParameters($method, true), $reference, $callOriginalMethods, $method->isStatic() ); } /** * @param string $templateDir * @param string $className * @param string $methodName * @param bool $cloneArguments * @param string $modifier * @param string $arguments_decl * @param string $arguments_call * @param string $reference * @param bool $callOriginalMethods * @param bool $static * @return string */ protected function generateMockedMethodDefinition($templateDir, $className, $methodName, $cloneArguments = true, $modifier = 'public', $arguments_decl = '', $arguments_call = '', $reference = '', $callOriginalMethods = false, $static = false) { if ($static) { $templateFile = 'mocked_static_method.tpl'; } else { $templateFile = sprintf( '%s_method.tpl', $callOriginalMethods ? 'proxied' : 'mocked' ); } $template = new Text_Template($templateDir . $templateFile); $template->setVar( array( 'arguments_decl' => $arguments_decl, 'arguments_call' => $arguments_call, 'arguments_count' => !empty($arguments_call) ? count(explode(',', $arguments_call)) : 0, 'class_name' => $className, 'method_name' => $methodName, 'modifier' => $modifier, 'reference' => $reference, 'clone_arguments' => $cloneArguments ? 'TRUE' : 'FALSE' ) ); return $template->render(); } /** * @param ReflectionMethod $method * @return bool */ protected function canMockMethod(ReflectionMethod $method) { if ($method->isConstructor() || $method->isFinal() || $method->isPrivate() || isset($this->blacklistedMethodNames[$method->getName()])) { return false; } return true; } /** * Returns the parameters of a function or method. * * @param ReflectionMethod $method * @param bool $forCall * @return string * @throws PHPUnit_Framework_MockObject_RuntimeException * @since Method available since Release 2.0.0 */ protected function getMethodParameters(ReflectionMethod $method, $forCall = false) { $parameters = array(); foreach ($method->getParameters() as $i => $parameter) { $name = '$' . $parameter->getName(); /* Note: PHP extensions may use empty names for reference arguments * or "..." for methods taking a variable number of arguments. */ if ($name === '$' || $name === '$...') { $name = '$arg' . $i; } if ($this->isVariadic($parameter)) { if ($forCall) { continue; } else { $name = '...' . $name; } } $default = ''; $reference = ''; $typeDeclaration = ''; if (!$forCall) { if ($this->hasType($parameter)) { $typeDeclaration = (string) $parameter->getType() . ' '; } elseif ($parameter->isArray()) { $typeDeclaration = 'array '; } elseif ((defined('HHVM_VERSION') || version_compare(PHP_VERSION, '5.4.0', '>=')) && $parameter->isCallable()) { $typeDeclaration = 'callable '; } else { try { $class = $parameter->getClass(); } catch (ReflectionException $e) { throw new PHPUnit_Framework_MockObject_RuntimeException( sprintf( 'Cannot mock %s::%s() because a class or ' . 'interface used in the signature is not loaded', $method->getDeclaringClass()->getName(), $method->getName() ), 0, $e ); } if ($class !== null) { $typeDeclaration = $class->getName() . ' '; } } if (!$this->isVariadic($parameter)) { if ($parameter->isDefaultValueAvailable()) { $value = $parameter->getDefaultValue(); $default = ' = ' . var_export($value, true); } elseif ($parameter->isOptional()) { $default = ' = null'; } } } if ($parameter->isPassedByReference()) { $reference = '&'; } $parameters[] = $typeDeclaration . $reference . $name . $default; } return implode(', ', $parameters); } /** * @param ReflectionParameter $parameter * @return bool * @since Method available since Release 2.2.1 */ private function isVariadic(ReflectionParameter $parameter) { return method_exists('ReflectionParameter', 'isVariadic') && $parameter->isVariadic(); } /** * @param ReflectionParameter $parameter * @return bool * @since Method available since Release 2.3.4 */ private function hasType(ReflectionParameter $parameter) { return method_exists('ReflectionParameter', 'hasType') && $parameter->hasType(); } /** * @param string $className * @return array * @since Method available since Release 2.3.2 */ private function getClassMethods($className) { $class = new ReflectionClass($className); $methods = array(); foreach ($class->getMethods() as $method) { if (($method->isPublic() || $method->isAbstract()) && !in_array($method->getName(), $methods)) { $methods[] = $method->getName(); } } return $methods; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Builder interface for invocation order matches. * * @since Interface available since Release 1.0.0 */ interface PHPUnit_Framework_MockObject_Builder_Match extends PHPUnit_Framework_MockObject_Builder_Stub { /** * Defines the expectation which must occur before the current is valid. * * @param string $id The identification of the expectation that should * occur before this one. * @return PHPUnit_Framework_MockObject_Builder_Stub */ public function after($id); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Builder interface for unique identifiers. * * Defines the interface for recording unique identifiers. The identifiers * can be used to define the invocation order of expectations. The expectation * is recorded using id() and then defined in order using * PHPUnit_Framework_MockObject_Builder_Match::after(). * * @since Interface available since Release 1.0.0 */ interface PHPUnit_Framework_MockObject_Builder_Identity { /** * Sets the identification of the expectation to $id. * * @note The identifier is unique per mock object. * @param string $id Unique identifiation of expectation. */ public function id($id); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Builder for mocked or stubbed invocations. * * Provides methods for building expectations without having to resort to * instantiating the various matchers manually. These methods also form a * more natural way of reading the expectation. This class should be together * with the test case PHPUnit_Framework_MockObject_TestCase. * * @since Class available since Release 1.0.0 */ class PHPUnit_Framework_MockObject_Builder_InvocationMocker implements PHPUnit_Framework_MockObject_Builder_MethodNameMatch { /** * @var PHPUnit_Framework_MockObject_Stub_MatcherCollection */ protected $collection; /** * @var PHPUnit_Framework_MockObject_Matcher */ protected $matcher; /** * @param PHPUnit_Framework_MockObject_Stub_MatcherCollection $collection * @param PHPUnit_Framework_MockObject_Matcher_Invocation $invocationMatcher */ public function __construct(PHPUnit_Framework_MockObject_Stub_MatcherCollection $collection, PHPUnit_Framework_MockObject_Matcher_Invocation $invocationMatcher) { $this->collection = $collection; $this->matcher = new PHPUnit_Framework_MockObject_Matcher( $invocationMatcher ); $this->collection->addMatcher($this->matcher); } /** * @return PHPUnit_Framework_MockObject_Matcher */ public function getMatcher() { return $this->matcher; } /** * @param mixed $id * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker */ public function id($id) { $this->collection->registerId($id, $this); return $this; } /** * @param PHPUnit_Framework_MockObject_Stub $stub * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker */ public function will(PHPUnit_Framework_MockObject_Stub $stub) { $this->matcher->stub = $stub; return $this; } /** * @param mixed $value * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker */ public function willReturn($value) { $stub = new PHPUnit_Framework_MockObject_Stub_Return( $value ); return $this->will($stub); } /** * @param array $valueMap * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker */ public function willReturnMap(array $valueMap) { $stub = new PHPUnit_Framework_MockObject_Stub_ReturnValueMap( $valueMap ); return $this->will($stub); } /** * @param mixed $argumentIndex * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker */ public function willReturnArgument($argumentIndex) { $stub = new PHPUnit_Framework_MockObject_Stub_ReturnArgument( $argumentIndex ); return $this->will($stub); } /** * @param callable $callback * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker */ public function willReturnCallback($callback) { $stub = new PHPUnit_Framework_MockObject_Stub_ReturnCallback( $callback ); return $this->will($stub); } /** * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker */ public function willReturnSelf() { $stub = new PHPUnit_Framework_MockObject_Stub_ReturnSelf(); return $this->will($stub); } /** * @param mixed $value, ... * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker */ public function willReturnOnConsecutiveCalls() { $args = func_get_args(); $stub = new PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls($args); return $this->will($stub); } /** * @param Exception $exception * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker */ public function willThrowException(Exception $exception) { $stub = new PHPUnit_Framework_MockObject_Stub_Exception($exception); return $this->will($stub); } /** * @param mixed $id * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker */ public function after($id) { $this->matcher->afterMatchBuilderId = $id; return $this; } /** * Validate that a parameters matcher can be defined, throw exceptions otherwise. * * @throws PHPUnit_Framework_Exception */ private function canDefineParameters() { if ($this->matcher->methodNameMatcher === null) { throw new PHPUnit_Framework_Exception( 'Method name matcher is not defined, cannot define parameter ' . ' matcher without one' ); } if ($this->matcher->parametersMatcher !== null) { throw new PHPUnit_Framework_Exception( 'Parameter matcher is already defined, cannot redefine' ); } } /** * @param mixed $argument, ... * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker */ public function with() { $args = func_get_args(); $this->canDefineParameters(); $this->matcher->parametersMatcher = new PHPUnit_Framework_MockObject_Matcher_Parameters($args); return $this; } /** * @param mixed ...$argument * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker */ public function withConsecutive() { $args = func_get_args(); $this->canDefineParameters(); $this->matcher->parametersMatcher = new PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters($args); return $this; } /** * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker */ public function withAnyParameters() { $this->canDefineParameters(); $this->matcher->parametersMatcher = new PHPUnit_Framework_MockObject_Matcher_AnyParameters; return $this; } /** * @param PHPUnit_Framework_Constraint|string $constraint * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker */ public function method($constraint) { if ($this->matcher->methodNameMatcher !== null) { throw new PHPUnit_Framework_Exception( 'Method name matcher is already defined, cannot redefine' ); } $this->matcher->methodNameMatcher = new PHPUnit_Framework_MockObject_Matcher_MethodName($constraint); return $this; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Builder interface for parameter matchers. * * @since Interface available since Release 1.0.0 */ interface PHPUnit_Framework_MockObject_Builder_ParametersMatch extends PHPUnit_Framework_MockObject_Builder_Match { /** * Sets the parameters to match for, each parameter to this funtion will * be part of match. To perform specific matches or constraints create a * new PHPUnit_Framework_Constraint and use it for the parameter. * If the parameter value is not a constraint it will use the * PHPUnit_Framework_Constraint_IsEqual for the value. * * Some examples: * * // match first parameter with value 2 * $b->with(2); * // match first parameter with value 'smock' and second identical to 42 * $b->with('smock', new PHPUnit_Framework_Constraint_IsEqual(42)); * * * @return PHPUnit_Framework_MockObject_Builder_ParametersMatch */ public function with(); /** * Sets a matcher which allows any kind of parameters. * * Some examples: * * // match any number of parameters * $b->withAnyParamers(); * * * @return PHPUnit_Framework_MockObject_Matcher_AnyParameters */ public function withAnyParameters(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Interface for builders which can register builders with a given identification. * * This interface relates to PHPUnit_Framework_MockObject_Builder_Identity. * * @since Interface available since Release 1.0.0 */ interface PHPUnit_Framework_MockObject_Builder_Namespace { /** * Looks up the match builder with identification $id and returns it. * * @param string $id The identifiction of the match builder. * @return PHPUnit_Framework_MockObject_Builder_Match */ public function lookupId($id); /** * Registers the match builder $builder with the identification $id. The * builder can later be looked up using lookupId() to figure out if it * has been invoked. * * @param string $id * The identification of the match builder. * @param PHPUnit_Framework_MockObject_Builder_Match $builder * The builder which is being registered. */ public function registerId($id, PHPUnit_Framework_MockObject_Builder_Match $builder); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Builder interface for matcher of method names. * * @since Interface available since Release 1.0.0 */ interface PHPUnit_Framework_MockObject_Builder_MethodNameMatch extends PHPUnit_Framework_MockObject_Builder_ParametersMatch { /** * Adds a new method name match and returns the parameter match object for * further matching possibilities. * * @param PHPUnit_Framework_Constraint $name * Constraint for matching method, if a string is passed it will use * the PHPUnit_Framework_Constraint_IsEqual. * @return PHPUnit_Framework_MockObject_Builder_ParametersMatch */ public function method($name); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Builder interface for stubs which are actions replacing an invocation. * * @since Interface available since Release 1.0.0 */ interface PHPUnit_Framework_MockObject_Builder_Stub extends PHPUnit_Framework_MockObject_Builder_Identity { /** * Stubs the matching method with the stub object $stub. Any invocations of * the matched method will now be handled by the stub instead. * * @param PHPUnit_Framework_MockObject_Stub $stub The stub object. * @return PHPUnit_Framework_MockObject_Builder_Identity */ public function will(PHPUnit_Framework_MockObject_Stub $stub); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Represents a non-static invocation. * * @since Class available since Release 1.0.0 */ class PHPUnit_Framework_MockObject_Invocation_Object extends PHPUnit_Framework_MockObject_Invocation_Static { /** * @var object */ public $object; /** * @param string $className * @param string $methodname * @param array $parameters * @param object $object * @param object $cloneObjects */ public function __construct($className, $methodName, array $parameters, $object, $cloneObjects = false) { parent::__construct($className, $methodName, $parameters, $cloneObjects); $this->object = $object; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use SebastianBergmann\Exporter\Exporter; /** * Represents a static invocation. * * @since Class available since Release 1.0.0 */ class PHPUnit_Framework_MockObject_Invocation_Static implements PHPUnit_Framework_MockObject_Invocation, PHPUnit_Framework_SelfDescribing { /** * @var array */ protected static $uncloneableExtensions = array( 'mysqli' => true, 'SQLite' => true, 'sqlite3' => true, 'tidy' => true, 'xmlwriter' => true, 'xsl' => true ); /** * @var array */ protected static $uncloneableClasses = array( 'Closure', 'COMPersistHelper', 'IteratorIterator', 'RecursiveIteratorIterator', 'SplFileObject', 'PDORow', 'ZipArchive' ); /** * @var string */ public $className; /** * @var string */ public $methodName; /** * @var array */ public $parameters; /** * @param string $className * @param string $methodname * @param array $parameters * @param bool $cloneObjects */ public function __construct($className, $methodName, array $parameters, $cloneObjects = false) { $this->className = $className; $this->methodName = $methodName; $this->parameters = $parameters; if (!$cloneObjects) { return; } foreach ($this->parameters as $key => $value) { if (is_object($value)) { $this->parameters[$key] = $this->cloneObject($value); } } } /** * @return string */ public function toString() { $exporter = new Exporter; return sprintf( '%s::%s(%s)', $this->className, $this->methodName, implode( ', ', array_map( array($exporter, 'shortenedExport'), $this->parameters ) ) ); } /** * @param object $original * @return object */ protected function cloneObject($original) { $cloneable = null; $object = new ReflectionObject($original); // Check the blacklist before asking PHP reflection to work around // https://bugs.php.net/bug.php?id=53967 if ($object->isInternal() && isset(self::$uncloneableExtensions[$object->getExtensionName()])) { $cloneable = false; } if ($cloneable === null) { foreach (self::$uncloneableClasses as $class) { if ($original instanceof $class) { $cloneable = false; break; } } } if ($cloneable === null && method_exists($object, 'isCloneable')) { $cloneable = $object->isCloneable(); } if ($cloneable === null && $object->hasMethod('__clone')) { $method = $object->getMethod('__clone'); $cloneable = $method->isPublic(); } if ($cloneable === null) { $cloneable = true; } if ($cloneable) { try { return clone $original; } catch (Exception $e) { return $original; } } else { return $original; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * An object that stubs the process of a normal method for a mock object. * * The stub object will replace the code for the stubbed method and return a * specific value instead of the original value. * * @since Interface available since Release 1.0.0 */ interface PHPUnit_Framework_MockObject_Stub extends PHPUnit_Framework_SelfDescribing { /** * Fakes the processing of the invocation $invocation by returning a * specific value. * * @param PHPUnit_Framework_MockObject_Invocation $invocation * The invocation which was mocked and matched by the current method * and argument matchers. * @return mixed */ public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use SebastianBergmann\Exporter\Exporter; /** * Stubs a method by raising a user-defined exception. * * @since Class available since Release 1.0.0 */ class PHPUnit_Framework_MockObject_Stub_Exception implements PHPUnit_Framework_MockObject_Stub { protected $exception; public function __construct(Exception $exception) { $this->exception = $exception; } public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation) { throw $this->exception; } public function toString() { $exporter = new Exporter; return sprintf( 'raise user-specified exception %s', $exporter->export($this->exception) ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use SebastianBergmann\Exporter\Exporter; /** * Stubs a method by returning a user-defined value. * * @since Class available since Release 1.0.0 */ class PHPUnit_Framework_MockObject_Stub_Return implements PHPUnit_Framework_MockObject_Stub { protected $value; public function __construct($value) { $this->value = $value; } public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation) { return $this->value; } public function toString() { $exporter = new Exporter; return sprintf( 'return user-specified value %s', $exporter->export($this->value) ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Stubs a method by returning the current object. * * @since Class available since Release 1.1.0 */ class PHPUnit_Framework_MockObject_Stub_ReturnSelf implements PHPUnit_Framework_MockObject_Stub { public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation) { if (!$invocation instanceof PHPUnit_Framework_MockObject_Invocation_Object) { throw new PHPUnit_Framework_Exception( 'The current object can only be returned when mocking an ' . 'object, not a static class.' ); } return $invocation->object; } public function toString() { return 'return the current object'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Stubs a method by returning a value from a map. * * @since Class available since Release 1.1.0 */ class PHPUnit_Framework_MockObject_Stub_ReturnValueMap implements PHPUnit_Framework_MockObject_Stub { protected $valueMap; public function __construct(array $valueMap) { $this->valueMap = $valueMap; } public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation) { $parameterCount = count($invocation->parameters); foreach ($this->valueMap as $map) { if (!is_array($map) || $parameterCount != count($map) - 1) { continue; } $return = array_pop($map); if ($invocation->parameters === $map) { return $return; } } return; } public function toString() { return 'return value from a map'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use SebastianBergmann\Exporter\Exporter; /** * Stubs a method by returning a user-defined stack of values. * * @since Class available since Release 1.0.0 */ class PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls implements PHPUnit_Framework_MockObject_Stub { protected $stack; protected $value; public function __construct($stack) { $this->stack = $stack; } public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation) { $this->value = array_shift($this->stack); if ($this->value instanceof PHPUnit_Framework_MockObject_Stub) { $this->value = $this->value->invoke($invocation); } return $this->value; } public function toString() { $exporter = new Exporter; return sprintf( 'return user-specified value %s', $exporter->export($this->value) ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Stubs a method by returning a user-defined value. * * @since Interface available since Release 1.0.0 */ interface PHPUnit_Framework_MockObject_Stub_MatcherCollection { /** * Adds a new matcher to the collection which can be used as an expectation * or a stub. * * @param PHPUnit_Framework_MockObject_Matcher_Invocation $matcher * Matcher for invocations to mock objects. */ public function addMatcher(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 1.0.0 */ class PHPUnit_Framework_MockObject_Stub_ReturnCallback implements PHPUnit_Framework_MockObject_Stub { protected $callback; public function __construct($callback) { $this->callback = $callback; } public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation) { return call_user_func_array($this->callback, $invocation->parameters); } public function toString() { if (is_array($this->callback)) { if (is_object($this->callback[0])) { $class = get_class($this->callback[0]); $type = '->'; } else { $class = $this->callback[0]; $type = '::'; } return sprintf( 'return result of user defined callback %s%s%s() with the ' . 'passed arguments', $class, $type, $this->callback[1] ); } else { return 'return result of user defined callback ' . $this->callback . ' with the passed arguments'; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Stubs a method by returning an argument that was passed to the mocked method. * * @since Class available since Release 1.0.0 */ class PHPUnit_Framework_MockObject_Stub_ReturnArgument extends PHPUnit_Framework_MockObject_Stub_Return { protected $argumentIndex; public function __construct($argumentIndex) { $this->argumentIndex = $argumentIndex; } public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation) { if (isset($invocation->parameters[$this->argumentIndex])) { return $invocation->parameters[$this->argumentIndex]; } else { return; } } public function toString() { return sprintf('return argument #%d', $this->argumentIndex); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Marker interface for PHPUnit exceptions. * * @since Interface available since Release 4.0.0 */ interface PHPUnit_Exception { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A Test can be run and collect its results. * * @since Interface available since Release 2.0.0 */ interface PHPUnit_Framework_Test extends Countable { /** * Runs a test and collects its result in a TestResult instance. * * @param PHPUnit_Framework_TestResult $result * @return PHPUnit_Framework_TestResult */ public function run(PHPUnit_Framework_TestResult $result = null); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Base class for all PHPUnit Framework exceptions. * * Ensures that exceptions thrown during a test run do not leave stray * references behind. * * Every Exception contains a stack trace. Each stack frame contains the 'args' * of the called function. The function arguments can contain references to * instantiated objects. The references prevent the objects from being * destructed (until test results are eventually printed), so memory cannot be * freed up. * * With enabled process isolation, test results are serialized in the child * process and unserialized in the parent process. The stack trace of Exceptions * may contain objects that cannot be serialized or unserialized (e.g., PDO * connections). Unserializing user-space objects from the child process into * the parent would break the intended encapsulation of process isolation. * * @see http://fabien.potencier.org/article/9/php-serialization-stack-traces-and-exceptions * * @since Class available since Release 3.4.0 */ class PHPUnit_Framework_Exception extends RuntimeException implements PHPUnit_Exception { /** * @var array */ protected $serializableTrace; public function __construct($message = '', $code = 0, Exception $previous = null) { parent::__construct($message, $code, $previous); $this->serializableTrace = $this->getTrace(); foreach ($this->serializableTrace as $i => $call) { unset($this->serializableTrace[$i]['args']); } } /** * Returns the serializable trace (without 'args'). * * @return array */ public function getSerializableTrace() { return $this->serializableTrace; } /** * @return string */ public function __toString() { $string = PHPUnit_Framework_TestFailure::exceptionToString($this); if ($trace = PHPUnit_Util_Filter::getFilteredStacktrace($this)) { $string .= "\n" . $trace; } return $string; } public function __sleep() { return array_keys(get_object_vars($this)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Extension to PHPUnit_Framework_AssertionFailedError to mark the special * case of a test that printed output. * * @since Class available since Release 3.6.0 */ class PHPUnit_Framework_OutputError extends PHPUnit_Framework_AssertionFailedError { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A set of assert methods. * * @since Class available since Release 2.0.0 */ abstract class PHPUnit_Framework_Assert { /** * @var int */ private static $count = 0; /** * Asserts that an array has a specified key. * * @param mixed $key * @param array|ArrayAccess $array * @param string $message * @since Method available since Release 3.0.0 */ public static function assertArrayHasKey($key, $array, $message = '') { if (!(is_integer($key) || is_string($key))) { throw PHPUnit_Util_InvalidArgumentHelper::factory( 1, 'integer or string' ); } if (!(is_array($array) || $array instanceof ArrayAccess)) { throw PHPUnit_Util_InvalidArgumentHelper::factory( 2, 'array or ArrayAccess' ); } $constraint = new PHPUnit_Framework_Constraint_ArrayHasKey($key); self::assertThat($array, $constraint, $message); } /** * Asserts that an array has a specified subset. * * @param array|ArrayAccess $subset * @param array|ArrayAccess $array * @param bool $strict Check for object identity * @param string $message * @since Method available since Release 4.4.0 */ public static function assertArraySubset($subset, $array, $strict = false, $message = '') { if (!is_array($subset)) { throw PHPUnit_Util_InvalidArgumentHelper::factory( 1, 'array or ArrayAccess' ); } if (!is_array($array)) { throw PHPUnit_Util_InvalidArgumentHelper::factory( 2, 'array or ArrayAccess' ); } $constraint = new PHPUnit_Framework_Constraint_ArraySubset($subset, $strict); self::assertThat($array, $constraint, $message); } /** * Asserts that an array does not have a specified key. * * @param mixed $key * @param array|ArrayAccess $array * @param string $message * @since Method available since Release 3.0.0 */ public static function assertArrayNotHasKey($key, $array, $message = '') { if (!(is_integer($key) || is_string($key))) { throw PHPUnit_Util_InvalidArgumentHelper::factory( 1, 'integer or string' ); } if (!(is_array($array) || $array instanceof ArrayAccess)) { throw PHPUnit_Util_InvalidArgumentHelper::factory( 2, 'array or ArrayAccess' ); } $constraint = new PHPUnit_Framework_Constraint_Not( new PHPUnit_Framework_Constraint_ArrayHasKey($key) ); self::assertThat($array, $constraint, $message); } /** * Asserts that a haystack contains a needle. * * @param mixed $needle * @param mixed $haystack * @param string $message * @param bool $ignoreCase * @param bool $checkForObjectIdentity * @param bool $checkForNonObjectIdentity * @since Method available since Release 2.1.0 */ public static function assertContains($needle, $haystack, $message = '', $ignoreCase = false, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false) { if (is_array($haystack) || is_object($haystack) && $haystack instanceof Traversable) { $constraint = new PHPUnit_Framework_Constraint_TraversableContains( $needle, $checkForObjectIdentity, $checkForNonObjectIdentity ); } elseif (is_string($haystack)) { if (!is_string($needle)) { throw PHPUnit_Util_InvalidArgumentHelper::factory( 1, 'string' ); } $constraint = new PHPUnit_Framework_Constraint_StringContains( $needle, $ignoreCase ); } else { throw PHPUnit_Util_InvalidArgumentHelper::factory( 2, 'array, traversable or string' ); } self::assertThat($haystack, $constraint, $message); } /** * Asserts that a haystack that is stored in a static attribute of a class * or an attribute of an object contains a needle. * * @param mixed $needle * @param string $haystackAttributeName * @param mixed $haystackClassOrObject * @param string $message * @param bool $ignoreCase * @param bool $checkForObjectIdentity * @param bool $checkForNonObjectIdentity * @since Method available since Release 3.0.0 */ public static function assertAttributeContains($needle, $haystackAttributeName, $haystackClassOrObject, $message = '', $ignoreCase = false, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false) { self::assertContains( $needle, self::readAttribute($haystackClassOrObject, $haystackAttributeName), $message, $ignoreCase, $checkForObjectIdentity, $checkForNonObjectIdentity ); } /** * Asserts that a haystack does not contain a needle. * * @param mixed $needle * @param mixed $haystack * @param string $message * @param bool $ignoreCase * @param bool $checkForObjectIdentity * @param bool $checkForNonObjectIdentity * @since Method available since Release 2.1.0 */ public static function assertNotContains($needle, $haystack, $message = '', $ignoreCase = false, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false) { if (is_array($haystack) || is_object($haystack) && $haystack instanceof Traversable) { $constraint = new PHPUnit_Framework_Constraint_Not( new PHPUnit_Framework_Constraint_TraversableContains( $needle, $checkForObjectIdentity, $checkForNonObjectIdentity ) ); } elseif (is_string($haystack)) { if (!is_string($needle)) { throw PHPUnit_Util_InvalidArgumentHelper::factory( 1, 'string' ); } $constraint = new PHPUnit_Framework_Constraint_Not( new PHPUnit_Framework_Constraint_StringContains( $needle, $ignoreCase ) ); } else { throw PHPUnit_Util_InvalidArgumentHelper::factory( 2, 'array, traversable or string' ); } self::assertThat($haystack, $constraint, $message); } /** * Asserts that a haystack that is stored in a static attribute of a class * or an attribute of an object does not contain a needle. * * @param mixed $needle * @param string $haystackAttributeName * @param mixed $haystackClassOrObject * @param string $message * @param bool $ignoreCase * @param bool $checkForObjectIdentity * @param bool $checkForNonObjectIdentity * @since Method available since Release 3.0.0 */ public static function assertAttributeNotContains($needle, $haystackAttributeName, $haystackClassOrObject, $message = '', $ignoreCase = false, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false) { self::assertNotContains( $needle, self::readAttribute($haystackClassOrObject, $haystackAttributeName), $message, $ignoreCase, $checkForObjectIdentity, $checkForNonObjectIdentity ); } /** * Asserts that a haystack contains only values of a given type. * * @param string $type * @param mixed $haystack * @param bool $isNativeType * @param string $message * @since Method available since Release 3.1.4 */ public static function assertContainsOnly($type, $haystack, $isNativeType = null, $message = '') { if (!(is_array($haystack) || is_object($haystack) && $haystack instanceof Traversable)) { throw PHPUnit_Util_InvalidArgumentHelper::factory( 2, 'array or traversable' ); } if ($isNativeType == null) { $isNativeType = PHPUnit_Util_Type::isType($type); } self::assertThat( $haystack, new PHPUnit_Framework_Constraint_TraversableContainsOnly( $type, $isNativeType ), $message ); } /** * Asserts that a haystack contains only instances of a given classname * * @param string $classname * @param array|Traversable $haystack * @param string $message */ public static function assertContainsOnlyInstancesOf($classname, $haystack, $message = '') { if (!(is_array($haystack) || is_object($haystack) && $haystack instanceof Traversable)) { throw PHPUnit_Util_InvalidArgumentHelper::factory( 2, 'array or traversable' ); } self::assertThat( $haystack, new PHPUnit_Framework_Constraint_TraversableContainsOnly( $classname, false ), $message ); } /** * Asserts that a haystack that is stored in a static attribute of a class * or an attribute of an object contains only values of a given type. * * @param string $type * @param string $haystackAttributeName * @param mixed $haystackClassOrObject * @param bool $isNativeType * @param string $message * @since Method available since Release 3.1.4 */ public static function assertAttributeContainsOnly($type, $haystackAttributeName, $haystackClassOrObject, $isNativeType = null, $message = '') { self::assertContainsOnly( $type, self::readAttribute($haystackClassOrObject, $haystackAttributeName), $isNativeType, $message ); } /** * Asserts that a haystack does not contain only values of a given type. * * @param string $type * @param mixed $haystack * @param bool $isNativeType * @param string $message * @since Method available since Release 3.1.4 */ public static function assertNotContainsOnly($type, $haystack, $isNativeType = null, $message = '') { if (!(is_array($haystack) || is_object($haystack) && $haystack instanceof Traversable)) { throw PHPUnit_Util_InvalidArgumentHelper::factory( 2, 'array or traversable' ); } if ($isNativeType == null) { $isNativeType = PHPUnit_Util_Type::isType($type); } self::assertThat( $haystack, new PHPUnit_Framework_Constraint_Not( new PHPUnit_Framework_Constraint_TraversableContainsOnly( $type, $isNativeType ) ), $message ); } /** * Asserts that a haystack that is stored in a static attribute of a class * or an attribute of an object does not contain only values of a given * type. * * @param string $type * @param string $haystackAttributeName * @param mixed $haystackClassOrObject * @param bool $isNativeType * @param string $message * @since Method available since Release 3.1.4 */ public static function assertAttributeNotContainsOnly($type, $haystackAttributeName, $haystackClassOrObject, $isNativeType = null, $message = '') { self::assertNotContainsOnly( $type, self::readAttribute($haystackClassOrObject, $haystackAttributeName), $isNativeType, $message ); } /** * Asserts the number of elements of an array, Countable or Traversable. * * @param int $expectedCount * @param mixed $haystack * @param string $message */ public static function assertCount($expectedCount, $haystack, $message = '') { if (!is_int($expectedCount)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'integer'); } if (!$haystack instanceof Countable && !$haystack instanceof Traversable && !is_array($haystack)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'countable or traversable'); } self::assertThat( $haystack, new PHPUnit_Framework_Constraint_Count($expectedCount), $message ); } /** * Asserts the number of elements of an array, Countable or Traversable * that is stored in an attribute. * * @param int $expectedCount * @param string $haystackAttributeName * @param mixed $haystackClassOrObject * @param string $message * @since Method available since Release 3.6.0 */ public static function assertAttributeCount($expectedCount, $haystackAttributeName, $haystackClassOrObject, $message = '') { self::assertCount( $expectedCount, self::readAttribute($haystackClassOrObject, $haystackAttributeName), $message ); } /** * Asserts the number of elements of an array, Countable or Traversable. * * @param int $expectedCount * @param mixed $haystack * @param string $message */ public static function assertNotCount($expectedCount, $haystack, $message = '') { if (!is_int($expectedCount)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'integer'); } if (!$haystack instanceof Countable && !$haystack instanceof Traversable && !is_array($haystack)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'countable or traversable'); } $constraint = new PHPUnit_Framework_Constraint_Not( new PHPUnit_Framework_Constraint_Count($expectedCount) ); self::assertThat($haystack, $constraint, $message); } /** * Asserts the number of elements of an array, Countable or Traversable * that is stored in an attribute. * * @param int $expectedCount * @param string $haystackAttributeName * @param mixed $haystackClassOrObject * @param string $message * @since Method available since Release 3.6.0 */ public static function assertAttributeNotCount($expectedCount, $haystackAttributeName, $haystackClassOrObject, $message = '') { self::assertNotCount( $expectedCount, self::readAttribute($haystackClassOrObject, $haystackAttributeName), $message ); } /** * Asserts that two variables are equal. * * @param mixed $expected * @param mixed $actual * @param string $message * @param float $delta * @param int $maxDepth * @param bool $canonicalize * @param bool $ignoreCase */ public static function assertEquals($expected, $actual, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) { $constraint = new PHPUnit_Framework_Constraint_IsEqual( $expected, $delta, $maxDepth, $canonicalize, $ignoreCase ); self::assertThat($actual, $constraint, $message); } /** * Asserts that a variable is equal to an attribute of an object. * * @param mixed $expected * @param string $actualAttributeName * @param string $actualClassOrObject * @param string $message * @param float $delta * @param int $maxDepth * @param bool $canonicalize * @param bool $ignoreCase */ public static function assertAttributeEquals($expected, $actualAttributeName, $actualClassOrObject, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) { self::assertEquals( $expected, self::readAttribute($actualClassOrObject, $actualAttributeName), $message, $delta, $maxDepth, $canonicalize, $ignoreCase ); } /** * Asserts that two variables are not equal. * * @param mixed $expected * @param mixed $actual * @param string $message * @param float $delta * @param int $maxDepth * @param bool $canonicalize * @param bool $ignoreCase * @since Method available since Release 2.3.0 */ public static function assertNotEquals($expected, $actual, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) { $constraint = new PHPUnit_Framework_Constraint_Not( new PHPUnit_Framework_Constraint_IsEqual( $expected, $delta, $maxDepth, $canonicalize, $ignoreCase ) ); self::assertThat($actual, $constraint, $message); } /** * Asserts that a variable is not equal to an attribute of an object. * * @param mixed $expected * @param string $actualAttributeName * @param string $actualClassOrObject * @param string $message * @param float $delta * @param int $maxDepth * @param bool $canonicalize * @param bool $ignoreCase */ public static function assertAttributeNotEquals($expected, $actualAttributeName, $actualClassOrObject, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) { self::assertNotEquals( $expected, self::readAttribute($actualClassOrObject, $actualAttributeName), $message, $delta, $maxDepth, $canonicalize, $ignoreCase ); } /** * Asserts that a variable is empty. * * @param mixed $actual * @param string $message * @throws PHPUnit_Framework_AssertionFailedError */ public static function assertEmpty($actual, $message = '') { self::assertThat($actual, self::isEmpty(), $message); } /** * Asserts that a static attribute of a class or an attribute of an object * is empty. * * @param string $haystackAttributeName * @param mixed $haystackClassOrObject * @param string $message * @since Method available since Release 3.5.0 */ public static function assertAttributeEmpty($haystackAttributeName, $haystackClassOrObject, $message = '') { self::assertEmpty( self::readAttribute($haystackClassOrObject, $haystackAttributeName), $message ); } /** * Asserts that a variable is not empty. * * @param mixed $actual * @param string $message * @throws PHPUnit_Framework_AssertionFailedError */ public static function assertNotEmpty($actual, $message = '') { self::assertThat($actual, self::logicalNot(self::isEmpty()), $message); } /** * Asserts that a static attribute of a class or an attribute of an object * is not empty. * * @param string $haystackAttributeName * @param mixed $haystackClassOrObject * @param string $message * @since Method available since Release 3.5.0 */ public static function assertAttributeNotEmpty($haystackAttributeName, $haystackClassOrObject, $message = '') { self::assertNotEmpty( self::readAttribute($haystackClassOrObject, $haystackAttributeName), $message ); } /** * Asserts that a value is greater than another value. * * @param mixed $expected * @param mixed $actual * @param string $message * @since Method available since Release 3.1.0 */ public static function assertGreaterThan($expected, $actual, $message = '') { self::assertThat($actual, self::greaterThan($expected), $message); } /** * Asserts that an attribute is greater than another value. * * @param mixed $expected * @param string $actualAttributeName * @param string $actualClassOrObject * @param string $message * @since Method available since Release 3.1.0 */ public static function assertAttributeGreaterThan($expected, $actualAttributeName, $actualClassOrObject, $message = '') { self::assertGreaterThan( $expected, self::readAttribute($actualClassOrObject, $actualAttributeName), $message ); } /** * Asserts that a value is greater than or equal to another value. * * @param mixed $expected * @param mixed $actual * @param string $message * @since Method available since Release 3.1.0 */ public static function assertGreaterThanOrEqual($expected, $actual, $message = '') { self::assertThat( $actual, self::greaterThanOrEqual($expected), $message ); } /** * Asserts that an attribute is greater than or equal to another value. * * @param mixed $expected * @param string $actualAttributeName * @param string $actualClassOrObject * @param string $message * @since Method available since Release 3.1.0 */ public static function assertAttributeGreaterThanOrEqual($expected, $actualAttributeName, $actualClassOrObject, $message = '') { self::assertGreaterThanOrEqual( $expected, self::readAttribute($actualClassOrObject, $actualAttributeName), $message ); } /** * Asserts that a value is smaller than another value. * * @param mixed $expected * @param mixed $actual * @param string $message * @since Method available since Release 3.1.0 */ public static function assertLessThan($expected, $actual, $message = '') { self::assertThat($actual, self::lessThan($expected), $message); } /** * Asserts that an attribute is smaller than another value. * * @param mixed $expected * @param string $actualAttributeName * @param string $actualClassOrObject * @param string $message * @since Method available since Release 3.1.0 */ public static function assertAttributeLessThan($expected, $actualAttributeName, $actualClassOrObject, $message = '') { self::assertLessThan( $expected, self::readAttribute($actualClassOrObject, $actualAttributeName), $message ); } /** * Asserts that a value is smaller than or equal to another value. * * @param mixed $expected * @param mixed $actual * @param string $message * @since Method available since Release 3.1.0 */ public static function assertLessThanOrEqual($expected, $actual, $message = '') { self::assertThat($actual, self::lessThanOrEqual($expected), $message); } /** * Asserts that an attribute is smaller than or equal to another value. * * @param mixed $expected * @param string $actualAttributeName * @param string $actualClassOrObject * @param string $message * @since Method available since Release 3.1.0 */ public static function assertAttributeLessThanOrEqual($expected, $actualAttributeName, $actualClassOrObject, $message = '') { self::assertLessThanOrEqual( $expected, self::readAttribute($actualClassOrObject, $actualAttributeName), $message ); } /** * Asserts that the contents of one file is equal to the contents of another * file. * * @param string $expected * @param string $actual * @param string $message * @param bool $canonicalize * @param bool $ignoreCase * @since Method available since Release 3.2.14 */ public static function assertFileEquals($expected, $actual, $message = '', $canonicalize = false, $ignoreCase = false) { self::assertFileExists($expected, $message); self::assertFileExists($actual, $message); self::assertEquals( file_get_contents($expected), file_get_contents($actual), $message, 0, 10, $canonicalize, $ignoreCase ); } /** * Asserts that the contents of one file is not equal to the contents of * another file. * * @param string $expected * @param string $actual * @param string $message * @param bool $canonicalize * @param bool $ignoreCase * @since Method available since Release 3.2.14 */ public static function assertFileNotEquals($expected, $actual, $message = '', $canonicalize = false, $ignoreCase = false) { self::assertFileExists($expected, $message); self::assertFileExists($actual, $message); self::assertNotEquals( file_get_contents($expected), file_get_contents($actual), $message, 0, 10, $canonicalize, $ignoreCase ); } /** * Asserts that the contents of a string is equal * to the contents of a file. * * @param string $expectedFile * @param string $actualString * @param string $message * @param bool $canonicalize * @param bool $ignoreCase * @since Method available since Release 3.3.0 */ public static function assertStringEqualsFile($expectedFile, $actualString, $message = '', $canonicalize = false, $ignoreCase = false) { self::assertFileExists($expectedFile, $message); self::assertEquals( file_get_contents($expectedFile), $actualString, $message, 0, 10, $canonicalize, $ignoreCase ); } /** * Asserts that the contents of a string is not equal * to the contents of a file. * * @param string $expectedFile * @param string $actualString * @param string $message * @param bool $canonicalize * @param bool $ignoreCase * @since Method available since Release 3.3.0 */ public static function assertStringNotEqualsFile($expectedFile, $actualString, $message = '', $canonicalize = false, $ignoreCase = false) { self::assertFileExists($expectedFile, $message); self::assertNotEquals( file_get_contents($expectedFile), $actualString, $message, 0, 10, $canonicalize, $ignoreCase ); } /** * Asserts that a file exists. * * @param string $filename * @param string $message * @since Method available since Release 3.0.0 */ public static function assertFileExists($filename, $message = '') { if (!is_string($filename)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } $constraint = new PHPUnit_Framework_Constraint_FileExists; self::assertThat($filename, $constraint, $message); } /** * Asserts that a file does not exist. * * @param string $filename * @param string $message * @since Method available since Release 3.0.0 */ public static function assertFileNotExists($filename, $message = '') { if (!is_string($filename)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } $constraint = new PHPUnit_Framework_Constraint_Not( new PHPUnit_Framework_Constraint_FileExists ); self::assertThat($filename, $constraint, $message); } /** * Asserts that a condition is true. * * @param bool $condition * @param string $message * @throws PHPUnit_Framework_AssertionFailedError */ public static function assertTrue($condition, $message = '') { self::assertThat($condition, self::isTrue(), $message); } /** * Asserts that a condition is not true. * * @param bool $condition * @param string $message * @throws PHPUnit_Framework_AssertionFailedError */ public static function assertNotTrue($condition, $message = '') { self::assertThat($condition, self::logicalNot(self::isTrue()), $message); } /** * Asserts that a condition is false. * * @param bool $condition * @param string $message * @throws PHPUnit_Framework_AssertionFailedError */ public static function assertFalse($condition, $message = '') { self::assertThat($condition, self::isFalse(), $message); } /** * Asserts that a condition is not false. * * @param bool $condition * @param string $message * @throws PHPUnit_Framework_AssertionFailedError */ public static function assertNotFalse($condition, $message = '') { self::assertThat($condition, self::logicalNot(self::isFalse()), $message); } /** * Asserts that a variable is not null. * * @param mixed $actual * @param string $message */ public static function assertNotNull($actual, $message = '') { self::assertThat($actual, self::logicalNot(self::isNull()), $message); } /** * Asserts that a variable is null. * * @param mixed $actual * @param string $message */ public static function assertNull($actual, $message = '') { self::assertThat($actual, self::isNull(), $message); } /** * Asserts that a class has a specified attribute. * * @param string $attributeName * @param string $className * @param string $message * @since Method available since Release 3.1.0 */ public static function assertClassHasAttribute($attributeName, $className, $message = '') { if (!is_string($attributeName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (!preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'valid attribute name'); } if (!is_string($className) || !class_exists($className)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'class name', $className); } $constraint = new PHPUnit_Framework_Constraint_ClassHasAttribute( $attributeName ); self::assertThat($className, $constraint, $message); } /** * Asserts that a class does not have a specified attribute. * * @param string $attributeName * @param string $className * @param string $message * @since Method available since Release 3.1.0 */ public static function assertClassNotHasAttribute($attributeName, $className, $message = '') { if (!is_string($attributeName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (!preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'valid attribute name'); } if (!is_string($className) || !class_exists($className)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'class name', $className); } $constraint = new PHPUnit_Framework_Constraint_Not( new PHPUnit_Framework_Constraint_ClassHasAttribute($attributeName) ); self::assertThat($className, $constraint, $message); } /** * Asserts that a class has a specified static attribute. * * @param string $attributeName * @param string $className * @param string $message * @since Method available since Release 3.1.0 */ public static function assertClassHasStaticAttribute($attributeName, $className, $message = '') { if (!is_string($attributeName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (!preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'valid attribute name'); } if (!is_string($className) || !class_exists($className)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'class name', $className); } $constraint = new PHPUnit_Framework_Constraint_ClassHasStaticAttribute( $attributeName ); self::assertThat($className, $constraint, $message); } /** * Asserts that a class does not have a specified static attribute. * * @param string $attributeName * @param string $className * @param string $message * @since Method available since Release 3.1.0 */ public static function assertClassNotHasStaticAttribute($attributeName, $className, $message = '') { if (!is_string($attributeName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (!preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'valid attribute name'); } if (!is_string($className) || !class_exists($className)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'class name', $className); } $constraint = new PHPUnit_Framework_Constraint_Not( new PHPUnit_Framework_Constraint_ClassHasStaticAttribute( $attributeName ) ); self::assertThat($className, $constraint, $message); } /** * Asserts that an object has a specified attribute. * * @param string $attributeName * @param object $object * @param string $message * @since Method available since Release 3.0.0 */ public static function assertObjectHasAttribute($attributeName, $object, $message = '') { if (!is_string($attributeName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (!preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'valid attribute name'); } if (!is_object($object)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'object'); } $constraint = new PHPUnit_Framework_Constraint_ObjectHasAttribute( $attributeName ); self::assertThat($object, $constraint, $message); } /** * Asserts that an object does not have a specified attribute. * * @param string $attributeName * @param object $object * @param string $message * @since Method available since Release 3.0.0 */ public static function assertObjectNotHasAttribute($attributeName, $object, $message = '') { if (!is_string($attributeName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (!preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'valid attribute name'); } if (!is_object($object)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'object'); } $constraint = new PHPUnit_Framework_Constraint_Not( new PHPUnit_Framework_Constraint_ObjectHasAttribute($attributeName) ); self::assertThat($object, $constraint, $message); } /** * Asserts that two variables have the same type and value. * Used on objects, it asserts that two variables reference * the same object. * * @param mixed $expected * @param mixed $actual * @param string $message */ public static function assertSame($expected, $actual, $message = '') { if (is_bool($expected) && is_bool($actual)) { self::assertEquals($expected, $actual, $message); } else { $constraint = new PHPUnit_Framework_Constraint_IsIdentical( $expected ); self::assertThat($actual, $constraint, $message); } } /** * Asserts that a variable and an attribute of an object have the same type * and value. * * @param mixed $expected * @param string $actualAttributeName * @param object $actualClassOrObject * @param string $message */ public static function assertAttributeSame($expected, $actualAttributeName, $actualClassOrObject, $message = '') { self::assertSame( $expected, self::readAttribute($actualClassOrObject, $actualAttributeName), $message ); } /** * Asserts that two variables do not have the same type and value. * Used on objects, it asserts that two variables do not reference * the same object. * * @param mixed $expected * @param mixed $actual * @param string $message */ public static function assertNotSame($expected, $actual, $message = '') { if (is_bool($expected) && is_bool($actual)) { self::assertNotEquals($expected, $actual, $message); } else { $constraint = new PHPUnit_Framework_Constraint_Not( new PHPUnit_Framework_Constraint_IsIdentical($expected) ); self::assertThat($actual, $constraint, $message); } } /** * Asserts that a variable and an attribute of an object do not have the * same type and value. * * @param mixed $expected * @param string $actualAttributeName * @param object $actualClassOrObject * @param string $message */ public static function assertAttributeNotSame($expected, $actualAttributeName, $actualClassOrObject, $message = '') { self::assertNotSame( $expected, self::readAttribute($actualClassOrObject, $actualAttributeName), $message ); } /** * Asserts that a variable is of a given type. * * @param string $expected * @param mixed $actual * @param string $message * @since Method available since Release 3.5.0 */ public static function assertInstanceOf($expected, $actual, $message = '') { if (!(is_string($expected) && (class_exists($expected) || interface_exists($expected)))) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'class or interface name'); } $constraint = new PHPUnit_Framework_Constraint_IsInstanceOf( $expected ); self::assertThat($actual, $constraint, $message); } /** * Asserts that an attribute is of a given type. * * @param string $expected * @param string $attributeName * @param mixed $classOrObject * @param string $message * @since Method available since Release 3.5.0 */ public static function assertAttributeInstanceOf($expected, $attributeName, $classOrObject, $message = '') { self::assertInstanceOf( $expected, self::readAttribute($classOrObject, $attributeName), $message ); } /** * Asserts that a variable is not of a given type. * * @param string $expected * @param mixed $actual * @param string $message * @since Method available since Release 3.5.0 */ public static function assertNotInstanceOf($expected, $actual, $message = '') { if (!(is_string($expected) && (class_exists($expected) || interface_exists($expected)))) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'class or interface name'); } $constraint = new PHPUnit_Framework_Constraint_Not( new PHPUnit_Framework_Constraint_IsInstanceOf($expected) ); self::assertThat($actual, $constraint, $message); } /** * Asserts that an attribute is of a given type. * * @param string $expected * @param string $attributeName * @param mixed $classOrObject * @param string $message * @since Method available since Release 3.5.0 */ public static function assertAttributeNotInstanceOf($expected, $attributeName, $classOrObject, $message = '') { self::assertNotInstanceOf( $expected, self::readAttribute($classOrObject, $attributeName), $message ); } /** * Asserts that a variable is of a given type. * * @param string $expected * @param mixed $actual * @param string $message * @since Method available since Release 3.5.0 */ public static function assertInternalType($expected, $actual, $message = '') { if (!is_string($expected)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } $constraint = new PHPUnit_Framework_Constraint_IsType( $expected ); self::assertThat($actual, $constraint, $message); } /** * Asserts that an attribute is of a given type. * * @param string $expected * @param string $attributeName * @param mixed $classOrObject * @param string $message * @since Method available since Release 3.5.0 */ public static function assertAttributeInternalType($expected, $attributeName, $classOrObject, $message = '') { self::assertInternalType( $expected, self::readAttribute($classOrObject, $attributeName), $message ); } /** * Asserts that a variable is not of a given type. * * @param string $expected * @param mixed $actual * @param string $message * @since Method available since Release 3.5.0 */ public static function assertNotInternalType($expected, $actual, $message = '') { if (!is_string($expected)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } $constraint = new PHPUnit_Framework_Constraint_Not( new PHPUnit_Framework_Constraint_IsType($expected) ); self::assertThat($actual, $constraint, $message); } /** * Asserts that an attribute is of a given type. * * @param string $expected * @param string $attributeName * @param mixed $classOrObject * @param string $message * @since Method available since Release 3.5.0 */ public static function assertAttributeNotInternalType($expected, $attributeName, $classOrObject, $message = '') { self::assertNotInternalType( $expected, self::readAttribute($classOrObject, $attributeName), $message ); } /** * Asserts that a string matches a given regular expression. * * @param string $pattern * @param string $string * @param string $message */ public static function assertRegExp($pattern, $string, $message = '') { if (!is_string($pattern)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (!is_string($string)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string'); } $constraint = new PHPUnit_Framework_Constraint_PCREMatch($pattern); self::assertThat($string, $constraint, $message); } /** * Asserts that a string does not match a given regular expression. * * @param string $pattern * @param string $string * @param string $message * @since Method available since Release 2.1.0 */ public static function assertNotRegExp($pattern, $string, $message = '') { if (!is_string($pattern)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (!is_string($string)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string'); } $constraint = new PHPUnit_Framework_Constraint_Not( new PHPUnit_Framework_Constraint_PCREMatch($pattern) ); self::assertThat($string, $constraint, $message); } /** * Assert that the size of two arrays (or `Countable` or `Traversable` objects) * is the same. * * @param array|Countable|Traversable $expected * @param array|Countable|Traversable $actual * @param string $message */ public static function assertSameSize($expected, $actual, $message = '') { if (!$expected instanceof Countable && !$expected instanceof Traversable && !is_array($expected)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'countable or traversable'); } if (!$actual instanceof Countable && !$actual instanceof Traversable && !is_array($actual)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'countable or traversable'); } self::assertThat( $actual, new PHPUnit_Framework_Constraint_SameSize($expected), $message ); } /** * Assert that the size of two arrays (or `Countable` or `Traversable` objects) * is not the same. * * @param array|Countable|Traversable $expected * @param array|Countable|Traversable $actual * @param string $message */ public static function assertNotSameSize($expected, $actual, $message = '') { if (!$expected instanceof Countable && !$expected instanceof Traversable && !is_array($expected)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'countable or traversable'); } if (!$actual instanceof Countable && !$actual instanceof Traversable && !is_array($actual)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'countable or traversable'); } $constraint = new PHPUnit_Framework_Constraint_Not( new PHPUnit_Framework_Constraint_SameSize($expected) ); self::assertThat($actual, $constraint, $message); } /** * Asserts that a string matches a given format string. * * @param string $format * @param string $string * @param string $message * @since Method available since Release 3.5.0 */ public static function assertStringMatchesFormat($format, $string, $message = '') { if (!is_string($format)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (!is_string($string)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string'); } $constraint = new PHPUnit_Framework_Constraint_StringMatches($format); self::assertThat($string, $constraint, $message); } /** * Asserts that a string does not match a given format string. * * @param string $format * @param string $string * @param string $message * @since Method available since Release 3.5.0 */ public static function assertStringNotMatchesFormat($format, $string, $message = '') { if (!is_string($format)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (!is_string($string)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string'); } $constraint = new PHPUnit_Framework_Constraint_Not( new PHPUnit_Framework_Constraint_StringMatches($format) ); self::assertThat($string, $constraint, $message); } /** * Asserts that a string matches a given format file. * * @param string $formatFile * @param string $string * @param string $message * @since Method available since Release 3.5.0 */ public static function assertStringMatchesFormatFile($formatFile, $string, $message = '') { self::assertFileExists($formatFile, $message); if (!is_string($string)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string'); } $constraint = new PHPUnit_Framework_Constraint_StringMatches( file_get_contents($formatFile) ); self::assertThat($string, $constraint, $message); } /** * Asserts that a string does not match a given format string. * * @param string $formatFile * @param string $string * @param string $message * @since Method available since Release 3.5.0 */ public static function assertStringNotMatchesFormatFile($formatFile, $string, $message = '') { self::assertFileExists($formatFile, $message); if (!is_string($string)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string'); } $constraint = new PHPUnit_Framework_Constraint_Not( new PHPUnit_Framework_Constraint_StringMatches( file_get_contents($formatFile) ) ); self::assertThat($string, $constraint, $message); } /** * Asserts that a string starts with a given prefix. * * @param string $prefix * @param string $string * @param string $message * @since Method available since Release 3.4.0 */ public static function assertStringStartsWith($prefix, $string, $message = '') { if (!is_string($prefix)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (!is_string($string)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string'); } $constraint = new PHPUnit_Framework_Constraint_StringStartsWith( $prefix ); self::assertThat($string, $constraint, $message); } /** * Asserts that a string starts not with a given prefix. * * @param string $prefix * @param string $string * @param string $message * @since Method available since Release 3.4.0 */ public static function assertStringStartsNotWith($prefix, $string, $message = '') { if (!is_string($prefix)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (!is_string($string)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string'); } $constraint = new PHPUnit_Framework_Constraint_Not( new PHPUnit_Framework_Constraint_StringStartsWith($prefix) ); self::assertThat($string, $constraint, $message); } /** * Asserts that a string ends with a given suffix. * * @param string $suffix * @param string $string * @param string $message * @since Method available since Release 3.4.0 */ public static function assertStringEndsWith($suffix, $string, $message = '') { if (!is_string($suffix)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (!is_string($string)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string'); } $constraint = new PHPUnit_Framework_Constraint_StringEndsWith($suffix); self::assertThat($string, $constraint, $message); } /** * Asserts that a string ends not with a given suffix. * * @param string $suffix * @param string $string * @param string $message * @since Method available since Release 3.4.0 */ public static function assertStringEndsNotWith($suffix, $string, $message = '') { if (!is_string($suffix)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (!is_string($string)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string'); } $constraint = new PHPUnit_Framework_Constraint_Not( new PHPUnit_Framework_Constraint_StringEndsWith($suffix) ); self::assertThat($string, $constraint, $message); } /** * Asserts that two XML files are equal. * * @param string $expectedFile * @param string $actualFile * @param string $message * @since Method available since Release 3.1.0 */ public static function assertXmlFileEqualsXmlFile($expectedFile, $actualFile, $message = '') { $expected = PHPUnit_Util_XML::loadFile($expectedFile); $actual = PHPUnit_Util_XML::loadFile($actualFile); self::assertEquals($expected, $actual, $message); } /** * Asserts that two XML files are not equal. * * @param string $expectedFile * @param string $actualFile * @param string $message * @since Method available since Release 3.1.0 */ public static function assertXmlFileNotEqualsXmlFile($expectedFile, $actualFile, $message = '') { $expected = PHPUnit_Util_XML::loadFile($expectedFile); $actual = PHPUnit_Util_XML::loadFile($actualFile); self::assertNotEquals($expected, $actual, $message); } /** * Asserts that two XML documents are equal. * * @param string $expectedFile * @param string $actualXml * @param string $message * @since Method available since Release 3.3.0 */ public static function assertXmlStringEqualsXmlFile($expectedFile, $actualXml, $message = '') { $expected = PHPUnit_Util_XML::loadFile($expectedFile); $actual = PHPUnit_Util_XML::load($actualXml); self::assertEquals($expected, $actual, $message); } /** * Asserts that two XML documents are not equal. * * @param string $expectedFile * @param string $actualXml * @param string $message * @since Method available since Release 3.3.0 */ public static function assertXmlStringNotEqualsXmlFile($expectedFile, $actualXml, $message = '') { $expected = PHPUnit_Util_XML::loadFile($expectedFile); $actual = PHPUnit_Util_XML::load($actualXml); self::assertNotEquals($expected, $actual, $message); } /** * Asserts that two XML documents are equal. * * @param string $expectedXml * @param string $actualXml * @param string $message * @since Method available since Release 3.1.0 */ public static function assertXmlStringEqualsXmlString($expectedXml, $actualXml, $message = '') { $expected = PHPUnit_Util_XML::load($expectedXml); $actual = PHPUnit_Util_XML::load($actualXml); self::assertEquals($expected, $actual, $message); } /** * Asserts that two XML documents are not equal. * * @param string $expectedXml * @param string $actualXml * @param string $message * @since Method available since Release 3.1.0 */ public static function assertXmlStringNotEqualsXmlString($expectedXml, $actualXml, $message = '') { $expected = PHPUnit_Util_XML::load($expectedXml); $actual = PHPUnit_Util_XML::load($actualXml); self::assertNotEquals($expected, $actual, $message); } /** * Asserts that a hierarchy of DOMElements matches. * * @param DOMElement $expectedElement * @param DOMElement $actualElement * @param bool $checkAttributes * @param string $message * @since Method available since Release 3.3.0 */ public static function assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement, $checkAttributes = false, $message = '') { $expectedElement = clone $expectedElement; $actualElement = clone $actualElement; self::assertEquals( $expectedElement->tagName, $actualElement->tagName, $message ); if ($checkAttributes) { self::assertEquals( $expectedElement->attributes->length, $actualElement->attributes->length, sprintf( '%s%sNumber of attributes on node "%s" does not match', $message, !empty($message) ? "\n" : '', $expectedElement->tagName ) ); for ($i = 0; $i < $expectedElement->attributes->length; $i++) { $expectedAttribute = $expectedElement->attributes->item($i); $actualAttribute = $actualElement->attributes->getNamedItem( $expectedAttribute->name ); if (!$actualAttribute) { self::fail( sprintf( '%s%sCould not find attribute "%s" on node "%s"', $message, !empty($message) ? "\n" : '', $expectedAttribute->name, $expectedElement->tagName ) ); } } } PHPUnit_Util_XML::removeCharacterDataNodes($expectedElement); PHPUnit_Util_XML::removeCharacterDataNodes($actualElement); self::assertEquals( $expectedElement->childNodes->length, $actualElement->childNodes->length, sprintf( '%s%sNumber of child nodes of "%s" differs', $message, !empty($message) ? "\n" : '', $expectedElement->tagName ) ); for ($i = 0; $i < $expectedElement->childNodes->length; $i++) { self::assertEqualXMLStructure( $expectedElement->childNodes->item($i), $actualElement->childNodes->item($i), $checkAttributes, $message ); } } /** * Assert the presence, absence, or count of elements in a document matching * the CSS $selector, regardless of the contents of those elements. * * The first argument, $selector, is the CSS selector used to match * the elements in the $actual document. * * The second argument, $count, can be either boolean or numeric. * When boolean, it asserts for presence of elements matching the selector * (true) or absence of elements (false). * When numeric, it asserts the count of elements. * * assertSelectCount("#binder", true, $xml); // any? * assertSelectCount(".binder", 3, $xml); // exactly 3? * * @param array $selector * @param int|bool|array $count * @param mixed $actual * @param string $message * @param bool $isHtml * @since Method available since Release 3.3.0 * @deprecated * @codeCoverageIgnore */ public static function assertSelectCount($selector, $count, $actual, $message = '', $isHtml = true) { trigger_error(__METHOD__ . ' is deprecated', E_USER_DEPRECATED); self::assertSelectEquals( $selector, true, $count, $actual, $message, $isHtml ); } /** * assertSelectRegExp("#binder .name", "/Mike|Derek/", true, $xml); // any? * assertSelectRegExp("#binder .name", "/Mike|Derek/", 3, $xml); // 3? * * @param array $selector * @param string $pattern * @param int|bool|array $count * @param mixed $actual * @param string $message * @param bool $isHtml * @since Method available since Release 3.3.0 * @deprecated * @codeCoverageIgnore */ public static function assertSelectRegExp($selector, $pattern, $count, $actual, $message = '', $isHtml = true) { trigger_error(__METHOD__ . ' is deprecated', E_USER_DEPRECATED); self::assertSelectEquals( $selector, "regexp:$pattern", $count, $actual, $message, $isHtml ); } /** * assertSelectEquals("#binder .name", "Chuck", true, $xml); // any? * assertSelectEquals("#binder .name", "Chuck", false, $xml); // none? * * @param array $selector * @param string $content * @param int|bool|array $count * @param mixed $actual * @param string $message * @param bool $isHtml * @since Method available since Release 3.3.0 * @deprecated * @codeCoverageIgnore */ public static function assertSelectEquals($selector, $content, $count, $actual, $message = '', $isHtml = true) { trigger_error(__METHOD__ . ' is deprecated', E_USER_DEPRECATED); $tags = PHPUnit_Util_XML::cssSelect( $selector, $content, $actual, $isHtml ); // assert specific number of elements if (is_numeric($count)) { $counted = $tags ? count($tags) : 0; self::assertEquals($count, $counted, $message); } // assert any elements exist if true, assert no elements exist if false elseif (is_bool($count)) { $any = count($tags) > 0 && $tags[0] instanceof DOMNode; if ($count) { self::assertTrue($any, $message); } else { self::assertFalse($any, $message); } } // check for range number of elements elseif (is_array($count) && (isset($count['>']) || isset($count['<']) || isset($count['>=']) || isset($count['<=']))) { $counted = $tags ? count($tags) : 0; if (isset($count['>'])) { self::assertTrue($counted > $count['>'], $message); } if (isset($count['>='])) { self::assertTrue($counted >= $count['>='], $message); } if (isset($count['<'])) { self::assertTrue($counted < $count['<'], $message); } if (isset($count['<='])) { self::assertTrue($counted <= $count['<='], $message); } } else { throw new PHPUnit_Framework_Exception; } } /** * Evaluate an HTML or XML string and assert its structure and/or contents. * * The first argument ($matcher) is an associative array that specifies the * match criteria for the assertion: * * - `id` : the node with the given id attribute must match the * corresponding value. * - `tag` : the node type must match the corresponding value. * - `attributes` : a hash. The node's attributes must match the * corresponding values in the hash. * - `content` : The text content must match the given value. * - `parent` : a hash. The node's parent must match the * corresponding hash. * - `child` : a hash. At least one of the node's immediate children * must meet the criteria described by the hash. * - `ancestor` : a hash. At least one of the node's ancestors must * meet the criteria described by the hash. * - `descendant` : a hash. At least one of the node's descendants must * meet the criteria described by the hash. * - `children` : a hash, for counting children of a node. * Accepts the keys: * - `count` : a number which must equal the number of children * that match * - `less_than` : the number of matching children must be greater * than this number * - `greater_than` : the number of matching children must be less than * this number * - `only` : another hash consisting of the keys to use to match * on the children, and only matching children will be * counted * * * // Matcher that asserts that there is an element with an id="my_id". * $matcher = array('id' => 'my_id'); * * // Matcher that asserts that there is a "span" tag. * $matcher = array('tag' => 'span'); * * // Matcher that asserts that there is a "span" tag with the content * // "Hello World". * $matcher = array('tag' => 'span', 'content' => 'Hello World'); * * // Matcher that asserts that there is a "span" tag with content matching * // the regular expression pattern. * $matcher = array('tag' => 'span', 'content' => 'regexp:/Try P(HP|ython)/'); * * // Matcher that asserts that there is a "span" with an "list" class * // attribute. * $matcher = array( * 'tag' => 'span', * 'attributes' => array('class' => 'list') * ); * * // Matcher that asserts that there is a "span" inside of a "div". * $matcher = array( * 'tag' => 'span', * 'parent' => array('tag' => 'div') * ); * * // Matcher that asserts that there is a "span" somewhere inside a * // "table". * $matcher = array( * 'tag' => 'span', * 'ancestor' => array('tag' => 'table') * ); * * // Matcher that asserts that there is a "span" with at least one "em" * // child. * $matcher = array( * 'tag' => 'span', * 'child' => array('tag' => 'em') * ); * * // Matcher that asserts that there is a "span" containing a (possibly * // nested) "strong" tag. * $matcher = array( * 'tag' => 'span', * 'descendant' => array('tag' => 'strong') * ); * * // Matcher that asserts that there is a "span" containing 5-10 "em" tags * // as immediate children. * $matcher = array( * 'tag' => 'span', * 'children' => array( * 'less_than' => 11, * 'greater_than' => 4, * 'only' => array('tag' => 'em') * ) * ); * * // Matcher that asserts that there is a "div", with an "ul" ancestor and * // a "li" parent (with class="enum"), and containing a "span" descendant * // that contains an element with id="my_test" and the text "Hello World". * $matcher = array( * 'tag' => 'div', * 'ancestor' => array('tag' => 'ul'), * 'parent' => array( * 'tag' => 'li', * 'attributes' => array('class' => 'enum') * ), * 'descendant' => array( * 'tag' => 'span', * 'child' => array( * 'id' => 'my_test', * 'content' => 'Hello World' * ) * ) * ); * * // Use assertTag() to apply a $matcher to a piece of $html. * $this->assertTag($matcher, $html); * * // Use assertTag() to apply a $matcher to a piece of $xml. * $this->assertTag($matcher, $xml, '', false); * * * The second argument ($actual) is a string containing either HTML or * XML text to be tested. * * The third argument ($message) is an optional message that will be * used if the assertion fails. * * The fourth argument ($html) is an optional flag specifying whether * to load the $actual string into a DOMDocument using the HTML or * XML load strategy. It is true by default, which assumes the HTML * load strategy. In many cases, this will be acceptable for XML as well. * * @param array $matcher * @param string $actual * @param string $message * @param bool $isHtml * @since Method available since Release 3.3.0 * @deprecated * @codeCoverageIgnore */ public static function assertTag($matcher, $actual, $message = '', $isHtml = true) { trigger_error(__METHOD__ . ' is deprecated', E_USER_DEPRECATED); $dom = PHPUnit_Util_XML::load($actual, $isHtml); $tags = PHPUnit_Util_XML::findNodes($dom, $matcher, $isHtml); $matched = count($tags) > 0 && $tags[0] instanceof DOMNode; self::assertTrue($matched, $message); } /** * This assertion is the exact opposite of assertTag(). * * Rather than asserting that $matcher results in a match, it asserts that * $matcher does not match. * * @param array $matcher * @param string $actual * @param string $message * @param bool $isHtml * @since Method available since Release 3.3.0 * @deprecated * @codeCoverageIgnore */ public static function assertNotTag($matcher, $actual, $message = '', $isHtml = true) { trigger_error(__METHOD__ . ' is deprecated', E_USER_DEPRECATED); $dom = PHPUnit_Util_XML::load($actual, $isHtml); $tags = PHPUnit_Util_XML::findNodes($dom, $matcher, $isHtml); $matched = count($tags) > 0 && $tags[0] instanceof DOMNode; self::assertFalse($matched, $message); } /** * Evaluates a PHPUnit_Framework_Constraint matcher object. * * @param mixed $value * @param PHPUnit_Framework_Constraint $constraint * @param string $message * @since Method available since Release 3.0.0 */ public static function assertThat($value, PHPUnit_Framework_Constraint $constraint, $message = '') { self::$count += count($constraint); $constraint->evaluate($value, $message); } /** * Asserts that a string is a valid JSON string. * * @param string $actualJson * @param string $message * @since Method available since Release 3.7.20 */ public static function assertJson($actualJson, $message = '') { if (!is_string($actualJson)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } self::assertThat($actualJson, self::isJson(), $message); } /** * Asserts that two given JSON encoded objects or arrays are equal. * * @param string $expectedJson * @param string $actualJson * @param string $message */ public static function assertJsonStringEqualsJsonString($expectedJson, $actualJson, $message = '') { self::assertJson($expectedJson, $message); self::assertJson($actualJson, $message); $expected = json_decode($expectedJson); $actual = json_decode($actualJson); self::assertEquals($expected, $actual, $message); } /** * Asserts that two given JSON encoded objects or arrays are not equal. * * @param string $expectedJson * @param string $actualJson * @param string $message */ public static function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, $message = '') { self::assertJson($expectedJson, $message); self::assertJson($actualJson, $message); $expected = json_decode($expectedJson); $actual = json_decode($actualJson); self::assertNotEquals($expected, $actual, $message); } /** * Asserts that the generated JSON encoded object and the content of the given file are equal. * * @param string $expectedFile * @param string $actualJson * @param string $message */ public static function assertJsonStringEqualsJsonFile($expectedFile, $actualJson, $message = '') { self::assertFileExists($expectedFile, $message); $expectedJson = file_get_contents($expectedFile); self::assertJson($expectedJson, $message); self::assertJson($actualJson, $message); // call constraint $constraint = new PHPUnit_Framework_Constraint_JsonMatches( $expectedJson ); self::assertThat($actualJson, $constraint, $message); } /** * Asserts that the generated JSON encoded object and the content of the given file are not equal. * * @param string $expectedFile * @param string $actualJson * @param string $message */ public static function assertJsonStringNotEqualsJsonFile($expectedFile, $actualJson, $message = '') { self::assertFileExists($expectedFile, $message); $expectedJson = file_get_contents($expectedFile); self::assertJson($expectedJson, $message); self::assertJson($actualJson, $message); // call constraint $constraint = new PHPUnit_Framework_Constraint_JsonMatches( $expectedJson ); self::assertThat($actualJson, new PHPUnit_Framework_Constraint_Not($constraint), $message); } /** * Asserts that two JSON files are not equal. * * @param string $expectedFile * @param string $actualFile * @param string $message */ public static function assertJsonFileNotEqualsJsonFile($expectedFile, $actualFile, $message = '') { self::assertFileExists($expectedFile, $message); self::assertFileExists($actualFile, $message); $actualJson = file_get_contents($actualFile); $expectedJson = file_get_contents($expectedFile); self::assertJson($expectedJson, $message); self::assertJson($actualJson, $message); // call constraint $constraintExpected = new PHPUnit_Framework_Constraint_JsonMatches( $expectedJson ); $constraintActual = new PHPUnit_Framework_Constraint_JsonMatches($actualJson); self::assertThat($expectedJson, new PHPUnit_Framework_Constraint_Not($constraintActual), $message); self::assertThat($actualJson, new PHPUnit_Framework_Constraint_Not($constraintExpected), $message); } /** * Asserts that two JSON files are equal. * * @param string $expectedFile * @param string $actualFile * @param string $message */ public static function assertJsonFileEqualsJsonFile($expectedFile, $actualFile, $message = '') { self::assertFileExists($expectedFile, $message); self::assertFileExists($actualFile, $message); $actualJson = file_get_contents($actualFile); $expectedJson = file_get_contents($expectedFile); self::assertJson($expectedJson, $message); self::assertJson($actualJson, $message); // call constraint $constraintExpected = new PHPUnit_Framework_Constraint_JsonMatches( $expectedJson ); $constraintActual = new PHPUnit_Framework_Constraint_JsonMatches($actualJson); self::assertThat($expectedJson, $constraintActual, $message); self::assertThat($actualJson, $constraintExpected, $message); } /** * Returns a PHPUnit_Framework_Constraint_And matcher object. * * @return PHPUnit_Framework_Constraint_And * @since Method available since Release 3.0.0 */ public static function logicalAnd() { $constraints = func_get_args(); $constraint = new PHPUnit_Framework_Constraint_And; $constraint->setConstraints($constraints); return $constraint; } /** * Returns a PHPUnit_Framework_Constraint_Or matcher object. * * @return PHPUnit_Framework_Constraint_Or * @since Method available since Release 3.0.0 */ public static function logicalOr() { $constraints = func_get_args(); $constraint = new PHPUnit_Framework_Constraint_Or; $constraint->setConstraints($constraints); return $constraint; } /** * Returns a PHPUnit_Framework_Constraint_Not matcher object. * * @param PHPUnit_Framework_Constraint $constraint * @return PHPUnit_Framework_Constraint_Not * @since Method available since Release 3.0.0 */ public static function logicalNot(PHPUnit_Framework_Constraint $constraint) { return new PHPUnit_Framework_Constraint_Not($constraint); } /** * Returns a PHPUnit_Framework_Constraint_Xor matcher object. * * @return PHPUnit_Framework_Constraint_Xor * @since Method available since Release 3.0.0 */ public static function logicalXor() { $constraints = func_get_args(); $constraint = new PHPUnit_Framework_Constraint_Xor; $constraint->setConstraints($constraints); return $constraint; } /** * Returns a PHPUnit_Framework_Constraint_IsAnything matcher object. * * @return PHPUnit_Framework_Constraint_IsAnything * @since Method available since Release 3.0.0 */ public static function anything() { return new PHPUnit_Framework_Constraint_IsAnything; } /** * Returns a PHPUnit_Framework_Constraint_IsTrue matcher object. * * @return PHPUnit_Framework_Constraint_IsTrue * @since Method available since Release 3.3.0 */ public static function isTrue() { return new PHPUnit_Framework_Constraint_IsTrue; } /** * Returns a PHPUnit_Framework_Constraint_Callback matcher object. * * @param callable $callback * @return PHPUnit_Framework_Constraint_Callback */ public static function callback($callback) { return new PHPUnit_Framework_Constraint_Callback($callback); } /** * Returns a PHPUnit_Framework_Constraint_IsFalse matcher object. * * @return PHPUnit_Framework_Constraint_IsFalse * @since Method available since Release 3.3.0 */ public static function isFalse() { return new PHPUnit_Framework_Constraint_IsFalse; } /** * Returns a PHPUnit_Framework_Constraint_IsJson matcher object. * * @return PHPUnit_Framework_Constraint_IsJson * @since Method available since Release 3.7.20 */ public static function isJson() { return new PHPUnit_Framework_Constraint_IsJson; } /** * Returns a PHPUnit_Framework_Constraint_IsNull matcher object. * * @return PHPUnit_Framework_Constraint_IsNull * @since Method available since Release 3.3.0 */ public static function isNull() { return new PHPUnit_Framework_Constraint_IsNull; } /** * Returns a PHPUnit_Framework_Constraint_Attribute matcher object. * * @param PHPUnit_Framework_Constraint $constraint * @param string $attributeName * @return PHPUnit_Framework_Constraint_Attribute * @since Method available since Release 3.1.0 */ public static function attribute(PHPUnit_Framework_Constraint $constraint, $attributeName) { return new PHPUnit_Framework_Constraint_Attribute( $constraint, $attributeName ); } /** * Returns a PHPUnit_Framework_Constraint_TraversableContains matcher * object. * * @param mixed $value * @param bool $checkForObjectIdentity * @param bool $checkForNonObjectIdentity * @return PHPUnit_Framework_Constraint_TraversableContains * @since Method available since Release 3.0.0 */ public static function contains($value, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false) { return new PHPUnit_Framework_Constraint_TraversableContains($value, $checkForObjectIdentity, $checkForNonObjectIdentity); } /** * Returns a PHPUnit_Framework_Constraint_TraversableContainsOnly matcher * object. * * @param string $type * @return PHPUnit_Framework_Constraint_TraversableContainsOnly * @since Method available since Release 3.1.4 */ public static function containsOnly($type) { return new PHPUnit_Framework_Constraint_TraversableContainsOnly($type); } /** * Returns a PHPUnit_Framework_Constraint_TraversableContainsOnly matcher * object. * * @param string $classname * @return PHPUnit_Framework_Constraint_TraversableContainsOnly */ public static function containsOnlyInstancesOf($classname) { return new PHPUnit_Framework_Constraint_TraversableContainsOnly($classname, false); } /** * Returns a PHPUnit_Framework_Constraint_ArrayHasKey matcher object. * * @param mixed $key * @return PHPUnit_Framework_Constraint_ArrayHasKey * @since Method available since Release 3.0.0 */ public static function arrayHasKey($key) { return new PHPUnit_Framework_Constraint_ArrayHasKey($key); } /** * Returns a PHPUnit_Framework_Constraint_IsEqual matcher object. * * @param mixed $value * @param float $delta * @param int $maxDepth * @param bool $canonicalize * @param bool $ignoreCase * @return PHPUnit_Framework_Constraint_IsEqual * @since Method available since Release 3.0.0 */ public static function equalTo($value, $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) { return new PHPUnit_Framework_Constraint_IsEqual( $value, $delta, $maxDepth, $canonicalize, $ignoreCase ); } /** * Returns a PHPUnit_Framework_Constraint_IsEqual matcher object * that is wrapped in a PHPUnit_Framework_Constraint_Attribute matcher * object. * * @param string $attributeName * @param mixed $value * @param float $delta * @param int $maxDepth * @param bool $canonicalize * @param bool $ignoreCase * @return PHPUnit_Framework_Constraint_Attribute * @since Method available since Release 3.1.0 */ public static function attributeEqualTo($attributeName, $value, $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) { return self::attribute( self::equalTo( $value, $delta, $maxDepth, $canonicalize, $ignoreCase ), $attributeName ); } /** * Returns a PHPUnit_Framework_Constraint_IsEmpty matcher object. * * @return PHPUnit_Framework_Constraint_IsEmpty * @since Method available since Release 3.5.0 */ public static function isEmpty() { return new PHPUnit_Framework_Constraint_IsEmpty; } /** * Returns a PHPUnit_Framework_Constraint_FileExists matcher object. * * @return PHPUnit_Framework_Constraint_FileExists * @since Method available since Release 3.0.0 */ public static function fileExists() { return new PHPUnit_Framework_Constraint_FileExists; } /** * Returns a PHPUnit_Framework_Constraint_GreaterThan matcher object. * * @param mixed $value * @return PHPUnit_Framework_Constraint_GreaterThan * @since Method available since Release 3.0.0 */ public static function greaterThan($value) { return new PHPUnit_Framework_Constraint_GreaterThan($value); } /** * Returns a PHPUnit_Framework_Constraint_Or matcher object that wraps * a PHPUnit_Framework_Constraint_IsEqual and a * PHPUnit_Framework_Constraint_GreaterThan matcher object. * * @param mixed $value * @return PHPUnit_Framework_Constraint_Or * @since Method available since Release 3.1.0 */ public static function greaterThanOrEqual($value) { return self::logicalOr( new PHPUnit_Framework_Constraint_IsEqual($value), new PHPUnit_Framework_Constraint_GreaterThan($value) ); } /** * Returns a PHPUnit_Framework_Constraint_ClassHasAttribute matcher object. * * @param string $attributeName * @return PHPUnit_Framework_Constraint_ClassHasAttribute * @since Method available since Release 3.1.0 */ public static function classHasAttribute($attributeName) { return new PHPUnit_Framework_Constraint_ClassHasAttribute( $attributeName ); } /** * Returns a PHPUnit_Framework_Constraint_ClassHasStaticAttribute matcher * object. * * @param string $attributeName * @return PHPUnit_Framework_Constraint_ClassHasStaticAttribute * @since Method available since Release 3.1.0 */ public static function classHasStaticAttribute($attributeName) { return new PHPUnit_Framework_Constraint_ClassHasStaticAttribute( $attributeName ); } /** * Returns a PHPUnit_Framework_Constraint_ObjectHasAttribute matcher object. * * @param string $attributeName * @return PHPUnit_Framework_Constraint_ObjectHasAttribute * @since Method available since Release 3.0.0 */ public static function objectHasAttribute($attributeName) { return new PHPUnit_Framework_Constraint_ObjectHasAttribute( $attributeName ); } /** * Returns a PHPUnit_Framework_Constraint_IsIdentical matcher object. * * @param mixed $value * @return PHPUnit_Framework_Constraint_IsIdentical * @since Method available since Release 3.0.0 */ public static function identicalTo($value) { return new PHPUnit_Framework_Constraint_IsIdentical($value); } /** * Returns a PHPUnit_Framework_Constraint_IsInstanceOf matcher object. * * @param string $className * @return PHPUnit_Framework_Constraint_IsInstanceOf * @since Method available since Release 3.0.0 */ public static function isInstanceOf($className) { return new PHPUnit_Framework_Constraint_IsInstanceOf($className); } /** * Returns a PHPUnit_Framework_Constraint_IsType matcher object. * * @param string $type * @return PHPUnit_Framework_Constraint_IsType * @since Method available since Release 3.0.0 */ public static function isType($type) { return new PHPUnit_Framework_Constraint_IsType($type); } /** * Returns a PHPUnit_Framework_Constraint_LessThan matcher object. * * @param mixed $value * @return PHPUnit_Framework_Constraint_LessThan * @since Method available since Release 3.0.0 */ public static function lessThan($value) { return new PHPUnit_Framework_Constraint_LessThan($value); } /** * Returns a PHPUnit_Framework_Constraint_Or matcher object that wraps * a PHPUnit_Framework_Constraint_IsEqual and a * PHPUnit_Framework_Constraint_LessThan matcher object. * * @param mixed $value * @return PHPUnit_Framework_Constraint_Or * @since Method available since Release 3.1.0 */ public static function lessThanOrEqual($value) { return self::logicalOr( new PHPUnit_Framework_Constraint_IsEqual($value), new PHPUnit_Framework_Constraint_LessThan($value) ); } /** * Returns a PHPUnit_Framework_Constraint_PCREMatch matcher object. * * @param string $pattern * @return PHPUnit_Framework_Constraint_PCREMatch * @since Method available since Release 3.0.0 */ public static function matchesRegularExpression($pattern) { return new PHPUnit_Framework_Constraint_PCREMatch($pattern); } /** * Returns a PHPUnit_Framework_Constraint_StringMatches matcher object. * * @param string $string * @return PHPUnit_Framework_Constraint_StringMatches * @since Method available since Release 3.5.0 */ public static function matches($string) { return new PHPUnit_Framework_Constraint_StringMatches($string); } /** * Returns a PHPUnit_Framework_Constraint_StringStartsWith matcher object. * * @param mixed $prefix * @return PHPUnit_Framework_Constraint_StringStartsWith * @since Method available since Release 3.4.0 */ public static function stringStartsWith($prefix) { return new PHPUnit_Framework_Constraint_StringStartsWith($prefix); } /** * Returns a PHPUnit_Framework_Constraint_StringContains matcher object. * * @param string $string * @param bool $case * @return PHPUnit_Framework_Constraint_StringContains * @since Method available since Release 3.0.0 */ public static function stringContains($string, $case = true) { return new PHPUnit_Framework_Constraint_StringContains($string, $case); } /** * Returns a PHPUnit_Framework_Constraint_StringEndsWith matcher object. * * @param mixed $suffix * @return PHPUnit_Framework_Constraint_StringEndsWith * @since Method available since Release 3.4.0 */ public static function stringEndsWith($suffix) { return new PHPUnit_Framework_Constraint_StringEndsWith($suffix); } /** * Returns a PHPUnit_Framework_Constraint_Count matcher object. * * @param int $count * @return PHPUnit_Framework_Constraint_Count */ public static function countOf($count) { return new PHPUnit_Framework_Constraint_Count($count); } /** * Fails a test with the given message. * * @param string $message * @throws PHPUnit_Framework_AssertionFailedError */ public static function fail($message = '') { throw new PHPUnit_Framework_AssertionFailedError($message); } /** * Returns the value of an attribute of a class or an object. * This also works for attributes that are declared protected or private. * * @param mixed $classOrObject * @param string $attributeName * @return mixed * @throws PHPUnit_Framework_Exception */ public static function readAttribute($classOrObject, $attributeName) { if (!is_string($attributeName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string'); } if (!preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'valid attribute name'); } if (is_string($classOrObject)) { if (!class_exists($classOrObject)) { throw PHPUnit_Util_InvalidArgumentHelper::factory( 1, 'class name' ); } return self::getStaticAttribute( $classOrObject, $attributeName ); } elseif (is_object($classOrObject)) { return self::getObjectAttribute( $classOrObject, $attributeName ); } else { throw PHPUnit_Util_InvalidArgumentHelper::factory( 1, 'class name or object' ); } } /** * Returns the value of a static attribute. * This also works for attributes that are declared protected or private. * * @param string $className * @param string $attributeName * @return mixed * @throws PHPUnit_Framework_Exception * @since Method available since Release 4.0.0 */ public static function getStaticAttribute($className, $attributeName) { if (!is_string($className)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (!class_exists($className)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'class name'); } if (!is_string($attributeName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string'); } if (!preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'valid attribute name'); } $class = new ReflectionClass($className); while ($class) { $attributes = $class->getStaticProperties(); if (array_key_exists($attributeName, $attributes)) { return $attributes[$attributeName]; } $class = $class->getParentClass(); } throw new PHPUnit_Framework_Exception( sprintf( 'Attribute "%s" not found in class.', $attributeName ) ); } /** * Returns the value of an object's attribute. * This also works for attributes that are declared protected or private. * * @param object $object * @param string $attributeName * @return mixed * @throws PHPUnit_Framework_Exception * @since Method available since Release 4.0.0 */ public static function getObjectAttribute($object, $attributeName) { if (!is_object($object)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'object'); } if (!is_string($attributeName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string'); } if (!preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'valid attribute name'); } try { $attribute = new ReflectionProperty($object, $attributeName); } catch (ReflectionException $e) { $reflector = new ReflectionObject($object); while ($reflector = $reflector->getParentClass()) { try { $attribute = $reflector->getProperty($attributeName); break; } catch (ReflectionException $e) { } } } if (isset($attribute)) { if (!$attribute || $attribute->isPublic()) { return $object->$attributeName; } $attribute->setAccessible(true); $value = $attribute->getValue($object); $attribute->setAccessible(false); return $value; } throw new PHPUnit_Framework_Exception( sprintf( 'Attribute "%s" not found in object.', $attributeName ) ); } /** * Mark the test as incomplete. * * @param string $message * @throws PHPUnit_Framework_IncompleteTestError * @since Method available since Release 3.0.0 */ public static function markTestIncomplete($message = '') { throw new PHPUnit_Framework_IncompleteTestError($message); } /** * Mark the test as skipped. * * @param string $message * @throws PHPUnit_Framework_SkippedTestError * @since Method available since Release 3.0.0 */ public static function markTestSkipped($message = '') { throw new PHPUnit_Framework_SkippedTestError($message); } /** * Return the current assertion count. * * @return int * @since Method available since Release 3.3.3 */ public static function getCount() { return self::$count; } /** * Reset the assertion counter. * * @since Method available since Release 3.3.3 */ public static function resetCount() { self::$count = 0; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 4.0.0 */ class PHPUnit_Framework_CodeCoverageException extends PHPUnit_Framework_Exception { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A TestSuite is a composite of Tests. It runs a collection of test cases. * * Here is an example using the dynamic test definition. * * * addTest(new MathTest('testPass')); * ?> * * * Alternatively, a TestSuite can extract the tests to be run automatically. * To do so you pass a ReflectionClass instance for your * PHPUnit_Framework_TestCase class to the PHPUnit_Framework_TestSuite * constructor. * * * * * * This constructor creates a suite with all the methods starting with * "test" that take no arguments. * * @since Class available since Release 2.0.0 */ class PHPUnit_Framework_TestSuite implements PHPUnit_Framework_Test, PHPUnit_Framework_SelfDescribing, IteratorAggregate { /** * Last count of tests in this suite. * * @var int|null */ private $cachedNumTests; /** * Enable or disable the backup and restoration of the $GLOBALS array. * * @var bool */ protected $backupGlobals = null; /** * Enable or disable the backup and restoration of static attributes. * * @var bool */ protected $backupStaticAttributes = null; /** * @var bool */ private $disallowChangesToGlobalState = null; /** * @var bool */ protected $runTestInSeparateProcess = false; /** * The name of the test suite. * * @var string */ protected $name = ''; /** * The test groups of the test suite. * * @var array */ protected $groups = array(); /** * The tests in the test suite. * * @var array */ protected $tests = array(); /** * The number of tests in the test suite. * * @var int */ protected $numTests = -1; /** * @var bool */ protected $testCase = false; /** * @var array */ protected $foundClasses = array(); /** * @var PHPUnit_Runner_Filter_Factory */ private $iteratorFilter = null; /** * Constructs a new TestSuite: * * - PHPUnit_Framework_TestSuite() constructs an empty TestSuite. * * - PHPUnit_Framework_TestSuite(ReflectionClass) constructs a * TestSuite from the given class. * * - PHPUnit_Framework_TestSuite(ReflectionClass, String) * constructs a TestSuite from the given class with the given * name. * * - PHPUnit_Framework_TestSuite(String) either constructs a * TestSuite from the given class (if the passed string is the * name of an existing class) or constructs an empty TestSuite * with the given name. * * @param mixed $theClass * @param string $name * @throws PHPUnit_Framework_Exception */ public function __construct($theClass = '', $name = '') { $argumentsValid = false; if (is_object($theClass) && $theClass instanceof ReflectionClass) { $argumentsValid = true; } elseif (is_string($theClass) && $theClass !== '' && class_exists($theClass, false)) { $argumentsValid = true; if ($name == '') { $name = $theClass; } $theClass = new ReflectionClass($theClass); } elseif (is_string($theClass)) { $this->setName($theClass); return; } if (!$argumentsValid) { throw new PHPUnit_Framework_Exception; } if (!$theClass->isSubclassOf('PHPUnit_Framework_TestCase')) { throw new PHPUnit_Framework_Exception( 'Class "' . $theClass->name . '" does not extend PHPUnit_Framework_TestCase.' ); } if ($name != '') { $this->setName($name); } else { $this->setName($theClass->getName()); } $constructor = $theClass->getConstructor(); if ($constructor !== null && !$constructor->isPublic()) { $this->addTest( self::warning( sprintf( 'Class "%s" has no public constructor.', $theClass->getName() ) ) ); return; } foreach ($theClass->getMethods() as $method) { $this->addTestMethod($theClass, $method); } if (empty($this->tests)) { $this->addTest( self::warning( sprintf( 'No tests found in class "%s".', $theClass->getName() ) ) ); } $this->testCase = true; } /** * Returns a string representation of the test suite. * * @return string */ public function toString() { return $this->getName(); } /** * Adds a test to the suite. * * @param PHPUnit_Framework_Test $test * @param array $groups */ public function addTest(PHPUnit_Framework_Test $test, $groups = array()) { $class = new ReflectionClass($test); if (!$class->isAbstract()) { $this->tests[] = $test; $this->numTests = -1; if ($test instanceof self && empty($groups)) { $groups = $test->getGroups(); } if (empty($groups)) { $groups = array('default'); } foreach ($groups as $group) { if (!isset($this->groups[$group])) { $this->groups[$group] = array($test); } else { $this->groups[$group][] = $test; } } } } /** * Adds the tests from the given class to the suite. * * @param mixed $testClass * @throws PHPUnit_Framework_Exception */ public function addTestSuite($testClass) { if (is_string($testClass) && class_exists($testClass)) { $testClass = new ReflectionClass($testClass); } if (!is_object($testClass)) { throw PHPUnit_Util_InvalidArgumentHelper::factory( 1, 'class name or object' ); } if ($testClass instanceof self) { $this->addTest($testClass); } elseif ($testClass instanceof ReflectionClass) { $suiteMethod = false; if (!$testClass->isAbstract()) { if ($testClass->hasMethod(PHPUnit_Runner_BaseTestRunner::SUITE_METHODNAME)) { $method = $testClass->getMethod( PHPUnit_Runner_BaseTestRunner::SUITE_METHODNAME ); if ($method->isStatic()) { $this->addTest( $method->invoke(null, $testClass->getName()) ); $suiteMethod = true; } } } if (!$suiteMethod && !$testClass->isAbstract()) { $this->addTest(new self($testClass)); } } else { throw new PHPUnit_Framework_Exception; } } /** * Wraps both addTest() and addTestSuite * as well as the separate import statements for the user's convenience. * * If the named file cannot be read or there are no new tests that can be * added, a PHPUnit_Framework_Warning will be created instead, * leaving the current test run untouched. * * @param string $filename * @throws PHPUnit_Framework_Exception * @since Method available since Release 2.3.0 */ public function addTestFile($filename) { if (!is_string($filename)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (file_exists($filename) && substr($filename, -5) == '.phpt') { $this->addTest( new PHPUnit_Extensions_PhptTestCase($filename) ); return; } // The given file may contain further stub classes in addition to the // test class itself. Figure out the actual test class. $classes = get_declared_classes(); $filename = PHPUnit_Util_Fileloader::checkAndLoad($filename); $newClasses = array_diff(get_declared_classes(), $classes); // The diff is empty in case a parent class (with test methods) is added // AFTER a child class that inherited from it. To account for that case, // cumulate all discovered classes, so the parent class may be found in // a later invocation. if ($newClasses) { // On the assumption that test classes are defined first in files, // process discovered classes in approximate LIFO order, so as to // avoid unnecessary reflection. $this->foundClasses = array_merge($newClasses, $this->foundClasses); } // The test class's name must match the filename, either in full, or as // a PEAR/PSR-0 prefixed shortname ('NameSpace_ShortName'), or as a // PSR-1 local shortname ('NameSpace\ShortName'). The comparison must be // anchored to prevent false-positive matches (e.g., 'OtherShortName'). $shortname = basename($filename, '.php'); $shortnameRegEx = '/(?:^|_|\\\\)' . preg_quote($shortname, '/') . '$/'; foreach ($this->foundClasses as $i => $className) { if (preg_match($shortnameRegEx, $className)) { $class = new ReflectionClass($className); if ($class->getFileName() == $filename) { $newClasses = array($className); unset($this->foundClasses[$i]); break; } } } foreach ($newClasses as $className) { $class = new ReflectionClass($className); if (!$class->isAbstract()) { if ($class->hasMethod(PHPUnit_Runner_BaseTestRunner::SUITE_METHODNAME)) { $method = $class->getMethod( PHPUnit_Runner_BaseTestRunner::SUITE_METHODNAME ); if ($method->isStatic()) { $this->addTest($method->invoke(null, $className)); } } elseif ($class->implementsInterface('PHPUnit_Framework_Test')) { $this->addTestSuite($class); } } } $this->numTests = -1; } /** * Wrapper for addTestFile() that adds multiple test files. * * @param array|Iterator $filenames * @throws PHPUnit_Framework_Exception * @since Method available since Release 2.3.0 */ public function addTestFiles($filenames) { if (!(is_array($filenames) || (is_object($filenames) && $filenames instanceof Iterator))) { throw PHPUnit_Util_InvalidArgumentHelper::factory( 1, 'array or iterator' ); } foreach ($filenames as $filename) { $this->addTestFile((string) $filename); } } /** * Counts the number of test cases that will be run by this test. * * @param bool $preferCache Indicates if cache is preferred. * @return int */ public function count($preferCache = false) { if ($preferCache && $this->cachedNumTests != null) { $numTests = $this->cachedNumTests; } else { $numTests = 0; foreach ($this as $test) { $numTests += count($test); } $this->cachedNumTests = $numTests; } return $numTests; } /** * @param ReflectionClass $theClass * @param string $name * @return PHPUnit_Framework_Test * @throws PHPUnit_Framework_Exception */ public static function createTest(ReflectionClass $theClass, $name) { $className = $theClass->getName(); if (!$theClass->isInstantiable()) { return self::warning( sprintf('Cannot instantiate class "%s".', $className) ); } $backupSettings = PHPUnit_Util_Test::getBackupSettings( $className, $name ); $preserveGlobalState = PHPUnit_Util_Test::getPreserveGlobalStateSettings( $className, $name ); $runTestInSeparateProcess = PHPUnit_Util_Test::getProcessIsolationSettings( $className, $name ); $constructor = $theClass->getConstructor(); if ($constructor !== null) { $parameters = $constructor->getParameters(); // TestCase() or TestCase($name) if (count($parameters) < 2) { $test = new $className; } // TestCase($name, $data) else { try { $data = PHPUnit_Util_Test::getProvidedData( $className, $name ); } catch (PHPUnit_Framework_IncompleteTestError $e) { $message = sprintf( 'Test for %s::%s marked incomplete by data provider', $className, $name ); $_message = $e->getMessage(); if (!empty($_message)) { $message .= "\n" . $_message; } $data = self::incompleteTest($className, $name, $message); } catch (PHPUnit_Framework_SkippedTestError $e) { $message = sprintf( 'Test for %s::%s skipped by data provider', $className, $name ); $_message = $e->getMessage(); if (!empty($_message)) { $message .= "\n" . $_message; } $data = self::skipTest($className, $name, $message); } catch (Throwable $_t) { $t = $_t; } catch (Exception $_t) { $t = $_t; } if (isset($t)) { $message = sprintf( 'The data provider specified for %s::%s is invalid.', $className, $name ); $_message = $t->getMessage(); if (!empty($_message)) { $message .= "\n" . $_message; } $data = self::warning($message); } // Test method with @dataProvider. if (isset($data)) { $test = new PHPUnit_Framework_TestSuite_DataProvider( $className . '::' . $name ); if (empty($data)) { $data = self::warning( sprintf( 'No tests found in suite "%s".', $test->getName() ) ); } $groups = PHPUnit_Util_Test::getGroups($className, $name); if ($data instanceof PHPUnit_Framework_Warning || $data instanceof PHPUnit_Framework_SkippedTestCase || $data instanceof PHPUnit_Framework_IncompleteTestCase) { $test->addTest($data, $groups); } else { foreach ($data as $_dataName => $_data) { $_test = new $className($name, $_data, $_dataName); if ($runTestInSeparateProcess) { $_test->setRunTestInSeparateProcess(true); if ($preserveGlobalState !== null) { $_test->setPreserveGlobalState($preserveGlobalState); } } if ($backupSettings['backupGlobals'] !== null) { $_test->setBackupGlobals( $backupSettings['backupGlobals'] ); } if ($backupSettings['backupStaticAttributes'] !== null) { $_test->setBackupStaticAttributes( $backupSettings['backupStaticAttributes'] ); } $test->addTest($_test, $groups); } } } else { $test = new $className; } } } if (!isset($test)) { throw new PHPUnit_Framework_Exception('No valid test provided.'); } if ($test instanceof PHPUnit_Framework_TestCase) { $test->setName($name); if ($runTestInSeparateProcess) { $test->setRunTestInSeparateProcess(true); if ($preserveGlobalState !== null) { $test->setPreserveGlobalState($preserveGlobalState); } } if ($backupSettings['backupGlobals'] !== null) { $test->setBackupGlobals($backupSettings['backupGlobals']); } if ($backupSettings['backupStaticAttributes'] !== null) { $test->setBackupStaticAttributes( $backupSettings['backupStaticAttributes'] ); } } return $test; } /** * Creates a default TestResult object. * * @return PHPUnit_Framework_TestResult */ protected function createResult() { return new PHPUnit_Framework_TestResult; } /** * Returns the name of the suite. * * @return string */ public function getName() { return $this->name; } /** * Returns the test groups of the suite. * * @return array * @since Method available since Release 3.2.0 */ public function getGroups() { return array_keys($this->groups); } public function getGroupDetails() { return $this->groups; } /** * Set tests groups of the test case * * @param array $groups * @since Method available since Release 4.0.0 */ public function setGroupDetails(array $groups) { $this->groups = $groups; } /** * Runs the tests and collects their result in a TestResult. * * @param PHPUnit_Framework_TestResult $result * @return PHPUnit_Framework_TestResult */ public function run(PHPUnit_Framework_TestResult $result = null) { if ($result === null) { $result = $this->createResult(); } if (count($this) == 0) { return $result; } $hookMethods = PHPUnit_Util_Test::getHookMethods($this->name); $result->startTestSuite($this); try { $this->setUp(); foreach ($hookMethods['beforeClass'] as $beforeClassMethod) { if ($this->testCase === true && class_exists($this->name, false) && method_exists($this->name, $beforeClassMethod)) { if ($missingRequirements = PHPUnit_Util_Test::getMissingRequirements($this->name, $beforeClassMethod)) { $this->markTestSuiteSkipped(implode(PHP_EOL, $missingRequirements)); } call_user_func(array($this->name, $beforeClassMethod)); } } } catch (PHPUnit_Framework_SkippedTestSuiteError $e) { $numTests = count($this); for ($i = 0; $i < $numTests; $i++) { $result->startTest($this); $result->addFailure($this, $e, 0); $result->endTest($this, 0); } $this->tearDown(); $result->endTestSuite($this); return $result; } catch (Throwable $_t) { $t = $_t; } catch (Exception $_t) { $t = $_t; } if (isset($t)) { $numTests = count($this); for ($i = 0; $i < $numTests; $i++) { $result->startTest($this); $result->addError($this, $t, 0); $result->endTest($this, 0); } $this->tearDown(); $result->endTestSuite($this); return $result; } foreach ($this as $test) { if ($result->shouldStop()) { break; } if ($test instanceof PHPUnit_Framework_TestCase || $test instanceof self) { $test->setDisallowChangesToGlobalState($this->disallowChangesToGlobalState); $test->setBackupGlobals($this->backupGlobals); $test->setBackupStaticAttributes($this->backupStaticAttributes); $test->setRunTestInSeparateProcess($this->runTestInSeparateProcess); } $test->run($result); } foreach ($hookMethods['afterClass'] as $afterClassMethod) { if ($this->testCase === true && class_exists($this->name, false) && method_exists($this->name, $afterClassMethod)) { call_user_func(array($this->name, $afterClassMethod)); } } $this->tearDown(); $result->endTestSuite($this); return $result; } /** * @param bool $runTestInSeparateProcess * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.7.0 */ public function setRunTestInSeparateProcess($runTestInSeparateProcess) { if (is_bool($runTestInSeparateProcess)) { $this->runTestInSeparateProcess = $runTestInSeparateProcess; } else { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean'); } } /** * Runs a test. * * @deprecated * @param PHPUnit_Framework_Test $test * @param PHPUnit_Framework_TestResult $result */ public function runTest(PHPUnit_Framework_Test $test, PHPUnit_Framework_TestResult $result) { $test->run($result); } /** * Sets the name of the suite. * * @param string */ public function setName($name) { $this->name = $name; } /** * Returns the test at the given index. * * @param int * @return PHPUnit_Framework_Test */ public function testAt($index) { if (isset($this->tests[$index])) { return $this->tests[$index]; } else { return false; } } /** * Returns the tests as an enumeration. * * @return array */ public function tests() { return $this->tests; } /** * Set tests of the test suite * * @param array $tests * @since Method available since Release 4.0.0 */ public function setTests(array $tests) { $this->tests = $tests; } /** * Mark the test suite as skipped. * * @param string $message * @throws PHPUnit_Framework_SkippedTestSuiteError * @since Method available since Release 3.0.0 */ public function markTestSuiteSkipped($message = '') { throw new PHPUnit_Framework_SkippedTestSuiteError($message); } /** * @param ReflectionClass $class * @param ReflectionMethod $method */ protected function addTestMethod(ReflectionClass $class, ReflectionMethod $method) { if (!$this->isTestMethod($method)) { return; } $name = $method->getName(); if (!$method->isPublic()) { $this->addTest( self::warning( sprintf( 'Test method "%s" in test class "%s" is not public.', $name, $class->getName() ) ) ); return; } $test = self::createTest($class, $name); if ($test instanceof PHPUnit_Framework_TestCase || $test instanceof PHPUnit_Framework_TestSuite_DataProvider) { $test->setDependencies( PHPUnit_Util_Test::getDependencies($class->getName(), $name) ); } $this->addTest( $test, PHPUnit_Util_Test::getGroups($class->getName(), $name) ); } /** * @param ReflectionMethod $method * @return bool */ public static function isTestMethod(ReflectionMethod $method) { if (strpos($method->name, 'test') === 0) { return true; } // @scenario on TestCase::testMethod() // @test on TestCase::testMethod() $doc_comment = $method->getDocComment(); return strpos($doc_comment, '@test') !== false || strpos($doc_comment, '@scenario') !== false; } /** * @param string $message * @return PHPUnit_Framework_Warning */ protected static function warning($message) { return new PHPUnit_Framework_Warning($message); } /** * @param string $class * @param string $methodName * @param string $message * @return PHPUnit_Framework_SkippedTestCase * @since Method available since Release 4.3.0 */ protected static function skipTest($class, $methodName, $message) { return new PHPUnit_Framework_SkippedTestCase($class, $methodName, $message); } /** * @param string $class * @param string $methodName * @param string $message * @return PHPUnit_Framework_IncompleteTestCase * @since Method available since Release 4.3.0 */ protected static function incompleteTest($class, $methodName, $message) { return new PHPUnit_Framework_IncompleteTestCase($class, $methodName, $message); } /** * @param bool $disallowChangesToGlobalState * @since Method available since Release 4.6.0 */ public function setDisallowChangesToGlobalState($disallowChangesToGlobalState) { if (is_null($this->disallowChangesToGlobalState) && is_bool($disallowChangesToGlobalState)) { $this->disallowChangesToGlobalState = $disallowChangesToGlobalState; } } /** * @param bool $backupGlobals * @since Method available since Release 3.3.0 */ public function setBackupGlobals($backupGlobals) { if (is_null($this->backupGlobals) && is_bool($backupGlobals)) { $this->backupGlobals = $backupGlobals; } } /** * @param bool $backupStaticAttributes * @since Method available since Release 3.4.0 */ public function setBackupStaticAttributes($backupStaticAttributes) { if (is_null($this->backupStaticAttributes) && is_bool($backupStaticAttributes)) { $this->backupStaticAttributes = $backupStaticAttributes; } } /** * Returns an iterator for this test suite. * * @return RecursiveIteratorIterator * @since Method available since Release 3.1.0 */ public function getIterator() { $iterator = new PHPUnit_Util_TestSuiteIterator($this); if ($this->iteratorFilter !== null) { $iterator = $this->iteratorFilter->factory($iterator, $this); } return $iterator; } public function injectFilter(PHPUnit_Runner_Filter_Factory $filter) { $this->iteratorFilter = $filter; foreach ($this as $test) { if ($test instanceof self) { $test->injectFilter($filter); } } } /** * Template Method that is called before the tests * of this test suite are run. * * @since Method available since Release 3.1.0 */ protected function setUp() { } /** * Template Method that is called after the tests * of this test suite have finished running. * * @since Method available since Release 3.1.0 */ protected function tearDown() { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A marker interface for marking any exception/error as result of an unit * test as risky. * * @since Interface available since Release 4.0.0 */ interface PHPUnit_Framework_RiskyTest { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Interface for classes that can return a description of itself. * * @since Interface available since Release 3.0.0 */ interface PHPUnit_Framework_SelfDescribing { /** * Returns a string representation of the object. * * @return string */ public function toString(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Returns a matcher that matches when the method is executed * zero or more times. * * @return PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount * @since Method available since Release 3.0.0 */ function any() { return call_user_func_array( 'PHPUnit_Framework_TestCase::any', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_IsAnything matcher object. * * @return PHPUnit_Framework_Constraint_IsAnything * @since Method available since Release 3.0.0 */ function anything() { return call_user_func_array( 'PHPUnit_Framework_Assert::anything', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_ArrayHasKey matcher object. * * @param mixed $key * @return PHPUnit_Framework_Constraint_ArrayHasKey * @since Method available since Release 3.0.0 */ function arrayHasKey($key) { return call_user_func_array( 'PHPUnit_Framework_Assert::arrayHasKey', func_get_args() ); } /** * Asserts that an array has a specified key. * * @param mixed $key * @param array|ArrayAccess $array * @param string $message * @since Method available since Release 3.0.0 */ function assertArrayHasKey($key, $array, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertArrayHasKey', func_get_args() ); } /** * Asserts that an array has a specified subset. * * @param array|ArrayAccess $subset * @param array|ArrayAccess $array * @param bool $strict Check for object identity * @param string $message * @since Method available since Release 4.4.0 */ function assertArraySubset($subset, $array, $strict = false, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertArraySubset', func_get_args() ); } /** * Asserts that an array does not have a specified key. * * @param mixed $key * @param array|ArrayAccess $array * @param string $message * @since Method available since Release 3.0.0 */ function assertArrayNotHasKey($key, $array, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertArrayNotHasKey', func_get_args() ); } /** * Asserts that a haystack that is stored in a static attribute of a class * or an attribute of an object contains a needle. * * @param mixed $needle * @param string $haystackAttributeName * @param mixed $haystackClassOrObject * @param string $message * @param bool $ignoreCase * @param bool $checkForObjectIdentity * @param bool $checkForNonObjectIdentity * @since Method available since Release 3.0.0 */ function assertAttributeContains($needle, $haystackAttributeName, $haystackClassOrObject, $message = '', $ignoreCase = false, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false) { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeContains', func_get_args() ); } /** * Asserts that a haystack that is stored in a static attribute of a class * or an attribute of an object contains only values of a given type. * * @param string $type * @param string $haystackAttributeName * @param mixed $haystackClassOrObject * @param bool $isNativeType * @param string $message * @since Method available since Release 3.1.4 */ function assertAttributeContainsOnly($type, $haystackAttributeName, $haystackClassOrObject, $isNativeType = null, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeContainsOnly', func_get_args() ); } /** * Asserts the number of elements of an array, Countable or Traversable * that is stored in an attribute. * * @param int $expectedCount * @param string $haystackAttributeName * @param mixed $haystackClassOrObject * @param string $message * @since Method available since Release 3.6.0 */ function assertAttributeCount($expectedCount, $haystackAttributeName, $haystackClassOrObject, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeCount', func_get_args() ); } /** * Asserts that a static attribute of a class or an attribute of an object * is empty. * * @param string $haystackAttributeName * @param mixed $haystackClassOrObject * @param string $message * @since Method available since Release 3.5.0 */ function assertAttributeEmpty($haystackAttributeName, $haystackClassOrObject, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeEmpty', func_get_args() ); } /** * Asserts that a variable is equal to an attribute of an object. * * @param mixed $expected * @param string $actualAttributeName * @param string $actualClassOrObject * @param string $message * @param float $delta * @param int $maxDepth * @param bool $canonicalize * @param bool $ignoreCase */ function assertAttributeEquals($expected, $actualAttributeName, $actualClassOrObject, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeEquals', func_get_args() ); } /** * Asserts that an attribute is greater than another value. * * @param mixed $expected * @param string $actualAttributeName * @param string $actualClassOrObject * @param string $message * @since Method available since Release 3.1.0 */ function assertAttributeGreaterThan($expected, $actualAttributeName, $actualClassOrObject, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeGreaterThan', func_get_args() ); } /** * Asserts that an attribute is greater than or equal to another value. * * @param mixed $expected * @param string $actualAttributeName * @param string $actualClassOrObject * @param string $message * @since Method available since Release 3.1.0 */ function assertAttributeGreaterThanOrEqual($expected, $actualAttributeName, $actualClassOrObject, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeGreaterThanOrEqual', func_get_args() ); } /** * Asserts that an attribute is of a given type. * * @param string $expected * @param string $attributeName * @param mixed $classOrObject * @param string $message * @since Method available since Release 3.5.0 */ function assertAttributeInstanceOf($expected, $attributeName, $classOrObject, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeInstanceOf', func_get_args() ); } /** * Asserts that an attribute is of a given type. * * @param string $expected * @param string $attributeName * @param mixed $classOrObject * @param string $message * @since Method available since Release 3.5.0 */ function assertAttributeInternalType($expected, $attributeName, $classOrObject, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeInternalType', func_get_args() ); } /** * Asserts that an attribute is smaller than another value. * * @param mixed $expected * @param string $actualAttributeName * @param string $actualClassOrObject * @param string $message * @since Method available since Release 3.1.0 */ function assertAttributeLessThan($expected, $actualAttributeName, $actualClassOrObject, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeLessThan', func_get_args() ); } /** * Asserts that an attribute is smaller than or equal to another value. * * @param mixed $expected * @param string $actualAttributeName * @param string $actualClassOrObject * @param string $message * @since Method available since Release 3.1.0 */ function assertAttributeLessThanOrEqual($expected, $actualAttributeName, $actualClassOrObject, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeLessThanOrEqual', func_get_args() ); } /** * Asserts that a haystack that is stored in a static attribute of a class * or an attribute of an object does not contain a needle. * * @param mixed $needle * @param string $haystackAttributeName * @param mixed $haystackClassOrObject * @param string $message * @param bool $ignoreCase * @param bool $checkForObjectIdentity * @param bool $checkForNonObjectIdentity * @since Method available since Release 3.0.0 */ function assertAttributeNotContains($needle, $haystackAttributeName, $haystackClassOrObject, $message = '', $ignoreCase = false, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false) { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeNotContains', func_get_args() ); } /** * Asserts that a haystack that is stored in a static attribute of a class * or an attribute of an object does not contain only values of a given * type. * * @param string $type * @param string $haystackAttributeName * @param mixed $haystackClassOrObject * @param bool $isNativeType * @param string $message * @since Method available since Release 3.1.4 */ function assertAttributeNotContainsOnly($type, $haystackAttributeName, $haystackClassOrObject, $isNativeType = null, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeNotContainsOnly', func_get_args() ); } /** * Asserts the number of elements of an array, Countable or Traversable * that is stored in an attribute. * * @param int $expectedCount * @param string $haystackAttributeName * @param mixed $haystackClassOrObject * @param string $message * @since Method available since Release 3.6.0 */ function assertAttributeNotCount($expectedCount, $haystackAttributeName, $haystackClassOrObject, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeNotCount', func_get_args() ); } /** * Asserts that a static attribute of a class or an attribute of an object * is not empty. * * @param string $haystackAttributeName * @param mixed $haystackClassOrObject * @param string $message * @since Method available since Release 3.5.0 */ function assertAttributeNotEmpty($haystackAttributeName, $haystackClassOrObject, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeNotEmpty', func_get_args() ); } /** * Asserts that a variable is not equal to an attribute of an object. * * @param mixed $expected * @param string $actualAttributeName * @param string $actualClassOrObject * @param string $message * @param float $delta * @param int $maxDepth * @param bool $canonicalize * @param bool $ignoreCase */ function assertAttributeNotEquals($expected, $actualAttributeName, $actualClassOrObject, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeNotEquals', func_get_args() ); } /** * Asserts that an attribute is of a given type. * * @param string $expected * @param string $attributeName * @param mixed $classOrObject * @param string $message * @since Method available since Release 3.5.0 */ function assertAttributeNotInstanceOf($expected, $attributeName, $classOrObject, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeNotInstanceOf', func_get_args() ); } /** * Asserts that an attribute is of a given type. * * @param string $expected * @param string $attributeName * @param mixed $classOrObject * @param string $message * @since Method available since Release 3.5.0 */ function assertAttributeNotInternalType($expected, $attributeName, $classOrObject, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeNotInternalType', func_get_args() ); } /** * Asserts that a variable and an attribute of an object do not have the * same type and value. * * @param mixed $expected * @param string $actualAttributeName * @param object $actualClassOrObject * @param string $message */ function assertAttributeNotSame($expected, $actualAttributeName, $actualClassOrObject, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeNotSame', func_get_args() ); } /** * Asserts that a variable and an attribute of an object have the same type * and value. * * @param mixed $expected * @param string $actualAttributeName * @param object $actualClassOrObject * @param string $message */ function assertAttributeSame($expected, $actualAttributeName, $actualClassOrObject, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertAttributeSame', func_get_args() ); } /** * Asserts that a class has a specified attribute. * * @param string $attributeName * @param string $className * @param string $message * @since Method available since Release 3.1.0 */ function assertClassHasAttribute($attributeName, $className, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertClassHasAttribute', func_get_args() ); } /** * Asserts that a class has a specified static attribute. * * @param string $attributeName * @param string $className * @param string $message * @since Method available since Release 3.1.0 */ function assertClassHasStaticAttribute($attributeName, $className, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertClassHasStaticAttribute', func_get_args() ); } /** * Asserts that a class does not have a specified attribute. * * @param string $attributeName * @param string $className * @param string $message * @since Method available since Release 3.1.0 */ function assertClassNotHasAttribute($attributeName, $className, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertClassNotHasAttribute', func_get_args() ); } /** * Asserts that a class does not have a specified static attribute. * * @param string $attributeName * @param string $className * @param string $message * @since Method available since Release 3.1.0 */ function assertClassNotHasStaticAttribute($attributeName, $className, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertClassNotHasStaticAttribute', func_get_args() ); } /** * Asserts that a haystack contains a needle. * * @param mixed $needle * @param mixed $haystack * @param string $message * @param bool $ignoreCase * @param bool $checkForObjectIdentity * @param bool $checkForNonObjectIdentity * @since Method available since Release 2.1.0 */ function assertContains($needle, $haystack, $message = '', $ignoreCase = false, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false) { return call_user_func_array( 'PHPUnit_Framework_Assert::assertContains', func_get_args() ); } /** * Asserts that a haystack contains only values of a given type. * * @param string $type * @param mixed $haystack * @param bool $isNativeType * @param string $message * @since Method available since Release 3.1.4 */ function assertContainsOnly($type, $haystack, $isNativeType = null, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertContainsOnly', func_get_args() ); } /** * Asserts that a haystack contains only instances of a given classname * * @param string $classname * @param array|Traversable $haystack * @param string $message */ function assertContainsOnlyInstancesOf($classname, $haystack, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertContainsOnlyInstancesOf', func_get_args() ); } /** * Asserts the number of elements of an array, Countable or Traversable. * * @param int $expectedCount * @param mixed $haystack * @param string $message */ function assertCount($expectedCount, $haystack, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertCount', func_get_args() ); } /** * Asserts that a variable is empty. * * @param mixed $actual * @param string $message * @throws PHPUnit_Framework_AssertionFailedError */ function assertEmpty($actual, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertEmpty', func_get_args() ); } /** * Asserts that a hierarchy of DOMElements matches. * * @param DOMElement $expectedElement * @param DOMElement $actualElement * @param bool $checkAttributes * @param string $message * @since Method available since Release 3.3.0 */ function assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement, $checkAttributes = false, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertEqualXMLStructure', func_get_args() ); } /** * Asserts that two variables are equal. * * @param mixed $expected * @param mixed $actual * @param string $message * @param float $delta * @param int $maxDepth * @param bool $canonicalize * @param bool $ignoreCase */ function assertEquals($expected, $actual, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) { return call_user_func_array( 'PHPUnit_Framework_Assert::assertEquals', func_get_args() ); } /** * Asserts that a condition is not true. * * @param bool $condition * @param string $message * @throws PHPUnit_Framework_AssertionFailedError */ function assertNotTrue($condition, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertNotTrue', func_get_args() ); } /** * Asserts that a condition is false. * * @param bool $condition * @param string $message * @throws PHPUnit_Framework_AssertionFailedError */ function assertFalse($condition, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertFalse', func_get_args() ); } /** * Asserts that the contents of one file is equal to the contents of another * file. * * @param string $expected * @param string $actual * @param string $message * @param bool $canonicalize * @param bool $ignoreCase * @since Method available since Release 3.2.14 */ function assertFileEquals($expected, $actual, $message = '', $canonicalize = false, $ignoreCase = false) { return call_user_func_array( 'PHPUnit_Framework_Assert::assertFileEquals', func_get_args() ); } /** * Asserts that a file exists. * * @param string $filename * @param string $message * @since Method available since Release 3.0.0 */ function assertFileExists($filename, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertFileExists', func_get_args() ); } /** * Asserts that the contents of one file is not equal to the contents of * another file. * * @param string $expected * @param string $actual * @param string $message * @param bool $canonicalize * @param bool $ignoreCase * @since Method available since Release 3.2.14 */ function assertFileNotEquals($expected, $actual, $message = '', $canonicalize = false, $ignoreCase = false) { return call_user_func_array( 'PHPUnit_Framework_Assert::assertFileNotEquals', func_get_args() ); } /** * Asserts that a file does not exist. * * @param string $filename * @param string $message * @since Method available since Release 3.0.0 */ function assertFileNotExists($filename, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertFileNotExists', func_get_args() ); } /** * Asserts that a value is greater than another value. * * @param mixed $expected * @param mixed $actual * @param string $message * @since Method available since Release 3.1.0 */ function assertGreaterThan($expected, $actual, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertGreaterThan', func_get_args() ); } /** * Asserts that a value is greater than or equal to another value. * * @param mixed $expected * @param mixed $actual * @param string $message * @since Method available since Release 3.1.0 */ function assertGreaterThanOrEqual($expected, $actual, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertGreaterThanOrEqual', func_get_args() ); } /** * Asserts that a variable is of a given type. * * @param string $expected * @param mixed $actual * @param string $message * @since Method available since Release 3.5.0 */ function assertInstanceOf($expected, $actual, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertInstanceOf', func_get_args() ); } /** * Asserts that a variable is of a given type. * * @param string $expected * @param mixed $actual * @param string $message * @since Method available since Release 3.5.0 */ function assertInternalType($expected, $actual, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertInternalType', func_get_args() ); } /** * Asserts that a string is a valid JSON string. * * @param string $actualJson * @param string $message * @since Method available since Release 3.7.20 */ function assertJson($actualJson, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertJson', func_get_args() ); } /** * Asserts that two JSON files are equal. * * @param string $expectedFile * @param string $actualFile * @param string $message */ function assertJsonFileEqualsJsonFile($expectedFile, $actualFile, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertJsonFileEqualsJsonFile', func_get_args() ); } /** * Asserts that two JSON files are not equal. * * @param string $expectedFile * @param string $actualFile * @param string $message */ function assertJsonFileNotEqualsJsonFile($expectedFile, $actualFile, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertJsonFileNotEqualsJsonFile', func_get_args() ); } /** * Asserts that the generated JSON encoded object and the content of the given file are equal. * * @param string $expectedFile * @param string $actualJson * @param string $message */ function assertJsonStringEqualsJsonFile($expectedFile, $actualJson, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertJsonStringEqualsJsonFile', func_get_args() ); } /** * Asserts that two given JSON encoded objects or arrays are equal. * * @param string $expectedJson * @param string $actualJson * @param string $message */ function assertJsonStringEqualsJsonString($expectedJson, $actualJson, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertJsonStringEqualsJsonString', func_get_args() ); } /** * Asserts that the generated JSON encoded object and the content of the given file are not equal. * * @param string $expectedFile * @param string $actualJson * @param string $message */ function assertJsonStringNotEqualsJsonFile($expectedFile, $actualJson, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertJsonStringNotEqualsJsonFile', func_get_args() ); } /** * Asserts that two given JSON encoded objects or arrays are not equal. * * @param string $expectedJson * @param string $actualJson * @param string $message */ function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertJsonStringNotEqualsJsonString', func_get_args() ); } /** * Asserts that a value is smaller than another value. * * @param mixed $expected * @param mixed $actual * @param string $message * @since Method available since Release 3.1.0 */ function assertLessThan($expected, $actual, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertLessThan', func_get_args() ); } /** * Asserts that a value is smaller than or equal to another value. * * @param mixed $expected * @param mixed $actual * @param string $message * @since Method available since Release 3.1.0 */ function assertLessThanOrEqual($expected, $actual, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertLessThanOrEqual', func_get_args() ); } /** * Asserts that a haystack does not contain a needle. * * @param mixed $needle * @param mixed $haystack * @param string $message * @param bool $ignoreCase * @param bool $checkForObjectIdentity * @param bool $checkForNonObjectIdentity * @since Method available since Release 2.1.0 */ function assertNotContains($needle, $haystack, $message = '', $ignoreCase = false, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false) { return call_user_func_array( 'PHPUnit_Framework_Assert::assertNotContains', func_get_args() ); } /** * Asserts that a haystack does not contain only values of a given type. * * @param string $type * @param mixed $haystack * @param bool $isNativeType * @param string $message * @since Method available since Release 3.1.4 */ function assertNotContainsOnly($type, $haystack, $isNativeType = null, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertNotContainsOnly', func_get_args() ); } /** * Asserts the number of elements of an array, Countable or Traversable. * * @param int $expectedCount * @param mixed $haystack * @param string $message */ function assertNotCount($expectedCount, $haystack, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertNotCount', func_get_args() ); } /** * Asserts that a variable is not empty. * * @param mixed $actual * @param string $message * @throws PHPUnit_Framework_AssertionFailedError */ function assertNotEmpty($actual, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertNotEmpty', func_get_args() ); } /** * Asserts that two variables are not equal. * * @param mixed $expected * @param mixed $actual * @param string $message * @param float $delta * @param int $maxDepth * @param bool $canonicalize * @param bool $ignoreCase * @since Method available since Release 2.3.0 */ function assertNotEquals($expected, $actual, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) { return call_user_func_array( 'PHPUnit_Framework_Assert::assertNotEquals', func_get_args() ); } /** * Asserts that a variable is not of a given type. * * @param string $expected * @param mixed $actual * @param string $message * @since Method available since Release 3.5.0 */ function assertNotInstanceOf($expected, $actual, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertNotInstanceOf', func_get_args() ); } /** * Asserts that a variable is not of a given type. * * @param string $expected * @param mixed $actual * @param string $message * @since Method available since Release 3.5.0 */ function assertNotInternalType($expected, $actual, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertNotInternalType', func_get_args() ); } /** * Asserts that a condition is not false. * * @param bool $condition * @param string $message * @throws PHPUnit_Framework_AssertionFailedError */ function assertNotFalse($condition, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertNotFalse', func_get_args() ); } /** * Asserts that a variable is not null. * * @param mixed $actual * @param string $message */ function assertNotNull($actual, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertNotNull', func_get_args() ); } /** * Asserts that a string does not match a given regular expression. * * @param string $pattern * @param string $string * @param string $message * @since Method available since Release 2.1.0 */ function assertNotRegExp($pattern, $string, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertNotRegExp', func_get_args() ); } /** * Asserts that two variables do not have the same type and value. * Used on objects, it asserts that two variables do not reference * the same object. * * @param mixed $expected * @param mixed $actual * @param string $message */ function assertNotSame($expected, $actual, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertNotSame', func_get_args() ); } /** * Assert that the size of two arrays (or `Countable` or `Traversable` objects) * is not the same. * * @param array|Countable|Traversable $expected * @param array|Countable|Traversable $actual * @param string $message */ function assertNotSameSize($expected, $actual, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertNotSameSize', func_get_args() ); } /** * This assertion is the exact opposite of assertTag(). * * Rather than asserting that $matcher results in a match, it asserts that * $matcher does not match. * * @param array $matcher * @param string $actual * @param string $message * @param bool $isHtml * @since Method available since Release 3.3.0 */ function assertNotTag($matcher, $actual, $message = '', $isHtml = true) { return call_user_func_array( 'PHPUnit_Framework_Assert::assertNotTag', func_get_args() ); } /** * Asserts that a variable is null. * * @param mixed $actual * @param string $message */ function assertNull($actual, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertNull', func_get_args() ); } /** * Asserts that an object has a specified attribute. * * @param string $attributeName * @param object $object * @param string $message * @since Method available since Release 3.0.0 */ function assertObjectHasAttribute($attributeName, $object, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertObjectHasAttribute', func_get_args() ); } /** * Asserts that an object does not have a specified attribute. * * @param string $attributeName * @param object $object * @param string $message * @since Method available since Release 3.0.0 */ function assertObjectNotHasAttribute($attributeName, $object, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertObjectNotHasAttribute', func_get_args() ); } /** * Asserts that a string matches a given regular expression. * * @param string $pattern * @param string $string * @param string $message */ function assertRegExp($pattern, $string, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertRegExp', func_get_args() ); } /** * Asserts that two variables have the same type and value. * Used on objects, it asserts that two variables reference * the same object. * * @param mixed $expected * @param mixed $actual * @param string $message */ function assertSame($expected, $actual, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertSame', func_get_args() ); } /** * Assert that the size of two arrays (or `Countable` or `Traversable` objects) * is the same. * * @param array|Countable|Traversable $expected * @param array|Countable|Traversable $actual * @param string $message */ function assertSameSize($expected, $actual, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertSameSize', func_get_args() ); } /** * Assert the presence, absence, or count of elements in a document matching * the CSS $selector, regardless of the contents of those elements. * * The first argument, $selector, is the CSS selector used to match * the elements in the $actual document. * * The second argument, $count, can be either boolean or numeric. * When boolean, it asserts for presence of elements matching the selector * (true) or absence of elements (false). * When numeric, it asserts the count of elements. * * assertSelectCount("#binder", true, $xml); // any? * assertSelectCount(".binder", 3, $xml); // exactly 3? * * @param array $selector * @param int $count * @param mixed $actual * @param string $message * @param bool $isHtml * @since Method available since Release 3.3.0 */ function assertSelectCount($selector, $count, $actual, $message = '', $isHtml = true) { return call_user_func_array( 'PHPUnit_Framework_Assert::assertSelectCount', func_get_args() ); } /** * assertSelectEquals("#binder .name", "Chuck", true, $xml); // any? * assertSelectEquals("#binder .name", "Chuck", false, $xml); // none? * * @param array $selector * @param string $content * @param int $count * @param mixed $actual * @param string $message * @param bool $isHtml * @since Method available since Release 3.3.0 */ function assertSelectEquals($selector, $content, $count, $actual, $message = '', $isHtml = true) { return call_user_func_array( 'PHPUnit_Framework_Assert::assertSelectEquals', func_get_args() ); } /** * assertSelectRegExp("#binder .name", "/Mike|Derek/", true, $xml); // any? * assertSelectRegExp("#binder .name", "/Mike|Derek/", 3, $xml);// 3? * * @param array $selector * @param string $pattern * @param int $count * @param mixed $actual * @param string $message * @param bool $isHtml * @since Method available since Release 3.3.0 */ function assertSelectRegExp($selector, $pattern, $count, $actual, $message = '', $isHtml = true) { return call_user_func_array( 'PHPUnit_Framework_Assert::assertSelectRegExp', func_get_args() ); } /** * Asserts that a string ends not with a given prefix. * * @param string $suffix * @param string $string * @param string $message * @since Method available since Release 3.4.0 */ function assertStringEndsNotWith($suffix, $string, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertStringEndsNotWith', func_get_args() ); } /** * Asserts that a string ends with a given prefix. * * @param string $suffix * @param string $string * @param string $message * @since Method available since Release 3.4.0 */ function assertStringEndsWith($suffix, $string, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertStringEndsWith', func_get_args() ); } /** * Asserts that the contents of a string is equal * to the contents of a file. * * @param string $expectedFile * @param string $actualString * @param string $message * @param bool $canonicalize * @param bool $ignoreCase * @since Method available since Release 3.3.0 */ function assertStringEqualsFile($expectedFile, $actualString, $message = '', $canonicalize = false, $ignoreCase = false) { return call_user_func_array( 'PHPUnit_Framework_Assert::assertStringEqualsFile', func_get_args() ); } /** * Asserts that a string matches a given format string. * * @param string $format * @param string $string * @param string $message * @since Method available since Release 3.5.0 */ function assertStringMatchesFormat($format, $string, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertStringMatchesFormat', func_get_args() ); } /** * Asserts that a string matches a given format file. * * @param string $formatFile * @param string $string * @param string $message * @since Method available since Release 3.5.0 */ function assertStringMatchesFormatFile($formatFile, $string, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertStringMatchesFormatFile', func_get_args() ); } /** * Asserts that the contents of a string is not equal * to the contents of a file. * * @param string $expectedFile * @param string $actualString * @param string $message * @param bool $canonicalize * @param bool $ignoreCase * @since Method available since Release 3.3.0 */ function assertStringNotEqualsFile($expectedFile, $actualString, $message = '', $canonicalize = false, $ignoreCase = false) { return call_user_func_array( 'PHPUnit_Framework_Assert::assertStringNotEqualsFile', func_get_args() ); } /** * Asserts that a string does not match a given format string. * * @param string $format * @param string $string * @param string $message * @since Method available since Release 3.5.0 */ function assertStringNotMatchesFormat($format, $string, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertStringNotMatchesFormat', func_get_args() ); } /** * Asserts that a string does not match a given format string. * * @param string $formatFile * @param string $string * @param string $message * @since Method available since Release 3.5.0 */ function assertStringNotMatchesFormatFile($formatFile, $string, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertStringNotMatchesFormatFile', func_get_args() ); } /** * Asserts that a string starts not with a given prefix. * * @param string $prefix * @param string $string * @param string $message * @since Method available since Release 3.4.0 */ function assertStringStartsNotWith($prefix, $string, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertStringStartsNotWith', func_get_args() ); } /** * Asserts that a string starts with a given prefix. * * @param string $prefix * @param string $string * @param string $message * @since Method available since Release 3.4.0 */ function assertStringStartsWith($prefix, $string, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertStringStartsWith', func_get_args() ); } /** * Evaluate an HTML or XML string and assert its structure and/or contents. * * The first argument ($matcher) is an associative array that specifies the * match criteria for the assertion: * * - `id` : the node with the given id attribute must match the * corresponding value. * - `tag` : the node type must match the corresponding value. * - `attributes` : a hash. The node's attributes must match the * corresponding values in the hash. * - `content` : The text content must match the given value. * - `parent` : a hash. The node's parent must match the * corresponding hash. * - `child`: a hash. At least one of the node's immediate children * must meet the criteria described by the hash. * - `ancestor` : a hash. At least one of the node's ancestors must * meet the criteria described by the hash. * - `descendant` : a hash. At least one of the node's descendants must * meet the criteria described by the hash. * - `children` : a hash, for counting children of a node. * Accepts the keys: *- `count`: a number which must equal the number of children * that match *- `less_than`: the number of matching children must be greater * than this number *- `greater_than` : the number of matching children must be less than * this number *- `only` : another hash consisting of the keys to use to match * on the children, and only matching children will be * counted * * * // Matcher that asserts that there is an element with an id="my_id". * $matcher = array('id' => 'my_id'); * * // Matcher that asserts that there is a "span" tag. * $matcher = array('tag' => 'span'); * * // Matcher that asserts that there is a "span" tag with the content * // "Hello World". * $matcher = array('tag' => 'span', 'content' => 'Hello World'); * * // Matcher that asserts that there is a "span" tag with content matching * // the regular expression pattern. * $matcher = array('tag' => 'span', 'content' => 'regexp:/Try P(HP|ython)/'); * * // Matcher that asserts that there is a "span" with an "list" class * // attribute. * $matcher = array( * 'tag'=> 'span', * 'attributes' => array('class' => 'list') * ); * * // Matcher that asserts that there is a "span" inside of a "div". * $matcher = array( * 'tag'=> 'span', * 'parent' => array('tag' => 'div') * ); * * // Matcher that asserts that there is a "span" somewhere inside a * // "table". * $matcher = array( * 'tag' => 'span', * 'ancestor' => array('tag' => 'table') * ); * * // Matcher that asserts that there is a "span" with at least one "em" * // child. * $matcher = array( * 'tag' => 'span', * 'child' => array('tag' => 'em') * ); * * // Matcher that asserts that there is a "span" containing a (possibly * // nested) "strong" tag. * $matcher = array( * 'tag'=> 'span', * 'descendant' => array('tag' => 'strong') * ); * * // Matcher that asserts that there is a "span" containing 5-10 "em" tags * // as immediate children. * $matcher = array( * 'tag' => 'span', * 'children' => array( * 'less_than'=> 11, * 'greater_than' => 4, * 'only' => array('tag' => 'em') * ) * ); * * // Matcher that asserts that there is a "div", with an "ul" ancestor and * // a "li" parent (with class="enum"), and containing a "span" descendant * // that contains an element with id="my_test" and the text "Hello World". * $matcher = array( * 'tag'=> 'div', * 'ancestor' => array('tag' => 'ul'), * 'parent' => array( * 'tag'=> 'li', * 'attributes' => array('class' => 'enum') * ), * 'descendant' => array( * 'tag' => 'span', * 'child' => array( * 'id' => 'my_test', * 'content' => 'Hello World' * ) * ) * ); * * // Use assertTag() to apply a $matcher to a piece of $html. * $this->assertTag($matcher, $html); * * // Use assertTag() to apply a $matcher to a piece of $xml. * $this->assertTag($matcher, $xml, '', false); * * * The second argument ($actual) is a string containing either HTML or * XML text to be tested. * * The third argument ($message) is an optional message that will be * used if the assertion fails. * * The fourth argument ($html) is an optional flag specifying whether * to load the $actual string into a DOMDocument using the HTML or * XML load strategy. It is true by default, which assumes the HTML * load strategy. In many cases, this will be acceptable for XML as well. * * @param array $matcher * @param string $actual * @param string $message * @param bool $isHtml * @since Method available since Release 3.3.0 */ function assertTag($matcher, $actual, $message = '', $isHtml = true) { return call_user_func_array( 'PHPUnit_Framework_Assert::assertTag', func_get_args() ); } /** * Evaluates a PHPUnit_Framework_Constraint matcher object. * * @param mixed$value * @param PHPUnit_Framework_Constraint $constraint * @param string $message * @since Method available since Release 3.0.0 */ function assertThat($value, PHPUnit_Framework_Constraint $constraint, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertThat', func_get_args() ); } /** * Asserts that a condition is true. * * @param bool $condition * @param string $message * @throws PHPUnit_Framework_AssertionFailedError */ function assertTrue($condition, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertTrue', func_get_args() ); } /** * Asserts that two XML files are equal. * * @param string $expectedFile * @param string $actualFile * @param string $message * @since Method available since Release 3.1.0 */ function assertXmlFileEqualsXmlFile($expectedFile, $actualFile, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertXmlFileEqualsXmlFile', func_get_args() ); } /** * Asserts that two XML files are not equal. * * @param string $expectedFile * @param string $actualFile * @param string $message * @since Method available since Release 3.1.0 */ function assertXmlFileNotEqualsXmlFile($expectedFile, $actualFile, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertXmlFileNotEqualsXmlFile', func_get_args() ); } /** * Asserts that two XML documents are equal. * * @param string $expectedFile * @param string $actualXml * @param string $message * @since Method available since Release 3.3.0 */ function assertXmlStringEqualsXmlFile($expectedFile, $actualXml, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertXmlStringEqualsXmlFile', func_get_args() ); } /** * Asserts that two XML documents are equal. * * @param string $expectedXml * @param string $actualXml * @param string $message * @since Method available since Release 3.1.0 */ function assertXmlStringEqualsXmlString($expectedXml, $actualXml, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertXmlStringEqualsXmlString', func_get_args() ); } /** * Asserts that two XML documents are not equal. * * @param string $expectedFile * @param string $actualXml * @param string $message * @since Method available since Release 3.3.0 */ function assertXmlStringNotEqualsXmlFile($expectedFile, $actualXml, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertXmlStringNotEqualsXmlFile', func_get_args() ); } /** * Asserts that two XML documents are not equal. * * @param string $expectedXml * @param string $actualXml * @param string $message * @since Method available since Release 3.1.0 */ function assertXmlStringNotEqualsXmlString($expectedXml, $actualXml, $message = '') { return call_user_func_array( 'PHPUnit_Framework_Assert::assertXmlStringNotEqualsXmlString', func_get_args() ); } /** * Returns a matcher that matches when the method is executed * at the given $index. * * @param int $index * @return PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex * @since Method available since Release 3.0.0 */ function at($index) { return call_user_func_array( 'PHPUnit_Framework_TestCase::at', func_get_args() ); } /** * Returns a matcher that matches when the method is executed at least once. * * @return PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce * @since Method available since Release 3.0.0 */ function atLeastOnce() { return call_user_func_array( 'PHPUnit_Framework_TestCase::atLeastOnce', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_Attribute matcher object. * * @param PHPUnit_Framework_Constraint $constraint * @param string $attributeName * @return PHPUnit_Framework_Constraint_Attribute * @since Method available since Release 3.1.0 */ function attribute(PHPUnit_Framework_Constraint $constraint, $attributeName) { return call_user_func_array( 'PHPUnit_Framework_Assert::attribute', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_IsEqual matcher object * that is wrapped in a PHPUnit_Framework_Constraint_Attribute matcher * object. * * @param string $attributeName * @param mixed $value * @param float $delta * @param int $maxDepth * @param bool $canonicalize * @param bool $ignoreCase * @return PHPUnit_Framework_Constraint_Attribute * @since Method available since Release 3.1.0 */ function attributeEqualTo($attributeName, $value, $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) { return call_user_func_array( 'PHPUnit_Framework_Assert::attributeEqualTo', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_Callback matcher object. * * @param callable $callback * @return PHPUnit_Framework_Constraint_Callback */ function callback($callback) { return call_user_func_array( 'PHPUnit_Framework_Assert::callback', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_ClassHasAttribute matcher object. * * @param string $attributeName * @return PHPUnit_Framework_Constraint_ClassHasAttribute * @since Method available since Release 3.1.0 */ function classHasAttribute($attributeName) { return call_user_func_array( 'PHPUnit_Framework_Assert::classHasAttribute', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_ClassHasStaticAttribute matcher * object. * * @param string $attributeName * @return PHPUnit_Framework_Constraint_ClassHasStaticAttribute * @since Method available since Release 3.1.0 */ function classHasStaticAttribute($attributeName) { return call_user_func_array( 'PHPUnit_Framework_Assert::classHasStaticAttribute', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_TraversableContains matcher * object. * * @param mixed $value * @param bool $checkForObjectIdentity * @param bool $checkForNonObjectIdentity * @return PHPUnit_Framework_Constraint_TraversableContains * @since Method available since Release 3.0.0 */ function contains($value, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false) { return call_user_func_array( 'PHPUnit_Framework_Assert::contains', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_TraversableContainsOnly matcher * object. * * @param string $type * @return PHPUnit_Framework_Constraint_TraversableContainsOnly * @since Method available since Release 3.1.4 */ function containsOnly($type) { return call_user_func_array( 'PHPUnit_Framework_Assert::containsOnly', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_TraversableContainsOnly matcher * object. * * @param string $classname * @return PHPUnit_Framework_Constraint_TraversableContainsOnly */ function containsOnlyInstancesOf($classname) { return call_user_func_array( 'PHPUnit_Framework_Assert::containsOnlyInstancesOf', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_IsEqual matcher object. * * @param mixed $value * @param float $delta * @param int $maxDepth * @param bool $canonicalize * @param bool $ignoreCase * @return PHPUnit_Framework_Constraint_IsEqual * @since Method available since Release 3.0.0 */ function equalTo($value, $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) { return call_user_func_array( 'PHPUnit_Framework_Assert::equalTo', func_get_args() ); } /** * Returns a matcher that matches when the method is executed * exactly $count times. * * @param int $count * @return PHPUnit_Framework_MockObject_Matcher_InvokedCount * @since Method available since Release 3.0.0 */ function exactly($count) { return call_user_func_array( 'PHPUnit_Framework_TestCase::exactly', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_FileExists matcher object. * * @return PHPUnit_Framework_Constraint_FileExists * @since Method available since Release 3.0.0 */ function fileExists() { return call_user_func_array( 'PHPUnit_Framework_Assert::fileExists', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_GreaterThan matcher object. * * @param mixed $value * @return PHPUnit_Framework_Constraint_GreaterThan * @since Method available since Release 3.0.0 */ function greaterThan($value) { return call_user_func_array( 'PHPUnit_Framework_Assert::greaterThan', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_Or matcher object that wraps * a PHPUnit_Framework_Constraint_IsEqual and a * PHPUnit_Framework_Constraint_GreaterThan matcher object. * * @param mixed $value * @return PHPUnit_Framework_Constraint_Or * @since Method available since Release 3.1.0 */ function greaterThanOrEqual($value) { return call_user_func_array( 'PHPUnit_Framework_Assert::greaterThanOrEqual', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_IsIdentical matcher object. * * @param mixed $value * @return PHPUnit_Framework_Constraint_IsIdentical * @since Method available since Release 3.0.0 */ function identicalTo($value) { return call_user_func_array( 'PHPUnit_Framework_Assert::identicalTo', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_IsEmpty matcher object. * * @return PHPUnit_Framework_Constraint_IsEmpty * @since Method available since Release 3.5.0 */ function isEmpty() { return call_user_func_array( 'PHPUnit_Framework_Assert::isEmpty', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_IsFalse matcher object. * * @return PHPUnit_Framework_Constraint_IsFalse * @since Method available since Release 3.3.0 */ function isFalse() { return call_user_func_array( 'PHPUnit_Framework_Assert::isFalse', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_IsInstanceOf matcher object. * * @param string $className * @return PHPUnit_Framework_Constraint_IsInstanceOf * @since Method available since Release 3.0.0 */ function isInstanceOf($className) { return call_user_func_array( 'PHPUnit_Framework_Assert::isInstanceOf', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_IsJson matcher object. * * @return PHPUnit_Framework_Constraint_IsJson * @since Method available since Release 3.7.20 */ function isJson() { return call_user_func_array( 'PHPUnit_Framework_Assert::isJson', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_IsNull matcher object. * * @return PHPUnit_Framework_Constraint_IsNull * @since Method available since Release 3.3.0 */ function isNull() { return call_user_func_array( 'PHPUnit_Framework_Assert::isNull', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_IsTrue matcher object. * * @return PHPUnit_Framework_Constraint_IsTrue * @since Method available since Release 3.3.0 */ function isTrue() { return call_user_func_array( 'PHPUnit_Framework_Assert::isTrue', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_IsType matcher object. * * @param string $type * @return PHPUnit_Framework_Constraint_IsType * @since Method available since Release 3.0.0 */ function isType($type) { return call_user_func_array( 'PHPUnit_Framework_Assert::isType', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_LessThan matcher object. * * @param mixed $value * @return PHPUnit_Framework_Constraint_LessThan * @since Method available since Release 3.0.0 */ function lessThan($value) { return call_user_func_array( 'PHPUnit_Framework_Assert::lessThan', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_Or matcher object that wraps * a PHPUnit_Framework_Constraint_IsEqual and a * PHPUnit_Framework_Constraint_LessThan matcher object. * * @param mixed $value * @return PHPUnit_Framework_Constraint_Or * @since Method available since Release 3.1.0 */ function lessThanOrEqual($value) { return call_user_func_array( 'PHPUnit_Framework_Assert::lessThanOrEqual', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_And matcher object. * * @return PHPUnit_Framework_Constraint_And * @since Method available since Release 3.0.0 */ function logicalAnd() { return call_user_func_array( 'PHPUnit_Framework_Assert::logicalAnd', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_Not matcher object. * * @param PHPUnit_Framework_Constraint $constraint * @return PHPUnit_Framework_Constraint_Not * @since Method available since Release 3.0.0 */ function logicalNot(PHPUnit_Framework_Constraint $constraint) { return call_user_func_array( 'PHPUnit_Framework_Assert::logicalNot', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_Or matcher object. * * @return PHPUnit_Framework_Constraint_Or * @since Method available since Release 3.0.0 */ function logicalOr() { return call_user_func_array( 'PHPUnit_Framework_Assert::logicalOr', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_Xor matcher object. * * @return PHPUnit_Framework_Constraint_Xor * @since Method available since Release 3.0.0 */ function logicalXor() { return call_user_func_array( 'PHPUnit_Framework_Assert::logicalXor', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_StringMatches matcher object. * * @param string $string * @return PHPUnit_Framework_Constraint_StringMatches * @since Method available since Release 3.5.0 */ function matches($string) { return call_user_func_array( 'PHPUnit_Framework_Assert::matches', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_PCREMatch matcher object. * * @param string $pattern * @return PHPUnit_Framework_Constraint_PCREMatch * @since Method available since Release 3.0.0 */ function matchesRegularExpression($pattern) { return call_user_func_array( 'PHPUnit_Framework_Assert::matchesRegularExpression', func_get_args() ); } /** * Returns a matcher that matches when the method is never executed. * * @return PHPUnit_Framework_MockObject_Matcher_InvokedCount * @since Method available since Release 3.0.0 */ function never() { return call_user_func_array( 'PHPUnit_Framework_TestCase::never', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_ObjectHasAttribute matcher object. * * @param string $attributeName * @return PHPUnit_Framework_Constraint_ObjectHasAttribute * @since Method available since Release 3.0.0 */ function objectHasAttribute($attributeName) { return call_user_func_array( 'PHPUnit_Framework_Assert::objectHasAttribute', func_get_args() ); } /** * @param mixed $value, ... * @return PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls * @since Method available since Release 3.0.0 */ function onConsecutiveCalls() { return call_user_func_array( 'PHPUnit_Framework_TestCase::onConsecutiveCalls', func_get_args() ); } /** * Returns a matcher that matches when the method is executed exactly once. * * @return PHPUnit_Framework_MockObject_Matcher_InvokedCount * @since Method available since Release 3.0.0 */ function once() { return call_user_func_array( 'PHPUnit_Framework_TestCase::once', func_get_args() ); } /** * @param int $argumentIndex * @return PHPUnit_Framework_MockObject_Stub_ReturnArgument * @since Method available since Release 3.3.0 */ function returnArgument($argumentIndex) { return call_user_func_array( 'PHPUnit_Framework_TestCase::returnArgument', func_get_args() ); } /** * @param mixed $callback * @return PHPUnit_Framework_MockObject_Stub_ReturnCallback * @since Method available since Release 3.3.0 */ function returnCallback($callback) { return call_user_func_array( 'PHPUnit_Framework_TestCase::returnCallback', func_get_args() ); } /** * Returns the current object. * * This method is useful when mocking a fluent interface. * * @return PHPUnit_Framework_MockObject_Stub_ReturnSelf * @since Method available since Release 3.6.0 */ function returnSelf() { return call_user_func_array( 'PHPUnit_Framework_TestCase::returnSelf', func_get_args() ); } /** * @param mixed $value * @return PHPUnit_Framework_MockObject_Stub_Return * @since Method available since Release 3.0.0 */ function returnValue($value) { return call_user_func_array( 'PHPUnit_Framework_TestCase::returnValue', func_get_args() ); } /** * @param array $valueMap * @return PHPUnit_Framework_MockObject_Stub_ReturnValueMap * @since Method available since Release 3.6.0 */ function returnValueMap(array $valueMap) { return call_user_func_array( 'PHPUnit_Framework_TestCase::returnValueMap', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_StringContains matcher object. * * @param string $string * @param bool $case * @return PHPUnit_Framework_Constraint_StringContains * @since Method available since Release 3.0.0 */ function stringContains($string, $case = true) { return call_user_func_array( 'PHPUnit_Framework_Assert::stringContains', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_StringEndsWith matcher object. * * @param mixed $suffix * @return PHPUnit_Framework_Constraint_StringEndsWith * @since Method available since Release 3.4.0 */ function stringEndsWith($suffix) { return call_user_func_array( 'PHPUnit_Framework_Assert::stringEndsWith', func_get_args() ); } /** * Returns a PHPUnit_Framework_Constraint_StringStartsWith matcher object. * * @param mixed $prefix * @return PHPUnit_Framework_Constraint_StringStartsWith * @since Method available since Release 3.4.0 */ function stringStartsWith($prefix) { return call_user_func_array( 'PHPUnit_Framework_Assert::stringStartsWith', func_get_args() ); } /** * @param Exception $exception * @return PHPUnit_Framework_MockObject_Stub_Exception * @since Method available since Release 3.1.0 */ function throwException(Exception $exception) { return call_user_func_array( 'PHPUnit_Framework_TestCase::throwException', func_get_args() ); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Creates a synthetic failed assertion. * * @since Class available since Release 3.5.0 */ class PHPUnit_Framework_SyntheticError extends PHPUnit_Framework_AssertionFailedError { /** * The synthetic file. * * @var string */ protected $syntheticFile = ''; /** * The synthetic line number. * * @var int */ protected $syntheticLine = 0; /** * The synthetic trace. * * @var array */ protected $syntheticTrace = array(); /** * Constructor. * * @param string $message * @param int $code * @param string $file * @param int $line * @param array $trace */ public function __construct($message, $code, $file, $line, $trace) { parent::__construct($message, $code); $this->syntheticFile = $file; $this->syntheticLine = $line; $this->syntheticTrace = $trace; } /** * @return string */ public function getSyntheticFile() { return $this->syntheticFile; } /** * @return int */ public function getSyntheticLine() { return $this->syntheticLine; } /** * @return array */ public function getSyntheticTrace() { return $this->syntheticTrace; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Extension to PHPUnit_Framework_AssertionFailedError to mark the special * case of a risky test. * * @since Class available since Release 4.0.0 */ class PHPUnit_Framework_RiskyTestError extends PHPUnit_Framework_AssertionFailedError implements PHPUnit_Framework_RiskyTest { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A Listener for test progress. * * @since Interface available since Release 2.0.0 */ interface PHPUnit_Framework_TestListener { /** * An error occurred. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addError(PHPUnit_Framework_Test $test, Exception $e, $time); /** * A failure occurred. * * @param PHPUnit_Framework_Test $test * @param PHPUnit_Framework_AssertionFailedError $e * @param float $time */ public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time); /** * Incomplete test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time); /** * Risky test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time * @since Method available since Release 4.0.0 */ public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time); /** * Skipped test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time * @since Method available since Release 3.0.0 */ public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time); /** * A test suite started. * * @param PHPUnit_Framework_TestSuite $suite * @since Method available since Release 2.2.0 */ public function startTestSuite(PHPUnit_Framework_TestSuite $suite); /** * A test suite ended. * * @param PHPUnit_Framework_TestSuite $suite * @since Method available since Release 2.2.0 */ public function endTestSuite(PHPUnit_Framework_TestSuite $suite); /** * A test started. * * @param PHPUnit_Framework_Test $test */ public function startTest(PHPUnit_Framework_Test $test); /** * A test ended. * * @param PHPUnit_Framework_Test $test * @param float $time */ public function endTest(PHPUnit_Framework_Test $test, $time); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Wraps Exceptions thrown by code under test. * * Re-instantiates Exceptions thrown by user-space code to retain their original * class names, properties, and stack traces (but without arguments). * * Unlike PHPUnit_Framework_Exception, the complete stack of previous Exceptions * is processed. * * @since Class available since Release 4.3.0 */ class PHPUnit_Framework_ExceptionWrapper extends PHPUnit_Framework_Exception { /** * @var string */ protected $classname; /** * @var PHPUnit_Framework_ExceptionWrapper|null */ protected $previous; /** * @param Throwable|Exception $e */ public function __construct($e) { // PDOException::getCode() is a string. // @see http://php.net/manual/en/class.pdoexception.php#95812 parent::__construct($e->getMessage(), (int) $e->getCode()); $this->classname = get_class($e); $this->file = $e->getFile(); $this->line = $e->getLine(); $this->serializableTrace = $e->getTrace(); foreach ($this->serializableTrace as $i => $call) { unset($this->serializableTrace[$i]['args']); } if ($e->getPrevious()) { $this->previous = new self($e->getPrevious()); } } /** * @return string */ public function getClassname() { return $this->classname; } /** * @return PHPUnit_Framework_ExceptionWrapper */ public function getPreviousWrapped() { return $this->previous; } /** * @return string */ public function __toString() { $string = PHPUnit_Framework_TestFailure::exceptionToString($this); if ($trace = PHPUnit_Util_Filter::getFilteredStacktrace($this)) { $string .= "\n" . $trace; } if ($this->previous) { $string .= "\nCaused by\n" . $this->previous; } return $string; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A marker interface for marking a unit test as being skipped. * * @since Interface available since Release 3.0.0 */ interface PHPUnit_Framework_SkippedTest { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 4.0.0 */ class PHPUnit_Framework_InvalidCoversTargetException extends PHPUnit_Framework_CodeCoverageException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use SebastianBergmann\GlobalState\Snapshot; use SebastianBergmann\GlobalState\Restorer; use SebastianBergmann\GlobalState\Blacklist; use SebastianBergmann\Diff\Differ; use SebastianBergmann\Exporter\Exporter; use Prophecy\Exception\Prediction\PredictionException; use Prophecy\Prophet; /** * A TestCase defines the fixture to run multiple tests. * * To define a TestCase * * 1) Implement a subclass of PHPUnit_Framework_TestCase. * 2) Define instance variables that store the state of the fixture. * 3) Initialize the fixture state by overriding setUp(). * 4) Clean-up after a test by overriding tearDown(). * * Each test runs in its own fixture so there can be no side effects * among test runs. * * Here is an example: * * * value1 = 2; * $this->value2 = 3; * } * } * ?> * * * For each test implement a method which interacts with the fixture. * Verify the expected results with assertions specified by calling * assert with a boolean. * * * assertTrue($this->value1 + $this->value2 == 5); * } * ?> * * * @since Class available since Release 2.0.0 */ abstract class PHPUnit_Framework_TestCase extends PHPUnit_Framework_Assert implements PHPUnit_Framework_Test, PHPUnit_Framework_SelfDescribing { /** * Enable or disable the backup and restoration of the $GLOBALS array. * Overwrite this attribute in a child class of TestCase. * Setting this attribute in setUp() has no effect! * * @var bool */ protected $backupGlobals = null; /** * @var array */ protected $backupGlobalsBlacklist = array(); /** * Enable or disable the backup and restoration of static attributes. * Overwrite this attribute in a child class of TestCase. * Setting this attribute in setUp() has no effect! * * @var bool */ protected $backupStaticAttributes = null; /** * @var array */ protected $backupStaticAttributesBlacklist = array(); /** * Whether or not this test is to be run in a separate PHP process. * * @var bool */ protected $runTestInSeparateProcess = null; /** * Whether or not this test should preserve the global state when * running in a separate PHP process. * * @var bool */ protected $preserveGlobalState = true; /** * Whether or not this test is running in a separate PHP process. * * @var bool */ private $inIsolation = false; /** * @var array */ private $data = array(); /** * @var string */ private $dataName = ''; /** * @var bool */ private $useErrorHandler = null; /** * The name of the expected Exception. * * @var mixed */ private $expectedException = null; /** * The message of the expected Exception. * * @var string */ private $expectedExceptionMessage = ''; /** * The regex pattern to validate the expected Exception message. * * @var string */ private $expectedExceptionMessageRegExp = ''; /** * The code of the expected Exception. * * @var int */ private $expectedExceptionCode; /** * The name of the test case. * * @var string */ private $name = null; /** * @var array */ private $dependencies = array(); /** * @var array */ private $dependencyInput = array(); /** * @var array */ private $iniSettings = array(); /** * @var array */ private $locale = array(); /** * @var array */ private $mockObjects = array(); /** * @var array */ private $mockObjectGenerator = null; /** * @var int */ private $status; /** * @var string */ private $statusMessage = ''; /** * @var int */ private $numAssertions = 0; /** * @var PHPUnit_Framework_TestResult */ private $result; /** * @var mixed */ private $testResult; /** * @var string */ private $output = ''; /** * @var string */ private $outputExpectedRegex = null; /** * @var string */ private $outputExpectedString = null; /** * @var mixed */ private $outputCallback = false; /** * @var bool */ private $outputBufferingActive = false; /** * @var int */ private $outputBufferingLevel; /** * @var SebastianBergmann\GlobalState\Snapshot */ private $snapshot; /** * @var Prophecy\Prophet */ private $prophet; /** * @var bool */ private $disallowChangesToGlobalState = false; /** * Constructs a test case with the given name. * * @param string $name * @param array $data * @param string $dataName */ public function __construct($name = null, array $data = array(), $dataName = '') { if ($name !== null) { $this->setName($name); } $this->data = $data; $this->dataName = $dataName; } /** * Returns a string representation of the test case. * * @return string */ public function toString() { $class = new ReflectionClass($this); $buffer = sprintf( '%s::%s', $class->name, $this->getName(false) ); return $buffer . $this->getDataSetAsString(); } /** * Counts the number of test cases executed by run(TestResult result). * * @return int */ public function count() { return 1; } /** * Returns the annotations for this test. * * @return array * @since Method available since Release 3.4.0 */ public function getAnnotations() { return PHPUnit_Util_Test::parseTestMethodAnnotations( get_class($this), $this->name ); } /** * Gets the name of a TestCase. * * @param bool $withDataSet * @return string */ public function getName($withDataSet = true) { if ($withDataSet) { return $this->name . $this->getDataSetAsString(false); } else { return $this->name; } } /** * Returns the size of the test. * * @return int * @since Method available since Release 3.6.0 */ public function getSize() { return PHPUnit_Util_Test::getSize( get_class($this), $this->getName(false) ); } /** * @return string * @since Method available since Release 3.6.0 */ public function getActualOutput() { if (!$this->outputBufferingActive) { return $this->output; } else { return ob_get_contents(); } } /** * @return bool * @since Method available since Release 3.6.0 */ public function hasOutput() { if (strlen($this->output) === 0) { return false; } if ($this->hasExpectationOnOutput()) { return false; } return true; } /** * @param string $expectedRegex * @since Method available since Release 3.6.0 * @throws PHPUnit_Framework_Exception */ public function expectOutputRegex($expectedRegex) { if ($this->outputExpectedString !== null) { throw new PHPUnit_Framework_Exception; } if (is_string($expectedRegex) || is_null($expectedRegex)) { $this->outputExpectedRegex = $expectedRegex; } } /** * @param string $expectedString * @since Method available since Release 3.6.0 */ public function expectOutputString($expectedString) { if ($this->outputExpectedRegex !== null) { throw new PHPUnit_Framework_Exception; } if (is_string($expectedString) || is_null($expectedString)) { $this->outputExpectedString = $expectedString; } } /** * @return bool * @since Method available since Release 3.6.5 * @deprecated */ public function hasPerformedExpectationsOnOutput() { return $this->hasExpectationOnOutput(); } /** * @return bool * @since Method available since Release 4.3.3 */ public function hasExpectationOnOutput() { return is_string($this->outputExpectedString) || is_string($this->outputExpectedRegex); } /** * @return string * @since Method available since Release 3.2.0 */ public function getExpectedException() { return $this->expectedException; } /** * @param mixed $exceptionName * @param string $exceptionMessage * @param int $exceptionCode * @since Method available since Release 3.2.0 */ public function setExpectedException($exceptionName, $exceptionMessage = '', $exceptionCode = null) { $this->expectedException = $exceptionName; $this->expectedExceptionMessage = $exceptionMessage; $this->expectedExceptionCode = $exceptionCode; } /** * @param mixed $exceptionName * @param string $exceptionMessageRegExp * @param int $exceptionCode * @since Method available since Release 4.3.0 */ public function setExpectedExceptionRegExp($exceptionName, $exceptionMessageRegExp = '', $exceptionCode = null) { $this->expectedException = $exceptionName; $this->expectedExceptionMessageRegExp = $exceptionMessageRegExp; $this->expectedExceptionCode = $exceptionCode; } /** * @since Method available since Release 3.4.0 */ protected function setExpectedExceptionFromAnnotation() { try { $expectedException = PHPUnit_Util_Test::getExpectedException( get_class($this), $this->name ); if ($expectedException !== false) { $this->setExpectedException( $expectedException['class'], $expectedException['message'], $expectedException['code'] ); if (!empty($expectedException['message_regex'])) { $this->setExpectedExceptionRegExp( $expectedException['class'], $expectedException['message_regex'], $expectedException['code'] ); } } } catch (ReflectionException $e) { } } /** * @param bool $useErrorHandler * @since Method available since Release 3.4.0 */ public function setUseErrorHandler($useErrorHandler) { $this->useErrorHandler = $useErrorHandler; } /** * @since Method available since Release 3.4.0 */ protected function setUseErrorHandlerFromAnnotation() { try { $useErrorHandler = PHPUnit_Util_Test::getErrorHandlerSettings( get_class($this), $this->name ); if ($useErrorHandler !== null) { $this->setUseErrorHandler($useErrorHandler); } } catch (ReflectionException $e) { } } /** * @since Method available since Release 3.6.0 */ protected function checkRequirements() { if (!$this->name || !method_exists($this, $this->name)) { return; } $missingRequirements = PHPUnit_Util_Test::getMissingRequirements( get_class($this), $this->name ); if (!empty($missingRequirements)) { $this->markTestSkipped(implode(PHP_EOL, $missingRequirements)); } } /** * Returns the status of this test. * * @return int * @since Method available since Release 3.1.0 */ public function getStatus() { return $this->status; } /** * Returns the status message of this test. * * @return string * @since Method available since Release 3.3.0 */ public function getStatusMessage() { return $this->statusMessage; } /** * Returns whether or not this test has failed. * * @return bool * @since Method available since Release 3.0.0 */ public function hasFailed() { $status = $this->getStatus(); return $status == PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE || $status == PHPUnit_Runner_BaseTestRunner::STATUS_ERROR; } /** * Runs the test case and collects the results in a TestResult object. * If no TestResult object is passed a new one will be created. * * @param PHPUnit_Framework_TestResult $result * @return PHPUnit_Framework_TestResult * @throws PHPUnit_Framework_Exception */ public function run(PHPUnit_Framework_TestResult $result = null) { if ($result === null) { $result = $this->createResult(); } if (!$this instanceof PHPUnit_Framework_Warning) { $this->setTestResultObject($result); $this->setUseErrorHandlerFromAnnotation(); } if ($this->useErrorHandler !== null) { $oldErrorHandlerSetting = $result->getConvertErrorsToExceptions(); $result->convertErrorsToExceptions($this->useErrorHandler); } if (!$this instanceof PHPUnit_Framework_Warning && !$this->handleDependencies()) { return; } if ($this->runTestInSeparateProcess === true && $this->inIsolation !== true && !$this instanceof PHPUnit_Extensions_SeleniumTestCase && !$this instanceof PHPUnit_Extensions_PhptTestCase) { $class = new ReflectionClass($this); $template = new Text_Template( __DIR__ . '/../Util/PHP/Template/TestCaseMethod.tpl' ); if ($this->preserveGlobalState) { $constants = PHPUnit_Util_GlobalState::getConstantsAsString(); $globals = PHPUnit_Util_GlobalState::getGlobalsAsString(); $includedFiles = PHPUnit_Util_GlobalState::getIncludedFilesAsString(); $iniSettings = PHPUnit_Util_GlobalState::getIniSettingsAsString(); } else { $constants = ''; if (!empty($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { $globals = '$GLOBALS[\'__PHPUNIT_BOOTSTRAP\'] = ' . var_export($GLOBALS['__PHPUNIT_BOOTSTRAP'], true) . ";\n"; } else { $globals = ''; } $includedFiles = ''; $iniSettings = ''; } $coverage = $result->getCollectCodeCoverageInformation() ? 'true' : 'false'; $isStrictAboutTestsThatDoNotTestAnything = $result->isStrictAboutTestsThatDoNotTestAnything() ? 'true' : 'false'; $isStrictAboutOutputDuringTests = $result->isStrictAboutOutputDuringTests() ? 'true' : 'false'; $isStrictAboutTestSize = $result->isStrictAboutTestSize() ? 'true' : 'false'; $isStrictAboutTodoAnnotatedTests = $result->isStrictAboutTodoAnnotatedTests() ? 'true' : 'false'; if (defined('PHPUNIT_COMPOSER_INSTALL')) { $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, true); } else { $composerAutoload = '\'\''; } if (defined('__PHPUNIT_PHAR__')) { $phar = var_export(__PHPUNIT_PHAR__, true); } else { $phar = '\'\''; } if ($result->getCodeCoverage()) { $codeCoverageFilter = $result->getCodeCoverage()->filter(); } else { $codeCoverageFilter = null; } $data = var_export(serialize($this->data), true); $dataName = var_export($this->dataName, true); $dependencyInput = var_export(serialize($this->dependencyInput), true); $includePath = var_export(get_include_path(), true); $codeCoverageFilter = var_export(serialize($codeCoverageFilter), true); // must do these fixes because TestCaseMethod.tpl has unserialize('{data}') in it, and we can't break BC // the lines above used to use addcslashes() rather than var_export(), which breaks null byte escape sequences $data = "'." . $data . ".'"; $dataName = "'.(" . $dataName . ").'"; $dependencyInput = "'." . $dependencyInput . ".'"; $includePath = "'." . $includePath . ".'"; $codeCoverageFilter = "'." . $codeCoverageFilter . ".'"; $template->setVar( array( 'composerAutoload' => $composerAutoload, 'phar' => $phar, 'filename' => $class->getFileName(), 'className' => $class->getName(), 'methodName' => $this->name, 'collectCodeCoverageInformation' => $coverage, 'data' => $data, 'dataName' => $dataName, 'dependencyInput' => $dependencyInput, 'constants' => $constants, 'globals' => $globals, 'include_path' => $includePath, 'included_files' => $includedFiles, 'iniSettings' => $iniSettings, 'isStrictAboutTestsThatDoNotTestAnything' => $isStrictAboutTestsThatDoNotTestAnything, 'isStrictAboutOutputDuringTests' => $isStrictAboutOutputDuringTests, 'isStrictAboutTestSize' => $isStrictAboutTestSize, 'isStrictAboutTodoAnnotatedTests' => $isStrictAboutTodoAnnotatedTests, 'codeCoverageFilter' => $codeCoverageFilter ) ); $this->prepareTemplate($template); $php = PHPUnit_Util_PHP::factory(); $php->runTestJob($template->render(), $this, $result); } else { $result->run($this); } if ($this->useErrorHandler !== null) { $result->convertErrorsToExceptions($oldErrorHandlerSetting); } $this->result = null; return $result; } /** * Runs the bare test sequence. */ public function runBare() { $this->numAssertions = 0; $this->snapshotGlobalState(); $this->startOutputBuffering(); clearstatcache(); $currentWorkingDirectory = getcwd(); $hookMethods = PHPUnit_Util_Test::getHookMethods(get_class($this)); try { $hasMetRequirements = false; $this->checkRequirements(); $hasMetRequirements = true; if ($this->inIsolation) { foreach ($hookMethods['beforeClass'] as $method) { $this->$method(); } } $this->setExpectedExceptionFromAnnotation(); foreach ($hookMethods['before'] as $method) { $this->$method(); } $this->assertPreConditions(); $this->testResult = $this->runTest(); $this->verifyMockObjects(); $this->assertPostConditions(); $this->status = PHPUnit_Runner_BaseTestRunner::STATUS_PASSED; } catch (PHPUnit_Framework_IncompleteTest $e) { $this->status = PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE; $this->statusMessage = $e->getMessage(); } catch (PHPUnit_Framework_SkippedTest $e) { $this->status = PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED; $this->statusMessage = $e->getMessage(); } catch (PHPUnit_Framework_AssertionFailedError $e) { $this->status = PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE; $this->statusMessage = $e->getMessage(); } catch (PredictionException $e) { $this->status = PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE; $this->statusMessage = $e->getMessage(); } catch (Throwable $_e) { $e = $_e; } catch (Exception $_e) { $e = $_e; } if (isset($_e)) { $this->status = PHPUnit_Runner_BaseTestRunner::STATUS_ERROR; $this->statusMessage = $_e->getMessage(); } // Clean up the mock objects. $this->mockObjects = array(); $this->prophet = null; // Tear down the fixture. An exception raised in tearDown() will be // caught and passed on when no exception was raised before. try { if ($hasMetRequirements) { foreach ($hookMethods['after'] as $method) { $this->$method(); } if ($this->inIsolation) { foreach ($hookMethods['afterClass'] as $method) { $this->$method(); } } } } catch (Throwable $_e) { if (!isset($e)) { $e = $_e; } } catch (Exception $_e) { if (!isset($e)) { $e = $_e; } } try { $this->stopOutputBuffering(); } catch (PHPUnit_Framework_RiskyTestError $_e) { if (!isset($e)) { $e = $_e; } } clearstatcache(); if ($currentWorkingDirectory != getcwd()) { chdir($currentWorkingDirectory); } $this->restoreGlobalState(); // Clean up INI settings. foreach ($this->iniSettings as $varName => $oldValue) { ini_set($varName, $oldValue); } $this->iniSettings = array(); // Clean up locale settings. foreach ($this->locale as $category => $locale) { setlocale($category, $locale); } // Perform assertion on output. if (!isset($e)) { try { if ($this->outputExpectedRegex !== null) { $this->assertRegExp($this->outputExpectedRegex, $this->output); } elseif ($this->outputExpectedString !== null) { $this->assertEquals($this->outputExpectedString, $this->output); } } catch (Throwable $_e) { $e = $_e; } catch (Exception $_e) { $e = $_e; } } // Workaround for missing "finally". if (isset($e)) { if ($e instanceof PredictionException) { $e = new PHPUnit_Framework_AssertionFailedError($e->getMessage()); } if (!$e instanceof Exception) { // Rethrow Error directly on PHP 7 as onNotSuccessfulTest does not support it throw $e; } $this->onNotSuccessfulTest($e); } } /** * Override to run the test and assert its state. * * @return mixed * @throws Exception|PHPUnit_Framework_Exception * @throws PHPUnit_Framework_Exception */ protected function runTest() { if ($this->name === null) { throw new PHPUnit_Framework_Exception( 'PHPUnit_Framework_TestCase::$name must not be null.' ); } try { $class = new ReflectionClass($this); $method = $class->getMethod($this->name); } catch (ReflectionException $e) { $this->fail($e->getMessage()); } try { $testResult = $method->invokeArgs( $this, array_merge($this->data, $this->dependencyInput) ); } catch (Throwable $_e) { $e = $_e; } catch (Exception $_e) { $e = $_e; } if (isset($e)) { $checkException = false; if (is_string($this->expectedException)) { $checkException = true; if ($e instanceof PHPUnit_Framework_Exception) { $checkException = false; } $reflector = new ReflectionClass($this->expectedException); if ($this->expectedException == 'PHPUnit_Framework_Exception' || $reflector->isSubclassOf('PHPUnit_Framework_Exception')) { $checkException = true; } } if ($checkException) { $this->assertThat( $e, new PHPUnit_Framework_Constraint_Exception( $this->expectedException ) ); if (is_string($this->expectedExceptionMessage) && !empty($this->expectedExceptionMessage)) { $this->assertThat( $e, new PHPUnit_Framework_Constraint_ExceptionMessage( $this->expectedExceptionMessage ) ); } if (is_string($this->expectedExceptionMessageRegExp) && !empty($this->expectedExceptionMessageRegExp)) { $this->assertThat( $e, new PHPUnit_Framework_Constraint_ExceptionMessageRegExp( $this->expectedExceptionMessageRegExp ) ); } if ($this->expectedExceptionCode !== null) { $this->assertThat( $e, new PHPUnit_Framework_Constraint_ExceptionCode( $this->expectedExceptionCode ) ); } return; } else { throw $e; } } if ($this->expectedException !== null) { $this->assertThat( null, new PHPUnit_Framework_Constraint_Exception( $this->expectedException ) ); } return $testResult; } /** * Verifies the mock object expectations. * * @since Method available since Release 3.5.0 */ protected function verifyMockObjects() { foreach ($this->mockObjects as $mockObject) { if ($mockObject->__phpunit_hasMatchers()) { $this->numAssertions++; } $mockObject->__phpunit_verify(); } if ($this->prophet !== null) { try { $this->prophet->checkPredictions(); } catch (Throwable $t) { /* Intentionally left empty */ } catch (Exception $e) { /* Intentionally left empty */ } foreach ($this->prophet->getProphecies() as $objectProphecy) { foreach ($objectProphecy->getMethodProphecies() as $methodProphecies) { foreach ($methodProphecies as $methodProphecy) { $this->numAssertions += count($methodProphecy->getCheckedPredictions()); } } } if (isset($e)) { throw $e; } } } /** * Sets the name of a TestCase. * * @param string */ public function setName($name) { $this->name = $name; } /** * Sets the dependencies of a TestCase. * * @param array $dependencies * @since Method available since Release 3.4.0 */ public function setDependencies(array $dependencies) { $this->dependencies = $dependencies; } /** * Returns true if the tests has dependencies * * @return bool * @since Method available since Release 4.0.0 */ public function hasDependencies() { return count($this->dependencies) > 0; } /** * Sets * * @param array $dependencyInput * @since Method available since Release 3.4.0 */ public function setDependencyInput(array $dependencyInput) { $this->dependencyInput = $dependencyInput; } /** * @param bool $disallowChangesToGlobalState * @since Method available since Release 4.6.0 */ public function setDisallowChangesToGlobalState($disallowChangesToGlobalState) { $this->disallowChangesToGlobalState = $disallowChangesToGlobalState; } /** * Calling this method in setUp() has no effect! * * @param bool $backupGlobals * @since Method available since Release 3.3.0 */ public function setBackupGlobals($backupGlobals) { if (is_null($this->backupGlobals) && is_bool($backupGlobals)) { $this->backupGlobals = $backupGlobals; } } /** * Calling this method in setUp() has no effect! * * @param bool $backupStaticAttributes * @since Method available since Release 3.4.0 */ public function setBackupStaticAttributes($backupStaticAttributes) { if (is_null($this->backupStaticAttributes) && is_bool($backupStaticAttributes)) { $this->backupStaticAttributes = $backupStaticAttributes; } } /** * @param bool $runTestInSeparateProcess * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.4.0 */ public function setRunTestInSeparateProcess($runTestInSeparateProcess) { if (is_bool($runTestInSeparateProcess)) { if ($this->runTestInSeparateProcess === null) { $this->runTestInSeparateProcess = $runTestInSeparateProcess; } } else { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean'); } } /** * @param bool $preserveGlobalState * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.4.0 */ public function setPreserveGlobalState($preserveGlobalState) { if (is_bool($preserveGlobalState)) { $this->preserveGlobalState = $preserveGlobalState; } else { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean'); } } /** * @param bool $inIsolation * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.4.0 */ public function setInIsolation($inIsolation) { if (is_bool($inIsolation)) { $this->inIsolation = $inIsolation; } else { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean'); } } /** * @return bool * @since Method available since Release 4.3.0 */ public function isInIsolation() { return $this->inIsolation; } /** * @return mixed * @since Method available since Release 3.4.0 */ public function getResult() { return $this->testResult; } /** * @param mixed $result * @since Method available since Release 3.4.0 */ public function setResult($result) { $this->testResult = $result; } /** * @param callable $callback * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.6.0 */ public function setOutputCallback($callback) { if (!is_callable($callback)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'callback'); } $this->outputCallback = $callback; } /** * @return PHPUnit_Framework_TestResult * @since Method available since Release 3.5.7 */ public function getTestResultObject() { return $this->result; } /** * @param PHPUnit_Framework_TestResult $result * @since Method available since Release 3.6.0 */ public function setTestResultObject(PHPUnit_Framework_TestResult $result) { $this->result = $result; } /** * This method is a wrapper for the ini_set() function that automatically * resets the modified php.ini setting to its original value after the * test is run. * * @param string $varName * @param string $newValue * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.0.0 */ protected function iniSet($varName, $newValue) { if (!is_string($varName)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } $currentValue = ini_set($varName, $newValue); if ($currentValue !== false) { $this->iniSettings[$varName] = $currentValue; } else { throw new PHPUnit_Framework_Exception( sprintf( 'INI setting "%s" could not be set to "%s".', $varName, $newValue ) ); } } /** * This method is a wrapper for the setlocale() function that automatically * resets the locale to its original value after the test is run. * * @param int $category * @param string $locale * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.1.0 */ protected function setLocale() { $args = func_get_args(); if (count($args) < 2) { throw new PHPUnit_Framework_Exception; } $category = $args[0]; $locale = $args[1]; $categories = array( LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME ); if (defined('LC_MESSAGES')) { $categories[] = LC_MESSAGES; } if (!in_array($category, $categories)) { throw new PHPUnit_Framework_Exception; } if (!is_array($locale) && !is_string($locale)) { throw new PHPUnit_Framework_Exception; } $this->locale[$category] = setlocale($category, null); $result = call_user_func_array('setlocale', $args); if ($result === false) { throw new PHPUnit_Framework_Exception( 'The locale functionality is not implemented on your platform, ' . 'the specified locale does not exist or the category name is ' . 'invalid.' ); } } /** * Returns a mock object for the specified class. * * @param string $originalClassName Name of the class to mock. * @param array|null $methods When provided, only methods whose names are in the array * are replaced with a configurable test double. The behavior * of the other methods is not changed. * Providing null means that no methods will be replaced. * @param array $arguments Parameters to pass to the original class' constructor. * @param string $mockClassName Class name for the generated test double class. * @param bool $callOriginalConstructor Can be used to disable the call to the original class' constructor. * @param bool $callOriginalClone Can be used to disable the call to the original class' clone constructor. * @param bool $callAutoload Can be used to disable __autoload() during the generation of the test double class. * @param bool $cloneArguments * @param bool $callOriginalMethods * @return PHPUnit_Framework_MockObject_MockObject * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.0.0 */ public function getMock($originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $cloneArguments = false, $callOriginalMethods = false) { $mockObject = $this->getMockObjectGenerator()->getMock( $originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods ); $this->mockObjects[] = $mockObject; return $mockObject; } /** * Returns a builder object to create mock objects using a fluent interface. * * @param string $className * @return PHPUnit_Framework_MockObject_MockBuilder * @since Method available since Release 3.5.0 */ public function getMockBuilder($className) { return new PHPUnit_Framework_MockObject_MockBuilder($this, $className); } /** * Mocks the specified class and returns the name of the mocked class. * * @param string $originalClassName * @param array $methods * @param array $arguments * @param string $mockClassName * @param bool $callOriginalConstructor * @param bool $callOriginalClone * @param bool $callAutoload * @param bool $cloneArguments * @return string * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.5.0 */ protected function getMockClass($originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = false, $callOriginalClone = true, $callAutoload = true, $cloneArguments = false) { $mock = $this->getMock( $originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $cloneArguments ); return get_class($mock); } /** * Returns a mock object for the specified abstract class with all abstract * methods of the class mocked. Concrete methods are not mocked by default. * To mock concrete methods, use the 7th parameter ($mockedMethods). * * @param string $originalClassName * @param array $arguments * @param string $mockClassName * @param bool $callOriginalConstructor * @param bool $callOriginalClone * @param bool $callAutoload * @param array $mockedMethods * @param bool $cloneArguments * @return PHPUnit_Framework_MockObject_MockObject * @since Method available since Release 3.4.0 * @throws PHPUnit_Framework_Exception */ public function getMockForAbstractClass($originalClassName, array $arguments = array(), $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = array(), $cloneArguments = false) { $mockObject = $this->getMockObjectGenerator()->getMockForAbstractClass( $originalClassName, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments ); $this->mockObjects[] = $mockObject; return $mockObject; } /** * Returns a mock object based on the given WSDL file. * * @param string $wsdlFile * @param string $originalClassName * @param string $mockClassName * @param array $methods * @param bool $callOriginalConstructor * @param array $options An array of options passed to SOAPClient::_construct * @return PHPUnit_Framework_MockObject_MockObject * @since Method available since Release 3.4.0 */ protected function getMockFromWsdl($wsdlFile, $originalClassName = '', $mockClassName = '', array $methods = array(), $callOriginalConstructor = true, array $options = array()) { if ($originalClassName === '') { $originalClassName = str_replace('.wsdl', '', basename($wsdlFile)); } if (!class_exists($originalClassName)) { eval( $this->getMockObjectGenerator()->generateClassFromWsdl( $wsdlFile, $originalClassName, $methods, $options ) ); } return $this->getMock( $originalClassName, $methods, array('', $options), $mockClassName, $callOriginalConstructor, false, false ); } /** * Returns a mock object for the specified trait with all abstract methods * of the trait mocked. Concrete methods to mock can be specified with the * `$mockedMethods` parameter. * * @param string $traitName * @param array $arguments * @param string $mockClassName * @param bool $callOriginalConstructor * @param bool $callOriginalClone * @param bool $callAutoload * @param array $mockedMethods * @param bool $cloneArguments * @return PHPUnit_Framework_MockObject_MockObject * @since Method available since Release 4.0.0 * @throws PHPUnit_Framework_Exception */ public function getMockForTrait($traitName, array $arguments = array(), $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = array(), $cloneArguments = false) { $mockObject = $this->getMockObjectGenerator()->getMockForTrait( $traitName, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments ); $this->mockObjects[] = $mockObject; return $mockObject; } /** * Returns an object for the specified trait. * * @param string $traitName * @param array $arguments * @param string $traitClassName * @param bool $callOriginalConstructor * @param bool $callOriginalClone * @param bool $callAutoload * @param bool $cloneArguments * @return object * @since Method available since Release 3.6.0 * @throws PHPUnit_Framework_Exception */ protected function getObjectForTrait($traitName, array $arguments = array(), $traitClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $cloneArguments = false) { return $this->getMockObjectGenerator()->getObjectForTrait( $traitName, $arguments, $traitClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $cloneArguments ); } /** * @param string|null $classOrInterface * @return \Prophecy\Prophecy\ObjectProphecy * @throws \LogicException * @since Method available since Release 4.5.0 */ protected function prophesize($classOrInterface = null) { return $this->getProphet()->prophesize($classOrInterface); } /** * Adds a value to the assertion counter. * * @param int $count * @since Method available since Release 3.3.3 */ public function addToAssertionCount($count) { $this->numAssertions += $count; } /** * Returns the number of assertions performed by this test. * * @return int * @since Method available since Release 3.3.0 */ public function getNumAssertions() { return $this->numAssertions; } /** * Returns a matcher that matches when the method is executed * zero or more times. * * @return PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount * @since Method available since Release 3.0.0 */ public static function any() { return new PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount; } /** * Returns a matcher that matches when the method is never executed. * * @return PHPUnit_Framework_MockObject_Matcher_InvokedCount * @since Method available since Release 3.0.0 */ public static function never() { return new PHPUnit_Framework_MockObject_Matcher_InvokedCount(0); } /** * Returns a matcher that matches when the method is executed * at least N times. * * @param int $requiredInvocations * @return PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastCount * @since Method available since Release 4.2.0 */ public static function atLeast($requiredInvocations) { return new PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastCount( $requiredInvocations ); } /** * Returns a matcher that matches when the method is executed at least once. * * @return PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce * @since Method available since Release 3.0.0 */ public static function atLeastOnce() { return new PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce; } /** * Returns a matcher that matches when the method is executed exactly once. * * @return PHPUnit_Framework_MockObject_Matcher_InvokedCount * @since Method available since Release 3.0.0 */ public static function once() { return new PHPUnit_Framework_MockObject_Matcher_InvokedCount(1); } /** * Returns a matcher that matches when the method is executed * exactly $count times. * * @param int $count * @return PHPUnit_Framework_MockObject_Matcher_InvokedCount * @since Method available since Release 3.0.0 */ public static function exactly($count) { return new PHPUnit_Framework_MockObject_Matcher_InvokedCount($count); } /** * Returns a matcher that matches when the method is executed * at most N times. * * @param int $allowedInvocations * @return PHPUnit_Framework_MockObject_Matcher_InvokedAtMostCount * @since Method available since Release 4.2.0 */ public static function atMost($allowedInvocations) { return new PHPUnit_Framework_MockObject_Matcher_InvokedAtMostCount( $allowedInvocations ); } /** * Returns a matcher that matches when the method is executed * at the given index. * * @param int $index * @return PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex * @since Method available since Release 3.0.0 */ public static function at($index) { return new PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex($index); } /** * @param mixed $value * @return PHPUnit_Framework_MockObject_Stub_Return * @since Method available since Release 3.0.0 */ public static function returnValue($value) { return new PHPUnit_Framework_MockObject_Stub_Return($value); } /** * @param array $valueMap * @return PHPUnit_Framework_MockObject_Stub_ReturnValueMap * @since Method available since Release 3.6.0 */ public static function returnValueMap(array $valueMap) { return new PHPUnit_Framework_MockObject_Stub_ReturnValueMap($valueMap); } /** * @param int $argumentIndex * @return PHPUnit_Framework_MockObject_Stub_ReturnArgument * @since Method available since Release 3.3.0 */ public static function returnArgument($argumentIndex) { return new PHPUnit_Framework_MockObject_Stub_ReturnArgument( $argumentIndex ); } /** * @param mixed $callback * @return PHPUnit_Framework_MockObject_Stub_ReturnCallback * @since Method available since Release 3.3.0 */ public static function returnCallback($callback) { return new PHPUnit_Framework_MockObject_Stub_ReturnCallback($callback); } /** * Returns the current object. * * This method is useful when mocking a fluent interface. * * @return PHPUnit_Framework_MockObject_Stub_ReturnSelf * @since Method available since Release 3.6.0 */ public static function returnSelf() { return new PHPUnit_Framework_MockObject_Stub_ReturnSelf(); } /** * @param Exception $exception * @return PHPUnit_Framework_MockObject_Stub_Exception * @since Method available since Release 3.1.0 */ public static function throwException(Exception $exception) { return new PHPUnit_Framework_MockObject_Stub_Exception($exception); } /** * @param mixed $value, ... * @return PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls * @since Method available since Release 3.0.0 */ public static function onConsecutiveCalls() { $args = func_get_args(); return new PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls($args); } /** * Gets the data set description of a TestCase. * * @param bool $includeData * @return string * @since Method available since Release 3.3.0 */ protected function getDataSetAsString($includeData = true) { $buffer = ''; if (!empty($this->data)) { if (is_int($this->dataName)) { $buffer .= sprintf(' with data set #%d', $this->dataName); } else { $buffer .= sprintf(' with data set "%s"', $this->dataName); } $exporter = new Exporter; if ($includeData) { $buffer .= sprintf(' (%s)', $exporter->shortenedRecursiveExport($this->data)); } } return $buffer; } /** * Creates a default TestResult object. * * @return PHPUnit_Framework_TestResult */ protected function createResult() { return new PHPUnit_Framework_TestResult; } /** * @since Method available since Release 3.5.4 */ protected function handleDependencies() { if (!empty($this->dependencies) && !$this->inIsolation) { $className = get_class($this); $passed = $this->result->passed(); $passedKeys = array_keys($passed); $numKeys = count($passedKeys); for ($i = 0; $i < $numKeys; $i++) { $pos = strpos($passedKeys[$i], ' with data set'); if ($pos !== false) { $passedKeys[$i] = substr($passedKeys[$i], 0, $pos); } } $passedKeys = array_flip(array_unique($passedKeys)); foreach ($this->dependencies as $dependency) { if (strpos($dependency, '::') === false) { $dependency = $className . '::' . $dependency; } if (!isset($passedKeys[$dependency])) { $this->result->addError( $this, new PHPUnit_Framework_SkippedTestError( sprintf( 'This test depends on "%s" to pass.', $dependency ) ), 0 ); return false; } if (isset($passed[$dependency])) { if ($passed[$dependency]['size'] != PHPUnit_Util_Test::UNKNOWN && $this->getSize() != PHPUnit_Util_Test::UNKNOWN && $passed[$dependency]['size'] > $this->getSize()) { $this->result->addError( $this, new PHPUnit_Framework_SkippedTestError( 'This test depends on a test that is larger than itself.' ), 0 ); return false; } $this->dependencyInput[$dependency] = $passed[$dependency]['result']; } else { $this->dependencyInput[$dependency] = null; } } } return true; } /** * This method is called before the first test of this test class is run. * * @since Method available since Release 3.4.0 */ public static function setUpBeforeClass() { } /** * Sets up the fixture, for example, open a network connection. * This method is called before a test is executed. */ protected function setUp() { } /** * Performs assertions shared by all tests of a test case. * * This method is called before the execution of a test starts * and after setUp() is called. * * @since Method available since Release 3.2.8 */ protected function assertPreConditions() { } /** * Performs assertions shared by all tests of a test case. * * This method is called before the execution of a test ends * and before tearDown() is called. * * @since Method available since Release 3.2.8 */ protected function assertPostConditions() { } /** * Tears down the fixture, for example, close a network connection. * This method is called after a test is executed. */ protected function tearDown() { } /** * This method is called after the last test of this test class is run. * * @since Method available since Release 3.4.0 */ public static function tearDownAfterClass() { } /** * This method is called when a test method did not execute successfully. * * @param Exception $e * @since Method available since Release 3.4.0 * @throws Exception */ protected function onNotSuccessfulTest(Exception $e) { throw $e; } /** * Performs custom preparations on the process isolation template. * * @param Text_Template $template * @since Method available since Release 3.4.0 */ protected function prepareTemplate(Text_Template $template) { } /** * Get the mock object generator, creating it if it doesn't exist. * * @return PHPUnit_Framework_MockObject_Generator */ protected function getMockObjectGenerator() { if (null === $this->mockObjectGenerator) { $this->mockObjectGenerator = new PHPUnit_Framework_MockObject_Generator; } return $this->mockObjectGenerator; } /** * @since Method available since Release 4.2.0 */ private function startOutputBuffering() { while (!defined('PHPUNIT_TESTSUITE') && ob_get_level() > 0) { ob_end_clean(); } ob_start(); $this->outputBufferingActive = true; $this->outputBufferingLevel = ob_get_level(); } /** * @since Method available since Release 4.2.0 */ private function stopOutputBuffering() { if (ob_get_level() != $this->outputBufferingLevel) { while (ob_get_level() > 0) { ob_end_clean(); } throw new PHPUnit_Framework_RiskyTestError( 'Test code or tested code did not (only) close its own output buffers' ); } $output = ob_get_contents(); if ($this->outputCallback === false) { $this->output = $output; } else { $this->output = call_user_func_array( $this->outputCallback, array($output) ); } ob_end_clean(); $this->outputBufferingActive = false; $this->outputBufferingLevel = ob_get_level(); } private function snapshotGlobalState() { $backupGlobals = $this->backupGlobals === null || $this->backupGlobals === true; if ($this->runTestInSeparateProcess || $this->inIsolation || (!$backupGlobals && !$this->backupStaticAttributes)) { return; } $this->snapshot = $this->createGlobalStateSnapshot($backupGlobals); } private function restoreGlobalState() { if (!$this->snapshot instanceof Snapshot) { return; } $backupGlobals = $this->backupGlobals === null || $this->backupGlobals === true; if ($this->disallowChangesToGlobalState) { $this->compareGlobalStateSnapshots( $this->snapshot, $this->createGlobalStateSnapshot($backupGlobals) ); } $restorer = new Restorer; if ($backupGlobals) { $restorer->restoreGlobalVariables($this->snapshot); } if ($this->backupStaticAttributes) { $restorer->restoreStaticAttributes($this->snapshot); } $this->snapshot = null; } /** * @param bool $backupGlobals * @return Snapshot */ private function createGlobalStateSnapshot($backupGlobals) { $blacklist = new Blacklist; foreach ($this->backupGlobalsBlacklist as $globalVariable) { $blacklist->addGlobalVariable($globalVariable); } if (!defined('PHPUNIT_TESTSUITE')) { $blacklist->addClassNamePrefix('PHPUnit'); $blacklist->addClassNamePrefix('File_Iterator'); $blacklist->addClassNamePrefix('PHP_CodeCoverage'); $blacklist->addClassNamePrefix('PHP_Invoker'); $blacklist->addClassNamePrefix('PHP_Timer'); $blacklist->addClassNamePrefix('PHP_Token'); $blacklist->addClassNamePrefix('Symfony'); $blacklist->addClassNamePrefix('Text_Template'); $blacklist->addClassNamePrefix('Doctrine\Instantiator'); foreach ($this->backupStaticAttributesBlacklist as $class => $attributes) { foreach ($attributes as $attribute) { $blacklist->addStaticAttribute($class, $attribute); } } } return new Snapshot( $blacklist, $backupGlobals, $this->backupStaticAttributes, false, false, false, false, false, false, false ); } /** * @param Snapshot $before * @param Snapshot $after * @throws PHPUnit_Framework_RiskyTestError */ private function compareGlobalStateSnapshots(Snapshot $before, Snapshot $after) { $backupGlobals = $this->backupGlobals === null || $this->backupGlobals === true; if ($backupGlobals) { $this->compareGlobalStateSnapshotPart( $before->globalVariables(), $after->globalVariables(), "--- Global variables before the test\n+++ Global variables after the test\n" ); $this->compareGlobalStateSnapshotPart( $before->superGlobalVariables(), $after->superGlobalVariables(), "--- Super-global variables before the test\n+++ Super-global variables after the test\n" ); } if ($this->backupStaticAttributes) { $this->compareGlobalStateSnapshotPart( $before->staticAttributes(), $after->staticAttributes(), "--- Static attributes before the test\n+++ Static attributes after the test\n" ); } } /** * @param array $before * @param array $after * @param string $header * @throws PHPUnit_Framework_RiskyTestError */ private function compareGlobalStateSnapshotPart(array $before, array $after, $header) { if ($before != $after) { $differ = new Differ($header); $exporter = new Exporter; $diff = $differ->diff( $exporter->export($before), $exporter->export($after) ); throw new PHPUnit_Framework_RiskyTestError( $diff ); } } /** * @return Prophecy\Prophet * @since Method available since Release 4.5.0 */ private function getProphet() { if ($this->prophet === null) { $this->prophet = new Prophet; } return $this->prophet; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Exception for expectations which failed their check. * * The exception contains the error message and optionally a * SebastianBergmann\Comparator\ComparisonFailure which is used to * generate diff output of the failed expectations. * * @since Class available since Release 3.0.0 */ class PHPUnit_Framework_ExpectationFailedException extends PHPUnit_Framework_AssertionFailedError { /** * @var SebastianBergmann\Comparator\ComparisonFailure */ protected $comparisonFailure; public function __construct($message, SebastianBergmann\Comparator\ComparisonFailure $comparisonFailure = null, Exception $previous = null) { $this->comparisonFailure = $comparisonFailure; parent::__construct($message, 0, $previous); } /** * @return SebastianBergmann\Comparator\ComparisonFailure */ public function getComparisonFailure() { return $this->comparisonFailure; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Wrapper for PHP errors. * * @since Class available since Release 2.2.0 */ class PHPUnit_Framework_Error extends PHPUnit_Framework_Exception { /** * Constructor. * * @param string $message * @param int $code * @param string $file * @param int $line * @param Exception $previous */ public function __construct($message, $code, $file, $line, Exception $previous = null) { parent::__construct($message, $code, $previous); $this->file = $file; $this->line = $line; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use SebastianBergmann\Exporter\Exporter; /** * Abstract base class for constraints. which are placed upon any value. * * @since Interface available since Release 3.0.0 */ abstract class PHPUnit_Framework_Constraint implements Countable, PHPUnit_Framework_SelfDescribing { protected $exporter; public function __construct() { $this->exporter = new Exporter; } /** * Evaluates the constraint for parameter $other * * If $returnResult is set to false (the default), an exception is thrown * in case of a failure. null is returned otherwise. * * If $returnResult is true, the result of the evaluation is returned as * a boolean value instead: true in case of success, false in case of a * failure. * * @param mixed $other Value or object to evaluate. * @param string $description Additional information about the test * @param bool $returnResult Whether to return a result or throw an exception * @return mixed * @throws PHPUnit_Framework_ExpectationFailedException */ public function evaluate($other, $description = '', $returnResult = false) { $success = false; if ($this->matches($other)) { $success = true; } if ($returnResult) { return $success; } if (!$success) { $this->fail($other, $description); } } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * This method can be overridden to implement the evaluation algorithm. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { return false; } /** * Counts the number of constraint elements. * * @return int * @since Method available since Release 3.4.0 */ public function count() { return 1; } /** * Throws an exception for the given compared value and test description * * @param mixed $other Evaluated value or object. * @param string $description Additional information about the test * @param SebastianBergmann\Comparator\ComparisonFailure $comparisonFailure * @throws PHPUnit_Framework_ExpectationFailedException */ protected function fail($other, $description, SebastianBergmann\Comparator\ComparisonFailure $comparisonFailure = null) { $failureDescription = sprintf( 'Failed asserting that %s.', $this->failureDescription($other) ); $additionalFailureDescription = $this->additionalFailureDescription($other); if ($additionalFailureDescription) { $failureDescription .= "\n" . $additionalFailureDescription; } if (!empty($description)) { $failureDescription = $description . "\n" . $failureDescription; } throw new PHPUnit_Framework_ExpectationFailedException( $failureDescription, $comparisonFailure ); } /** * Return additional failure description where needed * * The function can be overridden to provide additional failure * information like a diff * * @param mixed $other Evaluated value or object. * @return string */ protected function additionalFailureDescription($other) { return ''; } /** * Returns the description of the failure * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * * To provide additional failure information additionalFailureDescription * can be used. * * @param mixed $other Evaluated value or object. * @return string */ protected function failureDescription($other) { return $this->exporter->export($other) . ' ' . $this->toString(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Thrown when an assertion failed. * * @since Class available since Release 2.0.0 */ class PHPUnit_Framework_AssertionFailedError extends PHPUnit_Framework_Exception implements PHPUnit_Framework_SelfDescribing { /** * Wrapper for getMessage() which is declared as final. * * @return string */ public function toString() { return $this->getMessage(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * An incomplete test case * * @since Class available since Release 4.3.0 */ class PHPUnit_Framework_IncompleteTestCase extends PHPUnit_Framework_TestCase { /** * @var string */ protected $message = ''; /** * @var bool */ protected $backupGlobals = false; /** * @var bool */ protected $backupStaticAttributes = false; /** * @var bool */ protected $runTestInSeparateProcess = false; /** * @var bool */ protected $useErrorHandler = false; /** * @var bool */ protected $useOutputBuffering = false; /** * @param string $className * @param string $methodName * @param string $message */ public function __construct($className, $methodName, $message = '') { $this->message = $message; parent::__construct($className . '::' . $methodName); } /** * @throws PHPUnit_Framework_Exception */ protected function runTest() { $this->markTestIncomplete($this->message); } /** * @return string */ public function getMessage() { return $this->message; } /** * Returns a string representation of the test case. * * @return string */ public function toString() { return $this->getName(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 3.4.0 */ class PHPUnit_Framework_TestSuite_DataProvider extends PHPUnit_Framework_TestSuite { /** * Sets the dependencies of a TestCase. * * @param array $dependencies */ public function setDependencies(array $dependencies) { foreach ($this->tests as $test) { $test->setDependencies($dependencies); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Wrapper for PHP deprecated errors. * You can disable deprecated-to-exception conversion by setting * * * PHPUnit_Framework_Error_Deprecated::$enabled = false; * * * @since Class available since Release 3.3.0 */ class PHPUnit_Framework_Error_Deprecated extends PHPUnit_Framework_Error { public static $enabled = true; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Wrapper for PHP notices. * You can disable notice-to-exception conversion by setting * * * PHPUnit_Framework_Error_Notice::$enabled = false; * * * @since Class available since Release 3.3.0 */ class PHPUnit_Framework_Error_Notice extends PHPUnit_Framework_Error { public static $enabled = true; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Wrapper for PHP warnings. * You can disable notice-to-exception conversion by setting * * * PHPUnit_Framework_Error_Warning::$enabled = false; * * * @since Class available since Release 3.3.0 */ class PHPUnit_Framework_Error_Warning extends PHPUnit_Framework_Error { public static $enabled = true; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Extension to PHPUnit_Framework_AssertionFailedError to mark the special * case of an incomplete test. * * @since Class available since Release 2.0.0 */ class PHPUnit_Framework_IncompleteTestError extends PHPUnit_Framework_AssertionFailedError implements PHPUnit_Framework_IncompleteTest { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Extension to PHPUnit_Framework_AssertionFailedError to mark the special * case of a test that is skipped because of an invalid @covers annotation. * * @since Class available since Release 4.0.0 */ class PHPUnit_Framework_InvalidCoversTargetError extends PHPUnit_Framework_AssertionFailedError implements PHPUnit_Framework_SkippedTest { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Extension to PHPUnit_Framework_AssertionFailedError to mark the special * case of a skipped test suite. * * @since Class available since Release 3.1.0 */ class PHPUnit_Framework_SkippedTestSuiteError extends PHPUnit_Framework_AssertionFailedError implements PHPUnit_Framework_SkippedTest { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A TestResult collects the results of executing a test case. * * @since Class available since Release 2.0.0 */ class PHPUnit_Framework_TestResult implements Countable { /** * @var array */ protected $passed = array(); /** * @var array */ protected $errors = array(); /** * @var array */ protected $failures = array(); /** * @var array */ protected $notImplemented = array(); /** * @var array */ protected $risky = array(); /** * @var array */ protected $skipped = array(); /** * @var array */ protected $listeners = array(); /** * @var int */ protected $runTests = 0; /** * @var float */ protected $time = 0; /** * @var PHPUnit_Framework_TestSuite */ protected $topTestSuite = null; /** * Code Coverage information. * * @var PHP_CodeCoverage */ protected $codeCoverage; /** * @var bool */ protected $convertErrorsToExceptions = true; /** * @var bool */ protected $stop = false; /** * @var bool */ protected $stopOnError = false; /** * @var bool */ protected $stopOnFailure = false; /** * @var bool */ protected $beStrictAboutTestsThatDoNotTestAnything = false; /** * @var bool */ protected $beStrictAboutOutputDuringTests = false; /** * @var bool */ protected $beStrictAboutTestSize = false; /** * @var bool */ protected $beStrictAboutTodoAnnotatedTests = false; /** * @var bool */ protected $stopOnRisky = false; /** * @var bool */ protected $stopOnIncomplete = false; /** * @var bool */ protected $stopOnSkipped = false; /** * @var bool */ protected $lastTestFailed = false; /** * @var int */ protected $timeoutForSmallTests = 1; /** * @var int */ protected $timeoutForMediumTests = 10; /** * @var int */ protected $timeoutForLargeTests = 60; /** * Registers a TestListener. * * @param PHPUnit_Framework_TestListener */ public function addListener(PHPUnit_Framework_TestListener $listener) { $this->listeners[] = $listener; } /** * Unregisters a TestListener. * * @param PHPUnit_Framework_TestListener $listener */ public function removeListener(PHPUnit_Framework_TestListener $listener) { foreach ($this->listeners as $key => $_listener) { if ($listener === $_listener) { unset($this->listeners[$key]); } } } /** * Flushes all flushable TestListeners. * * @since Method available since Release 3.0.0 */ public function flushListeners() { foreach ($this->listeners as $listener) { if ($listener instanceof PHPUnit_Util_Printer) { $listener->flush(); } } } /** * Adds an error to the list of errors. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) { if ($e instanceof PHPUnit_Framework_RiskyTest) { $this->risky[] = new PHPUnit_Framework_TestFailure($test, $e); $notifyMethod = 'addRiskyTest'; if ($this->stopOnRisky) { $this->stop(); } } elseif ($e instanceof PHPUnit_Framework_IncompleteTest) { $this->notImplemented[] = new PHPUnit_Framework_TestFailure($test, $e); $notifyMethod = 'addIncompleteTest'; if ($this->stopOnIncomplete) { $this->stop(); } } elseif ($e instanceof PHPUnit_Framework_SkippedTest) { $this->skipped[] = new PHPUnit_Framework_TestFailure($test, $e); $notifyMethod = 'addSkippedTest'; if ($this->stopOnSkipped) { $this->stop(); } } else { $this->errors[] = new PHPUnit_Framework_TestFailure($test, $e); $notifyMethod = 'addError'; if ($this->stopOnError || $this->stopOnFailure) { $this->stop(); } } foreach ($this->listeners as $listener) { $listener->$notifyMethod($test, $e, $time); } $this->lastTestFailed = true; $this->time += $time; } /** * Adds a failure to the list of failures. * The passed in exception caused the failure. * * @param PHPUnit_Framework_Test $test * @param PHPUnit_Framework_AssertionFailedError $e * @param float $time */ public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { if ($e instanceof PHPUnit_Framework_RiskyTest || $e instanceof PHPUnit_Framework_OutputError) { $this->risky[] = new PHPUnit_Framework_TestFailure($test, $e); $notifyMethod = 'addRiskyTest'; if ($this->stopOnRisky) { $this->stop(); } } elseif ($e instanceof PHPUnit_Framework_IncompleteTest) { $this->notImplemented[] = new PHPUnit_Framework_TestFailure($test, $e); $notifyMethod = 'addIncompleteTest'; if ($this->stopOnIncomplete) { $this->stop(); } } elseif ($e instanceof PHPUnit_Framework_SkippedTest) { $this->skipped[] = new PHPUnit_Framework_TestFailure($test, $e); $notifyMethod = 'addSkippedTest'; if ($this->stopOnSkipped) { $this->stop(); } } else { $this->failures[] = new PHPUnit_Framework_TestFailure($test, $e); $notifyMethod = 'addFailure'; if ($this->stopOnFailure) { $this->stop(); } } foreach ($this->listeners as $listener) { $listener->$notifyMethod($test, $e, $time); } $this->lastTestFailed = true; $this->time += $time; } /** * Informs the result that a testsuite will be started. * * @param PHPUnit_Framework_TestSuite $suite * @since Method available since Release 2.2.0 */ public function startTestSuite(PHPUnit_Framework_TestSuite $suite) { if ($this->topTestSuite === null) { $this->topTestSuite = $suite; } foreach ($this->listeners as $listener) { $listener->startTestSuite($suite); } } /** * Informs the result that a testsuite was completed. * * @param PHPUnit_Framework_TestSuite $suite * @since Method available since Release 2.2.0 */ public function endTestSuite(PHPUnit_Framework_TestSuite $suite) { foreach ($this->listeners as $listener) { $listener->endTestSuite($suite); } } /** * Informs the result that a test will be started. * * @param PHPUnit_Framework_Test $test */ public function startTest(PHPUnit_Framework_Test $test) { $this->lastTestFailed = false; $this->runTests += count($test); foreach ($this->listeners as $listener) { $listener->startTest($test); } } /** * Informs the result that a test was completed. * * @param PHPUnit_Framework_Test $test * @param float $time */ public function endTest(PHPUnit_Framework_Test $test, $time) { foreach ($this->listeners as $listener) { $listener->endTest($test, $time); } if (!$this->lastTestFailed && $test instanceof PHPUnit_Framework_TestCase) { $class = get_class($test); $key = $class . '::' . $test->getName(); $this->passed[$key] = array( 'result' => $test->getResult(), 'size' => PHPUnit_Util_Test::getSize( $class, $test->getName(false) ) ); $this->time += $time; } } /** * Returns true if no risky test occurred. * * @return bool * @since Method available since Release 4.0.0 */ public function allHarmless() { return $this->riskyCount() == 0; } /** * Gets the number of risky tests. * * @return int * @since Method available since Release 4.0.0 */ public function riskyCount() { return count($this->risky); } /** * Returns true if no incomplete test occurred. * * @return bool */ public function allCompletelyImplemented() { return $this->notImplementedCount() == 0; } /** * Gets the number of incomplete tests. * * @return int */ public function notImplementedCount() { return count($this->notImplemented); } /** * Returns an Enumeration for the risky tests. * * @return array * @since Method available since Release 4.0.0 */ public function risky() { return $this->risky; } /** * Returns an Enumeration for the incomplete tests. * * @return array */ public function notImplemented() { return $this->notImplemented; } /** * Returns true if no test has been skipped. * * @return bool * @since Method available since Release 3.0.0 */ public function noneSkipped() { return $this->skippedCount() == 0; } /** * Gets the number of skipped tests. * * @return int * @since Method available since Release 3.0.0 */ public function skippedCount() { return count($this->skipped); } /** * Returns an Enumeration for the skipped tests. * * @return array * @since Method available since Release 3.0.0 */ public function skipped() { return $this->skipped; } /** * Gets the number of detected errors. * * @return int */ public function errorCount() { return count($this->errors); } /** * Returns an Enumeration for the errors. * * @return array */ public function errors() { return $this->errors; } /** * Gets the number of detected failures. * * @return int */ public function failureCount() { return count($this->failures); } /** * Returns an Enumeration for the failures. * * @return array */ public function failures() { return $this->failures; } /** * Returns the names of the tests that have passed. * * @return array * @since Method available since Release 3.4.0 */ public function passed() { return $this->passed; } /** * Returns the (top) test suite. * * @return PHPUnit_Framework_TestSuite * @since Method available since Release 3.0.0 */ public function topTestSuite() { return $this->topTestSuite; } /** * Returns whether code coverage information should be collected. * * @return bool If code coverage should be collected * @since Method available since Release 3.2.0 */ public function getCollectCodeCoverageInformation() { return $this->codeCoverage !== null; } /** * Runs a TestCase. * * @param PHPUnit_Framework_Test $test */ public function run(PHPUnit_Framework_Test $test) { PHPUnit_Framework_Assert::resetCount(); $error = false; $failure = false; $incomplete = false; $risky = false; $skipped = false; $this->startTest($test); $errorHandlerSet = false; if ($this->convertErrorsToExceptions) { $oldErrorHandler = set_error_handler( array('PHPUnit_Util_ErrorHandler', 'handleError'), E_ALL | E_STRICT ); if ($oldErrorHandler === null) { $errorHandlerSet = true; } else { restore_error_handler(); } } $collectCodeCoverage = $this->codeCoverage !== null && !$test instanceof PHPUnit_Extensions_SeleniumTestCase && !$test instanceof PHPUnit_Framework_Warning; if ($collectCodeCoverage) { // We need to blacklist test source files when no whitelist is used. if (!$this->codeCoverage->filter()->hasWhitelist()) { $classes = $this->getHierarchy(get_class($test), true); foreach ($classes as $class) { $this->codeCoverage->filter()->addFileToBlacklist( $class->getFileName() ); } } $this->codeCoverage->start($test); } PHP_Timer::start(); try { if (!$test instanceof PHPUnit_Framework_Warning && $test->getSize() != PHPUnit_Util_Test::UNKNOWN && $this->beStrictAboutTestSize && extension_loaded('pcntl') && class_exists('PHP_Invoker')) { switch ($test->getSize()) { case PHPUnit_Util_Test::SMALL: $_timeout = $this->timeoutForSmallTests; break; case PHPUnit_Util_Test::MEDIUM: $_timeout = $this->timeoutForMediumTests; break; case PHPUnit_Util_Test::LARGE: $_timeout = $this->timeoutForLargeTests; break; } $invoker = new PHP_Invoker; $invoker->invoke(array($test, 'runBare'), array(), $_timeout); } else { $test->runBare(); } } catch (PHPUnit_Framework_AssertionFailedError $e) { $failure = true; if ($e instanceof PHPUnit_Framework_RiskyTestError) { $risky = true; } elseif ($e instanceof PHPUnit_Framework_IncompleteTestError) { $incomplete = true; } elseif ($e instanceof PHPUnit_Framework_SkippedTestError) { $skipped = true; } } catch (PHPUnit_Framework_Exception $e) { $error = true; } catch (Throwable $e) { $e = new PHPUnit_Framework_ExceptionWrapper($e); $error = true; } catch (Exception $e) { $e = new PHPUnit_Framework_ExceptionWrapper($e); $error = true; } $time = PHP_Timer::stop(); $test->addToAssertionCount(PHPUnit_Framework_Assert::getCount()); if ($this->beStrictAboutTestsThatDoNotTestAnything && $test->getNumAssertions() == 0) { $risky = true; } if ($collectCodeCoverage) { $append = !$risky && !$incomplete && !$skipped; $linesToBeCovered = array(); $linesToBeUsed = array(); if ($append && $test instanceof PHPUnit_Framework_TestCase) { $linesToBeCovered = PHPUnit_Util_Test::getLinesToBeCovered( get_class($test), $test->getName(false) ); $linesToBeUsed = PHPUnit_Util_Test::getLinesToBeUsed( get_class($test), $test->getName(false) ); } try { $this->codeCoverage->stop( $append, $linesToBeCovered, $linesToBeUsed ); } catch (PHP_CodeCoverage_Exception_UnintentionallyCoveredCode $cce) { $this->addFailure( $test, new PHPUnit_Framework_UnintentionallyCoveredCodeError( 'This test executed code that is not listed as code to be covered or used:' . PHP_EOL . $cce->getMessage() ), $time ); } catch (PHPUnit_Framework_InvalidCoversTargetException $cce) { $this->addFailure( $test, new PHPUnit_Framework_InvalidCoversTargetError( $cce->getMessage() ), $time ); } catch (PHP_CodeCoverage_Exception $cce) { $error = true; if (!isset($e)) { $e = $cce; } } } if ($errorHandlerSet === true) { restore_error_handler(); } if ($error === true) { $this->addError($test, $e, $time); } elseif ($failure === true) { $this->addFailure($test, $e, $time); } elseif ($this->beStrictAboutTestsThatDoNotTestAnything && $test->getNumAssertions() == 0) { $this->addFailure( $test, new PHPUnit_Framework_RiskyTestError( 'This test did not perform any assertions' ), $time ); } elseif ($this->beStrictAboutOutputDuringTests && $test->hasOutput()) { $this->addFailure( $test, new PHPUnit_Framework_OutputError( sprintf( 'This test printed output: %s', $test->getActualOutput() ) ), $time ); } elseif ($this->beStrictAboutTodoAnnotatedTests && $test instanceof PHPUnit_Framework_TestCase) { $annotations = $test->getAnnotations(); if (isset($annotations['method']['todo'])) { $this->addFailure( $test, new PHPUnit_Framework_RiskyTestError( 'Test method is annotated with @todo' ), $time ); } } $this->endTest($test, $time); } /** * Gets the number of run tests. * * @return int */ public function count() { return $this->runTests; } /** * Checks whether the test run should stop. * * @return bool */ public function shouldStop() { return $this->stop; } /** * Marks that the test run should stop. */ public function stop() { $this->stop = true; } /** * Returns the PHP_CodeCoverage object. * * @return PHP_CodeCoverage * @since Method available since Release 3.5.0 */ public function getCodeCoverage() { return $this->codeCoverage; } /** * Sets the PHP_CodeCoverage object. * * @param PHP_CodeCoverage $codeCoverage * @since Method available since Release 3.6.0 */ public function setCodeCoverage(PHP_CodeCoverage $codeCoverage) { $this->codeCoverage = $codeCoverage; } /** * Enables or disables the error-to-exception conversion. * * @param bool $flag * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.2.14 */ public function convertErrorsToExceptions($flag) { if (!is_bool($flag)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean'); } $this->convertErrorsToExceptions = $flag; } /** * Returns the error-to-exception conversion setting. * * @return bool * @since Method available since Release 3.4.0 */ public function getConvertErrorsToExceptions() { return $this->convertErrorsToExceptions; } /** * Enables or disables the stopping when an error occurs. * * @param bool $flag * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.5.0 */ public function stopOnError($flag) { if (!is_bool($flag)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean'); } $this->stopOnError = $flag; } /** * Enables or disables the stopping when a failure occurs. * * @param bool $flag * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.1.0 */ public function stopOnFailure($flag) { if (!is_bool($flag)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean'); } $this->stopOnFailure = $flag; } /** * @param bool $flag * @throws PHPUnit_Framework_Exception * @since Method available since Release 4.0.0 */ public function beStrictAboutTestsThatDoNotTestAnything($flag) { if (!is_bool($flag)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean'); } $this->beStrictAboutTestsThatDoNotTestAnything = $flag; } /** * @return bool * @since Method available since Release 4.0.0 */ public function isStrictAboutTestsThatDoNotTestAnything() { return $this->beStrictAboutTestsThatDoNotTestAnything; } /** * @param bool $flag * @throws PHPUnit_Framework_Exception * @since Method available since Release 4.0.0 */ public function beStrictAboutOutputDuringTests($flag) { if (!is_bool($flag)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean'); } $this->beStrictAboutOutputDuringTests = $flag; } /** * @return bool * @since Method available since Release 4.0.0 */ public function isStrictAboutOutputDuringTests() { return $this->beStrictAboutOutputDuringTests; } /** * @param bool $flag * @throws PHPUnit_Framework_Exception * @since Method available since Release 4.0.0 */ public function beStrictAboutTestSize($flag) { if (!is_bool($flag)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean'); } $this->beStrictAboutTestSize = $flag; } /** * @return bool * @since Method available since Release 4.0.0 */ public function isStrictAboutTestSize() { return $this->beStrictAboutTestSize; } /** * @param bool $flag * @throws PHPUnit_Framework_Exception * @since Method available since Release 4.2.0 */ public function beStrictAboutTodoAnnotatedTests($flag) { if (!is_bool($flag)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean'); } $this->beStrictAboutTodoAnnotatedTests = $flag; } /** * @return bool * @since Method available since Release 4.2.0 */ public function isStrictAboutTodoAnnotatedTests() { return $this->beStrictAboutTodoAnnotatedTests; } /** * Enables or disables the stopping for risky tests. * * @param bool $flag * @throws PHPUnit_Framework_Exception * @since Method available since Release 4.0.0 */ public function stopOnRisky($flag) { if (!is_bool($flag)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean'); } $this->stopOnRisky = $flag; } /** * Enables or disables the stopping for incomplete tests. * * @param bool $flag * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.5.0 */ public function stopOnIncomplete($flag) { if (!is_bool($flag)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean'); } $this->stopOnIncomplete = $flag; } /** * Enables or disables the stopping for skipped tests. * * @param bool $flag * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.1.0 */ public function stopOnSkipped($flag) { if (!is_bool($flag)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean'); } $this->stopOnSkipped = $flag; } /** * Returns the time spent running the tests. * * @return float */ public function time() { return $this->time; } /** * Returns whether the entire test was successful or not. * * @return bool */ public function wasSuccessful() { return empty($this->errors) && empty($this->failures); } /** * Sets the timeout for small tests. * * @param int $timeout * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.6.0 */ public function setTimeoutForSmallTests($timeout) { if (!is_integer($timeout)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'integer'); } $this->timeoutForSmallTests = $timeout; } /** * Sets the timeout for medium tests. * * @param int $timeout * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.6.0 */ public function setTimeoutForMediumTests($timeout) { if (!is_integer($timeout)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'integer'); } $this->timeoutForMediumTests = $timeout; } /** * Sets the timeout for large tests. * * @param int $timeout * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.6.0 */ public function setTimeoutForLargeTests($timeout) { if (!is_integer($timeout)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'integer'); } $this->timeoutForLargeTests = $timeout; } /** * Returns the class hierarchy for a given class. * * @param string $className * @param bool $asReflectionObjects * @return array */ protected function getHierarchy($className, $asReflectionObjects = false) { if ($asReflectionObjects) { $classes = array(new ReflectionClass($className)); } else { $classes = array($className); } $done = false; while (!$done) { if ($asReflectionObjects) { $class = new ReflectionClass( $classes[count($classes) - 1]->getName() ); } else { $class = new ReflectionClass($classes[count($classes) - 1]); } $parent = $class->getParentClass(); if ($parent !== false) { if ($asReflectionObjects) { $classes[] = $parent; } else { $classes[] = $parent->getName(); } } else { $done = true; } } return $classes; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A TestFailure collects a failed test together with the caught exception. * * @since Class available since Release 2.0.0 */ class PHPUnit_Framework_TestFailure { /** * @var string */ private $testName; /** * @var PHPUnit_Framework_Test|null */ protected $failedTest; /** * @var Exception */ protected $thrownException; /** * Constructs a TestFailure with the given test and exception. * * @param PHPUnit_Framework_Test $failedTest * @param Exception $thrownException */ public function __construct(PHPUnit_Framework_Test $failedTest, Exception $thrownException) { if ($failedTest instanceof PHPUnit_Framework_SelfDescribing) { $this->testName = $failedTest->toString(); } else { $this->testName = get_class($failedTest); } if (!$failedTest instanceof PHPUnit_Framework_TestCase || !$failedTest->isInIsolation()) { $this->failedTest = $failedTest; } $this->thrownException = $thrownException; } /** * Returns a short description of the failure. * * @return string */ public function toString() { return sprintf( '%s: %s', $this->testName, $this->thrownException->getMessage() ); } /** * Returns a description for the thrown exception. * * @return string * @since Method available since Release 3.4.0 */ public function getExceptionAsString() { return self::exceptionToString($this->thrownException); } /** * Returns a description for an exception. * * @param Exception $e * @return string * @since Method available since Release 3.2.0 */ public static function exceptionToString(Exception $e) { if ($e instanceof PHPUnit_Framework_SelfDescribing) { $buffer = $e->toString(); if ($e instanceof PHPUnit_Framework_ExpectationFailedException && $e->getComparisonFailure()) { $buffer = $buffer . $e->getComparisonFailure()->getDiff(); } if (!empty($buffer)) { $buffer = trim($buffer) . "\n"; } } elseif ($e instanceof PHPUnit_Framework_Error) { $buffer = $e->getMessage() . "\n"; } elseif ($e instanceof PHPUnit_Framework_ExceptionWrapper) { $buffer = $e->getClassname() . ': ' . $e->getMessage() . "\n"; } else { $buffer = get_class($e) . ': ' . $e->getMessage() . "\n"; } return $buffer; } /** * Returns the name of the failing test (including data set, if any). * * @return string * @since Method available since Release 4.3.0 */ public function getTestName() { return $this->testName; } /** * Returns the failing test. * * Note: The test object is not set when the test is executed in process * isolation. * * @see PHPUnit_Framework_Exception * * @return PHPUnit_Framework_Test|null */ public function failedTest() { return $this->failedTest; } /** * Gets the thrown exception. * * @return Exception */ public function thrownException() { return $this->thrownException; } /** * Returns the exception's message. * * @return string */ public function exceptionMessage() { return $this->thrownException()->getMessage(); } /** * Returns true if the thrown exception * is of type AssertionFailedError. * * @return bool */ public function isFailure() { return ($this->thrownException() instanceof PHPUnit_Framework_AssertionFailedError); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A marker interface for marking any exception/error as result of an unit * test as incomplete implementation or currently not implemented. * * @since Interface available since Release 2.0.0 */ interface PHPUnit_Framework_IncompleteTest { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A skipped test case * * @since Class available since Release 4.3.0 */ class PHPUnit_Framework_SkippedTestCase extends PHPUnit_Framework_TestCase { /** * @var string */ protected $message = ''; /** * @var bool */ protected $backupGlobals = false; /** * @var bool */ protected $backupStaticAttributes = false; /** * @var bool */ protected $runTestInSeparateProcess = false; /** * @var bool */ protected $useErrorHandler = false; /** * @var bool */ protected $useOutputBuffering = false; /** * @param string $message */ public function __construct($className, $methodName, $message = '') { $this->message = $message; parent::__construct($className . '::' . $methodName); } /** * @throws PHPUnit_Framework_Exception */ protected function runTest() { $this->markTestSkipped($this->message); } /** * @return string */ public function getMessage() { return $this->message; } /** * Returns a string representation of the test case. * * @return string */ public function toString() { return $this->getName(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that asserts that the class it is evaluated for has a given * attribute. * * The attribute name is passed in the constructor. * * @since Class available since Release 3.1.0 */ class PHPUnit_Framework_Constraint_ClassHasAttribute extends PHPUnit_Framework_Constraint { /** * @var string */ protected $attributeName; /** * @param string $attributeName */ public function __construct($attributeName) { parent::__construct(); $this->attributeName = $attributeName; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { $class = new ReflectionClass($other); return $class->hasProperty($this->attributeName); } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return sprintf( 'has attribute "%s"', $this->attributeName ); } /** * Returns the description of the failure * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * * @param mixed $other Evaluated value or object. * @return string */ protected function failureDescription($other) { return sprintf( '%sclass "%s" %s', is_object($other) ? 'object of ' : '', is_object($other) ? get_class($other) : $other, $this->toString() ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that asserts that the Traversable it is applied to contains * a given value. * * @since Class available since Release 3.0.0 */ class PHPUnit_Framework_Constraint_TraversableContains extends PHPUnit_Framework_Constraint { /** * @var bool */ protected $checkForObjectIdentity; /** * @var bool */ protected $checkForNonObjectIdentity; /** * @var mixed */ protected $value; /** * @param mixed $value * @param bool $checkForObjectIdentity * @param bool $checkForNonObjectIdentity * @throws PHPUnit_Framework_Exception */ public function __construct($value, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false) { parent::__construct(); if (!is_bool($checkForObjectIdentity)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'boolean'); } if (!is_bool($checkForNonObjectIdentity)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'boolean'); } $this->checkForObjectIdentity = $checkForObjectIdentity; $this->checkForNonObjectIdentity = $checkForNonObjectIdentity; $this->value = $value; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { if ($other instanceof SplObjectStorage) { return $other->contains($this->value); } if (is_object($this->value)) { foreach ($other as $element) { if (($this->checkForObjectIdentity && $element === $this->value) || (!$this->checkForObjectIdentity && $element == $this->value)) { return true; } } } else { foreach ($other as $element) { if (($this->checkForNonObjectIdentity && $element === $this->value) || (!$this->checkForNonObjectIdentity && $element == $this->value)) { return true; } } } return false; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { if (is_string($this->value) && strpos($this->value, "\n") !== false) { return 'contains "' . $this->value . '"'; } else { return 'contains ' . $this->exporter->export($this->value); } } /** * Returns the description of the failure * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * * @param mixed $other Evaluated value or object. * @return string */ protected function failureDescription($other) { return sprintf( '%s %s', is_array($other) ? 'an array' : 'a traversable', $this->toString() ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 3.6.6 */ class PHPUnit_Framework_Constraint_ExceptionCode extends PHPUnit_Framework_Constraint { /** * @var int */ protected $expectedCode; /** * @param int $expected */ public function __construct($expected) { parent::__construct(); $this->expectedCode = $expected; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param Exception $other * @return bool */ protected function matches($other) { return (string) $other->getCode() == (string) $this->expectedCode; } /** * Returns the description of the failure * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * * @param mixed $other Evaluated value or object. * @return string */ protected function failureDescription($other) { return sprintf( '%s is equal to expected exception code %s', $this->exporter->export($other->getCode()), $this->exporter->export($this->expectedCode) ); } /** * @return string */ public function toString() { return 'exception code is '; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 3.6.6 */ class PHPUnit_Framework_Constraint_Exception extends PHPUnit_Framework_Constraint { /** * @var string */ protected $className; /** * @param string $className */ public function __construct($className) { parent::__construct(); $this->className = $className; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { return $other instanceof $this->className; } /** * Returns the description of the failure * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * * @param mixed $other Evaluated value or object. * @return string */ protected function failureDescription($other) { if ($other !== null) { $message = ''; if ($other instanceof Exception) { $message = '. Message was: "' . $other->getMessage() . '" at' . "\n" . $other->getTraceAsString(); } return sprintf( 'exception of type "%s" matches expected exception "%s"%s', get_class($other), $this->className, $message ); } return sprintf( 'exception of type "%s" is thrown', $this->className ); } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return sprintf( 'exception of type "%s"', $this->className ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 3.6.0 */ class PHPUnit_Framework_Constraint_SameSize extends PHPUnit_Framework_Constraint_Count { /** * @var int */ protected $expectedCount; /** * @param int $expected */ public function __construct($expected) { parent::__construct($this->getCountOf($expected)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Asserts whether or not two JSON objects are equal. * * @since Class available since Release 3.7.0 */ class PHPUnit_Framework_Constraint_JsonMatches extends PHPUnit_Framework_Constraint { /** * @var string */ protected $value; /** * Creates a new constraint. * * @param string $value */ public function __construct($value) { parent::__construct(); $this->value = $value; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * This method can be overridden to implement the evaluation algorithm. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { $decodedOther = json_decode($other); if (json_last_error()) { return false; } $decodedValue = json_decode($this->value); if (json_last_error()) { return false; } return $decodedOther == $decodedValue; } /** * Returns a string representation of the object. * * @return string */ public function toString() { return sprintf( 'matches JSON string "%s"', $this->value ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that asserts that the object it is evaluated for is an instance * of a given class. * * The expected class name is passed in the constructor. * * @since Class available since Release 3.0.0 */ class PHPUnit_Framework_Constraint_IsInstanceOf extends PHPUnit_Framework_Constraint { /** * @var string */ protected $className; /** * @param string $className */ public function __construct($className) { parent::__construct(); $this->className = $className; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { return ($other instanceof $this->className); } /** * Returns the description of the failure * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * * @param mixed $other Evaluated value or object. * @return string */ protected function failureDescription($other) { return sprintf( '%s is an instance of %s "%s"', $this->exporter->shortenedExport($other), $this->getType(), $this->className ); } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return sprintf( 'is instance of %s "%s"', $this->getType(), $this->className ); } private function getType() { try { $reflection = new ReflectionClass($this->className); if ($reflection->isInterface()) { return 'interface'; } } catch (ReflectionException $e) { } return 'class'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that asserts that the object it is evaluated for has a given * attribute. * * The attribute name is passed in the constructor. * * @since Class available since Release 3.0.0 */ class PHPUnit_Framework_Constraint_ObjectHasAttribute extends PHPUnit_Framework_Constraint_ClassHasAttribute { /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { $object = new ReflectionObject($other); return $object->hasProperty($this->attributeName); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that asserts that the value it is evaluated for is of a * specified type. * * The expected value is passed in the constructor. * * @since Class available since Release 3.0.0 */ class PHPUnit_Framework_Constraint_IsType extends PHPUnit_Framework_Constraint { const TYPE_ARRAY = 'array'; const TYPE_BOOL = 'bool'; const TYPE_FLOAT = 'float'; const TYPE_INT = 'int'; const TYPE_NULL = 'null'; const TYPE_NUMERIC = 'numeric'; const TYPE_OBJECT = 'object'; const TYPE_RESOURCE = 'resource'; const TYPE_STRING = 'string'; const TYPE_SCALAR = 'scalar'; const TYPE_CALLABLE = 'callable'; /** * @var array */ protected $types = array( 'array' => true, 'boolean' => true, 'bool' => true, 'double' => true, 'float' => true, 'integer' => true, 'int' => true, 'null' => true, 'numeric' => true, 'object' => true, 'real' => true, 'resource' => true, 'string' => true, 'scalar' => true, 'callable' => true ); /** * @var string */ protected $type; /** * @param string $type * @throws PHPUnit_Framework_Exception */ public function __construct($type) { parent::__construct(); if (!isset($this->types[$type])) { throw new PHPUnit_Framework_Exception( sprintf( 'Type specified for PHPUnit_Framework_Constraint_IsType <%s> ' . 'is not a valid type.', $type ) ); } $this->type = $type; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { switch ($this->type) { case 'numeric': return is_numeric($other); case 'integer': case 'int': return is_integer($other); case 'double': case 'float': case 'real': return is_float($other); case 'string': return is_string($other); case 'boolean': case 'bool': return is_bool($other); case 'null': return is_null($other); case 'array': return is_array($other); case 'object': return is_object($other); case 'resource': return is_resource($other) || is_string(@get_resource_type($other)); case 'scalar': return is_scalar($other); case 'callable': return is_callable($other); } } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return sprintf( 'is of type "%s"', $this->type ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that asserts that the value it is evaluated for is less than * a given value. * * @since Class available since Release 3.0.0 */ class PHPUnit_Framework_Constraint_LessThan extends PHPUnit_Framework_Constraint { /** * @var numeric */ protected $value; /** * @param numeric $value */ public function __construct($value) { parent::__construct(); $this->value = $value; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { return $this->value > $other; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return 'is less than ' . $this->exporter->export($this->value); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that asserts that the array it is evaluated for has a specified subset. * * Uses array_replace_recursive() to check if a key value subset is part of the * subject array. * * @since Class available since Release 4.4.0 */ class PHPUnit_Framework_Constraint_ArraySubset extends PHPUnit_Framework_Constraint { /** * @var array|ArrayAccess */ protected $subset; /** * @var bool */ protected $strict; /** * @param array|ArrayAccess $subset * @param bool $strict Check for object identity */ public function __construct($subset, $strict = false) { parent::__construct(); $this->strict = $strict; $this->subset = $subset; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param array|ArrayAccess $other Array or ArrayAccess object to evaluate. * @return bool */ protected function matches($other) { $patched = array_replace_recursive($other, $this->subset); if ($this->strict) { return $other === $patched; } else { return $other == $patched; } } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return 'has the subset ' . $this->exporter->export($this->subset); } /** * Returns the description of the failure * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * * @param mixed $other Evaluated value or object. * @return string */ protected function failureDescription($other) { return 'an array ' . $this->toString(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that accepts true. * * @since Class available since Release 3.3.0 */ class PHPUnit_Framework_Constraint_IsTrue extends PHPUnit_Framework_Constraint { /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { return $other === true; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return 'is true'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that asserts that a string is valid JSON. * * @since Class available since Release 3.7.20 */ class PHPUnit_Framework_Constraint_IsJson extends PHPUnit_Framework_Constraint { /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { json_decode($other); if (json_last_error()) { return false; } return true; } /** * Returns the description of the failure * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * * @param mixed $other Evaluated value or object. * @return string */ protected function failureDescription($other) { json_decode($other); $error = PHPUnit_Framework_Constraint_JsonMatches_ErrorMessageProvider::determineJsonError( json_last_error() ); return sprintf( '%s is valid JSON (%s)', $this->exporter->shortenedExport($other), $error ); } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return 'is valid JSON'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that checks if one value is equal to another. * * Equality is checked with PHP's == operator, the operator is explained in * detail at {@url http://www.php.net/manual/en/types.comparisons.php}. * Two values are equal if they have the same value disregarding type. * * The expected value is passed in the constructor. * * @since Class available since Release 3.0.0 */ class PHPUnit_Framework_Constraint_IsEqual extends PHPUnit_Framework_Constraint { /** * @var mixed */ protected $value; /** * @var float */ protected $delta = 0.0; /** * @var int */ protected $maxDepth = 10; /** * @var bool */ protected $canonicalize = false; /** * @var bool */ protected $ignoreCase = false; /** * @var SebastianBergmann\Comparator\ComparisonFailure */ protected $lastFailure; /** * @param mixed $value * @param float $delta * @param int $maxDepth * @param bool $canonicalize * @param bool $ignoreCase * @throws PHPUnit_Framework_Exception */ public function __construct($value, $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) { parent::__construct(); if (!is_numeric($delta)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'numeric'); } if (!is_int($maxDepth)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'integer'); } if (!is_bool($canonicalize)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(4, 'boolean'); } if (!is_bool($ignoreCase)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(5, 'boolean'); } $this->value = $value; $this->delta = $delta; $this->maxDepth = $maxDepth; $this->canonicalize = $canonicalize; $this->ignoreCase = $ignoreCase; } /** * Evaluates the constraint for parameter $other * * If $returnResult is set to false (the default), an exception is thrown * in case of a failure. null is returned otherwise. * * If $returnResult is true, the result of the evaluation is returned as * a boolean value instead: true in case of success, false in case of a * failure. * * @param mixed $other Value or object to evaluate. * @param string $description Additional information about the test * @param bool $returnResult Whether to return a result or throw an exception * @return mixed * @throws PHPUnit_Framework_ExpectationFailedException */ public function evaluate($other, $description = '', $returnResult = false) { // If $this->value and $other are identical, they are also equal. // This is the most common path and will allow us to skip // initialization of all the comparators. if ($this->value === $other) { return true; } $comparatorFactory = SebastianBergmann\Comparator\Factory::getInstance(); try { $comparator = $comparatorFactory->getComparatorFor( $this->value, $other ); $comparator->assertEquals( $this->value, $other, $this->delta, $this->canonicalize, $this->ignoreCase ); } catch (SebastianBergmann\Comparator\ComparisonFailure $f) { if ($returnResult) { return false; } throw new PHPUnit_Framework_ExpectationFailedException( trim($description . "\n" . $f->getMessage()), $f ); } return true; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { $delta = ''; if (is_string($this->value)) { if (strpos($this->value, "\n") !== false) { return 'is equal to '; } else { return sprintf( 'is equal to ', $this->value ); } } else { if ($this->delta != 0) { $delta = sprintf( ' with delta <%F>', $this->delta ); } return sprintf( 'is equal to %s%s', $this->exporter->export($this->value), $delta ); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Logical XOR. * * @since Class available since Release 3.0.0 */ class PHPUnit_Framework_Constraint_Xor extends PHPUnit_Framework_Constraint { /** * @var PHPUnit_Framework_Constraint[] */ protected $constraints = array(); /** * @param PHPUnit_Framework_Constraint[] $constraints */ public function setConstraints(array $constraints) { $this->constraints = array(); foreach ($constraints as $constraint) { if (!($constraint instanceof PHPUnit_Framework_Constraint)) { $constraint = new PHPUnit_Framework_Constraint_IsEqual( $constraint ); } $this->constraints[] = $constraint; } } /** * Evaluates the constraint for parameter $other * * If $returnResult is set to false (the default), an exception is thrown * in case of a failure. null is returned otherwise. * * If $returnResult is true, the result of the evaluation is returned as * a boolean value instead: true in case of success, false in case of a * failure. * * @param mixed $other Value or object to evaluate. * @param string $description Additional information about the test * @param bool $returnResult Whether to return a result or throw an exception * @return mixed * @throws PHPUnit_Framework_ExpectationFailedException */ public function evaluate($other, $description = '', $returnResult = false) { $success = true; $lastResult = null; $constraint = null; foreach ($this->constraints as $constraint) { $result = $constraint->evaluate($other, $description, true); if ($result === $lastResult) { $success = false; break; } $lastResult = $result; } if ($returnResult) { return $success; } if (!$success) { $this->fail($other, $description); } } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { $text = ''; foreach ($this->constraints as $key => $constraint) { if ($key > 0) { $text .= ' xor '; } $text .= $constraint->toString(); } return $text; } /** * Counts the number of constraint elements. * * @return int * @since Method available since Release 3.4.0 */ public function count() { $count = 0; foreach ($this->constraints as $constraint) { $count += count($constraint); } return $count; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Provides human readable messages for each JSON error. * * @since Class available since Release 3.7.0 */ class PHPUnit_Framework_Constraint_JsonMatches_ErrorMessageProvider { /** * Translates JSON error to a human readable string. * * @param string $error * @param string $prefix * @return string */ public static function determineJsonError($error, $prefix = '') { switch ($error) { case JSON_ERROR_NONE: return; case JSON_ERROR_DEPTH: return $prefix . 'Maximum stack depth exceeded'; case JSON_ERROR_STATE_MISMATCH: return $prefix . 'Underflow or the modes mismatch'; case JSON_ERROR_CTRL_CHAR: return $prefix . 'Unexpected control character found'; case JSON_ERROR_SYNTAX: return $prefix . 'Syntax error, malformed JSON'; case JSON_ERROR_UTF8: return $prefix . 'Malformed UTF-8 characters, possibly incorrectly encoded'; default: return $prefix . 'Unknown error'; } } /** * Translates a given type to a human readable message prefix. * * @param string $type * @return string */ public static function translateTypeToPrefix($type) { switch (strtolower($type)) { case 'expected': $prefix = 'Expected value JSON decode error - '; break; case 'actual': $prefix = 'Actual value JSON decode error - '; break; default: $prefix = ''; break; } return $prefix; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use SebastianBergmann\Diff\Differ; /** * ... * * @since Class available since Release 3.5.0 */ class PHPUnit_Framework_Constraint_StringMatches extends PHPUnit_Framework_Constraint_PCREMatch { /** * @var string */ protected $string; /** * @param string $string */ public function __construct($string) { parent::__construct($string); $this->pattern = $this->createPatternFromFormat( preg_replace('/\r\n/', "\n", $string) ); $this->string = $string; } protected function failureDescription($other) { return 'format description matches text'; } protected function additionalFailureDescription($other) { $from = preg_split('(\r\n|\r|\n)', $this->string); $to = preg_split('(\r\n|\r|\n)', $other); foreach ($from as $index => $line) { if (isset($to[$index]) && $line !== $to[$index]) { $line = $this->createPatternFromFormat($line); if (preg_match($line, $to[$index]) > 0) { $from[$index] = $to[$index]; } } } $this->string = implode("\n", $from); $other = implode("\n", $to); $differ = new Differ("--- Expected\n+++ Actual\n"); return $differ->diff($this->string, $other); } protected function createPatternFromFormat($string) { $string = str_replace( array( '%e', '%s', '%S', '%a', '%A', '%w', '%i', '%d', '%x', '%f', '%c' ), array( '\\' . DIRECTORY_SEPARATOR, '[^\r\n]+', '[^\r\n]*', '.+', '.*', '\s*', '[+-]?\d+', '\d+', '[0-9a-fA-F]+', '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', '.' ), preg_quote($string, '/') ); return '/^' . $string . '$/s'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Logical NOT. * * @since Class available since Release 3.0.0 */ class PHPUnit_Framework_Constraint_Not extends PHPUnit_Framework_Constraint { /** * @var PHPUnit_Framework_Constraint */ protected $constraint; /** * @param PHPUnit_Framework_Constraint $constraint */ public function __construct($constraint) { parent::__construct(); if (!($constraint instanceof PHPUnit_Framework_Constraint)) { $constraint = new PHPUnit_Framework_Constraint_IsEqual($constraint); } $this->constraint = $constraint; } /** * @param string $string * @return string */ public static function negate($string) { return str_replace( array( 'contains ', 'exists', 'has ', 'is ', 'are ', 'matches ', 'starts with ', 'ends with ', 'reference ', 'not not ' ), array( 'does not contain ', 'does not exist', 'does not have ', 'is not ', 'are not ', 'does not match ', 'starts not with ', 'ends not with ', 'don\'t reference ', 'not ' ), $string ); } /** * Evaluates the constraint for parameter $other * * If $returnResult is set to false (the default), an exception is thrown * in case of a failure. null is returned otherwise. * * If $returnResult is true, the result of the evaluation is returned as * a boolean value instead: true in case of success, false in case of a * failure. * * @param mixed $other Value or object to evaluate. * @param string $description Additional information about the test * @param bool $returnResult Whether to return a result or throw an exception * @return mixed * @throws PHPUnit_Framework_ExpectationFailedException */ public function evaluate($other, $description = '', $returnResult = false) { $success = !$this->constraint->evaluate($other, $description, true); if ($returnResult) { return $success; } if (!$success) { $this->fail($other, $description); } } /** * Returns the description of the failure * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * * @param mixed $other Evaluated value or object. * @return string */ protected function failureDescription($other) { switch (get_class($this->constraint)) { case 'PHPUnit_Framework_Constraint_And': case 'PHPUnit_Framework_Constraint_Not': case 'PHPUnit_Framework_Constraint_Or': return 'not( ' . $this->constraint->failureDescription($other) . ' )'; default: return self::negate( $this->constraint->failureDescription($other) ); } } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { switch (get_class($this->constraint)) { case 'PHPUnit_Framework_Constraint_And': case 'PHPUnit_Framework_Constraint_Not': case 'PHPUnit_Framework_Constraint_Or': return 'not( ' . $this->constraint->toString() . ' )'; default: return self::negate( $this->constraint->toString() ); } } /** * Counts the number of constraint elements. * * @return int * @since Method available since Release 3.4.0 */ public function count() { return count($this->constraint); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 4.3.0 */ class PHPUnit_Framework_Constraint_ExceptionMessageRegExp extends PHPUnit_Framework_Constraint { /** * @var int */ protected $expectedMessageRegExp; /** * @param string $expected */ public function __construct($expected) { parent::__construct(); $this->expectedMessageRegExp = $expected; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param Exception $other * @return bool */ protected function matches($other) { $match = PHPUnit_Util_Regex::pregMatchSafe($this->expectedMessageRegExp, $other->getMessage()); if (false === $match) { throw new PHPUnit_Framework_Exception( "Invalid expected exception message regex given: '{$this->expectedMessageRegExp}'" ); } return 1 === $match; } /** * Returns the description of the failure * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * * @param mixed $other Evaluated value or object. * @return string */ protected function failureDescription($other) { return sprintf( "exception message '%s' matches '%s'", $other->getMessage(), $this->expectedMessageRegExp ); } /** * @return string */ public function toString() { return 'exception message matches '; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that asserts that the value it is evaluated for is greater * than a given value. * * @since Class available since Release 3.0.0 */ class PHPUnit_Framework_Constraint_GreaterThan extends PHPUnit_Framework_Constraint { /** * @var numeric */ protected $value; /** * @param numeric $value */ public function __construct($value) { parent::__construct(); $this->value = $value; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { return $this->value < $other; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return 'is greater than ' . $this->exporter->export($this->value); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 3.6.6 */ class PHPUnit_Framework_Constraint_ExceptionMessage extends PHPUnit_Framework_Constraint { /** * @var int */ protected $expectedMessage; /** * @param string $expected */ public function __construct($expected) { parent::__construct(); $this->expectedMessage = $expected; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param Exception $other * @return bool */ protected function matches($other) { return strpos($other->getMessage(), $this->expectedMessage) !== false; } /** * Returns the description of the failure * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * * @param mixed $other Evaluated value or object. * @return string */ protected function failureDescription($other) { return sprintf( "exception message '%s' contains '%s'", $other->getMessage(), $this->expectedMessage ); } /** * @return string */ public function toString() { return 'exception message contains '; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that evaluates against a specified closure. */ class PHPUnit_Framework_Constraint_Callback extends PHPUnit_Framework_Constraint { private $callback; /** * @param callable $callback * @throws PHPUnit_Framework_Exception */ public function __construct($callback) { if (!is_callable($callback)) { throw PHPUnit_Util_InvalidArgumentHelper::factory( 1, 'callable' ); } parent::__construct(); $this->callback = $callback; } /** * Evaluates the constraint for parameter $value. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { return call_user_func($this->callback, $other); } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return 'is accepted by specified callback'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that asserts that the array it is evaluated for has a given key. * * Uses array_key_exists() to check if the key is found in the input array, if * not found the evaluation fails. * * The array key is passed in the constructor. * * @since Class available since Release 3.0.0 */ class PHPUnit_Framework_Constraint_ArrayHasKey extends PHPUnit_Framework_Constraint { /** * @var int|string */ protected $key; /** * @param int|string $key */ public function __construct($key) { parent::__construct(); $this->key = $key; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { if (is_array($other)) { return array_key_exists($this->key, $other); } if ($other instanceof ArrayAccess) { return $other->offsetExists($this->key); } return false; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return 'has the key ' . $this->exporter->export($this->key); } /** * Returns the description of the failure * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * * @param mixed $other Evaluated value or object. * @return string */ protected function failureDescription($other) { return 'an array ' . $this->toString(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that asserts that the string it is evaluated for contains * a given string. * * Uses strpos() to find the position of the string in the input, if not found * the evaluation fails. * * The sub-string is passed in the constructor. * * @since Class available since Release 3.0.0 */ class PHPUnit_Framework_Constraint_StringContains extends PHPUnit_Framework_Constraint { /** * @var string */ protected $string; /** * @var bool */ protected $ignoreCase; /** * @param string $string * @param bool $ignoreCase */ public function __construct($string, $ignoreCase = false) { parent::__construct(); $this->string = $string; $this->ignoreCase = $ignoreCase; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { if ($this->ignoreCase) { return stripos($other, $this->string) !== false; } else { return strpos($other, $this->string) !== false; } } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { if ($this->ignoreCase) { $string = strtolower($this->string); } else { $string = $this->string; } return sprintf( 'contains "%s"', $string ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that asserts that the Traversable it is applied to contains * only values of a given type. * * @since Class available since Release 3.1.4 */ class PHPUnit_Framework_Constraint_TraversableContainsOnly extends PHPUnit_Framework_Constraint { /** * @var PHPUnit_Framework_Constraint */ protected $constraint; /** * @var string */ protected $type; /** * @param string $type * @param bool $isNativeType */ public function __construct($type, $isNativeType = true) { parent::__construct(); if ($isNativeType) { $this->constraint = new PHPUnit_Framework_Constraint_IsType($type); } else { $this->constraint = new PHPUnit_Framework_Constraint_IsInstanceOf( $type ); } $this->type = $type; } /** * Evaluates the constraint for parameter $other * * If $returnResult is set to false (the default), an exception is thrown * in case of a failure. null is returned otherwise. * * If $returnResult is true, the result of the evaluation is returned as * a boolean value instead: true in case of success, false in case of a * failure. * * @param mixed $other Value or object to evaluate. * @param string $description Additional information about the test * @param bool $returnResult Whether to return a result or throw an exception * @return mixed * @throws PHPUnit_Framework_ExpectationFailedException */ public function evaluate($other, $description = '', $returnResult = false) { $success = true; foreach ($other as $item) { if (!$this->constraint->evaluate($item, '', true)) { $success = false; break; } } if ($returnResult) { return $success; } if (!$success) { $this->fail($other, $description); } } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return 'contains only values of type "' . $this->type . '"'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that checks whether a variable is empty(). * * @since Class available since Release 3.5.0 */ class PHPUnit_Framework_Constraint_IsEmpty extends PHPUnit_Framework_Constraint { /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { if ($other instanceof Countable) { return count($other) === 0; } return empty($other); } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return 'is empty'; } /** * Returns the description of the failure * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * * @param mixed $other Evaluated value or object. * @return string */ protected function failureDescription($other) { $type = gettype($other); return sprintf( '%s %s %s', $type[0] == 'a' || $type[0] == 'o' ? 'an' : 'a', $type, $this->toString() ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that asserts that the string it is evaluated for matches * a regular expression. * * Checks a given value using the Perl Compatible Regular Expression extension * in PHP. The pattern is matched by executing preg_match(). * * The pattern string passed in the constructor. * * @since Class available since Release 3.0.0 */ class PHPUnit_Framework_Constraint_PCREMatch extends PHPUnit_Framework_Constraint { /** * @var string */ protected $pattern; /** * @param string $pattern */ public function __construct($pattern) { parent::__construct(); $this->pattern = $pattern; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { return preg_match($this->pattern, $other) > 0; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return sprintf( 'matches PCRE pattern "%s"', $this->pattern ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that accepts null. * * @since Class available since Release 3.3.0 */ class PHPUnit_Framework_Constraint_IsNull extends PHPUnit_Framework_Constraint { /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { return $other === null; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return 'is null'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 3.1.0 */ class PHPUnit_Framework_Constraint_Attribute extends PHPUnit_Framework_Constraint_Composite { /** * @var string */ protected $attributeName; /** * @param PHPUnit_Framework_Constraint $constraint * @param string $attributeName */ public function __construct(PHPUnit_Framework_Constraint $constraint, $attributeName) { parent::__construct($constraint); $this->attributeName = $attributeName; } /** * Evaluates the constraint for parameter $other * * If $returnResult is set to false (the default), an exception is thrown * in case of a failure. null is returned otherwise. * * If $returnResult is true, the result of the evaluation is returned as * a boolean value instead: true in case of success, false in case of a * failure. * * @param mixed $other Value or object to evaluate. * @param string $description Additional information about the test * @param bool $returnResult Whether to return a result or throw an exception * @return mixed * @throws PHPUnit_Framework_ExpectationFailedException */ public function evaluate($other, $description = '', $returnResult = false) { return parent::evaluate( PHPUnit_Framework_Assert::readAttribute( $other, $this->attributeName ), $description, $returnResult ); } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return 'attribute "' . $this->attributeName . '" ' . $this->innerConstraint->toString(); } /** * Returns the description of the failure * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * * @param mixed $other Evaluated value or object. * @return string */ protected function failureDescription($other) { return $this->toString(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that accepts false. * * @since Class available since Release 3.3.0 */ class PHPUnit_Framework_Constraint_IsFalse extends PHPUnit_Framework_Constraint { /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { return $other === false; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return 'is false'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that asserts that the class it is evaluated for has a given * static attribute. * * The attribute name is passed in the constructor. * * @since Class available since Release 3.1.0 */ class PHPUnit_Framework_Constraint_ClassHasStaticAttribute extends PHPUnit_Framework_Constraint_ClassHasAttribute { /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { $class = new ReflectionClass($other); if ($class->hasProperty($this->attributeName)) { $attribute = $class->getProperty($this->attributeName); return $attribute->isStatic(); } else { return false; } } /** * Returns a string representation of the constraint. * * @return string * @since Method available since Release 3.3.0 */ public function toString() { return sprintf( 'has static attribute "%s"', $this->attributeName ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Logical OR. * * @since Class available since Release 3.0.0 */ class PHPUnit_Framework_Constraint_Or extends PHPUnit_Framework_Constraint { /** * @var PHPUnit_Framework_Constraint[] */ protected $constraints = array(); /** * @param PHPUnit_Framework_Constraint[] $constraints */ public function setConstraints(array $constraints) { $this->constraints = array(); foreach ($constraints as $constraint) { if (!($constraint instanceof PHPUnit_Framework_Constraint)) { $constraint = new PHPUnit_Framework_Constraint_IsEqual( $constraint ); } $this->constraints[] = $constraint; } } /** * Evaluates the constraint for parameter $other * * If $returnResult is set to false (the default), an exception is thrown * in case of a failure. null is returned otherwise. * * If $returnResult is true, the result of the evaluation is returned as * a boolean value instead: true in case of success, false in case of a * failure. * * @param mixed $other Value or object to evaluate. * @param string $description Additional information about the test * @param bool $returnResult Whether to return a result or throw an exception * @return mixed * @throws PHPUnit_Framework_ExpectationFailedException */ public function evaluate($other, $description = '', $returnResult = false) { $success = false; $constraint = null; foreach ($this->constraints as $constraint) { if ($constraint->evaluate($other, $description, true)) { $success = true; break; } } if ($returnResult) { return $success; } if (!$success) { $this->fail($other, $description); } } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { $text = ''; foreach ($this->constraints as $key => $constraint) { if ($key > 0) { $text .= ' or '; } $text .= $constraint->toString(); } return $text; } /** * Counts the number of constraint elements. * * @return int * @since Method available since Release 3.4.0 */ public function count() { $count = 0; foreach ($this->constraints as $constraint) { $count += count($constraint); } return $count; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that asserts that one value is identical to another. * * Identical check is performed with PHP's === operator, the operator is * explained in detail at * {@url http://www.php.net/manual/en/types.comparisons.php}. * Two values are identical if they have the same value and are of the same * type. * * The expected value is passed in the constructor. * * @since Class available since Release 3.0.0 */ class PHPUnit_Framework_Constraint_IsIdentical extends PHPUnit_Framework_Constraint { /** * @var float */ const EPSILON = 0.0000000001; /** * @var mixed */ protected $value; /** * @param mixed $value */ public function __construct($value) { parent::__construct(); $this->value = $value; } /** * Evaluates the constraint for parameter $other * * If $returnResult is set to false (the default), an exception is thrown * in case of a failure. null is returned otherwise. * * If $returnResult is true, the result of the evaluation is returned as * a boolean value instead: true in case of success, false in case of a * failure. * * @param mixed $other Value or object to evaluate. * @param string $description Additional information about the test * @param bool $returnResult Whether to return a result or throw an exception * @return mixed * @throws PHPUnit_Framework_ExpectationFailedException */ public function evaluate($other, $description = '', $returnResult = false) { if (is_double($this->value) && is_double($other) && !is_infinite($this->value) && !is_infinite($other) && !is_nan($this->value) && !is_nan($other)) { $success = abs($this->value - $other) < self::EPSILON; } else { $success = $this->value === $other; } if ($returnResult) { return $success; } if (!$success) { $f = null; // if both values are strings, make sure a diff is generated if (is_string($this->value) && is_string($other)) { $f = new SebastianBergmann\Comparator\ComparisonFailure( $this->value, $other, $this->value, $other ); } $this->fail($other, $description, $f); } } /** * Returns the description of the failure * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * * @param mixed $other Evaluated value or object. * @return string */ protected function failureDescription($other) { if (is_object($this->value) && is_object($other)) { return 'two variables reference the same object'; } if (is_string($this->value) && is_string($other)) { return 'two strings are identical'; } return parent::failureDescription($other); } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { if (is_object($this->value)) { return 'is identical to an object of class "' . get_class($this->value) . '"'; } else { return 'is identical to ' . $this->exporter->export($this->value); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that checks if the file(name) that it is evaluated for exists. * * The file path to check is passed as $other in evaluate(). * * @since Class available since Release 3.0.0 */ class PHPUnit_Framework_Constraint_FileExists extends PHPUnit_Framework_Constraint { /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { return file_exists($other); } /** * Returns the description of the failure * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * * @param mixed $other Evaluated value or object. * @return string */ protected function failureDescription($other) { return sprintf( 'file "%s" exists', $other ); } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return 'file exists'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 3.1.0 */ abstract class PHPUnit_Framework_Constraint_Composite extends PHPUnit_Framework_Constraint { /** * @var PHPUnit_Framework_Constraint */ protected $innerConstraint; /** * @param PHPUnit_Framework_Constraint $innerConstraint */ public function __construct(PHPUnit_Framework_Constraint $innerConstraint) { parent::__construct(); $this->innerConstraint = $innerConstraint; } /** * Evaluates the constraint for parameter $other * * If $returnResult is set to false (the default), an exception is thrown * in case of a failure. null is returned otherwise. * * If $returnResult is true, the result of the evaluation is returned as * a boolean value instead: true in case of success, false in case of a * failure. * * @param mixed $other Value or object to evaluate. * @param string $description Additional information about the test * @param bool $returnResult Whether to return a result or throw an exception * @return mixed * @throws PHPUnit_Framework_ExpectationFailedException */ public function evaluate($other, $description = '', $returnResult = false) { try { return $this->innerConstraint->evaluate( $other, $description, $returnResult ); } catch (PHPUnit_Framework_ExpectationFailedException $e) { $this->fail($other, $description); } } /** * Counts the number of constraint elements. * * @return int */ public function count() { return count($this->innerConstraint); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that asserts that the string it is evaluated for begins with a * given prefix. * * @since Class available since Release 3.4.0 */ class PHPUnit_Framework_Constraint_StringStartsWith extends PHPUnit_Framework_Constraint { /** * @var string */ protected $prefix; /** * @param string $prefix */ public function __construct($prefix) { parent::__construct(); $this->prefix = $prefix; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { return strpos($other, $this->prefix) === 0; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return 'starts with "' . $this->prefix . '"'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that asserts that the string it is evaluated for ends with a given * suffix. * * @since Class available since Release 3.4.0 */ class PHPUnit_Framework_Constraint_StringEndsWith extends PHPUnit_Framework_Constraint { /** * @var string */ protected $suffix; /** * @param string $suffix */ public function __construct($suffix) { parent::__construct(); $this->suffix = $suffix; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ protected function matches($other) { return substr($other, 0 - strlen($this->suffix)) == $this->suffix; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return 'ends with "' . $this->suffix . '"'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Constraint that accepts any input value. * * @since Class available since Release 3.0.0 */ class PHPUnit_Framework_Constraint_IsAnything extends PHPUnit_Framework_Constraint { /** * Evaluates the constraint for parameter $other * * If $returnResult is set to false (the default), an exception is thrown * in case of a failure. null is returned otherwise. * * If $returnResult is true, the result of the evaluation is returned as * a boolean value instead: true in case of success, false in case of a * failure. * * @param mixed $other Value or object to evaluate. * @param string $description Additional information about the test * @param bool $returnResult Whether to return a result or throw an exception * @return mixed * @throws PHPUnit_Framework_ExpectationFailedException */ public function evaluate($other, $description = '', $returnResult = false) { return $returnResult ? true : null; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return 'is anything'; } /** * Counts the number of constraint elements. * * @return int * @since Method available since Release 3.5.0 */ public function count() { return 0; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 3.6.0 */ class PHPUnit_Framework_Constraint_Count extends PHPUnit_Framework_Constraint { /** * @var int */ protected $expectedCount = 0; /** * @param int $expected */ public function __construct($expected) { parent::__construct(); $this->expectedCount = $expected; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other * @return bool */ protected function matches($other) { return $this->expectedCount === $this->getCountOf($other); } /** * @param mixed $other * @return bool */ protected function getCountOf($other) { if ($other instanceof Countable || is_array($other)) { return count($other); } elseif ($other instanceof Traversable) { if ($other instanceof IteratorAggregate) { $iterator = $other->getIterator(); } else { $iterator = $other; } $key = $iterator->key(); $count = iterator_count($iterator); // manually rewind $iterator to previous key, since iterator_count // moves pointer if ($key !== null) { $iterator->rewind(); while ($iterator->valid() && $key !== $iterator->key()) { $iterator->next(); } } return $count; } } /** * Returns the description of the failure * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * * @param mixed $other Evaluated value or object. * @return string */ protected function failureDescription($other) { return sprintf( 'actual size %d matches expected size %d', $this->getCountOf($other), $this->expectedCount ); } /** * @return string */ public function toString() { return sprintf( 'count matches %d', $this->expectedCount ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Logical AND. * * @since Class available since Release 3.0.0 */ class PHPUnit_Framework_Constraint_And extends PHPUnit_Framework_Constraint { /** * @var PHPUnit_Framework_Constraint[] */ protected $constraints = array(); /** * @var PHPUnit_Framework_Constraint */ protected $lastConstraint = null; /** * @param PHPUnit_Framework_Constraint[] $constraints * @throws PHPUnit_Framework_Exception */ public function setConstraints(array $constraints) { $this->constraints = array(); foreach ($constraints as $constraint) { if (!($constraint instanceof PHPUnit_Framework_Constraint)) { throw new PHPUnit_Framework_Exception( 'All parameters to ' . __CLASS__ . ' must be a constraint object.' ); } $this->constraints[] = $constraint; } } /** * Evaluates the constraint for parameter $other * * If $returnResult is set to false (the default), an exception is thrown * in case of a failure. null is returned otherwise. * * If $returnResult is true, the result of the evaluation is returned as * a boolean value instead: true in case of success, false in case of a * failure. * * @param mixed $other Value or object to evaluate. * @param string $description Additional information about the test * @param bool $returnResult Whether to return a result or throw an exception * @return mixed * @throws PHPUnit_Framework_ExpectationFailedException */ public function evaluate($other, $description = '', $returnResult = false) { $success = true; $constraint = null; foreach ($this->constraints as $constraint) { if (!$constraint->evaluate($other, $description, true)) { $success = false; break; } } if ($returnResult) { return $success; } if (!$success) { $this->fail($other, $description); } } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { $text = ''; foreach ($this->constraints as $key => $constraint) { if ($key > 0) { $text .= ' and '; } $text .= $constraint->toString(); } return $text; } /** * Counts the number of constraint elements. * * @return int * @since Method available since Release 3.4.0 */ public function count() { $count = 0; foreach ($this->constraints as $constraint) { $count += count($constraint); } return $count; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * An empty Listener that can be extended to implement TestListener * with just a few lines of code. * @see PHPUnit_Framework_TestListener for documentation on the API methods. * * @since Class available since Release 4.0.0 */ abstract class PHPUnit_Framework_BaseTestListener implements PHPUnit_Framework_TestListener { public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) { } public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { } public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) { } public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) { } public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) { } public function startTestSuite(PHPUnit_Framework_TestSuite $suite) { } public function endTestSuite(PHPUnit_Framework_TestSuite $suite) { } public function startTest(PHPUnit_Framework_Test $test) { } public function endTest(PHPUnit_Framework_Test $test, $time) { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Extension to PHPUnit_Framework_AssertionFailedError to mark the special * case of a skipped test. * * @since Class available since Release 3.0.0 */ class PHPUnit_Framework_SkippedTestError extends PHPUnit_Framework_AssertionFailedError implements PHPUnit_Framework_SkippedTest { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Extension to PHPUnit_Framework_AssertionFailedError to mark the special * case of a test test that unintentionally covers code. * * @since Class available since Release 4.0.0 */ class PHPUnit_Framework_UnintentionallyCoveredCodeError extends PHPUnit_Framework_RiskyTestError { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A warning. * * @since Class available since Release 2.0.0 */ class PHPUnit_Framework_Warning extends PHPUnit_Framework_TestCase { /** * @var string */ protected $message = ''; /** * @var bool */ protected $backupGlobals = false; /** * @var bool */ protected $backupStaticAttributes = false; /** * @var bool */ protected $runTestInSeparateProcess = false; /** * @var bool */ protected $useErrorHandler = false; /** * @param string $message */ public function __construct($message = '') { $this->message = $message; parent::__construct('Warning'); } /** * @throws PHPUnit_Framework_Exception */ protected function runTest() { $this->fail($this->message); } /** * @return string * @since Method available since Release 3.0.0 */ public function getMessage() { return $this->message; } /** * Returns a string representation of the test case. * * @return string * @since Method available since Release 3.4.0 */ public function toString() { return 'Warning'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * We have a TestSuite object A. * In TestSuite object A we have Tests tagged with @group. * We want a TestSuite object B that contains TestSuite objects C, D, ... * for the Tests tagged with @group C, @group D, ... * Running the Tests from TestSuite object B results in Tests tagged with both * @group C and @group D in TestSuite object A to be run twice . * * * $suite = new PHPUnit_Extensions_GroupTestSuite($A, array('C', 'D')); * * * @since Class available since Release 3.3.0 */ class PHPUnit_Extensions_GroupTestSuite extends PHPUnit_Framework_TestSuite { public function __construct(PHPUnit_Framework_TestSuite $suite, array $groups) { $groupSuites = array(); $name = $suite->getName(); foreach ($groups as $group) { $groupSuites[$group] = new PHPUnit_Framework_TestSuite($name . ' - ' . $group); $this->addTest($groupSuites[$group]); } $tests = new RecursiveIteratorIterator( new PHPUnit_Util_TestSuiteIterator($suite), RecursiveIteratorIterator::LEAVES_ONLY ); foreach ($tests as $test) { if ($test instanceof PHPUnit_Framework_TestCase) { $testGroups = PHPUnit_Util_Test::getGroups( get_class($test), $test->getName(false) ); foreach ($groups as $group) { foreach ($testGroups as $testGroup) { if ($group == $testGroup) { $groupSuites[$group]->addTest($test); } } } } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Suite for .phpt test cases. * * @since Class available since Release 3.1.4 */ class PHPUnit_Extensions_PhptTestSuite extends PHPUnit_Framework_TestSuite { /** * Constructs a new TestSuite for .phpt test cases. * * @param string $directory * @throws PHPUnit_Framework_Exception */ public function __construct($directory) { if (is_string($directory) && is_dir($directory)) { $this->setName($directory); $facade = new File_Iterator_Facade; $files = $facade->getFilesAsArray($directory, '.phpt'); foreach ($files as $file) { $this->addTestFile($file); } } else { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'directory name'); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A Decorator that runs a test repeatedly. * * @since Class available since Release 2.0.0 */ class PHPUnit_Extensions_RepeatedTest extends PHPUnit_Extensions_TestDecorator { /** * @var bool */ protected $processIsolation = false; /** * @var int */ protected $timesRepeat = 1; /** * @param PHPUnit_Framework_Test $test * @param int $timesRepeat * @param bool $processIsolation * @throws PHPUnit_Framework_Exception */ public function __construct(PHPUnit_Framework_Test $test, $timesRepeat = 1, $processIsolation = false) { parent::__construct($test); if (is_integer($timesRepeat) && $timesRepeat >= 0) { $this->timesRepeat = $timesRepeat; } else { throw PHPUnit_Util_InvalidArgumentHelper::factory( 2, 'positive integer' ); } $this->processIsolation = $processIsolation; } /** * Counts the number of test cases that * will be run by this test. * * @return int */ public function count() { return $this->timesRepeat * count($this->test); } /** * Runs the decorated test and collects the * result in a TestResult. * * @param PHPUnit_Framework_TestResult $result * @return PHPUnit_Framework_TestResult * @throws PHPUnit_Framework_Exception */ public function run(PHPUnit_Framework_TestResult $result = null) { if ($result === null) { $result = $this->createResult(); } //@codingStandardsIgnoreStart for ($i = 0; $i < $this->timesRepeat && !$result->shouldStop(); $i++) { //@codingStandardsIgnoreEnd if ($this->test instanceof PHPUnit_Framework_TestSuite) { $this->test->setRunTestInSeparateProcess($this->processIsolation); } $this->test->run($result); } return $result; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Base class for test listeners that interact with an issue tracker. * * @since Class available since Release 3.4.0 */ abstract class PHPUnit_Extensions_TicketListener implements PHPUnit_Framework_TestListener { /** * @var array */ protected $ticketCounts = array(); /** * @var bool */ protected $ran = false; /** * An error occurred. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) { } /** * A failure occurred. * * @param PHPUnit_Framework_Test $test * @param PHPUnit_Framework_AssertionFailedError $e * @param float $time */ public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { } /** * Incomplete test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) { } /** * Risky test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time * @since Method available since Release 4.0.0 */ public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) { } /** * Skipped test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time * @since Method available since Release 3.0.0 */ public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) { } /** * A test suite started. * * @param PHPUnit_Framework_TestSuite $suite * @since Method available since Release 2.2.0 */ public function startTestSuite(PHPUnit_Framework_TestSuite $suite) { } /** * A test suite ended. * * @param PHPUnit_Framework_TestSuite $suite * @since Method available since Release 2.2.0 */ public function endTestSuite(PHPUnit_Framework_TestSuite $suite) { } /** * A test started. * * @param PHPUnit_Framework_Test $test */ public function startTest(PHPUnit_Framework_Test $test) { if (!$test instanceof PHPUnit_Framework_Warning) { if ($this->ran) { return; } $name = $test->getName(false); $tickets = PHPUnit_Util_Test::getTickets(get_class($test), $name); foreach ($tickets as $ticket) { $this->ticketCounts[$ticket][$name] = 1; } $this->ran = true; } } /** * A test ended. * * @param PHPUnit_Framework_Test $test * @param float $time */ public function endTest(PHPUnit_Framework_Test $test, $time) { if (!$test instanceof PHPUnit_Framework_Warning) { if ($test->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_PASSED) { $ifStatus = array('assigned', 'new', 'reopened'); $newStatus = 'closed'; $message = 'Automatically closed by PHPUnit (test passed).'; $resolution = 'fixed'; $cumulative = true; } elseif ($test->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE) { $ifStatus = array('closed'); $newStatus = 'reopened'; $message = 'Automatically reopened by PHPUnit (test failed).'; $resolution = ''; $cumulative = false; } else { return; } $name = $test->getName(false); $tickets = PHPUnit_Util_Test::getTickets(get_class($test), $name); foreach ($tickets as $ticket) { // Remove this test from the totals (if it passed). if ($test->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_PASSED) { unset($this->ticketCounts[$ticket][$name]); } // Only close tickets if ALL referenced cases pass // but reopen tickets if a single test fails. if ($cumulative) { // Determine number of to-pass tests: if (count($this->ticketCounts[$ticket]) > 0) { // There exist remaining test cases with this reference. $adjustTicket = false; } else { // No remaining tickets, go ahead and adjust. $adjustTicket = true; } } else { $adjustTicket = true; } $ticketInfo = $this->getTicketInfo($ticket); if ($adjustTicket && in_array($ticketInfo['status'], $ifStatus)) { $this->updateTicket($ticket, $newStatus, $message, $resolution); } } } } /** * @param mixed $ticketId * @return mixed */ abstract protected function getTicketInfo($ticketId = null); /** * @param string $ticketId * @param string $newStatus * @param string $message * @param string $resolution */ abstract protected function updateTicket($ticketId, $newStatus, $message, $resolution); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A Decorator for Tests. * * Use TestDecorator as the base class for defining new * test decorators. Test decorator subclasses can be introduced * to add behaviour before or after a test is run. * * @since Class available since Release 2.0.0 */ class PHPUnit_Extensions_TestDecorator extends PHPUnit_Framework_Assert implements PHPUnit_Framework_Test, PHPUnit_Framework_SelfDescribing { /** * The Test to be decorated. * * @var object */ protected $test = null; /** * Constructor. * * @param PHPUnit_Framework_Test $test */ public function __construct(PHPUnit_Framework_Test $test) { $this->test = $test; } /** * Returns a string representation of the test. * * @return string */ public function toString() { return $this->test->toString(); } /** * Runs the test and collects the * result in a TestResult. * * @param PHPUnit_Framework_TestResult $result */ public function basicRun(PHPUnit_Framework_TestResult $result) { $this->test->run($result); } /** * Counts the number of test cases that * will be run by this test. * * @return int */ public function count() { return count($this->test); } /** * Creates a default TestResult object. * * @return PHPUnit_Framework_TestResult */ protected function createResult() { return new PHPUnit_Framework_TestResult; } /** * Returns the test to be run. * * @return PHPUnit_Framework_Test */ public function getTest() { return $this->test; } /** * Runs the decorated test and collects the * result in a TestResult. * * @param PHPUnit_Framework_TestResult $result * @return PHPUnit_Framework_TestResult */ public function run(PHPUnit_Framework_TestResult $result = null) { if ($result === null) { $result = $this->createResult(); } $this->basicRun($result); return $result; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Runner for PHPT test cases. * * @since Class available since Release 3.1.4 */ class PHPUnit_Extensions_PhptTestCase implements PHPUnit_Framework_Test, PHPUnit_Framework_SelfDescribing { /** * @var string */ private $filename; /** * @var array */ private $settings = array( 'allow_url_fopen=1', 'auto_append_file=', 'auto_prepend_file=', 'disable_functions=', 'display_errors=1', 'docref_root=', 'docref_ext=.html', 'error_append_string=', 'error_prepend_string=', 'error_reporting=-1', 'html_errors=0', 'log_errors=0', 'magic_quotes_runtime=0', 'output_handler=', 'open_basedir=', 'output_buffering=Off', 'report_memleaks=0', 'report_zend_debug=0', 'safe_mode=0', 'track_errors=1', 'xdebug.default_enable=0' ); /** * Constructs a test case with the given filename. * * @param string $filename * @throws PHPUnit_Framework_Exception */ public function __construct($filename) { if (!is_string($filename)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } if (!is_file($filename)) { throw new PHPUnit_Framework_Exception( sprintf( 'File "%s" does not exist.', $filename ) ); } $this->filename = $filename; } /** * Counts the number of test cases executed by run(TestResult result). * * @return int */ public function count() { return 1; } /** * Runs a test and collects its result in a TestResult instance. * * @param PHPUnit_Framework_TestResult $result * @return PHPUnit_Framework_TestResult */ public function run(PHPUnit_Framework_TestResult $result = null) { $sections = $this->parse(); $code = $this->render($sections['FILE']); if ($result === null) { $result = new PHPUnit_Framework_TestResult; } $php = PHPUnit_Util_PHP::factory(); $skip = false; $time = 0; $settings = $this->settings; $result->startTest($this); if (isset($sections['INI'])) { $settings = array_merge($settings, $this->parseIniSection($sections['INI'])); } if (isset($sections['SKIPIF'])) { $jobResult = $php->runJob($sections['SKIPIF'], $settings); if (!strncasecmp('skip', ltrim($jobResult['stdout']), 4)) { if (preg_match('/^\s*skip\s*(.+)\s*/i', $jobResult['stdout'], $message)) { $message = substr($message[1], 2); } else { $message = ''; } $result->addFailure($this, new PHPUnit_Framework_SkippedTestError($message), 0); $skip = true; } } if (!$skip) { PHP_Timer::start(); $jobResult = $php->runJob($code, $settings); $time = PHP_Timer::stop(); if (isset($sections['EXPECT'])) { $assertion = 'assertEquals'; $expected = $sections['EXPECT']; } else { $assertion = 'assertStringMatchesFormat'; $expected = $sections['EXPECTF']; } $output = preg_replace('/\r\n/', "\n", trim($jobResult['stdout'])); $expected = preg_replace('/\r\n/', "\n", trim($expected)); try { PHPUnit_Framework_Assert::$assertion($expected, $output); } catch (PHPUnit_Framework_AssertionFailedError $e) { $result->addFailure($this, $e, $time); } catch (Throwable $t) { $result->addError($this, $t, $time); } catch (Exception $e) { $result->addError($this, $e, $time); } } $result->endTest($this, $time); return $result; } /** * Returns the name of the test case. * * @return string */ public function getName() { return $this->toString(); } /** * Returns a string representation of the test case. * * @return string */ public function toString() { return $this->filename; } /** * @return array * @throws PHPUnit_Framework_Exception */ private function parse() { $sections = array(); $section = ''; foreach (file($this->filename) as $line) { if (preg_match('/^--([_A-Z]+)--/', $line, $result)) { $section = $result[1]; $sections[$section] = ''; continue; } elseif (empty($section)) { throw new PHPUnit_Framework_Exception('Invalid PHPT file'); } $sections[$section] .= $line; } if (!isset($sections['FILE']) || (!isset($sections['EXPECT']) && !isset($sections['EXPECTF']))) { throw new PHPUnit_Framework_Exception('Invalid PHPT file'); } return $sections; } /** * @param string $code * @return string */ private function render($code) { return str_replace( array( '__DIR__', '__FILE__' ), array( "'" . dirname($this->filename) . "'", "'" . $this->filename . "'" ), $code ); } /** * Parse --INI-- section key value pairs and return as array. * * @param string * @return array */ protected function parseIniSection($content) { return preg_split('/\n|\r/', $content, -1, PREG_SPLIT_NO_EMPTY); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A TestRunner for the Command Line Interface (CLI) * PHP SAPI Module. * * @since Class available since Release 3.0.0 */ class PHPUnit_TextUI_Command { /** * @var array */ protected $arguments = array( 'listGroups' => false, 'loader' => null, 'useDefaultConfiguration' => true ); /** * @var array */ protected $options = array(); /** * @var array */ protected $longOptions = array( 'colors==' => null, 'bootstrap=' => null, 'columns=' => null, 'configuration=' => null, 'coverage-clover=' => null, 'coverage-crap4j=' => null, 'coverage-html=' => null, 'coverage-php=' => null, 'coverage-text==' => null, 'coverage-xml=' => null, 'debug' => null, 'exclude-group=' => null, 'filter=' => null, 'testsuite=' => null, 'group=' => null, 'help' => null, 'include-path=' => null, 'list-groups' => null, 'loader=' => null, 'log-json=' => null, 'log-junit=' => null, 'log-tap=' => null, 'process-isolation' => null, 'repeat=' => null, 'stderr' => null, 'stop-on-error' => null, 'stop-on-failure' => null, 'stop-on-incomplete' => null, 'stop-on-risky' => null, 'stop-on-skipped' => null, 'report-useless-tests' => null, 'strict-coverage' => null, 'disallow-test-output' => null, 'enforce-time-limit' => null, 'disallow-todo-tests' => null, 'strict-global-state' => null, 'strict' => null, 'tap' => null, 'testdox' => null, 'testdox-html=' => null, 'testdox-text=' => null, 'test-suffix=' => null, 'no-configuration' => null, 'no-coverage' => null, 'no-globals-backup' => null, 'printer=' => null, 'static-backup' => null, 'verbose' => null, 'version' => null ); /** * @var bool */ private $versionStringPrinted = false; /** * @param bool $exit */ public static function main($exit = true) { $command = new static; return $command->run($_SERVER['argv'], $exit); } /** * @param array $argv * @param bool $exit * @return int */ public function run(array $argv, $exit = true) { $this->handleArguments($argv); $runner = $this->createRunner(); if (is_object($this->arguments['test']) && $this->arguments['test'] instanceof PHPUnit_Framework_Test) { $suite = $this->arguments['test']; } else { $suite = $runner->getTest( $this->arguments['test'], $this->arguments['testFile'], $this->arguments['testSuffixes'] ); } if ($this->arguments['listGroups']) { $this->printVersionString(); print "Available test group(s):\n"; $groups = $suite->getGroups(); sort($groups); foreach ($groups as $group) { print " - $group\n"; } if ($exit) { exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT); } else { return PHPUnit_TextUI_TestRunner::SUCCESS_EXIT; } } unset($this->arguments['test']); unset($this->arguments['testFile']); try { $result = $runner->doRun($suite, $this->arguments); } catch (PHPUnit_Framework_Exception $e) { print $e->getMessage() . "\n"; } $ret = PHPUnit_TextUI_TestRunner::FAILURE_EXIT; if (isset($result) && $result->wasSuccessful()) { $ret = PHPUnit_TextUI_TestRunner::SUCCESS_EXIT; } elseif (!isset($result) || $result->errorCount() > 0) { $ret = PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT; } if ($exit) { exit($ret); } else { return $ret; } } /** * Create a TestRunner, override in subclasses. * * @return PHPUnit_TextUI_TestRunner * @since Method available since Release 3.6.0 */ protected function createRunner() { return new PHPUnit_TextUI_TestRunner($this->arguments['loader']); } /** * Handles the command-line arguments. * * A child class of PHPUnit_TextUI_Command can hook into the argument * parsing by adding the switch(es) to the $longOptions array and point to a * callback method that handles the switch(es) in the child class like this * * * longOptions['my-switch'] = 'myHandler'; * // my-secondswitch will accept a value - note the equals sign * $this->longOptions['my-secondswitch='] = 'myOtherHandler'; * } * * // --my-switch -> myHandler() * protected function myHandler() * { * } * * // --my-secondswitch foo -> myOtherHandler('foo') * protected function myOtherHandler ($value) * { * } * * // You will also need this - the static keyword in the * // PHPUnit_TextUI_Command will mean that it'll be * // PHPUnit_TextUI_Command that gets instantiated, * // not MyCommand * public static function main($exit = true) * { * $command = new static; * * return $command->run($_SERVER['argv'], $exit); * } * * } * * * @param array $argv */ protected function handleArguments(array $argv) { if (defined('__PHPUNIT_PHAR__')) { $this->longOptions['check-version'] = null; $this->longOptions['selfupdate'] = null; $this->longOptions['self-update'] = null; } try { $this->options = PHPUnit_Util_Getopt::getopt( $argv, 'd:c:hv', array_keys($this->longOptions) ); } catch (PHPUnit_Framework_Exception $e) { $this->showError($e->getMessage()); } foreach ($this->options[0] as $option) { switch ($option[0]) { case '--colors': $this->arguments['colors'] = $option[1] ?: PHPUnit_TextUI_ResultPrinter::COLOR_AUTO; break; case '--bootstrap': $this->arguments['bootstrap'] = $option[1]; break; case '--columns': if (is_numeric($option[1])) { $this->arguments['columns'] = (int) $option[1]; } elseif ($option[1] == 'max') { $this->arguments['columns'] = 'max'; } break; case 'c': case '--configuration': $this->arguments['configuration'] = $option[1]; break; case '--coverage-clover': $this->arguments['coverageClover'] = $option[1]; break; case '--coverage-crap4j': $this->arguments['coverageCrap4J'] = $option[1]; break; case '--coverage-html': $this->arguments['coverageHtml'] = $option[1]; break; case '--coverage-php': $this->arguments['coveragePHP'] = $option[1]; break; case '--coverage-text': if ($option[1] === null) { $option[1] = 'php://stdout'; } $this->arguments['coverageText'] = $option[1]; $this->arguments['coverageTextShowUncoveredFiles'] = false; $this->arguments['coverageTextShowOnlySummary'] = false; break; case '--coverage-xml': $this->arguments['coverageXml'] = $option[1]; break; case 'd': $ini = explode('=', $option[1]); if (isset($ini[0])) { if (isset($ini[1])) { ini_set($ini[0], $ini[1]); } else { ini_set($ini[0], true); } } break; case '--debug': $this->arguments['debug'] = true; break; case 'h': case '--help': $this->showHelp(); exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT); break; case '--filter': $this->arguments['filter'] = $option[1]; break; case '--testsuite': $this->arguments['testsuite'] = $option[1]; break; case '--group': $this->arguments['groups'] = explode(',', $option[1]); break; case '--exclude-group': $this->arguments['excludeGroups'] = explode( ',', $option[1] ); break; case '--test-suffix': $this->arguments['testSuffixes'] = explode( ',', $option[1] ); break; case '--include-path': $includePath = $option[1]; break; case '--list-groups': $this->arguments['listGroups'] = true; break; case '--printer': $this->arguments['printer'] = $option[1]; break; case '--loader': $this->arguments['loader'] = $option[1]; break; case '--log-json': $this->arguments['jsonLogfile'] = $option[1]; break; case '--log-junit': $this->arguments['junitLogfile'] = $option[1]; break; case '--log-tap': $this->arguments['tapLogfile'] = $option[1]; break; case '--process-isolation': $this->arguments['processIsolation'] = true; break; case '--repeat': $this->arguments['repeat'] = (int) $option[1]; break; case '--stderr': $this->arguments['stderr'] = true; break; case '--stop-on-error': $this->arguments['stopOnError'] = true; break; case '--stop-on-failure': $this->arguments['stopOnFailure'] = true; break; case '--stop-on-incomplete': $this->arguments['stopOnIncomplete'] = true; break; case '--stop-on-risky': $this->arguments['stopOnRisky'] = true; break; case '--stop-on-skipped': $this->arguments['stopOnSkipped'] = true; break; case '--tap': $this->arguments['printer'] = 'PHPUnit_Util_Log_TAP'; break; case '--testdox': $this->arguments['printer'] = 'PHPUnit_Util_TestDox_ResultPrinter_Text'; break; case '--testdox-html': $this->arguments['testdoxHTMLFile'] = $option[1]; break; case '--testdox-text': $this->arguments['testdoxTextFile'] = $option[1]; break; case '--no-configuration': $this->arguments['useDefaultConfiguration'] = false; break; case '--no-coverage': $this->arguments['noCoverage'] = true; break; case '--no-globals-backup': $this->arguments['backupGlobals'] = false; break; case '--static-backup': $this->arguments['backupStaticAttributes'] = true; break; case 'v': case '--verbose': $this->arguments['verbose'] = true; break; case '--version': $this->printVersionString(); exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT); break; case '--report-useless-tests': $this->arguments['reportUselessTests'] = true; break; case '--strict-coverage': $this->arguments['strictCoverage'] = true; break; case '--strict-global-state': $this->arguments['disallowChangesToGlobalState'] = true; break; case '--disallow-test-output': $this->arguments['disallowTestOutput'] = true; break; case '--enforce-time-limit': $this->arguments['enforceTimeLimit'] = true; break; case '--disallow-todo-tests': $this->arguments['disallowTodoAnnotatedTests'] = true; break; case '--strict': $this->arguments['reportUselessTests'] = true; $this->arguments['strictCoverage'] = true; $this->arguments['disallowTestOutput'] = true; $this->arguments['enforceTimeLimit'] = true; $this->arguments['disallowTodoAnnotatedTests'] = true; $this->arguments['deprecatedStrictModeOption'] = true; break; case '--check-version': $this->handleVersionCheck(); break; case '--selfupdate': case '--self-update': $this->handleSelfUpdate(); break; default: $optionName = str_replace('--', '', $option[0]); if (isset($this->longOptions[$optionName])) { $handler = $this->longOptions[$optionName]; } elseif (isset($this->longOptions[$optionName . '='])) { $handler = $this->longOptions[$optionName . '=']; } if (isset($handler) && is_callable(array($this, $handler))) { $this->$handler($option[1]); } } } $this->handleCustomTestSuite(); if (!isset($this->arguments['test'])) { if (isset($this->options[1][0])) { $this->arguments['test'] = $this->options[1][0]; } if (isset($this->options[1][1])) { $this->arguments['testFile'] = realpath($this->options[1][1]); } else { $this->arguments['testFile'] = ''; } if (isset($this->arguments['test']) && is_file($this->arguments['test']) && substr($this->arguments['test'], -5, 5) != '.phpt') { $this->arguments['testFile'] = realpath($this->arguments['test']); $this->arguments['test'] = substr($this->arguments['test'], 0, strrpos($this->arguments['test'], '.')); } } if (!isset($this->arguments['testSuffixes'])) { $this->arguments['testSuffixes'] = array('Test.php', '.phpt'); } if (isset($includePath)) { ini_set( 'include_path', $includePath . PATH_SEPARATOR . ini_get('include_path') ); } if ($this->arguments['loader'] !== null) { $this->arguments['loader'] = $this->handleLoader($this->arguments['loader']); } if (isset($this->arguments['configuration']) && is_dir($this->arguments['configuration'])) { $configurationFile = $this->arguments['configuration'] . '/phpunit.xml'; if (file_exists($configurationFile)) { $this->arguments['configuration'] = realpath( $configurationFile ); } elseif (file_exists($configurationFile . '.dist')) { $this->arguments['configuration'] = realpath( $configurationFile . '.dist' ); } } elseif (!isset($this->arguments['configuration']) && $this->arguments['useDefaultConfiguration']) { if (file_exists('phpunit.xml')) { $this->arguments['configuration'] = realpath('phpunit.xml'); } elseif (file_exists('phpunit.xml.dist')) { $this->arguments['configuration'] = realpath( 'phpunit.xml.dist' ); } } if (isset($this->arguments['configuration'])) { try { $configuration = PHPUnit_Util_Configuration::getInstance( $this->arguments['configuration'] ); } catch (Throwable $e) { print $e->getMessage() . "\n"; exit(PHPUnit_TextUI_TestRunner::FAILURE_EXIT); } catch (Exception $e) { print $e->getMessage() . "\n"; exit(PHPUnit_TextUI_TestRunner::FAILURE_EXIT); } $phpunit = $configuration->getPHPUnitConfiguration(); $configuration->handlePHPConfiguration(); /* * Issue #1216 */ if (isset($this->arguments['bootstrap'])) { $this->handleBootstrap($this->arguments['bootstrap']); } elseif (isset($phpunit['bootstrap'])) { $this->handleBootstrap($phpunit['bootstrap']); } /* * Issue #657 */ if (isset($phpunit['stderr']) && ! isset($this->arguments['stderr'])) { $this->arguments['stderr'] = $phpunit['stderr']; } if (isset($phpunit['columns']) && ! isset($this->arguments['columns'])) { $this->arguments['columns'] = $phpunit['columns']; } if (isset($phpunit['printerClass'])) { if (isset($phpunit['printerFile'])) { $file = $phpunit['printerFile']; } else { $file = ''; } $this->arguments['printer'] = $this->handlePrinter( $phpunit['printerClass'], $file ); } if (isset($phpunit['testSuiteLoaderClass'])) { if (isset($phpunit['testSuiteLoaderFile'])) { $file = $phpunit['testSuiteLoaderFile']; } else { $file = ''; } $this->arguments['loader'] = $this->handleLoader( $phpunit['testSuiteLoaderClass'], $file ); } $browsers = $configuration->getSeleniumBrowserConfiguration(); if (!empty($browsers)) { $this->arguments['deprecatedSeleniumConfiguration'] = true; if (class_exists('PHPUnit_Extensions_SeleniumTestCase')) { PHPUnit_Extensions_SeleniumTestCase::$browsers = $browsers; } } if (!isset($this->arguments['test'])) { $testSuite = $configuration->getTestSuiteConfiguration(isset($this->arguments['testsuite']) ? $this->arguments['testsuite'] : null); if ($testSuite !== null) { $this->arguments['test'] = $testSuite; } } } elseif (isset($this->arguments['bootstrap'])) { $this->handleBootstrap($this->arguments['bootstrap']); } if (isset($this->arguments['printer']) && is_string($this->arguments['printer'])) { $this->arguments['printer'] = $this->handlePrinter($this->arguments['printer']); } if (isset($this->arguments['test']) && is_string($this->arguments['test']) && substr($this->arguments['test'], -5, 5) == '.phpt') { $test = new PHPUnit_Extensions_PhptTestCase($this->arguments['test']); $this->arguments['test'] = new PHPUnit_Framework_TestSuite; $this->arguments['test']->addTest($test); } if (!isset($this->arguments['test']) || (isset($this->arguments['testDatabaseLogRevision']) && !isset($this->arguments['testDatabaseDSN']))) { $this->showHelp(); exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT); } } /** * Handles the loading of the PHPUnit_Runner_TestSuiteLoader implementation. * * @param string $loaderClass * @param string $loaderFile * @return PHPUnit_Runner_TestSuiteLoader */ protected function handleLoader($loaderClass, $loaderFile = '') { if (!class_exists($loaderClass, false)) { if ($loaderFile == '') { $loaderFile = PHPUnit_Util_Filesystem::classNameToFilename( $loaderClass ); } $loaderFile = stream_resolve_include_path($loaderFile); if ($loaderFile) { require $loaderFile; } } if (class_exists($loaderClass, false)) { $class = new ReflectionClass($loaderClass); if ($class->implementsInterface('PHPUnit_Runner_TestSuiteLoader') && $class->isInstantiable()) { return $class->newInstance(); } } if ($loaderClass == 'PHPUnit_Runner_StandardTestSuiteLoader') { return; } $this->showError( sprintf( 'Could not use "%s" as loader.', $loaderClass ) ); } /** * Handles the loading of the PHPUnit_Util_Printer implementation. * * @param string $printerClass * @param string $printerFile * @return PHPUnit_Util_Printer */ protected function handlePrinter($printerClass, $printerFile = '') { if (!class_exists($printerClass, false)) { if ($printerFile == '') { $printerFile = PHPUnit_Util_Filesystem::classNameToFilename( $printerClass ); } $printerFile = stream_resolve_include_path($printerFile); if ($printerFile) { require $printerFile; } } if (class_exists($printerClass)) { $class = new ReflectionClass($printerClass); if ($class->implementsInterface('PHPUnit_Framework_TestListener') && $class->isSubclassOf('PHPUnit_Util_Printer') && $class->isInstantiable()) { if ($class->isSubclassOf('PHPUnit_TextUI_ResultPrinter')) { return $printerClass; } $outputStream = isset($this->arguments['stderr']) ? 'php://stderr' : null; return $class->newInstance($outputStream); } } $this->showError( sprintf( 'Could not use "%s" as printer.', $printerClass ) ); } /** * Loads a bootstrap file. * * @param string $filename */ protected function handleBootstrap($filename) { try { PHPUnit_Util_Fileloader::checkAndLoad($filename); } catch (PHPUnit_Framework_Exception $e) { $this->showError($e->getMessage()); } } /** * @since Method available since Release 4.0.0 */ protected function handleSelfUpdate() { $this->printVersionString(); $localFilename = realpath($_SERVER['argv'][0]); if (!is_writable($localFilename)) { print 'No write permission to update ' . $localFilename . "\n"; exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT); } if (!extension_loaded('openssl')) { print "The OpenSSL extension is not loaded.\n"; exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT); } $remoteFilename = sprintf( 'https://phar.phpunit.de/phpunit%s.phar', PHPUnit_Runner_Version::getReleaseChannel() ); $tempFilename = tempnam(sys_get_temp_dir(), 'phpunit') . '.phar'; // Workaround for https://bugs.php.net/bug.php?id=65538 $caFile = dirname($tempFilename) . '/ca.pem'; copy(__PHPUNIT_PHAR_ROOT__ . '/phar/ca.pem', $caFile); print 'Updating the PHPUnit PHAR ... '; $options = array( 'ssl' => array( 'allow_self_signed' => false, 'cafile' => $caFile, 'verify_peer' => true ) ); if (PHP_VERSION_ID < 50600) { $options['ssl']['CN_match'] = 'phar.phpunit.de'; $options['ssl']['SNI_server_name'] = 'phar.phpunit.de'; } file_put_contents( $tempFilename, file_get_contents( $remoteFilename, false, stream_context_create($options) ) ); chmod($tempFilename, 0777 & ~umask()); try { $phar = new Phar($tempFilename); unset($phar); rename($tempFilename, $localFilename); unlink($caFile); } catch (Throwable $_e) { $e = $_e; } catch (Exception $_e) { $e = $_e; } if (isset($e)) { unlink($caFile); unlink($tempFilename); print " done\n\n" . $e->getMessage() . "\n"; exit(2); } print " done\n"; exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT); } /** * @since Method available since Release 4.8.0 */ protected function handleVersionCheck() { $this->printVersionString(); $latestVersion = file_get_contents('https://phar.phpunit.de/latest-version-of/phpunit'); $isOutdated = version_compare($latestVersion, PHPUnit_Runner_Version::id(), '>'); if ($isOutdated) { print "You are not using the latest version of PHPUnit.\n"; print 'Use "phpunit --self-update" to install PHPUnit ' . $latestVersion . "\n"; } else { print "You are using the latest version of PHPUnit.\n"; } exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT); } /** * Show the help message. */ protected function showHelp() { $this->printVersionString(); print << Code Coverage Options: --coverage-clover Generate code coverage report in Clover XML format. --coverage-crap4j Generate code coverage report in Crap4J XML format. --coverage-html Generate code coverage report in HTML format. --coverage-php Export PHP_CodeCoverage object to file. --coverage-text= Generate code coverage report in text format. Default: Standard output. --coverage-xml Generate code coverage report in PHPUnit XML format. Logging Options: --log-junit Log test execution in JUnit XML format to file. --log-tap Log test execution in TAP format to file. --log-json Log test execution in JSON format. --testdox-html Write agile documentation in HTML format to file. --testdox-text Write agile documentation in Text format to file. Test Selection Options: --filter Filter which tests to run. --testsuite Filter which testsuite to run. --group ... Only runs tests from the specified group(s). --exclude-group ... Exclude tests from the specified group(s). --list-groups List available test groups. --test-suffix ... Only search for test in files with specified suffix(es). Default: Test.php,.phpt Test Execution Options: --report-useless-tests Be strict about tests that do not test anything. --strict-coverage Be strict about unintentionally covered code. --strict-global-state Be strict about changes to global state --disallow-test-output Be strict about output during tests. --enforce-time-limit Enforce time limit based on test size. --disallow-todo-tests Disallow @todo-annotated tests. --process-isolation Run each test in a separate PHP process. --no-globals-backup Do not backup and restore \$GLOBALS for each test. --static-backup Backup and restore static attributes for each test. --colors= Use colors in output ("never", "auto" or "always"). --columns Number of columns to use for progress output. --columns max Use maximum number of columns for progress output. --stderr Write to STDERR instead of STDOUT. --stop-on-error Stop execution upon first error. --stop-on-failure Stop execution upon first error or failure. --stop-on-risky Stop execution upon first risky test. --stop-on-skipped Stop execution upon first skipped test. --stop-on-incomplete Stop execution upon first incomplete test. -v|--verbose Output more verbose information. --debug Display debugging information during test execution. --loader TestSuiteLoader implementation to use. --repeat Runs the test(s) repeatedly. --tap Report test execution progress in TAP format. --testdox Report test execution progress in TestDox format. --printer TestListener implementation to use. Configuration Options: --bootstrap A "bootstrap" PHP file that is run before the tests. -c|--configuration Read configuration from XML file. --no-configuration Ignore default configuration file (phpunit.xml). --no-coverage Ignore code coverage configuration. --include-path Prepend PHP's include_path with given path(s). -d key[=value] Sets a php.ini value. Miscellaneous Options: -h|--help Prints this usage information. --version Prints the version and exits. EOT; if (defined('__PHPUNIT_PHAR__')) { print "\n --check-version Check whether PHPUnit is the latest version."; print "\n --self-update Update PHPUnit to the latest version.\n"; } } /** * Custom callback for test suite discovery. */ protected function handleCustomTestSuite() { } private function printVersionString() { if ($this->versionStringPrinted) { return; } print PHPUnit_Runner_Version::getVersionString() . "\n\n"; $this->versionStringPrinted = true; } /** */ private function showError($message) { $this->printVersionString(); print $message . "\n"; exit(PHPUnit_TextUI_TestRunner::FAILURE_EXIT); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use SebastianBergmann\Environment\Runtime; /** * A TestRunner for the Command Line Interface (CLI) * PHP SAPI Module. * * @since Class available since Release 2.0.0 */ class PHPUnit_TextUI_TestRunner extends PHPUnit_Runner_BaseTestRunner { const SUCCESS_EXIT = 0; const FAILURE_EXIT = 1; const EXCEPTION_EXIT = 2; /** * @var PHP_CodeCoverage_Filter */ protected $codeCoverageFilter; /** * @var PHPUnit_Runner_TestSuiteLoader */ protected $loader = null; /** * @var PHPUnit_TextUI_ResultPrinter */ protected $printer = null; /** * @var bool */ protected static $versionStringPrinted = false; /** * @var array */ private $missingExtensions = array(); /** * @var Runtime */ private $runtime; /** * @param PHPUnit_Runner_TestSuiteLoader $loader * @param PHP_CodeCoverage_Filter $filter * @since Method available since Release 3.4.0 */ public function __construct(PHPUnit_Runner_TestSuiteLoader $loader = null, PHP_CodeCoverage_Filter $filter = null) { if ($filter === null) { $filter = $this->getCodeCoverageFilter(); } $this->codeCoverageFilter = $filter; $this->loader = $loader; $this->runtime = new Runtime; } /** * @param PHPUnit_Framework_Test|ReflectionClass $test * @param array $arguments * @return PHPUnit_Framework_TestResult * @throws PHPUnit_Framework_Exception */ public static function run($test, array $arguments = array()) { if ($test instanceof ReflectionClass) { $test = new PHPUnit_Framework_TestSuite($test); } if ($test instanceof PHPUnit_Framework_Test) { $aTestRunner = new self; return $aTestRunner->doRun( $test, $arguments ); } else { throw new PHPUnit_Framework_Exception( 'No test case or test suite found.' ); } } /** * @return PHPUnit_Framework_TestResult */ protected function createTestResult() { return new PHPUnit_Framework_TestResult; } private function processSuiteFilters(PHPUnit_Framework_TestSuite $suite, array $arguments) { if (!$arguments['filter'] && empty($arguments['groups']) && empty($arguments['excludeGroups'])) { return; } $filterFactory = new PHPUnit_Runner_Filter_Factory(); if (!empty($arguments['excludeGroups'])) { $filterFactory->addFilter( new ReflectionClass('PHPUnit_Runner_Filter_Group_Exclude'), $arguments['excludeGroups'] ); } if (!empty($arguments['groups'])) { $filterFactory->addFilter( new ReflectionClass('PHPUnit_Runner_Filter_Group_Include'), $arguments['groups'] ); } if ($arguments['filter']) { $filterFactory->addFilter( new ReflectionClass('PHPUnit_Runner_Filter_Test'), $arguments['filter'] ); } $suite->injectFilter($filterFactory); } /** * @param PHPUnit_Framework_Test $suite * @param array $arguments * @return PHPUnit_Framework_TestResult */ public function doRun(PHPUnit_Framework_Test $suite, array $arguments = array()) { $this->handleConfiguration($arguments); $this->processSuiteFilters($suite, $arguments); if (isset($arguments['bootstrap'])) { $GLOBALS['__PHPUNIT_BOOTSTRAP'] = $arguments['bootstrap']; } if ($arguments['backupGlobals'] === false) { $suite->setBackupGlobals(false); } if ($arguments['backupStaticAttributes'] === true) { $suite->setBackupStaticAttributes(true); } if ($arguments['disallowChangesToGlobalState'] === true) { $suite->setDisallowChangesToGlobalState(true); } if (is_integer($arguments['repeat'])) { $test = new PHPUnit_Extensions_RepeatedTest( $suite, $arguments['repeat'], $arguments['processIsolation'] ); $suite = new PHPUnit_Framework_TestSuite(); $suite->addTest($test); } $result = $this->createTestResult(); if (!$arguments['convertErrorsToExceptions']) { $result->convertErrorsToExceptions(false); } if (!$arguments['convertNoticesToExceptions']) { PHPUnit_Framework_Error_Notice::$enabled = false; } if (!$arguments['convertWarningsToExceptions']) { PHPUnit_Framework_Error_Warning::$enabled = false; } if ($arguments['stopOnError']) { $result->stopOnError(true); } if ($arguments['stopOnFailure']) { $result->stopOnFailure(true); } if ($arguments['stopOnIncomplete']) { $result->stopOnIncomplete(true); } if ($arguments['stopOnRisky']) { $result->stopOnRisky(true); } if ($arguments['stopOnSkipped']) { $result->stopOnSkipped(true); } if ($this->printer === null) { if (isset($arguments['printer']) && $arguments['printer'] instanceof PHPUnit_Util_Printer) { $this->printer = $arguments['printer']; } else { $printerClass = 'PHPUnit_TextUI_ResultPrinter'; if (isset($arguments['printer']) && is_string($arguments['printer']) && class_exists($arguments['printer'], false)) { $class = new ReflectionClass($arguments['printer']); if ($class->isSubclassOf('PHPUnit_TextUI_ResultPrinter')) { $printerClass = $arguments['printer']; } } $this->printer = new $printerClass( isset($arguments['stderr']) ? 'php://stderr' : null, $arguments['verbose'], $arguments['colors'], $arguments['debug'], $arguments['columns'] ); } } if (!$this->printer instanceof PHPUnit_Util_Log_TAP) { $this->printer->write( PHPUnit_Runner_Version::getVersionString() . "\n" ); self::$versionStringPrinted = true; if ($arguments['verbose']) { $this->printer->write( sprintf( "\nRuntime:\t%s", $this->runtime->getNameWithVersion() ) ); if ($this->runtime->hasXdebug()) { $this->printer->write( sprintf( ' with Xdebug %s', phpversion('xdebug') ) ); } if (isset($arguments['configuration'])) { $this->printer->write( sprintf( "\nConfiguration:\t%s", $arguments['configuration']->getFilename() ) ); } $this->printer->write("\n"); } if (isset($arguments['deprecatedStrictModeOption'])) { print "Warning:\tDeprecated option \"--strict\" used\n"; } elseif (isset($arguments['deprecatedStrictModeSetting'])) { print "Warning:\tDeprecated configuration setting \"strict\" used\n"; } if (isset($arguments['deprecatedSeleniumConfiguration'])) { print "Warning:\tDeprecated configuration setting \"selenium\" used\n"; } } foreach ($arguments['listeners'] as $listener) { $result->addListener($listener); } $result->addListener($this->printer); if (isset($arguments['testdoxHTMLFile'])) { $result->addListener( new PHPUnit_Util_TestDox_ResultPrinter_HTML( $arguments['testdoxHTMLFile'] ) ); } if (isset($arguments['testdoxTextFile'])) { $result->addListener( new PHPUnit_Util_TestDox_ResultPrinter_Text( $arguments['testdoxTextFile'] ) ); } $codeCoverageReports = 0; if (isset($arguments['coverageClover'])) { $codeCoverageReports++; } if (isset($arguments['coverageCrap4J'])) { $codeCoverageReports++; } if (isset($arguments['coverageHtml'])) { $codeCoverageReports++; } if (isset($arguments['coveragePHP'])) { $codeCoverageReports++; } if (isset($arguments['coverageText'])) { $codeCoverageReports++; } if (isset($arguments['coverageXml'])) { $codeCoverageReports++; } if (isset($arguments['noCoverage'])) { $codeCoverageReports = 0; } if ($codeCoverageReports > 0 && (!extension_loaded('tokenizer') || !$this->runtime->canCollectCodeCoverage())) { if (!extension_loaded('tokenizer')) { $this->showExtensionNotLoadedWarning( 'tokenizer', 'No code coverage will be generated.' ); } elseif (!extension_loaded('Xdebug')) { $this->showExtensionNotLoadedWarning( 'Xdebug', 'No code coverage will be generated.' ); } $codeCoverageReports = 0; } if (!$this->printer instanceof PHPUnit_Util_Log_TAP) { if ($codeCoverageReports > 0 && !$this->codeCoverageFilter->hasWhitelist()) { $this->printer->write("Warning:\tNo whitelist configured for code coverage\n"); } $this->printer->write("\n"); } if ($codeCoverageReports > 0) { $codeCoverage = new PHP_CodeCoverage( null, $this->codeCoverageFilter ); $codeCoverage->setAddUncoveredFilesFromWhitelist( $arguments['addUncoveredFilesFromWhitelist'] ); $codeCoverage->setCheckForUnintentionallyCoveredCode( $arguments['strictCoverage'] ); $codeCoverage->setProcessUncoveredFilesFromWhitelist( $arguments['processUncoveredFilesFromWhitelist'] ); if (isset($arguments['forceCoversAnnotation'])) { $codeCoverage->setForceCoversAnnotation( $arguments['forceCoversAnnotation'] ); } if (isset($arguments['mapTestClassNameToCoveredClassName'])) { $codeCoverage->setMapTestClassNameToCoveredClassName( $arguments['mapTestClassNameToCoveredClassName'] ); } $result->setCodeCoverage($codeCoverage); } if ($codeCoverageReports > 1) { if (isset($arguments['cacheTokens'])) { $codeCoverage->setCacheTokens($arguments['cacheTokens']); } } if (isset($arguments['jsonLogfile'])) { $result->addListener( new PHPUnit_Util_Log_JSON($arguments['jsonLogfile']) ); } if (isset($arguments['tapLogfile'])) { $result->addListener( new PHPUnit_Util_Log_TAP($arguments['tapLogfile']) ); } if (isset($arguments['junitLogfile'])) { $result->addListener( new PHPUnit_Util_Log_JUnit( $arguments['junitLogfile'], $arguments['logIncompleteSkipped'] ) ); } $result->beStrictAboutTestsThatDoNotTestAnything($arguments['reportUselessTests']); $result->beStrictAboutOutputDuringTests($arguments['disallowTestOutput']); $result->beStrictAboutTodoAnnotatedTests($arguments['disallowTodoAnnotatedTests']); $result->beStrictAboutTestSize($arguments['enforceTimeLimit']); $result->setTimeoutForSmallTests($arguments['timeoutForSmallTests']); $result->setTimeoutForMediumTests($arguments['timeoutForMediumTests']); $result->setTimeoutForLargeTests($arguments['timeoutForLargeTests']); if ($suite instanceof PHPUnit_Framework_TestSuite) { $suite->setRunTestInSeparateProcess($arguments['processIsolation']); } $suite->run($result); unset($suite); $result->flushListeners(); if ($this->printer instanceof PHPUnit_TextUI_ResultPrinter) { $this->printer->printResult($result); } if (isset($codeCoverage)) { if (isset($arguments['coverageClover'])) { $this->printer->write( "\nGenerating code coverage report in Clover XML format ..." ); $writer = new PHP_CodeCoverage_Report_Clover; $writer->process($codeCoverage, $arguments['coverageClover']); $this->printer->write(" done\n"); unset($writer); } if (isset($arguments['coverageCrap4J'])) { $this->printer->write( "\nGenerating Crap4J report XML file ..." ); $writer = new PHP_CodeCoverage_Report_Crap4j($arguments['crap4jThreshold']); $writer->process($codeCoverage, $arguments['coverageCrap4J']); $this->printer->write(" done\n"); unset($writer); } if (isset($arguments['coverageHtml'])) { $this->printer->write( "\nGenerating code coverage report in HTML format ..." ); $writer = new PHP_CodeCoverage_Report_HTML( $arguments['reportLowUpperBound'], $arguments['reportHighLowerBound'], sprintf( ' and PHPUnit %s', PHPUnit_Runner_Version::id() ) ); $writer->process($codeCoverage, $arguments['coverageHtml']); $this->printer->write(" done\n"); unset($writer); } if (isset($arguments['coveragePHP'])) { $this->printer->write( "\nGenerating code coverage report in PHP format ..." ); $writer = new PHP_CodeCoverage_Report_PHP; $writer->process($codeCoverage, $arguments['coveragePHP']); $this->printer->write(" done\n"); unset($writer); } if (isset($arguments['coverageText'])) { if ($arguments['coverageText'] == 'php://stdout') { $outputStream = $this->printer; $colors = $arguments['colors']; } else { $outputStream = new PHPUnit_Util_Printer($arguments['coverageText']); $colors = false; } $processor = new PHP_CodeCoverage_Report_Text( $arguments['reportLowUpperBound'], $arguments['reportHighLowerBound'], $arguments['coverageTextShowUncoveredFiles'], $arguments['coverageTextShowOnlySummary'] ); $outputStream->write( $processor->process($codeCoverage, $colors) ); } if (isset($arguments['coverageXml'])) { $this->printer->write( "\nGenerating code coverage report in PHPUnit XML format ..." ); $writer = new PHP_CodeCoverage_Report_XML; $writer->process($codeCoverage, $arguments['coverageXml']); $this->printer->write(" done\n"); unset($writer); } } return $result; } /** * @param PHPUnit_TextUI_ResultPrinter $resultPrinter */ public function setPrinter(PHPUnit_TextUI_ResultPrinter $resultPrinter) { $this->printer = $resultPrinter; } /** * Override to define how to handle a failed loading of * a test suite. * * @param string $message */ protected function runFailed($message) { $this->write($message . PHP_EOL); exit(self::FAILURE_EXIT); } /** * @param string $buffer * @since Method available since Release 3.1.0 */ protected function write($buffer) { if (PHP_SAPI != 'cli') { $buffer = htmlspecialchars($buffer); } if ($this->printer !== null) { $this->printer->write($buffer); } else { print $buffer; } } /** * Returns the loader to be used. * * @return PHPUnit_Runner_TestSuiteLoader * @since Method available since Release 2.2.0 */ public function getLoader() { if ($this->loader === null) { $this->loader = new PHPUnit_Runner_StandardTestSuiteLoader; } return $this->loader; } /** * @param array $arguments * @since Method available since Release 3.2.1 */ protected function handleConfiguration(array &$arguments) { if (isset($arguments['configuration']) && !$arguments['configuration'] instanceof PHPUnit_Util_Configuration) { $arguments['configuration'] = PHPUnit_Util_Configuration::getInstance( $arguments['configuration'] ); } $arguments['debug'] = isset($arguments['debug']) ? $arguments['debug'] : false; $arguments['filter'] = isset($arguments['filter']) ? $arguments['filter'] : false; $arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : array(); if (isset($arguments['configuration'])) { $arguments['configuration']->handlePHPConfiguration(); $phpunitConfiguration = $arguments['configuration']->getPHPUnitConfiguration(); if (isset($phpunitConfiguration['deprecatedStrictModeSetting'])) { $arguments['deprecatedStrictModeSetting'] = true; } if (isset($phpunitConfiguration['backupGlobals']) && !isset($arguments['backupGlobals'])) { $arguments['backupGlobals'] = $phpunitConfiguration['backupGlobals']; } if (isset($phpunitConfiguration['backupStaticAttributes']) && !isset($arguments['backupStaticAttributes'])) { $arguments['backupStaticAttributes'] = $phpunitConfiguration['backupStaticAttributes']; } if (isset($phpunitConfiguration['disallowChangesToGlobalState']) && !isset($arguments['disallowChangesToGlobalState'])) { $arguments['disallowChangesToGlobalState'] = $phpunitConfiguration['disallowChangesToGlobalState']; } if (isset($phpunitConfiguration['bootstrap']) && !isset($arguments['bootstrap'])) { $arguments['bootstrap'] = $phpunitConfiguration['bootstrap']; } if (isset($phpunitConfiguration['cacheTokens']) && !isset($arguments['cacheTokens'])) { $arguments['cacheTokens'] = $phpunitConfiguration['cacheTokens']; } if (isset($phpunitConfiguration['colors']) && !isset($arguments['colors'])) { $arguments['colors'] = $phpunitConfiguration['colors']; } if (isset($phpunitConfiguration['convertErrorsToExceptions']) && !isset($arguments['convertErrorsToExceptions'])) { $arguments['convertErrorsToExceptions'] = $phpunitConfiguration['convertErrorsToExceptions']; } if (isset($phpunitConfiguration['convertNoticesToExceptions']) && !isset($arguments['convertNoticesToExceptions'])) { $arguments['convertNoticesToExceptions'] = $phpunitConfiguration['convertNoticesToExceptions']; } if (isset($phpunitConfiguration['convertWarningsToExceptions']) && !isset($arguments['convertWarningsToExceptions'])) { $arguments['convertWarningsToExceptions'] = $phpunitConfiguration['convertWarningsToExceptions']; } if (isset($phpunitConfiguration['processIsolation']) && !isset($arguments['processIsolation'])) { $arguments['processIsolation'] = $phpunitConfiguration['processIsolation']; } if (isset($phpunitConfiguration['stopOnError']) && !isset($arguments['stopOnError'])) { $arguments['stopOnError'] = $phpunitConfiguration['stopOnError']; } if (isset($phpunitConfiguration['stopOnFailure']) && !isset($arguments['stopOnFailure'])) { $arguments['stopOnFailure'] = $phpunitConfiguration['stopOnFailure']; } if (isset($phpunitConfiguration['stopOnIncomplete']) && !isset($arguments['stopOnIncomplete'])) { $arguments['stopOnIncomplete'] = $phpunitConfiguration['stopOnIncomplete']; } if (isset($phpunitConfiguration['stopOnRisky']) && !isset($arguments['stopOnRisky'])) { $arguments['stopOnRisky'] = $phpunitConfiguration['stopOnRisky']; } if (isset($phpunitConfiguration['stopOnSkipped']) && !isset($arguments['stopOnSkipped'])) { $arguments['stopOnSkipped'] = $phpunitConfiguration['stopOnSkipped']; } if (isset($phpunitConfiguration['timeoutForSmallTests']) && !isset($arguments['timeoutForSmallTests'])) { $arguments['timeoutForSmallTests'] = $phpunitConfiguration['timeoutForSmallTests']; } if (isset($phpunitConfiguration['timeoutForMediumTests']) && !isset($arguments['timeoutForMediumTests'])) { $arguments['timeoutForMediumTests'] = $phpunitConfiguration['timeoutForMediumTests']; } if (isset($phpunitConfiguration['timeoutForLargeTests']) && !isset($arguments['timeoutForLargeTests'])) { $arguments['timeoutForLargeTests'] = $phpunitConfiguration['timeoutForLargeTests']; } if (isset($phpunitConfiguration['reportUselessTests']) && !isset($arguments['reportUselessTests'])) { $arguments['reportUselessTests'] = $phpunitConfiguration['reportUselessTests']; } if (isset($phpunitConfiguration['strictCoverage']) && !isset($arguments['strictCoverage'])) { $arguments['strictCoverage'] = $phpunitConfiguration['strictCoverage']; } if (isset($phpunitConfiguration['disallowTestOutput']) && !isset($arguments['disallowTestOutput'])) { $arguments['disallowTestOutput'] = $phpunitConfiguration['disallowTestOutput']; } if (isset($phpunitConfiguration['enforceTimeLimit']) && !isset($arguments['enforceTimeLimit'])) { $arguments['enforceTimeLimit'] = $phpunitConfiguration['enforceTimeLimit']; } if (isset($phpunitConfiguration['disallowTodoAnnotatedTests']) && !isset($arguments['disallowTodoAnnotatedTests'])) { $arguments['disallowTodoAnnotatedTests'] = $phpunitConfiguration['disallowTodoAnnotatedTests']; } if (isset($phpunitConfiguration['verbose']) && !isset($arguments['verbose'])) { $arguments['verbose'] = $phpunitConfiguration['verbose']; } if (isset($phpunitConfiguration['forceCoversAnnotation']) && !isset($arguments['forceCoversAnnotation'])) { $arguments['forceCoversAnnotation'] = $phpunitConfiguration['forceCoversAnnotation']; } if (isset($phpunitConfiguration['mapTestClassNameToCoveredClassName']) && !isset($arguments['mapTestClassNameToCoveredClassName'])) { $arguments['mapTestClassNameToCoveredClassName'] = $phpunitConfiguration['mapTestClassNameToCoveredClassName']; } $groupCliArgs = array(); if (!empty($arguments['groups'])) { $groupCliArgs = $arguments['groups']; } $groupConfiguration = $arguments['configuration']->getGroupConfiguration(); if (!empty($groupConfiguration['include']) && !isset($arguments['groups'])) { $arguments['groups'] = $groupConfiguration['include']; } if (!empty($groupConfiguration['exclude']) && !isset($arguments['excludeGroups'])) { $arguments['excludeGroups'] = array_diff($groupConfiguration['exclude'], $groupCliArgs); } foreach ($arguments['configuration']->getListenerConfiguration() as $listener) { if (!class_exists($listener['class'], false) && $listener['file'] !== '') { require_once $listener['file']; } if (class_exists($listener['class'])) { if (count($listener['arguments']) == 0) { $listener = new $listener['class']; } else { $listenerClass = new ReflectionClass( $listener['class'] ); $listener = $listenerClass->newInstanceArgs( $listener['arguments'] ); } if ($listener instanceof PHPUnit_Framework_TestListener) { $arguments['listeners'][] = $listener; } } } $loggingConfiguration = $arguments['configuration']->getLoggingConfiguration(); if (isset($loggingConfiguration['coverage-clover']) && !isset($arguments['coverageClover'])) { $arguments['coverageClover'] = $loggingConfiguration['coverage-clover']; } if (isset($loggingConfiguration['coverage-crap4j']) && !isset($arguments['coverageCrap4J'])) { $arguments['coverageCrap4J'] = $loggingConfiguration['coverage-crap4j']; if (isset($loggingConfiguration['crap4jThreshold']) && !isset($arguments['crap4jThreshold'])) { $arguments['crap4jThreshold'] = $loggingConfiguration['crap4jThreshold']; } } if (isset($loggingConfiguration['coverage-html']) && !isset($arguments['coverageHtml'])) { if (isset($loggingConfiguration['lowUpperBound']) && !isset($arguments['reportLowUpperBound'])) { $arguments['reportLowUpperBound'] = $loggingConfiguration['lowUpperBound']; } if (isset($loggingConfiguration['highLowerBound']) && !isset($arguments['reportHighLowerBound'])) { $arguments['reportHighLowerBound'] = $loggingConfiguration['highLowerBound']; } $arguments['coverageHtml'] = $loggingConfiguration['coverage-html']; } if (isset($loggingConfiguration['coverage-php']) && !isset($arguments['coveragePHP'])) { $arguments['coveragePHP'] = $loggingConfiguration['coverage-php']; } if (isset($loggingConfiguration['coverage-text']) && !isset($arguments['coverageText'])) { $arguments['coverageText'] = $loggingConfiguration['coverage-text']; if (isset($loggingConfiguration['coverageTextShowUncoveredFiles'])) { $arguments['coverageTextShowUncoveredFiles'] = $loggingConfiguration['coverageTextShowUncoveredFiles']; } else { $arguments['coverageTextShowUncoveredFiles'] = false; } if (isset($loggingConfiguration['coverageTextShowOnlySummary'])) { $arguments['coverageTextShowOnlySummary'] = $loggingConfiguration['coverageTextShowOnlySummary']; } else { $arguments['coverageTextShowOnlySummary'] = false; } } if (isset($loggingConfiguration['coverage-xml']) && !isset($arguments['coverageXml'])) { $arguments['coverageXml'] = $loggingConfiguration['coverage-xml']; } if (isset($loggingConfiguration['json']) && !isset($arguments['jsonLogfile'])) { $arguments['jsonLogfile'] = $loggingConfiguration['json']; } if (isset($loggingConfiguration['plain'])) { $arguments['listeners'][] = new PHPUnit_TextUI_ResultPrinter( $loggingConfiguration['plain'], true ); } if (isset($loggingConfiguration['tap']) && !isset($arguments['tapLogfile'])) { $arguments['tapLogfile'] = $loggingConfiguration['tap']; } if (isset($loggingConfiguration['junit']) && !isset($arguments['junitLogfile'])) { $arguments['junitLogfile'] = $loggingConfiguration['junit']; if (isset($loggingConfiguration['logIncompleteSkipped']) && !isset($arguments['logIncompleteSkipped'])) { $arguments['logIncompleteSkipped'] = $loggingConfiguration['logIncompleteSkipped']; } } if (isset($loggingConfiguration['testdox-html']) && !isset($arguments['testdoxHTMLFile'])) { $arguments['testdoxHTMLFile'] = $loggingConfiguration['testdox-html']; } if (isset($loggingConfiguration['testdox-text']) && !isset($arguments['testdoxTextFile'])) { $arguments['testdoxTextFile'] = $loggingConfiguration['testdox-text']; } if ((isset($arguments['coverageClover']) || isset($arguments['coverageCrap4J']) || isset($arguments['coverageHtml']) || isset($arguments['coveragePHP']) || isset($arguments['coverageText']) || isset($arguments['coverageXml'])) && $this->runtime->canCollectCodeCoverage()) { $filterConfiguration = $arguments['configuration']->getFilterConfiguration(); $arguments['addUncoveredFilesFromWhitelist'] = $filterConfiguration['whitelist']['addUncoveredFilesFromWhitelist']; $arguments['processUncoveredFilesFromWhitelist'] = $filterConfiguration['whitelist']['processUncoveredFilesFromWhitelist']; if (empty($filterConfiguration['whitelist']['include']['directory']) && empty($filterConfiguration['whitelist']['include']['file'])) { foreach ($filterConfiguration['blacklist']['include']['directory'] as $dir) { $this->codeCoverageFilter->addDirectoryToBlacklist( $dir['path'], $dir['suffix'], $dir['prefix'], $dir['group'] ); } foreach ($filterConfiguration['blacklist']['include']['file'] as $file) { $this->codeCoverageFilter->addFileToBlacklist($file); } foreach ($filterConfiguration['blacklist']['exclude']['directory'] as $dir) { $this->codeCoverageFilter->removeDirectoryFromBlacklist( $dir['path'], $dir['suffix'], $dir['prefix'], $dir['group'] ); } foreach ($filterConfiguration['blacklist']['exclude']['file'] as $file) { $this->codeCoverageFilter->removeFileFromBlacklist($file); } } foreach ($filterConfiguration['whitelist']['include']['directory'] as $dir) { $this->codeCoverageFilter->addDirectoryToWhitelist( $dir['path'], $dir['suffix'], $dir['prefix'] ); } foreach ($filterConfiguration['whitelist']['include']['file'] as $file) { $this->codeCoverageFilter->addFileToWhitelist($file); } foreach ($filterConfiguration['whitelist']['exclude']['directory'] as $dir) { $this->codeCoverageFilter->removeDirectoryFromWhitelist( $dir['path'], $dir['suffix'], $dir['prefix'] ); } foreach ($filterConfiguration['whitelist']['exclude']['file'] as $file) { $this->codeCoverageFilter->removeFileFromWhitelist($file); } } } $arguments['addUncoveredFilesFromWhitelist'] = isset($arguments['addUncoveredFilesFromWhitelist']) ? $arguments['addUncoveredFilesFromWhitelist'] : true; $arguments['processUncoveredFilesFromWhitelist'] = isset($arguments['processUncoveredFilesFromWhitelist']) ? $arguments['processUncoveredFilesFromWhitelist'] : false; $arguments['backupGlobals'] = isset($arguments['backupGlobals']) ? $arguments['backupGlobals'] : null; $arguments['backupStaticAttributes'] = isset($arguments['backupStaticAttributes']) ? $arguments['backupStaticAttributes'] : null; $arguments['disallowChangesToGlobalState'] = isset($arguments['disallowChangesToGlobalState']) ? $arguments['disallowChangesToGlobalState'] : null; $arguments['cacheTokens'] = isset($arguments['cacheTokens']) ? $arguments['cacheTokens'] : false; $arguments['columns'] = isset($arguments['columns']) ? $arguments['columns'] : 80; $arguments['colors'] = isset($arguments['colors']) ? $arguments['colors'] : PHPUnit_TextUI_ResultPrinter::COLOR_DEFAULT; $arguments['convertErrorsToExceptions'] = isset($arguments['convertErrorsToExceptions']) ? $arguments['convertErrorsToExceptions'] : true; $arguments['convertNoticesToExceptions'] = isset($arguments['convertNoticesToExceptions']) ? $arguments['convertNoticesToExceptions'] : true; $arguments['convertWarningsToExceptions'] = isset($arguments['convertWarningsToExceptions']) ? $arguments['convertWarningsToExceptions'] : true; $arguments['excludeGroups'] = isset($arguments['excludeGroups']) ? $arguments['excludeGroups'] : array(); $arguments['groups'] = isset($arguments['groups']) ? $arguments['groups'] : array(); $arguments['logIncompleteSkipped'] = isset($arguments['logIncompleteSkipped']) ? $arguments['logIncompleteSkipped'] : false; $arguments['processIsolation'] = isset($arguments['processIsolation']) ? $arguments['processIsolation'] : false; $arguments['repeat'] = isset($arguments['repeat']) ? $arguments['repeat'] : false; $arguments['reportHighLowerBound'] = isset($arguments['reportHighLowerBound']) ? $arguments['reportHighLowerBound'] : 90; $arguments['reportLowUpperBound'] = isset($arguments['reportLowUpperBound']) ? $arguments['reportLowUpperBound'] : 50; $arguments['crap4jThreshold'] = isset($arguments['crap4jThreshold']) ? $arguments['crap4jThreshold'] : 30; $arguments['stopOnError'] = isset($arguments['stopOnError']) ? $arguments['stopOnError'] : false; $arguments['stopOnFailure'] = isset($arguments['stopOnFailure']) ? $arguments['stopOnFailure'] : false; $arguments['stopOnIncomplete'] = isset($arguments['stopOnIncomplete']) ? $arguments['stopOnIncomplete'] : false; $arguments['stopOnRisky'] = isset($arguments['stopOnRisky']) ? $arguments['stopOnRisky'] : false; $arguments['stopOnSkipped'] = isset($arguments['stopOnSkipped']) ? $arguments['stopOnSkipped'] : false; $arguments['timeoutForSmallTests'] = isset($arguments['timeoutForSmallTests']) ? $arguments['timeoutForSmallTests'] : 1; $arguments['timeoutForMediumTests'] = isset($arguments['timeoutForMediumTests']) ? $arguments['timeoutForMediumTests'] : 10; $arguments['timeoutForLargeTests'] = isset($arguments['timeoutForLargeTests']) ? $arguments['timeoutForLargeTests'] : 60; $arguments['reportUselessTests'] = isset($arguments['reportUselessTests']) ? $arguments['reportUselessTests'] : false; $arguments['strictCoverage'] = isset($arguments['strictCoverage']) ? $arguments['strictCoverage'] : false; $arguments['disallowTestOutput'] = isset($arguments['disallowTestOutput']) ? $arguments['disallowTestOutput'] : false; $arguments['enforceTimeLimit'] = isset($arguments['enforceTimeLimit']) ? $arguments['enforceTimeLimit'] : false; $arguments['disallowTodoAnnotatedTests'] = isset($arguments['disallowTodoAnnotatedTests']) ? $arguments['disallowTodoAnnotatedTests'] : false; $arguments['verbose'] = isset($arguments['verbose']) ? $arguments['verbose'] : false; } /** * @param $extension * @param string $message * @since Method available since Release 4.7.3 */ private function showExtensionNotLoadedWarning($extension, $message = '') { if (isset($this->missingExtensions[$extension])) { return; } $this->write("Warning:\t" . 'The ' . $extension . ' extension is not loaded' . "\n"); if (!empty($message)) { $this->write("\t\t" . $message . "\n"); } $this->missingExtensions[$extension] = true; } /** * @return PHP_CodeCoverage_Filter */ private function getCodeCoverageFilter() { $filter = new PHP_CodeCoverage_Filter; if (defined('__PHPUNIT_PHAR__')) { $filter->addFileToBlacklist(__PHPUNIT_PHAR__); } $blacklist = new PHPUnit_Util_Blacklist; foreach ($blacklist->getBlacklistedDirectories() as $directory) { $filter->addDirectoryToBlacklist($directory); } return $filter; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use SebastianBergmann\Environment\Console; /** * Prints the result of a TextUI TestRunner run. * * @since Class available since Release 2.0.0 */ class PHPUnit_TextUI_ResultPrinter extends PHPUnit_Util_Printer implements PHPUnit_Framework_TestListener { const EVENT_TEST_START = 0; const EVENT_TEST_END = 1; const EVENT_TESTSUITE_START = 2; const EVENT_TESTSUITE_END = 3; const COLOR_NEVER = 'never'; const COLOR_AUTO = 'auto'; const COLOR_ALWAYS = 'always'; const COLOR_DEFAULT = self::COLOR_NEVER; /** * @var array */ private static $ansiCodes = array( 'bold' => 1, 'fg-black' => 30, 'fg-red' => 31, 'fg-green' => 32, 'fg-yellow' => 33, 'fg-blue' => 34, 'fg-magenta' => 35, 'fg-cyan' => 36, 'fg-white' => 37, 'bg-black' => 40, 'bg-red' => 41, 'bg-green' => 42, 'bg-yellow' => 43, 'bg-blue' => 44, 'bg-magenta' => 45, 'bg-cyan' => 46, 'bg-white' => 47 ); /** * @var int */ protected $column = 0; /** * @var int */ protected $maxColumn; /** * @var bool */ protected $lastTestFailed = false; /** * @var int */ protected $numAssertions = 0; /** * @var int */ protected $numTests = -1; /** * @var int */ protected $numTestsRun = 0; /** * @var int */ protected $numTestsWidth; /** * @var bool */ protected $colors = false; /** * @var bool */ protected $debug = false; /** * @var bool */ protected $verbose = false; /** * @var int */ private $numberOfColumns; /** * Constructor. * * @param mixed $out * @param bool $verbose * @param string $colors * @param bool $debug * @param int|string $numberOfColumns * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.0.0 */ public function __construct($out = null, $verbose = false, $colors = self::COLOR_DEFAULT, $debug = false, $numberOfColumns = 80) { parent::__construct($out); if (!is_bool($verbose)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'boolean'); } $availableColors = array(self::COLOR_NEVER, self::COLOR_AUTO, self::COLOR_ALWAYS); if (!in_array($colors, $availableColors)) { throw PHPUnit_Util_InvalidArgumentHelper::factory( 3, vsprintf('value from "%s", "%s" or "%s"', $availableColors) ); } if (!is_bool($debug)) { throw PHPUnit_Util_InvalidArgumentHelper::factory(4, 'boolean'); } if (!is_int($numberOfColumns) && $numberOfColumns != 'max') { throw PHPUnit_Util_InvalidArgumentHelper::factory(5, 'integer or "max"'); } $console = new Console; $maxNumberOfColumns = $console->getNumberOfColumns(); if ($numberOfColumns == 'max' || $numberOfColumns > $maxNumberOfColumns) { $numberOfColumns = $maxNumberOfColumns; } $this->numberOfColumns = $numberOfColumns; $this->verbose = $verbose; $this->debug = $debug; if ($colors === self::COLOR_AUTO && $console->hasColorSupport()) { $this->colors = true; } else { $this->colors = (self::COLOR_ALWAYS === $colors); } } /** * @param PHPUnit_Framework_TestResult $result */ public function printResult(PHPUnit_Framework_TestResult $result) { $this->printHeader(); $this->printErrors($result); $printSeparator = $result->errorCount() > 0; if ($printSeparator && $result->failureCount() > 0) { $this->write("\n--\n\n"); } $printSeparator = $printSeparator || $result->failureCount() > 0; $this->printFailures($result); if ($this->verbose) { if ($printSeparator && $result->riskyCount() > 0) { $this->write("\n--\n\n"); } $printSeparator = $printSeparator || $result->riskyCount() > 0; $this->printRisky($result); if ($printSeparator && $result->notImplementedCount() > 0) { $this->write("\n--\n\n"); } $printSeparator = $printSeparator || $result->notImplementedCount() > 0; $this->printIncompletes($result); if ($printSeparator && $result->skippedCount() > 0) { $this->write("\n--\n\n"); } $this->printSkipped($result); } $this->printFooter($result); } /** * @param array $defects * @param string $type */ protected function printDefects(array $defects, $type) { $count = count($defects); if ($count == 0) { return; } $this->write( sprintf( "There %s %d %s%s:\n", ($count == 1) ? 'was' : 'were', $count, $type, ($count == 1) ? '' : 's' ) ); $i = 1; foreach ($defects as $defect) { $this->printDefect($defect, $i++); } } /** * @param PHPUnit_Framework_TestFailure $defect * @param int $count */ protected function printDefect(PHPUnit_Framework_TestFailure $defect, $count) { $this->printDefectHeader($defect, $count); $this->printDefectTrace($defect); } /** * @param PHPUnit_Framework_TestFailure $defect * @param int $count */ protected function printDefectHeader(PHPUnit_Framework_TestFailure $defect, $count) { $this->write( sprintf( "\n%d) %s\n", $count, $defect->getTestName() ) ); } /** * @param PHPUnit_Framework_TestFailure $defect */ protected function printDefectTrace(PHPUnit_Framework_TestFailure $defect) { $e = $defect->thrownException(); $this->write((string) $e); while ($e = $e->getPrevious()) { $this->write("\nCaused by\n" . $e); } } /** * @param PHPUnit_Framework_TestResult $result */ protected function printErrors(PHPUnit_Framework_TestResult $result) { $this->printDefects($result->errors(), 'error'); } /** * @param PHPUnit_Framework_TestResult $result */ protected function printFailures(PHPUnit_Framework_TestResult $result) { $this->printDefects($result->failures(), 'failure'); } /** * @param PHPUnit_Framework_TestResult $result */ protected function printIncompletes(PHPUnit_Framework_TestResult $result) { $this->printDefects($result->notImplemented(), 'incomplete test'); } /** * @param PHPUnit_Framework_TestResult $result * @since Method available since Release 4.0.0 */ protected function printRisky(PHPUnit_Framework_TestResult $result) { $this->printDefects($result->risky(), 'risky test'); } /** * @param PHPUnit_Framework_TestResult $result * @since Method available since Release 3.0.0 */ protected function printSkipped(PHPUnit_Framework_TestResult $result) { $this->printDefects($result->skipped(), 'skipped test'); } protected function printHeader() { $this->write("\n\n" . PHP_Timer::resourceUsage() . "\n\n"); } /** * @param PHPUnit_Framework_TestResult $result */ protected function printFooter(PHPUnit_Framework_TestResult $result) { if (count($result) === 0) { $this->writeWithColor( 'fg-black, bg-yellow', 'No tests executed!' ); } elseif ($result->wasSuccessful() && $result->allHarmless() && $result->allCompletelyImplemented() && $result->noneSkipped()) { $this->writeWithColor( 'fg-black, bg-green', sprintf( 'OK (%d test%s, %d assertion%s)', count($result), (count($result) == 1) ? '' : 's', $this->numAssertions, ($this->numAssertions == 1) ? '' : 's' ) ); } else { if ($result->wasSuccessful()) { $color = 'fg-black, bg-yellow'; if ($this->verbose) { $this->write("\n"); } $this->writeWithColor( $color, 'OK, but incomplete, skipped, or risky tests!' ); } else { $color = 'fg-white, bg-red'; $this->write("\n"); $this->writeWithColor($color, 'FAILURES!'); } $this->writeCountString(count($result), 'Tests', $color, true); $this->writeCountString($this->numAssertions, 'Assertions', $color, true); $this->writeCountString($result->errorCount(), 'Errors', $color); $this->writeCountString($result->failureCount(), 'Failures', $color); $this->writeCountString($result->skippedCount(), 'Skipped', $color); $this->writeCountString($result->notImplementedCount(), 'Incomplete', $color); $this->writeCountString($result->riskyCount(), 'Risky', $color); $this->writeWithColor($color, '.', true); } } /** */ public function printWaitPrompt() { $this->write("\n to continue\n"); } /** * An error occurred. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->writeProgressWithColor('fg-red, bold', 'E'); $this->lastTestFailed = true; } /** * A failure occurred. * * @param PHPUnit_Framework_Test $test * @param PHPUnit_Framework_AssertionFailedError $e * @param float $time */ public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { $this->writeProgressWithColor('bg-red, fg-white', 'F'); $this->lastTestFailed = true; } /** * Incomplete test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->writeProgressWithColor('fg-yellow, bold', 'I'); $this->lastTestFailed = true; } /** * Risky test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time * @since Method available since Release 4.0.0 */ public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->writeProgressWithColor('fg-yellow, bold', 'R'); $this->lastTestFailed = true; } /** * Skipped test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time * @since Method available since Release 3.0.0 */ public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->writeProgressWithColor('fg-cyan, bold', 'S'); $this->lastTestFailed = true; } /** * A testsuite started. * * @param PHPUnit_Framework_TestSuite $suite * @since Method available since Release 2.2.0 */ public function startTestSuite(PHPUnit_Framework_TestSuite $suite) { if ($this->numTests == -1) { $this->numTests = count($suite); $this->numTestsWidth = strlen((string) $this->numTests); $this->maxColumn = $this->numberOfColumns - strlen(' / (XXX%)') - (2 * $this->numTestsWidth); } } /** * A testsuite ended. * * @param PHPUnit_Framework_TestSuite $suite * @since Method available since Release 2.2.0 */ public function endTestSuite(PHPUnit_Framework_TestSuite $suite) { } /** * A test started. * * @param PHPUnit_Framework_Test $test */ public function startTest(PHPUnit_Framework_Test $test) { if ($this->debug) { $this->write( sprintf( "\nStarting test '%s'.\n", PHPUnit_Util_Test::describe($test) ) ); } } /** * A test ended. * * @param PHPUnit_Framework_Test $test * @param float $time */ public function endTest(PHPUnit_Framework_Test $test, $time) { if (!$this->lastTestFailed) { $this->writeProgress('.'); } if ($test instanceof PHPUnit_Framework_TestCase) { $this->numAssertions += $test->getNumAssertions(); } elseif ($test instanceof PHPUnit_Extensions_PhptTestCase) { $this->numAssertions++; } $this->lastTestFailed = false; if ($test instanceof PHPUnit_Framework_TestCase) { if (!$test->hasExpectationOnOutput()) { $this->write($test->getActualOutput()); } } } /** * @param string $progress */ protected function writeProgress($progress) { $this->write($progress); $this->column++; $this->numTestsRun++; if ($this->column == $this->maxColumn) { $this->write( sprintf( ' %' . $this->numTestsWidth . 'd / %' . $this->numTestsWidth . 'd (%3s%%)', $this->numTestsRun, $this->numTests, floor(($this->numTestsRun / $this->numTests) * 100) ) ); $this->writeNewLine(); } } protected function writeNewLine() { $this->column = 0; $this->write("\n"); } /** * Formats a buffer with a specified ANSI color sequence if colors are * enabled. * * @param string $color * @param string $buffer * @return string * @since Method available since Release 4.0.0 */ protected function formatWithColor($color, $buffer) { if (!$this->colors) { return $buffer; } $codes = array_map('trim', explode(',', $color)); $lines = explode("\n", $buffer); $padding = max(array_map('strlen', $lines)); $styles = array(); foreach ($codes as $code) { $styles[] = self::$ansiCodes[$code]; } $style = sprintf("\x1b[%sm", implode(';', $styles)); $styledLines = array(); foreach ($lines as $line) { $styledLines[] = $style . str_pad($line, $padding) . "\x1b[0m"; } return implode("\n", $styledLines); } /** * Writes a buffer out with a color sequence if colors are enabled. * * @param string $color * @param string $buffer * @param bool $lf * @since Method available since Release 4.0.0 */ protected function writeWithColor($color, $buffer, $lf = true) { $this->write($this->formatWithColor($color, $buffer)); if ($lf) { $this->write("\n"); } } /** * Writes progress with a color sequence if colors are enabled. * * @param string $color * @param string $buffer * @since Method available since Release 4.0.0 */ protected function writeProgressWithColor($color, $buffer) { $buffer = $this->formatWithColor($color, $buffer); $this->writeProgress($buffer); } /** * @param int $count * @param string $name * @param string $color * @param bool $always * @since Method available since Release 4.6.5 */ private function writeCountString($count, $name, $color, $always = false) { static $first = true; if ($always || $count > 0) { $this->writeWithColor( $color, sprintf( '%s%s: %d', !$first ? ', ' : '', $name, $count ), false ); $first = false; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if (!function_exists('trait_exists')) { function trait_exists($traitname, $autoload = true) { return false; } } /** * Test helpers. * * @since Class available since Release 3.0.0 */ class PHPUnit_Util_Test { const REGEX_DATA_PROVIDER = '/@dataProvider\s+([a-zA-Z0-9._:-\\\\x7f-\xff]+)/'; const REGEX_TEST_WITH = '/@testWith\s+/'; const REGEX_EXPECTED_EXCEPTION = '(@expectedException\s+([:.\w\\\\x7f-\xff]+)(?:[\t ]+(\S*))?(?:[\t ]+(\S*))?\s*$)m'; const REGEX_REQUIRES_VERSION = '/@requires\s+(?PPHP(?:Unit)?)\s+(?P[\d\.-]+(dev|(RC|alpha|beta)[\d\.])?)[ \t]*\r?$/m'; const REGEX_REQUIRES_OS = '/@requires\s+OS\s+(?P.+?)[ \t]*\r?$/m'; const REGEX_REQUIRES = '/@requires\s+(?Pfunction|extension)\s+(?P([^ ]+?))[ \t]*\r?$/m'; const UNKNOWN = -1; const SMALL = 0; const MEDIUM = 1; const LARGE = 2; private static $annotationCache = array(); private static $hookMethods = array(); /** * @param PHPUnit_Framework_Test $test * @param bool $asString * @return mixed */ public static function describe(PHPUnit_Framework_Test $test, $asString = true) { if ($asString) { if ($test instanceof PHPUnit_Framework_SelfDescribing) { return $test->toString(); } else { return get_class($test); } } else { if ($test instanceof PHPUnit_Framework_TestCase) { return array( get_class($test), $test->getName() ); } elseif ($test instanceof PHPUnit_Framework_SelfDescribing) { return array('', $test->toString()); } else { return array('', get_class($test)); } } } /** * @param string $className * @param string $methodName * @return array|bool * @throws PHPUnit_Framework_CodeCoverageException * @since Method available since Release 4.0.0 */ public static function getLinesToBeCovered($className, $methodName) { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); if (isset($annotations['class']['coversNothing']) || isset($annotations['method']['coversNothing'])) { return false; } return self::getLinesToBeCoveredOrUsed($className, $methodName, 'covers'); } /** * Returns lines of code specified with the @uses annotation. * * @param string $className * @param string $methodName * @return array * @since Method available since Release 4.0.0 */ public static function getLinesToBeUsed($className, $methodName) { return self::getLinesToBeCoveredOrUsed($className, $methodName, 'uses'); } /** * @param string $className * @param string $methodName * @param string $mode * @return array * @throws PHPUnit_Framework_CodeCoverageException * @since Method available since Release 4.2.0 */ private static function getLinesToBeCoveredOrUsed($className, $methodName, $mode) { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); $classShortcut = null; if (!empty($annotations['class'][$mode . 'DefaultClass'])) { if (count($annotations['class'][$mode . 'DefaultClass']) > 1) { throw new PHPUnit_Framework_CodeCoverageException( sprintf( 'More than one @%sClass annotation in class or interface "%s".', $mode, $className ) ); } $classShortcut = $annotations['class'][$mode . 'DefaultClass'][0]; } $list = array(); if (isset($annotations['class'][$mode])) { $list = $annotations['class'][$mode]; } if (isset($annotations['method'][$mode])) { $list = array_merge($list, $annotations['method'][$mode]); } $codeList = array(); foreach (array_unique($list) as $element) { if ($classShortcut && strncmp($element, '::', 2) === 0) { $element = $classShortcut . $element; } $element = preg_replace('/[\s()]+$/', '', $element); $codeList = array_merge( $codeList, self::resolveElementToReflectionObjects($element) ); } return self::resolveReflectionObjectsToLines($codeList); } /** * Returns the requirements for a test. * * @param string $className * @param string $methodName * @return array * @since Method available since Release 3.6.0 */ public static function getRequirements($className, $methodName) { $reflector = new ReflectionClass($className); $docComment = $reflector->getDocComment(); $reflector = new ReflectionMethod($className, $methodName); $docComment .= "\n" . $reflector->getDocComment(); $requires = array(); if ($count = preg_match_all(self::REGEX_REQUIRES_OS, $docComment, $matches)) { $requires['OS'] = sprintf( '/%s/i', addcslashes($matches['value'][$count - 1], '/') ); } if ($count = preg_match_all(self::REGEX_REQUIRES_VERSION, $docComment, $matches)) { for ($i = 0; $i < $count; $i++) { $requires[$matches['name'][$i]] = $matches['value'][$i]; } } // https://bugs.php.net/bug.php?id=63055 $matches = array(); if ($count = preg_match_all(self::REGEX_REQUIRES, $docComment, $matches)) { for ($i = 0; $i < $count; $i++) { $name = $matches['name'][$i] . 's'; if (!isset($requires[$name])) { $requires[$name] = array(); } $requires[$name][] = $matches['value'][$i]; } } return $requires; } /** * Returns the missing requirements for a test. * * @param string $className * @param string $methodName * @return array * @since Method available since Release 4.3.0 */ public static function getMissingRequirements($className, $methodName) { $required = static::getRequirements($className, $methodName); $missing = array(); if (!empty($required['PHP']) && version_compare(PHP_VERSION, $required['PHP'], '<')) { $missing[] = sprintf('PHP %s (or later) is required.', $required['PHP']); } if (!empty($required['PHPUnit'])) { $phpunitVersion = PHPUnit_Runner_Version::id(); if (version_compare($phpunitVersion, $required['PHPUnit'], '<')) { $missing[] = sprintf('PHPUnit %s (or later) is required.', $required['PHPUnit']); } } if (!empty($required['OS']) && !preg_match($required['OS'], PHP_OS)) { $missing[] = sprintf('Operating system matching %s is required.', $required['OS']); } if (!empty($required['functions'])) { foreach ($required['functions'] as $function) { $pieces = explode('::', $function); if (2 === count($pieces) && method_exists($pieces[0], $pieces[1])) { continue; } if (function_exists($function)) { continue; } $missing[] = sprintf('Function %s is required.', $function); } } if (!empty($required['extensions'])) { foreach ($required['extensions'] as $extension) { if (!extension_loaded($extension)) { $missing[] = sprintf('Extension %s is required.', $extension); } } } return $missing; } /** * Returns the expected exception for a test. * * @param string $className * @param string $methodName * @return array * @since Method available since Release 3.3.6 */ public static function getExpectedException($className, $methodName) { $reflector = new ReflectionMethod($className, $methodName); $docComment = $reflector->getDocComment(); $docComment = substr($docComment, 3, -2); if (preg_match(self::REGEX_EXPECTED_EXCEPTION, $docComment, $matches)) { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); $class = $matches[1]; $code = null; $message = ''; $messageRegExp = ''; if (isset($matches[2])) { $message = trim($matches[2]); } elseif (isset($annotations['method']['expectedExceptionMessage'])) { $message = self::parseAnnotationContent( $annotations['method']['expectedExceptionMessage'][0] ); } if (isset($annotations['method']['expectedExceptionMessageRegExp'])) { $messageRegExp = self::parseAnnotationContent( $annotations['method']['expectedExceptionMessageRegExp'][0] ); } if (isset($matches[3])) { $code = $matches[3]; } elseif (isset($annotations['method']['expectedExceptionCode'])) { $code = self::parseAnnotationContent( $annotations['method']['expectedExceptionCode'][0] ); } if (is_numeric($code)) { $code = (int) $code; } elseif (is_string($code) && defined($code)) { $code = (int) constant($code); } return array( 'class' => $class, 'code' => $code, 'message' => $message, 'message_regex' => $messageRegExp ); } return false; } /** * Parse annotation content to use constant/class constant values * * Constants are specified using a starting '@'. For example: @ClassName::CONST_NAME * * If the constant is not found the string is used as is to ensure maximum BC. * * @param string $message * @return string */ private static function parseAnnotationContent($message) { if (strpos($message, '::') !== false && count(explode('::', $message) == 2)) { if (defined($message)) { $message = constant($message); } } return $message; } /** * Returns the provided data for a method. * * @param string $className * @param string $methodName * @return array|Iterator when a data provider is specified and exists * null when no data provider is specified * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.2.0 */ public static function getProvidedData($className, $methodName) { $reflector = new ReflectionMethod($className, $methodName); $docComment = $reflector->getDocComment(); $data = null; if ($dataProviderData = self::getDataFromDataProviderAnnotation($docComment, $className, $methodName)) { $data = $dataProviderData; } if ($testWithData = self::getDataFromTestWithAnnotation($docComment)) { $data = $testWithData; } if ($data !== null) { if (is_object($data)) { $data = iterator_to_array($data); } foreach ($data as $key => $value) { if (!is_array($value)) { throw new PHPUnit_Framework_Exception( sprintf( 'Data set %s is invalid.', is_int($key) ? '#' . $key : '"' . $key . '"' ) ); } } } return $data; } /** * Returns the provided data for a method. * * @param string $docComment * @param string $className * @param string $methodName * @return array|Iterator when a data provider is specified and exists * null when no data provider is specified * @throws PHPUnit_Framework_Exception */ private static function getDataFromDataProviderAnnotation($docComment, $className, $methodName) { if (preg_match(self::REGEX_DATA_PROVIDER, $docComment, $matches)) { $dataProviderMethodNameNamespace = explode('\\', $matches[1]); $leaf = explode('::', array_pop($dataProviderMethodNameNamespace)); $dataProviderMethodName = array_pop($leaf); if (!empty($dataProviderMethodNameNamespace)) { $dataProviderMethodNameNamespace = implode('\\', $dataProviderMethodNameNamespace) . '\\'; } else { $dataProviderMethodNameNamespace = ''; } if (!empty($leaf)) { $dataProviderClassName = $dataProviderMethodNameNamespace . array_pop($leaf); } else { $dataProviderClassName = $className; } $dataProviderClass = new ReflectionClass($dataProviderClassName); $dataProviderMethod = $dataProviderClass->getMethod( $dataProviderMethodName ); if ($dataProviderMethod->isStatic()) { $object = null; } else { $object = $dataProviderClass->newInstance(); } if ($dataProviderMethod->getNumberOfParameters() == 0) { $data = $dataProviderMethod->invoke($object); } else { $data = $dataProviderMethod->invoke($object, $methodName); } return $data; } } /** * @param string $docComment full docComment string * @return array when @testWith annotation is defined * null when @testWith annotation is omitted * @throws PHPUnit_Framework_Exception when @testWith annotation is defined but cannot be parsed */ public static function getDataFromTestWithAnnotation($docComment) { $docComment = self::cleanUpMultiLineAnnotation($docComment); if (preg_match(self::REGEX_TEST_WITH, $docComment, $matches, PREG_OFFSET_CAPTURE)) { $offset = strlen($matches[0][0]) + $matches[0][1]; $annotationContent = substr($docComment, $offset); $data = array(); foreach (explode("\n", $annotationContent) as $candidateRow) { $candidateRow = trim($candidateRow); $dataSet = json_decode($candidateRow, true); if (json_last_error() != JSON_ERROR_NONE) { break; } $data[] = $dataSet; } if (!$data) { throw new PHPUnit_Framework_Exception('The dataset for the @testWith annotation cannot be parsed.'); } return $data; } } private static function cleanUpMultiLineAnnotation($docComment) { //removing initial ' * ' for docComment $docComment = preg_replace('/' . '\n' . '\s*' . '\*' . '\s?' . '/', "\n", $docComment); $docComment = substr($docComment, 0, -1); $docComment = rtrim($docComment, "\n"); return $docComment; } /** * @param string $className * @param string $methodName * @return array * @throws ReflectionException * @since Method available since Release 3.4.0 */ public static function parseTestMethodAnnotations($className, $methodName = '') { if (!isset(self::$annotationCache[$className])) { $class = new ReflectionClass($className); self::$annotationCache[$className] = self::parseAnnotations($class->getDocComment()); } if (!empty($methodName) && !isset(self::$annotationCache[$className . '::' . $methodName])) { try { $method = new ReflectionMethod($className, $methodName); $annotations = self::parseAnnotations($method->getDocComment()); } catch (ReflectionException $e) { $annotations = array(); } self::$annotationCache[$className . '::' . $methodName] = $annotations; } return array( 'class' => self::$annotationCache[$className], 'method' => !empty($methodName) ? self::$annotationCache[$className . '::' . $methodName] : array() ); } /** * @param string $docblock * @return array * @since Method available since Release 3.4.0 */ private static function parseAnnotations($docblock) { $annotations = array(); // Strip away the docblock header and footer to ease parsing of one line annotations $docblock = substr($docblock, 3, -2); if (preg_match_all('/@(?P[A-Za-z_-]+)(?:[ \t]+(?P.*?))?[ \t]*\r?$/m', $docblock, $matches)) { $numMatches = count($matches[0]); for ($i = 0; $i < $numMatches; ++$i) { $annotations[$matches['name'][$i]][] = $matches['value'][$i]; } } return $annotations; } /** * Returns the backup settings for a test. * * @param string $className * @param string $methodName * @return array * @since Method available since Release 3.4.0 */ public static function getBackupSettings($className, $methodName) { return array( 'backupGlobals' => self::getBooleanAnnotationSetting( $className, $methodName, 'backupGlobals' ), 'backupStaticAttributes' => self::getBooleanAnnotationSetting( $className, $methodName, 'backupStaticAttributes' ) ); } /** * Returns the dependencies for a test class or method. * * @param string $className * @param string $methodName * @return array * @since Method available since Release 3.4.0 */ public static function getDependencies($className, $methodName) { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); $dependencies = array(); if (isset($annotations['class']['depends'])) { $dependencies = $annotations['class']['depends']; } if (isset($annotations['method']['depends'])) { $dependencies = array_merge( $dependencies, $annotations['method']['depends'] ); } return array_unique($dependencies); } /** * Returns the error handler settings for a test. * * @param string $className * @param string $methodName * @return bool * @since Method available since Release 3.4.0 */ public static function getErrorHandlerSettings($className, $methodName) { return self::getBooleanAnnotationSetting( $className, $methodName, 'errorHandler' ); } /** * Returns the groups for a test class or method. * * @param string $className * @param string $methodName * @return array * @since Method available since Release 3.2.0 */ public static function getGroups($className, $methodName = '') { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); $groups = array(); if (isset($annotations['method']['author'])) { $groups = $annotations['method']['author']; } elseif (isset($annotations['class']['author'])) { $groups = $annotations['class']['author']; } if (isset($annotations['class']['group'])) { $groups = array_merge($groups, $annotations['class']['group']); } if (isset($annotations['method']['group'])) { $groups = array_merge($groups, $annotations['method']['group']); } if (isset($annotations['class']['ticket'])) { $groups = array_merge($groups, $annotations['class']['ticket']); } if (isset($annotations['method']['ticket'])) { $groups = array_merge($groups, $annotations['method']['ticket']); } foreach (array('method', 'class') as $element) { foreach (array('small', 'medium', 'large') as $size) { if (isset($annotations[$element][$size])) { $groups[] = $size; break 2; } if (isset($annotations[$element][$size])) { $groups[] = $size; break 2; } } } return array_unique($groups); } /** * Returns the size of the test. * * @param string $className * @param string $methodName * @return int * @since Method available since Release 3.6.0 */ public static function getSize($className, $methodName) { $groups = array_flip(self::getGroups($className, $methodName)); $size = self::UNKNOWN; $class = new ReflectionClass($className); if (isset($groups['large']) || (class_exists('PHPUnit_Extensions_Database_TestCase', false) && $class->isSubclassOf('PHPUnit_Extensions_Database_TestCase')) || (class_exists('PHPUnit_Extensions_SeleniumTestCase', false) && $class->isSubclassOf('PHPUnit_Extensions_SeleniumTestCase'))) { $size = self::LARGE; } elseif (isset($groups['medium'])) { $size = self::MEDIUM; } elseif (isset($groups['small'])) { $size = self::SMALL; } return $size; } /** * Returns the tickets for a test class or method. * * @param string $className * @param string $methodName * @return array * @since Method available since Release 3.4.0 */ public static function getTickets($className, $methodName) { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); $tickets = array(); if (isset($annotations['class']['ticket'])) { $tickets = $annotations['class']['ticket']; } if (isset($annotations['method']['ticket'])) { $tickets = array_merge($tickets, $annotations['method']['ticket']); } return array_unique($tickets); } /** * Returns the process isolation settings for a test. * * @param string $className * @param string $methodName * @return bool * @since Method available since Release 3.4.1 */ public static function getProcessIsolationSettings($className, $methodName) { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); if (isset($annotations['class']['runTestsInSeparateProcesses']) || isset($annotations['method']['runInSeparateProcess'])) { return true; } else { return false; } } /** * Returns the preserve global state settings for a test. * * @param string $className * @param string $methodName * @return bool * @since Method available since Release 3.4.0 */ public static function getPreserveGlobalStateSettings($className, $methodName) { return self::getBooleanAnnotationSetting( $className, $methodName, 'preserveGlobalState' ); } /** * @param string $className * @return array * @since Method available since Release 4.0.8 */ public static function getHookMethods($className) { if (!class_exists($className, false)) { return self::emptyHookMethodsArray(); } if (!isset(self::$hookMethods[$className])) { self::$hookMethods[$className] = self::emptyHookMethodsArray(); try { $class = new ReflectionClass($className); foreach ($class->getMethods() as $method) { if (self::isBeforeClassMethod($method)) { self::$hookMethods[$className]['beforeClass'][] = $method->getName(); } if (self::isBeforeMethod($method)) { self::$hookMethods[$className]['before'][] = $method->getName(); } if (self::isAfterMethod($method)) { self::$hookMethods[$className]['after'][] = $method->getName(); } if (self::isAfterClassMethod($method)) { self::$hookMethods[$className]['afterClass'][] = $method->getName(); } } } catch (ReflectionException $e) { } } return self::$hookMethods[$className]; } /** * @return array * @since Method available since Release 4.0.9 */ private static function emptyHookMethodsArray() { return array( 'beforeClass' => array('setUpBeforeClass'), 'before' => array('setUp'), 'after' => array('tearDown'), 'afterClass' => array('tearDownAfterClass') ); } /** * @param string $className * @param string $methodName * @param string $settingName * @return bool * @since Method available since Release 3.4.0 */ private static function getBooleanAnnotationSetting($className, $methodName, $settingName) { $annotations = self::parseTestMethodAnnotations( $className, $methodName ); $result = null; if (isset($annotations['class'][$settingName])) { if ($annotations['class'][$settingName][0] == 'enabled') { $result = true; } elseif ($annotations['class'][$settingName][0] == 'disabled') { $result = false; } } if (isset($annotations['method'][$settingName])) { if ($annotations['method'][$settingName][0] == 'enabled') { $result = true; } elseif ($annotations['method'][$settingName][0] == 'disabled') { $result = false; } } return $result; } /** * @param string $element * @return array * @throws PHPUnit_Framework_InvalidCoversTargetException * @since Method available since Release 4.0.0 */ private static function resolveElementToReflectionObjects($element) { $codeToCoverList = array(); if (strpos($element, '::') !== false) { list($className, $methodName) = explode('::', $element); if (isset($methodName[0]) && $methodName[0] == '<') { $classes = array($className); foreach ($classes as $className) { if (!class_exists($className) && !interface_exists($className)) { throw new PHPUnit_Framework_InvalidCoversTargetException( sprintf( 'Trying to @cover or @use not existing class or ' . 'interface "%s".', $className ) ); } $class = new ReflectionClass($className); $methods = $class->getMethods(); $inverse = isset($methodName[1]) && $methodName[1] == '!'; if (strpos($methodName, 'protected')) { $visibility = 'isProtected'; } elseif (strpos($methodName, 'private')) { $visibility = 'isPrivate'; } elseif (strpos($methodName, 'public')) { $visibility = 'isPublic'; } foreach ($methods as $method) { if ($inverse && !$method->$visibility()) { $codeToCoverList[] = $method; } elseif (!$inverse && $method->$visibility()) { $codeToCoverList[] = $method; } } } } else { $classes = array($className); foreach ($classes as $className) { if ($className == '' && function_exists($methodName)) { $codeToCoverList[] = new ReflectionFunction( $methodName ); } else { if (!((class_exists($className) || interface_exists($className) || trait_exists($className)) && method_exists($className, $methodName))) { throw new PHPUnit_Framework_InvalidCoversTargetException( sprintf( 'Trying to @cover or @use not existing method "%s::%s".', $className, $methodName ) ); } $codeToCoverList[] = new ReflectionMethod( $className, $methodName ); } } } } else { $extended = false; if (strpos($element, '') !== false) { $element = str_replace('', '', $element); $extended = true; } $classes = array($element); if ($extended) { $classes = array_merge( $classes, class_implements($element), class_parents($element) ); } foreach ($classes as $className) { if (!class_exists($className) && !interface_exists($className) && !trait_exists($className)) { throw new PHPUnit_Framework_InvalidCoversTargetException( sprintf( 'Trying to @cover or @use not existing class or ' . 'interface "%s".', $className ) ); } $codeToCoverList[] = new ReflectionClass($className); } } return $codeToCoverList; } /** * @param array $reflectors * @return array */ private static function resolveReflectionObjectsToLines(array $reflectors) { $result = array(); foreach ($reflectors as $reflector) { $filename = $reflector->getFileName(); if (!isset($result[$filename])) { $result[$filename] = array(); } $result[$filename] = array_unique( array_merge( $result[$filename], range($reflector->getStartLine(), $reflector->getEndLine()) ) ); } return $result; } /** * @param ReflectionMethod $method * @return bool * @since Method available since Release 4.0.8 */ private static function isBeforeClassMethod(ReflectionMethod $method) { return $method->isStatic() && strpos($method->getDocComment(), '@beforeClass') !== false; } /** * @param ReflectionMethod $method * @return bool * @since Method available since Release 4.0.8 */ private static function isBeforeMethod(ReflectionMethod $method) { return preg_match('/@before\b/', $method->getDocComment()); } /** * @param ReflectionMethod $method * @return bool * @since Method available since Release 4.0.8 */ private static function isAfterClassMethod(ReflectionMethod $method) { return $method->isStatic() && strpos($method->getDocComment(), '@afterClass') !== false; } /** * @param ReflectionMethod $method * @return bool * @since Method available since Release 4.0.8 */ private static function isAfterMethod(ReflectionMethod $method) { return preg_match('/@after\b/', $method->getDocComment()); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Filesystem helpers. * * @since Class available since Release 3.0.0 */ class PHPUnit_Util_Filesystem { /** * @var array */ protected static $buffer = array(); /** * Maps class names to source file names: * - PEAR CS: Foo_Bar_Baz -> Foo/Bar/Baz.php * - Namespace: Foo\Bar\Baz -> Foo/Bar/Baz.php * * @param string $className * @return string * @since Method available since Release 3.4.0 */ public static function classNameToFilename($className) { return str_replace( array('_', '\\'), DIRECTORY_SEPARATOR, $className ) . '.php'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Command-line options parsing class. * * @since Class available since Release 3.0.0 */ class PHPUnit_Util_Getopt { public static function getopt(array $args, $short_options, $long_options = null) { if (empty($args)) { return array(array(), array()); } $opts = array(); $non_opts = array(); if ($long_options) { sort($long_options); } if (isset($args[0][0]) && $args[0][0] != '-') { array_shift($args); } reset($args); array_map('trim', $args); while (list($i, $arg) = each($args)) { if ($arg == '') { continue; } if ($arg == '--') { $non_opts = array_merge($non_opts, array_slice($args, $i + 1)); break; } if ($arg[0] != '-' || (strlen($arg) > 1 && $arg[1] == '-' && !$long_options)) { $non_opts[] = $args[$i]; continue; } elseif (strlen($arg) > 1 && $arg[1] == '-') { self::parseLongOption( substr($arg, 2), $long_options, $opts, $args ); } else { self::parseShortOption( substr($arg, 1), $short_options, $opts, $args ); } } return array($opts, $non_opts); } protected static function parseShortOption($arg, $short_options, &$opts, &$args) { $argLen = strlen($arg); for ($i = 0; $i < $argLen; $i++) { $opt = $arg[$i]; $opt_arg = null; if (($spec = strstr($short_options, $opt)) === false || $arg[$i] == ':') { throw new PHPUnit_Framework_Exception( "unrecognized option -- $opt" ); } if (strlen($spec) > 1 && $spec[1] == ':') { if (strlen($spec) > 2 && $spec[2] == ':') { if ($i + 1 < $argLen) { $opts[] = array($opt, substr($arg, $i + 1)); break; } } else { if ($i + 1 < $argLen) { $opts[] = array($opt, substr($arg, $i + 1)); break; } elseif (list(, $opt_arg) = each($args)) { } else { throw new PHPUnit_Framework_Exception( "option requires an argument -- $opt" ); } } } $opts[] = array($opt, $opt_arg); } } protected static function parseLongOption($arg, $long_options, &$opts, &$args) { $count = count($long_options); $list = explode('=', $arg); $opt = $list[0]; $opt_arg = null; if (count($list) > 1) { $opt_arg = $list[1]; } $opt_len = strlen($opt); for ($i = 0; $i < $count; $i++) { $long_opt = $long_options[$i]; $opt_start = substr($long_opt, 0, $opt_len); if ($opt_start != $opt) { continue; } $opt_rest = substr($long_opt, $opt_len); if ($opt_rest != '' && $opt[0] != '=' && $i + 1 < $count && $opt == substr($long_options[$i+1], 0, $opt_len)) { throw new PHPUnit_Framework_Exception( "option --$opt is ambiguous" ); } if (substr($long_opt, -1) == '=') { if (substr($long_opt, -2) != '==') { if (!strlen($opt_arg) && !(list(, $opt_arg) = each($args))) { throw new PHPUnit_Framework_Exception( "option --$opt requires an argument" ); } } } elseif ($opt_arg) { throw new PHPUnit_Framework_Exception( "option --$opt doesn't allow an argument" ); } $full_option = '--' . preg_replace('/={1,2}$/', '', $long_opt); $opts[] = array($full_option, $opt_arg); return; } throw new PHPUnit_Framework_Exception("unrecognized option --$opt"); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ // Workaround for http://bugs.php.net/bug.php?id=47987, // see https://github.com/sebastianbergmann/phpunit/issues#issue/125 for details // Use dirname(__DIR__) instead of using /../ because of https://github.com/facebook/hhvm/issues/5215 require_once dirname(__DIR__) . '/Framework/Error.php'; require_once dirname(__DIR__) . '/Framework/Error/Notice.php'; require_once dirname(__DIR__) . '/Framework/Error/Warning.php'; require_once dirname(__DIR__) . '/Framework/Error/Deprecated.php'; /** * Error handler that converts PHP errors and warnings to exceptions. * * @since Class available since Release 3.3.0 */ class PHPUnit_Util_ErrorHandler { protected static $errorStack = array(); /** * Returns the error stack. * * @return array */ public static function getErrorStack() { return self::$errorStack; } /** * @param int $errno * @param string $errstr * @param string $errfile * @param int $errline * @throws PHPUnit_Framework_Error */ public static function handleError($errno, $errstr, $errfile, $errline) { if (!($errno & error_reporting())) { return false; } self::$errorStack[] = array($errno, $errstr, $errfile, $errline); $trace = debug_backtrace(false); array_shift($trace); foreach ($trace as $frame) { if ($frame['function'] == '__toString') { return false; } } if ($errno == E_NOTICE || $errno == E_USER_NOTICE || $errno == E_STRICT) { if (PHPUnit_Framework_Error_Notice::$enabled !== true) { return false; } $exception = 'PHPUnit_Framework_Error_Notice'; } elseif ($errno == E_WARNING || $errno == E_USER_WARNING) { if (PHPUnit_Framework_Error_Warning::$enabled !== true) { return false; } $exception = 'PHPUnit_Framework_Error_Warning'; } elseif ($errno == E_DEPRECATED || $errno == E_USER_DEPRECATED) { if (PHPUnit_Framework_Error_Deprecated::$enabled !== true) { return false; } $exception = 'PHPUnit_Framework_Error_Deprecated'; } else { $exception = 'PHPUnit_Framework_Error'; } throw new $exception($errstr, $errno, $errfile, $errline); } /** * Registers an error handler and returns a function that will restore * the previous handler when invoked * @param int $severity PHP predefined error constant * @throws Exception if event of specified severity is emitted */ public static function handleErrorOnce($severity = E_WARNING) { $terminator = function () { static $expired = false; if (!$expired) { $expired = true; // cleans temporary error handler return restore_error_handler(); } }; set_error_handler(function ($errno, $errstr) use ($severity) { if ($errno === $severity) { return; } return false; }); return $terminator; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * String helpers. * * @since Class available since Release 3.6.0 */ class PHPUnit_Util_String { /** * Converts a string to UTF-8 encoding. * * @param string $string * @return string */ public static function convertToUtf8($string) { if (!self::isUtf8($string)) { if (function_exists('mb_convert_encoding')) { $string = mb_convert_encoding($string, 'UTF-8'); } else { $string = utf8_encode($string); } } return $string; } /** * Checks a string for UTF-8 encoding. * * @param string $string * @return bool */ protected static function isUtf8($string) { $length = strlen($string); for ($i = 0; $i < $length; $i++) { if (ord($string[$i]) < 0x80) { $n = 0; } elseif ((ord($string[$i]) & 0xE0) == 0xC0) { $n = 1; } elseif ((ord($string[$i]) & 0xF0) == 0xE0) { $n = 2; } elseif ((ord($string[$i]) & 0xF0) == 0xF0) { $n = 3; } else { return false; } for ($j = 0; $j < $n; $j++) { if ((++$i == $length) || ((ord($string[$i]) & 0xC0) != 0x80)) { return false; } } } return true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Factory for PHPUnit_Framework_Exception objects that are used to describe * invalid arguments passed to a function or method. * * @since Class available since Release 3.4.0 */ class PHPUnit_Util_InvalidArgumentHelper { /** * @param int $argument * @param string $type * @param mixed $value * @return PHPUnit_Framework_Exception */ public static function factory($argument, $type, $value = null) { $stack = debug_backtrace(false); return new PHPUnit_Framework_Exception( sprintf( 'Argument #%d%sof %s::%s() must be a %s', $argument, $value !== null ? ' (' . gettype($value) . '#' . $value . ')' : ' (No Value) ', $stack[1]['class'], $stack[1]['function'], $type ) ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Utility class that can print to STDOUT or write to a file. * * @since Class available since Release 2.0.0 */ class PHPUnit_Util_Printer { /** * If true, flush output after every write. * * @var bool */ protected $autoFlush = false; /** * @var resource */ protected $out; /** * @var string */ protected $outTarget; /** * @var bool */ protected $printsHTML = false; /** * Constructor. * * @param mixed $out * @throws PHPUnit_Framework_Exception */ public function __construct($out = null) { if ($out !== null) { if (is_string($out)) { if (strpos($out, 'socket://') === 0) { $out = explode(':', str_replace('socket://', '', $out)); if (sizeof($out) != 2) { throw new PHPUnit_Framework_Exception; } $this->out = fsockopen($out[0], $out[1]); } else { if (strpos($out, 'php://') === false && !is_dir(dirname($out))) { mkdir(dirname($out), 0777, true); } $this->out = fopen($out, 'wt'); } $this->outTarget = $out; } else { $this->out = $out; } } } /** * Flush buffer, optionally tidy up HTML, and close output if it's not to a php stream */ public function flush() { if ($this->out && strncmp($this->outTarget, 'php://', 6) !== 0) { fclose($this->out); } if ($this->printsHTML === true && $this->outTarget !== null && strpos($this->outTarget, 'php://') !== 0 && strpos($this->outTarget, 'socket://') !== 0 && extension_loaded('tidy')) { file_put_contents( $this->outTarget, tidy_repair_file( $this->outTarget, array('indent' => true, 'wrap' => 0), 'utf8' ) ); } } /** * Performs a safe, incremental flush. * * Do not confuse this function with the flush() function of this class, * since the flush() function may close the file being written to, rendering * the current object no longer usable. * * @since Method available since Release 3.3.0 */ public function incrementalFlush() { if ($this->out) { fflush($this->out); } else { flush(); } } /** * @param string $buffer */ public function write($buffer) { if ($this->out) { fwrite($this->out, $buffer); if ($this->autoFlush) { $this->incrementalFlush(); } } else { if (PHP_SAPI != 'cli') { $buffer = htmlspecialchars($buffer); } print $buffer; if ($this->autoFlush) { $this->incrementalFlush(); } } } /** * Check auto-flush mode. * * @return bool * @since Method available since Release 3.3.0 */ public function getAutoFlush() { return $this->autoFlush; } /** * Set auto-flushing mode. * * If set, *incremental* flushes will be done after each write. This should * not be confused with the different effects of this class' flush() method. * * @param bool $autoFlush * @since Method available since Release 3.3.0 */ public function setAutoFlush($autoFlush) { if (is_bool($autoFlush)) { $this->autoFlush = $autoFlush; } else { throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean'); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Wrapper for the PHPUnit XML configuration file. * * Example XML configuration file: * * * * * * * /path/to/files * /path/to/MyTest.php * /path/to/files/exclude * * * * * * name * * * name * * * * * * /path/to/files * /path/to/file * * /path/to/files * /path/to/file * * * * /path/to/files * /path/to/file * * /path/to/files * /path/to/file * * * * * * * * * * Sebastian * * * 22 * April * 19.78 * * * MyRelativeFile.php * MyRelativeDir * * * * * * * * * * * * * * * * * * . * * * * * * * * * * * * * * * * * * * @since Class available since Release 3.2.0 */ class PHPUnit_Util_Configuration { private static $instances = array(); protected $document; protected $xpath; protected $filename; /** * Loads a PHPUnit configuration file. * * @param string $filename */ protected function __construct($filename) { $this->filename = $filename; $this->document = PHPUnit_Util_XML::loadFile($filename, false, true, true); $this->xpath = new DOMXPath($this->document); } /** * @since Method available since Release 3.4.0 */ final private function __clone() { } /** * Returns a PHPUnit configuration object. * * @param string $filename * @return PHPUnit_Util_Configuration * @since Method available since Release 3.4.0 */ public static function getInstance($filename) { $realpath = realpath($filename); if ($realpath === false) { throw new PHPUnit_Framework_Exception( sprintf( 'Could not read "%s".', $filename ) ); } if (!isset(self::$instances[$realpath])) { self::$instances[$realpath] = new self($realpath); } return self::$instances[$realpath]; } /** * Returns the realpath to the configuration file. * * @return string * @since Method available since Release 3.6.0 */ public function getFilename() { return $this->filename; } /** * Returns the configuration for SUT filtering. * * @return array * @since Method available since Release 3.2.1 */ public function getFilterConfiguration() { $addUncoveredFilesFromWhitelist = true; $processUncoveredFilesFromWhitelist = false; $tmp = $this->xpath->query('filter/whitelist'); if ($tmp->length == 1) { if ($tmp->item(0)->hasAttribute('addUncoveredFilesFromWhitelist')) { $addUncoveredFilesFromWhitelist = $this->getBoolean( (string) $tmp->item(0)->getAttribute( 'addUncoveredFilesFromWhitelist' ), true ); } if ($tmp->item(0)->hasAttribute('processUncoveredFilesFromWhitelist')) { $processUncoveredFilesFromWhitelist = $this->getBoolean( (string) $tmp->item(0)->getAttribute( 'processUncoveredFilesFromWhitelist' ), false ); } } return array( 'blacklist' => array( 'include' => array( 'directory' => $this->readFilterDirectories( 'filter/blacklist/directory' ), 'file' => $this->readFilterFiles( 'filter/blacklist/file' ) ), 'exclude' => array( 'directory' => $this->readFilterDirectories( 'filter/blacklist/exclude/directory' ), 'file' => $this->readFilterFiles( 'filter/blacklist/exclude/file' ) ) ), 'whitelist' => array( 'addUncoveredFilesFromWhitelist' => $addUncoveredFilesFromWhitelist, 'processUncoveredFilesFromWhitelist' => $processUncoveredFilesFromWhitelist, 'include' => array( 'directory' => $this->readFilterDirectories( 'filter/whitelist/directory' ), 'file' => $this->readFilterFiles( 'filter/whitelist/file' ) ), 'exclude' => array( 'directory' => $this->readFilterDirectories( 'filter/whitelist/exclude/directory' ), 'file' => $this->readFilterFiles( 'filter/whitelist/exclude/file' ) ) ) ); } /** * Returns the configuration for groups. * * @return array * @since Method available since Release 3.2.1 */ public function getGroupConfiguration() { $groups = array( 'include' => array(), 'exclude' => array() ); foreach ($this->xpath->query('groups/include/group') as $group) { $groups['include'][] = (string) $group->textContent; } foreach ($this->xpath->query('groups/exclude/group') as $group) { $groups['exclude'][] = (string) $group->textContent; } return $groups; } /** * Returns the configuration for listeners. * * @return array * @since Method available since Release 3.4.0 */ public function getListenerConfiguration() { $result = array(); foreach ($this->xpath->query('listeners/listener') as $listener) { $class = (string) $listener->getAttribute('class'); $file = ''; $arguments = array(); if ($listener->getAttribute('file')) { $file = $this->toAbsolutePath( (string) $listener->getAttribute('file'), true ); } foreach ($listener->childNodes as $node) { if ($node instanceof DOMElement && $node->tagName == 'arguments') { foreach ($node->childNodes as $argument) { if ($argument instanceof DOMElement) { if ($argument->tagName == 'file' || $argument->tagName == 'directory') { $arguments[] = $this->toAbsolutePath((string) $argument->textContent); } else { $arguments[] = PHPUnit_Util_XML::xmlToVariable($argument); } } } } } $result[] = array( 'class' => $class, 'file' => $file, 'arguments' => $arguments ); } return $result; } /** * Returns the logging configuration. * * @return array */ public function getLoggingConfiguration() { $result = array(); foreach ($this->xpath->query('logging/log') as $log) { $type = (string) $log->getAttribute('type'); $target = (string) $log->getAttribute('target'); if (!$target) { continue; } $target = $this->toAbsolutePath($target); if ($type == 'coverage-html') { if ($log->hasAttribute('lowUpperBound')) { $result['lowUpperBound'] = $this->getInteger( (string) $log->getAttribute('lowUpperBound'), 50 ); } if ($log->hasAttribute('highLowerBound')) { $result['highLowerBound'] = $this->getInteger( (string) $log->getAttribute('highLowerBound'), 90 ); } } elseif ($type == 'coverage-crap4j') { if ($log->hasAttribute('threshold')) { $result['crap4jThreshold'] = $this->getInteger( (string) $log->getAttribute('threshold'), 30 ); } } elseif ($type == 'junit') { if ($log->hasAttribute('logIncompleteSkipped')) { $result['logIncompleteSkipped'] = $this->getBoolean( (string) $log->getAttribute('logIncompleteSkipped'), false ); } } elseif ($type == 'coverage-text') { if ($log->hasAttribute('showUncoveredFiles')) { $result['coverageTextShowUncoveredFiles'] = $this->getBoolean( (string) $log->getAttribute('showUncoveredFiles'), false ); } if ($log->hasAttribute('showOnlySummary')) { $result['coverageTextShowOnlySummary'] = $this->getBoolean( (string) $log->getAttribute('showOnlySummary'), false ); } } $result[$type] = $target; } return $result; } /** * Returns the PHP configuration. * * @return array * @since Method available since Release 3.2.1 */ public function getPHPConfiguration() { $result = array( 'include_path' => array(), 'ini' => array(), 'const' => array(), 'var' => array(), 'env' => array(), 'post' => array(), 'get' => array(), 'cookie' => array(), 'server' => array(), 'files' => array(), 'request' => array() ); foreach ($this->xpath->query('php/includePath') as $includePath) { $path = (string) $includePath->textContent; if ($path) { $result['include_path'][] = $this->toAbsolutePath($path); } } foreach ($this->xpath->query('php/ini') as $ini) { $name = (string) $ini->getAttribute('name'); $value = (string) $ini->getAttribute('value'); $result['ini'][$name] = $value; } foreach ($this->xpath->query('php/const') as $const) { $name = (string) $const->getAttribute('name'); $value = (string) $const->getAttribute('value'); $result['const'][$name] = $this->getBoolean($value, $value); } foreach (array('var', 'env', 'post', 'get', 'cookie', 'server', 'files', 'request') as $array) { foreach ($this->xpath->query('php/' . $array) as $var) { $name = (string) $var->getAttribute('name'); $value = (string) $var->getAttribute('value'); $result[$array][$name] = $this->getBoolean($value, $value); } } return $result; } /** * Handles the PHP configuration. * * @since Method available since Release 3.2.20 */ public function handlePHPConfiguration() { $configuration = $this->getPHPConfiguration(); if (! empty($configuration['include_path'])) { ini_set( 'include_path', implode(PATH_SEPARATOR, $configuration['include_path']) . PATH_SEPARATOR . ini_get('include_path') ); } foreach ($configuration['ini'] as $name => $value) { if (defined($value)) { $value = constant($value); } ini_set($name, $value); } foreach ($configuration['const'] as $name => $value) { if (!defined($name)) { define($name, $value); } } foreach (array('var', 'post', 'get', 'cookie', 'server', 'files', 'request') as $array) { // See https://github.com/sebastianbergmann/phpunit/issues/277 switch ($array) { case 'var': $target = &$GLOBALS; break; case 'server': $target = &$_SERVER; break; default: $target = &$GLOBALS['_' . strtoupper($array)]; break; } foreach ($configuration[$array] as $name => $value) { $target[$name] = $value; } } foreach ($configuration['env'] as $name => $value) { if (false === getenv($name)) { putenv("{$name}={$value}"); } if (!isset($_ENV[$name])) { $_ENV[$name] = $value; } } } /** * Returns the PHPUnit configuration. * * @return array * @since Method available since Release 3.2.14 */ public function getPHPUnitConfiguration() { $result = array(); $root = $this->document->documentElement; if ($root->hasAttribute('cacheTokens')) { $result['cacheTokens'] = $this->getBoolean( (string) $root->getAttribute('cacheTokens'), false ); } if ($root->hasAttribute('columns')) { $columns = (string) $root->getAttribute('columns'); if ($columns == 'max') { $result['columns'] = 'max'; } else { $result['columns'] = $this->getInteger($columns, 80); } } if ($root->hasAttribute('colors')) { /* only allow boolean for compatibility with previous versions 'always' only allowed from command line */ if ($this->getBoolean($root->getAttribute('colors'), false)) { $result['colors'] = PHPUnit_TextUI_ResultPrinter::COLOR_AUTO; } else { $result['colors'] = PHPUnit_TextUI_ResultPrinter::COLOR_NEVER; } } /* * Issue #657 */ if ($root->hasAttribute('stderr')) { $result['stderr'] = $this->getBoolean( (string) $root->getAttribute('stderr'), false ); } if ($root->hasAttribute('backupGlobals')) { $result['backupGlobals'] = $this->getBoolean( (string) $root->getAttribute('backupGlobals'), true ); } if ($root->hasAttribute('backupStaticAttributes')) { $result['backupStaticAttributes'] = $this->getBoolean( (string) $root->getAttribute('backupStaticAttributes'), false ); } if ($root->getAttribute('bootstrap')) { $result['bootstrap'] = $this->toAbsolutePath( (string) $root->getAttribute('bootstrap') ); } if ($root->hasAttribute('convertErrorsToExceptions')) { $result['convertErrorsToExceptions'] = $this->getBoolean( (string) $root->getAttribute('convertErrorsToExceptions'), true ); } if ($root->hasAttribute('convertNoticesToExceptions')) { $result['convertNoticesToExceptions'] = $this->getBoolean( (string) $root->getAttribute('convertNoticesToExceptions'), true ); } if ($root->hasAttribute('convertWarningsToExceptions')) { $result['convertWarningsToExceptions'] = $this->getBoolean( (string) $root->getAttribute('convertWarningsToExceptions'), true ); } if ($root->hasAttribute('forceCoversAnnotation')) { $result['forceCoversAnnotation'] = $this->getBoolean( (string) $root->getAttribute('forceCoversAnnotation'), false ); } if ($root->hasAttribute('mapTestClassNameToCoveredClassName')) { $result['mapTestClassNameToCoveredClassName'] = $this->getBoolean( (string) $root->getAttribute('mapTestClassNameToCoveredClassName'), false ); } if ($root->hasAttribute('processIsolation')) { $result['processIsolation'] = $this->getBoolean( (string) $root->getAttribute('processIsolation'), false ); } if ($root->hasAttribute('stopOnError')) { $result['stopOnError'] = $this->getBoolean( (string) $root->getAttribute('stopOnError'), false ); } if ($root->hasAttribute('stopOnFailure')) { $result['stopOnFailure'] = $this->getBoolean( (string) $root->getAttribute('stopOnFailure'), false ); } if ($root->hasAttribute('stopOnIncomplete')) { $result['stopOnIncomplete'] = $this->getBoolean( (string) $root->getAttribute('stopOnIncomplete'), false ); } if ($root->hasAttribute('stopOnRisky')) { $result['stopOnRisky'] = $this->getBoolean( (string) $root->getAttribute('stopOnRisky'), false ); } if ($root->hasAttribute('stopOnSkipped')) { $result['stopOnSkipped'] = $this->getBoolean( (string) $root->getAttribute('stopOnSkipped'), false ); } if ($root->hasAttribute('testSuiteLoaderClass')) { $result['testSuiteLoaderClass'] = (string) $root->getAttribute( 'testSuiteLoaderClass' ); } if ($root->getAttribute('testSuiteLoaderFile')) { $result['testSuiteLoaderFile'] = $this->toAbsolutePath( (string) $root->getAttribute('testSuiteLoaderFile') ); } if ($root->hasAttribute('printerClass')) { $result['printerClass'] = (string) $root->getAttribute( 'printerClass' ); } if ($root->getAttribute('printerFile')) { $result['printerFile'] = $this->toAbsolutePath( (string) $root->getAttribute('printerFile') ); } if ($root->hasAttribute('timeoutForSmallTests')) { $result['timeoutForSmallTests'] = $this->getInteger( (string) $root->getAttribute('timeoutForSmallTests'), 1 ); } if ($root->hasAttribute('timeoutForMediumTests')) { $result['timeoutForMediumTests'] = $this->getInteger( (string) $root->getAttribute('timeoutForMediumTests'), 10 ); } if ($root->hasAttribute('timeoutForLargeTests')) { $result['timeoutForLargeTests'] = $this->getInteger( (string) $root->getAttribute('timeoutForLargeTests'), 60 ); } if ($root->hasAttribute('beStrictAboutTestsThatDoNotTestAnything')) { $result['reportUselessTests'] = $this->getBoolean( (string) $root->getAttribute('beStrictAboutTestsThatDoNotTestAnything'), false ); } if ($root->hasAttribute('checkForUnintentionallyCoveredCode')) { $result['strictCoverage'] = $this->getBoolean( (string) $root->getAttribute('checkForUnintentionallyCoveredCode'), false ); } if ($root->hasAttribute('beStrictAboutOutputDuringTests')) { $result['disallowTestOutput'] = $this->getBoolean( (string) $root->getAttribute('beStrictAboutOutputDuringTests'), false ); } if ($root->hasAttribute('beStrictAboutChangesToGlobalState')) { $result['disallowChangesToGlobalState'] = $this->getBoolean( (string) $root->getAttribute('beStrictAboutChangesToGlobalState'), false ); } if ($root->hasAttribute('beStrictAboutTestSize')) { $result['enforceTimeLimit'] = $this->getBoolean( (string) $root->getAttribute('beStrictAboutTestSize'), false ); } if ($root->hasAttribute('beStrictAboutTodoAnnotatedTests')) { $result['disallowTodoAnnotatedTests'] = $this->getBoolean( (string) $root->getAttribute('beStrictAboutTodoAnnotatedTests'), false ); } if ($root->hasAttribute('strict')) { $flag = $this->getBoolean( (string) $root->getAttribute('strict'), false ); $result['reportUselessTests'] = $flag; $result['strictCoverage'] = $flag; $result['disallowTestOutput'] = $flag; $result['enforceTimeLimit'] = $flag; $result['disallowTodoAnnotatedTests'] = $flag; $result['deprecatedStrictModeSetting'] = true; } if ($root->hasAttribute('verbose')) { $result['verbose'] = $this->getBoolean( (string) $root->getAttribute('verbose'), false ); } return $result; } /** * Returns the SeleniumTestCase browser configuration. * * @return array * @since Method available since Release 3.2.9 */ public function getSeleniumBrowserConfiguration() { $result = array(); foreach ($this->xpath->query('selenium/browser') as $config) { $name = (string) $config->getAttribute('name'); $browser = (string) $config->getAttribute('browser'); if ($config->hasAttribute('host')) { $host = (string) $config->getAttribute('host'); } else { $host = 'localhost'; } if ($config->hasAttribute('port')) { $port = $this->getInteger( (string) $config->getAttribute('port'), 4444 ); } else { $port = 4444; } if ($config->hasAttribute('timeout')) { $timeout = $this->getInteger( (string) $config->getAttribute('timeout'), 30000 ); } else { $timeout = 30000; } $result[] = array( 'name' => $name, 'browser' => $browser, 'host' => $host, 'port' => $port, 'timeout' => $timeout ); } return $result; } /** * Returns the test suite configuration. * * @return PHPUnit_Framework_TestSuite * @since Method available since Release 3.2.1 */ public function getTestSuiteConfiguration($testSuiteFilter = null) { $testSuiteNodes = $this->xpath->query('testsuites/testsuite'); if ($testSuiteNodes->length == 0) { $testSuiteNodes = $this->xpath->query('testsuite'); } if ($testSuiteNodes->length == 1) { return $this->getTestSuite($testSuiteNodes->item(0), $testSuiteFilter); } if ($testSuiteNodes->length > 1) { $suite = new PHPUnit_Framework_TestSuite; foreach ($testSuiteNodes as $testSuiteNode) { $suite->addTestSuite( $this->getTestSuite($testSuiteNode, $testSuiteFilter) ); } return $suite; } } /** * @param DOMElement $testSuiteNode * @return PHPUnit_Framework_TestSuite * @since Method available since Release 3.4.0 */ protected function getTestSuite(DOMElement $testSuiteNode, $testSuiteFilter = null) { if ($testSuiteNode->hasAttribute('name')) { $suite = new PHPUnit_Framework_TestSuite( (string) $testSuiteNode->getAttribute('name') ); } else { $suite = new PHPUnit_Framework_TestSuite; } $exclude = array(); foreach ($testSuiteNode->getElementsByTagName('exclude') as $excludeNode) { $excludeFile = (string) $excludeNode->textContent; if ($excludeFile) { $exclude[] = $this->toAbsolutePath($excludeFile); } } $fileIteratorFacade = new File_Iterator_Facade; foreach ($testSuiteNode->getElementsByTagName('directory') as $directoryNode) { if ($testSuiteFilter && $directoryNode->parentNode->getAttribute('name') != $testSuiteFilter) { continue; } $directory = (string) $directoryNode->textContent; if (empty($directory)) { continue; } if ($directoryNode->hasAttribute('phpVersion')) { $phpVersion = (string) $directoryNode->getAttribute('phpVersion'); } else { $phpVersion = PHP_VERSION; } if ($directoryNode->hasAttribute('phpVersionOperator')) { $phpVersionOperator = (string) $directoryNode->getAttribute('phpVersionOperator'); } else { $phpVersionOperator = '>='; } if (!version_compare(PHP_VERSION, $phpVersion, $phpVersionOperator)) { continue; } if ($directoryNode->hasAttribute('prefix')) { $prefix = (string) $directoryNode->getAttribute('prefix'); } else { $prefix = ''; } if ($directoryNode->hasAttribute('suffix')) { $suffix = (string) $directoryNode->getAttribute('suffix'); } else { $suffix = 'Test.php'; } $files = $fileIteratorFacade->getFilesAsArray( $this->toAbsolutePath($directory), $suffix, $prefix, $exclude ); $suite->addTestFiles($files); } foreach ($testSuiteNode->getElementsByTagName('file') as $fileNode) { if ($testSuiteFilter && $fileNode->parentNode->getAttribute('name') != $testSuiteFilter) { continue; } $file = (string) $fileNode->textContent; if (empty($file)) { continue; } // Get the absolute path to the file $file = $fileIteratorFacade->getFilesAsArray( $this->toAbsolutePath($file) ); if (!isset($file[0])) { continue; } $file = $file[0]; if ($fileNode->hasAttribute('phpVersion')) { $phpVersion = (string) $fileNode->getAttribute('phpVersion'); } else { $phpVersion = PHP_VERSION; } if ($fileNode->hasAttribute('phpVersionOperator')) { $phpVersionOperator = (string) $fileNode->getAttribute('phpVersionOperator'); } else { $phpVersionOperator = '>='; } if (!version_compare(PHP_VERSION, $phpVersion, $phpVersionOperator)) { continue; } $suite->addTestFile($file); } return $suite; } /** * @param string $value * @param bool $default * @return bool * @since Method available since Release 3.2.3 */ protected function getBoolean($value, $default) { if (strtolower($value) == 'false') { return false; } elseif (strtolower($value) == 'true') { return true; } return $default; } /** * @param string $value * @param bool $default * @return bool * @since Method available since Release 3.6.0 */ protected function getInteger($value, $default) { if (is_numeric($value)) { return (int) $value; } return $default; } /** * @param string $query * @return array * @since Method available since Release 3.2.3 */ protected function readFilterDirectories($query) { $directories = array(); foreach ($this->xpath->query($query) as $directory) { $directoryPath = (string) $directory->textContent; if (!$directoryPath) { continue; } if ($directory->hasAttribute('prefix')) { $prefix = (string) $directory->getAttribute('prefix'); } else { $prefix = ''; } if ($directory->hasAttribute('suffix')) { $suffix = (string) $directory->getAttribute('suffix'); } else { $suffix = '.php'; } if ($directory->hasAttribute('group')) { $group = (string) $directory->getAttribute('group'); } else { $group = 'DEFAULT'; } $directories[] = array( 'path' => $this->toAbsolutePath($directoryPath), 'prefix' => $prefix, 'suffix' => $suffix, 'group' => $group ); } return $directories; } /** * @param string $query * @return array * @since Method available since Release 3.2.3 */ protected function readFilterFiles($query) { $files = array(); foreach ($this->xpath->query($query) as $file) { $filePath = (string) $file->textContent; if ($filePath) { $files[] = $this->toAbsolutePath($filePath); } } return $files; } /** * @param string $path * @param bool $useIncludePath * @return string * @since Method available since Release 3.5.0 */ protected function toAbsolutePath($path, $useIncludePath = false) { if ($path[0] === '/') { return $path; } // Matches the following on Windows: // - \\NetworkComputer\Path // - \\.\D: // - \\.\c: // - C:\Windows // - C:\windows // - C:/windows // - c:/windows if (defined('PHP_WINDOWS_VERSION_BUILD') && ($path[0] === '\\' || (strlen($path) >= 3 && preg_match('#^[A-Z]\:[/\\\]#i', substr($path, 0, 3))))) { return $path; } // Stream if (strpos($path, '://') !== false) { return $path; } $file = dirname($this->filename) . DIRECTORY_SEPARATOR . $path; if ($useIncludePath && !file_exists($file)) { $includePathFile = stream_resolve_include_path($path); if ($includePathFile) { $file = $includePathFile; } } return $file; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 3.4.0 */ class PHPUnit_Util_GlobalState { /** * @var array */ protected static $superGlobalArrays = array( '_ENV', '_POST', '_GET', '_COOKIE', '_SERVER', '_FILES', '_REQUEST' ); /** * @var array */ protected static $superGlobalArraysLong = array( 'HTTP_ENV_VARS', 'HTTP_POST_VARS', 'HTTP_GET_VARS', 'HTTP_COOKIE_VARS', 'HTTP_SERVER_VARS', 'HTTP_POST_FILES' ); public static function getIncludedFilesAsString() { return static::processIncludedFilesAsString(get_included_files()); } public static function processIncludedFilesAsString(array $files) { $blacklist = new PHPUnit_Util_Blacklist; $prefix = false; $result = ''; if (defined('__PHPUNIT_PHAR__')) { $prefix = 'phar://' . __PHPUNIT_PHAR__ . '/'; } for ($i = count($files) - 1; $i > 0; $i--) { $file = $files[$i]; if ($prefix !== false && strpos($file, $prefix) === 0) { continue; } // Skip virtual file system protocols if (preg_match('/^(vfs|phpvfs[a-z0-9]+):/', $file)) { continue; } if (!$blacklist->isBlacklisted($file) && is_file($file)) { $result = 'require_once \'' . $file . "';\n" . $result; } } return $result; } public static function getIniSettingsAsString() { $result = ''; $iniSettings = ini_get_all(null, false); foreach ($iniSettings as $key => $value) { $result .= sprintf( '@ini_set(%s, %s);' . "\n", self::exportVariable($key), self::exportVariable($value) ); } return $result; } public static function getConstantsAsString() { $constants = get_defined_constants(true); $result = ''; if (isset($constants['user'])) { foreach ($constants['user'] as $name => $value) { $result .= sprintf( 'if (!defined(\'%s\')) define(\'%s\', %s);' . "\n", $name, $name, self::exportVariable($value) ); } } return $result; } public static function getGlobalsAsString() { $result = ''; $superGlobalArrays = self::getSuperGlobalArrays(); foreach ($superGlobalArrays as $superGlobalArray) { if (isset($GLOBALS[$superGlobalArray]) && is_array($GLOBALS[$superGlobalArray])) { foreach (array_keys($GLOBALS[$superGlobalArray]) as $key) { if ($GLOBALS[$superGlobalArray][$key] instanceof Closure) { continue; } $result .= sprintf( '$GLOBALS[\'%s\'][\'%s\'] = %s;' . "\n", $superGlobalArray, $key, self::exportVariable($GLOBALS[$superGlobalArray][$key]) ); } } } $blacklist = $superGlobalArrays; $blacklist[] = 'GLOBALS'; foreach (array_keys($GLOBALS) as $key) { if (!in_array($key, $blacklist) && !$GLOBALS[$key] instanceof Closure) { $result .= sprintf( '$GLOBALS[\'%s\'] = %s;' . "\n", $key, self::exportVariable($GLOBALS[$key]) ); } } return $result; } protected static function getSuperGlobalArrays() { if (ini_get('register_long_arrays') == '1') { return array_merge( self::$superGlobalArrays, self::$superGlobalArraysLong ); } else { return self::$superGlobalArrays; } } protected static function exportVariable($variable) { if (is_scalar($variable) || is_null($variable) || (is_array($variable) && self::arrayOnlyContainsScalars($variable))) { return var_export($variable, true); } return 'unserialize(' . var_export(serialize($variable), true) . ')'; } protected static function arrayOnlyContainsScalars(array $array) { $result = true; foreach ($array as $element) { if (is_array($element)) { $result = self::arrayOnlyContainsScalars($element); } elseif (!is_scalar($element) && !is_null($element)) { $result = false; } if ($result === false) { break; } } return $result; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A TestListener that generates JSON messages. * * @since Class available since Release 3.0.0 */ class PHPUnit_Util_Log_JSON extends PHPUnit_Util_Printer implements PHPUnit_Framework_TestListener { /** * @var string */ protected $currentTestSuiteName = ''; /** * @var string */ protected $currentTestName = ''; /** * @var bool */ protected $currentTestPass = true; /** * An error occurred. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->writeCase( 'error', $time, PHPUnit_Util_Filter::getFilteredStacktrace($e, false), $e->getMessage(), $test ); $this->currentTestPass = false; } /** * A failure occurred. * * @param PHPUnit_Framework_Test $test * @param PHPUnit_Framework_AssertionFailedError $e * @param float $time */ public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { $this->writeCase( 'fail', $time, PHPUnit_Util_Filter::getFilteredStacktrace($e, false), $e->getMessage(), $test ); $this->currentTestPass = false; } /** * Incomplete test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->writeCase( 'error', $time, PHPUnit_Util_Filter::getFilteredStacktrace($e, false), 'Incomplete Test: ' . $e->getMessage(), $test ); $this->currentTestPass = false; } /** * Risky test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time * @since Method available since Release 4.0.0 */ public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->writeCase( 'error', $time, PHPUnit_Util_Filter::getFilteredStacktrace($e, false), 'Risky Test: ' . $e->getMessage(), $test ); $this->currentTestPass = false; } /** * Skipped test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->writeCase( 'error', $time, PHPUnit_Util_Filter::getFilteredStacktrace($e, false), 'Skipped Test: ' . $e->getMessage(), $test ); $this->currentTestPass = false; } /** * A testsuite started. * * @param PHPUnit_Framework_TestSuite $suite */ public function startTestSuite(PHPUnit_Framework_TestSuite $suite) { $this->currentTestSuiteName = $suite->getName(); $this->currentTestName = ''; $this->write( array( 'event' => 'suiteStart', 'suite' => $this->currentTestSuiteName, 'tests' => count($suite) ) ); } /** * A testsuite ended. * * @param PHPUnit_Framework_TestSuite $suite */ public function endTestSuite(PHPUnit_Framework_TestSuite $suite) { $this->currentTestSuiteName = ''; $this->currentTestName = ''; } /** * A test started. * * @param PHPUnit_Framework_Test $test */ public function startTest(PHPUnit_Framework_Test $test) { $this->currentTestName = PHPUnit_Util_Test::describe($test); $this->currentTestPass = true; $this->write( array( 'event' => 'testStart', 'suite' => $this->currentTestSuiteName, 'test' => $this->currentTestName ) ); } /** * A test ended. * * @param PHPUnit_Framework_Test $test * @param float $time */ public function endTest(PHPUnit_Framework_Test $test, $time) { if ($this->currentTestPass) { $this->writeCase('pass', $time, array(), '', $test); } } /** * @param string $status * @param float $time * @param array $trace * @param string $message * @param PHPUnit_Framework_TestCase|null $test */ protected function writeCase($status, $time, array $trace = array(), $message = '', $test = null) { $output = ''; // take care of TestSuite producing error (e.g. by running into exception) as TestSuite doesn't have hasOutput if ($test !== null && method_exists($test, 'hasOutput') && $test->hasOutput()) { $output = $test->getActualOutput(); } $this->write( array( 'event' => 'test', 'suite' => $this->currentTestSuiteName, 'test' => $this->currentTestName, 'status' => $status, 'time' => $time, 'trace' => $trace, 'message' => PHPUnit_Util_String::convertToUtf8($message), 'output' => $output, ) ); } /** * @param string $buffer */ public function write($buffer) { array_walk_recursive($buffer, function (&$input) { if (is_string($input)) { $input = PHPUnit_Util_String::convertToUtf8($input); } }); $flags = 0; if (defined('JSON_PRETTY_PRINT')) { $flags |= JSON_PRETTY_PRINT; } parent::write(json_encode($buffer, $flags)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A TestListener that generates a logfile of the * test execution using the Test Anything Protocol (TAP). * * @since Class available since Release 3.0.0 */ class PHPUnit_Util_Log_TAP extends PHPUnit_Util_Printer implements PHPUnit_Framework_TestListener { /** * @var int */ protected $testNumber = 0; /** * @var int */ protected $testSuiteLevel = 0; /** * @var bool */ protected $testSuccessful = true; /** * Constructor. * * @param mixed $out * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.3.4 */ public function __construct($out = null) { parent::__construct($out); $this->write("TAP version 13\n"); } /** * An error occurred. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->writeNotOk($test, 'Error'); } /** * A failure occurred. * * @param PHPUnit_Framework_Test $test * @param PHPUnit_Framework_AssertionFailedError $e * @param float $time */ public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { $this->writeNotOk($test, 'Failure'); $message = explode( "\n", PHPUnit_Framework_TestFailure::exceptionToString($e) ); $diagnostic = array( 'message' => $message[0], 'severity' => 'fail' ); if ($e instanceof PHPUnit_Framework_ExpectationFailedException) { $cf = $e->getComparisonFailure(); if ($cf !== null) { $diagnostic['data'] = array( 'got' => $cf->getActual(), 'expected' => $cf->getExpected() ); } } $yaml = new Symfony\Component\Yaml\Dumper; $this->write( sprintf( " ---\n%s ...\n", $yaml->dump($diagnostic, 2, 2) ) ); } /** * Incomplete test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->writeNotOk($test, '', 'TODO Incomplete Test'); } /** * Risky test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time * @since Method available since Release 4.0.0 */ public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->write( sprintf( "ok %d - # RISKY%s\n", $this->testNumber, $e->getMessage() != '' ? ' ' . $e->getMessage() : '' ) ); $this->testSuccessful = false; } /** * Skipped test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time * @since Method available since Release 3.0.0 */ public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->write( sprintf( "ok %d - # SKIP%s\n", $this->testNumber, $e->getMessage() != '' ? ' ' . $e->getMessage() : '' ) ); $this->testSuccessful = false; } /** * A testsuite started. * * @param PHPUnit_Framework_TestSuite $suite */ public function startTestSuite(PHPUnit_Framework_TestSuite $suite) { $this->testSuiteLevel++; } /** * A testsuite ended. * * @param PHPUnit_Framework_TestSuite $suite */ public function endTestSuite(PHPUnit_Framework_TestSuite $suite) { $this->testSuiteLevel--; if ($this->testSuiteLevel == 0) { $this->write(sprintf("1..%d\n", $this->testNumber)); } } /** * A test started. * * @param PHPUnit_Framework_Test $test */ public function startTest(PHPUnit_Framework_Test $test) { $this->testNumber++; $this->testSuccessful = true; } /** * A test ended. * * @param PHPUnit_Framework_Test $test * @param float $time */ public function endTest(PHPUnit_Framework_Test $test, $time) { if ($this->testSuccessful === true) { $this->write( sprintf( "ok %d - %s\n", $this->testNumber, PHPUnit_Util_Test::describe($test) ) ); } $this->writeDiagnostics($test); } /** * @param PHPUnit_Framework_Test $test * @param string $prefix * @param string $directive */ protected function writeNotOk(PHPUnit_Framework_Test $test, $prefix = '', $directive = '') { $this->write( sprintf( "not ok %d - %s%s%s\n", $this->testNumber, $prefix != '' ? $prefix . ': ' : '', PHPUnit_Util_Test::describe($test), $directive != '' ? ' # ' . $directive : '' ) ); $this->testSuccessful = false; } /** * @param PHPUnit_Framework_Test $test */ private function writeDiagnostics(PHPUnit_Framework_Test $test) { if (!$test instanceof PHPUnit_Framework_TestCase) { return; } if (!$test->hasOutput()) { return; } foreach (explode("\n", trim($test->getActualOutput())) as $line) { $this->write( sprintf( "# %s\n", $line ) ); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * A TestListener that generates a logfile of the test execution in XML markup. * * The XML markup used is the same as the one that is used by the JUnit Ant task. * * @since Class available since Release 2.1.0 */ class PHPUnit_Util_Log_JUnit extends PHPUnit_Util_Printer implements PHPUnit_Framework_TestListener { /** * @var DOMDocument */ protected $document; /** * @var DOMElement */ protected $root; /** * @var bool */ protected $logIncompleteSkipped = false; /** * @var bool */ protected $writeDocument = true; /** * @var DOMElement[] */ protected $testSuites = array(); /** * @var integer[] */ protected $testSuiteTests = array(0); /** * @var integer[] */ protected $testSuiteAssertions = array(0); /** * @var integer[] */ protected $testSuiteErrors = array(0); /** * @var integer[] */ protected $testSuiteFailures = array(0); /** * @var integer[] */ protected $testSuiteTimes = array(0); /** * @var int */ protected $testSuiteLevel = 0; /** * @var DOMElement */ protected $currentTestCase = null; /** * @var bool */ protected $attachCurrentTestCase = true; /** * Constructor. * * @param mixed $out * @param bool $logIncompleteSkipped */ public function __construct($out = null, $logIncompleteSkipped = false) { $this->document = new DOMDocument('1.0', 'UTF-8'); $this->document->formatOutput = true; $this->root = $this->document->createElement('testsuites'); $this->document->appendChild($this->root); parent::__construct($out); $this->logIncompleteSkipped = $logIncompleteSkipped; } /** * Flush buffer and close output. */ public function flush() { if ($this->writeDocument === true) { $this->write($this->getXML()); } parent::flush(); } /** * An error occurred. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) { if ($this->currentTestCase === null) { return; } if ($test instanceof PHPUnit_Framework_SelfDescribing) { $buffer = $test->toString() . "\n"; } else { $buffer = ''; } $buffer .= PHPUnit_Framework_TestFailure::exceptionToString($e) . "\n" . PHPUnit_Util_Filter::getFilteredStacktrace($e); $error = $this->document->createElement( 'error', PHPUnit_Util_XML::prepareString($buffer) ); $error->setAttribute('type', get_class($e)); $this->currentTestCase->appendChild($error); $this->testSuiteErrors[$this->testSuiteLevel]++; } /** * A failure occurred. * * @param PHPUnit_Framework_Test $test * @param PHPUnit_Framework_AssertionFailedError $e * @param float $time */ public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { if ($this->currentTestCase === null) { return; } if ($test instanceof PHPUnit_Framework_SelfDescribing) { $buffer = $test->toString() . "\n"; } else { $buffer = ''; } $buffer .= PHPUnit_Framework_TestFailure::exceptionToString($e) . "\n" . PHPUnit_Util_Filter::getFilteredStacktrace($e); $failure = $this->document->createElement( 'failure', PHPUnit_Util_XML::prepareString($buffer) ); $failure->setAttribute('type', get_class($e)); $this->currentTestCase->appendChild($failure); $this->testSuiteFailures[$this->testSuiteLevel]++; } /** * Incomplete test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) { if ($this->logIncompleteSkipped && $this->currentTestCase !== null) { $error = $this->document->createElement( 'error', PHPUnit_Util_XML::prepareString( "Incomplete Test\n" . PHPUnit_Util_Filter::getFilteredStacktrace($e) ) ); $error->setAttribute('type', get_class($e)); $this->currentTestCase->appendChild($error); $this->testSuiteErrors[$this->testSuiteLevel]++; } else { $this->attachCurrentTestCase = false; } } /** * Risky test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time * @since Method available since Release 4.0.0 */ public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) { if ($this->logIncompleteSkipped && $this->currentTestCase !== null) { $error = $this->document->createElement( 'error', PHPUnit_Util_XML::prepareString( "Risky Test\n" . PHPUnit_Util_Filter::getFilteredStacktrace($e) ) ); $error->setAttribute('type', get_class($e)); $this->currentTestCase->appendChild($error); $this->testSuiteErrors[$this->testSuiteLevel]++; } else { $this->attachCurrentTestCase = false; } } /** * Skipped test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time * @since Method available since Release 3.0.0 */ public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) { if ($this->logIncompleteSkipped && $this->currentTestCase !== null) { $error = $this->document->createElement( 'error', PHPUnit_Util_XML::prepareString( "Skipped Test\n" . PHPUnit_Util_Filter::getFilteredStacktrace($e) ) ); $error->setAttribute('type', get_class($e)); $this->currentTestCase->appendChild($error); $this->testSuiteErrors[$this->testSuiteLevel]++; } else { $this->attachCurrentTestCase = false; } } /** * A testsuite started. * * @param PHPUnit_Framework_TestSuite $suite * @since Method available since Release 2.2.0 */ public function startTestSuite(PHPUnit_Framework_TestSuite $suite) { $testSuite = $this->document->createElement('testsuite'); $testSuite->setAttribute('name', $suite->getName()); if (class_exists($suite->getName(), false)) { try { $class = new ReflectionClass($suite->getName()); $testSuite->setAttribute('file', $class->getFileName()); } catch (ReflectionException $e) { } } if ($this->testSuiteLevel > 0) { $this->testSuites[$this->testSuiteLevel]->appendChild($testSuite); } else { $this->root->appendChild($testSuite); } $this->testSuiteLevel++; $this->testSuites[$this->testSuiteLevel] = $testSuite; $this->testSuiteTests[$this->testSuiteLevel] = 0; $this->testSuiteAssertions[$this->testSuiteLevel] = 0; $this->testSuiteErrors[$this->testSuiteLevel] = 0; $this->testSuiteFailures[$this->testSuiteLevel] = 0; $this->testSuiteTimes[$this->testSuiteLevel] = 0; } /** * A testsuite ended. * * @param PHPUnit_Framework_TestSuite $suite * @since Method available since Release 2.2.0 */ public function endTestSuite(PHPUnit_Framework_TestSuite $suite) { $this->testSuites[$this->testSuiteLevel]->setAttribute( 'tests', $this->testSuiteTests[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'assertions', $this->testSuiteAssertions[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'failures', $this->testSuiteFailures[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'errors', $this->testSuiteErrors[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'time', sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel]) ); if ($this->testSuiteLevel > 1) { $this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel]; $this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel]; $this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel]; $this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel]; $this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel]; } $this->testSuiteLevel--; } /** * A test started. * * @param PHPUnit_Framework_Test $test */ public function startTest(PHPUnit_Framework_Test $test) { $testCase = $this->document->createElement('testcase'); $testCase->setAttribute('name', $test->getName()); if ($test instanceof PHPUnit_Framework_TestCase) { $class = new ReflectionClass($test); $methodName = $test->getName(); if ($class->hasMethod($methodName)) { $method = $class->getMethod($test->getName()); $testCase->setAttribute('class', $class->getName()); $testCase->setAttribute('file', $class->getFileName()); $testCase->setAttribute('line', $method->getStartLine()); } } $this->currentTestCase = $testCase; } /** * A test ended. * * @param PHPUnit_Framework_Test $test * @param float $time */ public function endTest(PHPUnit_Framework_Test $test, $time) { if ($this->attachCurrentTestCase) { if ($test instanceof PHPUnit_Framework_TestCase) { $numAssertions = $test->getNumAssertions(); $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions; $this->currentTestCase->setAttribute( 'assertions', $numAssertions ); } $this->currentTestCase->setAttribute( 'time', sprintf('%F', $time) ); $this->testSuites[$this->testSuiteLevel]->appendChild( $this->currentTestCase ); $this->testSuiteTests[$this->testSuiteLevel]++; $this->testSuiteTimes[$this->testSuiteLevel] += $time; if (method_exists($test, 'hasOutput') && $test->hasOutput()) { $systemOut = $this->document->createElement('system-out'); $systemOut->appendChild( $this->document->createTextNode($test->getActualOutput()) ); $this->currentTestCase->appendChild($systemOut); } } $this->attachCurrentTestCase = true; $this->currentTestCase = null; } /** * Returns the XML as a string. * * @return string * @since Method available since Release 2.2.0 */ public function getXML() { return $this->document->saveXML(); } /** * Enables or disables the writing of the document * in flush(). * * This is a "hack" needed for the integration of * PHPUnit with Phing. * * @return string * @since Method available since Release 2.2.0 */ public function setWriteDocument($flag) { if (is_bool($flag)) { $this->writeDocument = $flag; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Utility class for blacklisting PHPUnit's own source code files. * * @since Class available since Release 4.0.0 */ class PHPUnit_Util_Blacklist { /** * @var array */ public static $blacklistedClassNames = array( 'File_Iterator' => 1, 'PHP_CodeCoverage' => 1, 'PHP_Invoker' => 1, 'PHP_Timer' => 1, 'PHP_Token' => 1, 'PHPUnit_Framework_TestCase' => 2, 'PHPUnit_Extensions_Database_TestCase' => 2, 'PHPUnit_Framework_MockObject_Generator' => 2, 'PHPUnit_Extensions_SeleniumTestCase' => 2, 'Text_Template' => 1, 'Symfony\Component\Yaml\Yaml' => 1, 'SebastianBergmann\Diff\Diff' => 1, 'SebastianBergmann\Environment\Runtime' => 1, 'SebastianBergmann\Comparator\Comparator' => 1, 'SebastianBergmann\Exporter\Exporter' => 1, 'SebastianBergmann\GlobalState\Snapshot' => 1, 'SebastianBergmann\RecursionContext\Context' => 1, 'SebastianBergmann\Version' => 1, 'Composer\Autoload\ClassLoader' => 1, 'Doctrine\Instantiator\Instantiator' => 1, 'phpDocumentor\Reflection\DocBlock' => 1, 'Prophecy\Prophet' => 1 ); /** * @var array */ private static $directories; /** * @return array * @since Method available since Release 4.1.0 */ public function getBlacklistedDirectories() { $this->initialize(); return self::$directories; } /** * @param string $file * @return bool */ public function isBlacklisted($file) { if (defined('PHPUNIT_TESTSUITE')) { return false; } $this->initialize(); foreach (self::$directories as $directory) { if (strpos($file, $directory) === 0) { return true; } } return false; } private function initialize() { if (self::$directories === null) { self::$directories = array(); foreach (self::$blacklistedClassNames as $className => $parent) { if (!class_exists($className)) { continue; } $reflector = new ReflectionClass($className); $directory = $reflector->getFileName(); for ($i = 0; $i < $parent; $i++) { $directory = dirname($directory); } self::$directories[] = $directory; } // Hide process isolation workaround on Windows. // @see PHPUnit_Util_PHP::factory() // @see PHPUnit_Util_PHP_Windows::process() if (DIRECTORY_SEPARATOR === '\\') { // tempnam() prefix is limited to first 3 chars. // @see http://php.net/manual/en/function.tempnam.php self::$directories[] = sys_get_temp_dir() . '\\PHP'; } } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Iterator for test suites. * * @since Class available since Release 3.1.0 */ class PHPUnit_Util_TestSuiteIterator implements RecursiveIterator { /** * @var int */ protected $position; /** * @var PHPUnit_Framework_Test[] */ protected $tests; /** * @param PHPUnit_Framework_TestSuite $testSuite */ public function __construct(PHPUnit_Framework_TestSuite $testSuite) { $this->tests = $testSuite->tests(); } /** * Rewinds the Iterator to the first element. */ public function rewind() { $this->position = 0; } /** * Checks if there is a current element after calls to rewind() or next(). * * @return bool */ public function valid() { return $this->position < count($this->tests); } /** * Returns the key of the current element. * * @return int */ public function key() { return $this->position; } /** * Returns the current element. * * @return PHPUnit_Framework_Test */ public function current() { return $this->valid() ? $this->tests[$this->position] : null; } /** * Moves forward to next element. */ public function next() { $this->position++; } /** * Returns the sub iterator for the current element. * * @return PHPUnit_Util_TestSuiteIterator */ public function getChildren() { return new self( $this->tests[$this->position] ); } /** * Checks whether the current element has children. * * @return bool */ public function hasChildren() { return $this->tests[$this->position] instanceof PHPUnit_Framework_TestSuite; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Utility class for code filtering. * * @since Class available since Release 2.0.0 */ class PHPUnit_Util_Filter { /** * Filters stack frames from PHPUnit classes. * * @param Exception $e * @param bool $asString * @return string */ public static function getFilteredStacktrace(Exception $e, $asString = true) { $prefix = false; $script = realpath($GLOBALS['_SERVER']['SCRIPT_NAME']); if (defined('__PHPUNIT_PHAR_ROOT__')) { $prefix = __PHPUNIT_PHAR_ROOT__; } if ($asString === true) { $filteredStacktrace = ''; } else { $filteredStacktrace = array(); } if ($e instanceof PHPUnit_Framework_SyntheticError) { $eTrace = $e->getSyntheticTrace(); $eFile = $e->getSyntheticFile(); $eLine = $e->getSyntheticLine(); } elseif ($e instanceof PHPUnit_Framework_Exception) { $eTrace = $e->getSerializableTrace(); $eFile = $e->getFile(); $eLine = $e->getLine(); } else { if ($e->getPrevious()) { $e = $e->getPrevious(); } $eTrace = $e->getTrace(); $eFile = $e->getFile(); $eLine = $e->getLine(); } if (!self::frameExists($eTrace, $eFile, $eLine)) { array_unshift( $eTrace, array('file' => $eFile, 'line' => $eLine) ); } $blacklist = new PHPUnit_Util_Blacklist; foreach ($eTrace as $frame) { if (isset($frame['file']) && is_file($frame['file']) && !$blacklist->isBlacklisted($frame['file']) && ($prefix === false || strpos($frame['file'], $prefix) !== 0) && $frame['file'] !== $script) { if ($asString === true) { $filteredStacktrace .= sprintf( "%s:%s\n", $frame['file'], isset($frame['line']) ? $frame['line'] : '?' ); } else { $filteredStacktrace[] = $frame; } } } return $filteredStacktrace; } /** * @param array $trace * @param string $file * @param int $line * @return bool * @since Method available since Release 3.3.2 */ private static function frameExists(array $trace, $file, $line) { foreach ($trace as $frame) { if (isset($frame['file']) && $frame['file'] == $file && isset($frame['line']) && $frame['line'] == $line) { return true; } } return false; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Prints TestDox documentation in HTML format. * * @since Class available since Release 2.1.0 */ class PHPUnit_Util_TestDox_ResultPrinter_HTML extends PHPUnit_Util_TestDox_ResultPrinter { /** * @var bool */ protected $printsHTML = true; /** * Handler for 'start run' event. */ protected function startRun() { $this->write(''); } /** * Handler for 'start class' event. * * @param string $name */ protected function startClass($name) { $this->write( '

    ' . $this->currentTestClassPrettified . '

      ' ); } /** * Handler for 'on test' event. * * @param string $name * @param bool $success */ protected function onTest($name, $success = true) { if (!$success) { $strikeOpen = ''; $strikeClose = ''; } else { $strikeOpen = ''; $strikeClose = ''; } $this->write('
    • ' . $strikeOpen . $name . $strikeClose . '
    • '); } /** * Handler for 'end class' event. * * @param string $name */ protected function endClass($name) { $this->write('
    '); } /** * Handler for 'end run' event. */ protected function endRun() { $this->write(''); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Prints TestDox documentation in text format. * * @since Class available since Release 2.1.0 */ class PHPUnit_Util_TestDox_ResultPrinter_Text extends PHPUnit_Util_TestDox_ResultPrinter { /** * Handler for 'start class' event. * * @param string $name */ protected function startClass($name) { $this->write($this->currentTestClassPrettified . "\n"); } /** * Handler for 'on test' event. * * @param string $name * @param bool $success */ protected function onTest($name, $success = true) { if ($success) { $this->write(' [x] '); } else { $this->write(' [ ] '); } $this->write($name . "\n"); } /** * Handler for 'end class' event. * * @param string $name */ protected function endClass($name) { $this->write("\n"); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Base class for printers of TestDox documentation. * * @since Class available since Release 2.1.0 */ abstract class PHPUnit_Util_TestDox_ResultPrinter extends PHPUnit_Util_Printer implements PHPUnit_Framework_TestListener { /** * @var PHPUnit_Util_TestDox_NamePrettifier */ protected $prettifier; /** * @var string */ protected $testClass = ''; /** * @var int */ protected $testStatus = false; /** * @var array */ protected $tests = array(); /** * @var int */ protected $successful = 0; /** * @var int */ protected $failed = 0; /** * @var int */ protected $risky = 0; /** * @var int */ protected $skipped = 0; /** * @var int */ protected $incomplete = 0; /** * @var string */ protected $currentTestClassPrettified; /** * @var string */ protected $currentTestMethodPrettified; /** * Constructor. * * @param resource $out */ public function __construct($out = null) { parent::__construct($out); $this->prettifier = new PHPUnit_Util_TestDox_NamePrettifier; $this->startRun(); } /** * Flush buffer and close output. */ public function flush() { $this->doEndClass(); $this->endRun(); parent::flush(); } /** * An error occurred. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) { if (!$this->isOfInterest($test)) { return; } $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_ERROR; $this->failed++; } /** * A failure occurred. * * @param PHPUnit_Framework_Test $test * @param PHPUnit_Framework_AssertionFailedError $e * @param float $time */ public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { if (!$this->isOfInterest($test)) { return; } $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE; $this->failed++; } /** * Incomplete test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) { if (!$this->isOfInterest($test)) { return; } $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE; $this->incomplete++; } /** * Risky test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time * @since Method available since Release 4.0.0 */ public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) { if (!$this->isOfInterest($test)) { return; } $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_RISKY; $this->risky++; } /** * Skipped test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time * @since Method available since Release 3.0.0 */ public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) { if (!$this->isOfInterest($test)) { return; } $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED; $this->skipped++; } /** * A testsuite started. * * @param PHPUnit_Framework_TestSuite $suite * @since Method available since Release 2.2.0 */ public function startTestSuite(PHPUnit_Framework_TestSuite $suite) { } /** * A testsuite ended. * * @param PHPUnit_Framework_TestSuite $suite * @since Method available since Release 2.2.0 */ public function endTestSuite(PHPUnit_Framework_TestSuite $suite) { } /** * A test started. * * @param PHPUnit_Framework_Test $test */ public function startTest(PHPUnit_Framework_Test $test) { if (!$this->isOfInterest($test)) { return; } $class = get_class($test); if ($this->testClass != $class) { if ($this->testClass != '') { $this->doEndClass(); } $this->currentTestClassPrettified = $this->prettifier->prettifyTestClass($class); $this->startClass($class); $this->testClass = $class; $this->tests = array(); } $prettified = false; $annotations = $test->getAnnotations(); if (isset($annotations['method']['testdox'][0])) { $this->currentTestMethodPrettified = $annotations['method']['testdox'][0]; $prettified = true; } if (!$prettified) { $this->currentTestMethodPrettified = $this->prettifier->prettifyTestMethod($test->getName(false)); } $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_PASSED; } /** * A test ended. * * @param PHPUnit_Framework_Test $test * @param float $time */ public function endTest(PHPUnit_Framework_Test $test, $time) { if (!$this->isOfInterest($test)) { return; } if (!isset($this->tests[$this->currentTestMethodPrettified])) { if ($this->testStatus == PHPUnit_Runner_BaseTestRunner::STATUS_PASSED) { $this->tests[$this->currentTestMethodPrettified]['success'] = 1; $this->tests[$this->currentTestMethodPrettified]['failure'] = 0; } else { $this->tests[$this->currentTestMethodPrettified]['success'] = 0; $this->tests[$this->currentTestMethodPrettified]['failure'] = 1; } } else { if ($this->testStatus == PHPUnit_Runner_BaseTestRunner::STATUS_PASSED) { $this->tests[$this->currentTestMethodPrettified]['success']++; } else { $this->tests[$this->currentTestMethodPrettified]['failure']++; } } $this->currentTestClassPrettified = null; $this->currentTestMethodPrettified = null; } /** * @since Method available since Release 2.3.0 */ protected function doEndClass() { foreach ($this->tests as $name => $data) { $this->onTest($name, $data['failure'] == 0); } $this->endClass($this->testClass); } /** * Handler for 'start run' event. */ protected function startRun() { } /** * Handler for 'start class' event. * * @param string $name */ protected function startClass($name) { } /** * Handler for 'on test' event. * * @param string $name * @param bool $success */ protected function onTest($name, $success = true) { } /** * Handler for 'end class' event. * * @param string $name */ protected function endClass($name) { } /** * Handler for 'end run' event. */ protected function endRun() { } private function isOfInterest(PHPUnit_Framework_Test $test) { return $test instanceof PHPUnit_Framework_TestCase && get_class($test) != 'PHPUnit_Framework_Warning'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Prettifies class and method names for use in TestDox documentation. * * @since Class available since Release 2.1.0 */ class PHPUnit_Util_TestDox_NamePrettifier { /** * @var string */ protected $prefix = 'Test'; /** * @var string */ protected $suffix = 'Test'; /** * @var array */ protected $strings = array(); /** * Prettifies the name of a test class. * * @param string $name * @return string */ public function prettifyTestClass($name) { $title = $name; if ($this->suffix !== null && $this->suffix == substr($name, -1 * strlen($this->suffix))) { $title = substr($title, 0, strripos($title, $this->suffix)); } if ($this->prefix !== null && $this->prefix == substr($name, 0, strlen($this->prefix))) { $title = substr($title, strlen($this->prefix)); } if (substr($title, 0, 1) == '\\') { $title = substr($title, 1); } return $title; } /** * Prettifies the name of a test method. * * @param string $name * @return string */ public function prettifyTestMethod($name) { $buffer = ''; if (!is_string($name) || strlen($name) == 0) { return $buffer; } $string = preg_replace('#\d+$#', '', $name, -1, $count); if (in_array($string, $this->strings)) { $name = $string; } elseif ($count == 0) { $this->strings[] = $string; } if (strpos($name, '_') !== false) { return str_replace('_', ' ', $name); } $max = strlen($name); if (substr($name, 0, 4) == 'test') { $offset = 4; } else { $offset = 0; $name[0] = strtoupper($name[0]); } $wasNumeric = false; for ($i = $offset; $i < $max; $i++) { if ($i > $offset && ord($name[$i]) >= 65 && ord($name[$i]) <= 90) { $buffer .= ' ' . strtolower($name[$i]); } else { $isNumeric = is_numeric($name[$i]); if (!$wasNumeric && $isNumeric) { $buffer .= ' '; $wasNumeric = true; } if ($wasNumeric && !$isNumeric) { $wasNumeric = false; } $buffer .= $name[$i]; } } return $buffer; } /** * Sets the prefix of test names. * * @param string $prefix */ public function setPrefix($prefix) { $this->prefix = $prefix; } /** * Sets the suffix of test names. * * @param string $suffix */ public function setSuffix($suffix) { $this->suffix = $suffix; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Error handler that converts PHP errors and warnings to exceptions. * * @since Class available since Release 4.2.0 */ class PHPUnit_Util_Regex { public static function pregMatchSafe($pattern, $subject, $matches = null, $flags = 0, $offset = 0) { $handler_terminator = PHPUnit_Util_ErrorHandler::handleErrorOnce(E_WARNING); $match = preg_match($pattern, $subject, $matches, $flags, $offset); $handler_terminator(); // cleaning return $match; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Utility class for textual type (and value) representation. * * @since Class available since Release 3.0.0 */ class PHPUnit_Util_Type { public static function isType($type) { return in_array( $type, array( 'numeric', 'integer', 'int', 'float', 'string', 'boolean', 'bool', 'null', 'array', 'object', 'resource', 'scalar' ) ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use SebastianBergmann\Environment\Runtime; /** * Default utility for PHP sub-processes. * * @since Class available since Release 3.5.12 */ class PHPUnit_Util_PHP_Default extends PHPUnit_Util_PHP { /** * Runs a single job (PHP code) using a separate PHP process. * * @param string $job * @param array $settings * @return array * @throws PHPUnit_Framework_Exception */ public function runJob($job, array $settings = array()) { $runtime = new Runtime; $process = proc_open( $runtime->getBinary() . $this->settingsToParameters($settings), array( 0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w') ), $pipes ); if (!is_resource($process)) { throw new PHPUnit_Framework_Exception( 'Unable to spawn worker process' ); } $this->process($pipes[0], $job); fclose($pipes[0]); $stdout = stream_get_contents($pipes[1]); fclose($pipes[1]); $stderr = stream_get_contents($pipes[2]); fclose($pipes[2]); proc_close($process); $this->cleanup(); return array('stdout' => $stdout, 'stderr' => $stderr); } /** * @param resource $pipe * @param string $job * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.5.12 */ protected function process($pipe, $job) { fwrite($pipe, $job); } /** * @since Method available since Release 3.5.12 */ protected function cleanup() { } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use SebastianBergmann\Environment\Runtime; /** * Windows utility for PHP sub-processes. * * @since Class available since Release 3.5.12 */ class PHPUnit_Util_PHP_Windows extends PHPUnit_Util_PHP_Default { /** * @var string */ private $tempFile; /** * {@inheritdoc} * * Reading from STDOUT or STDERR hangs forever on Windows if the output is * too large. * * @see https://bugs.php.net/bug.php?id=51800 */ public function runJob($job, array $settings = array()) { $runtime = new Runtime; if (false === $stdout_handle = tmpfile()) { throw new PHPUnit_Framework_Exception( 'A temporary file could not be created; verify that your TEMP environment variable is writable' ); } $process = proc_open( $runtime->getBinary() . $this->settingsToParameters($settings), array( 0 => array('pipe', 'r'), 1 => $stdout_handle, 2 => array('pipe', 'w') ), $pipes ); if (!is_resource($process)) { throw new PHPUnit_Framework_Exception( 'Unable to spawn worker process' ); } $this->process($pipes[0], $job); fclose($pipes[0]); $stderr = stream_get_contents($pipes[2]); fclose($pipes[2]); proc_close($process); rewind($stdout_handle); $stdout = stream_get_contents($stdout_handle); fclose($stdout_handle); $this->cleanup(); return array('stdout' => $stdout, 'stderr' => $stderr); } /** * @param resource $pipe * @param string $job * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.5.12 */ protected function process($pipe, $job) { if (!($this->tempFile = tempnam(sys_get_temp_dir(), 'PHPUnit')) || file_put_contents($this->tempFile, $job) === false) { throw new PHPUnit_Framework_Exception( 'Unable to write temporary file' ); } fwrite( $pipe, 'tempFile, true) . '; ?>' ); } /** * @since Method available since Release 3.5.12 */ protected function cleanup() { unlink($this->tempFile); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Utility methods to load PHP sourcefiles. * * @since Class available since Release 2.3.0 */ class PHPUnit_Util_Fileloader { /** * Checks if a PHP sourcefile is readable. * The sourcefile is loaded through the load() method. * * @param string $filename * @return string * @throws PHPUnit_Framework_Exception */ public static function checkAndLoad($filename) { $includePathFilename = stream_resolve_include_path($filename); if (!$includePathFilename || !is_readable($includePathFilename)) { throw new PHPUnit_Framework_Exception( sprintf('Cannot open file "%s".' . "\n", $filename) ); } self::load($includePathFilename); return $includePathFilename; } /** * Loads a PHP sourcefile. * * @param string $filename * @return mixed * @since Method available since Release 3.0.0 */ public static function load($filename) { $oldVariableNames = array_keys(get_defined_vars()); include_once $filename; $newVariables = get_defined_vars(); $newVariableNames = array_diff( array_keys($newVariables), $oldVariableNames ); foreach ($newVariableNames as $variableName) { if ($variableName != 'oldVariableNames') { $GLOBALS[$variableName] = $newVariables[$variableName]; } } return $filename; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * XML helpers. * * @since Class available since Release 3.2.0 */ class PHPUnit_Util_XML { /** * Escapes a string for the use in XML documents * Any Unicode character is allowed, excluding the surrogate blocks, FFFE, * and FFFF (not even as character reference). * See http://www.w3.org/TR/xml/#charsets * * @param string $string * @return string * @since Method available since Release 3.4.6 */ public static function prepareString($string) { return preg_replace( '/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/', '', htmlspecialchars( PHPUnit_Util_String::convertToUtf8($string), ENT_QUOTES, 'UTF-8' ) ); } /** * Loads an XML (or HTML) file into a DOMDocument object. * * @param string $filename * @param bool $isHtml * @param bool $xinclude * @param bool $strict * @return DOMDocument * @since Method available since Release 3.3.0 */ public static function loadFile($filename, $isHtml = false, $xinclude = false, $strict = false) { $reporting = error_reporting(0); $contents = file_get_contents($filename); error_reporting($reporting); if ($contents === false) { throw new PHPUnit_Framework_Exception( sprintf( 'Could not read "%s".', $filename ) ); } return self::load($contents, $isHtml, $filename, $xinclude, $strict); } /** * Load an $actual document into a DOMDocument. This is called * from the selector assertions. * * If $actual is already a DOMDocument, it is returned with * no changes. Otherwise, $actual is loaded into a new DOMDocument * as either HTML or XML, depending on the value of $isHtml. If $isHtml is * false and $xinclude is true, xinclude is performed on the loaded * DOMDocument. * * Note: prior to PHPUnit 3.3.0, this method loaded a file and * not a string as it currently does. To load a file into a * DOMDocument, use loadFile() instead. * * @param string|DOMDocument $actual * @param bool $isHtml * @param string $filename * @param bool $xinclude * @param bool $strict * @return DOMDocument * @since Method available since Release 3.3.0 */ public static function load($actual, $isHtml = false, $filename = '', $xinclude = false, $strict = false) { if ($actual instanceof DOMDocument) { return $actual; } // Required for XInclude on Windows. if ($xinclude) { $cwd = getcwd(); @chdir(dirname($filename)); } $document = new DOMDocument; $document->preserveWhiteSpace = false; $internal = libxml_use_internal_errors(true); $message = ''; $reporting = error_reporting(0); if ('' !== $filename) { // Necessary for xinclude $document->documentURI = $filename; } if ($isHtml) { $loaded = $document->loadHTML($actual); } else { $loaded = $document->loadXML($actual); } if (!$isHtml && $xinclude) { $document->xinclude(); } foreach (libxml_get_errors() as $error) { $message .= "\n" . $error->message; } libxml_use_internal_errors($internal); error_reporting($reporting); if ($xinclude) { @chdir($cwd); } if ($loaded === false || ($strict && $message !== '')) { if ($filename !== '') { throw new PHPUnit_Framework_Exception( sprintf( 'Could not load "%s".%s', $filename, $message != '' ? "\n" . $message : '' ) ); } else { throw new PHPUnit_Framework_Exception($message); } } return $document; } /** * @param DOMNode $node * @return string * @since Method available since Release 3.4.0 */ public static function nodeToText(DOMNode $node) { if ($node->childNodes->length == 1) { return $node->textContent; } $result = ''; foreach ($node->childNodes as $childNode) { $result .= $node->ownerDocument->saveXML($childNode); } return $result; } /** * @param DOMNode $node * @since Method available since Release 3.3.0 */ public static function removeCharacterDataNodes(DOMNode $node) { if ($node->hasChildNodes()) { for ($i = $node->childNodes->length - 1; $i >= 0; $i--) { if (($child = $node->childNodes->item($i)) instanceof DOMCharacterData) { $node->removeChild($child); } } } } /** * "Convert" a DOMElement object into a PHP variable. * * @param DOMElement $element * @return mixed * @since Method available since Release 3.4.0 */ public static function xmlToVariable(DOMElement $element) { $variable = null; switch ($element->tagName) { case 'array': $variable = array(); foreach ($element->getElementsByTagName('element') as $element) { $item = $element->childNodes->item(0); if ($item instanceof DOMText) { $item = $element->childNodes->item(1); } $value = self::xmlToVariable($item); if ($element->hasAttribute('key')) { $variable[(string) $element->getAttribute('key')] = $value; } else { $variable[] = $value; } } break; case 'object': $className = $element->getAttribute('class'); if ($element->hasChildNodes()) { $arguments = $element->childNodes->item(1)->childNodes; $constructorArgs = array(); foreach ($arguments as $argument) { if ($argument instanceof DOMElement) { $constructorArgs[] = self::xmlToVariable($argument); } } $class = new ReflectionClass($className); $variable = $class->newInstanceArgs($constructorArgs); } else { $variable = new $className; } break; case 'boolean': $variable = $element->textContent == 'true' ? true : false; break; case 'integer': case 'double': case 'string': $variable = $element->textContent; settype($variable, $element->tagName); break; } return $variable; } /** * Validate list of keys in the associative array. * * @param array $hash * @param array $validKeys * @return array * @throws PHPUnit_Framework_Exception * @since Method available since Release 3.3.0 */ public static function assertValidKeys(array $hash, array $validKeys) { $valids = array(); // Normalize validation keys so that we can use both indexed and // associative arrays. foreach ($validKeys as $key => $val) { is_int($key) ? $valids[$val] = null : $valids[$key] = $val; } $validKeys = array_keys($valids); // Check for invalid keys. foreach ($hash as $key => $value) { if (!in_array($key, $validKeys)) { $unknown[] = $key; } } if (!empty($unknown)) { throw new PHPUnit_Framework_Exception( 'Unknown key(s): ' . implode(', ', $unknown) ); } // Add default values for any valid keys that are empty. foreach ($valids as $key => $value) { if (!isset($hash[$key])) { $hash[$key] = $value; } } return $hash; } /** * Parse a CSS selector into an associative array suitable for * use with findNodes(). * * @param string $selector * @param mixed $content * @return array * @since Method available since Release 3.3.0 */ public static function convertSelectToTag($selector, $content = true) { $selector = trim(preg_replace("/\s+/", ' ', $selector)); // substitute spaces within attribute value while (preg_match('/\[[^\]]+"[^"]+\s[^"]+"\]/', $selector)) { $selector = preg_replace( '/(\[[^\]]+"[^"]+)\s([^"]+"\])/', '$1__SPACE__$2', $selector ); } if (strstr($selector, ' ')) { $elements = explode(' ', $selector); } else { $elements = array($selector); } $previousTag = array(); foreach (array_reverse($elements) as $element) { $element = str_replace('__SPACE__', ' ', $element); // child selector if ($element == '>') { $previousTag = array('child' => $previousTag['descendant']); continue; } // adjacent-sibling selector if ($element == '+') { $previousTag = array('adjacent-sibling' => $previousTag['descendant']); continue; } $tag = array(); // match element tag preg_match("/^([^\.#\[]*)/", $element, $eltMatches); if (!empty($eltMatches[1])) { $tag['tag'] = $eltMatches[1]; } // match attributes (\[[^\]]*\]*), ids (#[^\.#\[]*), // and classes (\.[^\.#\[]*)) preg_match_all( "/(\[[^\]]*\]*|#[^\.#\[]*|\.[^\.#\[]*)/", $element, $matches ); if (!empty($matches[1])) { $classes = array(); $attrs = array(); foreach ($matches[1] as $match) { // id matched if (substr($match, 0, 1) == '#') { $tag['id'] = substr($match, 1); } // class matched elseif (substr($match, 0, 1) == '.') { $classes[] = substr($match, 1); } // attribute matched elseif (substr($match, 0, 1) == '[' && substr($match, -1, 1) == ']') { $attribute = substr($match, 1, strlen($match) - 2); $attribute = str_replace('"', '', $attribute); // match single word if (strstr($attribute, '~=')) { list($key, $value) = explode('~=', $attribute); $value = "regexp:/.*\b$value\b.*/"; } // match substring elseif (strstr($attribute, '*=')) { list($key, $value) = explode('*=', $attribute); $value = "regexp:/.*$value.*/"; } // exact match else { list($key, $value) = explode('=', $attribute); } $attrs[$key] = $value; } } if (!empty($classes)) { $tag['class'] = implode(' ', $classes); } if (!empty($attrs)) { $tag['attributes'] = $attrs; } } // tag content if (is_string($content)) { $tag['content'] = $content; } // determine previous child/descendants if (!empty($previousTag['descendant'])) { $tag['descendant'] = $previousTag['descendant']; } elseif (!empty($previousTag['child'])) { $tag['child'] = $previousTag['child']; } elseif (!empty($previousTag['adjacent-sibling'])) { $tag['adjacent-sibling'] = $previousTag['adjacent-sibling']; unset($tag['content']); } $previousTag = array('descendant' => $tag); } return $tag; } /** * Parse an $actual document and return an array of DOMNodes * matching the CSS $selector. If an error occurs, it will * return false. * * To only return nodes containing a certain content, give * the $content to match as a string. Otherwise, setting * $content to true will return all nodes matching $selector. * * The $actual document may be a DOMDocument or a string * containing XML or HTML, identified by $isHtml. * * @param array $selector * @param string $content * @param mixed $actual * @param bool $isHtml * @return bool|array * @since Method available since Release 3.3.0 */ public static function cssSelect($selector, $content, $actual, $isHtml = true) { $matcher = self::convertSelectToTag($selector, $content); $dom = self::load($actual, $isHtml); $tags = self::findNodes($dom, $matcher, $isHtml); return $tags; } /** * Parse out the options from the tag using DOM object tree. * * @param DOMDocument $dom * @param array $options * @param bool $isHtml * @return array * @since Method available since Release 3.3.0 */ public static function findNodes(DOMDocument $dom, array $options, $isHtml = true) { $valid = array( 'id', 'class', 'tag', 'content', 'attributes', 'parent', 'child', 'ancestor', 'descendant', 'children', 'adjacent-sibling' ); $filtered = array(); $options = self::assertValidKeys($options, $valid); // find the element by id if ($options['id']) { $options['attributes']['id'] = $options['id']; } if ($options['class']) { $options['attributes']['class'] = $options['class']; } $nodes = array(); // find the element by a tag type if ($options['tag']) { if ($isHtml) { $elements = self::getElementsByCaseInsensitiveTagName( $dom, $options['tag'] ); } else { $elements = $dom->getElementsByTagName($options['tag']); } foreach ($elements as $element) { $nodes[] = $element; } if (empty($nodes)) { return false; } } // no tag selected, get them all else { $tags = array( 'a', 'abbr', 'acronym', 'address', 'area', 'b', 'base', 'bdo', 'big', 'blockquote', 'body', 'br', 'button', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'div', 'dfn', 'dl', 'dt', 'em', 'fieldset', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'link', 'map', 'meta', 'noframes', 'noscript', 'object', 'ol', 'optgroup', 'option', 'p', 'param', 'pre', 'q', 'samp', 'script', 'select', 'small', 'span', 'strong', 'style', 'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'title', 'tr', 'tt', 'ul', 'var', // HTML5 'article', 'aside', 'audio', 'bdi', 'canvas', 'command', 'datalist', 'details', 'dialog', 'embed', 'figure', 'figcaption', 'footer', 'header', 'hgroup', 'keygen', 'mark', 'meter', 'nav', 'output', 'progress', 'ruby', 'rt', 'rp', 'track', 'section', 'source', 'summary', 'time', 'video', 'wbr' ); foreach ($tags as $tag) { if ($isHtml) { $elements = self::getElementsByCaseInsensitiveTagName( $dom, $tag ); } else { $elements = $dom->getElementsByTagName($tag); } foreach ($elements as $element) { $nodes[] = $element; } } if (empty($nodes)) { return false; } } // filter by attributes if ($options['attributes']) { foreach ($nodes as $node) { $invalid = false; foreach ($options['attributes'] as $name => $value) { // match by regexp if like "regexp:/foo/i" if (preg_match('/^regexp\s*:\s*(.*)/i', $value, $matches)) { if (!preg_match($matches[1], $node->getAttribute($name))) { $invalid = true; } } // class can match only a part elseif ($name == 'class') { // split to individual classes $findClasses = explode( ' ', preg_replace("/\s+/", ' ', $value) ); $allClasses = explode( ' ', preg_replace("/\s+/", ' ', $node->getAttribute($name)) ); // make sure each class given is in the actual node foreach ($findClasses as $findClass) { if (!in_array($findClass, $allClasses)) { $invalid = true; } } } // match by exact string else { if ($node->getAttribute($name) != $value) { $invalid = true; } } } // if every attribute given matched if (!$invalid) { $filtered[] = $node; } } $nodes = $filtered; $filtered = array(); if (empty($nodes)) { return false; } } // filter by content if ($options['content'] !== null) { foreach ($nodes as $node) { $invalid = false; // match by regexp if like "regexp:/foo/i" if (preg_match('/^regexp\s*:\s*(.*)/i', $options['content'], $matches)) { if (!preg_match($matches[1], self::getNodeText($node))) { $invalid = true; } } // match empty string elseif ($options['content'] === '') { if (self::getNodeText($node) !== '') { $invalid = true; } } // match by exact string elseif (strstr(self::getNodeText($node), $options['content']) === false) { $invalid = true; } if (!$invalid) { $filtered[] = $node; } } $nodes = $filtered; $filtered = array(); if (empty($nodes)) { return false; } } // filter by parent node if ($options['parent']) { $parentNodes = self::findNodes($dom, $options['parent'], $isHtml); $parentNode = isset($parentNodes[0]) ? $parentNodes[0] : null; foreach ($nodes as $node) { if ($parentNode !== $node->parentNode) { continue; } $filtered[] = $node; } $nodes = $filtered; $filtered = array(); if (empty($nodes)) { return false; } } // filter by child node if ($options['child']) { $childNodes = self::findNodes($dom, $options['child'], $isHtml); $childNodes = !empty($childNodes) ? $childNodes : array(); foreach ($nodes as $node) { foreach ($node->childNodes as $child) { foreach ($childNodes as $childNode) { if ($childNode === $child) { $filtered[] = $node; } } } } $nodes = $filtered; $filtered = array(); if (empty($nodes)) { return false; } } // filter by adjacent-sibling if ($options['adjacent-sibling']) { $adjacentSiblingNodes = self::findNodes($dom, $options['adjacent-sibling'], $isHtml); $adjacentSiblingNodes = !empty($adjacentSiblingNodes) ? $adjacentSiblingNodes : array(); foreach ($nodes as $node) { $sibling = $node; while ($sibling = $sibling->nextSibling) { if ($sibling->nodeType !== XML_ELEMENT_NODE) { continue; } foreach ($adjacentSiblingNodes as $adjacentSiblingNode) { if ($sibling === $adjacentSiblingNode) { $filtered[] = $node; break; } } break; } } $nodes = $filtered; $filtered = array(); if (empty($nodes)) { return false; } } // filter by ancestor if ($options['ancestor']) { $ancestorNodes = self::findNodes($dom, $options['ancestor'], $isHtml); $ancestorNode = isset($ancestorNodes[0]) ? $ancestorNodes[0] : null; foreach ($nodes as $node) { $parent = $node->parentNode; while ($parent && $parent->nodeType != XML_HTML_DOCUMENT_NODE) { if ($parent === $ancestorNode) { $filtered[] = $node; } $parent = $parent->parentNode; } } $nodes = $filtered; $filtered = array(); if (empty($nodes)) { return false; } } // filter by descendant if ($options['descendant']) { $descendantNodes = self::findNodes($dom, $options['descendant'], $isHtml); $descendantNodes = !empty($descendantNodes) ? $descendantNodes : array(); foreach ($nodes as $node) { foreach (self::getDescendants($node) as $descendant) { foreach ($descendantNodes as $descendantNode) { if ($descendantNode === $descendant) { $filtered[] = $node; } } } } $nodes = $filtered; $filtered = array(); if (empty($nodes)) { return false; } } // filter by children if ($options['children']) { $validChild = array('count', 'greater_than', 'less_than', 'only'); $childOptions = self::assertValidKeys( $options['children'], $validChild ); foreach ($nodes as $node) { $childNodes = $node->childNodes; foreach ($childNodes as $childNode) { if ($childNode->nodeType !== XML_CDATA_SECTION_NODE && $childNode->nodeType !== XML_TEXT_NODE) { $children[] = $childNode; } } // we must have children to pass this filter if (!empty($children)) { // exact count of children if ($childOptions['count'] !== null) { if (count($children) !== $childOptions['count']) { break; } } // range count of children elseif ($childOptions['less_than'] !== null && $childOptions['greater_than'] !== null) { if (count($children) >= $childOptions['less_than'] || count($children) <= $childOptions['greater_than']) { break; } } // less than a given count elseif ($childOptions['less_than'] !== null) { if (count($children) >= $childOptions['less_than']) { break; } } // more than a given count elseif ($childOptions['greater_than'] !== null) { if (count($children) <= $childOptions['greater_than']) { break; } } // match each child against a specific tag if ($childOptions['only']) { $onlyNodes = self::findNodes( $dom, $childOptions['only'], $isHtml ); // try to match each child to one of the 'only' nodes foreach ($children as $child) { $matched = false; foreach ($onlyNodes as $onlyNode) { if ($onlyNode === $child) { $matched = true; } } if (!$matched) { break 2; } } } $filtered[] = $node; } } $nodes = $filtered; if (empty($nodes)) { return; } } // return the first node that matches all criteria return !empty($nodes) ? $nodes : array(); } /** * Recursively get flat array of all descendants of this node. * * @param DOMNode $node * @return array * @since Method available since Release 3.3.0 */ protected static function getDescendants(DOMNode $node) { $allChildren = array(); $childNodes = $node->childNodes ? $node->childNodes : array(); foreach ($childNodes as $child) { if ($child->nodeType === XML_CDATA_SECTION_NODE || $child->nodeType === XML_TEXT_NODE) { continue; } $children = self::getDescendants($child); $allChildren = array_merge($allChildren, $children, array($child)); } return isset($allChildren) ? $allChildren : array(); } /** * Gets elements by case insensitive tagname. * * @param DOMDocument $dom * @param string $tag * @return DOMNodeList * @since Method available since Release 3.4.0 */ protected static function getElementsByCaseInsensitiveTagName(DOMDocument $dom, $tag) { $elements = $dom->getElementsByTagName(strtolower($tag)); if ($elements->length == 0) { $elements = $dom->getElementsByTagName(strtoupper($tag)); } return $elements; } /** * Get the text value of this node's child text node. * * @param DOMNode $node * @return string * @since Method available since Release 3.3.0 */ protected static function getNodeText(DOMNode $node) { if (!$node->childNodes instanceof DOMNodeList) { return ''; } $result = ''; foreach ($node->childNodes as $childNode) { if ($childNode->nodeType === XML_TEXT_NODE || $childNode->nodeType === XML_CDATA_SECTION_NODE) { $result .= trim($childNode->data) . ' '; } else { $result .= self::getNodeText($childNode); } } return str_replace(' ', ' ', $result); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Utility methods for PHP sub-processes. * * @since Class available since Release 3.4.0 */ abstract class PHPUnit_Util_PHP { /** * @return PHPUnit_Util_PHP * @since Method available since Release 3.5.12 */ public static function factory() { if (DIRECTORY_SEPARATOR == '\\') { return new PHPUnit_Util_PHP_Windows; } return new PHPUnit_Util_PHP_Default; } /** * Runs a single test in a separate PHP process. * * @param string $job * @param PHPUnit_Framework_Test $test * @param PHPUnit_Framework_TestResult $result * @throws PHPUnit_Framework_Exception */ public function runTestJob($job, PHPUnit_Framework_Test $test, PHPUnit_Framework_TestResult $result) { $result->startTest($test); $_result = $this->runJob($job); $this->processChildResult( $test, $result, $_result['stdout'], $_result['stderr'] ); } /** * Runs a single job (PHP code) using a separate PHP process. * * @param string $job * @param array $settings * @return array * @throws PHPUnit_Framework_Exception */ abstract public function runJob($job, array $settings = array()); /** * @param array $settings * @return string * @since Method available since Release 4.0.0 */ protected function settingsToParameters(array $settings) { $buffer = ''; foreach ($settings as $setting) { $buffer .= ' -d ' . $setting; } return $buffer; } /** * Processes the TestResult object from an isolated process. * * @param PHPUnit_Framework_Test $test * @param PHPUnit_Framework_TestResult $result * @param string $stdout * @param string $stderr * @since Method available since Release 3.5.0 */ private function processChildResult(PHPUnit_Framework_Test $test, PHPUnit_Framework_TestResult $result, $stdout, $stderr) { $time = 0; if (!empty($stderr)) { $result->addError( $test, new PHPUnit_Framework_Exception(trim($stderr)), $time ); } else { set_error_handler(function ($errno, $errstr, $errfile, $errline) { throw new ErrorException($errstr, $errno, $errno, $errfile, $errline); }); try { if (strpos($stdout, "#!/usr/bin/env php\n") === 0) { $stdout = substr($stdout, 19); } $childResult = unserialize(str_replace("#!/usr/bin/env php\n", '', $stdout)); restore_error_handler(); } catch (ErrorException $e) { restore_error_handler(); $childResult = false; $result->addError( $test, new PHPUnit_Framework_Exception(trim($stdout), 0, $e), $time ); } if ($childResult !== false) { if (!empty($childResult['output'])) { $output = $childResult['output']; } $test->setResult($childResult['testResult']); $test->addToAssertionCount($childResult['numAssertions']); $childResult = $childResult['result']; if ($result->getCollectCodeCoverageInformation()) { $result->getCodeCoverage()->merge( $childResult->getCodeCoverage() ); } $time = $childResult->time(); $notImplemented = $childResult->notImplemented(); $risky = $childResult->risky(); $skipped = $childResult->skipped(); $errors = $childResult->errors(); $failures = $childResult->failures(); if (!empty($notImplemented)) { $result->addError( $test, $this->getException($notImplemented[0]), $time ); } elseif (!empty($risky)) { $result->addError( $test, $this->getException($risky[0]), $time ); } elseif (!empty($skipped)) { $result->addError( $test, $this->getException($skipped[0]), $time ); } elseif (!empty($errors)) { $result->addError( $test, $this->getException($errors[0]), $time ); } elseif (!empty($failures)) { $result->addFailure( $test, $this->getException($failures[0]), $time ); } } } $result->endTest($test, $time); if (!empty($output)) { print $output; } } /** * Gets the thrown exception from a PHPUnit_Framework_TestFailure. * * @param PHPUnit_Framework_TestFailure $error * @return Exception * @since Method available since Release 3.6.0 * @see https://github.com/sebastianbergmann/phpunit/issues/74 */ private function getException(PHPUnit_Framework_TestFailure $error) { $exception = $error->thrownException(); if ($exception instanceof __PHP_Incomplete_Class) { $exceptionArray = array(); foreach ((array) $exception as $key => $value) { $key = substr($key, strrpos($key, "\0") + 1); $exceptionArray[$key] = $value; } $exception = new PHPUnit_Framework_SyntheticError( sprintf( '%s: %s', $exceptionArray['_PHP_Incomplete_Class_Name'], $exceptionArray['message'] ), $exceptionArray['code'], $exceptionArray['file'], $exceptionArray['line'], $exceptionArray['trace'] ); } return $exception; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 4.0.0 */ class PHPUnit_Runner_Exception extends RuntimeException implements PHPUnit_Exception { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 4.0.0 */ class PHPUnit_Runner_Filter_Test extends RecursiveFilterIterator { /** * @var string */ protected $filter = null; /** * @var int */ protected $filterMin; /** * @var int */ protected $filterMax; /** * @param RecursiveIterator $iterator * @param string $filter */ public function __construct(RecursiveIterator $iterator, $filter) { parent::__construct($iterator); $this->setFilter($filter); } /** * @param string $filter */ protected function setFilter($filter) { if (PHPUnit_Util_Regex::pregMatchSafe($filter, '') === false) { // Handles: // * testAssertEqualsSucceeds#4 // * testAssertEqualsSucceeds#4-8 if (preg_match('/^(.*?)#(\d+)(?:-(\d+))?$/', $filter, $matches)) { if (isset($matches[3]) && $matches[2] < $matches[3]) { $filter = sprintf( '%s.*with data set #(\d+)$', $matches[1] ); $this->filterMin = $matches[2]; $this->filterMax = $matches[3]; } else { $filter = sprintf( '%s.*with data set #%s$', $matches[1], $matches[2] ); } } // Handles: // * testDetermineJsonError@JSON_ERROR_NONE // * testDetermineJsonError@JSON.* elseif (preg_match('/^(.*?)@(.+)$/', $filter, $matches)) { $filter = sprintf( '%s.*with data set "%s"$', $matches[1], $matches[2] ); } // Escape delimiters in regular expression. Do NOT use preg_quote, // to keep magic characters. $filter = sprintf('/%s/', str_replace( '/', '\\/', $filter )); } $this->filter = $filter; } /** * @return bool */ public function accept() { $test = $this->getInnerIterator()->current(); if ($test instanceof PHPUnit_Framework_TestSuite) { return true; } $tmp = PHPUnit_Util_Test::describe($test, false); if ($tmp[0] != '') { $name = implode('::', $tmp); } else { $name = $tmp[1]; } $accepted = preg_match($this->filter, $name, $matches); if ($accepted && isset($this->filterMax)) { $set = end($matches); $accepted = $set >= $this->filterMin && $set <= $this->filterMax; } return $accepted; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 4.0.0 */ class PHPUnit_Runner_Filter_Group_Exclude extends PHPUnit_Runner_Filter_GroupFilterIterator { protected function doAccept($hash) { return !in_array($hash, $this->groupTests); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 4.0.0 */ class PHPUnit_Runner_Filter_Group_Include extends PHPUnit_Runner_Filter_GroupFilterIterator { protected function doAccept($hash) { return in_array($hash, $this->groupTests); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 4.0.0 */ class PHPUnit_Runner_Filter_Factory { /** * @var array */ private $filters = array(); /** * @param ReflectionClass $filter * @param mixed $args */ public function addFilter(ReflectionClass $filter, $args) { if (!$filter->isSubclassOf('RecursiveFilterIterator')) { throw new InvalidArgumentException( sprintf( 'Class "%s" does not extend RecursiveFilterIterator', $filter->name ) ); } $this->filters[] = array($filter, $args); } /** * @return FilterIterator */ public function factory(Iterator $iterator, PHPUnit_Framework_TestSuite $suite) { foreach ($this->filters as $filter) { list($class, $args) = $filter; $iterator = $class->newInstance($iterator, $args, $suite); } return $iterator; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * @since Class available since Release 4.0.0 */ abstract class PHPUnit_Runner_Filter_GroupFilterIterator extends RecursiveFilterIterator { /** * @var array */ protected $groupTests = array(); /** * @param RecursiveIterator $iterator * @param array $groups * @param PHPUnit_Framework_TestSuite $suite */ public function __construct(RecursiveIterator $iterator, array $groups, PHPUnit_Framework_TestSuite $suite) { parent::__construct($iterator); foreach ($suite->getGroupDetails() as $group => $tests) { if (in_array($group, $groups)) { $testHashes = array_map( function ($test) { return spl_object_hash($test); }, $tests ); $this->groupTests = array_merge($this->groupTests, $testHashes); } } } /** * @return bool */ public function accept() { $test = $this->getInnerIterator()->current(); if ($test instanceof PHPUnit_Framework_TestSuite) { return true; } return $this->doAccept(spl_object_hash($test)); } abstract protected function doAccept($hash); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * An interface to define how a test suite should be loaded. * * @since Interface available since Release 2.0.0 */ interface PHPUnit_Runner_TestSuiteLoader { /** * @param string $suiteClassName * @param string $suiteClassFile * @return ReflectionClass */ public function load($suiteClassName, $suiteClassFile = ''); /** * @param ReflectionClass $aClass * @return ReflectionClass */ public function reload(ReflectionClass $aClass); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Base class for all test runners. * * @since Class available since Release 2.0.0 */ abstract class PHPUnit_Runner_BaseTestRunner { const STATUS_PASSED = 0; const STATUS_SKIPPED = 1; const STATUS_INCOMPLETE = 2; const STATUS_FAILURE = 3; const STATUS_ERROR = 4; const STATUS_RISKY = 5; const SUITE_METHODNAME = 'suite'; /** * Returns the loader to be used. * * @return PHPUnit_Runner_TestSuiteLoader */ public function getLoader() { return new PHPUnit_Runner_StandardTestSuiteLoader; } /** * Returns the Test corresponding to the given suite. * This is a template method, subclasses override * the runFailed() and clearStatus() methods. * * @param string $suiteClassName * @param string $suiteClassFile * @param mixed $suffixes * @return PHPUnit_Framework_Test */ public function getTest($suiteClassName, $suiteClassFile = '', $suffixes = '') { if (is_dir($suiteClassName) && !is_file($suiteClassName . '.php') && empty($suiteClassFile)) { $facade = new File_Iterator_Facade; $files = $facade->getFilesAsArray( $suiteClassName, $suffixes ); $suite = new PHPUnit_Framework_TestSuite($suiteClassName); $suite->addTestFiles($files); return $suite; } try { $testClass = $this->loadSuiteClass( $suiteClassName, $suiteClassFile ); } catch (PHPUnit_Framework_Exception $e) { $this->runFailed($e->getMessage()); return; } try { $suiteMethod = $testClass->getMethod(self::SUITE_METHODNAME); if (!$suiteMethod->isStatic()) { $this->runFailed( 'suite() method must be static.' ); return; } try { $test = $suiteMethod->invoke(null, $testClass->getName()); } catch (ReflectionException $e) { $this->runFailed( sprintf( "Failed to invoke suite() method.\n%s", $e->getMessage() ) ); return; } } catch (ReflectionException $e) { try { $test = new PHPUnit_Framework_TestSuite($testClass); } catch (PHPUnit_Framework_Exception $e) { $test = new PHPUnit_Framework_TestSuite; $test->setName($suiteClassName); } } $this->clearStatus(); return $test; } /** * Returns the loaded ReflectionClass for a suite name. * * @param string $suiteClassName * @param string $suiteClassFile * @return ReflectionClass */ protected function loadSuiteClass($suiteClassName, $suiteClassFile = '') { $loader = $this->getLoader(); return $loader->load($suiteClassName, $suiteClassFile); } /** * Clears the status message. */ protected function clearStatus() { } /** * Override to define how to handle a failed loading of * a test suite. * * @param string $message */ abstract protected function runFailed($message); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * The standard test suite loader. * * @since Class available since Release 2.0.0 */ class PHPUnit_Runner_StandardTestSuiteLoader implements PHPUnit_Runner_TestSuiteLoader { /** * @param string $suiteClassName * @param string $suiteClassFile * @return ReflectionClass * @throws PHPUnit_Framework_Exception */ public function load($suiteClassName, $suiteClassFile = '') { $suiteClassName = str_replace('.php', '', $suiteClassName); if (empty($suiteClassFile)) { $suiteClassFile = PHPUnit_Util_Filesystem::classNameToFilename( $suiteClassName ); } if (!class_exists($suiteClassName, false)) { $loadedClasses = get_declared_classes(); $filename = PHPUnit_Util_Fileloader::checkAndLoad($suiteClassFile); $loadedClasses = array_values( array_diff(get_declared_classes(), $loadedClasses) ); } if (!class_exists($suiteClassName, false) && !empty($loadedClasses)) { $offset = 0 - strlen($suiteClassName); foreach ($loadedClasses as $loadedClass) { $class = new ReflectionClass($loadedClass); if (substr($loadedClass, $offset) === $suiteClassName && $class->getFileName() == $filename) { $suiteClassName = $loadedClass; break; } } } if (!class_exists($suiteClassName, false) && !empty($loadedClasses)) { $testCaseClass = 'PHPUnit_Framework_TestCase'; foreach ($loadedClasses as $loadedClass) { $class = new ReflectionClass($loadedClass); $classFile = $class->getFileName(); if ($class->isSubclassOf($testCaseClass) && !$class->isAbstract()) { $suiteClassName = $loadedClass; $testCaseClass = $loadedClass; if ($classFile == realpath($suiteClassFile)) { break; } } if ($class->hasMethod('suite')) { $method = $class->getMethod('suite'); if (!$method->isAbstract() && $method->isPublic() && $method->isStatic()) { $suiteClassName = $loadedClass; if ($classFile == realpath($suiteClassFile)) { break; } } } } } if (class_exists($suiteClassName, false)) { $class = new ReflectionClass($suiteClassName); if ($class->getFileName() == realpath($suiteClassFile)) { return $class; } } throw new PHPUnit_Framework_Exception( sprintf( "Class '%s' could not be found in '%s'.", $suiteClassName, $suiteClassFile ) ); } /** * @param ReflectionClass $aClass * @return ReflectionClass */ public function reload(ReflectionClass $aClass) { return $aClass; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * This class defines the current version of PHPUnit. * * @since Class available since Release 2.0.0 */ class PHPUnit_Runner_Version { private static $pharVersion; private static $version; /** * Returns the current version of PHPUnit. * * @return string */ public static function id() { if (self::$pharVersion !== null) { return self::$pharVersion; } if (self::$version === null) { $version = new SebastianBergmann\Version('4.8.10', dirname(dirname(__DIR__))); self::$version = $version->getVersion(); } return self::$version; } /** * @return string */ public static function getVersionString() { return 'PHPUnit ' . self::id() . ' by Sebastian Bergmann and contributors.'; } /** * @return string * @since Method available since Release 4.0.0 */ public static function getReleaseChannel() { if (strpos(self::$pharVersion, 'alpha') !== false) { return '-alpha'; } if (strpos(self::$pharVersion, 'beta') !== false) { return '-beta'; } return ''; } } #sՉ(/V[Rh`%+ni&<|/ @"V `%iWK !AGBMB