php_process 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+ gem "knjrbfw"
6
+ gem "php-serialize"
7
+
8
+ # Add dependencies to develop your gem here.
9
+ # Include everything needed to run rake, tests, features, etc.
10
+ group :development do
11
+ gem "rspec", "~> 2.8.0"
12
+ gem "rdoc", "~> 3.12"
13
+ gem "bundler", ">= 1.0.0"
14
+ gem "jeweler", "~> 1.8.3"
15
+ gem "rcov", ">= 0"
16
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Kasper Johansen
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,19 @@
1
+ = php_process
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to php_process
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
9
+ * Fork the project.
10
+ * Start a feature/bugfix branch.
11
+ * Commit and push until you are happy with your contribution.
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2012 Kasper Johansen. See LICENSE.txt for
18
+ further details.
19
+
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "php_process"
18
+ gem.homepage = "http://github.com/kaspernj/php_process"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Ruby-to-PHP bridge}
21
+ gem.description = %Q{Spawns a PHP process and proxies calls to it, making it possible to proxy objects and more.}
22
+ gem.email = "k@spernj.org"
23
+ gem.authors = ["Kasper Johansen"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'rdoc/task'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "php_process #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,273 @@
1
+ require "knjrbfw"
2
+ require "base64"
3
+ require "php_serialize"
4
+
5
+ #This class starts a PHP-process and proxies various calls to it. It also spawns proxy-objects, which can you can call like they were normal Ruby-objects.
6
+ #===Examples
7
+ # php = Php_process.new
8
+ # print "PID of PHP-process: #{php.func("getmypid")}\n"
9
+ # print "Explode test: #{php.func("explode", ";", "1;2;3;4;5")}\n"
10
+ class Php_process
11
+ #A hash that contains links between Ruby object IDs and the PHP object IDs. It can be read because the proxy-objects adds their data to it.
12
+ attr_reader :object_ids
13
+
14
+ #This object controls which IDs should be unset on the PHP-side by being a destructor on the Ruby-side.
15
+ def objects_unsetter(id)
16
+ @object_unset_ids << @object_ids[id]
17
+ @object_ids.delete(id)
18
+ end
19
+
20
+ #Spawns various used variables, launches the process and more.
21
+ #===Examples
22
+ #If you want debugging printed to stderr:
23
+ # php = Php_process.new(:debug => true)
24
+ def initialize(args = {})
25
+ @args = args
26
+ @debug = @args[:debug]
27
+ @send_count = 0
28
+ @responses = Knj::Threadsafe::Synced_hash.new
29
+ @object_ids = Knj::Threadsafe::Synced_hash.new
30
+ @object_unset_ids = Knj::Threadsafe::Synced_array.new
31
+
32
+ cmd_str = "/usr/bin/env php5 \"#{File.dirname(__FILE__)}/php_script.php\""
33
+
34
+ if RUBY_ENGINE == "jruby"
35
+ pid, @stdin, @stdout, @stderr = IO.popen4(cmd_str)
36
+ else
37
+ @stdin, @stdout, @stderr = Open3.popen3(cmd_str)
38
+ end
39
+
40
+ @stdout.sync = true
41
+ @stdin.sync = true
42
+
43
+ @stdout.autoclose = true
44
+ @stdin.autoclose = true
45
+
46
+ @err_thread = Knj::Thread.new do
47
+ @stderr.each_line do |str|
48
+ if str.match(/^PHP Fatal error: (.+)\s+/)
49
+ @fatal = str.strip
50
+ end
51
+
52
+ @args[:on_err].call(str) if @args[:on_err]
53
+ $stderr.print "Process error: #{str}" if @debug
54
+ end
55
+ end
56
+
57
+ $stderr.print "Waiting for PHP-script to be ready.\n" if @debug
58
+ started = false
59
+ @stdout.lines do |line|
60
+ if match = line.match(/^php_script_ready:(\d+)\n/)
61
+ started = true
62
+ break
63
+ end
64
+
65
+ $stderr.print "Line gotten while waiting: #{line}" if @debug
66
+ end
67
+
68
+ raise "PHP process wasnt started." if !started
69
+ check_alive
70
+
71
+ $stderr.print "PHP-script ready.\n" if @debug
72
+ start_read_loop
73
+ end
74
+
75
+ #Returns various info in a hash about the object-cache on the PHP-side.
76
+ def object_cache_info
77
+ return self.send(:type => :object_cache_info)
78
+ end
79
+
80
+ #Joins all the threads.
81
+ def join
82
+ @thread.join if @thread
83
+ @err_thread.join if @err_thread
84
+ end
85
+
86
+ #Destroys the object closing and unsetting everything.
87
+ def destroy
88
+ @thread.kill if @thread
89
+ @err_thread.kill if @err_thread
90
+ @stdout.close if @stdout
91
+ @stdin.close if @stdin
92
+ @stderr.close if @stderr
93
+ @thread = nil
94
+ @err_thread = nil
95
+ @fatal = nil
96
+ @responses = nil
97
+ @object_ids = nil
98
+ @object_unset_ids = nil
99
+ @send_count = nil
100
+ @args = nil
101
+ @debug = nil
102
+ end
103
+
104
+ #Proxies to 'send_real' but calls 'flush_unset_ids' first.
105
+ def send(hash)
106
+ self.flush_unset_ids
107
+ return send_real(hash)
108
+ end
109
+
110
+ #Evaluates a string containing PHP-code and returns the result.
111
+ #===Examples
112
+ # print php.eval("array(1 => 2);") #=> {1=>2}
113
+ def eval(eval_str)
114
+ return self.send(:type => :eval, :eval_str => eval_str)
115
+ end
116
+
117
+ #Spawns a new object from a given class with given arguments and returns it.
118
+ #===Examples
119
+ # pe = php.new("PHPExcel")
120
+ # pe.getProperties.setCreator("kaspernj")
121
+ def new(classname, *args)
122
+ return self.send(:type => :new, :class => classname, :args => args)["object"]
123
+ end
124
+
125
+ #Call a function in PHP.
126
+ #===Examples
127
+ # arr = php.func("explode", ";", "1;2;3;4;5")
128
+ # pid_of_php_process = php.func("getmypid")
129
+ # php.func("require_once", "PHPExcel.php")
130
+ def func(func_name, *args)
131
+ return self.send(:type => :func, :func_name => func_name, :args => args)["result"]
132
+ end
133
+
134
+ #This flushes the unset IDs to the PHP-process and frees memory. This is automatically called if 500 IDs are waiting to be flushed. Normally you would not need or have to call this manually.
135
+ #===Examples
136
+ # php.flush_unset_ids(true)
137
+ def flush_unset_ids(force = false)
138
+ return nil if !force and @object_unset_ids.length < 500
139
+ while @object_unset_ids.length > 0 and elements = @object_unset_ids.shift(500)
140
+ $stderr.print "Sending unsets: #{elements}\n" if @debug
141
+ send_real("type" => "unset_ids", "ids" => elements)
142
+ end
143
+ end
144
+
145
+ private
146
+
147
+ #Generates the command from the given object and sends it to the PHP-process. Then returns the parsed result.
148
+ def send_real(hash)
149
+ $stderr.print "Sending: #{hash}\n" if @debug
150
+ str = Base64.strict_encode64(PHP.serialize(hash))
151
+ @stdin.write("send:#{@send_count}:#{str}\n")
152
+ id = @send_count
153
+ @send_count += 1
154
+
155
+ #Slep a tiny bit to wait for first answer.
156
+ sleep 0.001
157
+
158
+ #Then return result.
159
+ return read_result(id)
160
+ end
161
+
162
+ #Searches for a result for a ID and returns it. Runs 'check_alive' to see if the process should be interrupted.
163
+ def read_result(id)
164
+ loop do
165
+ if @responses.key?(id)
166
+ resp = @responses[id]
167
+ @responses.delete(id)
168
+
169
+ if resp.is_a?(Hash) and resp["type"] == "error"
170
+ raise "#{resp["msg"]}\n\n#{resp["bt"]}"
171
+ end
172
+
173
+ return read_parsed_data(resp)
174
+ end
175
+
176
+ check_alive
177
+ sleep 0.05
178
+ $stderr.print "Waiting for answer to ID: #{id}\n" if @debug
179
+ end
180
+ end
181
+
182
+ #Checks if something is wrong. Maybe stdout got closed or a fatal error appeared on stderr?
183
+ def check_alive
184
+ raise "stdout closed." if @stdout and @stdout.closed?
185
+ raise @fatal if @fatal
186
+ end
187
+
188
+ #Parses special hashes to proxy-objects and leaves the rest. This is used automatically.
189
+ def read_parsed_data(data)
190
+ if data.is_a?(Hash) and data["type"] == "php_process_proxy" and data.key?("id")
191
+ return Proxy_obj.new(
192
+ :php => self,
193
+ :id => data["id"].to_i,
194
+ :class => data["class"].to_sym
195
+ )
196
+ elsif data.is_a?(Hash)
197
+ newdata = {}
198
+ data.each do |key, val|
199
+ newdata[key] = read_parsed_data(val)
200
+ end
201
+
202
+ return newdata
203
+ else
204
+ return data
205
+ end
206
+ end
207
+
208
+ #Starts the thread which reads answers from the PHP-process. This is called automatically from the constructor.
209
+ def start_read_loop
210
+ @thread = Knj::Thread.new do
211
+ @stdout.lines do |line|
212
+ data = line.split(":")
213
+ args = PHP.unserialize(Base64.strict_decode64(data[2].strip))
214
+ type = data[0]
215
+ id = data[1].to_i
216
+ $stderr.print "Received: #{id}:#{type}:#{args}\n" if @debug
217
+
218
+ if type == "answer"
219
+ @responses[id] = args
220
+ else
221
+ raise "Unknown type: '#{type}'."
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
227
+
228
+ #This object proxies calls to the object it refers to on the PHP-side. It is automatically spawned from "php.new" and should not be spawned manually.
229
+ #===Examples
230
+ # php = Php_process.new
231
+ # pe = php.new("PHPExcel")
232
+ # pe.getProperties.setCreator("kaspernj")
233
+ class Php_process::Proxy_obj
234
+ #Sets required instance-variables and defines the finalizer for unsetting on the PHP-side.
235
+ def initialize(args)
236
+ @args = args
237
+ @args[:php].object_ids[self.__id__] = @args[:id]
238
+
239
+ #Define finalizer so we can remove the object on PHPs side, when it is collected on the Ruby-side.
240
+ ObjectSpace.define_finalizer(self, @args[:php].method(:objects_unsetter))
241
+ end
242
+
243
+ #Returns the PHP-class of the object that this object refers to as a symbol.
244
+ #===Examples
245
+ # proxy_obj.__phpclass #=> :PHPExcel
246
+ def __phpclass
247
+ return @args[:class]
248
+ end
249
+
250
+ #Sets an instance-variable on the object.
251
+ #===Examples
252
+ # proxy_obj = php.new("stdClass")
253
+ # proxy_obj.__set_var("testvar", 5)
254
+ # proxy_obj.__get_var("testvar") #=> 5
255
+ def __set_var(name, val)
256
+ @args[:php].send(:type => "set_var", :id => @args[:id], :name => name, :val => val)
257
+ return nil
258
+ end
259
+
260
+ #Returns an instance-variable by name.
261
+ #===Examples
262
+ # proxy_obj = php.new("stdClass")
263
+ # proxy_obj.__set_var("testvar", 5)
264
+ # proxy_obj.__get_var("testvar") #=> 5
265
+ def __get_var(name)
266
+ return @args[:php].send(:type => "get_var", :id => @args[:id], :name => name)["result"]
267
+ end
268
+
269
+ #Uses 'method_missing' to proxy all other calls onto the PHP-process and the PHP-object. Then returns the parsed result.
270
+ def method_missing(method_name, *args)
271
+ return @args[:php].send(:type => :object_call, :method => method_name, :args => args, :id => @args[:id])["result"]
272
+ end
273
+ end
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env php5
2
+ <?php
3
+
4
+ class php_process{
5
+ function __construct(){
6
+ $this->sock_stdin = fopen("php://stdin", "r");
7
+ $this->sock_stdout = fopen("php://stdout", "w");
8
+ $this->objects = array();
9
+ $this->objects_count = 0;
10
+ $proxy_to_func = array("func", "get_var", "object_cache_info", "object_call", "require_once_path", "set_var", "unset_ids");
11
+
12
+ print "php_script_ready:" . getmypid() . "\n";
13
+
14
+ while(true){
15
+ $line = fgets($this->sock_stdin, 1048576);
16
+ $data = explode(":", $line);
17
+ $type = $data[0];
18
+ $id = intval($data[1]);
19
+ $args = unserialize(base64_decode($data[2]));
20
+
21
+ try{
22
+ if ($type == "send"){
23
+ if ($args["type"] == "eval"){
24
+ $res = eval($args["eval_str"] . ";");
25
+ $this->answer($id, $res);
26
+ }elseif($args["type"] == "new"){
27
+ $this->new_object($id, $args);
28
+ }elseif(in_array($args["type"], $proxy_to_func)){
29
+ $this->$args["type"]($id, $args);
30
+ }else{
31
+ throw new exception("Unknown send-type: " . $args["type"]);
32
+ }
33
+ }else{
34
+ throw new exception("Invalid type: " . $type);
35
+ }
36
+ }catch(exception $e){
37
+ $this->answer($id, array("type" => "error", "msg" => $e->getMessage(), "bt" => $e->getTraceAsString()));
38
+ }
39
+ }
40
+ }
41
+
42
+ function parse_data($data){
43
+ if (is_array($data)){
44
+ foreach($data as $key => $val){
45
+ if (is_object($val)){
46
+ $this->objects[$this->objects_count] = $val;
47
+ $data[$key] = array("type" => "php_process_proxy", "id" => $this->objects_count, "class" => get_class($val));
48
+ $this->objects_count++;
49
+ }
50
+ }
51
+
52
+ return $data;
53
+ }elseif(is_object($data)){
54
+ $this->objects[$this->objects_count] = $val;
55
+ $ret = array("type" => "php_process_proxy", "id" => $this->objects_count, "class" => get_class($val));
56
+ $this->objects_count++;
57
+ return $ret;
58
+ }else{
59
+ return $data;
60
+ }
61
+ }
62
+
63
+ function answer($id, $data){
64
+ if (!fwrite($this->sock_stdout, "answer:" . $id . ":" . base64_encode(serialize($this->parse_data($data))) . "\n")){
65
+ throw new exception("Could not write to socket.");
66
+ }
67
+ }
68
+
69
+ function new_object($id, $args){
70
+ $class = $args["class"];
71
+ $new_args = $args["args"];
72
+
73
+ $klass = new ReflectionClass($class);
74
+ $object = $klass->newInstanceArgs($new_args);
75
+
76
+ $this->answer($id, array(
77
+ "object" => $object
78
+ ));
79
+ }
80
+
81
+ function set_var($id, $args){
82
+ $object = $this->objects[$args["id"]];
83
+ if (!$object){
84
+ throw new exception("No object by that ID: " . $args["id"]);
85
+ }
86
+
87
+ $object->$args["name"] = $args["val"];
88
+ $this->answer($id, true);
89
+ }
90
+
91
+ function get_var($id, $args){
92
+ $object = $this->objects[$args["id"]];
93
+ if (!$object){
94
+ throw new exception("No object by that ID: " . $args["id"]);
95
+ }
96
+
97
+ $this->answer($id, array(
98
+ "result" => $object->$args["name"]
99
+ ));
100
+ }
101
+
102
+ function require_once_path($id, $args){
103
+ require_once $args["filepath"];
104
+ $this->answer($id, true);
105
+ }
106
+
107
+ function object_call($id, $args){
108
+ $object = $this->objects[$args["id"]];
109
+ if (!$object){
110
+ throw new exception("No object by that ID: " . $args["id"]);
111
+ }
112
+
113
+ $res = call_user_func_array(array($object, $args["method"]), $args["args"]);
114
+ $this->answer($id, array(
115
+ "result" => $res
116
+ ));
117
+ }
118
+
119
+ function func($id, $args){
120
+ //These functions cant be called normally. Hack them with eval instead.
121
+ $specials = array("require", "require_once", "include", "include_once");
122
+
123
+ if (in_array($args["func_name"], $specials)){
124
+ $eval_str = $args["func_name"] . "(";
125
+ $count = 0;
126
+ foreach($args["args"] as $key => $val){
127
+ if (!is_numeric($key)){
128
+ throw new exception("Invalid key: '" . $key . "'.");
129
+ }
130
+
131
+ if ($count > 0){
132
+ $eval_str .= ",";
133
+ }
134
+
135
+ $eval_str .= "\$args['args'][" . $count . "]";
136
+ $count++;
137
+ }
138
+ $eval_str .= ");";
139
+
140
+ $res = eval($eval_str);
141
+ }else{
142
+ $res = call_user_func_array($args["func_name"], $args["args"]);
143
+ }
144
+ $this->answer($id, array("result" => $res));
145
+ }
146
+
147
+ function unset_ids($id, $args){
148
+ foreach($args["ids"] as $obj_id){
149
+ unset($this->objects[$obj_id]);
150
+ }
151
+
152
+ $this->answer($id, true);
153
+ }
154
+
155
+ function object_cache_info($id, $args){
156
+ $types = array();
157
+ foreach($this->objects as $key => $val){
158
+ if (is_object($val)){
159
+ $types[] = "object: " . get_class($val);
160
+ }else{
161
+ $types[] = gettype($val);
162
+ }
163
+ }
164
+
165
+ $this->answer($id, array(
166
+ "count" => count($this->objects),
167
+ "types" => $types
168
+ ));
169
+ }
170
+ }
171
+
172
+ $php_process = new php_process();
@@ -0,0 +1,103 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "PhpProcess" do
4
+ it "should be able to start" do
5
+ require "timeout"
6
+
7
+ $php = Php_process.new(:debug => false)
8
+
9
+
10
+ #Test function calls without arguments.
11
+ pid = $php.func("getmypid")
12
+ raise "Invalid PID: #{id}" if pid.to_i <= 0
13
+
14
+
15
+ #Test function calls with arguments.
16
+ res = $php.func("explode", ";", "1;2;4;5")
17
+ raise "Expected length of result to be 4 but it wasnt: #{res.length}" if res.length != 4
18
+
19
+
20
+ #Test eval.
21
+ resp = $php.eval("return array(1 => 2)")
22
+ raise "Expected hash: '#{resp.class.name}'." if !resp.is_a?(Hash)
23
+ raise "Expected key 1 to be 2: #{resp}." if resp[1] != 2
24
+
25
+
26
+ #Test spawn object and set instance variables.
27
+ proxy_obj = $php.new("stdClass")
28
+ proxy_obj.__set_var("testvar", 5)
29
+ val = proxy_obj.__get_var("testvar")
30
+ raise "Expected val to be 5 but it wasnt: #{val}" if val != 5
31
+ proxy_obj = nil
32
+
33
+
34
+ #Test spawn require and method-calling on objects.
35
+ $php.func("require_once", "knj/http.php")
36
+ http = $php.new("knj_httpbrowser")
37
+ http.connect("www.partyworm.dk")
38
+ resp = http.get("/?show=frontpage")
39
+ raise "Expected length of HTML to be longer than 200: #{resp.to_s.length}" if resp.to_s.length < 200
40
+ http = nil
41
+
42
+
43
+ #Test Table-Writer.
44
+ $php.func("require_once", "knj/table_writer.php")
45
+ $php.func("require_once", "knj/csv.php")
46
+ tw = $php.new("knj_table_writer", {
47
+ "filepath" => "/tmp/php_process_test.csv",
48
+ "format" => "csv",
49
+ "expl" => ";",
50
+ "surr" => '"',
51
+ "encoding" => "utf8"
52
+ })
53
+
54
+
55
+ #Should be able to write 1000 rows in less than 5 sec.
56
+ Timeout.timeout(5) do
57
+ 0.upto(1000) do |i|
58
+ tw.write_row(["test#{i}", i, "test#{i}", i.to_f])
59
+ end
60
+ end
61
+
62
+ tw.close
63
+ tw = nil
64
+
65
+
66
+
67
+ #Test PHPExcel.
68
+ $php.func("require_once", "PHPExcel.php")
69
+ tw = $php.new("knj_table_writer", {
70
+ "filepath" => "/tmp/php_process_test.xlsx",
71
+ "format" => "excel2007"
72
+ })
73
+
74
+
75
+ #Should be able to write 1000 rows in less than 5 sec.
76
+ Timeout.timeout(5) do
77
+ 0.upto(1000) do |i|
78
+ tw.write_row(["test#{i}", i, "test#{i}", i.to_f])
79
+ end
80
+ end
81
+
82
+ tw.close
83
+ tw = nil
84
+
85
+
86
+ #Try some really advanced object-stuff.
87
+ pe = $php.new("PHPExcel")
88
+ pe.getProperties.setCreator("kaspernj")
89
+ pe = nil
90
+
91
+
92
+ #Check garbage collection, cache and stuff.
93
+ cache_info1 = $php.object_cache_info
94
+ GC.start
95
+ $php.flush_unset_ids(true)
96
+ cache_info2 = $php.object_cache_info
97
+ raise "Cache count should be below #{cache_info1["count"]} but it wasnt: #{cache_info2}." if cache_info2["count"] >= cache_info1["count"]
98
+
99
+
100
+ #Destroy the object.
101
+ $php.destroy
102
+ end
103
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'php_process'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: php_process
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kasper Johansen
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-04 00:00:00.000000000 +02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: knjrbfw
17
+ requirement: &11276880 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *11276880
26
+ - !ruby/object:Gem::Dependency
27
+ name: php-serialize
28
+ requirement: &11276100 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *11276100
37
+ - !ruby/object:Gem::Dependency
38
+ name: rspec
39
+ requirement: &11275360 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: 2.8.0
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *11275360
48
+ - !ruby/object:Gem::Dependency
49
+ name: rdoc
50
+ requirement: &11274560 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: '3.12'
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *11274560
59
+ - !ruby/object:Gem::Dependency
60
+ name: bundler
61
+ requirement: &11273780 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: 1.0.0
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: *11273780
70
+ - !ruby/object:Gem::Dependency
71
+ name: jeweler
72
+ requirement: &11272860 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 1.8.3
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: *11272860
81
+ - !ruby/object:Gem::Dependency
82
+ name: rcov
83
+ requirement: &11262100 !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: *11262100
92
+ description: Spawns a PHP process and proxies calls to it, making it possible to proxy
93
+ objects and more.
94
+ email: k@spernj.org
95
+ executables: []
96
+ extensions: []
97
+ extra_rdoc_files:
98
+ - LICENSE.txt
99
+ - README.rdoc
100
+ files:
101
+ - .document
102
+ - .rspec
103
+ - Gemfile
104
+ - LICENSE.txt
105
+ - README.rdoc
106
+ - Rakefile
107
+ - VERSION
108
+ - lib/php_process.rb
109
+ - lib/php_script.php
110
+ - spec/php_process_spec.rb
111
+ - spec/spec_helper.rb
112
+ has_rdoc: true
113
+ homepage: http://github.com/kaspernj/php_process
114
+ licenses:
115
+ - MIT
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ segments:
127
+ - 0
128
+ hash: -1916711195729869743
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubyforge_project:
137
+ rubygems_version: 1.6.2
138
+ signing_key:
139
+ specification_version: 3
140
+ summary: Ruby-to-PHP bridge
141
+ test_files: []