Response.php 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967
  1. <?php
  2. /**
  3. * Hoa
  4. *
  5. *
  6. * @license
  7. *
  8. * New BSD License
  9. *
  10. * Copyright © 2007-2017, Hoa community. All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or without
  13. * modification, are permitted provided that the following conditions are met:
  14. * * Redistributions of source code must retain the above copyright
  15. * notice, this list of conditions and the following disclaimer.
  16. * * Redistributions in binary form must reproduce the above copyright
  17. * notice, this list of conditions and the following disclaimer in the
  18. * documentation and/or other materials provided with the distribution.
  19. * * Neither the name of the Hoa nor the names of its contributors may be
  20. * used to endorse or promote products derived from this software without
  21. * specific prior written permission.
  22. *
  23. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  24. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  25. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  26. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
  27. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  28. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  29. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  30. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  31. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  32. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  33. * POSSIBILITY OF SUCH DAMAGE.
  34. */
  35. namespace Hoa\Http\Response;
  36. use Hoa\Consistency;
  37. use Hoa\Http;
  38. use Hoa\Stream;
  39. /**
  40. * Class \Hoa\Http\Response.
  41. *
  42. * HTTP response support.
  43. *
  44. * @TODO Follow http://tools.ietf.org/html/draft-nottingham-http-new-status-03.
  45. *
  46. * @copyright Copyright © 2007-2017 Hoa community
  47. * @license New BSD License
  48. */
  49. class Response
  50. extends Http
  51. implements Stream\IStream\Out,
  52. Stream\IStream\Bufferable
  53. {
  54. /**
  55. * Continue (please, see RFC7231).
  56. *
  57. * @const string
  58. */
  59. const STATUS_CONTINUE = '100 Continue';
  60. /**
  61. * Switching protocols (please, see RFC7231).
  62. *
  63. * @const string
  64. */
  65. const STATUS_SWITCHING_PROTOCOLS = '101 Switching Protocols';
  66. /**
  67. * Processing (please, see RFC2518).
  68. *
  69. * @const string
  70. */
  71. const STATUS_PROCESSING = '102 Processing';
  72. /**
  73. * OK (please, see RFC7231).
  74. *
  75. * @const string
  76. */
  77. const STATUS_OK = '200 Ok';
  78. /**
  79. * Created (please, see RFC7231).
  80. *
  81. * @const string
  82. */
  83. const STATUS_CREATED = '201 Created';
  84. /**
  85. * Accepted (please, see RFC7231).
  86. *
  87. * @const string
  88. */
  89. const STATUS_ACCEPTED = '202 Accepted';
  90. /**
  91. * Non-authoritative information (please, see RFC7231).
  92. *
  93. * @const string
  94. */
  95. const STATUS_NON_AUTHORITATIVE_INFORMATION = '203 Non Authoritative Information';
  96. /**
  97. * No content (please, see RFC7231).
  98. *
  99. * @const string
  100. */
  101. const STATUS_NO_CONTENT = '204 No Content';
  102. /**
  103. * Reset content (please, see RFC7231).
  104. *
  105. * @const string
  106. */
  107. const STATUS_RESET_CONTENT = '205 Reset Content';
  108. /**
  109. * Partial content (please, see RFC7233).
  110. *
  111. * @const string
  112. */
  113. const STATUS_PARTIAL_CONTENT = '206 Partial Content';
  114. /**
  115. * Multi-status (please, see RFC4918).
  116. *
  117. * @const string
  118. */
  119. const STATUS_MULTI_STATUS = '207 Multi-Status';
  120. /**
  121. * Already Reported (please, see RFC5842).
  122. *
  123. * @const string
  124. */
  125. const STATUS_ALREADY_REPORTED = '208 Already Reported';
  126. /**
  127. * IM used (please, see RFC3229).
  128. *
  129. * @const string
  130. */
  131. const STATUS_IM_USED = '226 IM Used';
  132. /**
  133. * Multiple choices (please, see RFC7231).
  134. *
  135. * @const string
  136. */
  137. const STATUS_MULTIPLE_CHOICES = '300 Multiple Choices';
  138. /**
  139. * Moved permanently (please, see RFC7231).
  140. *
  141. * @const string
  142. */
  143. const STATUS_MOVED_PERMANENTLY = '301 Moved Permanently';
  144. /**
  145. * Found (please, see RFC7231).
  146. *
  147. * @const string
  148. */
  149. const STATUS_FOUND = '302 Found';
  150. /**
  151. * See other (please, see RFC7231).
  152. *
  153. * @const string
  154. */
  155. const STATUS_SEE_OTHER = '303 See Other';
  156. /**
  157. * Not modified (please, see RFC7232).
  158. *
  159. * @const string
  160. */
  161. const STATUS_NOT_MODIFIED = '304 Not Modified';
  162. /**
  163. * Use proxy (please, see RFC7231).
  164. *
  165. * @const string
  166. */
  167. const STATUS_USE_PROXY = '305 Use Proxy';
  168. /**
  169. * Temporary redirect (please, see RFC7231).
  170. *
  171. * @const string
  172. */
  173. const STATUS_TEMPORARY_REDIRECT = '307 Temporary Redirect';
  174. /**
  175. * Permanent redirect (please, see RFC7238).
  176. *
  177. * @const string
  178. */
  179. const STATUS_PERMANENT_REDIRECT = '308 Permanent Redirect';
  180. /**
  181. * Bad request (please, see RFC7231).
  182. *
  183. * @const string
  184. */
  185. const STATUS_BAD_REQUEST = '400 Bad Request';
  186. /**
  187. * Unauthorized (please, see RFC7235).
  188. *
  189. * @const string
  190. */
  191. const STATUS_UNAUTHORIZED = '401 Unauthorized';
  192. /**
  193. * Payment required (please, see RFC7231).
  194. *
  195. * @const string
  196. */
  197. const STATUS_PAYMENT_REQUIRED = '402 Payment Required';
  198. /**
  199. * Forbidden (please, see RFC7231).
  200. *
  201. * @const string
  202. */
  203. const STATUS_FORBIDDEN = '403 Forbidden';
  204. /**
  205. * Not found (please, see RFC7231).
  206. *
  207. * @const string
  208. */
  209. const STATUS_NOT_FOUND = '404 Not Found';
  210. /**
  211. * Method not allowed (please, see RFC7231).
  212. *
  213. * @const string
  214. */
  215. const STATUS_METHOD_NOT_ALLOWED = '405 Method Not Allowed';
  216. /**
  217. * Not acceptable (please, see RFC7231).
  218. *
  219. * @const string
  220. */
  221. const STATUS_NOT_ACCEPTABLE = '406 Not Acceptable';
  222. /**
  223. * Proxy authentication required (please, see RFC7235).
  224. *
  225. * @const string
  226. */
  227. const STATUS_PROXY_AUTHENTICATION_REQUIRED = '407 Proxy Authentication Required';
  228. /**
  229. * Request time-out (please, see RFC7231).
  230. *
  231. * @const string
  232. */
  233. const STATUS_REQUEST_TIME_OUT = '408 Request Timeout';
  234. /**
  235. * Conflict (please, see RFC7231).
  236. *
  237. * @const string
  238. */
  239. const STATUS_CONFLICT = '409 Conflict';
  240. /**
  241. * Gone (please, see RFC7231).
  242. *
  243. * @const string
  244. */
  245. const STATUS_GONE = '410 Gone';
  246. /**
  247. * Length required (please, see RFC7231).
  248. *
  249. * @const string
  250. */
  251. const STATUS_LENGTH_REQUIRED = '411 Length Required';
  252. /**
  253. * Precondition failed (please, see RFC7232).
  254. *
  255. * @const string
  256. */
  257. const STATUS_PRECONDITION_FAILED = '412 Precondition Failed';
  258. /**
  259. * Request entity too large (please, see RFC7231).
  260. *
  261. * @const string
  262. */
  263. const STATUS_REQUEST_ENTITY_TOO_LARGE = '413 Request Entity Too Large';
  264. /**
  265. * Request URI too large (please, see RFC7231).
  266. *
  267. * @const string
  268. */
  269. const STATUS_REQUEST_URI_TOO_LARGE = '414 Request URI Too Large';
  270. /**
  271. * Unsupported media type (please, see RFC7231).
  272. *
  273. * @const string
  274. */
  275. const STATUS_UNSUPPORTED_MEDIA_TYPE = '415 Unsupported Media Type';
  276. /**
  277. * Requested range not satisfiable (please, see RFC7233).
  278. *
  279. * @const string
  280. */
  281. const STATUS_REQUESTED_RANGE_NOT_SATISFIABLE = '416 Requested Range Not Satisfiable';
  282. /**
  283. * Expectation failed (please, see RFC7231).
  284. *
  285. * @const string
  286. */
  287. const STATUS_EXPECTATION_FAILED = '417 Expectation Failed';
  288. /**
  289. * I'm a teapot (see RFC2324, April Fool's joke).
  290. *
  291. * @const string
  292. */
  293. const STATUS_IM_A_TEAPOT = '418 I\'m a teapot';
  294. /**
  295. * Unprocessable Entity (please, see RFC4918).
  296. *
  297. * @const string
  298. */
  299. const STATUS_UNPROCESSABLE_ENTITY = '422 Unprocessable Entity';
  300. /**
  301. * Locked (please, see RFC4918).
  302. *
  303. * @const string
  304. */
  305. const STATUS_LOCKED = '423 Locked';
  306. /**
  307. * Failed Dependency (please, see RFC4918).
  308. *
  309. * @const string
  310. */
  311. const STATUS_FAILED_DEPENDENCY = '424 Failed Dependency';
  312. /**
  313. * Upgrade required (please, see RFC7231).
  314. *
  315. * @const string
  316. */
  317. const STATUS_UPGRADE_REQUIRED = '426 Upgrade Required';
  318. /**
  319. * Precondition Required (please, see RFC6585).
  320. *
  321. * @const string
  322. */
  323. const STATUS_PRECONDITION_REQUIRED = '428 Precondition Required';
  324. /**
  325. * Too Many Requests (please, see RFC6585).
  326. *
  327. * @const string
  328. */
  329. const STATUS_TOO_MANY_REQUESTS = '429 Too Many Requests';
  330. /**
  331. * Request Header Fields Too Large (please, see RFC6585).
  332. *
  333. * @const string
  334. */
  335. const STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = '431 Request Header Fields Too Large';
  336. /**
  337. * Internal server error (please, see RFC7231).
  338. *
  339. * @const string
  340. */
  341. const STATUS_INTERNAL_SERVER_ERROR = '500 Internal Server Error';
  342. /**
  343. * Not implemented (please, see RFC7231).
  344. *
  345. * @const string
  346. */
  347. const STATUS_NOT_IMPLEMENTED = '501 Not Implemented';
  348. /**
  349. * Bad gateway (please, see RFC7231).
  350. *
  351. * @const string
  352. */
  353. const STATUS_BAD_GATEWAY = '502 Bad Gateway';
  354. /**
  355. * Service unavailable (please, see RFC7231).
  356. *
  357. * @const string
  358. */
  359. const STATUS_SERVICE_UNAVAILABLE = '503 Service Unavailable';
  360. /**
  361. * Gateway time-out (please, see RFC7231).
  362. *
  363. * @const string
  364. */
  365. const STATUS_GATEWAY_TIME_OUT = '504 Gateway Time Out';
  366. /**
  367. * HTTP version not supported (please, see RFC7231).
  368. *
  369. * @const string
  370. */
  371. const STATUS_HTTP_VERSION_NOT_SUPPORTED = '505 HTTP Version Not Supported';
  372. /**
  373. * Variant Also Negotiates (please, see RFC2295).
  374. *
  375. * @const string
  376. */
  377. const STATUS_VARIANT_ALSO_NEGOTIATES = '506 Variant Also Negotiates';
  378. /**
  379. * Insufficient Storage (please, see RFC4918).
  380. *
  381. * @const string
  382. */
  383. const STATUS_INSUFFICIENT_STORAGE = '507 Insufficient Storage';
  384. /**
  385. * Loop Detected (please, see RFC5842).
  386. *
  387. * @const string
  388. */
  389. const STATUS_LOOP_DETECTED = '508 Loop Detected';
  390. /**
  391. * Not Extended (please, see RFC2774).
  392. *
  393. * @const string
  394. */
  395. const STATUS_NOT_EXTENDED = '510 Not Extended';
  396. /**
  397. * Network Authentication Required (please, see RFC6585).
  398. *
  399. * @const string
  400. */
  401. const STATUS_NETWORK_AUTHENTICATION_REQUIRED = '511 Network Authentication Required';
  402. /**
  403. * Status (different ordering).
  404. *
  405. * @var array
  406. */
  407. private $_status = [];
  408. /**
  409. * This object hash.
  410. *
  411. * @var string
  412. */
  413. private $_hash = null;
  414. /**
  415. * ob_*() is stateless, so we manage a stack to avoid cross-buffers
  416. * manipulations.
  417. *
  418. * @var array
  419. */
  420. private static $_stack = [];
  421. /**
  422. * Constructor.
  423. *
  424. * @param bool $newBuffer Whether we run $this->newBuffer().
  425. * Following arguments are for this
  426. * method.
  427. * @param mixed $callable Callable.
  428. * @param int $size Size.
  429. */
  430. public function __construct($newBuffer = true, $callable = null, $size = null)
  431. {
  432. parent::__construct();
  433. $this->_hash = spl_object_hash($this);
  434. if (true === $newBuffer) {
  435. $this->newBuffer($callable, $size);
  436. }
  437. if (empty($this->_status)) {
  438. $reflection = new \ReflectionClass($this);
  439. foreach ($reflection->getConstants() as $value) {
  440. $this->_status[$this->getStatus($value)] = $value;
  441. }
  442. }
  443. return;
  444. }
  445. /**
  446. * Parse a HTTP packet.
  447. *
  448. * @param string $packet HTTP packet.
  449. * @return void
  450. * @throws \Hoa\Http\Exception
  451. */
  452. public function parse($packet)
  453. {
  454. $headers = explode("\r\n", $packet);
  455. $status = array_shift($headers);
  456. $this->setBody(null);
  457. foreach ($headers as $i => $header) {
  458. if ('' == trim($header)) {
  459. unset($headers[$i]);
  460. $this->setBody(
  461. trim(
  462. implode("\r\n", array_splice($headers, $i))
  463. )
  464. );
  465. break;
  466. }
  467. }
  468. if (0 === preg_match('#^HTTP/(1\.(?:0|1))\s+(\d{3})#i', $status, $matches)) {
  469. throw new Http\Exception(
  470. 'HTTP status is not well-formed: %s.',
  471. 0,
  472. $status
  473. );
  474. }
  475. if (!isset($this->_status[$matches[2]])) {
  476. throw new Http\Exception(
  477. 'Unknown HTTP status %d in %s.',
  478. 1,
  479. [$matches[2], $status]
  480. );
  481. }
  482. $this->setHttpVersion((float) $matches[1]);
  483. $this->_parse($headers);
  484. $this['status'] = $this->_status[$matches[2]];
  485. return;
  486. }
  487. /**
  488. * Get real status from static::STATUS_* constants.
  489. *
  490. * @param string $status Status.
  491. * @return int
  492. */
  493. public static function getStatus($status)
  494. {
  495. return (int) substr($status, 0, 3);
  496. }
  497. /**
  498. * Send a new status.
  499. *
  500. * @param string $status Status. Please, see static::STATUS_*
  501. * constants.
  502. * @param bool $replace Whether replace an existing sent header.
  503. * @return void
  504. */
  505. public function sendStatus($status, $replace = true)
  506. {
  507. return $this->sendHeader('status', $status, $replace, $status);
  508. }
  509. /**
  510. * Send a new header.
  511. *
  512. * @param string $header Header.
  513. * @param string $value Value.
  514. * @param bool $replace Whether replace an existing sent header.
  515. * @param string $status Force a specific status. Please, see
  516. * static::STATUS_* constants.
  517. * @return void
  518. */
  519. public function sendHeader(
  520. $header,
  521. $value,
  522. $replace = true,
  523. $status = null
  524. ) {
  525. if (0 === strcasecmp('status', $header) &&
  526. false === self::$_fcgi) {
  527. header(
  528. 'HTTP/1.1 ' . $value,
  529. $replace,
  530. static::getStatus($value)
  531. );
  532. return;
  533. }
  534. header(
  535. $header . ': ' . $value,
  536. $replace,
  537. null !== $status ? static::getStatus($status) : null
  538. );
  539. return;
  540. }
  541. /**
  542. * Send all headers.
  543. *
  544. * @return void
  545. */
  546. public function sendHeaders()
  547. {
  548. foreach ($this->_headers as $header => $value) {
  549. $this->sendHeader($header, $value);
  550. }
  551. return;
  552. }
  553. /**
  554. * Get send headers.
  555. *
  556. * @return string
  557. */
  558. public function getSentHeaders()
  559. {
  560. return implode("\r\n", headers_list());
  561. }
  562. /**
  563. * Start a new buffer.
  564. * The callable acts like a filter.
  565. *
  566. * @param mixed $callable Callable.
  567. * @param int $size Size.
  568. * @return int
  569. */
  570. public function newBuffer($callable = null, $size = null)
  571. {
  572. $last = current(self::$_stack);
  573. $hash = $this->getHash();
  574. if (false === $last || $hash != $last[0]) {
  575. self::$_stack[] = [
  576. 0 => $hash,
  577. 1 => 1
  578. ];
  579. } else {
  580. ++self::$_stack[key(self::$_stack)][1];
  581. }
  582. end(self::$_stack);
  583. if (null === $callable) {
  584. ob_start();
  585. } else {
  586. ob_start(xcallable($callable), null === $size ? 0 : $size);
  587. }
  588. return $this->getBufferLevel();
  589. }
  590. /**
  591. * Flush the buffer.
  592. *
  593. * @param bool $force Force to flush the output buffer.
  594. * @return void
  595. */
  596. public function flush($force = false)
  597. {
  598. if (0 >= $this->getBufferSize()) {
  599. return;
  600. }
  601. ob_flush();
  602. if (true === $force) {
  603. flush();
  604. }
  605. return;
  606. }
  607. /**
  608. * Delete buffer.
  609. *
  610. * @return bool
  611. * @throws \Hoa\Http\Exception\CrossBufferization
  612. */
  613. public function deleteBuffer()
  614. {
  615. $key = key(self::$_stack);
  616. if ($this->getHash() != self::$_stack[$key][0]) {
  617. throw new Http\Exception\CrossBufferization(
  618. 'Cannot delete this buffer because it was not opened by this ' .
  619. 'class (%s, %s).',
  620. 2,
  621. [get_class($this), $this->getHash()]
  622. );
  623. }
  624. $out = ob_end_clean();
  625. if (false === $out) {
  626. return false;
  627. }
  628. --self::$_stack[$key][1];
  629. if (0 >= self::$_stack[$key][1]) {
  630. unset(self::$_stack[$key]);
  631. }
  632. return true;
  633. }
  634. /**
  635. * Get buffer level.
  636. *
  637. * @return int
  638. */
  639. public function getBufferLevel()
  640. {
  641. return ob_get_level();
  642. }
  643. /**
  644. * Get buffer size.
  645. *
  646. * @return int
  647. */
  648. public function getBufferSize()
  649. {
  650. return ob_get_length();
  651. }
  652. /**
  653. * Write n characters.
  654. *
  655. * @param string $string String.
  656. * @param int $length Length.
  657. * @return mixed
  658. * @throws \Hoa\Http\Exception
  659. */
  660. public function write($string, $length)
  661. {
  662. if (0 > $length) {
  663. throw new Http\Exception(
  664. 'Length must be greater than 0, given %d.',
  665. 3,
  666. $length
  667. );
  668. }
  669. if (strlen($string) > $length) {
  670. $string = substr($string, 0, $length);
  671. }
  672. echo $string;
  673. return;
  674. }
  675. /**
  676. * Write a string.
  677. *
  678. * @param string $string String.
  679. * @return mixed
  680. */
  681. public function writeString($string)
  682. {
  683. echo (string) $string;
  684. return;
  685. }
  686. /**
  687. * Write a character.
  688. *
  689. * @param string $character Character.
  690. * @return mixed
  691. */
  692. public function writeCharacter($character)
  693. {
  694. echo $character[0];
  695. return;
  696. }
  697. /**
  698. * Write a boolean.
  699. *
  700. * @param bool $boolean Boolean.
  701. * @return mixed
  702. */
  703. public function writeBoolean($boolean)
  704. {
  705. echo (string) (bool) $boolean;
  706. return;
  707. }
  708. /**
  709. * Write an integer.
  710. *
  711. * @param int $integer Integer.
  712. * @return mixed
  713. */
  714. public function writeInteger($integer)
  715. {
  716. echo (string) (int) $integer;
  717. return;
  718. }
  719. /**
  720. * Write a float.
  721. *
  722. * @param float $float Float.
  723. * @return mixed
  724. */
  725. public function writeFloat($float)
  726. {
  727. echo (string) (float) $float;
  728. return;
  729. }
  730. /**
  731. * Write an array.
  732. *
  733. * @param array $array Array.
  734. * @return mixed
  735. */
  736. public function writeArray(array $array)
  737. {
  738. echo var_export($array, true);
  739. return;
  740. }
  741. /**
  742. * Write a line.
  743. *
  744. * @param string $line Line.
  745. * @return mixed
  746. */
  747. public function writeLine($line)
  748. {
  749. if (false !== $n = strpos($line, "\n")) {
  750. $line = substr($line, 0, $n + 1);
  751. }
  752. echo $line;
  753. return;
  754. }
  755. /**
  756. * Write all, i.e. as much as possible.
  757. *
  758. * @param string $string String.
  759. * @return mixed
  760. */
  761. public function writeAll($string)
  762. {
  763. echo $string;
  764. return;
  765. }
  766. /**
  767. * Truncate a file to a given length.
  768. *
  769. * @param int $size Size.
  770. * @return bool
  771. */
  772. public function truncate($size)
  773. {
  774. if (0 === $size) {
  775. ob_clean();
  776. return true;
  777. }
  778. $bSize = $this->getBufferSize();
  779. if ($size >= $bSize) {
  780. return true;
  781. }
  782. echo substr(ob_get_clean(), 0, $size);
  783. return true;
  784. }
  785. /**
  786. * Get the current stream.
  787. *
  788. * @return resource
  789. */
  790. public function getStream()
  791. {
  792. return fopen('php://stdout', 'w');
  793. }
  794. /**
  795. * Get this object hash.
  796. *
  797. * @return string
  798. */
  799. public function getHash()
  800. {
  801. return $this->_hash;
  802. }
  803. /**
  804. * Delete head buffer.
  805. *
  806. * @return void
  807. */
  808. public function __destruct()
  809. {
  810. $last = current(self::$_stack);
  811. if ($this->getHash() != $last[0]) {
  812. return;
  813. }
  814. for ($i = 0, $max = $last[1]; $i < $max; ++$i) {
  815. $this->flush();
  816. if (0 < $this->getBufferLevel()) {
  817. $this->deleteBuffer();
  818. }
  819. }
  820. return;
  821. }
  822. }
  823. /**
  824. * Flex entity.
  825. */
  826. Consistency::flexEntity('Hoa\Http\Response\Response');