PageRenderTime 65ms CodeModel.GetById 35ms RepoModel.GetById 1ms app.codeStats 0ms

/src/YapepBase/File/FileHandlerUnix.php

https://bitbucket.org/szeber/yapep_base
PHP | 662 lines | 283 code | 67 blank | 312 comment | 45 complexity | d45684ec159a549e4a5caffd032557b2 MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of YAPEPBase.
  4. *
  5. * @package YapepBase
  6. * @subpackage File
  7. * @copyright 2011 The YAPEP Project All rights reserved.
  8. * @license http://www.opensource.org/licenses/bsd-license.php BSD License
  9. */
  10. namespace YapepBase\File;
  11. use YapepBase\Application;
  12. use YapepBase\Exception\File\NotFoundException;
  13. use YapepBase\Exception\ParameterException;
  14. use YapepBase\Shell\CommandExecutor;
  15. use YapepBase\Exception\File\Exception;
  16. use YapepBase\Shell\ICommandExecutor;
  17. /**
  18. * File handler that uses unix commands
  19. *
  20. * @package YapepBase
  21. * @subpackage File
  22. */
  23. class FileHandlerUnix implements IFileHandler {
  24. /**
  25. * Sets the access and modification time of a file or directory.
  26. *
  27. * @link http://php.net/manual/en/function.touch.php
  28. *
  29. * @param string $path Tha path to the file or directory.
  30. * @param int $modificationTime The modification time to set (timestamp).
  31. * @param int $accessTime The access time to set(timestamp).
  32. *
  33. * @return void
  34. *
  35. * @throws \YapepBase\Exception\File\Exception If the operation failed.
  36. */
  37. public function touch($path, $modificationTime = null, $accessTime = null) {
  38. $diContainer = Application::getInstance()->getDiContainer();
  39. if (empty($modificationTime) && empty($accessTime)) {
  40. $this->runCommandAndThrowExceptionIfFailed($diContainer->getCommandExecutor('touch')
  41. ->addParam(null, $path), 'Touch failed for path: ' . $path);
  42. } else {
  43. if (!empty($modificationTime)) {
  44. $this->runCommandAndThrowExceptionIfFailed($diContainer->getCommandExecutor('touch')
  45. ->addParam('-m')
  46. ->addParam('-t', date('YmdHi.s', $modificationTime))
  47. ->addParam(null, $path), 'Touch failed for path (modification time): ' . $path);
  48. }
  49. if (!empty($accessTime)) {
  50. $this->runCommandAndThrowExceptionIfFailed($diContainer->getCommandExecutor('touch')
  51. ->addParam('-a')
  52. ->addParam('-t', date('YmdHi.s', $accessTime))
  53. ->addParam(null, $path), 'Touch failed for path (access time): ' . $path);
  54. }
  55. }
  56. }
  57. /**
  58. * Makes a directory. Be aware that by default it is recursive.
  59. *
  60. * @link http://php.net/manual/en/function.mkdir.php
  61. *
  62. * @param string $path The directory or structure(in case of recursive mode) to create.
  63. * @param int $mode The mode (rights) of the created directory, use octal value.
  64. * @param bool $isRecursive If TRUE the whole structure will be created.
  65. *
  66. * @return void
  67. *
  68. * @throws \YapepBase\Exception\File\Exception If the operation failed.
  69. */
  70. public function makeDirectory($path, $mode = 0755, $isRecursive = true) {
  71. $command = Application::getInstance()->getDiContainer()->getCommandExecutor('mkdir')
  72. ->addParam('-m', str_pad(decoct($mode), 5, '0', STR_PAD_LEFT));
  73. if ($isRecursive) {
  74. $command->addParam('-p');
  75. }
  76. $command->addParam(null, $path);
  77. $this->runCommandAndThrowExceptionIfFailed($command, 'Failed to create directory: ' . $path);
  78. }
  79. /**
  80. * Writes the given content to a file.
  81. *
  82. * @link http://php.net/manual/en/function.file-put-contents.php
  83. *
  84. * @param string $path Path to the file.
  85. * @param mixed $data Can be either a string, an array or a stream resource.
  86. * @param bool $append If TRUE, the given data will appended after the already existent data.
  87. * @param bool $lock If TRUE, the file will be locked at the writing.
  88. *
  89. * @return int The byte count of the written data.
  90. *
  91. * @throws \YapepBase\Exception\File\Exception If the operation failed.
  92. */
  93. public function write($path, $data, $append = false, $lock = false) {
  94. $redirectType = $append
  95. ? CommandExecutor::OUTPUT_REDIRECT_STDOUT_APPEND
  96. : CommandExecutor::OUTPUT_REDIRECT_STDOUT;
  97. $diContainer = Application::getInstance()->getDiContainer();
  98. // Make sure the binary is run and not any alias or shell built-in.
  99. $echoCommand = $diContainer->getCommandExecutor('env')
  100. ->addParam(null, 'echo')
  101. ->addParam('-n')
  102. ->addParam(null, $data)
  103. ->setOutputRedirection($redirectType, $path, true);
  104. if ($lock) {
  105. $this->runCommandAndThrowExceptionIfFailed($diContainer->getCommandExecutor('flock')
  106. ->addParam('-x')
  107. ->addParam(null, $path)
  108. ->addParam('-c', $echoCommand->getCommand()), 'Failed to write data to file: ' . $path);
  109. } else {
  110. $this->runCommandAndThrowExceptionIfFailed($echoCommand, 'Failed to write data to file: ' . $path);
  111. }
  112. return strlen($data);
  113. }
  114. /**
  115. * Changes the owner group and user of the file.
  116. *
  117. * @link http://php.net/manual/en/function.chgrp.php
  118. * @link http://php.net/manual/en/function.chown.php
  119. *
  120. * @param string $path Path to the file or directory.
  121. * @param string|int $group Name of the group, or the identifier.
  122. * @param string|int $user Name of the user, or the identifier.
  123. *
  124. * @return void
  125. *
  126. * @throws \YapepBase\Exception\File\NotFoundException If the file was not found.
  127. * @throws \YapepBase\Exception\File\Exception If it failed to set the owner.
  128. */
  129. public function changeOwner($path, $group = null, $user = null) {
  130. if (!$this->checkIsPathExists($path)) {
  131. throw new NotFoundException($path, 'Resource not found while changing owner: ' . $path);
  132. }
  133. if (is_numeric($user)) {
  134. $systemUser = posix_getpwuid($user);
  135. if (empty($systemUser)) {
  136. throw new Exception('Unable to find user with UID: ' . $user);
  137. }
  138. $user = $systemUser['name'];
  139. }
  140. if (is_numeric($group)) {
  141. $systemGroup = posix_getgrgid($group);
  142. if (empty($systemGroup)) {
  143. throw new Exception('Unable to find group with GID: ' . $group);
  144. }
  145. $group = $systemGroup['name'];
  146. }
  147. $this->runCommandAndThrowExceptionIfFailed(
  148. Application::getInstance()->getDiContainer()->getCommandExecutor('chown')
  149. ->addParam(null, (string)$user . ':' . (string)$group)
  150. ->addParam(null, $path),
  151. 'Failed to set the group "' . $group . '" and user "' . $user . '" of the resource: ' . $path);
  152. }
  153. /**
  154. * Changes the mode of the file.
  155. *
  156. * @link http://php.net/manual/en/function.chmod.php
  157. *
  158. * @param string $path Path to the file or directory.
  159. * @param int $mode The mode to set, use octal values.
  160. *
  161. * @return void
  162. *
  163. * @throws \YapepBase\Exception\File\NotFoundException If the file was not found.
  164. * @throws \YapepBase\Exception\File\Exception If it failed to set the mode.
  165. */
  166. public function changeMode($path, $mode) {
  167. if (!$this->checkIsPathExists($path)) {
  168. throw new NotFoundException($path, 'Resource not found while changing mode: ' . $path);
  169. }
  170. $this->runCommandAndThrowExceptionIfFailed(
  171. Application::getInstance()->getDiContainer()->getCommandExecutor('chmod')
  172. ->addParam(null, decoct($mode))
  173. ->addParam(null, $path),
  174. 'Failed to set the mode "' . decoct($mode) . '" of the resource: ' . $path);
  175. }
  176. /**
  177. * Copies a file.
  178. *
  179. * If the destination path already exists, it will be overwritten.
  180. *
  181. * @link http://php.net/manual/en/function.copy.php
  182. *
  183. * @param string $source Path to the source file.
  184. * @param string $destination The destination path.
  185. *
  186. * @return void
  187. *
  188. * @throws \YapepBase\Exception\File\NotFoundException If the source file was not found.
  189. * @throws \YapepBase\Exception\File\Exception If it failed to set the mode.
  190. */
  191. public function copy($source, $destination) {
  192. if (!$this->checkIsPathExists($source)) {
  193. throw new NotFoundException($source, 'Source file not found for copy: ' . $source);
  194. }
  195. $this->runCommandAndThrowExceptionIfFailed(
  196. Application::getInstance()->getDiContainer()->getCommandExecutor('cp')
  197. ->addParam(null, $source)
  198. ->addParam(null, $destination),
  199. 'Failed to copy file from ' . $source . ' to ' . $destination);
  200. }
  201. /**
  202. * Deletes a file.
  203. *
  204. * @link http://php.net/manual/en/function.unlink.php
  205. *
  206. * @param string $path Path to the file.
  207. *
  208. * @return void
  209. *
  210. * @throws \YapepBase\Exception\File\NotFoundException If the given path is not found.
  211. * @throws \YapepBase\Exception\File\Exception If it failed to delete the file or the given path
  212. * is not a regular file.
  213. */
  214. public function remove($path) {
  215. if ($this->checkIsDirectory($path)) {
  216. throw new Exception('The given path is not a valid file: ' . $path);
  217. }
  218. $this->runCommandAndThrowExceptionIfFailed(
  219. Application::getInstance()->getDiContainer()->getCommandExecutor('rm')
  220. ->addParam('-f')
  221. ->addParam(null, $path),
  222. 'Failed to remove file: ' . $path);
  223. }
  224. /**
  225. * Deletes a directory.
  226. *
  227. * @param string $path The directory to delete.
  228. * @param bool $isRecursive If TRUE the contents will be removed too.
  229. *
  230. * @throws \YapepBase\Exception\File\Exception If the given path is not empty, and recursive mode is off.
  231. * Or if the given path is not a valid directory, or deletion failed.
  232. *
  233. * @return void
  234. */
  235. public function removeDirectory($path, $isRecursive = false) {
  236. if (!$this->checkIsPathExists($path)) {
  237. return;
  238. }
  239. if (!$this->checkIsDirectory($path)) {
  240. throw new Exception('The given path is not a directory: ' . $path);
  241. }
  242. $content = $this->getList($path);
  243. // The given directory is not empty, but the deletion is not recursive
  244. if (!$isRecursive && !empty($content)) {
  245. throw new Exception('The given directory is not empty: ' . $path);
  246. }
  247. $command = Application::getInstance()->getDiContainer()->getCommandExecutor('rm')
  248. ->addParam('-f');
  249. if ($isRecursive){
  250. $command->addParam('-r');
  251. }
  252. $command->addParam(null, $path);
  253. $this->runCommandAndThrowExceptionIfFailed($command, 'Failed to delete directory: ' . $path);
  254. }
  255. /**
  256. * Moves the given file to the given destination.
  257. *
  258. * @link http://php.net/manual/en/function.rename.php
  259. * @link http://php.net/manual/en/function.move-uploaded-file.php
  260. *
  261. * @param string $sourcePath Path of the file to move.
  262. * @param string $destinationPath Destination of the moved file.
  263. * @param bool $checkIfIsUploaded If TRUE it will move the file only if the file was uploaded through HTTP.
  264. *
  265. * @throws \YapepBase\Exception\File\NotFoundException If the source file is not found.
  266. * @throws \YapepBase\Exception\File\Exception If the source file is not uploaded through HTTP and its checked
  267. * or the move failed.
  268. *
  269. * @return void
  270. */
  271. public function move($sourcePath, $destinationPath, $checkIfIsUploaded = false) {
  272. if (!$this->checkIsPathExists($sourcePath)) {
  273. throw new NotFoundException($sourcePath, 'The source file is not found for a file move: ' . $sourcePath);
  274. }
  275. if ($checkIfIsUploaded && !is_uploaded_file($sourcePath)) {
  276. throw new Exception('The given file is not uploaded through HTTP: ' . $sourcePath);
  277. }
  278. $this->runCommandAndThrowExceptionIfFailed(
  279. Application::getInstance()->getDiContainer()->getCommandExecutor('mv')
  280. ->addParam(null, $sourcePath)
  281. ->addParam(null, $destinationPath)
  282. , 'Failed to move file from ' . $sourcePath . ' to ' . $destinationPath);
  283. }
  284. /**
  285. * Returns the parent directory's path.
  286. *
  287. * @link http://php.net/manual/en/function.dirname.php
  288. *
  289. * @param string $path Path of the file or directory.
  290. *
  291. * @return string
  292. */
  293. public function getParentDirectory($path) {
  294. return dirname($path);
  295. }
  296. /**
  297. * Returns the current working directory.
  298. *
  299. * @return string|bool The full path of the current directory or FALSE on failure.
  300. */
  301. public function getCurrentDirectory() {
  302. $result = Application::getInstance()->getDiContainer()->getCommandExecutor('pwd')->run();
  303. if (!$result->isSuccessful()) {
  304. return false;
  305. }
  306. return trim($result->output);
  307. }
  308. /**
  309. * Reads entire file into a string.
  310. *
  311. * @link http://php.net/manual/en/function.file-get-contents.php
  312. *
  313. * @param string $path Path to the file to open
  314. * @param int $offset Offset where the reading starts on the original stream.
  315. * @param int $maxLength Maximum length of data read.
  316. *
  317. * @throws \YapepBase\Exception\File\NotFoundException If the given path does not exist.
  318. * @throws \YapepBase\Exception\File\Exception If the given path is not a file or the read failed.
  319. * @throws \YapepBase\Exception\ParameterException If the given maxLength is less then 0.
  320. *
  321. * @return string The content of the file, or FALSE on failure.
  322. */
  323. public function getAsString($path, $offset = -1, $maxLength = null) {
  324. if (!is_null($maxLength) && $maxLength < 0) {
  325. throw new ParameterException('The maximum length cannot be less then 0. ' . $maxLength . ' given');
  326. }
  327. if (!$this->checkIsFile($path)) {
  328. throw new Exception('The given path is not a file: ' . $path);
  329. }
  330. $diContainer = Application::getInstance()->getDiContainer();
  331. $command = $diContainer->getCommandExecutor('cat')
  332. ->addParam(null, $path);
  333. if ($maxLength === 0) {
  334. // With the maxlength being 0 we always return an empty string.
  335. return '';
  336. } elseif ($offset > 0) {
  337. $tailCommand = $diContainer->getCommandExecutor('tail')
  338. ->addParam('-c', '+' . ($offset + 1));
  339. $command->setChainedCommand($tailCommand, CommandExecutor::OPERATOR_PIPE);
  340. if ($maxLength > 0) {
  341. $headCommand = $diContainer->getCommandExecutor('head')
  342. ->addParam('-c', (int)$maxLength);
  343. $tailCommand->setChainedCommand($headCommand, CommandExecutor::OPERATOR_PIPE);
  344. }
  345. } elseif ((int)$maxLength > 0) {
  346. // The file_get_contents's maxlen parameter does not have a default value
  347. $headCommand = $diContainer->getCommandExecutor('head')
  348. ->addParam('-c', (int)$maxLength);
  349. $command->setChainedCommand($headCommand, CommandExecutor::OPERATOR_PIPE);
  350. }
  351. return $this->runCommandAndThrowExceptionIfFailed($command, 'Failed to read file: ' . $path)->output;
  352. }
  353. /**
  354. * Returns the list of files and directories at the given path.
  355. *
  356. * @param string $path The directory that will be scanned.
  357. *
  358. * @throws \YapepBase\Exception\File\NotFoundException If the given path does not exist.
  359. * @throws \YapepBase\Exception\File\Exception If the given path is not a directory.
  360. *
  361. * @return array
  362. */
  363. public function getList($path) {
  364. if (!$this->checkIsDirectory($path)) {
  365. throw new Exception('The given path is not a valid directory: ' . $path);
  366. }
  367. // Run ls through env, because on a lot of systems ls is aliased
  368. $result = Application::getInstance()->getDiContainer()->getCommandExecutor('env')
  369. ->addParam(null, 'ls')
  370. ->addParam('-1')
  371. ->addParam('-A')
  372. ->addParam(null, $path)
  373. ->run();
  374. if (!$result->isSuccessful()) {
  375. return array();
  376. }
  377. if ('' == trim($result->output)) {
  378. return array();
  379. }
  380. $content = explode("\n", trim($result->output));
  381. // Sort in PHP as otherwise leading dots may screw up the sorting
  382. sort($content);
  383. return $content;
  384. }
  385. /**
  386. * Lists the content of the given directory by glob.
  387. *
  388. * @link http://php.net/manual/en/function.glob.php
  389. *
  390. * @param string $path The path where the function should list.
  391. * @param string $pattern The pattern to match.
  392. * @param int $flags Flags to modify the behaviour of the search {@uses GLOB_*}.
  393. *
  394. * @throws \YapepBase\Exception\File\NotFoundException If the given path does not exist.
  395. * @throws \YapepBase\Exception\File\Exception If the given path is not a directory.
  396. *
  397. * @return array|bool The found path names, or FALSE on failure.
  398. */
  399. public function getListByGlob($path, $pattern, $flags = null) {
  400. if (!$this->checkIsDirectory($path)) {
  401. throw new Exception('The given path is not a valid directory: ' . $path);
  402. }
  403. $currentDir = $this->getCurrentDirectory();
  404. chdir($path);
  405. $result = glob($pattern, $flags);
  406. chdir($currentDir);
  407. return $result;
  408. }
  409. /**
  410. * Returns the file modification time.
  411. *
  412. * @link http://php.net/manual/en/function.filemtime.php
  413. *
  414. * @param string $path Path to the file or directory.
  415. *
  416. * @throws \YapepBase\Exception\File\NotFoundException If the given path does not exist.
  417. * @throws \YapepBase\Exception\File\Exception If we failed to get the modification time.
  418. *
  419. * @return int A unix timestamp, or FALSE on failure.
  420. */
  421. public function getModificationTime($path) {
  422. if (!$this->checkIsPathExists($path)) {
  423. throw new NotFoundException($path, 'The given path does not exist: ' . $path);
  424. }
  425. $result = filemtime($path);
  426. if (false === $result) {
  427. throw new Exception('Failed to get modification time for file: ' . $path);
  428. }
  429. return $result;
  430. }
  431. /**
  432. * Returns the size of the file.
  433. *
  434. * @link http://php.net/manual/en/function.filesize.php
  435. *
  436. * @param string $path Path to the file.
  437. *
  438. * @throws \YapepBase\Exception\File\NotFoundException If the given path does not exist.
  439. * @throws \YapepBase\Exception\File\Exception If we failed to get the file size.
  440. *
  441. * @return int|bool The size of the file in bytes, or FALSE on failure.
  442. */
  443. public function getSize($path) {
  444. if (!$this->checkIsPathExists($path)) {
  445. throw new NotFoundException($path, 'The given path does not exist: ' . $path);
  446. }
  447. $result = filesize($path);
  448. if (false === $result) {
  449. throw new Exception('Failed to get the size of file: ' . $path);
  450. }
  451. return $result;
  452. }
  453. /**
  454. * Checks if the given directory or file exists.
  455. *
  456. * @link http://php.net/manual/en/function.file-exists.php
  457. *
  458. * @param string $path Path to the file or directory.
  459. *
  460. * @return bool TRUE if it exits, FALSE if not.
  461. */
  462. public function checkIsPathExists($path) {
  463. return $this->runTestCommandOnFile($path, '-a');
  464. }
  465. /**
  466. * Checks if the given path is a directory or not.
  467. *
  468. * @link http://php.net/manual/en/function.is-dir.php
  469. *
  470. * @param string $path The path to check.
  471. *
  472. * @return bool TRUE if it is a directory, FALSE if not.
  473. *
  474. * @throws \YapepBase\Exception\File\NotFoundException If the path does not exist.
  475. */
  476. public function checkIsDirectory($path) {
  477. if (!$this->checkIsPathExists($path)) {
  478. throw new NotFoundException($path, 'The given path does not exist: ' . $path);
  479. }
  480. return $this->runTestCommandOnFile($path, '-d');
  481. }
  482. /**
  483. * Checks if the given path is a file or not.
  484. *
  485. * @link http://php.net/manual/en/function.is-file.php
  486. *
  487. * @param string $path The path to check.
  488. *
  489. * @return bool TRUE if it is a file, FALSE if not.
  490. *
  491. * @throws \YapepBase\Exception\File\NotFoundException If the path does not exits
  492. */
  493. public function checkIsFile($path) {
  494. if (!$this->checkIsPathExists($path)) {
  495. throw new NotFoundException($path, 'The given path does not exist: ' . $path);
  496. }
  497. return $this->runTestCommandOnFile($path, '-f');
  498. }
  499. /**
  500. * Checks if the given path is a symbolic link or not.
  501. *
  502. * @link http://php.net/manual/en/function.is-link.php
  503. *
  504. * @param string $path The path to check.
  505. *
  506. * @return bool TRUE if it is a symlink, FALSE if not.
  507. *
  508. * @throws \YapepBase\Exception\File\NotFoundException If the path does not exits
  509. */
  510. public function checkIsSymlink($path) {
  511. if (!$this->checkIsPathExists($path)) {
  512. throw new NotFoundException($path, 'The given path does not exist: ' . $path);
  513. }
  514. return $this->runTestCommandOnFile($path, '-L');
  515. }
  516. /**
  517. * Checks if the given path is readable.
  518. *
  519. * @link http://php.net/manual/en/function.is-writable.php
  520. *
  521. * @param string $path The path to check.
  522. *
  523. * @return bool TRUE if it is readable, FALSE if not.
  524. *
  525. * @throws \YapepBase\Exception\File\NotFoundException If the path does not exits
  526. */
  527. public function checkIsReadable($path) {
  528. if (!$this->checkIsPathExists($path)) {
  529. throw new NotFoundException($path, 'The given path does not exist: ' . $path);
  530. }
  531. return $this->runTestCommandOnFile($path, '-r');
  532. }
  533. /**
  534. * Checks if the given path is writable.
  535. *
  536. * @link http://php.net/manual/en/function.is-writable.php
  537. *
  538. * @param string $path The path to check.
  539. *
  540. * @return bool TRUE if it is writable, FALSE if not.
  541. *
  542. * @throws \YapepBase\Exception\File\NotFoundException If the path does not exits
  543. */
  544. public function checkIsWritable($path) {
  545. if (!$this->checkIsPathExists($path)) {
  546. throw new NotFoundException($path, 'The given path does not exist: ' . $path);
  547. }
  548. return $this->runTestCommandOnFile($path, '-w');
  549. }
  550. /**
  551. * Returns trailing name component of path
  552. *
  553. * @link http://php.net/manual/en/function.basename.php
  554. *
  555. * @param string $path The path.
  556. * @param string $suffix If the name component ends in suffix this will also be cut off.
  557. *
  558. * @return string
  559. */
  560. public function getBaseName($path, $suffix = null) {
  561. return basename($path, $suffix);
  562. }
  563. /**
  564. * Runs a test command with the specified test type switch and returns whether it was successful.
  565. *
  566. * @param string $path The path to test.
  567. * @param string $testTypeSwitch The switch that specifies the test type.
  568. *
  569. * @return bool
  570. */
  571. protected function runTestCommandOnFile($path, $testTypeSwitch) {
  572. return Application::getInstance()->getDiContainer()->getCommandExecutor('env')
  573. ->addParam(null, 'test')
  574. ->addParam($testTypeSwitch, $path)
  575. ->run()
  576. ->isSuccessful();
  577. }
  578. /**
  579. * Runs a command and throws an exception if the run fails.
  580. *
  581. * @param ICommandExecutor $command The command to run.
  582. * @param string $exceptionMessage The message of the exception.
  583. *
  584. * @return \YapepBase\Shell\CommandOutput The output of the command.
  585. *
  586. * @throws \YapepBase\Exception\File\Exception If the command failed to run.
  587. */
  588. protected function runCommandAndThrowExceptionIfFailed(ICommandExecutor $command, $exceptionMessage) {
  589. $result = $command->run();
  590. if (!$result->isSuccessful()) {
  591. throw new Exception($exceptionMessage, 0, null, $result);
  592. }
  593. return $result;
  594. }
  595. }