From d6764c059aa75dc5f296ea83bdb8d0b03871eabc Mon Sep 17 00:00:00 2001 From: Zaahid Bateson Date: Tue, 30 Jan 2024 20:48:19 -0800 Subject: [PATCH] New AbstractHeader::from static method Returns an IHeader type based on passed header name or based on static type if using a sub-class, i.e. AddressHeader::from returns an AddressHeader regardless of name. --- src/Header/AbstractHeader.php | 55 ++++++++++++ src/Header/HeaderFactory.php | 2 +- .../Header/HeaderFactoryTest.php | 41 +++++++++ .../Message/PartHeaderContainerTest.php | 90 +++++++++++-------- 4 files changed, 152 insertions(+), 36 deletions(-) diff --git a/src/Header/AbstractHeader.php b/src/Header/AbstractHeader.php index 27310ca2..94fd990a 100644 --- a/src/Header/AbstractHeader.php +++ b/src/Header/AbstractHeader.php @@ -7,6 +7,7 @@ namespace ZBateson\MailMimeParser\Header; +use ZBateson\MailMimeParser\MailMimeParser; use ZBateson\MailMimeParser\ErrorBag; use ZBateson\MailMimeParser\Header\Consumer\IConsumerService; use ZBateson\MailMimeParser\Header\Part\CommentPart; @@ -168,4 +169,58 @@ protected function validate() : void $this->addError('Header doesn\'t have a value', LogLevel::NOTICE); } } + + /** + * Checks if the passed $value parameter is null, and if so tries to parse + * a header line from $nameOrLine splitting on first occurrence of a ':' + * character. + * + * The returned array always contains two elements. The first being the + * name (or blank if a ':' char wasn't found and $value is null), and the + * second being the value. + * + * @return string[] + */ + protected static function getHeaderPartsFrom(string $nameOrLine, ?string $value = null) : array + { + $namePart = $nameOrLine; + $valuePart = $value; + if ($value === null) { + // full header line + $parts = explode(':', $nameOrLine, 2); + $namePart = (count($parts) > 1) ? $parts[0] : ''; + $valuePart = trim((count($parts) > 1) ? $parts[1] : $parts[0]); + } + return [ $namePart, $valuePart ]; + } + + /** + * Parses the passed parameters into an IHeader object. + * + * The type of returned IHeader is determined by the name of the header. + * See {@see HeaderFactory::newInstance} for more details. + * + * The required $nameOrLine parameter may contain either the name of a + * header to parse, or a full header line, e.g. From: email@example.com. If + * passing a full header line, the $value parameter must be set to null (the + * default). + * + * Note that more specific types can be called on directly. For instance an + * AddressHeader may be created by calling AddressHeader::from() which will + * ignore the name of the header, and always return an AddressHeader. + * + * @param string $nameOrLine The header's name or full header line. + * @param string|null $value The header's value, or null if passing a full + * header line to parse. + */ + public static function from(string $nameOrLine, ?string $value = null) : IHeader + { + $parts = static::getHeaderPartsFrom($nameOrLine, $value); + $container = MailMimeParser::getGlobalContainer(); + $hf = $container->get(HeaderFactory::class); + if (self::class !== static::class) { + return $hf->newInstanceOf($parts[0], $parts[1], static::class); + } + return $hf->newInstance($parts[0], $parts[1]); + } } diff --git a/src/Header/HeaderFactory.php b/src/Header/HeaderFactory.php index 8ee4da94..9f1ab74b 100644 --- a/src/Header/HeaderFactory.php +++ b/src/Header/HeaderFactory.php @@ -171,7 +171,7 @@ private function getClassFor(string $name) : string * @param string $value The header value. * @return IHeader The created header object. */ - public function newInstance(string $name, string $value) + public function newInstance(string $name, string $value) : IHeader { $class = $this->getClassFor($name); $this->logger->debug( diff --git a/tests/MailMimeParser/Header/HeaderFactoryTest.php b/tests/MailMimeParser/Header/HeaderFactoryTest.php index a4cffbc6..da2e47fe 100644 --- a/tests/MailMimeParser/Header/HeaderFactoryTest.php +++ b/tests/MailMimeParser/Header/HeaderFactoryTest.php @@ -17,6 +17,7 @@ * @group Headers * @group HeaderFactory * @covers ZBateson\MailMimeParser\Header\HeaderFactory + * @covers ZBateson\MailMimeParser\Header\AbstractHeader * @author Zaahid Bateson */ class HeaderFactoryTest extends TestCase @@ -191,4 +192,44 @@ public function testNewInstanceOf() : void $this->assertInstanceOf(\ZBateson\MailMimeParser\Header\ReceivedHeader::class, $header); } } + + public function testStaticFromNameValue() : void + { + $header = AbstractHeader::from('Subject', 'Test'); + $this->assertInstanceOf(SubjectHeader::class, $header); + $this->assertEquals('Subject', $header->getName()); + $this->assertEquals('Test', $header->getValue()); + } + + public function testStaticFromHeaderLine() : void + { + $header = AbstractHeader::from('Subject: Test'); + $this->assertInstanceOf(SubjectHeader::class, $header); + $this->assertEquals('Subject', $header->getName()); + $this->assertEquals('Test', $header->getValue()); + } + + public function testStaticFromHeaderLineNoName() : void + { + $header = AbstractHeader::from('Test'); + $this->assertInstanceOf(GenericHeader::class, $header); + $this->assertEquals('', $header->getName()); + $this->assertEquals('Test', $header->getValue()); + } + + public function testStaticFromHeaderLineMultipleColon() : void + { + $header = AbstractHeader::from('Subject: Test:Blah'); + $this->assertInstanceOf(SubjectHeader::class, $header); + $this->assertEquals('Subject', $header->getName()); + $this->assertEquals('Test:Blah', $header->getValue()); + } + + public function testStaticFromSpecializedHeader() : void + { + $header = SubjectHeader::from('From: Test:Blah'); + $this->assertInstanceOf(SubjectHeader::class, $header); + $this->assertEquals('From', $header->getName()); + $this->assertEquals('Test:Blah', $header->getValue()); + } } diff --git a/tests/MailMimeParser/Message/PartHeaderContainerTest.php b/tests/MailMimeParser/Message/PartHeaderContainerTest.php index e374b307..9e4a0a7c 100644 --- a/tests/MailMimeParser/Message/PartHeaderContainerTest.php +++ b/tests/MailMimeParser/Message/PartHeaderContainerTest.php @@ -3,6 +3,7 @@ namespace ZBateson\MailMimeParser\Message; use PHPUnit\Framework\TestCase; +use ZBateson\MailMimeParser\Header\IHeader; /** * Description of HeaderContainerTest @@ -36,6 +37,9 @@ public function testAddExistsGet() : void $this->assertFalse($ob->exists('third')); $this->assertFalse($ob->exists('first', 1)); + $mockFirstHeader = $this->getMockBuilder(IHeader::class)->getMock(); + $mockSecondHeader = $this->getMockBuilder(IHeader::class)->getMock(); + $this->mockHeaderFactory ->expects($this->exactly(2)) ->method('newInstance') @@ -43,14 +47,14 @@ public function testAddExistsGet() : void ['first', 'value'], ['second', 'value'] ) - ->willReturnOnConsecutiveCalls('first-value', 'second-value'); + ->willReturnOnConsecutiveCalls($mockFirstHeader, $mockSecondHeader); - $this->assertEquals('first-value', $ob->get('first')); - $this->assertEquals('second-value', $ob->get('second')); - $this->assertEquals('first-value', $ob->get('first', 0)); - $this->assertEquals('first-value', $ob->get('first')); - $this->assertEquals('second-value', $ob->get('second', 0)); - $this->assertEquals('second-value', $ob->get('second')); + $this->assertSame($mockFirstHeader, $ob->get('first')); + $this->assertSame($mockSecondHeader, $ob->get('second')); + $this->assertSame($mockFirstHeader, $ob->get('first', 0)); + $this->assertSame($mockFirstHeader, $ob->get('first')); + $this->assertSame($mockSecondHeader, $ob->get('second', 0)); + $this->assertSame($mockSecondHeader, $ob->get('second')); $this->assertNull($ob->get('other')); $this->assertNull($ob->get('second', 1)); @@ -73,6 +77,10 @@ public function testAddExistsGetSameName() : void $this->assertFalse($ob->exists('repeated', 3)); $this->assertFalse($ob->exists('something-else')); + $mockFirstHeader = $this->getMockBuilder(IHeader::class)->getMock(); + $mockSecondHeader = $this->getMockBuilder(IHeader::class)->getMock(); + $mockThirdHeader = $this->getMockBuilder(IHeader::class)->getMock(); + $this->mockHeaderFactory ->expects($this->exactly(3)) ->method('newInstance') @@ -81,15 +89,15 @@ public function testAddExistsGetSameName() : void ['repeated', 'second'], ['repeated', 'third'] ) - ->willReturnOnConsecutiveCalls('repeated-first', 'repeated-second', 'repeated-third'); + ->willReturnOnConsecutiveCalls($mockFirstHeader, $mockSecondHeader, $mockThirdHeader); - $this->assertEquals('repeated-first', $ob->get('repeated')); - $this->assertEquals('repeated-first', $ob->get('repeated', 0)); - $this->assertEquals('repeated-second', $ob->get('repeated', 1)); - $this->assertEquals('repeated-third', $ob->get('repeated', 2)); + $this->assertSame($mockFirstHeader, $ob->get('repeated')); + $this->assertSame($mockFirstHeader, $ob->get('repeated', 0)); + $this->assertSame($mockSecondHeader, $ob->get('repeated', 1)); + $this->assertSame($mockThirdHeader, $ob->get('repeated', 2)); $instanceHeaders = [ - 'repeated-first', 'repeated-second', 'repeated-third' + $mockFirstHeader, $mockSecondHeader, $mockThirdHeader ]; $this->assertEquals($instanceHeaders, $ob->getAll('repeated')); @@ -123,6 +131,12 @@ public function testAddSetExistsGet() : void $this->assertTrue($ob->exists('second', 1)); $this->assertTrue($ob->exists('third')); + $mockFirstUpdatedHeader = $this->getMockBuilder(IHeader::class)->getMock(); + $mockSecondUpdatedHeader = $this->getMockBuilder(IHeader::class)->getMock(); + $mockSecondFirstHeader = $this->getMockBuilder(IHeader::class)->getMock(); + $mockSecondHeader = $this->getMockBuilder(IHeader::class)->getMock(); + $mockThirdHeader = $this->getMockBuilder(IHeader::class)->getMock(); + $this->mockHeaderFactory ->expects($this->exactly(5)) ->method('newInstance') @@ -134,21 +148,21 @@ public function testAddSetExistsGet() : void ['third', 'value'] ) ->willReturnOnConsecutiveCalls( - 'first-updated-value', - 'second-second-updated-value', - 'second-first-value', - 'second-value', - 'third-value' + $mockFirstUpdatedHeader, + $mockSecondUpdatedHeader, + $mockSecondFirstHeader, + $mockSecondHeader, + $mockThirdHeader ); - $this->assertEquals('first-updated-value', $ob->get('first')); - $this->assertEquals('second-second-updated-value', $ob->get('second', 1)); - $this->assertEquals('second-first-value', $ob->get('first', 1)); - $this->assertEquals('second-value', $ob->get('second')); - $this->assertEquals('third-value', $ob->get('third')); + $this->assertSame($mockFirstUpdatedHeader, $ob->get('first')); + $this->assertSame($mockSecondUpdatedHeader, $ob->get('second', 1)); + $this->assertSame($mockSecondFirstHeader, $ob->get('first', 1)); + $this->assertSame($mockSecondHeader, $ob->get('second')); + $this->assertSame($mockThirdHeader, $ob->get('third')); $instanceHeaders = [ - 'first-updated-value', 'second-first-value' + $mockFirstUpdatedHeader, $mockSecondFirstHeader ]; $this->assertEquals($instanceHeaders, $ob->getAll('first')); @@ -182,7 +196,10 @@ public function testAddRemoveGetGetAs() : void $this->assertTrue($ob->exists('third')); $this->assertTrue($ob->exists('fourth')); - $oRet = $this->getMockForAbstractClass(\ZBateson\MailMimeParser\Header\IHeader::class); + $mockFirstHeader = $this->getMockBuilder(IHeader::class)->getMock(); + $mockSecondHeader = $this->getMockBuilder(IHeader::class)->getMock(); + $mockThirdHeader = $this->getMockBuilder(IHeader::class)->getMock(); + $mockSecondUpdatedHeader = $this->getMockBuilder(IHeader::class)->getMock(); $this->mockHeaderFactory ->expects($this->exactly(4)) ->method('newInstance') @@ -192,7 +209,7 @@ public function testAddRemoveGetGetAs() : void ['fourth', 'value'], ['second', 'updated'] ) - ->willReturnOnConsecutiveCalls('second-value', 'third-value', $oRet, 'second-updated'); + ->willReturnOnConsecutiveCalls($mockFirstHeader, $mockSecondHeader, $mockThirdHeader, $mockSecondUpdatedHeader); $custRet = $this->getMockForAbstractClass(\ZBateson\MailMimeParser\Header\IHeader::class); $this->mockHeaderFactory->expects($this->once()) @@ -201,9 +218,9 @@ public function testAddRemoveGetGetAs() : void ->willReturn($custRet); $this->assertNull($ob->get('first')); - $this->assertEquals('second-value', $ob->get('second')); - $this->assertEquals('third-value', $ob->get('third')); - $this->assertEquals($oRet, $ob->get('fourth')); + $this->assertSame($mockFirstHeader, $ob->get('second')); + $this->assertSame($mockSecondHeader, $ob->get('third')); + $this->assertSame($mockThirdHeader, $ob->get('fourth')); $headers = [ ['second', 'value'], ['third', 'value'], @@ -217,7 +234,7 @@ public function testAddRemoveGetGetAs() : void ['fourth', 'value'] ]; $this->assertNull($ob->get('second')); - $this->assertEquals('third-value', $ob->get('third')); + $this->assertSame($mockSecondHeader, $ob->get('third')); $this->assertEquals($headers, $ob->getHeaders()); $ob->set('second', 'updated'); @@ -227,7 +244,7 @@ public function testAddRemoveGetGetAs() : void ['second', 'updated'] ]; $this->assertEquals($headers, $ob->getHeaders()); - $this->assertEquals('second-updated', $ob->get('second')); + $this->assertSame($mockSecondUpdatedHeader, $ob->get('second')); $h = $ob->getAs('fourth', 'IHeaderClass'); $this->assertEquals($custRet, $h); @@ -259,6 +276,9 @@ public function testAddRemoveAllGet() : void $this->assertTrue($ob->exists('second', 2)); $this->assertTrue($ob->exists('third')); + $mockSecondFirstHeader = $this->getMockBuilder(IHeader::class)->getMock(); + $mockSecondHeader = $this->getMockBuilder(IHeader::class)->getMock(); + $mockSecondThirdSecondHeader = $this->getMockBuilder(IHeader::class)->getMock(); $this->mockHeaderFactory ->expects($this->exactly(3)) ->method('newInstance') @@ -267,18 +287,18 @@ public function testAddRemoveAllGet() : void ['second', 'value'], ['second', 'third-second'] ) - ->willReturnOnConsecutiveCalls('second-first-value', 'second-value', 'second-third-second-value'); + ->willReturnOnConsecutiveCalls($mockSecondFirstHeader, $mockSecondHeader, $mockSecondThirdSecondHeader); $this->assertNull($ob->get('first', 1)); - $this->assertEquals('second-first-value', $ob->get('first')); + $this->assertSame($mockSecondFirstHeader, $ob->get('first')); $ob->remove('second', 1); $this->assertTrue($ob->exists('second')); $this->assertTrue($ob->exists('second', 1)); $this->assertFalse($ob->exists('second', 2)); - $this->assertEquals('second-value', $ob->get('second')); - $this->assertEquals('second-third-second-value', $ob->get('second', 1)); + $this->assertSame($mockSecondHeader, $ob->get('second')); + $this->assertSame($mockSecondThirdSecondHeader, $ob->get('second', 1)); $this->assertNull($ob->get('second', 2)); $ob->removeAll('second');