class Php_process
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.
Examples¶ ↑
php = Php_process.new print "PID of PHP-process: #{php.func("getmypid")}\n" print "Explode test: #{php.func("explode", ";", "1;2;3;4;5")}\n"
Attributes
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.
Public Class Methods
Spawns various used variables, launches the process and more.
Examples¶ ↑
If you want debugging printed to stderr:
php = Php_process.new(:debug => true)
# File lib/php_process.rb, line 37 def initialize(args = {}) @args = args @debug = @args[:debug] @send_count = 0 @send_mutex = Mutex.new @responses = Tsafe::MonHash.new @object_ids = Tsafe::MonHash.new @object_unset_ids = Tsafe::MonArray.new @objects = Wref_map.new @constant_val_cache = Tsafe::MonHash.new #Used for 'create_func'. @callbacks = {} @callbacks_count = 0 @callbacks_mutex = Mutex.new if @args[:cmd_php] cmd_str = "#{@args[:cmd_php]} " else cmd_str = "/usr/bin/env php5 " end cmd_str << "\"#{File.dirname(__FILE__)}/php_script.php\"" if RUBY_ENGINE == "jruby" pid, @stdin, @stdout, @stderr = IO.popen4(cmd_str) else @stdin, @stdout, @stderr = Open3.popen3(cmd_str) end @stdout.sync = true @stdin.sync = true @stdin.set_encoding("iso-8859-1:utf-8") #@stderr.set_encoding("utf-8:iso-8859-1") @stdout.set_encoding("utf-8:iso-8859-1") @err_thread = Thread.new do begin @stderr.each_line do |str| @args[:on_err].call(str) if @args[:on_err] $stderr.print "Process error: #{str}" if @debug or @args[:debug_stderr] if str.match(/^PHP Fatal error: (.+)\s*/) @fatal = str.strip elsif str.match(/^Killed\s*$/) @fatal = "Process was killed." end break if (!@args and str.to_s.strip.length <= 0) or (@stderr and @stderr.closed?) end rescue => e $stderr.puts e.inspect $stderr.puts e.backtrace end end $stderr.print "Waiting for PHP-script to be ready.\n" if @debug started = false @stdout.lines do |line| if match = line.match(/^php_script_ready:(\d+)\n/) started = true break end $stderr.print "Line gotten while waiting: #{line}" if @debug end raise "PHP process wasnt started." if !started check_alive $stderr.print "PHP-script ready.\n" if @debug start_read_loop if block_given? begin yield(self) ensure self.destroy end end end
Returns the path to the gem.
# File lib/php_process.rb, line 15 def self.path return File.realpath(File.dirname(__FILE__)) end
Public Instance Methods
Returns the value of a constant on the PHP-side.
# File lib/php_process.rb, line 252 def constant_val(name) const_name = name.to_s if !@constant_val_cache.key?(const_name) @constant_val_cache[const_name] = self.send(:type => :constant_val, :name => name) end return @constant_val_cache[const_name] end
Creates a function on the PHP-side. When the function is called, it callbacks to the Ruby-side which then can execute stuff back to PHP.
Examples¶ ↑
func = php.create_func do |d| d.php.static("Gtk", "main_quit") end button.connect("clicked", func)
# File lib/php_process.rb, line 196 def create_func(args = {}, &block) callback_id = nil func = nil @callbacks_mutex.synchronize do callback_id = @callbacks_count func = Php_process::Created_function.new(:php => self, :id => callback_id) @callbacks[callback_id] = {:block => block, :func => func, :id => callback_id} @callbacks_count += 1 end raise "No callback-ID?" if !callback_id self.send(:type => :create_func, :callback_id => callback_id) return func end
Destroys the object closing and unsetting everything.
# File lib/php_process.rb, line 135 def destroy @thread.kill if @thread @err_thread.kill if @err_thread @stdout.close if @stdout @stdin.close if @stdin @stderr.close if @stderr @thread = nil @err_thread = nil @fatal = nil @responses = nil @object_ids = nil @object_unset_ids = nil @send_count = nil @args = nil @debug = nil end
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.
Examples¶ ↑
php.flush_unset_ids(true)
# File lib/php_process.rb, line 215 def flush_unset_ids(force = false) return nil if !force and @object_unset_ids.length < 500 while @object_unset_ids.length > 0 and elements = @object_unset_ids.shift(500) $stderr.print "Sending unsets: #{elements}\n" if @debug send_real("type" => "unset_ids", "ids" => elements) end #Clean wref-map. @objects.clean end
Call a function in PHP.
Examples¶ ↑
arr = php.func("explode", ";", "1;2;3;4;5") pid_of_php_process = php.func("getmypid") php.func("require_once", "PHPExcel.php")
# File lib/php_process.rb, line 178 def func(func_name, *args) return self.send(:type => :func, :func_name => func_name, :args => parse_data(args)) end
Joins all the threads.
# File lib/php_process.rb, line 129 def join @thread.join if @thread @err_thread.join if @err_thread end
Returns various informations about boths sides memory in a hash.
# File lib/php_process.rb, line 263 def memory_info return { :php_info => self.send(:type => :memory_info), :ruby_info => { :responses => @responses.length, :objects_ids => @object_ids.length, :object_unset_ids => @object_unset_ids.length, :objects => @objects.length } } end
Returns various info in a hash about the object-cache on the PHP-side.
# File lib/php_process.rb, line 124 def object_cache_info return self.send(:type => :object_cache_info) end
This object controls which IDs should be unset on the PHP-side by being a destructor on the Ruby-side.
# File lib/php_process.rb, line 23 def objects_unsetter(id) obj_count_id = @object_ids[id] if @object_unset_ids.index(obj_count_id) == nil @object_unset_ids << obj_count_id end @object_ids.delete(id) end
Parses argument-data into special hashes that can be used on the PHP-side. It is public because the proxy-objects uses it. Normally you would never use it.
# File lib/php_process.rb, line 227 def parse_data(data) if data.is_a?(Php_process::Proxy_obj) return {:type => :proxyobj, :id => data.args[:id]} elsif data.is_a?(Php_process::Created_function) return {:type => :php_process_created_function, :id => data.args[:id]} elsif data.is_a?(Hash) newhash = {} data.each do |key, val| newhash[key] = parse_data(val) end return newhash elsif data.is_a?(Array) newarr = [] data.each do |val| newarr << parse_data(val) end return newarr else return data end end
Proxies to ‘send_real’ but calls ‘flush_unset_ids’ first.
# File lib/php_process.rb, line 153 def send(hash) self.flush_unset_ids return send_real(hash) end
Sends a call to a static method on a class with given arguments.
Examples¶ ↑
php.static("Gtk", "main_quit")
# File lib/php_process.rb, line 185 def static(class_name, method_name, *args) return self.send(:type => :static_method_call, :class_name => class_name, :method_name => method_name, :args => parse_data(args)) end
Private Instance Methods
Checks if something is wrong. Maybe stdout got closed or a fatal error appeared on stderr?
# File lib/php_process.rb, line 330 def check_alive raise "stdout closed." if !@stdout or @stdout.closed? raise @fatal if @fatal end
Parses special hashes to proxy-objects and leaves the rest. This is used automatically.
# File lib/php_process.rb, line 336 def read_parsed_data(data) if data.is_a?(Array) and data.length == 2 and data[0] == "proxyobj" id = data[1].to_i if proxy_obj = @objects.get!(id) $stderr.print "Reuse proxy-obj!\n" if @debug return proxy_obj else $stderr.print "Spawn new proxy-obj!\n" if @debug proxy_obj = Proxy_obj.new( :php => self, :id => id ) @objects[id] = proxy_obj return proxy_obj end elsif data.is_a?(Hash) newdata = {} data.each do |key, val| newdata[key] = read_parsed_data(val) end return newdata else return data end end
Searches for a result for a ID and returns it. Runs ‘check_alive’ to see if the process should be interrupted.
# File lib/php_process.rb, line 307 def read_result(id) $stderr.print "Waiting for answer to ID: #{id}\n" if @debug check_alive begin resp = @responses[id].pop rescue Exception => e if e.class.name == "fatal" #Wait for fatal error to be registered through thread and then throw it. Thread.pass check_alive end raise e end @responses.delete(id) raise "#{resp["msg"]}\n\n#{resp["bt"]}" if resp.is_a?(Hash) and resp["type"] == "error" $stderr.print "Found answer #{id} - returning it.\n" if @debug return read_parsed_data(resp) end
Generates the command from the given object and sends it to the PHP-process. Then returns the parsed result.
# File lib/php_process.rb, line 278 def send_real(hash) $stderr.print "Sending: #{hash[:args]}\n" if @debug and hash[:args] str = Base64.strict_encode64(PHP.serialize(hash)) #Find new ID for the send-request. id = nil @send_mutex.synchronize do id = @send_count @send_count += 1 end @responses[id] = Queue.new begin @stdin.write("send:#{id}:#{str}\n") rescue Errno::EPIPE => e #Wait for fatal error and then throw it. Thread.pass check_alive #Or just throw the normal error. raise e end #Then return result. return read_result(id) end
Starts the thread which reads answers from the PHP-process. This is called automatically from the constructor.
# File lib/php_process.rb, line 365 def start_read_loop @thread = Thread.new do begin @stdout.lines do |line| break if line == nil or @stdout.closed? data = line.split(":") args = PHP.unserialize(Base64.strict_decode64(data[2].strip)) type = data[0] id = data[1].to_i $stderr.print "Received: #{id}:#{type}:#{args}\n" if @debug if type == "answer" @responses[id].push(args) elsif type == "send" if args["type"] == "call_back_created_func" Thread.new do begin func_d = @callbacks[args["func_id"].to_i] func_d[:block].call(*args["args"]) rescue => e $stderr.puts e.inspect $stderr.puts e.backtrace end end end else raise "Unknown type: '#{type}'." end end rescue => e $stderr.puts e.inspect $stderr.puts e.backtrace end end end