{"id":19300796,"url":"https://github.com/takuya/php-proc_open-wrapper","last_synced_at":"2025-04-12T06:41:51.785Z","repository":{"id":259163152,"uuid":"876545164","full_name":"takuya/php-proc_open-wrapper","owner":"takuya","description":"php proc_open wrapper class","archived":false,"fork":false,"pushed_at":"2025-04-11T10:36:32.000Z","size":107,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-11T12:23:40.547Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/takuya.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-10-22T06:41:04.000Z","updated_at":"2025-04-11T10:36:36.000Z","dependencies_parsed_at":"2024-11-19T12:21:43.206Z","dependency_job_id":"53b39a54-beda-470c-aca8-e5516ac0c91a","html_url":"https://github.com/takuya/php-proc_open-wrapper","commit_stats":null,"previous_names":["takuya/php-proc_open-wrapper"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takuya%2Fphp-proc_open-wrapper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takuya%2Fphp-proc_open-wrapper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takuya%2Fphp-proc_open-wrapper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takuya%2Fphp-proc_open-wrapper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/takuya","download_url":"https://codeload.github.com/takuya/php-proc_open-wrapper/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248530610,"owners_count":21119591,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-09T23:15:50.898Z","updated_at":"2025-04-12T06:41:51.775Z","avatar_url":"https://github.com/takuya.png","language":"PHP","readme":"## Run Process.\n\n\nRun process and run string by interpreter(ex.python,bash). and keep process long time task running.\n\n`proc_open` is distinctive style. so I make `proc_open` EASY and friendly.\n\n\n## Installing\n\nfrom packagist. \n```\ncomposer require takuya/php-proc_open-wrapper\n```\nfrom GitHub repos.\n```sh\nname='php-proc_open-wrapper'\ncomposer config repositories.$name \\\nvcs https://github.com/takuya/$name  \ncomposer require takuya/$name:master\ncomposer install\n```\n\ncheck installation.\n\nsample.php\n```php\n\u003c?php\nrequire_once 'vendor/autoload.php';\nuse Takuya\\ProcOpen\\ProcOpen;\n\n$p = new ProcOpen( ['php','-v'] );\n$p-\u003estart();\n// This is enough to wait process end, because blocked. \necho $output = stream_get_contents($p-\u003egetFd(1));\n```\nrun.\n```shell\nphp sample.php\n```\n\nwait long time process.\n\n```php\n\u003c?php\nrequire_once 'vendor/autoload.php';\nuse Takuya\\ProcOpen\\ProcOpen;\n\n$p = new ProcOpen( ['sleep','30'] );\n$p-\u003estart();\n$p-\u003ewait();\n```\n\n## comparison proc_open , ProcOpen.\ncompare proc_open(original) , ProcOpen(this package) in using.\n- process (no input)\n- process (with stdin,stderr,stdout )\n- process (read output)\n- process (pipe line)\n\n### simple (no input, no stdout)\nproc_open\n```php\n\u003c?php\n$proc = proc_open('rm -rf /tmp/ABcDE',[1=\u003e['pipe','w'],2=\u003e['pipe','w']],$io);\n\nwhile( ($pstat = proc_get_status($proc))\u0026\u0026 $pstat['running'] ){\n  usleep(100);\n}\n\n```\nProcOpen Wrapper(this package)\n```php\n\u003c?php\nrequire_once 'vendor/autoload.php';\nuse Takuya\\ProcOpen\\ProcOpen;\n//\n$p = new ProcOpen('rm -rf /tmp/ABcDE');\n$p-\u003estart();\n$p-\u003ewait();\n```\n### simple ( with stdin,stderr,stdout )\nproc_open\n```php\n\u003c?php\n\n$proc = proc_open('grep root',[0=\u003e['pipe','r'],1=\u003e['pipe','w'],2=\u003e['pipe','w']],$io);\n// pass stdin\nstream_copy_to_stream(fopen('/etc/passwd','r'),$io[0]);\nfclose($io[0]);\n// get stderr , stdout\nfile_put_contents('php://stderr',stream_get_contents($io[2]));\necho stream_get_contents($io[1]);\n```\nProcOpen Wrapper (this package)\n```php\n\u003c?php\nuse Takuya\\ProcOpen\\ProcOpen;\n\n$proc = new ProcOpen(['grep','root']);\n$proc-\u003esetInput(fopen('/etc/passwd','r'));\n$proc-\u003estart();\nfile_put_contents('php://stderr',$proc-\u003egetErrout());\necho $proc-\u003egetOutput();\n\n```\n### simple ( passthru IO)\n```php\n\u003c?php\n/// proc_open\n$proc_res = proc_open('/usr/bin/echo Hello World',[0=\u003e['pipe','r'],1=\u003eSTDOUT,2=\u003eSTDERR],$io);\n/// ProcOpen\n$proc = new ProcOpen(['/usr/bin/echo','Hello World']);\n$proc-\u003esetStdout(STDOUT);\n$proc-\u003esetStderr(STDERR);\n$proc-\u003erun();\n```\n### simple ( read output )\nOpen php Interactive Shell (`php -a`) and write and read out\n```shell\ntakuya@host :$ php -a\nInteractive shell\n\nphp \u003e echo 0;\n0\nphp \u003e echo 1;\n1\nphp \u003e echo 2;\n2\n```\n`proc_open` : Do as above. \n```php\n\u003c?php\n$proc = proc_open(['php','-a'],[0 =\u003e['pipe','r'],1 =\u003e['pipe','w']],$io);\nforeach (range(0, 9) as $str) {\n  echo fread($io[1],1024);\n  fwrite($io[0],\"echo {$str};\\n\");\n}\nfclose($io[0]);\necho stream_get_contents($io[1]);\n```\n`ProcOpen` Wrapper\n```php\n\u003c?php\n$proc = new ProcOpen(['php','-a']);\n$proc-\u003estart();\nforeach (range(0, 9) as $str) {\n  echo fread($proc-\u003estdout(),1024);\n  fwrite($proc-\u003estdin(),\"echo {$str};\\n\");\n}\nfclose($proc-\u003estdin());\necho $proc-\u003egetOutput();\n```\n\n### sample pipe(  String and Shell ) \n`proc_open` is accept `string $cmd` but not safe. \n```php\n\u003c?php\n# shell calling, Not Safe.\n$proc = proc_open('cat /etc/passwd | grep root',[1=\u003e['pipe','w']],$io);\necho stream_get_contents($io[1]);\n\n# pipe io , More Safe.\n$p1 = proc_open(['cat','/etc/passwd'],[1=\u003e['pipe','w']],$p1_io);\n$p2 = proc_open(['grep','root'],[0=\u003e$p1_io[1],1=\u003e['pipe','w']],$p2_io);\necho stream_get_contents($p2_io[1]);\n```\n`ProcOpen` wrapper, shell call and pipe io\n````php\n\u003c?php\n// shell calling, explicitly use SHELL.\n$p = new ProcOpen(['bash']);\n$p-\u003esetInput('cat /etc/passwd | grep root');\n$p-\u003estart();\necho $p-\u003egetOutput();\n\n// pipe io , more safe and easy to maintenance.\n$p1 = new ProcOpen(['cat','/etc/passwd']);\n$p2 = new ProcOpen(['grep','root']);\n$p1-\u003estart();\n$p2-\u003esetInput($p1-\u003estdout());\n$p2-\u003estart();\necho $p2-\u003egetOutput();\n````\n\n## Test\n\nphpunit \n\n```shell\ncomposer install \nvendor/bin/phpunit \nvendor/bin/phpunit --filter ProcOpen\n```\n## examples \n\nSee [Examples](https://github.com/takuya/php-proc_open-wrapper/tree/master/examples).\n\n## safer process call\n\nThis package directly exec command, not using 'shell'.\n\nproc_open executes command directly, without shell ( fork then exec ).\n\nUsing proc_open correctly, `directory traversal` vulnerability , `shell injection` vulnerability will not be occurred. Shell escaping will not be needed. this can reduce risks.\n\nDon't escape string , use array. \n\n#### use cmd array. not escaping.\nproc_open can accept cmd string, but cmd string may be danger(mal-escaping). exec cmd by array is more safe.\n\n```php\n\u003c?php\n$p = new ProcOpen( ['php','-v'] );\n$p-\u003estart();\n$p-\u003ewait();\n//\n$output = stream_get_contents($p-\u003egetFd(1));\n```\n\nyou must check arguments.\n\nTo prevent directory traversal. you must check args in advance. cmd string cannot be checked, but array can check.\n```php\n\u003c?php\n$file_name = '../../../../../../../../etc/shadow';\n$file_name = realpath('/my/app_root/'.basename($file_name);// false\nproc_open(['cat',$file_name]...);\n```\n\n## pipe process\n\nMake easy to pipe process by proc_open.\n\n```php\n\u003c?php\n//\n// run `php -i | grep pcntl` by ProcOpen class \n//\n$p1 = new ProcOpen( ['php','-i'] );\n$p1-\u003estart();\n\n$p2 = new ProcOpen(['grep','pcntl'])\n$p2-\u003esetInput($p1-\u003egetFd(1));//pipe p1-\u003estdout to p2-\u003estdin\n$p2-\u003estart();\n// \n$p1-\u003ewait();\n$p2-\u003ewait();\n//\n$output = stream_get_contents($p2-\u003egetFd(1));\n```\n\npipe by pure php. It's very painful.\n```php\n\u003c?php\n//\n// run `ls /etc | grep su` by proc_open() pipe\n//\n$p1_fd_res = [['pipe','r'],['pipe','w'],['pipe','w']];\n$p1 = proc_open(['ls','/etc'],$p1_fd_res,$p1_pipes);\nfclose($p1_pipes[0]);\n$p2_fd_res = [$p1_pipes[1],['pipe','w'],['pipe','w']];\n$p2 = proc_open(['grep','su'],$p2_fd_res,$p2_pipes);\n\nwhile(proc_get_status($p1)[\"running\"]){\nusleep(100);\n}\nwhile(proc_get_status($p2)[\"running\"]){\nusleep(100);\n}\n//\n$str = fread($p2_pipes[1],1024);\nvar_dump($str);\n```\n\nUse this ProcOpen class , reduce cost by naturally call proc_open.\n\n## Run string as shell script. \n```php\n\u003c?php\n$proc = new ProcOpen(['bash'],__DIR__,['SHELL'=\u003e'php']);\n$proc-\u003esetInput('\nfor i in {0..4}; do\n  echo $i\ndone;\n');\n$proc-\u003estart();\n$proc-\u003ewait();\n//blocking io\necho fread($proc-\u003egetFd(1), 1024);\n```\n\n## Run string as python code.\n\nrun python code in proc_open\n\n```php\n\u003c?php\n$proc = new ProcOpen(['python']);\n$proc-\u003esetInput('\nimport os;\nimport sys\nfor i in range(5):\n  print(i);\nprint(\"end\")\nprint(sys.path)\n');\n$proc-\u003estart();\n$proc-\u003ewait();\n//blocking io\necho stream_get_contents($proc-\u003egetFd(ProcOpen::STDOUT);\n```\n\n## Run php interactive shell `php -a `\n\n```php\n\u003c?php\n$proc = new ProcOpen(['php','-a']);\n$proc-\u003estart();\n\n$fp = $proc-\u003egetFd(ProcOpen::STDIN);\nfwrite($fp,'echo 0 ;'.PHP_EOL);\nfwrite($fp,'echo 1 ;'.PHP_EOL);\nfwrite($fp,'echo 2 ;'.PHP_EOL);\nfwrite($fp,'echo 3 ;'.PHP_EOL);\nfwrite($fp,'echo 4 ;'.PHP_EOL);\nfclose($fp); // finish php interactive shell\n\n// get output.\n$output= stream_get_contents($proc-\u003egetFd(1));\n\n```\n## wait() is not always needed.\nread output will be Blocked by OS until process end. `Proc#wait` is not always needed.\n```php\n\u003c?php\n$proc = new ProcOpen(['cat','/etc/passwd']);\n$proc-\u003estart();\n// $proc automatically wait by OS blocking io. \n// `$proc-\u003ewait();` is not necessary.\necho stream_get_contents($proc-\u003estdout);\n```\nProcOpen has  shortcut by function\n```php\n\u003c?php\n// a shortcut function.\n$proc-\u003egetOutput();\n// Same to this.\nstream_get_contents($proc-\u003estdout)\n```\n\n\n## Linux pipe max.\n\nLinux pipe will be Stuck(blocked) if left unread.\n\n`LINUX_PIPE_SIZE` is `1024*64`.\nso if you try to write one more byte (`1024*64+1`bytes) to `stdout`, process will be blocked by OS.\n\n```php\n\u003c?php\n$proc = new ProcOpen( ['php'] );\n$proc-\u003esetInput(\u003c\u003c\u003c'EOS'\n  \u003c?php\n  define('LINUX_PIPE_SIZE_MAX',1024*64+1);\n  foreach(range(1,LINUX_PIPE_SIZE_MAX) as $i){\n    echo 'a';\n  }\n  EOS);\n$proc-\u003estart();\n// this will be blocked.\n\n```\nTo avoid BUFF stuck use blockingIO instead of wait.\n```php\n\u003c?php\n$popen = new ProcOpen(['php','-i'],null,$env);\n$popen-\u003estart();\n// instead of wait() use blockingIO.\nreturn stream_get_contents($popen-\u003estdout());\n```\n\nor, To avoid blocking, you can use tmp-io.\n\n```php\n\u003c?php\n$proc = new ProcOpen( ['php'] );\n$proc-\u003esetInput(\u003c\u003c\u003c'EOS'\n  \u003c?php\n  define('LINUX_PIPE_SIZE_MAX',1024*64+1);\n  foreach(range(1,LINUX_PIPE_SIZE_MAX) as $i){\n    echo 'a';\n  }\n  EOS);\n$proc-\u003esetStdout($tmp = fopen('php://temp','w'));\n$proc-\u003estart();\n// this will be successfully finished.\n```\n\nOr use `select syscall` and read the buffer.\n\n```php\n\u003c?php\n$proc = new ProcOpen( ['php'] );\n$proc-\u003esetInput(\u003c\u003c\u003c'EOS'\n  \u003c?php\n  define('LINUX_PIPE_SIZE_MAX',1024*64+1);\n  foreach(range(1,LINUX_PIPE_SIZE_MAX) as $i){\n    echo 'a';\n  }\n  EOS);\n$proc-\u003estart();\n// use select not to be clogged.\n$output = '';\n$avail = stream_select( ...( $selected = [[$proc-\u003egetFd( 1 )], [], null, 0, 100] ) );\nif ( $avail \u003e 0 ) {\n  $output .= fread( $proc-\u003egetFd( 1 ), 1 );\n}\n```\n\n'php://temp' may be looks good, but that is not all good. It will be cast to TEMP FILE.\n\n```php\n$popen = new ProcOpen(['php','-i'],null,$env);\n// This is not memory , proc_open cast IO to /tmp/phpXXXX .\n$popen-\u003esetOutput($out=fopen('php://temp/maxmemory:'.(1024*1024*10)));\n$popen-\u003estart();\n$popen-\u003ewait();\n// in case Ctrl-C  this will remain temp_file in /tmp \necho $popen-\u003egetOutput();//\n```\nTo use fopen wrapppers , flag the property before start.\n```php\n$popen = new ProcOpen(['php','-i'],null,$env);\n$popen-\u003eenableBuffering();//FLAG\n// after add Flag , memory can be used.\n$popen-\u003esetOutput(fopen('php://memory','w+'));\n$popen-\u003estart();\n$popen-\u003ewait();\necho $popen-\u003egetOutput();// no blocking, no error.\n```\n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftakuya%2Fphp-proc_open-wrapper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftakuya%2Fphp-proc_open-wrapper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftakuya%2Fphp-proc_open-wrapper/lists"}