diff options
author | Emiel Bruijntjes <emiel.bruijntjes@copernica.com> | 2015-01-11 13:49:15 +0100 |
---|---|---|
committer | Emiel Bruijntjes <emiel.bruijntjes@copernica.com> | 2015-01-11 13:49:15 +0100 |
commit | ae9d580fe7a79052d614b7d48d8feb54836fe334 (patch) | |
tree | 874ed8c758b51a8c6f1b280620d70725f5936ac1 | |
parent | 1c663ea116121469e37ad2cb9480387c16c0236b (diff) |
added include(), require(), include_once() and require_once() methods, based on the Php::File class (feature built based on inspiration from pull request #147);
-rw-r--r-- | include/call.h | 5 | ||||
-rw-r--r-- | include/file.h | 20 | ||||
-rw-r--r-- | zend/eval.cpp | 65 | ||||
-rw-r--r-- | zend/file.cpp | 70 | ||||
-rw-r--r-- | zend/opcodes.cpp | 37 |
5 files changed, 151 insertions, 46 deletions
diff --git a/include/call.h b/include/call.h index 1a0c59f..279cbac 100644 --- a/include/call.h +++ b/include/call.h @@ -19,13 +19,16 @@ extern bool class_exists(const char *classname, size_t size, bool autoload = tr inline bool class_exists(const char *classname, bool autoload = true) { return class_exists(classname, strlen(classname), autoload); } inline bool class_exists(const std::string &classname, bool autoload = true) { return class_exists(classname.c_str(), classname.size(), autoload); } extern Value eval(const std::string &phpCode); +extern Value include(const std::string &filename); +extern Value include_once(const std::string &filename); inline bool is_a(const Value &obj, const char *classname, size_t size, bool allow_string = false) { return obj.instanceOf(classname, size, allow_string); } inline bool is_a(const Value &obj, const char *classname, bool allow_string = false) { return is_a(obj, classname, strlen(classname), allow_string); } inline bool is_a(const Value &obj, const std::string &classname, bool allow_string = false) { return is_a(obj, classname.c_str(), classname.size(), allow_string); } inline bool is_subclass_of(const Value &obj, const char *classname, size_t size, bool allow_string = true) { return obj.derivedFrom(classname, size, allow_string); } inline bool is_subclass_of(const Value &obj, const char *classname, bool allow_string = true) { return is_subclass_of(obj, classname, strlen(classname), allow_string); } inline bool is_subclass_of(const Value &obj, const std::string &classname, bool allow_string = true) { return is_subclass_of(obj, classname.c_str(), classname.size(), allow_string); } - +extern Value require(const std::string &filename); +extern Value require_once(const std::string &filename); /** * Call a function in PHP diff --git a/include/file.h b/include/file.h index 6e24516..7029bb6 100644 --- a/include/file.h +++ b/include/file.h @@ -55,7 +55,19 @@ public: virtual ~File(); /** - * Include the file once + * Does the file exist? + * @return boolean + */ + bool exists(); + + /** + * Is this a valid file? + * @return boolean + */ + bool valid(); + + /** + * Execute the file once (do nothing if the file already was executed) * @return Php::Value */ Value once(); @@ -78,6 +90,12 @@ private: * @var Opcodes */ Opcodes *_opcodes = nullptr; + + /** + * Compile the file + * @return bool + */ + bool compile(); }; diff --git a/zend/eval.cpp b/zend/eval.cpp index 32d807a..4c4bffc 100644 --- a/zend/eval.cpp +++ b/zend/eval.cpp @@ -63,6 +63,71 @@ Value eval(const std::string &phpCode) } /** + * Include a file + * @param filename + * @return Value + */ +Value include(const std::string &filename) +{ + // we can simply execute a file + return File(filename).execute(); +} + +/** + * Include a file only once + * @param filename + * @return Value + */ +Value include_once(const std::string &filename) +{ + // we can simply execute a file + return File(filename).execute(); +} + +/** + * Require a file + * This causes a fatal error if the file does not exist + * @param filename + * @return Value + */ +Value require(const std::string &filename) +{ + // create the file + File file(filename); + + // execute if it exists + if (file.exists()) return file.execute(); + + // trigger fatal error + error << filename << " does not exist" << std::flush; + + // unreachable + return nullptr; +} + +/** + * Require a file only once + * This causes a fatal error if the file does not exist + * @param filename + * @return Value + */ +Value require_once(const std::string &filename) +{ + // create the file + File file(filename); + + // execute if it exists + if (file.exists()) return file.once(); + + // trigger fatal error + error << filename << " does not exist" << std::flush; + + // unreachable + return nullptr; +} + + +/** * End of namespace */ } diff --git a/zend/file.cpp b/zend/file.cpp index 98c1441..43c2d7a 100644 --- a/zend/file.cpp +++ b/zend/file.cpp @@ -55,22 +55,22 @@ File::~File() } /** - * Execute the file - * @return Value + * Compile the file + * @return bool */ -Value File::execute() +bool File::compile() { - // do we already have the opcodes? - if (_opcodes) return _opcodes->execute(); - - // skip if there is no valid path - if (!_path) return nullptr; - + // never works if the path is invalid + if (!_path) return false; + + // is the file already compiled? + if (_opcodes) return _opcodes->valid(); + // we are going to open the file zend_file_handle fileHandle; // open the file - if (zend_stream_open(_path, &fileHandle TSRMLS_CC) == FAILURE) return nullptr; + if (zend_stream_open(_path, &fileHandle TSRMLS_CC) == FAILURE) return false; // make sure the path name is stored in the handle if (!fileHandle.opened_path) fileHandle.opened_path = estrdup(_path); @@ -80,12 +80,55 @@ Value File::execute() // we need the tsrm_ls variable TSRMLS_FETCH(); - + // create the opcodes _opcodes = new Opcodes(zend_compile_file(&fileHandle, ZEND_INCLUDE TSRMLS_CC)); // close the file handle zend_destroy_file_handle(&fileHandle TSRMLS_CC); + + // done + return _opcodes->valid(); +} + +/** + * Does the file exist? + * @return boolean + */ +bool File::exists() +{ + // it is of course not valid if the path could not be resolved + if (!_path) return false; + + // if we have valid opcodes, we're sure that it exists + if (_opcodes && _opcodes->valid()) return true; + + // retrieve stats + struct stat buf; + return stat(_path, &buf) == 0; +} + +/** + * Is this a valid file? + * @return boolean + */ +bool File::valid() +{ + // check if file is compilable + return compile(); +} + +/** + * Execute the file + * @return Value + */ +Value File::execute() +{ + // do we already have the opcodes? + if (_opcodes) return _opcodes->execute(); + + // try compiling the file + if (!compile()) return nullptr; // add the entry to the list of included files zend_hash_add_empty_element(&EG(included_files), _path, ::strlen(_path) + 1); @@ -100,9 +143,8 @@ Value File::execute() */ Value File::once() { - // skip if the opcodes are already known (then we know for sure that the - // file was already executed), also leap out if the filename was not even valid - if (_opcodes || !_path) return nullptr; + // skip if the path is invalid + if (!_path) return nullptr; // check if this file was already included if (zend_hash_exists(&EG(included_files), _path, ::strlen(_path) + 1)) return nullptr; diff --git a/zend/opcodes.cpp b/zend/opcodes.cpp index 61d8a75..9e4525a 100644 --- a/zend/opcodes.cpp +++ b/zend/opcodes.cpp @@ -87,9 +87,6 @@ Value Opcodes::execute() const // pointer that is going to hold the return value of the script zval *retval_ptr = nullptr; - // @todo should retval_ptr be allocated? - - // the zend engine is probably already busy processing opcodes, so we store // the current execute state before we're going to switch the runtime to // our own set of opcodes @@ -103,35 +100,15 @@ Value Opcodes::execute() const if (!EG(active_symbol_table)) zend_rebuild_symbol_table(TSRMLS_C); CG(interactive) = 0; - // this code was copied from zend_execute_API.c. this is what I think it does: - // the zend_execute() call could result in all sort of disastrous things, one - // of them is a full crash of the executing script. if that happens, the zend - // engine does a long jump right up to some other point in the code. the - // 'zend_try' and 'zend_catch' macros prevent this: they install a new - // destination for the long jump, so that we can catch a failure - zend_try - { - // the current exception - zval* oldException = EG(exception); + // the current exception + zval* oldException = EG(exception); - // execute the code - zend_execute(_opcodes TSRMLS_CC); - - // was an exception thrown inside the eval()'ed code? In that case we - // throw a C++ new exception to give the C++ code the chance to catch it - if (oldException != EG(exception) && EG(exception)) throw OrigException(EG(exception) TSRMLS_CC); - } - zend_catch - { - // the code could not be executed, and the zend engine is in big - // trouble now, in the original code the _opcodes are efree'd, but here - // we can just as well continue with the real bailout (the zend_try/ - // zend_catch pair was maybe not even necessary???) - zend_bailout(); - } - zend_end_try(); + // execute the code + zend_execute(_opcodes TSRMLS_CC); - // @todo do we have to do something smart with zval copy'ing? + // was an exception thrown inside the eval()'ed code? In that case we + // throw a C++ new exception to give the C++ code the chance to catch it + if (oldException != EG(exception) && EG(exception)) throw OrigException(EG(exception) TSRMLS_CC); // we're ready if there is no return value if (!retval_ptr) return nullptr; |