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

object_ids[R]

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

new(args = {}) { |self| ... } click to toggle source

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
path() click to toggle source

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

constant_val(name) click to toggle source

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
create_func(args = {}, &block) click to toggle source

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
destroy() click to toggle source

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
eval(eval_str) click to toggle source

Evaluates a string containing PHP-code and returns the result.

Examples

print php.eval("array(1 => 2);") #=> {1=>2}
# File lib/php_process.rb, line 161
def eval(eval_str)
  return self.send(:type => :eval, :eval_str => eval_str)
end
flush_unset_ids(force = false) click to toggle source

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
func(func_name, *args) click to toggle source

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
join() click to toggle source

Joins all the threads.

# File lib/php_process.rb, line 129
def join
  @thread.join if @thread
  @err_thread.join if @err_thread
end
memory_info() click to toggle source

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
new(classname, *args) click to toggle source

Spawns a new object from a given class with given arguments and returns it.

Examples

pe = php.new("PHPExcel")
pe.getProperties.setCreator("kaspernj")
# File lib/php_process.rb, line 169
def new(classname, *args)
  return self.send(:type => :new, :class => classname, :args => parse_data(args))
end
object_cache_info() click to toggle source

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
objects_unsetter(id) click to toggle source

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
parse_data(data) click to toggle source

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
send(hash) click to toggle source

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
static(class_name, method_name, *args) click to toggle source

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

check_alive() click to toggle source

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
read_parsed_data(data) click to toggle source

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
read_result(id) click to toggle source

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
send_real(hash) click to toggle source

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
start_read_loop() click to toggle source

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