/box/vendor/herrera-io/box/src/lib/Herrera/Box/Box.php

https://github.com/zendframework/ZF2Package · PHP · 410 lines · 210 code · 46 blank · 154 comment · 26 complexity · d6b4f8e77f68c26f39173c0abf46427c MD5 · raw file

  1. <?php
  2. namespace Herrera\Box;
  3. use FilesystemIterator;
  4. use Herrera\Box\Compactor\CompactorInterface;
  5. use Herrera\Box\Exception\FileException;
  6. use Herrera\Box\Exception\InvalidArgumentException;
  7. use Herrera\Box\Exception\OpenSslException;
  8. use Herrera\Box\Exception\UnexpectedValueException;
  9. use Phar;
  10. use Phine\Path\Path;
  11. use RecursiveDirectoryIterator;
  12. use RecursiveIteratorIterator;
  13. use RegexIterator;
  14. use SplFileInfo;
  15. use SplObjectStorage;
  16. use Traversable;
  17. /**
  18. * Provides additional, complimentary functionality to the Phar class.
  19. *
  20. * @author Kevin Herrera <kevin@herrera.io>
  21. */
  22. class Box
  23. {
  24. /**
  25. * The source code compactors.
  26. *
  27. * @var SplObjectStorage
  28. */
  29. private $compactors;
  30. /**
  31. * The path to the Phar file.
  32. *
  33. * @var string
  34. */
  35. private $file;
  36. /**
  37. * The Phar instance.
  38. *
  39. * @var Phar
  40. */
  41. private $phar;
  42. /**
  43. * The placeholder values.
  44. *
  45. * @var array
  46. */
  47. private $values = array();
  48. /**
  49. * Sets the Phar instance.
  50. *
  51. * @param Phar $phar The instance.
  52. * @param string $file The path to the Phar file.
  53. */
  54. public function __construct(Phar $phar, $file)
  55. {
  56. $this->compactors = new SplObjectStorage();
  57. $this->file = $file;
  58. $this->phar = $phar;
  59. }
  60. /**
  61. * Adds a file contents compactor.
  62. *
  63. * @param CompactorInterface $compactor The compactor.
  64. */
  65. public function addCompactor(CompactorInterface $compactor)
  66. {
  67. $this->compactors->attach($compactor);
  68. }
  69. /**
  70. * Adds a file to the Phar, after compacting it and replacing its
  71. * placeholders.
  72. *
  73. * @param string $file The file name.
  74. * @param string $local The local file name.
  75. *
  76. * @throws Exception\Exception
  77. * @throws FileException If the file could not be used.
  78. */
  79. public function addFile($file, $local = null)
  80. {
  81. if (null === $local) {
  82. $local = $file;
  83. }
  84. if (false === is_file($file)) {
  85. throw FileException::create(
  86. 'The file "%s" does not exist or is not a file.',
  87. $file
  88. );
  89. }
  90. if (false === ($contents = @file_get_contents($file))) {
  91. throw FileException::lastError();
  92. }
  93. $this->addFromString($local, $contents);
  94. }
  95. /**
  96. * Adds the contents from a file to the Phar, after compacting it and
  97. * replacing its placeholders.
  98. *
  99. * @param string $local The local name.
  100. * @param string $contents The contents.
  101. */
  102. public function addFromString($local, $contents)
  103. {
  104. $this->phar->addFromString(
  105. $local,
  106. $this->replaceValues($this->compactContents($local, $contents))
  107. );
  108. }
  109. /**
  110. * Similar to Phar::buildFromDirectory(), except the files will be
  111. * compacted and their placeholders replaced.
  112. *
  113. * @param string $dir The directory.
  114. * @param string $regex The regular expression filter.
  115. */
  116. public function buildFromDirectory($dir, $regex = null)
  117. {
  118. $iterator = new RecursiveIteratorIterator(
  119. new RecursiveDirectoryIterator(
  120. $dir,
  121. FilesystemIterator::KEY_AS_PATHNAME
  122. | FilesystemIterator::CURRENT_AS_FILEINFO
  123. | FilesystemIterator::SKIP_DOTS
  124. )
  125. );
  126. if ($regex) {
  127. $iterator = new RegexIterator($iterator, $regex);
  128. }
  129. $this->buildFromIterator($iterator, $dir);
  130. }
  131. /**
  132. * Similar to Phar::buildFromIterator(), except the files will be compacted
  133. * and their placeholders replaced.
  134. *
  135. * @param Traversable $iterator The iterator.
  136. * @param string $base The base directory path.
  137. *
  138. * @throws Exception\Exception
  139. * @throws UnexpectedValueException If the iterator value is unexpected.
  140. */
  141. public function buildFromIterator(Traversable $iterator, $base = null)
  142. {
  143. if ($base) {
  144. $base = Path::canonical($base . DIRECTORY_SEPARATOR);
  145. }
  146. foreach ($iterator as $key => $value) {
  147. if (is_string($value)) {
  148. if (false === is_string($key)) {
  149. throw UnexpectedValueException::create(
  150. 'The key returned by the iterator (%s) is not a string.',
  151. gettype($key)
  152. );
  153. }
  154. $key = Path::canonical($key);
  155. $value = Path::canonical($value);
  156. if (is_dir($value)) {
  157. $this->phar->addEmptyDir($key);
  158. } else {
  159. $this->addFile($value, $key);
  160. }
  161. } elseif ($value instanceof SplFileInfo) {
  162. if (null === $base) {
  163. throw InvalidArgumentException::create(
  164. 'The $base argument is required for SplFileInfo values.'
  165. );
  166. }
  167. /** @var $value SplFileInfo */
  168. $real = $value->getRealPath();
  169. if (0 !== strpos($real, $base)) {
  170. throw UnexpectedValueException::create(
  171. 'The file "%s" is not in the base directory.',
  172. $real
  173. );
  174. }
  175. $local = str_replace($base, '', $real);
  176. if ($value->isDir()) {
  177. $this->phar->addEmptyDir($local);
  178. } else {
  179. $this->addFile($real, $local);
  180. }
  181. } else {
  182. throw UnexpectedValueException::create(
  183. 'The iterator value "%s" was not expected.',
  184. gettype($value)
  185. );
  186. }
  187. }
  188. }
  189. /**
  190. * Compacts the file contents using the supported compactors.
  191. *
  192. * @param string $file The file name.
  193. * @param string $contents The file contents.
  194. *
  195. * @return string The compacted contents.
  196. */
  197. public function compactContents($file, $contents)
  198. {
  199. foreach ($this->compactors as $compactor) {
  200. /** @var $compactor CompactorInterface */
  201. if ($compactor->supports($file)) {
  202. $contents = $compactor->compact($contents);
  203. }
  204. }
  205. return $contents;
  206. }
  207. /**
  208. * Creates a new Phar and Box instance.
  209. *
  210. * @param string $file The file name.
  211. * @param integer $flags The RecursiveDirectoryIterator flags.
  212. * @param string $alias The Phar alias.
  213. *
  214. * @return Box The Box instance.
  215. */
  216. public static function create($file, $flags = null, $alias = null)
  217. {
  218. return new Box(new Phar($file, $flags, $alias), $file);
  219. }
  220. /**
  221. * Returns the Phar instance.
  222. *
  223. * @return Phar The instance.
  224. */
  225. public function getPhar()
  226. {
  227. return $this->phar;
  228. }
  229. /**
  230. * Returns the signature of the phar.
  231. *
  232. * This method does not use the extension to extract the phar's signature.
  233. *
  234. * @param string $path The phar file path.
  235. *
  236. * @return array The signature.
  237. */
  238. public static function getSignature($path)
  239. {
  240. return Signature::create($path)->get();
  241. }
  242. /**
  243. * Replaces the placeholders with their values.
  244. *
  245. * @param string $contents The contents.
  246. *
  247. * @return string The replaced contents.
  248. */
  249. public function replaceValues($contents)
  250. {
  251. return str_replace(
  252. array_keys($this->values),
  253. array_values($this->values),
  254. $contents
  255. );
  256. }
  257. /**
  258. * Sets the bootstrap loader stub using a file.
  259. *
  260. * @param string $file The file path.
  261. * @param boolean $replace Replace placeholders?
  262. *
  263. * @throws Exception\Exception
  264. * @throws FileException If the stub file could not be used.
  265. */
  266. public function setStubUsingFile($file, $replace = false)
  267. {
  268. if (false === is_file($file)) {
  269. throw FileException::create(
  270. 'The file "%s" does not exist or is not a file.',
  271. $file
  272. );
  273. }
  274. if (false === ($contents = @file_get_contents($file))) {
  275. throw FileException::lastError();
  276. }
  277. if ($replace) {
  278. $contents = $this->replaceValues($contents);
  279. }
  280. $this->phar->setStub($contents);
  281. }
  282. /**
  283. * Sets the placeholder values.
  284. *
  285. * @param array $values The values.
  286. *
  287. * @throws Exception\Exception
  288. * @throws InvalidArgumentException If a non-scalar value is used.
  289. */
  290. public function setValues(array $values)
  291. {
  292. foreach ($values as $value) {
  293. if (false === is_scalar($value)) {
  294. throw InvalidArgumentException::create(
  295. 'Non-scalar values (such as %s) are not supported.',
  296. gettype($value)
  297. );
  298. }
  299. }
  300. $this->values = $values;
  301. }
  302. /**
  303. * Signs the Phar using a private key.
  304. *
  305. * @param string $key The private key.
  306. * @param string $password The private key password.
  307. *
  308. * @throws Exception\Exception
  309. * @throws OpenSslException If the "openssl" extension could not be used
  310. * or has generated an error.
  311. */
  312. public function sign($key, $password = null)
  313. {
  314. OpenSslException::reset();
  315. if (false === extension_loaded('openssl')) {
  316. // @codeCoverageIgnoreStart
  317. throw OpenSslException::create(
  318. 'The "openssl" extension is not available.'
  319. );
  320. // @codeCoverageIgnoreEnd
  321. }
  322. if (false === ($resource = openssl_pkey_get_private($key, $password))) {
  323. // @codeCoverageIgnoreStart
  324. throw OpenSslException::lastError();
  325. // @codeCoverageIgnoreEnd
  326. }
  327. if (false === openssl_pkey_export($resource, $private)) {
  328. // @codeCoverageIgnoreStart
  329. throw OpenSslException::lastError();
  330. // @codeCoverageIgnoreEnd
  331. }
  332. if (false === ($details = openssl_pkey_get_details($resource))) {
  333. // @codeCoverageIgnoreStart
  334. throw OpenSslException::lastError();
  335. // @codeCoverageIgnoreEnd
  336. }
  337. $this->phar->setSignatureAlgorithm(Phar::OPENSSL, $private);
  338. if (false === @file_put_contents($this->file . '.pubkey', $details['key'])) {
  339. throw FileException::lastError();
  340. }
  341. }
  342. /**
  343. * Signs the Phar using a private key file.
  344. *
  345. * @param string $file The private key file name.
  346. * @param string $password The private key password.
  347. *
  348. * @throws Exception\Exception
  349. * @throws FileException If the private key file could not be read.
  350. */
  351. public function signUsingFile($file, $password = null)
  352. {
  353. if (false === is_file($file)) {
  354. throw FileException::create(
  355. 'The file "%s" does not exist or is not a file.',
  356. $file
  357. );
  358. }
  359. if (false === ($key = @file_get_contents($file))) {
  360. throw FileException::lastError();
  361. }
  362. $this->sign($key, $password);
  363. }
  364. }