-
Notifications
You must be signed in to change notification settings - Fork 25
/
value.cpp
226 lines (192 loc) · 8.15 KB
/
value.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
/**
* value.cpp
*
* Simple value casting functions for casting values
* between php and ecmascript runtime values.
*
* @copyright 2015 Copernica B.V.
*/
/**
* Dependencies
*/
#include "value.h"
#include "isolate.h"
#include "handle.h"
#include "object.h"
#include "array.h"
#include "jsobject.h"
/**
* Start namespace
*/
namespace JS {
/**
* Callback function to be used when invoking functions
* defined from the PHP side
*
* @param info callback information
*/
static void callback(const v8::FunctionCallbackInfo<v8::Value> &info)
{
// create a local handle, so properties "fall out of scope"
v8::HandleScope scope(Isolate::get());
// retrieve handle to the original object
Handle handle(info.Data());
// an array to hold all the arguments
Php::Array arguments;
// add all the arguments
for (int i = 0; i < info.Length(); ++i) arguments.set(i, value(info[i]));
// catch any exceptions the PHP code might throw
try
{
// now execute the function
Php::Value result(Php::call("call_user_func_array", handle, arguments));
// cast the value and set it as return parameter
info.GetReturnValue().Set(value(result));
}
catch (const Php::Exception& exception)
{
// pass the exception on to javascript userspace
Isolate::get()->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(Isolate::get(), exception.what())));
}
}
/**
* Cast a PHP runtime value to an ecmascript value
*
* @param input the value to cast
* @return v8::Handle<v8::Value>
*/
v8::Handle<v8::Value> value(const Php::Value &input)
{
// create a handle that we can return a value from
v8::EscapableHandleScope scope(Isolate::get());
// the result value we are assigning
v8::Local<v8::Value> result;
// are we dealing with a value originally from ecmascript?
if (input.instanceOf("JS\\Object"))
{
// cast the input to the original object
result = static_cast<JSObject*>(input.implementation())->object();
}
else
{
// the value can be of many types
switch (input.type())
{
case Php::Type::Null: result = v8::Null(Isolate::get()); break;
case Php::Type::Numeric: result = v8::Integer::New(Isolate::get(), input); break;
case Php::Type::Float: result = v8::Number::New(Isolate::get(), input); break;
case Php::Type::Bool: result = v8::Boolean::New(Isolate::get(), input); break;
case Php::Type::String: result = v8::String::NewFromUtf8(Isolate::get(), input); break;
case Php::Type::Object: result = Object(input); break;
case Php::Type::Callable: result = v8::FunctionTemplate::New(Isolate::get(), callback, Handle(input))->GetFunction(); break;
case Php::Type::Array: result = Array(input); break;
default:
// php 7 does not return the Bool type anymore, but rather True and False
// types, which would not compile with our legacy code, so we check if it
// is boolean here again, using a function that works identically for both
if (input.isBool()) result = v8::Boolean::New(Isolate::get(), input);
break;
}
}
// return the value by "escaping" it
return scope.Escape(result);
}
/**
* Cast an ecmascript value to a PHP runtime value
*
* @note The value cannot be const, as retrieving properties
* from arrays and objects cannot be done on const values
*
* @param input the value to cast
* @return Php::Value
*/
Php::Value value(v8::Handle<v8::Value> input)
{
// if we received an invalid input we simply return an empty PHP value
if (input.IsEmpty()) return nullptr;
// as is typical in javascript, a value can be of many types
// check the type of value that we have received so we can cast
if (input->IsBoolean()) return input->BooleanValue();
if (input->IsBooleanObject()) return input->BooleanValue();
if (input->IsInt32()) return input->Int32Value();
if (input->IsNumber()) return input->NumberValue();
if (input->IsNumberObject()) return input->NumberValue();
if (input->IsNull()) return nullptr;
if (input->IsUndefined()) return nullptr;
// special treatment for string-like types
// TODO: javascript dates might possibly be cast to a DateTime object
if (input->IsString() || input->IsStringObject() || input->IsRegExp())
{
// create the utf8 value (the only way to retrieve the content)
v8::String::Utf8Value utf8(input->ToString());
// and create the value to return
return {*utf8, utf8.length()};
}
// it could be callable too
if (input->IsFunction())
{
// create the function as a pointer that can be captured
auto function = std::make_shared<Stack<v8::Function>>(input.As<v8::Function>());
// the result to return
Php::Function result([function](Php::Parameters ¶ms) {
// create a "scope", so variables get destructed, retrieve the context and "enter" it
v8::HandleScope scope(Isolate::get());
v8::Local<v8::Context> context((*function)->CreationContext());
v8::Context::Scope contextScope(context);
// catch any errors that occur while either compiling or running the script
v8::TryCatch catcher;
// create a new array with parameters
std::vector<v8::Local<v8::Value>> array;
array.reserve(params.size());
// iterate over all the given parameters and add them to the arrau
for (auto ¶m: params) array.push_back(value(param));
// now we can actually call the function
v8::Local<v8::Value> result((*function)->Call(context->Global(), array.size(), array.data()));
// did we catch an exception?
if (catcher.HasCaught())
{
// retrieve the message describing the problem
v8::Local<v8::Message> message(catcher.Message());
v8::Local<v8::String> description(message->Get());
// convert the description to utf so we can dump it
v8::String::Utf8Value string(description);
// pass this exception on to PHP userspace
throw Php::Exception(std::string(*string, string.length()));
}
// convert the result to a PHP value and return it
return value(result);
});
// now return the result
return result;
}
// or perhaps an object
if (input->IsObject())
{
// retrieve the object and the first internal field
auto object = input.As<v8::Object>();
// does the object have internal fields?
if (object->InternalFieldCount())
{
// retrieve the field
auto field = object->GetInternalField(0);
// does it have an internal field and is it external? we are converting back
// an original PHP object, just retrieve the original thing that came from PHP
if (!field.IsEmpty() && field->IsExternal())
{
// the PHP value is stored in the first internal field,
// retrieve it and create the handle around it
Handle handle(field);
// dereference and return it
return *handle;
}
}
// create a new js object and convert it to userspace
return Php::Object("JS\\Object", new JSObject(object));
}
// we sadly don't support this type of value
return nullptr;
}
/**
* End namespace
*/
}