summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2015-01-11 13:49:15 +0100
committerEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2015-01-11 13:49:15 +0100
commitae9d580fe7a79052d614b7d48d8feb54836fe334 (patch)
tree874ed8c758b51a8c6f1b280620d70725f5936ac1
parent1c663ea116121469e37ad2cb9480387c16c0236b (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.h5
-rw-r--r--include/file.h20
-rw-r--r--zend/eval.cpp65
-rw-r--r--zend/file.cpp70
-rw-r--r--zend/opcodes.cpp37
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;