AbstractStream.php 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Mailer\Transport\Smtp\Stream;
  11. use Symfony\Component\Mailer\Exception\TransportException;
  12. /**
  13. * A stream supporting remote sockets and local processes.
  14. *
  15. * @author Fabien Potencier <fabien@symfony.com>
  16. * @author Nicolas Grekas <p@tchwork.com>
  17. * @author Chris Corbyn
  18. *
  19. * @internal
  20. */
  21. abstract class AbstractStream
  22. {
  23. protected $stream;
  24. protected $in;
  25. protected $out;
  26. private $debug = '';
  27. public function write(string $bytes, bool $debug = true): void
  28. {
  29. if ($debug) {
  30. foreach (explode("\n", trim($bytes)) as $line) {
  31. $this->debug .= sprintf("> %s\n", $line);
  32. }
  33. }
  34. $bytesToWrite = \strlen($bytes);
  35. $totalBytesWritten = 0;
  36. while ($totalBytesWritten < $bytesToWrite) {
  37. $bytesWritten = @fwrite($this->in, substr($bytes, $totalBytesWritten));
  38. if (false === $bytesWritten || 0 === $bytesWritten) {
  39. throw new TransportException('Unable to write bytes on the wire.');
  40. }
  41. $totalBytesWritten += $bytesWritten;
  42. }
  43. }
  44. /**
  45. * Flushes the contents of the stream (empty it) and set the internal pointer to the beginning.
  46. */
  47. public function flush(): void
  48. {
  49. fflush($this->in);
  50. }
  51. /**
  52. * Performs any initialization needed.
  53. */
  54. abstract public function initialize(): void;
  55. public function terminate(): void
  56. {
  57. $this->stream = $this->out = $this->in = null;
  58. }
  59. public function readLine(): string
  60. {
  61. if (feof($this->out)) {
  62. return '';
  63. }
  64. $line = fgets($this->out);
  65. if (0 === \strlen($line)) {
  66. $metas = stream_get_meta_data($this->out);
  67. if ($metas['timed_out']) {
  68. throw new TransportException(sprintf('Connection to "%s" timed out.', $this->getReadConnectionDescription()));
  69. }
  70. if ($metas['eof']) {
  71. throw new TransportException(sprintf('Connection to "%s" has been closed unexpectedly.', $this->getReadConnectionDescription()));
  72. }
  73. }
  74. $this->debug .= sprintf('< %s', $line);
  75. return $line;
  76. }
  77. public function getDebug(): string
  78. {
  79. $debug = $this->debug;
  80. $this->debug = '';
  81. return $debug;
  82. }
  83. public static function replace(string $from, string $to, iterable $chunks): \Generator
  84. {
  85. if ('' === $from) {
  86. yield from $chunks;
  87. return;
  88. }
  89. $carry = '';
  90. $fromLen = \strlen($from);
  91. foreach ($chunks as $chunk) {
  92. if ('' === $chunk = $carry.$chunk) {
  93. continue;
  94. }
  95. if (false !== strpos($chunk, $from)) {
  96. $chunk = explode($from, $chunk);
  97. $carry = array_pop($chunk);
  98. yield implode($to, $chunk).$to;
  99. } else {
  100. $carry = $chunk;
  101. }
  102. if (\strlen($carry) > $fromLen) {
  103. yield substr($carry, 0, -$fromLen);
  104. $carry = substr($carry, -$fromLen);
  105. }
  106. }
  107. if ('' !== $carry) {
  108. yield $carry;
  109. }
  110. }
  111. abstract protected function getReadConnectionDescription(): string;
  112. }