Skip to content

Commit

Permalink
New AbstractHeader::from static method
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
zbateson committed Jan 31, 2024
1 parent e2247c3 commit d6764c0
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 36 deletions.
55 changes: 55 additions & 0 deletions src/Header/AbstractHeader.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 protected]. 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]);
}
}
2 changes: 1 addition & 1 deletion src/Header/HeaderFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
41 changes: 41 additions & 0 deletions tests/MailMimeParser/Header/HeaderFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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());
}
}
90 changes: 55 additions & 35 deletions tests/MailMimeParser/Message/PartHeaderContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace ZBateson\MailMimeParser\Message;

use PHPUnit\Framework\TestCase;
use ZBateson\MailMimeParser\Header\IHeader;

/**
* Description of HeaderContainerTest
Expand Down Expand Up @@ -36,21 +37,24 @@ 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')
->withConsecutive(
['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));
Expand All @@ -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')
Expand All @@ -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'));

Expand Down Expand Up @@ -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')
Expand All @@ -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'));

Expand Down Expand Up @@ -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')
Expand All @@ -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())
Expand All @@ -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'],
Expand All @@ -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');
Expand All @@ -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);
Expand Down Expand Up @@ -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')
Expand All @@ -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');
Expand Down

0 comments on commit d6764c0

Please sign in to comment.