1
0
mirror of https://git.tt-rss.org/git/tt-rss.git synced 2025-12-16 03:45:57 +00:00

* switch to composer for qrcode and otp dependencies

* move most OTP-related stuff into userhelper
* remove old phpqrcode and otphp libraries
This commit is contained in:
Andrew Dolgov
2021-02-26 19:16:17 +03:00
parent bc4475b669
commit 3fd7856543
788 changed files with 63185 additions and 11711 deletions

View File

@@ -0,0 +1,4 @@
.idea
.vendor
composer.lock
*.phpunit.result.cache

View File

@@ -0,0 +1,5 @@
filter:
excluded_paths:
- examples/*
- tests/*
- vendor/*

View File

@@ -0,0 +1,18 @@
language: php
matrix:
include:
- php: 7.2
- php: 7.3
- php: 7.4snapshot
- php: nightly
allow_failures:
- php: 7.4snapshot
- php: nightly
before_script: travis_retry composer install --no-interaction --prefer-source
script: vendor/bin/phpunit --configuration phpunit.xml --coverage-clover clover.xml
after_script: bash <(curl -s https://codecov.io/bash)

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018 Smiley <smiley@chillerlan.net>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,153 @@
# chillerlan/php-settings-container
A container class for immutable settings objects. Not a DI container. PHP 7.2+
- [`SettingsContainerInterface`](https://github.com/chillerlan/php-settings-container/blob/master/src/SettingsContainerInterface.php) provides immutable properties with magic getter & setter and some fancy
[![version][packagist-badge]][packagist]
[![license][license-badge]][license]
[![Travis][travis-badge]][travis]
[![Coverage][coverage-badge]][coverage]
[![Scrunitizer][scrutinizer-badge]][scrutinizer]
[![Packagist downloads][downloads-badge]][downloads]
[![PayPal donate][donate-badge]][donate]
[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-settings-container.svg?style=flat-square
[packagist]: https://packagist.org/packages/chillerlan/php-settings-container
[license-badge]: https://img.shields.io/github/license/chillerlan/php-settings-container.svg?style=flat-square
[license]: https://github.com/chillerlan/php-settings-container/blob/master/LICENSE
[travis-badge]: https://img.shields.io/travis/chillerlan/php-settings-container.svg?style=flat-square
[travis]: https://travis-ci.org/chillerlan/php-settings-container
[coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-settings-container.svg?style=flat-square
[coverage]: https://codecov.io/github/chillerlan/php-settings-container
[scrutinizer-badge]: https://img.shields.io/scrutinizer/g/chillerlan/php-settings-container.svg?style=flat-square
[scrutinizer]: https://scrutinizer-ci.com/g/chillerlan/php-settings-container
[downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-settings-container.svg?style=flat-square
[downloads]: https://packagist.org/packages/chillerlan/php-settings-container/stats
[donate-badge]: https://img.shields.io/badge/donate-paypal-ff33aa.svg?style=flat-square
[donate]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=WLYUNAT9ZTJZ4
## Documentation
### Installation
**requires [composer](https://getcomposer.org)**
*composer.json* (note: replace `dev-master` with a version boundary)
```json
{
"require": {
"php": "^7.2",
"chillerlan/php-settings-container": "^1.0"
}
}
```
### Manual installation
Download the desired version of the package from [master](https://github.com/chillerlan/php-settings-container/archive/master.zip) or
[release](https://github.com/chillerlan/php-settings-container/releases) and extract the contents to your project folder. After that:
- run `composer install` to install the required dependencies and generate `/vendor/autoload.php`.
- if you use a custom autoloader, point the namespace `chillerlan\Settings` to the folder `src` of the package
Profit!
## Usage
The `SettingsContainerInterface` (wrapped in`SettingsContainerAbstract` ) provides plug-in functionality for immutable object variables and adds some fancy, like loading/saving JSON, arrays etc.
It takes iterable as the only constructor argument and calls a method with the trait's name on invocation (`MyTrait::MyTrait()`) for each used trait.
### Simple usage
```php
class MyContainer extends SettingsContainerAbstract{
protected $foo;
protected $bar;
}
```
```php
// use it just like a \stdClass
$container = new MyContainer;
$container->foo = 'what';
$container->bar = 'foo';
// which is equivalent to
$container = new MyContainer(['bar' => 'foo', 'foo' => 'what']);
// ...or try
$container->fromJSON('{"foo": "what", "bar": "foo"}');
// fetch all properties as array
$container->toArray(); // -> ['foo' => 'what', 'bar' => 'foo']
// or JSON
$container->toJSON(); // -> {"foo": "what", "bar": "foo"}
//non-existing properties will be ignored:
$container->nope = 'what';
var_dump($container->nope); // -> null
```
### Advanced usage
```php
trait SomeOptions{
protected $foo;
protected $what;
// this method will be called in SettingsContainerAbstract::construct()
// after the properties have been set
protected function SomeOptions(){
// just some constructor stuff...
$this->foo = strtoupper($this->foo);
}
// this method will be called from __set() when property $what is set
protected function set_what(string $value){
$this->what = md5($value);
}
}
trait MoreOptions{
protected $bar = 'whatever'; // provide default values
}
```
```php
$commonOptions = [
// SomeOptions
'foo' => 'whatever',
// MoreOptions
'bar' => 'nothing',
];
// now plug the several library options together to a single object
$container = new class ($commonOptions) extends SettingsContainerAbstract{
use SomeOptions, MoreOptions;
};
var_dump($container->foo); // -> WHATEVER (constructor ran strtoupper on the value)
var_dump($container->bar); // -> nothing
$container->what = 'some value';
var_dump($container->what); // -> md5 hash of "some value"
```
### API
#### [`SettingsContainerAbstract`](https://github.com/chillerlan/php-settings-container/blob/master/src/SettingsContainerAbstract.php)
method | return | info
-------- | ---- | -----------
`__construct(iterable $properties = null)` | - | calls `construct()` internally after the properties have been set
(protected) `construct()` | void | calls a method with trait name as replacement constructor for each used trait
`__get(string $property)` | mixed | calls `$this->{'get_'.$property}()` if such a method exists
`__set(string $property, $value)` | void | calls `$this->{'set_'.$property}($value)` if such a method exists
`__isset(string $property)` | bool |
`__unset(string $property)` | void |
`__toString()` | string | a JSON string
`toArray()` | array |
`fromIterable(iterable $properties)` | `SettingsContainerInterface` |
`toJSON(int $jsonOptions = null)` | string | accepts [JSON options constants](http://php.net/manual/json.constants.php)
`fromJSON(string $json)` | `SettingsContainerInterface` |
`jsonSerialize()` | mixed | implements the [`JsonSerializable`](https://www.php.net/manual/en/jsonserializable.jsonserialize.php) interface
## Disclaimer
This might be either an utterly genius or completely stupid idea - you decide. However, i like it and it works.
Also, this is not a dependency injection container. Stop using DI containers FFS.

View File

@@ -0,0 +1,40 @@
{
"name": "chillerlan/php-settings-container",
"description": "A container class for immutable settings objects. Not a DI container. PHP 7.2+",
"homepage": "https://github.com/chillerlan/php-settings-container",
"license": "MIT",
"type": "library",
"minimum-stability": "stable",
"keywords": [
"php7", "helper", "container", "settings"
],
"authors": [
{
"name": "Smiley",
"email": "smiley@chillerlan.net",
"homepage": "https://github.com/codemasher"
}
],
"support": {
"issues": "https://github.com/chillerlan/php-settings-container/issues",
"source": "https://github.com/chillerlan/php-settings-container"
},
"require": {
"php": "^7.2",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^8.3"
},
"autoload": {
"psr-4": {
"chillerlan\\Settings\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"chillerlan\\SettingsTest\\": "tests/",
"chillerlan\\SettingsExamples\\": "examples/"
}
}
}

View File

@@ -0,0 +1,46 @@
<?php
/**
* @filesource advanced.php
* @created 28.08.2018
* @author smiley <smiley@chillerlan.net>
* @copyright 2018 smiley
* @license MIT
*/
namespace chillerlan\SettingsExamples;
use chillerlan\Settings\SettingsContainerAbstract;
require_once __DIR__.'/../vendor/autoload.php';
// from library #1
trait SomeOptions{
protected $foo;
// this method will be called in SettingsContainerAbstract::__construct() after the properties have been set
protected function SomeOptions(){
// just some constructor stuff...
$this->foo = strtoupper($this->foo);
}
}
// from library #2
trait MoreOptions{
protected $bar = 'whatever'; // provide default values
}
$commonOptions = [
// SomeOptions
'foo' => 'whatever',
// MoreOptions
'bar' => 'nothing',
];
// now plug the several library options together to a single object
/** @var \chillerlan\Settings\SettingsContainerInterface $container */
$container = new class ($commonOptions) extends SettingsContainerAbstract{
use SomeOptions, MoreOptions; // ...
};
var_dump($container->foo); // -> WHATEVER (constructor ran strtoupper on the value)
var_dump($container->bar); // -> nothing

View File

@@ -0,0 +1,30 @@
<?php
/**
* @filesource simple.php
* @created 28.08.2018
* @author smiley <smiley@chillerlan.net>
* @copyright 2018 smiley
* @license MIT
*/
namespace chillerlan\SettingsExamples;
use chillerlan\Settings\SettingsContainerAbstract;
require_once __DIR__.'/../vendor/autoload.php';
class MyContainer extends SettingsContainerAbstract{
protected $foo;
protected $bar;
}
/** @var \chillerlan\Settings\SettingsContainerInterface $container */
$container = new MyContainer(['foo' => 'what']);
$container->bar = 'foo';
var_dump($container->toJSON()); // -> {"foo":"what","bar":"foo"}
// non-existing properties will be ignored:
$container->nope = 'what';
var_dump($container->nope); // -> NULL

View File

@@ -0,0 +1,35 @@
<?xml version="1.0"?>
<ruleset name="codemasher/php-settings-container PMD ruleset"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
<description>codemasher/php-settings-container PMD ruleset</description>
<exclude-pattern>*/examples/*</exclude-pattern>
<exclude-pattern>*/tests/*</exclude-pattern>
<exclude-pattern>*/vendor/*</exclude-pattern>
<rule ref="rulesets/cleancode.xml">
<exclude name="BooleanArgumentFlag"/>
</rule>
<rule ref="rulesets/codesize.xml/CyclomaticComplexity">
<priority>1</priority>
<properties>
<property name="maximum" value="150" />
</properties>
</rule>
<rule ref="rulesets/controversial.xml">
<exclude name="CamelCaseMethodName"/>
<exclude name="CamelCasePropertyName"/>
<exclude name="CamelCaseParameterName"/>
<exclude name="CamelCaseVariableName"/>
</rule>
<rule ref="rulesets/design.xml">
</rule>
<rule ref="rulesets/naming.xml">
<exclude name="LongVariable"/>
<exclude name="ShortVariable"/>
</rule>
<rule ref="rulesets/unusedcode.xml">
<exclude name="UnusedFormalParameter"/>
</rule>
</ruleset>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
<testsuites>
<testsuite name="php-settings-container test suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
</phpunit>

View File

@@ -0,0 +1,172 @@
<?php
/**
* Class SettingsContainerAbstract
*
* @filesource SettingsContainerAbstract.php
* @created 28.08.2018
* @package chillerlan\Settings
* @author Smiley <smiley@chillerlan.net>
* @copyright 2018 Smiley
* @license MIT
*/
namespace chillerlan\Settings;
use Exception, ReflectionClass, ReflectionProperty;
use function call_user_func, call_user_func_array, get_object_vars, json_decode, json_encode, method_exists, property_exists;
abstract class SettingsContainerAbstract implements SettingsContainerInterface{
/**
* SettingsContainerAbstract constructor.
*
* @param iterable|null $properties
*/
public function __construct(iterable $properties = null){
if(!empty($properties)){
$this->fromIterable($properties);
}
$this->construct();
}
/**
* calls a method with trait name as replacement constructor for each used trait
* (remember pre-php5 classname constructors? yeah, basically this.)
*
* @return void
*/
protected function construct():void{
$traits = (new ReflectionClass($this))->getTraits();
foreach($traits as $trait){
$method = $trait->getShortName();
if(method_exists($this, $method)){
call_user_func([$this, $method]);
}
}
}
/**
* @inheritdoc
*/
public function __get(string $property){
if(property_exists($this, $property) && !$this->isPrivate($property)){
if(method_exists($this, 'get_'.$property)){
return call_user_func([$this, 'get_'.$property]);
}
return $this->{$property};
}
return null;
}
/**
* @inheritdoc
*/
public function __set(string $property, $value):void{
if(!property_exists($this, $property) || $this->isPrivate($property)){
return;
}
if(method_exists($this, 'set_'.$property)){
call_user_func_array([$this, 'set_'.$property], [$value]);
return;
}
$this->{$property} = $value;
}
/**
* @inheritdoc
*/
public function __isset(string $property):bool{
return isset($this->{$property}) && !$this->isPrivate($property);
}
/**
* @internal Checks if a property is private
*
* @param string $property
*
* @return bool
*/
protected function isPrivate(string $property):bool{
return (new ReflectionProperty($this, $property))->isPrivate();
}
/**
* @inheritdoc
*/
public function __unset(string $property):void{
if($this->__isset($property)){
unset($this->{$property});
}
}
/**
* @inheritdoc
*/
public function __toString():string{
return $this->toJSON();
}
/**
* @inheritdoc
*/
public function toArray():array{
return get_object_vars($this);
}
/**
* @inheritdoc
*/
public function fromIterable(iterable $properties):SettingsContainerInterface{
foreach($properties as $key => $value){
$this->__set($key, $value);
}
return $this;
}
/**
* @inheritdoc
*/
public function toJSON(int $jsonOptions = null):string{
return json_encode($this, $jsonOptions ?? 0);
}
/**
* @inheritdoc
*/
public function fromJSON(string $json):SettingsContainerInterface{
$data = json_decode($json, true); // as of PHP 7.3: JSON_THROW_ON_ERROR
if($data === false || $data === null){
throw new Exception('error while decoding JSON');
}
return $this->fromIterable($data);
}
/**
* @inheritdoc
*/
public function jsonSerialize(){
return $this->toArray();
}
}

View File

@@ -0,0 +1,104 @@
<?php
/**
* Interface SettingsContainerInterface
*
* @filesource SettingsContainerInterface.php
* @created 28.08.2018
* @package chillerlan\Settings
* @author Smiley <smiley@chillerlan.net>
* @copyright 2018 Smiley
* @license MIT
*/
namespace chillerlan\Settings;
use JsonSerializable;
/**
* a generic container with magic getter and setter
*/
interface SettingsContainerInterface extends JsonSerializable{
/**
* Retrieve the value of $property
*
* @param string $property
*
* @return mixed
*/
public function __get(string $property);
/**
* Set $property to $value while avoiding private and non-existing properties
*
* @param string $property
* @param mixed $value
*
* @return void
*/
public function __set(string $property, $value):void;
/**
* Checks if $property is set (aka. not null), excluding private properties
*
* @param string $property
*
* @return bool
*/
public function __isset(string $property):bool;
/**
* Unsets $property while avoiding private and non-existing properties
*
* @param string $property
*
* @return void
*/
public function __unset(string $property):void;
/**
* @see SettingsContainerInterface::toJSON()
*
* @return string
*/
public function __toString():string;
/**
* Returns an array representation of the settings object
*
* @return array
*/
public function toArray():array;
/**
* Sets properties from a given iterable
*
* @param iterable $properties
*
* @return \chillerlan\Settings\SettingsContainerInterface
*/
public function fromIterable(iterable $properties):SettingsContainerInterface;
/**
* Returns a JSON representation of the settings object
* @see \json_encode()
*
* @param int|null $jsonOptions
*
* @return string
*/
public function toJSON(int $jsonOptions = null):string;
/**
* Sets properties from a given JSON string
*
* @param string $json
*
* @return \chillerlan\Settings\SettingsContainerInterface
*
* @throws \Exception
* @throws \JsonException
*/
public function fromJSON(string $json):SettingsContainerInterface;
}

View File

@@ -0,0 +1,105 @@
<?php
/**
* Class ContainerTraitTest
*
* @filesource ContainerTraitTest.php
* @created 28.08.2018
* @package chillerlan\SettingsTest
* @author Smiley <smiley@chillerlan.net>
* @copyright 2018 Smiley
* @license MIT
*/
namespace chillerlan\SettingsTest;
use PHPUnit\Framework\TestCase;
use Exception, TypeError;
class ContainerTraitTest extends TestCase{
public function testConstruct(){
$container = new TestContainer([
'test1' => 'test1',
'test2' => 'test2',
'test3' => 'test3',
'test4' => 'test4',
]);
$this->assertSame('test1', $container->test1);
$this->assertSame('test2', $container->test2);
$this->assertNull($container->test3);
$this->assertSame('test4', $container->test4);
$this->assertSame('success', $container->testConstruct);
}
public function testGet(){
$container = new TestContainer;
$this->assertSame('foo', $container->test1);
$this->assertNull($container->test2);
$this->assertNull($container->test3);
$this->assertNull($container->test4);
$this->assertNull($container->foo);
// isset test
$this->assertTrue(isset($container->test1));
$this->assertFalse(isset($container->test2));
$this->assertFalse(isset($container->test3));
$this->assertFalse(isset($container->test4));
$this->assertFalse(isset($container->foo));
// custom getter
$container->test6 = 'foo';
$this->assertSame(sha1('foo'), $container->test6);
// nullable/isset test
$container->test6 = null;
$this->assertFalse(isset($container->test6));
$this->assertSame('null', $container->test6);
}
public function testSet(){
$container = new TestContainer;
$container->test1 = 'bar';
$container->test2 = 'what';
$container->test3 = 'nope';
$this->assertSame('bar', $container->test1);
$this->assertSame('what', $container->test2);
$this->assertNull($container->test3);
// unset
unset($container->test1);
$this->assertFalse(isset($container->test1));
// custom setter
$container->test5 = 'bar';
$this->assertSame('bar_test5', $container->test5);
}
public function testToArray(){
$container = new TestContainer(['test1' => 'no', 'test2' => true, 'testConstruct' => 'success']);
$this->assertSame(['test1' => 'no', 'test2' => true, 'testConstruct' => 'success', 'test4' => null, 'test5' => null, 'test6' => null], $container->toArray());
}
public function testToJSON(){
$container = (new TestContainer)->fromJSON('{"test1":"no","test2":true,"testConstruct":"success"}');
$expected = '{"test1":"no","test2":true,"testConstruct":"success","test4":null,"test5":null,"test6":null}';
$this->assertSame($expected, $container->toJSON());
$this->assertSame($expected, (string)$container);
}
public function testFromJsonException(){
$this->expectException(Exception::class);
(new TestContainer)->fromJSON('-');
}
public function testFromJsonTypeError(){
$this->expectException(TypeError::class);
(new TestContainer)->fromJSON('2');
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* Class TestContainer
*
* @filesource TestContainer.php
* @created 28.08.2018
* @package chillerlan\SettingsTest
* @author Smiley <smiley@chillerlan.net>
* @copyright 2018 Smiley
* @license MIT
*/
namespace chillerlan\SettingsTest;
use chillerlan\Settings\SettingsContainerAbstract;
/**
* @property $test1
* @property $test2
* @property $test3
* @property $test4
* @property $test5
* @property $test6
*/
class TestContainer extends SettingsContainerAbstract{
use TestOptionsTrait;
private $test3 = 'what';
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* Trait TestOptionsTrait
*
* @filesource TestOptionsTrait.php
* @created 28.08.2018
* @package chillerlan\SettingsTest
* @author smiley <smiley@chillerlan.net>
* @copyright 2018 smiley
* @license MIT
*/
namespace chillerlan\SettingsTest;
trait TestOptionsTrait{
protected $test1 = 'foo';
protected $test2;
protected $testConstruct;
protected $test4;
protected $test5;
protected $test6;
protected function TestOptionsTrait(){
$this->testConstruct = 'success';
}
protected function set_test5($value){
$this->test5 = $value.'_test5';
}
protected function get_test6(){
return $this->test6 === null
? 'null'
: sha1($this->test6);
}
}