initial commit

This commit is contained in:
Joby Elliott 2018-08-23 12:49:17 -06:00
parent eec097629e
commit 25dbeb389c
10 changed files with 2001 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/vendor/

11
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,11 @@
image: php:7.1-alpine
before_script:
- apk update
- apk add git
- curl -sS https://getcomposer.org/installer | php
- php composer.phar install
test:
script:
- php composer.phar test

33
composer.json Normal file
View file

@ -0,0 +1,33 @@
{
"name": "byjoby/html-object-strings",
"description": "Abstraction layer for constructing arbitrary HTML tags in PHP",
"type": "library",
"require": {
"byjoby/flatrr": "dev-master"
},
"license": "MIT",
"authors": [{
"name": "Joby Elliott",
"email": "joby@byjoby.com"
}],
"minimum-stability": "dev",
"autoload": {
"psr-4": {
"HtmlObjectStrings\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"HtmlObjectStrings\\": "tests/"
}
},
"scripts": {
"test": [
"phpunit"
]
},
"require-dev": {
"phpunit/phpunit": "^7.4@dev",
"stevegrunwell/phpunit-markup-assertions": "dev-master"
}
}

1675
composer.lock generated Normal file

File diff suppressed because it is too large Load diff

7
phpunit.xml Normal file
View file

@ -0,0 +1,7 @@
<phpunit>
<testsuites>
<testsuite name="All Tests">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>

9
src/A.php Normal file
View file

@ -0,0 +1,9 @@
<?php
/* HTML Object Strings | https://gitlab.com/byjoby/html-object-strings | MIT License */
namespace HtmlObjectStrings;
class A extends GenericTag
{
const TAG = 'a';
const SELFCLOSING = false;
}

136
src/GenericTag.php Normal file
View file

@ -0,0 +1,136 @@
<?php
/* HTML Object Strings | https://gitlab.com/byjoby/html-object-strings | MIT License */
namespace HtmlObjectStrings;
use Flatrr\FlatArray;
class GenericTag extends FlatArray implements HtmlInterface
{
const TAG = 'span';
const SELFCLOSING = false;
public function __construct(array $data = null)
{
parent::__construct($data);
$this->merge([
'classes' => [],
'attributes' => []
]);
$this->htmlInit();
}
protected function htmlContent()
{
if (is_array($this['content'])) {
$content = implode(PHP_EOL, $this['content']);
} else {
$content = $this['content'];
}
return $content;
}
protected function htmlAttributes()
{
$attr = $this['attributes'];
if ($this->classes()) {
$attr['class'] = implode(' ', $this->classes());
}
return $attr;
}
protected function htmlInit()
{
$this['tag'] = static::TAG;
$this['selfclosing'] = static::SELFCLOSING;
}
public function addClass(string $name)
{
if (!$name) {
return;
}
$classes = $this['classes'];
$classes[] = $name;
$classes = array_unique($classes);
sort($classes);
unset($this['classes']);
$this['classes'] = $classes;
}
public function hasClass(string $name) : bool
{
return in_array($name, $this['classes']);
}
public function removeClass(string $name)
{
$classes = array_filter(
$this['classes'],
function ($e) use ($name) {
return $e != $name;
}
);
sort($classes);
unset($this['classes']);
$this['classes'] = $classes;
}
public function classes() : array
{
return $this['classes'];
}
public function attr(string $name, $value = null)
{
if ($value === false) {
unset($this['attributes.'.$name]);
return null;
}
if ($value !== null) {
$this['attributes.'.$name] = $value;
}
if (isset($this['attributes.'.$name])) {
return $this['attributes.'.$name];
}
return null;
}
public function data(string $name, $value = null)
{
return $this->attr("data-$name", $value);
}
public function string() : string
{
$out = '';
//build opening tag
$out .= '<'.$this['tag'];
//build attributes
if ($attr = $this->htmlAttributes()) {
foreach ($attr as $key => $value) {
if (!"$value") {
$out .= " $key";
} else {
$value = htmlspecialchars($value);
$out .= " $key=\"$value\"";
}
}
}
//continue t close opening tag and add content and closing tag if needed
if ($this['selfclosing']) {
$out .= ' />';
} else {
$out .= '>';
//build content
$out .= $this->htmlContent();
//build closing tag
$out .= '</'.$this['tag'].'>';
}
return $out;
}
public function __toString()
{
return $this->string();
}
}

20
src/HtmlInterface.php Normal file
View file

@ -0,0 +1,20 @@
<?php
/* HTML Object Strings | https://gitlab.com/byjoby/html-object-strings | MIT License */
namespace HtmlObjectStrings;
use Flatrr\FlatArrayInterface;
interface HtmlInterface extends FlatArrayInterface
{
public function attr(string $name, $value = null);
public function data(string $name, $value = null);
public function addClass(string $name);
public function hasClass(string $name) : bool;
public function removeClass(string $name);
public function classes() : array;
public function string() : string;
public function __toString();
}

30
src/Input.php Normal file
View file

@ -0,0 +1,30 @@
<?php
/* HTML Object Strings | https://gitlab.com/byjoby/html-object-strings | MIT License */
namespace HtmlObjectStrings;
class Input extends GenericTag
{
const TAG = 'input';
const SELFCLOSING = true;
const TYPE = 'text';
protected function htmlInit()
{
parent::htmlInit();
$this->attr('type', static::TYPE);
}
protected function htmlContent()
{
return parent::htmlContent();
}
protected function htmlAttributes()
{
$attr = parent::htmlAttributes();
if ($value = $this->htmlContent()) {
$attr['value'] = $value;
}
return $attr;
}
}

79
tests/GenericTagTest.php Normal file
View file

@ -0,0 +1,79 @@
<?php
/* HTML Object Strings | https://gitlab.com/byjoby/html-object-strings | MIT License */
declare(strict_types=1);
namespace HtmlObjectStrings;
use PHPUnit\Framework\TestCase;
class GenericTagTest extends TestCase
{
use \SteveGrunwell\PHPUnit_Markup_Assertions\MarkupAssertionsTrait;
public function testBasicClassManagement()
{
/*
This section tests the basic class adding, removing, and output features
*/
$h = new GenericTag();
//default is no classes
$this->assertEquals([], $h->classes());
$this->assertFalse($h->hasClass('foo'));
//adding a class
$h->addClass('foo');
//should now exist
$this->assertEquals(['foo'], $h->classes());
$this->assertTrue($h->hasClass('foo'));
//adding a second time shouldn't change anything
$h->addClass('foo');
$this->assertEquals(['foo'], $h->classes());
$this->assertTrue($h->hasClass('foo'));
//adding another class
$h->addClass('bar');
//should now exist, and classes should be in alphabetical order
$this->assertEquals(['bar','foo'], $h->classes());
$this->assertTrue($h->hasClass('bar'));
//removing a class
$h->addClass('abc');
$h->removeClass('bar');
//bar should now not exist, and classes should be in alphabetical order
$this->assertEquals(['abc','foo'], $h->classes());
$this->assertFalse($h->hasClass('bar'));
}
public function testDataAndAttributeManagement()
{
/*
This section tests the basic getting/setting of attributes
*/
$h = new GenericTag();
//set and get an attribute
$h->attr('foo', 'bar');
$this->assertEquals('bar', $h->attr('foo'));
//set and get data
$h->data('foo', 'baz');
$this->assertEquals('baz', $h->data('foo'));
$this->assertEquals('baz', $h->attr('data-foo'));
$this->assertEquals('bar', $h->attr('foo'));
}
public function testMarkupOutput()
{
/*
Test that output has the correct attributes and classes for what was
configured into the object
*/
$h = new GenericTag();
$h['tag'] = 'div';
$h['selfclosing'] = false;
$h['content'] = 'markup content';
$h->attr('id', 'h');
$h->data('foo', 'bar');
$h->addClass('class-foo');
$h->addClass('class-bar');
//should be a div tag
$this->assertContainsSelector('div', "$h");
$this->assertContainsSelector('div#h', "$h");
$this->assertContainsSelector('div[data-foo="bar"]', "$h");
$this->assertContainsSelector('div.class-foo.class-bar', "$h");
}
}