|
| 1 | +# rask/libload |
| 2 | + |
| 3 | +A small library to help in loading FFI libraries in a straight forward manner. |
| 4 | + |
| 5 | +## Rationale |
| 6 | + |
| 7 | +The vanilla methods of loading dynamic libraries via PHP FFI are simple and understandable: |
| 8 | + |
| 9 | +- `\FFI::cdef()` allows you to take in a library, and write the library header definition on the fly. |
| 10 | +- `\FFI::load()` allows you to take in a library header, inside which resides `FFI_LIB` that points to a dynamic library to load. |
| 11 | + |
| 12 | +This is all fine and well, apart from one thing: |
| 13 | + |
| 14 | +You're locked tight into the logic of `dlopen(3)`, which is a C function to load dynamic libraries. In essence this means the following: |
| 15 | + |
| 16 | +1. You cannot use relative paths for `FFI_LIB`, as those operate on current working directory, which can be whatever |
| 17 | +2. You cannot use absolute paths, as all software is runnable anywhere on any system in any path |
| 18 | +3. You cannot rely on `LD_LIBRARY_PATH` as it cannot be altered at runtime, and you would require users of your code to set it up properly |
| 19 | +4. You cannot use `/lib` or `/usr/lib`, as that would be senseless pollution on the user's system, not to speak of requiring admin privileges |
| 20 | + |
| 21 | +In smaller projects with limited audiences these limitations might not matter. Or maybe the library you intend to use is a well-known and often preinstalled one (e.g. `libc`). But once you want to distribute public code that relies on a custom built FFI library, you're in trouble. |
| 22 | + |
| 23 | +So, the only real reason this library exists is to bypass these limitations, and allow you to load a dynamic library in a few different ways. |
| 24 | + |
| 25 | +## Features |
| 26 | + |
| 27 | +- Load libraries the `dlopen(3)` way |
| 28 | +- Load libraries according to certain ways of `dlopen(3)` (e.g. allow `LD_LIBRARY_PATH` but disallow others) |
| 29 | +- Disable loading libraries using the `dlopen(3)` logic |
| 30 | +- Load libraries from specific paths, e.g. relative to the current PHP file or similar. |
| 31 | + |
| 32 | +## Example |
| 33 | + |
| 34 | +```php |
| 35 | +<?php |
| 36 | + |
| 37 | +use rask\Libload\Loader; |
| 38 | + |
| 39 | +$loader = new Loader(); |
| 40 | + |
| 41 | +$loader->disableDlopenLogic(); |
| 42 | +$loader->addLibraryPath('/var/app/path/to/libs'); |
| 43 | + |
| 44 | +try { |
| 45 | + $ffi = $loader->load(__DIR__ . '/libs/my_lib.h'); |
| 46 | +} catch (\rask\Libload\LibloadException $e) { |
| 47 | + // log it or something |
| 48 | +} |
| 49 | + |
| 50 | +assert($ffi instanceof \FFI); |
| 51 | +``` |
| 52 | + |
| 53 | +Where `my_lib.h` contains |
| 54 | + |
| 55 | +```h |
| 56 | +#define FFI_LIB "my_lib.so" |
| 57 | + |
| 58 | +... definitions here ... |
| 59 | +``` |
| 60 | + |
| 61 | +The example above instantiates a new `Loader`, and on that loader we disable the default logic of PHP and `dlopen(3)`. Then we pass in a library path (directory) where the loader should look for dynamic libraries in. Our header file `my_lib.h` contains an `FFI_LIB` definition that is not of the path type, and it will be used as a relative path once we disable the `dlopen(3)` default logic. |
| 62 | + |
| 63 | +So, if a dynamic library exists in `/var/app/path/to/libs/my_lib.so` it should get loaded and return a regular PHP `\FFI` instance for us. |
| 64 | + |
| 65 | +## How it works |
| 66 | + |
| 67 | +In a nutshell: |
| 68 | + |
| 69 | +The loader reads your header file, parses the `FFI_LIB` definitions, and then just uses `\FFI::cdef()` with the header file and a proper path to the library you actually want to load. |
| 70 | + |
| 71 | +A little hacky, yes. |
| 72 | + |
| 73 | +## Installation |
| 74 | + |
| 75 | + $ composer require rask/libload |
| 76 | + |
| 77 | +## Usage |
| 78 | + |
| 79 | +> to be written |
| 80 | +
|
| 81 | +## Todo |
| 82 | + |
| 83 | +- Make this package useless, by pestering PHP core devs to add this functionality to the PHP core FFI implementation |
| 84 | + |
| 85 | +## Notes |
| 86 | + |
| 87 | +Currently FFI library loading in opcache preloading contexts is limited to a `php.ini` setting called `ffi.preload`, meaning all bindings you want to have available during preloading must be defined there. This package does not change that, and currently this package targets CLI projects mainly. |
| 88 | + |
| 89 | +## Contributing |
| 90 | + |
| 91 | +Development requirements apart from a PHP CLI installation that supports FFI, you need to have `gcc` and `make` available, and being able to compile on a system that produces Linux shared object binaries (`*.so`). This means you probably cannot run tests on Windows or Mac right now. |
| 92 | + |
| 93 | +We need `gcc` and `make` so we can build a shared library for testing purposes when running PHPUnit. |
| 94 | + |
| 95 | +Before sending code as a pull request: |
| 96 | + |
| 97 | +- Try to add tests for your changes (`composer test`), or ask for help from the maintainers with it |
| 98 | +- Run linting and sttatic analysis against your code before commiting (`composer lint` and `composer stan`) |
| 99 | + |
| 100 | +If you have problems using the package, you can raise issues. Documentation and cleanup contributions very welcome. Feature requests are OK as long as they're not too far from the core purpose of this package: making loading FFI libraries a little simpler/easier/saner/etc. |
| 101 | + |
| 102 | +If you have any questions about how to contribute, you can create an issue and ask for pointers. |
| 103 | + |
| 104 | +## License |
| 105 | + |
| 106 | +MIT License, see [LICENSE.md](./LICENSE.md). |
0 commit comments