Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PHPStan #53

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: "CI"

on:
pull_request:
branches:
- master
push:
branches:
- master

jobs:
check:
name: "PHPStan"
runs-on: "ubuntu-latest"

strategy:
matrix:
php:
- "7.4"

steps:
- name: "Checkout"
uses: "actions/checkout@v3"

- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php }}"
env:
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"

- name: "Run PHPStan"
run: "vendor/bin/phpstan analyse --no-progress --error-format=github"
75 changes: 48 additions & 27 deletions Feed.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@
* @package UniversalFeedWriter
* @author Anis uddin Ahmad <[email protected]>
* @link http://www.ajaxray.com/projects/rss
*
* PHPStan does not support recursive type aliases
* https://github.com/phpstan/phpstan/issues/4637
* so we are manually approximating the fixed point
* to a depth that should be sufficient in practice.
* Feel free to add more layers if you need them.
* @_phpstan-recursive-type TagContent string|array<TagContent>|array<string, TagContent>
* @phpstan-type TagContentFix0 string
* @phpstan-type TagContentFix1 string|array<TagContentFix0>|array<string, TagContentFix0>
* @phpstan-type TagContentFix2 string|array<TagContentFix1>|array<string, TagContentFix1>
* @phpstan-type TagContentFix3 string|array<TagContentFix2>|array<string, TagContentFix2>
* @phpstan-type TagContentFix TagContentFix3
*
* @phpstan-type Element array{content: TagContentFix, attributes: array<string, string>}
*/
abstract class Feed
{
Expand All @@ -47,33 +61,45 @@ abstract class Feed

/**
* Collection of all channel elements
*
* @var array<string, array<Element>>
*/
private $channels = array();

/**
* Collection of items as object of \FeedWriter\Item class.
*
* @var Item[]
*/
private $items = array();

/**
* Collection of other version wise data.
*
* Currently used to store the 'rdf:about' attribute and image element of the channel (both RSS1 only).
*
* @var array{Image?: string|array{title: string, link: string, url: string}, ChannelAbout?: string}
*/
private $data = array();

/**
* The tag names which have to encoded as CDATA
*
* @var string[]
*/
private $CDATAEncoding = array();

/**
* Collection of XML namespaces
*
* @var array<string, string>
*/
private $namespaces = array();

/**
* Contains the format of this feed.
*
* @var Feed::RSS1|Feed::RSS2|Feed::ATOM
*/
private $version = null;

Expand All @@ -89,7 +115,7 @@ abstract class Feed
*
* If no version is given, a feed in RSS 2.0 format will be generated.
*
* @param string $version the version constant (RSS1/RSS2/ATOM).
* @param Feed::RSS1|Feed::RSS2|Feed::ATOM $version the version constant (RSS1/RSS2/ATOM).
*/
protected function __construct($version = Feed::RSS2)
{
Expand Down Expand Up @@ -192,13 +218,13 @@ public function addNamespace($prefix, $uri)
*
* @access public
* @param string $elementName name of the channel tag
* @param string $content content of the channel tag
* @param array array of element attributes with attribute name as array key
* @param bool TRUE if this element can appear multiple times
* @param TagContentFix $content content of the channel tag
* @param array<string, string> $attributes array of element attributes with attribute name as array key
* @param bool $multiple TRUE if this element can appear multiple times
* @return self
* @throws \InvalidArgumentException if the element name is not a string, empty or NULL.
*/
public function setChannelElement($elementName, $content, array $attributes = null, $multiple = false)
public function setChannelElement($elementName, $content, array $attributes = [], $multiple = false)
{
if (empty($elementName))
throw new \InvalidArgumentException('The element name may not be empty or NULL.');
Expand All @@ -211,7 +237,7 @@ public function setChannelElement($elementName, $content, array $attributes = nu
if ($multiple === TRUE)
$this->channels[$elementName][] = $entity;
else
$this->channels[$elementName] = $entity;
$this->channels[$elementName] = [$entity];

return $this;
}
Expand All @@ -221,7 +247,7 @@ public function setChannelElement($elementName, $content, array $attributes = nu
* should be 'channelName' => 'channelContent' format.
*
* @access public
* @param array array of channels
* @param array<string, TagContentFix> $elementArray array of channels
* @return self
*/
public function setChannelElementsFromArray(array $elementArray)
Expand Down Expand Up @@ -262,7 +288,7 @@ public function getMIMEType()
* if you need to pass a string around, use generateFeed() instead.
*
* @access public
* @param bool FALSE if the specific feed media type should be sent.
* @param bool $useGenericContentType FALSE if the specific feed media type should be sent.
* @return void
* @throws \InvalidArgumentException if the useGenericContentType parameter is not boolean.
*/
Expand Down Expand Up @@ -318,7 +344,7 @@ public function createNewItem()
* Add one or more tags to the list of CDATA encoded tags
*
* @access public
* @param array $tags An array of tag names that are merged into the list of tags which should be encoded as CDATA
* @param array<string> $tags An array of tag names that are merged into the list of tags which should be encoded as CDATA
* @return self
*/
public function addCDATAEncoding(array $tags)
Expand All @@ -332,7 +358,7 @@ public function addCDATAEncoding(array $tags)
* Get list of CDATA encoded properties
*
* @access public
* @return array Return an array of CDATA properties that are to be encoded as CDATA
* @return array<string> Return an array of CDATA properties that are to be encoded as CDATA
*/
public function getCDATAEncoding()
{
Expand All @@ -343,7 +369,7 @@ public function getCDATAEncoding()
* Remove tags from the list of CDATA encoded tags
*
* @access public
* @param array $tags An array of tag names that should be removed.
* @param array<string> $tags An array of tag names that should be removed.
* @return void
*/
public function removeCDATAEncoding(array $tags)
Expand Down Expand Up @@ -421,7 +447,7 @@ public function setTitle($title)
* Not supported in RSS1 feeds.
*
* @access public
* @param DateTimeInterface|int|string Date which should be used.
* @param DateTimeInterface|int|string $date Date which should be used.
* @return self
* @throws \InvalidArgumentException if the given date is not an implementation of DateTimeInterface, a UNIX timestamp or a date string.
* @throws InvalidOperationException if this method is called on an RSS1 feed.
Expand Down Expand Up @@ -670,7 +696,7 @@ public function setChannelAbout($url)
*/
public static function uuid($key = null, $prefix = '')
{
$key = ($key == null) ? uniqid(rand()) : $key;
$key = ($key == null) ? uniqid((string) rand()) : $key;
$chars = md5($key);
$uuid = substr($chars,0,8) . '-';
$uuid .= substr($chars,8,4) . '-';
Expand Down Expand Up @@ -719,7 +745,7 @@ public static function filterInvalidXMLChars($string, $replacement = '_') // def
* because they are hardcoded, e.g. rdf.
*
* @access private
* @return array Array with namespace prefix as value.
* @return array<string> Array with namespace prefix as value.
*/
private function getNamespacePrefixes()
{
Expand Down Expand Up @@ -823,8 +849,8 @@ private function makeFooter()
*
* @access private
* @param string $tagName name of the tag
* @param mixed $tagContent tag value as string or array of nested tags in 'tagName' => 'tagValue' format
* @param array $attributes Attributes (if any) in 'attrName' => 'attrValue' format
* @param TagContentFix $tagContent tag value as string or array of nested tags in 'tagName' => 'tagValue' format
* @param array<string, string> $attributes Attributes (if any) in 'attrName' => 'attrValue' format
* @param bool $omitEndTag True if the end tag should be omitted. Defaults to false.
* @return string formatted xml tag
* @throws \InvalidArgumentException if the tagContent is not an array and not a string.
Expand Down Expand Up @@ -890,7 +916,7 @@ private function makeChannels()
$out .= '<channel>' . PHP_EOL;
break;
case Feed::RSS1:
$out .= (isset($this->data['ChannelAbout']))? "<channel rdf:about=\"{$this->data['ChannelAbout']}\">" : "<channel rdf:about=\"{$this->channels['link']['content']}\">";
$out .= (isset($this->data['ChannelAbout']))? "<channel rdf:about=\"{$this->data['ChannelAbout']}\">" : "<channel rdf:about=\"{$this->channels['link'][0]['content']}\">";
break;
}

Expand All @@ -902,14 +928,9 @@ private function makeChannels()
$key = substr($key, 5);
}

// The channel element can occur multiple times, when the key 'content' is not in the array.
if (!array_key_exists('content', $value)) {
// If this is the case, iterate through the array with the multiple elements.
foreach ($value as $singleElement) {
$out .= $this->makeNode($key, $singleElement['content'], $singleElement['attributes']);
}
} else {
$out .= $this->makeNode($key, $value['content'], $value['attributes']);
// The channel element can occur multiple times.
foreach ($value as $singleElement) {
$out .= $this->makeNode($key, $singleElement['content'], $singleElement['attributes']);
}
}

Expand All @@ -927,7 +948,7 @@ private function makeChannels()
$out .= $this->makeNode('image', $this->data['Image'], array('rdf:about' => $this->data['Image']['url']));
} else if ($this->version == Feed::ATOM) {
// ATOM feeds have a unique feed ID. Use the title channel element as key.
$out .= $this->makeNode('id', Feed::uuid($this->channels['title']['content'], 'urn:uuid:'));
$out .= $this->makeNode('id', Feed::uuid($this->channels['title'][0]['content'], 'urn:uuid:'));
}

return $out;
Expand Down Expand Up @@ -977,7 +998,7 @@ private function makeItems()
* @return string The starting XML tag of an feed item.
* @throws InvalidOperationException if this object misses the data for the about attribute.
*/
private function startItem($about = false)
private function startItem($about = '')
{
$out = '';

Expand Down
25 changes: 17 additions & 8 deletions Item.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,37 @@
* @package UniversalFeedWriter
* @author Anis uddin Ahmad <[email protected]>
* @link http://www.ajaxray.com/projects/rss
*
* @phpstan-import-type TagContentFix from Feed
* @phpstan-import-type Element from Feed
*/
class Item
{
/**
* Collection of feed item elements
*
* @var array<string, Element>
*/
private $elements = array();

/**
* Contains the format of this feed.
*
* @var Feed::RSS1|Feed::RSS2|Feed::ATOM
*/
private $version;

/**
* Is used as a suffix when multiple elements have the same name.
*
* @var int
**/
private $_cpt = 0;

/**
* Constructor
*
* @param string $version constant (RSS1/RSS2/ATOM) RSS2 is default.
* @param Feed::RSS1|Feed::RSS2|Feed::ATOM $version constant (RSS1/RSS2/ATOM) RSS2 is default.
*/
public function __construct($version = Feed::RSS2)
{
Expand All @@ -76,14 +85,14 @@ private function cpt()
*
* @access public
* @param string $elementName The tag name of an element
* @param string $content The content of tag
* @param array $attributes Attributes (if any) in 'attrName' => 'attrValue' format
* @param TagContentFix $content The content of tag
* @param array<string, string> $attributes Attributes (if any) in 'attrName' => 'attrValue' format
* @param boolean $overwrite Specifies if an already existing element is overwritten.
* @param boolean $allowMultiple Specifies if multiple elements of the same name are allowed.
* @return self
* @throws \InvalidArgumentException if the element name is not a string, empty or NULL.
*/
public function addElement($elementName, $content, array $attributes = null, $overwrite = FALSE, $allowMultiple = FALSE)
public function addElement($elementName, $content, array $attributes = [], $overwrite = FALSE, $allowMultiple = FALSE)
{
if (empty($elementName))
throw new \InvalidArgumentException('The element name may not be empty or NULL.');
Expand Down Expand Up @@ -113,7 +122,7 @@ public function addElement($elementName, $content, array $attributes = null, $ov
* Elements which have attributes cannot be added by this method
*
* @access public
* @param array array of elements in 'tagName' => 'tagContent' format.
* @param array<string, TagContentFix> $elementArray array of elements in 'tagName' => 'tagContent' format.
* @return self
*/
public function addElementArray(array $elementArray)
Expand All @@ -129,7 +138,7 @@ public function addElementArray(array $elementArray)
* Return the collection of elements in this feed item
*
* @access public
* @return array All elements of this item.
* @return array<string, Element> All elements of this item.
* @throws InvalidOperationException on ATOM feeds if either a content or link element is missing.
* @throws InvalidOperationException on RSS1 feeds if a title or link element is missing.
*/
Expand Down Expand Up @@ -308,7 +317,7 @@ public function addEnclosure($url, $length, $type, $multiple = TRUE)
if (!is_string($type) || preg_match('/.+\/.+/', $type) != 1)
throw new \InvalidArgumentException('type parameter must be a string and a MIME type.');

$attributes = array('length' => $length, 'type' => $type);
$attributes = array('length' => (string) $length, 'type' => $type);

if ($this->version == Feed::RSS2) {
$attributes['url'] = $url;
Expand Down Expand Up @@ -405,7 +414,7 @@ public function setId($id, $permaLink = false)
if (!$found)
throw new \InvalidArgumentException("The ID must begin with an IANA-registered URI scheme.");

$this->addElement('id', $id, NULL, TRUE);
$this->addElement('id', $id, [], TRUE);
} else
throw new InvalidOperationException('A unique ID is not supported in RSS1 feeds.');

Expand Down
7 changes: 6 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
"name": "Tino Goratsch"
}
],
"minimum-stability": "dev",
"autoload": {
"psr-4": {
"FeedWriter\\": ""
Expand All @@ -67,9 +66,15 @@
"require" : {
"php": ">=5.3.0"
},
"require-dev": {
"phpstan/phpstan": "^1.10"
},
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
}
},
"scripts": {
"phpstan": "phpstan analyse --memory-limit 512M"
}
}
Loading