Commit 97a59465 by yeran

master

0 parents
Showing with 4799 additions and 0 deletions
/.idea/
/vendor/
/ignore/
/tests/resource/app/config/testing/
/tests/root/
/composer.lock
default:
rules:
cyclomaticComplexity: [ 10, 6, 2 ]
maintainabilityIndex: [ 0, 75, 95 ]
failure: average.maintainabilityIndex < 50 or sum.loc > 10000
path:
directory: src
extensions: php
exclude: features|tests
filter:
excluded_paths:
- 'tests/*'
- 'bin/*'
- '*.min.js'
- 'src/Cli/Output/Stream.php'
- 'src/Protocol/StreamWrapperTrait.php'
- 'phwoolcon-package/assets/phwoolcon/simpleStorage-0.2.1.js'
- 'phwoolcon-package/assets/phwoolcon/js-cookie-2.1.4.js'
checks:
php:
remove_extra_empty_lines: true
remove_php_closing_tag: true
remove_trailing_whitespace: true
fix_use_statements:
remove_unused: true
preserve_multiple: false
preserve_blanklines: true
# order_alphabetically: true
fix_php_opening_tag: true
fix_linefeed: true
fix_line_ending: true
fix_identation_4spaces: true
fix_doc_comments: true
build: false
tools:
external_code_coverage:
timeout: 600
runs: 1
language: php
sudo: required
services:
- memcached
- redis-server
- mysql
php:
- '5.5'
- '5.6'
- '7.0'
- '7.1'
- '7.2'
matrix:
fast_finish: true
allow_failures:
- php: '7.2'
mysql:
database: phwoolcon_test
username: travis
encoding: utf8mb4
cache:
directories:
- vendor
- $HOME/.composer/cache
- $HOME/cphalcon
- $HOME/pecl_cache
before_install:
- sudo apt-get update -qq
# Install Beanstalkd (Queue support) and Postfix (SMTP mailer support)
- sudo apt-get install -qq beanstalkd postfix
# Use test mail server
- sudo service postfix stop
- smtp-sink -d "%d.%H.%M.%S" localhost:2500 1000 &
- sudo service beanstalkd restart
- pecl channel-update pecl.php.net
- phpenv config-add tests/travis/php-ext.ini
# Install Extensions
- bash bin/ci-install-extensions
- composer install --prefer-source --no-interaction
before_script:
# Create database
- mysql -uroot -e 'CREATE DATABASE `phwoolcon_test` /*!40100 DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci */;'
- mysql -uroot -e "GRANT ALL PRIVILEGES ON *.* TO 'travis'@'%';";
# Print Phalcon and Swoole info
- php --ri phalcon
- php --ri swoole
script:
# Check code style
- vendor/bin/phpcs
# Running unit test
- vendor/bin/phpunit --coverage-clover=coverage.clover
after_success:
# Collect code coverage
# If need cache, check ETAG
# ETAG=( $(curl -IL https://scrutinizer-ci.com/ocular.phar 2>/dev/null | grep -i etag) ); echo ${ETAG[1]}
- |
if [[ "$TRAVIS_PHP_VERSION" == '7.1' ]]; then
wget https://scrutinizer-ci.com/ocular.phar
php ocular.phar code-coverage:upload --format=php-clover coverage.clover
fi
branches:
#Don't build tags
except:
- /^v\d/
# Phwoolcon Change Logs
## [v2.0]() (2020-xx-xx)
#### Features:
*
#### Tests:
*
#### Bug Fixes:
*
This diff is collapsed. Click to expand it.
# Phwoolcon
Phalcon + Swoole
***
本项目的目的是创建一个高性能的 Web 应用程序,既可以运行于传统的 php-fpm
模式下,也可以运行在服务模式下。
在服务模式中,你的应用程序可以减少许多非必要的重复计算,获得极致的性能。
如果在服务模式中出现了 Bug,你可以轻松地关闭服务模式,损失一些性能(但是
仍然很快)换取稳定性,待 Bug 修复后再启用服务模式。
# 使用
## 安装
这是 Phwoolcon 库。
你也可以用 composer 把 Phwoolcon 库加入到你的项目中:
```
composer require phwoolcon/phwoolcon
```
## 代码风格检查
请运行以下脚本:
```
tests/phpcs
```
警告和错误报告会被保存在这个文件里:
```
tests/root/storage/phpcs.txt
```
## 测试
请运行以下脚本:
```
tests/phpunit
```
代码覆盖率报告会以 HTML 格式被保存在这个文件夹里:
```
tests/root/storage/coverage/
```
用浏览器打开 `index.html` 即可阅读报告。
## 配置
请见 [phwoolcon-package/config/](phwoolcon-package/config/)
## 模板
See [phwoolcon-package/views/](phwoolcon-package/views/)
## 静态资源
See [phwoolcon-package/assets/](phwoolcon-package/assets/)
## 翻译/语言
See [phwoolcon-package/locale/](phwoolcon-package/locale/)
## 依赖注入
See [phwoolcon-package/di.php](phwoolcon-package/di.php)
# 主旨
* 关注性能
* 关注可伸缩性
* 提供强大的功能,但是保持直观易读的代码
* 基于组件,显式引入
* 功能可配置
* 代码可测试性
* 规范的代码风格(基于 [PSR-2](http://www.php-fig.org/psr/psr-2/)
# 功能
## 基础组件
* Extended Phalcon Config (Both in native PHP file and DB)
* Phalcon Cache
* Extended Phalcon ORM
* Error Codes
* View: Theme based layouts and templates
* Multiple DB connector
* Events
* Configurable Cookies
* Session
* Openssl based encryption/decryption
* Multiple Queue producer and asynchronous CLI worker
* Assets: Theme based, compilable JS/CSS management
* Log
* Lighten route dispatcher
* Internalization
* Finite state machine
* Simple HTTP client
* Swift Mailer
* Symfony CLI console
# 文档
* [API 参考文档](docs/ApiIndex.md)
# Phwoolcon
Phalcon + Swoole
***
[中文 Readme](README-zh.md)
[Why Do I Start Phwoolcon Project](https://github.com/phwoolcon/phwoolcon/wiki/%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E5%BC%80%E5%8F%91-Phwoolcon)
The purpose of this library is to create a high performance
web application, which can run in traditional php-fpm mode and
service mode.
In service mode, you gain extreme speed for your application,
by reducing lot of unnecessary and repetitive computing.
If you have bugs in service mode, you can easily turn off the service
mode, you loose some speed (but still fast) to gain more stability,
fix your bugs and apply service mode again.
# Usage
## Installation
This is the Phwoolcon library.
to start a new project.
Or add this library to your project by composer:
```
composer require phwoolcon/phwoolcon
```
## Code Style Checking
Please run the following script:
```
tests/phpcs
```
Any warnings or errors will be reported in file:
```
tests/root/storage/phpcs.txt
```
## Testing
Please run the following script:
```
tests/phpunit
```
The code coverage report in HTML format will be generated in folder:
```
tests/root/storage/coverage/
```
To read the report, please open `index.html` in a web browser.
## Configuration
See [phwoolcon-package/config/](phwoolcon-package/config/)
## Templates
See [phwoolcon-package/views/](phwoolcon-package/views/)
## Assets
See [phwoolcon-package/assets/](phwoolcon-package/assets/)
## Locale
See [phwoolcon-package/locale/](phwoolcon-package/locale/)
## Dependency Injection
See [phwoolcon-package/di.php](phwoolcon-package/di.php)
# Spirits
* Aimed at performance
* Aimed at scalability
* Powerful features, with intuitive and readable codes
* Component based, explicitly introduced
* Configurable features
* Code testability
* Follow standard coding style (based on [PSR-2](http://www.php-fig.org/psr/psr-2/))
# Features
## Base Components
* Extended Phalcon Config (Both in native PHP file and DB)
* Phalcon Cache
* Extended Phalcon ORM
* Error Codes
* View: Theme based layouts and templates
* Multiple DB connector
* Events
* Configurable Cookies
* Session
* Openssl based encryption/decryption
* Multiple Queue producer and asynchronous CLI worker
* Assets: Theme based, compilable JS/CSS management
* Log
* Lighten route dispatcher
* Internalization
* Finite state machine
* Simple HTTP client
* Swift Mailer
* Symfony CLI console
# Documents
* [API Reference](docs/ApiIndex.md)
#!/usr/bin/env bash
mkdir tmp-ci-install-extensions
cd tmp-ci-install-extensions
echo $(date +%H:%M:%S) Prepare installers...
echo '{"require":{"techpivot/phalcon-ci-installer":"~1.0","phwoolcon/ci-pecl-cacher":"~1.0"}}' > composer.json
composer install --prefer-source --no-interaction
echo $(date +%H:%M:%S) Installers ready
# Install imagick to pass Phalcon compiling
vendor/bin/ci-pecl-install imagick skip-update
# Install Swoole
vendor/bin/ci-pecl-install swoole
# Install Phalcon
vendor/bin/install-phalcon.sh
cd ..
rm -rf tmp-ci-install-extensions
#!/usr/bin/env bash
cd "$(dirname "$( dirname "${BASH_SOURCE[0]}" )")"
if [ ! -d "ignore/phpdoc" ]; then
mkdir -p ignore/phpdoc
fi
cd ignore/phpdoc
if [ ! -d "vendor" ]; then
composer require phwoolcon/phpdoc-markdown-public
else
file_m_time=`stat -c %Y composer.lock`
current_time=`date +%s`
diff=$(( current_time - file_m_time ))
if [ ${diff} -gt 86400 ]; then
composer update
touch composer.lock
fi
fi
cd ../..
rm -rf docs && ignore/phpdoc/vendor/bin/phpdoc
#!/usr/bin/env bash
cd "$(dirname "$( dirname "${BASH_SOURCE[0]}" )")"
echo Phwoolcon Skeleton Packager
echo
echo Working directory: `pwd`
echo
# Run composer update
composer update phwoolcon/skeleton
echo Copying skeleton from composer...
rm -rf ignore/skeleton
cp -r vendor/phwoolcon/skeleton/ ignore/skeleton
echo Creating tar package...
pushd ignore > /dev/null
rm -rf skeleton/.git
mkdir -p skeleton/phwoolcon-package/assets skeleton/phwoolcon-package/config skeleton/phwoolcon-package/views
tar --owner=0 --group=0 -cf ../src/Cli/Command/Package/skeleton.tar skeleton
popd > /dev/null
ls -lah --color src/Cli/Command/Package/skeleton.tar
echo
echo Done
{
"name": "phwoolcon/phwoolcon",
"description": "Phalcon + Swoole",
"keywords": ["phwoolcon", "phalcon", "swoole"],
"type": "library",
"license": "Apache-2.0",
"authors": [
{
"name": "Christopher CHEN",
"email": "fishdrowned@gmail.com"
}
],
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"php": ">=5.5.0",
"ext-curl": "*",
"ext-json": "*",
"ext-openssl": "*",
"ext-PDO": "*",
"ext-mbstring": "*",
"ext-phalcon": "~3.0",
"phwoolcon/fsm": "~1.0",
"phwoolcon/js-polyfills": "~1.0",
"phwoolcon/mail-renderer": "~1.0",
"symfony/console": "~3.0",
"pda/pheanstalk": "~3.1",
"swiftmailer/swiftmailer": "~5.4|~6.0",
"opis/closure": "3.0.1"
},
"require-dev": {
"phwoolcon/test-starter": "~1.0",
"phwoolcon/skeleton": "dev-master"
},
"suggest": {
"ext-swoole": "~1.8|~2.0"
},
"autoload": {
"psr-4": {
"Phwoolcon\\": "src",
"Phwoolcon\\Tests\\": "tests"
},
"exclude-from-classmap": [
"/tests/"
],
"files": ["functions.php"]
},
"bin": [
"bin/update-phwoolcon-package-resource",
"bin/generate-phpdoc"
],
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
}
}
This diff could not be displayed because it is too large.
<?xml version="1.0"?>
<ruleset name="PHALCON-PSR2">
<description>The PSR-2 coding standard compatible with phalcon.</description>
<!-- Include the whole PSR-2 standard -->
<rule ref="PSR2">
<!-- Disable rule: Property name should not be prefixed with an underscore -->
<exclude name="PSR2.Classes.PropertyDeclaration.Underscore"/>
<!-- Disable rule: Method name should not be prefixed with an underscore -->
<exclude name="PSR2.Methods.MethodDeclaration.Underscore"/>
</rule>
<file>src</file>
<file>tests</file>
<exclude-pattern>src/Cli/Output/Stream.php</exclude-pattern>
<exclude-pattern>src/Protocol/StreamWrapperTrait.php</exclude-pattern>
<exclude-pattern>src/Protocol/StreamWrapperInterface.php</exclude-pattern>
<exclude-pattern>*.min.js</exclude-pattern>
<exclude-pattern>*.min.css</exclude-pattern>
<exclude-pattern>tests/root/vendor</exclude-pattern>
<exclude-pattern>tests/root/storage</exclude-pattern>
<arg value="sp"/>
<arg name="colors"/>
<arg name="report-width" value="180"/>
</ruleset>
<?xml version="1.0" encoding="UTF-8" ?>
<phpdoc>
<title>Phwoolcon API Reference</title>
<parser>
<target>ignore/phpdoc/docs-build</target>
<default-package-name>Phwoolcon</default-package-name>
</parser>
<transformer>
<target>docs</target>
</transformer>
<files>
<directory>src</directory>
<file>functions.php</file>
</files>
<transformations>
<template name="ignore/phpdoc/vendor/phwoolcon/phpdoc-markdown-public/data/templates/markdown-public"/>
</transformations>
</phpdoc>
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/phwoolcon/test-starter/start.php"
backupGlobals="false"
backupStaticAttributes="false"
verbose="true"
colors="true"
stderr="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="true"
>
<php>
<ini name="intl.default_locale" value="en"/>
<ini name="intl.error_level" value="0"/>
<ini name="memory_limit" value="-1"/>
</php>
<testsuites>
<testsuite name="Phwoolcon - Unit Tests">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Phwoolcon - Integration Tests">
<directory>tests/Integration</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
<file>functions.php</file>
<exclude>
<file>src/Cli/Command/Migrate/template.php</file>
<file>src/Protocol/StreamWrapperTrait.php</file>
<file>src/Cli/Output/Stream.php</file>
<file>src/Cli/Command/ClearCacheCommand.php</file>
<file>src/Cli/Command/PhpunitPickPackageCommand.php</file>
<!-- TODO Implement service tests -->
<file>src/Cli/Command/ServiceCommand.php</file>
</exclude>
</whitelist>
</filter>
<logging>
<log type="coverage-html" target="tests/root/storage/coverage" charset="UTF-8" yui="true" highlight="true"/>
</logging>
</phpunit>
<?php
namespace Phwoolcon;
use Phalcon\Di;
class Aliases
{
public static function register(Di $di)
{
if ($aliases = Config::get('app.class_aliases')) {
foreach ($aliases as $alias => $class) {
class_exists($alias, false) or class_alias($class, $alias);
}
}
}
}
<?php
namespace Phwoolcon\Assets\Resource;
use Phalcon\Assets\Resource\Css as PhalconCss;
use Phwoolcon\Assets\ResourceTrait;
class Css extends PhalconCss
{
use ResourceTrait;
}
<?php
namespace Phwoolcon\Assets\Resource;
use Phalcon\Assets\Resource\Js as PhalconJs;
use Phwoolcon\Assets\ResourceTrait;
class Js extends PhalconJs
{
use ResourceTrait;
}
<?php
namespace Phwoolcon\Assets;
use Exception;
use Phwoolcon\Log;
/**
* Class ResourceTrait
* @package Phwoolcon\Assets
*
* @property $_local
* @property $_path
*/
trait ResourceTrait
{
public static $basePath;
public static $runningUnitTest = false;
protected $content;
/**
* @param string $previousHash
* @return string
*/
public function concatenateHash($previousHash)
{
$hash = dechex(crc32($previousHash . $this->getContent(ResourceTrait::$basePath)));
if (ResourceTrait::$runningUnitTest) {
Log::debug(sprintf('Asset hash: [%s]', $hash));
}
return $hash;
}
public function getContent($basePath = null)
{
if ($this->content === null) {
if (ResourceTrait::$runningUnitTest) {
Log::debug(sprintf('Asset path: [%s]', $this->_path));
}
try {
if ($this->_local) {
$this->content = parent::getContent($basePath);
} else {
$path = $this->_path;
substr($this->_path, 0, 2) == '//' and $path = 'http:' . $path;
$this->content = file_get_contents($path, false, stream_context_create([
'http' => [
'method' => 'GET',
'timeout' => 1,
],
]));
}
} catch (Exception $e) {
Log::exception($e);
$this->content = '';
}
}
return $this->content;
}
public static function setBasePath($path)
{
ResourceTrait::$basePath = $path;
}
/**
* @param boolean $flag
*/
public static function setRunningUnitTests($flag)
{
ResourceTrait::$runningUnitTest = $flag;
}
}
<?php
namespace Phwoolcon;
use Phalcon\Di;
use Phalcon\Cache\Frontend\Data;
use Phalcon\Cache\Backend;
use Phwoolcon\Cache\Backend\Redis;
use Phwoolcon\Exception\InvalidConfigException;
/**
* Class Cache
* @package Phwoolcon
*
* @method static int decrement(string $keyName = null, int $value = null)
* @uses Redis::decrement()
* @method static bool exists(string $keyName = null)
* @uses Redis::exists()
* @method static int increment(string $keyName = null, int $value = null)
* @uses Redis::increment()
* @method static array queryKeys(string $prefix = null)
* @uses Redis::queryKeys()
*/
class Cache
{
const TTL_ONE_MINUTE = 60;
const TTL_TEN_MINUTES = 600;
const TTL_ONE_HOUR = 3600;
const TTL_ONE_DAY = 86400;
const TTL_ONE_WEEK = 604800;
const TTL_ONE_MONTH = 2592000;
/**
* @var Backend|Backend\File|Redis
*/
protected static $cache;
/**
* @var Di
*/
protected static $di;
public static function __callStatic($name, $arguments)
{
static::$cache === null and static::$cache = static::$di->getShared('cache');
return call_user_func_array([static::$cache, $name], $arguments);
}
/**
* @param string $key
* @return bool
*/
public static function delete($key)
{
static::$cache === null and static::$cache = static::$di->getShared('cache');
return static::$cache->delete($key);
}
/**
* @return bool
*/
public static function flush()
{
static::$cache === null and static::$cache = static::$di->getShared('cache');
return static::$cache->flush();
}
/**
* @param string $key
* @return mixed
*/
public static function get($key, $default = null)
{
static::$cache === null and static::$cache = static::$di->getShared('cache');
return (null === $value = static::$cache->get($key)) ? $default : $value;
}
public static function register(Di $di)
{
static::$di = $di;
$di->remove('cache');
static::$cache = null;
$di->setShared('cache', function () {
$frontend = new Data(['lifetime' => static::TTL_ONE_DAY]);
$default = Config::get('cache.default');
$config = Config::get('cache.drivers.' . $default);
$class = $config['adapter'];
$options = $config['options'];
// @codeCoverageIgnoreStart
if (!$class || !class_exists($class)) {
throw new InvalidConfigException("Invalid cache adapter {$class}, please check config file cache.php");
}
// @codeCoverageIgnoreEnd
isset($options['cacheDir']) and $options['cacheDir'] = storagePath($options['cacheDir']) . '/';
/* @var Backend $backend */
$backend = new $class($frontend, $options);
// @codeCoverageIgnoreStart
if (!$backend instanceof Backend) {
throw new InvalidConfigException("Cache adapter {$class} should extend " . Backend::class);
}
// @codeCoverageIgnoreEnd
return $backend;
});
}
/**
* @param string $key
*/
public static function set($key, $value, $ttl = null)
{
static::$cache === null and static::$cache = static::$di->getShared('cache');
static::$cache->save($key, $value, $ttl);
}
}
<?php
namespace Phwoolcon\Cache\Backend;
use Phalcon\Cache\Backend\Redis as PhalconRedis;
use Phalcon\Cache\Exception;
use Phalcon\Cache\FrontendInterface;
/**
* Class Redis
*
* @package Phwoolcon\Cache\Backend\Adapter
*
* @property FrontendInterface $_frontend
* @property \Redis $_redis
*/
class Redis extends PhalconRedis
{
protected $compressThreshold = 2048;
public function _connect()
{
$options = $this->_options;
$redis = new \Redis;
// @codeCoverageIgnoreStart
if (empty($options['host']) ||
empty($options['port']) ||
empty($options['persistent']) ||
empty($options['index'])
) {
throw new Exception('Unexpected inconsistency in options');
}
isset($options['compress_threshold']) and $this->compressThreshold = $options['compress_threshold'];
// @codeCoverageIgnoreEnd
$host = $options['host'];
$port = $options['port'];
$index = $options['index'];
if ($options['persistent']) {
$success = $redis->pconnect($host, $port, 0, $index);
} else // @codeCoverageIgnoreStart
{
$success = $redis->connect($host, $port);
}
// @codeCoverageIgnoreEnd
// @codeCoverageIgnoreStart
if (!$success) {
throw new Exception('Could not connect to the Redis server ' . $host . ':' . $port);
}
if (!empty($options['auth'])) {
$success = $redis->auth($options['auth']);
if (!$success) {
throw new Exception('Failed to authenticate with the Redis server');
}
}
// @codeCoverageIgnoreEnd
if ($index) {
$success = $redis->select($index);
// @codeCoverageIgnoreStart
if (!$success) {
throw new Exception('Redis server selected database failed');
}
// @codeCoverageIgnoreEnd
}
$this->_redis = $redis;
}
protected function afterRetrieve($content)
{
if (is_numeric($content)) {
return $content;
}
if (isset($content{1}) && $content{0} == 'g') {
// gb: binary gzip - since 17.7.5
if ($content{1} == 'b') {
$content = gzinflate(substr($content, 2));
} // @codeCoverageIgnoreStart
// gz: base64 encoded gzip - since 16.12.2
elseif ($content{1} == 'z') {
$content = gzinflate(base64_decode(substr($content, 2)));
}
// @codeCoverageIgnoreEnd
}
return $this->_frontend->afterRetrieve($content);
}
protected function beforeStore($content)
{
if (is_numeric($content)) {
return $content;
}
$content = $this->_frontend->beforeStore($content);
// Compress big content
if (strlen($content) > $this->compressThreshold) {
// gz: base64 encoded gzip - since 16.12.2
// $content = 'gz' . base64_encode(gzdeflate($content, 9));
// gb: binary gzip - since 17.7.5
$content = 'gb' . gzdeflate($content, 9);
}
return $content;
}
public function decrement($keyName = null, $value = null)
{
// @codeCoverageIgnoreStart
if ($keyName === null) {
$lastKey = $this->_lastKey;
} // @codeCoverageIgnoreEnd
else {
$lastKey = $this->_lastKey = $this->_prefix . $keyName;
}
// @codeCoverageIgnoreStart
if (!$redis = $this->_redis) {
$this->_connect();
$redis = $this->_redis;
}
// @codeCoverageIgnoreEnd
if (!$value) {
return $redis->decr($lastKey);
}
return $redis->decrBy($lastKey, $value);
}
public function delete($keyName)
{
// @codeCoverageIgnoreStart
if (!$redis = $this->_redis) {
$this->_connect();
$redis = $this->_redis;
}
// @codeCoverageIgnoreEnd
$lastKey = $this->_prefix . $keyName;
return (bool)$redis->del($lastKey);
}
public function exists($keyName = null, $lifetime = null)
{
// @codeCoverageIgnoreStart
if ($keyName === null) {
$lastKey = $this->_lastKey;
} // @codeCoverageIgnoreEnd
else {
$lastKey = $this->_prefix . $keyName;
}
if ($lastKey) {
// @codeCoverageIgnoreStart
if (!$redis = $this->_redis) {
$this->_connect();
$redis = $this->_redis;
}
// @codeCoverageIgnoreEnd
return $redis->exists($lastKey);
}
// @codeCoverageIgnoreStart
return false;
// @codeCoverageIgnoreEnd
}
public function flush()
{
// @codeCoverageIgnoreStart
if (!$redis = $this->_redis) {
$this->_connect();
$redis = $this->_redis;
}
// @codeCoverageIgnoreEnd
$redis->flushDB();
return true;
}
public function get($keyName, $lifetime = null)
{
// @codeCoverageIgnoreStart
if (!$redis = $this->_redis) {
$this->_connect();
$redis = $this->_redis;
}
// @codeCoverageIgnoreEnd
$lastKey = $this->_lastKey = $this->_prefix . $keyName;
$content = $redis->get($lastKey);
if ($content === false) {
return null;
}
return $this->afterRetrieve($content);
}
/**
* @return \Redis
* @codeCoverageIgnore
*/
public function getRedis()
{
return $this->_redis;
}
public function increment($keyName = null, $value = null)
{
// @codeCoverageIgnoreStart
if ($keyName === null) {
$lastKey = $this->_lastKey;
} // @codeCoverageIgnoreEnd
else {
$lastKey = $this->_lastKey = $this->_prefix . $keyName;
}
// @codeCoverageIgnoreStart
if (!$redis = $this->_redis) {
$this->_connect();
$redis = $this->_redis;
}
// @codeCoverageIgnoreEnd
if (!$value) {
return $redis->incr($lastKey);
}
return $redis->incrBy($lastKey, $value);
}
public function queryKeys($prefix = null)
{
// @codeCoverageIgnoreStart
if (!$redis = $this->_redis) {
$this->_connect();
$redis = $this->_redis;
}
// @codeCoverageIgnoreEnd
return $redis->keys($this->_prefix . $prefix . '*') ?: [];
}
public function save($keyName = null, $content = null, $lifetime = null, $stopBuffer = true)
{
// @codeCoverageIgnoreStart
if ($keyName === null) {
$lastKey = $this->_lastKey;
} // @codeCoverageIgnoreEnd
else {
$lastKey = $this->_lastKey = $this->_prefix . $keyName;
}
// @codeCoverageIgnoreStart
if (!$lastKey) {
throw new Exception('The cache must be started first');
}
// @codeCoverageIgnoreEnd
$frontend = $this->_frontend;
// @codeCoverageIgnoreStart
if (!$redis = $this->_redis) {
$this->_connect();
$redis = $this->_redis;
}
// @codeCoverageIgnoreEnd
$cachedContent = $content === null ? $frontend->getContent() : $content;
$preparedContent = $this->beforeStore($cachedContent);
if ($lifetime === null) {
$ttl = $this->_lastLifetime ?: $frontend->getLifetime();
} else {
$ttl = $lifetime;
}
$success = $redis->set($lastKey, $preparedContent, $ttl);
// @codeCoverageIgnoreStart
if (!$success) {
throw new Exception('Failed storing the data in redis');
}
// @codeCoverageIgnoreEnd
if ($stopBuffer === true) {
$frontend->stop();
}
// @codeCoverageIgnoreStart
if ($frontend->isBuffering()) {
echo $cachedContent;
}
// @codeCoverageIgnoreEnd
$this->_started = false;
return $success;
}
}
<?php
namespace Phwoolcon\Cache;
use ArrayIterator;
use Phwoolcon\Cache;
use Phwoolcon\Config;
use Phwoolcon\Db;
use Phwoolcon\Events;
use Phwoolcon\I18n;
use Phwoolcon\Router;
use Phwoolcon\View;
class Clearer
{
const TYPE_ASSETS = 'assets';
const TYPE_CONFIG = 'config';
const TYPE_LOCALE = 'locale';
const TYPE_META = 'meta';
const TYPE_ROUTES = 'routes';
const TYPE_LOADER = 'sloader';
const TYPE_DATABASE = 'db';
const TYPE_VOLT = 'pvolt';
protected static $types = [
self::TYPE_CONFIG => 'Clear config cache',
self::TYPE_META => 'Clear model metadata',
self::TYPE_LOCALE => 'Clear locale cache',
self::TYPE_ASSETS => 'Clear assets cache',
self::TYPE_ROUTES => 'Clear routes cache',
self::TYPE_LOADER => 'Clear loader cache',
self::TYPE_DATABASE => 'Clear database cache',
self::TYPE_VOLT => 'Clear volt cache',
];
public static function clear($types = null)
{
$types = array_flip((array)$types);
$clearAll = true;
$messages = new ArrayIterator();
// @codeCoverageIgnoreStart
if (isset($types['config'])) {
$clearAll = false;
Config::clearCache();
$messages[] = 'Config cache cleared.';
}
// @codeCoverageIgnoreEnd
if (isset($types['meta'])) {
$clearAll = false;
Db::clearMetadata();
$messages[] = 'Model metadata cleared.';
}
if (isset($types['locale'])) {
$clearAll = false;
I18n::clearCache();
$messages[] = 'Locale cache cleared.';
}
if (isset($types['assets'])) {
$clearAll = false;
View::clearAssetsCache();
$messages[] = 'Assets cache cleared.';
}
if (isset($types['routes'])) {
$clearAll = false;
Router::clearCache();
$messages[] = 'Routes cache cleared.';
}
if (isset($types['sloader'])) {
$clearAll = false;
self::clearLoaderCache();
$messages[] = 'Loader cache cleared.';
}
if (isset($types['db'])) {
$clearAll = false;
self::clearDatabaseCache();
$messages[] = 'Database cache cleared.';
}
if (isset($types['pvolt'])) {
$clearAll = false;
self::clearVoltCache();
$messages[] = 'Volt cache cleared.';
}
if ($clearAll) {
Cache::flush();
Config::clearCache();
Router::clearCache();
self::clearLoaderCache();
self::clearDatabaseCache();
self::clearVoltCache();
$messages[] = 'Cache cleared.';
}
Events::fire('cache:after_clear', $messages, $messages);
return $messages;
}
/**
* @return array
* @codeCoverageIgnore
*/
public static function getTypes()
{
return self::$types;
}
/**
* @param array $types
* @codeCoverageIgnore
*/
public static function setTypes($types)
{
self::$types = $types;
}
/**
* 清除加载缓存
* @author wangyu <wangyu@ledouya.com>
* @createTime 2018/5/3 14:58
*/
public static function clearLoaderCache()
{
if ($files = glob(storagePath('cache') . '/loader*')) {
foreach ($files as $file) {
unlink($file);
}
}
}
/**
* 清除数据库缓存
* @author wangyu <wangyu@ledouya.com>
* @createTime 2018/5/3 14:58
*/
public static function clearDatabaseCache()
{
if ($files = glob(storagePath('db') . '/*')) {
foreach ($files as $file) {
unlink($file);
}
}
}
/**
* 清除视图编译文件
* @author wangyu <wangyu@ledouya.com>
* @createTime 2018/5/5 18:06
*/
public static function clearVoltCache()
{
if ($files = glob(storagePath('volt') . '/*')) {
foreach ($files as $file) {
unlink($file);
}
}
}
}
<?php
namespace Phwoolcon;
use Phalcon\Di;
use Symfony\Component\Console\Application;
class Cli
{
protected static $consoleWidth;
public static function register(Di $di)
{
$app = new Application(Config::get('app.name'), Config::get('app.version'));
foreach (Config::get('commands') as $name => $class) {
// @codeCoverageIgnoreStart
if (!class_exists($class)) {
fwrite(STDERR, "[Warning] commands config: Class {$class} not found for {$name}" . PHP_EOL);
continue;
}
// @codeCoverageIgnoreEnd
$app->add(new $class($name, $di));
}
$_SERVER['SCRIPT_NAME'] = '/index.php';
return $app;
}
public static function getConsoleWidth()
{
if (!static::$consoleWidth) {
static::$consoleWidth = (int)`tput cols`;
static::$consoleWidth > 30 or static::$consoleWidth = 80;
}
return static::$consoleWidth;
}
}
<?php
namespace Phwoolcon\Cli;
use LogicException;
use Phalcon\Di;
use Phwoolcon\Cli;
use Symfony\Component\Console\Command\Command as SymfonyCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
abstract class Command extends SymfonyCommand
{
protected $di;
/**
* @var \Symfony\Component\Console\Input\ArgvInput
*/
protected $input;
/**
* @var \Symfony\Component\Console\Output\ConsoleOutput
*/
protected $output;
/**
* @var SymfonyStyle
*/
protected $interactive;
protected $statusCode = 0;
protected $outputTimestamp = false;
public function __construct($name, Di $di)
{
$this->di = $di;
empty($_SERVER['PHWOOLCON_CLI_OUTPUT_TIMESTAMP']) or $this->outputTimestamp = true;
parent::__construct($name);
}
/**
* @codeCoverageIgnore
* @param string $question
* @param mixed $default
* @return string
*/
public function ask($question, $default = null)
{
return $this->interactive->ask($question, $default);
}
/**
* @codeCoverageIgnore
* @param string $question
* @param bool $default
* @return string
*/
public function confirm($question, $default = true)
{
return $this->interactive->confirm($question, $default);
}
public function createProgressBar($max = 0)
{
$progress = $this->interactive->createProgressBar($max);
$progress->setBarWidth(Cli::getConsoleWidth() - 12 - strlen($max) * 2);
return $progress;
}
public function execute(InputInterface $input, OutputInterface $output)
{
$this->input = $input;
$this->output = $output;
$this->interactive = new SymfonyStyle($input, $output);
$this->fire();
return $this->statusCode;
}
/**
* @codeCoverageIgnore
*/
public function fire()
{
throw new LogicException('You must override the fire() method in the concrete command class.');
}
/**
* @return Di
* @codeCoverageIgnore
*/
public function getDi()
{
return $this->di;
}
/**
* @param Di $di
* @return $this
* @codeCoverageIgnore
*/
public function setDi($di)
{
$this->di = $di;
return $this;
}
public function comment($message)
{
$this->writeln("<comment>{$message}</comment>");
}
/**
* @codeCoverageIgnore
* @param string $message
*/
public function debug($message)
{
$this->output->isDebug() and $this->comment($message);
}
/**
* @codeCoverageIgnore
* @param string $message
* @param int $statusCode
*/
public function error($message, $statusCode = 1)
{
$this->outputTimestamp and $message = $this->timestampMessage($message);
$output = $this->output instanceof ConsoleOutputInterface ? $this->output->getErrorOutput() : $this->output;
$output->writeln("<error>{$message}</error>");
$this->statusCode = $statusCode;
}
public function info($message)
{
$this->writeln("<info>{$message}</info>");
}
public function question($message)
{
$this->writeln("<question>{$message}</question>");
}
/**
* @codeCoverageIgnore
* @param string $message
*/
public function verbose($message)
{
$this->output->isVerbose() and $this->info($message);
}
/**
* @codeCoverageIgnore
* @param string $message
*/
public function veryVerbose($message)
{
$this->output->isVeryVerbose() and $this->info($message);
}
public function writeln($message)
{
$this->outputTimestamp and $message = $this->timestampMessage($message);
$this->output->writeln($message);
}
protected function timestampMessage($message)
{
return date('[Y-m-d H:i:s] ') . $message;
}
/**
* @codeCoverageIgnore
* @param $title
*/
protected function setCliTitle($title)
{
if (function_exists('cli_set_process_title')) {
cli_set_process_title($title);
}
}
}
<?php
namespace Phwoolcon\Cli\Command;
use Phwoolcon\Cache\Clearer;
use Phwoolcon\Cli\Command;
use Symfony\Component\Console\Input\InputOption;
class ClearCacheCommand extends Command
{
protected function configure()
{
$definition = [];
$shotCuts = [];
foreach (Clearer::getTypes() as $type => $label) {
$shotCut = isset($shotCuts[$type{0}]) ? null : $type{0};
$shotCuts[$shotCut] = true;
$definition[] = new InputOption($type, $shotCut, InputOption::VALUE_NONE, $label);
}
$this->setDefinition($definition)->setDescription('Clears cache');
}
public function fire()
{
$types = array_keys(array_filter($this->input->getOptions()));
foreach (Clearer::clear($types) as $message) {
$this->info($message);
}
}
}
<?php
namespace Phwoolcon\Cli\Command;
use Exception;
use Phalcon\Db\Column;
use Phalcon\Di;
use Phwoolcon\Cli\Command;
use Phwoolcon\Config;
use Phwoolcon\Db;
use Phwoolcon\Log;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Migrate extends Command
{
/**
* @var \Phalcon\Db\Adapter\Pdo|\Phwoolcon\Db\Adapter\Pdo\Mysql
*/
protected $db;
protected $table = 'migrations';
protected $sql = [];
protected $migrated = [];
protected $rawMigrated = [];
protected function checkMigrationsTable()
{
$db = $this->db;
$db->tableExists($this->table) or $db->createTable($this->table, null, [
'columns' => [
new Column('file', [
'type' => Column::TYPE_VARCHAR,
'size' => 255,
'notNull' => true,
'primary' => true,
]),
new Column('run_at', [
'type' => Column::TYPE_TIMESTAMP,
'notNull' => true,
'default' => 'CURRENT_TIMESTAMP',
]),
],
'options' => [
'TABLE_COLLATION' => 'utf8_unicode_ci',
],
]);
return $this;
}
public function cleanMigrationsTable()
{
// @codeCoverageIgnoreStart
if (!Config::runningUnitTest()) {
return;
}
// @codeCoverageIgnoreEnd
$this->db = Db::connection();
$this->db->dropTable($this->table);
$this->checkMigrationsTable();
}
protected function configure()
{
$this->setDescription('Run migration scripts.')
->setAliases(['migrate:up']);
}
public function execute(InputInterface $input, OutputInterface $output)
{
$this->db = Db::connection();
parent::execute($input, $output);
}
public function fire()
{
$this->checkMigrationsTable();
$this->runMigration();
}
/**
* @codeCoverageIgnore
*/
public function getDefaultTableCharset()
{
return Db::getDefaultTableCharset();
}
protected function loadMigrated()
{
$db = $this->db;
isset($this->sql[$sqlKey = 'load_migrated']) or $this->sql[$sqlKey] = strtr('SELECT * FROM `table`', [
'`table`' => $db->escapeIdentifier($this->table),
]);
$this->rawMigrated = $this->db->fetchAll($this->sql[$sqlKey]);
foreach ($this->rawMigrated as $row) {
$this->migrated[$row['file']] = $row['run_at'];
}
return $this;
}
protected function logAndShowInfo($info, $newline = true)
{
Log::info($info);
$newline ? $this->info($info) : $this->output->write("<info>{$info}</info>");
}
protected function migrationExecuted($filename, $flag = null)
{
$db = $this->db;
if ($flag === true) {
$db->insertAsDict($this->table, [
'file' => $filename,
'run_at' => $this->migrated[$filename] = date('Y-m-d H:i:s'),
]);
return true;
}
if ($flag === false) {
$db->delete($this->table, strtr('`file` = ?', ['`file`' => $db->escapeIdentifier('file')]), [$filename]);
unset($this->migrated[$filename]);
return false;
}
$this->migrated or $this->loadMigrated();
return isset($this->migrated[$filename]);
}
protected function runMigration()
{
$db = $this->db;
$migrated = false;
foreach (glob(migrationPath('*.php')) as $file) {
$filename = basename($file);
if ($this->migrationExecuted($filename)) {
continue;
}
$migrated = true;
$this->logAndShowInfo(sprintf('Start migration "%s"...', $filename), false);
$db->begin();
try {
$migration = include $file;
if (isset($migration['up']) && is_callable($migration['up'])) {
call_user_func($migration['up'], $db, $this);
}
$this->migrationExecuted($filename, true);
$db->commit();
} // @codeCoverageIgnoreStart
catch (Exception $e) {
$db->rollback();
Log::exception($e);
$this->error(' [ BAD ] ');
$this->error($e->getMessage());
return;
}
// @codeCoverageIgnoreEnd
$this->logAndShowInfo(' [ OK ] ');
}
if ($migrated) {
Db::clearMetadata();
} // @codeCoverageIgnoreStart
else {
$this->info('Nothing to be migrated.');
}
// @codeCoverageIgnoreEnd
}
public function clearMigratedCache()
{
$this->migrated = [];
$this->rawMigrated = [];
return $this;
}
}
<?php
use Phalcon\Db\Adapter\Pdo as Adapter;
use Phalcon\Db\Column;
use Phalcon\Db\Index;
use Phalcon\Db\Reference;
use Phwoolcon\Cli\Command\Migrate;
return [
'up' => function (Adapter $db, Migrate $migrate) {
},
'down' => function (Adapter $db, Migrate $migrate) {
},
];
<?php
namespace Phwoolcon\Cli\Command;
use Phwoolcon\Cli\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class MigrateCreate extends Command
{
protected function configure()
{
$this->addArgument('name', InputArgument::REQUIRED, 'The name of the migration')
->addOption('choose-target', 'c', InputOption::VALUE_NONE, 'Choose target if multiple')
->setDescription('Create a new migration script.')
->setAliases(['migrate:make']);
}
public function fire()
{
$filename = date('Y-m-d-His-') . $this->input->getArgument('name') . '.php';
$path = migrationPath($filename);
// @codeCoverageIgnoreStart
if (is_file($migrationCandidates = $_SERVER['PHWOOLCON_ROOT_PATH'] . '/vendor/phwoolcon/migrations.php')) {
$candidates = include $migrationCandidates;
$chooseTarget = $this->input->getOption('choose-target');
if (!$chooseTarget &&
isset($candidates['selected']) &&
isset($candidates['candidates'][$candidates['selected']])
) {
$path = $candidates['candidates'][$candidates['selected']] . '/' . $filename;
} elseif (count($candidates['candidates']) == 1) {
$path = reset($candidates['candidates']) . '/' . $filename;
} elseif (count($candidates['candidates']) > 1) {
$targets = array_merge([''], array_keys($candidates['candidates']));
unset($targets[0]);
$choose = $this->interactive->choice('Please choose migration target: ', $targets, reset($targets));
$candidates['selected'] = $choose;
fileSaveArray($migrationCandidates, $candidates);
$path = $candidates['candidates'][$choose] . '/' . $filename;
}
}
// @codeCoverageIgnoreEnd
file_put_contents($path, $this->template());
$this->output->writeln("<info>Created Migration:</info> {$filename}");
}
public function template()
{
return file_get_contents(__DIR__ . '/Migrate/template.php');
}
}
<?php
namespace Phwoolcon\Cli\Command;
use Exception;
use Phwoolcon\Db;
use Phwoolcon\Log;
use Symfony\Component\Console\Input\InputOption;
class MigrateList extends Migrate
{
protected function configure()
{
$this->setDescription('List available migrations.')
->addOption('installed', 'i', InputOption::VALUE_NONE, 'List installed migrations')
->addOption('all', 'a', InputOption::VALUE_NONE, 'List available and installed migrations');
}
public function fire()
{
$this->checkMigrationsTable();
if ($this->input->getOption('installed')) {
$this->listMigrated();
} elseif ($this->input->getOption('all')) {
$this->listMigrated();
$this->listToBeMigrated();
} else {
$this->listToBeMigrated();
}
}
protected function listMigrated()
{
$this->loadMigrated();
if ($this->migrated) {
$this->comment('Following migrations are installed:');
$this->interactive->table(['Script', 'Run at'], $this->rawMigrated);
} else {
$this->info('No migrations installed.');
}
return $this;
}
protected function listToBeMigrated()
{
$found = false;
foreach (glob(migrationPath('*.php')) as $file) {
$filename = basename($file);
if ($this->migrationExecuted($filename)) {
continue;
}
$found or $this->comment('Following migrations are ready for install:');
$found = true;
$this->info($filename);
}
$found or $this->info('Nothing to be migrated.');
return $this;
}
}
<?php
namespace Phwoolcon\Cli\Command;
use Exception;
use Phwoolcon\Config;
use Phwoolcon\Db;
use Phwoolcon\Log;
use Symfony\Component\Console\Input\InputOption;
class MigrateRevert extends Migrate
{
protected function configure()
{
$this->setDescription('Revert last migration.')
->addOption('force', 'f', InputOption::VALUE_NONE, 'Force execution without confirmation')
->setAliases(['migrate:down']);
}
public function fire()
{
$this->checkMigrationsTable();
if ($lastMigration = $this->getLastMigration()) {
$file = $lastMigration['file'];
$runAt = $lastMigration['run_at'];
$this->comment(sprintf(' You are going to revert migration "%s" which was run at %s', $file, $runAt));
if ($this->input->getOption('force') || $this->confirm('please confirm', false)) {
$this->revertMigration($file);
}
} // @codeCoverageIgnoreStart
else {
$this->info('No migrations to be reverted.');
}
// @codeCoverageIgnoreEnd
}
protected function getLastMigration()
{
$db = $this->db;
isset($this->sql[$sqlKey = 'get_last_migration']) or $this->sql[$sqlKey] =
strtr('SELECT * FROM `table` ORDER BY `run_at` DESC LIMIT 1', [
'`table`' => $db->escapeIdentifier($this->table),
'`run_at`' => $db->escapeIdentifier('run_at'),
]);
return $db->fetchOne($this->sql[$sqlKey]);
}
protected function revertMigration($filename)
{
$db = $this->db;
$db->begin();
$file = migrationPath($filename);
try {
$this->logAndShowInfo(sprintf('Start reverting migration "%s"...', $filename), false);
$migration = include $file;
if (isset($migration['down']) && is_callable($migration['down'])) {
call_user_func($migration['down'], $db, $this);
}
$this->migrationExecuted($filename, false);
$db->commit();
Db::clearMetadata();
$this->logAndShowInfo(' [ OK ] ');
} // @codeCoverageIgnoreStart
catch (Exception $e) {
$db->rollback();
Log::exception($e);
$this->error(' [ BAD ] ');
$this->error($e->getMessage());
}
// @codeCoverageIgnoreEnd
}
}
<?php
namespace Phwoolcon\Cli\Command\Package;
use PharData;
use Phwoolcon\Cli\Command;
/**
* Command to create a Phwoolcon package
*
* @package Phwoolcon\Cli\Command\Package
* @codeCoverageIgnore
*/
class CreateCommand extends Command
{
protected function configure()
{
$this->setDescription('Create a phwoolcon package under vendor dir.');
}
public function fire()
{
// Clear working dir
if (is_dir($workingDir = storagePath('package-skeleton'))) {
removeDir($workingDir);
}
// Extract skeleton
mkdir($workingDir, 0777, true);
$phar = new PharData(__DIR__ . '/skeleton.tar');
$phar->extractTo($workingDir);
// Run prefill script
$workingDir .= '/skeleton';
chdir($workingDir);
putenv('PHWOOLCON_ROOT_PATH=' . $_SERVER['PHWOOLCON_ROOT_PATH']);
$process = proc_open('php prefill-phwoolcon.php', [STDIN, STDOUT, STDERR], $pipes);
proc_close($process);
if (is_file($workingDir . '/prefill-phwoolcon.php')) {
$this->error('Error occurred while running `prefill-phwoolcon.php`');
}
}
}
No preview for this file type
<?php
namespace Phwoolcon\Cli\Command;
use Phwoolcon\Cli\Command;
use Symfony\Component\Console\Input\InputOption;
class PhpunitPickPackageCommand extends Command
{
protected function configure()
{
$this->setDescription('Pick a phwoolcon package for phpunit testing.')
->addOption('clear', 'c', InputOption::VALUE_NONE, 'Clear chosen result');
}
public function fire()
{
$outputFile = storagePath('phpunit-chosen-package');
if ($this->input->getOption('clear')) {
is_file($outputFile) and unlink($outputFile);
return;
}
$packages = [''];
$packageFiles = detectPhwoolconPackageFiles();
$rootPathLength = strlen($_SERVER['PHWOOLCON_ROOT_PATH']) + 1;
foreach ($packageFiles as $packageFile) {
$packages[] = substr(dirname(dirname($packageFile)), $rootPathLength);
}
unset($packages[0]);
$firstOption = reset($packages);
$default = is_file($outputFile) ? file_get_contents($outputFile) : $firstOption;
in_array($default, $packages) or $default = $firstOption;
$package = $this->interactive->choice('Run phpunit for ... ', $packages, $default);
file_put_contents($outputFile, $package);
}
}
<?php
namespace Phwoolcon\Cli\Command;
use Exception;
use Phwoolcon\Cli\Command;
use Phwoolcon\Config;
use Phwoolcon\Log;
use Phwoolcon\Queue\Listener;
use Phwoolcon\Util\Timer;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class QueueConsumeCommand extends Command
{
protected $outputTimestamp = true;
protected function configure()
{
$this->setDescription('Consume from a given queue')
->addArgument('queue', InputArgument::OPTIONAL, 'The queue name to consume from, e.g. async_email_sending');
$optional = InputOption::VALUE_OPTIONAL;
$this->addOption('topic', null, $optional, 'The queue topic to consume from')
->addOption('delay', null, $optional, 'Seconds to delay failed jobs', 0)
->addOption('interval', 'i', $optional, 'Seconds between jobs execution', 0)
->addOption('memory', null, $optional, 'The memory limit in megabytes', 128)
->addOption('sleep', null, $optional, 'Seconds to sleep when no job is available', 3)
->addOption('ttl', null, $optional, 'Seconds to keep this consumer running', 3600)
->addOption('tries', null, $optional, 'Number of times to attempt a job before logging it failed', 0)
->addOption('type', null, $optional, 'work type :fire;reserve', 'reserve')
->addOption('tags', null, $optional);
}
public function fire()
{
// $type = $this->input->getOption('type');
// if($type && $type == 'reserve'){
// self::reserve();
// return;
// }
$this->output->writeln('');
$this->info('QueueConsumeCommand Handler');
$this->info('topic: ' . $this->input->getOption('topic'));
$this->output->writeln('');
$runningUnitTest = Config::runningUnitTest();
$queueName = $this->input->getArgument('queue');
$topic = $this->input->getOption('topic');
$tags = $this->input->getOption('tags');
$delay = $this->input->getOption('delay');
$interval = $this->input->getOption('interval') * 1e6;
$sleep = $this->input->getOption('sleep');
$ttl = $this->input->getOption('ttl');
$maxTries = $this->input->getOption('tries');
$memory = $this->input->getOption('memory');
$memoryLimit = $memory * 1024 * 1024;
$listener = $this->getListener();
// Timer::start();
$i = 0;
while (true) {
// $this->info('---handler---');
// Memory leak protect
if (++$i >= 100) {
if (memory_get_usage() > $memoryLimit) {
$runningUnitTest and $this->comment('Memory leak protection');
break;
}
$i = 0;
}
try {
$listener->pop($queueName, $topic, $delay, $sleep, $maxTries, $tags);
$interval and usleep($interval);
} catch (Exception $e) {
Log::exception($e);
$this->error($e->getMessage());
break;
}
// Exit when exceeds ttl, let process manager (e.g. supervisord) to start the listener again
/*if (Timer::stop() >= $ttl) {
break;
}*/
}
}
public function reserve()
{
$runningUnitTest = Config::runningUnitTest();
$queueName = $this->input->getArgument('queue');
$topic = $this->input->getOption('topic');
$tags = $this->input->getOption('tags');
$delay = $this->input->getOption('delay');
$interval = $this->input->getOption('interval') * 1e6;
$sleep = $this->input->getOption('sleep');
$ttl = $this->input->getOption('ttl');
$maxTries = $this->input->getOption('tries');
$memory = $this->input->getOption('memory');
$memoryLimit = $memory * 1024 * 1024;
$listener = $this->getListener();
Timer::start();
$i = 0;
while ($listener->reserve($queueName, $topic, $delay, $sleep, $maxTries, $tags)) {
$this->info('---handler---');
// Memory leak protect
try {
if (++$i >= 100) {
if (memory_get_usage() > $memoryLimit) {
$runningUnitTest and $this->comment('Memory leak protection');
break;
}
$i = 0;
}
$interval and usleep($interval);
} catch (Exception $e) {
Log::exception($e);
$this->error($e->getMessage());
break;
}
// Exit when exceeds ttl, let process manager (e.g. supervisord) to start the listener again
if (Timer::stop() >= $ttl) {
break;
}
}
}
/**
* @return Listener
*/
protected function getListener()
{
$di = $this->di;
$listenerClass = Listener::class;
$di->has($listenerClass) and $listenerClass = $di->getRaw($listenerClass);
return new $listenerClass;
}
}
<?php
/**
* Created by IntelliJ IDEA.
* User: yeran
* Date: 2018/5/12
* Time: 上午12:32
*/
namespace Phwoolcon\Cli\Command;
use Exception;
use Phwoolcon\Config;
use Phwoolcon\Log;
use Phwoolcon\Queue\Listener;
use Phwoolcon\Util\Timer;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Phwoolcon\Cli\Command;
class RocketQueueConsumeCommand extends Command
{
protected $outputTimestamp = true;
protected function configure()
{
$this->setDescription('Consume from a given rocketMQ')
->addArgument('queue', InputArgument::OPTIONAL, 'The queue name to consume from, e.g. async_email_sending');
$optional = InputOption::VALUE_OPTIONAL;
$this->addOption('topic', null, $optional, 'The queue topic to consume from')
->addOption('delay', null, $optional, 'Seconds to delay failed jobs', 0)
->addOption('interval', 'i', $optional, 'Seconds between jobs execution', 0)
->addOption('memory', null, $optional, 'The memory limit in megabytes', 128)
->addOption('sleep', null, $optional, 'Seconds to sleep when no job is available', 3)
->addOption('ttl', null, $optional, 'Seconds to keep this consumer running', 3600)
->addOption('tries', null, $optional, 'Number of times to attempt a job before logging it failed', 0)
->addOption('type', null, $optional, 'work type :fire;reserve', 'reserve');
}
public function fire()
{
$runningUnitTest = Config::runningUnitTest();
$queueName = $this->input->getArgument('queue');
$topic = $this->input->getOption('topic');
$delay = $this->input->getOption('delay');
$interval = $this->input->getOption('interval') * 1e6;
$sleep = $this->input->getOption('sleep');
$ttl = $this->input->getOption('ttl');
$maxTries = $this->input->getOption('tries');
$memory = $this->input->getOption('memory');
$memoryLimit = $memory * 1024 * 1024;
$listener = $this->getListener();
Timer::start();
$i = 0;
while (true) {
// $this->info('---handler---');
// Memory leak protect
if (++$i >= 100) {
if (memory_get_usage() > $memoryLimit) {
$runningUnitTest and $this->comment('Memory leak protection');
break;
}
$i = 0;
}
try {
$listener->pop($queueName, $topic, $delay, $sleep, $maxTries);
$interval and usleep($interval);
} catch (Exception $e) {
Log::exception($e);
$this->error($e->getMessage());
break;
}
// Exit when exceeds ttl, let process manager (e.g. supervisord) to start the listener again
if (Timer::stop() >= $ttl) {
break;
}
}
}
public function reserve(){
$runningUnitTest = Config::runningUnitTest();
$queueName = $this->input->getArgument('queue');
$topic = $this->input->getOption('topic');
$delay = $this->input->getOption('delay');
$interval = $this->input->getOption('interval') * 1e6;
$sleep = $this->input->getOption('sleep');
$ttl = $this->input->getOption('ttl');
$maxTries = $this->input->getOption('tries');
$memory = $this->input->getOption('memory');
$memoryLimit = $memory * 1024 * 1024;
$listener = $this->getListener();
Timer::start();
$i = 0;
while ($listener->reserve($queueName, $topic, $delay, $sleep, $maxTries)) {
$this->info('---handler---');
// Memory leak protect
try {
if (++$i >= 100) {
if (memory_get_usage() > $memoryLimit) {
$runningUnitTest and $this->comment('Memory leak protection');
break;
}
$i = 0;
}
$interval and usleep($interval);
} catch (Exception $e) {
Log::exception($e);
$this->error($e->getMessage());
break;
}
// Exit when exceeds ttl, let process manager (e.g. supervisord) to start the listener again
if (Timer::stop() >= $ttl) {
break;
}
}
}
/**
* @return Listener
*/
protected function getListener()
{
$di = $this->di;
$listenerClass = Listener::class;
$di->has($listenerClass) and $listenerClass = $di->getRaw($listenerClass);
return new $listenerClass;
}
}
\ No newline at end of file
<?php
namespace Phwoolcon\Cli\Command;
use Phalcon\Di;
use Phwoolcon\Cli\Command;
use Phwoolcon\Daemon\Service;
use Symfony\Component\Console\Input\InputArgument;
class ServiceCommand extends Command
{
protected static $service;
protected function configure()
{
$this->setDefinition([
new InputArgument(
'action',
InputArgument::OPTIONAL,
"Specifies the action to the service. Should be one of :
start | stop | restart | reload | status | install | uninstall"
),
])->setDescription('Phwoolcon High Performance Service');
}
public function fire()
{
Service::register($this->di);
/* @var Service $service */
$service = $this->di->getShared('service');
$service->setCliCommand($this);
// self::$service = $service;
$serviceName = $service->getName();
switch (strtolower($this->input->getArgument('action'))) {
case 'start':
$this->comment("Starting {$serviceName}...");
$service->start();
break;
case 'stop':
$this->comment("Stopping {$serviceName}...");
$service->stop();
break;
case 'restart':
$this->comment("Stopping {$serviceName}...");
$service->stop();
sleep(1);
$this->comment("Starting {$serviceName}...");
$service->start();
break;
case 'reload':
$this->comment('Run: bin/cli service reload-create-new-instance');
$this->comment('then bin/cli service reload-shut-old-instance');
break;
case 'reload-create-new-instance':
$this->comment("Starting new {$serviceName}...");
$service->shift()
->start();
break;
case 'reload-shut-old-instance':
// Ensure new service instance is up
$port = $service->choosePort();
$retry = 0;
$service->sendCommand('status', $port, $error);
while ($error && $retry < 20) {
usleep(5e5);
++$retry;
$service->sendCommand('status', $port, $error);
}
if ($error) {
$this->error('Service reload failed: ' . var_export($error, true));
break;
}
$this->comment("Stopping old {$serviceName}...");
$service->stop('old');
$this->info('Service reloaded.');
break;
case 'status':
$service->showStatus();
break;
case 'install':
$service->install();
break;
case 'uninstall':
case 'remove':
$service->uninstall();
break;
default:
$this->info($this->getDescription());
$this->comment('Usage:');
$this->info('bin/cli service [ start | stop | restart | reload | status | install | uninstall ]');
$this->output->writeln('');
}
}
}
<?php
/**
* 命令行处理任务
* @author: wangyu <wangyu@ledouya.com>
* @createTime: 2018/4/23 9:12
*/
namespace Phwoolcon\Cli\Command;
use Exception;
use Phwoolcon\Cli\Command;
use Phwoolcon\Config;
use Phwoolcon\Log;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class TaskCommand extends Command
{
protected $outputTimestamp = true;
protected function configure()
{
$this->setDescription('Command line execute task.');
$optional = InputOption::VALUE_OPTIONAL;
$this->addOption('name', null, $optional, 'The task processing class and processing function')
->addOption('options', null, $optional, 'Custom options, URL query string', '');
}
public function fire()
{
set_time_limit(0);
$name = $this->input->getOption('name');
$options = $this->input->getOption('options');
if (empty($name)) {
$this->info($this->getDescription());
$this->comment('Usage:');
$this->info('bin\cli task --name Lestore\Test\Services\Tasks\TestTask::test');
$this->output->writeln('');
return false;
}
$classArray = explode('::', $name, 2);
if (empty($classArray[0]) || empty($classArray[1]) || !class_exists($classArray[0]) || !method_exists($classArray[0], $classArray[1])) {
$this->output->writeln('');
$this->error('Processing class or processing function not exists');
$this->output->writeln('');
return false;
}
$parameters = [];
if ($options) {
parse_str($options, $parameters);
}
try {
$returnValue = call_user_func($name, $parameters);
if (is_array($returnValue)) {
$returnValue = json_encode($returnValue, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
} elseif (is_object($returnValue)) {
$returnValue = json_encode($returnValue, JSON_FORCE_OBJECT);
}
$this->output->writeln('');
$this->info($returnValue);
$this->output->writeln('');
} catch (\Exception $e) {
$this->output->writeln('');
$this->info($e->getMessage());
$this->output->writeln('');
}
}
}
<?php
namespace Phwoolcon\Cli\Output;
use Phwoolcon\Protocol\StreamWrapperInterface;
use Phwoolcon\Protocol\StreamWrapperTrait;
class Stream implements StreamWrapperInterface
{
use StreamWrapperTrait {
stream_write as streamWriteReturn;
}
protected static $output = [];
public function stream_flush()
{
}
public function stream_open($path, $mode, $options, &$openedPath)
{
$this->path = $path;
isset(static::$output[$path]) or static::$output[$path] = '';
return true;
}
public function stream_read($count)
{
$start = $this->cursor;
$eof = $this->eof;
$this->updateCursor($count);
return $eof ? false : substr(static::$output[$this->path], $start, $count);
}
public function stream_write($data)
{
// @codeCoverageIgnoreStart
if (!isset(static::$output[$this->path])) {
return 0;
}
// @codeCoverageIgnoreEnd
static::$output[$this->path] .= $data;
return $this->streamWriteReturn($data);
}
public function unlink($path)
{
unset(static::$output[$path]);
return true;
}
}
<?php
namespace Phwoolcon;
use Phalcon\Config as PhalconConfig;
use Phalcon\Di;
class Config
{
protected static $config;
protected static $preloadConfig = [];
public static function clearCache()
{
$environment = static::environment();
is_file($cacheFile = storagePath('cache/config-' . $environment . '.php')) and unlink($cacheFile);
Cache::delete('db_configs_' . $environment);
}
public static function environment()
{
return static::get('environment');
}
/**
* @param $key string
* @param $defaultValue mixed
* @return mixed
*/
public static function get($key = null, $defaultValue = null)
{
return $key === null ? static::$config : fnGet(static::$config, $key, $defaultValue, '.');
}
protected static function loadDb(PhalconConfig $config)
{
$dbConfig = new PhalconConfig(Model\Config::all());
$config->merge($dbConfig);
static::$config = $config->toArray();
}
protected static function loadFiles($files)
{
$settings = [];
foreach ($files as $file) {
// @codeCoverageIgnoreStart
if (!is_file($file)) {
continue;
}
// @codeCoverageIgnoreEnd
$key = pathinfo($file, PATHINFO_FILENAME);
$value = include $file;
$settings[$key] = is_array($value) ? $value : [];
}
return $settings;
}
public static function register(Di $di)
{
$environment = isset($_SERVER['PHWOOLCON_ENV']) ? $_SERVER['PHWOOLCON_ENV'] : 'production';
// @codeCoverageIgnoreStart
if (is_file($cacheFile = storagePath('cache/config-' . $environment . '.php'))) {
static::$config = include $cacheFile;
Config::get('app.cache_config') or static::clearCache();
return;
}
// @codeCoverageIgnoreEnd
// Load preload files (for unit testing)
$config = new PhalconConfig(static::$preloadConfig);
// Load default configs
$defaultFiles = glob($_SERVER['PHWOOLCON_CONFIG_PATH'] . '/*.php');
$config->merge(new PhalconConfig(static::loadFiles($defaultFiles)));
// Load override configs
$overrideDirs = glob($_SERVER['PHWOOLCON_CONFIG_PATH'] . '/override-*/');
foreach ($overrideDirs as $overrideDir) {
$overrideFiles = glob($overrideDir . '*.php');
$overrideSettings = static::loadFiles($overrideFiles);
$overrideConfig = new PhalconConfig($overrideSettings);
$config->merge($overrideConfig);
}
// Load environment configs
$environmentFiles = glob($_SERVER['PHWOOLCON_CONFIG_PATH'] . '/' . $environment . '/*.php');
$environmentSettings = static::loadFiles($environmentFiles);
$environmentSettings['environment'] = $environment;
$environmentConfig = new PhalconConfig($environmentSettings);
$config->merge($environmentConfig);
$di->remove('config');
$di->setShared('config', $config);
static::$config = $config->toArray();
Config::get('database.default') and static::loadDb($config);
// @codeCoverageIgnoreStart
if (Config::get('app.cache_config')) {
is_dir($cacheDir = dirname($cacheFile)) or mkdir($cacheDir, 0777, true);
fileSaveArray($cacheFile, static::$config, function ($content) {
$replacement = <<<'EOF'
$_SERVER['PHWOOLCON_ROOT_PATH'] . '
EOF;
return str_replace("'{$_SERVER['PHWOOLCON_ROOT_PATH']}", $replacement, $content);
});
}
// @codeCoverageIgnoreEnd
}
/**
* @param string $key
* @param mixed $value
* @return mixed
*/
public static function set($key, $value)
{
array_set(static::$config, $key, $value, '.');
return $value;
}
public static function runningUnitTest()
{
return static::environment() == 'testing';
}
}
<?php
namespace Phwoolcon\Controller;
use Phwoolcon\Config;
use Phwoolcon\Controller;
use Phwoolcon\Session;
use Phwoolcon\View;
/**
* Class Admin
* @package Phwoolcon\Controller
*
* @property View $view
* @method Controller addPageTitle(string $title)
*/
trait Admin
{
public function initialize()
{
parent::initialize();
$this->addPageTitle(__(Config::get('view.admin.title_suffix')));
$this->view->setLayout(Config::get('view.admin.layout'));
$this->view->isAdmin(true);
}
}
<?php
namespace Phwoolcon\Controller\Admin;
use Phalcon\Validation\Exception as ValidationException;
use Phwoolcon\Config;
use Phwoolcon\Model\Config as ConfigModel;
trait ConfigTrait
{
protected function filterConfig($key, $data)
{
unset($data['_black_list'], $data['_white_list']);
// Process white list
if (is_array($allowedKeys = Config::get($key . '._white_list'))) {
$allowedData = [];
foreach ($allowedKeys as $key) {
array_set($allowedData, $key, fnGet($data, $key));
}
return $allowedData;
}
// Process black list
if (is_array($sensitiveKeys = Config::get($key . '._black_list'))) {
foreach ($sensitiveKeys as $key) {
array_forget($data, $key);
}
return $data;
}
throw new ValidationException("Config group '{$key}' is protected");
}
protected function getCurrentConfig($key)
{
$data = Config::get($key);
return $this->filterConfig($key, $data);
}
public static function getKeyLabel($key)
{
$labelKey = 'config_key_' . $key;
$label = __($labelKey);
return $label == $labelKey ? $key : $label;
}
protected function keyList()
{
$keys = [];
foreach (Config::get() as $key => $value) {
if (is_array(fnGet($value, '_black_list')) || is_array(fnGet($value, '_white_list'))) {
$keys[$key] = static::getKeyLabel($key);
}
}
ksort($keys);
return $keys;
}
protected function submitConfig($key, $data)
{
if ($data === '' || $data === null) {
$value = null;
} else {
if (is_string($data)) {
$data = json_decode($data, true);
if (json_last_error() != JSON_ERROR_NONE) {
throw new ValidationException(json_last_error_msg(), json_last_error());
}
}
$value = $this->filterConfig($key, $data);
}
ConfigModel::saveConfig($key, $value);
return $value;
}
}
<?php
namespace Phwoolcon\Controller;
use core\service\security\AES;
use core\service\security\KeyConfig;
use InvalidArgumentException;
use Phwoolcon\Config;
use phwoolcon\core\common\CodeMsg;
use Phwoolcon\ErrorCodes;
use Phwoolcon\Log;
use Phwoolcon\Router;
/**
* Class Api
* @package Phwoolcon\Controller
*
* @method \Phalcon\Http\Response jsonReturn(array $array, $httpCode = 200, $contentType = 'application/json')
*/
trait Api
{
protected $jsonApiContentType = 'application/vnd.api+json';
protected $jsonApiVersion = ['version' => '1.0'];
public function initialize()
{
Router::disableCsrfCheck();
Router::disableSession();
}
public function missingMethod()
{
Router::throw404Exception(json_encode([
'jsonapi' => $this->jsonApiVersion,
'errors' => [
[
'status' => 404,
'code' => 404,
'title' => '404 Not Found',
],
],
]), $this->jsonApiContentType);
}
/**
* Returns JSON API data
* @see http://jsonapi.org/format/#document-resource-objects
*
* @param array $data SHOULD contain `id` and `type`, MAY contain `attributes`, `relationships`, `links`
* @param array $meta
* @param array $extraData
* @param int $status
* @return \Phalcon\Http\Response
*/
public function jsonApiReturnData(array $data, array $meta = [], array $extraData = [], $status = 200)
{
$extraData['jsonapi'] = $this->jsonApiVersion;
$extraData['data'] = $data;
$meta and $extraData['meta'] = $meta;
return $this->jsonReturn($extraData, $status, $this->jsonApiContentType);
}
/**
* Returns JSON API error
* @see http://jsonapi.org/format/#errors
*
* @param array $errors Each error SHOULD contain `code` and `title`,
* MAY contain `id`, `status`, `links`, `detail`, `source`
* @param array $meta
* @param array $extraData
* @param int $status
* @return \Phalcon\Http\Response
*/
public function jsonApiReturnErrors(array $errors, array $meta = [], array $extraData = [], $status = 400)
{
foreach ($errors as &$error) {
isset($error['code']) and $error['code'] = (string)$error['code'];
}
unset($error);
$extraData['jsonapi'] = $this->jsonApiVersion;
$extraData['errors'] = $errors;
$meta and $extraData['meta'] = $meta;
return $this->jsonReturn($extraData, $status, $this->jsonApiContentType);
}
public function jsonApiReturnMeta(array $meta, array $extraData = [], $status = 200)
{
$extraData['jsonapi'] = $this->jsonApiVersion;
$extraData['meta'] = $meta;
return $this->jsonReturn($extraData, $status, $this->jsonApiContentType);
}
/**
* jsonApiReturn
* @param mixed $errorCode 错误码
* @param mixed $errorMsg 信息
* @param array $data 返回数据
* @param array $extraData 扩展数据
* @param int $status 状态码
* @param bool $is_encrypt 本次响应是否需要加密数据
* @return \Phalcon\Http\Response
* @author wangyu <wangyu@ledouya.com>
* @createTime 2018/5/25 14:45
*/
public function jsonApiReturn($errorCode, $errorMsg = null, array $data = null, array $extraData = [], $status = 200, $is_encrypt = true)
{
$extraData['jsonapi'] = $this->jsonApiVersion;
if (!empty($data)) {
if (true === $is_encrypt && true === Config::get('security.state.data') && !isset($data[ENCRYPT_FIELD_NAME])) {
$extraData['data'][ENCRYPT_FIELD_NAME] = AES::aes128Encrypt(json_encode($data), Config::get('security.secret_key.data'), Config::get('security.secret_iv.data'));
} else {
$extraData['data'] = $data;
}
}
// $extraData['error'] = $errorCode ?: 1;
$extraData['error'] = $errorCode;
if (!$errorMsg) {
$errorCode = ErrorCodes::getCodeMsg($errorCode);
$errorMsg = ($errorCode && count($errorCode) == 2) ? $errorCode[1] : '';
}
$extraData['error_reason'] = $errorMsg;
return $this->jsonReturn($extraData, $status, $this->jsonApiContentType);
}
}
<?php
namespace Phwoolcon;
use ReflectionProperty;
use Phalcon\Di;
use Phalcon\Events\Event;
use Phalcon\Http\Response\Cookies as PhalconCookies;
/**
* Class Cookies
* @package Phwoolcon
*
* @method static bool delete(string $name)
* @method static Http\Cookie get(string $name)
* @method static PhalconCookies reset()
*/
class Cookies
{
/**
* @var Di
*/
protected static $di;
/**
* @var PhalconCookies
*/
protected static $cookies;
/**
* @var ReflectionProperty
*/
protected static $cookiesReflection;
protected static $options;
public static function __callStatic($name, $arguments)
{
return call_user_func_array([static::$cookies, $name], $arguments);
}
public static function register(Di $di)
{
static::$di = $di;
$di->set('Phalcon\\Http\\Cookie', 'Phwoolcon\\Http\\Cookie');
static::$cookies = static::$di->getShared('cookies');
static::$cookies->reset();
static::$options = $options = Config::get('cookies');
static::$cookies->useEncryption($encrypt = $options['encrypt']);
$encrypt and static::$di->getShared('crypt')
->setKey($options['encrypt_key'])
->setPadding(Crypt::PADDING_ZERO);
/* @var \Phalcon\Http\Response $response */
if ($response = $di->getShared('response')) {
$response->setCookies(static::$cookies);
}
Events::attach('view:generatePhwoolconJsOptions', function (Event $event) {
$options = $event->getData() ?: [];
$options['cookies'] = [
'domain' => static::$options['domain'],
'path' => static::$options['path'],
];
$event->setData($options);
return $options;
});
}
/**
* @param string $name
* @param mixed $value
* @param int $expire
* @param string $path
* @param bool $secure
* @param string $domain
* @param bool $httpOnly
* @return PhalconCookies
*/
public static function set(
$name,
$value = null,
$expire = 0,
$path = null,
$secure = null,
$domain = null,
$httpOnly = null
) {
$options = static::$options;
$path === null and $path = $options['path'];
$domain === null and $domain = $options['domain'];
return static::$cookies->set($name, $value, $expire, $path, $secure, $domain, $httpOnly);
}
/**
* @return Http\Cookie[]
*/
public static function toArray()
{
if (static::$cookiesReflection === null) {
static::$cookiesReflection = new ReflectionProperty(static::$cookies, '_cookies');
static::$cookiesReflection->setAccessible(true);
}
return static::$cookiesReflection->getValue(static::$cookies);
}
}
<?php
namespace Phwoolcon;
use Phalcon\Crypt as PhalconCrypt;
class Crypt extends PhalconCrypt
{
protected static $opensslCipher = 'AES-256-CBC';
protected static $ivSize;
public static function opensslEncrypt($text, $key = null)
{
static::$ivSize or static::$ivSize = openssl_cipher_iv_length(static::$opensslCipher);
$iv = substr(base64_encode(random_bytes(static::$ivSize)), 0, static::$ivSize);
return $iv . openssl_encrypt($text, static::$opensslCipher, $key, 0, $iv);
}
public static function opensslDecrypt($text, $key = null)
{
static::$ivSize or static::$ivSize = openssl_cipher_iv_length(static::$opensslCipher);
$iv = substr($text, 0, static::$ivSize);
return openssl_decrypt(substr($text, static::$ivSize), static::$opensslCipher, $key, 0, $iv);
}
public static function reset()
{
static::$opensslCipher = 'AES-256-CBC';
static::$ivSize = openssl_cipher_iv_length(static::$opensslCipher);
}
}
<?php
namespace Phwoolcon\Daemon;
interface ServiceAwareInterface
{
public function reset();
}
<?php
namespace Phwoolcon;
use DateTime as PhpDateTime;
class DateTime extends PhpDateTime
{
const RFC2616 = 'D, d M Y H:i:s T';
const MYSQL_DATETIME = 'Y-m-d H:i:s';
const MYSQL_DATETIME_MICRO = 'Y-m-d H:i:s.u';
}
<?php
namespace Phwoolcon;
use Phalcon\Events\Event;
use Phwoolcon\Exception\InvalidConfigException;
use Phwoolcon\Model\MetaData\InCache;
use Phalcon\Di;
use Phalcon\Db as PhalconDb;
use Phalcon\Db\Adapter\Pdo as Adapter;
class Db extends PhalconDb
{
/**
* @var Di
*/
protected static $di;
/**
* @var static
*/
protected static $instance;
protected static $defaultTableCharset = [];
/**
* @var Adapter[]|Db\Adapter\Pdo\Mysql[]
*/
protected $connections = [];
protected $config;
public function __construct($config)
{
//PHP_SAPI == 'cli' ? $config['connections']['mysql']['persistent'] = true : $config['connections']['mysql']['persistent'] = false;
$this->config = $config;
$ormOptions = $config['orm_options'];
$ormOptions['distributed'] = $config['distributed'];
Model::setup($ormOptions);
if (fnGet($this->config, 'query_log')) {
Events::attach('db', function ($event, $connection) {
Log::debug('---db--event--');
$adapter = $event->getSource();
$binds = $adapter->getSqlVariables();
Log::debug(json_encode($adapter->getDescriptor()));
Log::debug($adapter->getRealSQLStatement() . '; binds = ' . var_export($binds, 1));
});
}
}
public static function clearMetadata()
{
/* @var InCache $metadata */
$metadata = static::$di->getShared('modelsMetadata');
$metadata->reset();
}
/**
* @param string $name
* @return string
*/
public static function getDefaultTableCharset($name = null)
{
static::$instance === null and static::$instance = static::$di->getShared('dbManager');
return self::$defaultTableCharset[$name ?: static::$instance->config['default']];
}
/**
* @param string $name
* @return Adapter|Db\Adapter\Pdo\Mysql
*/
protected function connect($name)
{
$name = static::$instance->config[$name] ?? $name;
$connection = $this->config['connections'][$name];
$class = $connection['adapter'];
isset($connection['charset']) and static::$defaultTableCharset[$name] = $connection['charset'];
if (isset($connection['default_table_charset'])) {
static::$defaultTableCharset[$name] = $connection['default_table_charset'];
}
unset($connection['adapter'], $connection['default_table_charset']);
// @codeCoverageIgnoreStart
if (!$class || !class_exists($class)) {
throw new InvalidConfigException("Invalid db adapter {$class}, please check config file database.php");
}
// @codeCoverageIgnoreEnd
$adapter = new $class($connection);
// @codeCoverageIgnoreStart
if (!$adapter instanceof Adapter) {
throw new InvalidConfigException("Db adapter {$class} should extend " . Adapter::class);
}
// @codeCoverageIgnoreEnd
return $adapter;
}
/**
* @param string $name
* @return Adapter|Db\Adapter\Pdo\Mysql
* @throws PhalconDb\Exception
*/
public static function connection($name = null)
{
static::$instance === null and static::$instance = static::$di->getShared('dbManager');
$db = static::$instance;
// @codeCoverageIgnoreStart
if (!$name = $name ?: 'default') {
throw new PhalconDb\Exception('Please set default database connection in your production config!');
}
// @codeCoverageIgnoreEnd
if (!isset($db->connections[$name])) {
$db->connections[$name] = $adapter = $db->connect($name);
$adapter->setEventsManager(static::$di->getShared('eventsManager'));
}
return $db->connections[$name];
}
public static function reconnect($name = null)
{
static::$instance === null and static::$instance = static::$di->getShared('dbManager');
$db = static::$instance;
$db->disconnect($name = $name ? $db->config['name'] : $db->config['default']);
if (isset($db->connections[$name])) {
$db->connections[$name]->connect();
return $db->connections[$name];
}
// @codeCoverageIgnoreStart
return static::connection($name);
// @codeCoverageIgnoreEnd
}
public function disconnect($name)
{
if (isset($this->connections[$name])) {
$this->connections[$name]->close();
}
}
public static function register(Di $di)
{
static::$di = $di;
$di->remove('modelsMetadata');
$di->setShared('modelsMetadata', function () {
return new InCache();
});
$di->setShared('dbManager', function () {
return new static(Config::get('database'));
});
$di->setShared('db', function () {
return static::connection();
});
}
}
<?php
namespace Phwoolcon\Db\Adapter\Pdo;
use Phalcon\Db\Adapter\Pdo\Mysql as PhalconMysql;
class Mysql extends PhalconMysql
{
}
<?php
namespace Phwoolcon;
use Closure;
use Phalcon\Di;
use Phalcon\Events\Event;
class DiFix extends Di
{
/**
* Fix over clever di service resolver in phalcon 2.1.x:
* let definition = \Closure::bind(definition, dependencyInjector)
* which leads to php warning "Cannot bind an instance to a static closure"
*
* @param Di $di
* @codeCoverageIgnore
* @see https://github.com/phalcon/cphalcon/issues/11709
* @see https://github.com/phalcon/cphalcon/commit/d67bef6cd22ca35795681b61d1c331cdefa24b09#diff-aaf42492472386533320a8415b05c5a6
*/
public static function fix(Di $di)
{
if ($_SERVER['PHWOOLCON_PHALCON_VERSION'] > 2010000) {
$di->setInternalEventsManager($di->getShared('eventsManager'));
Events::attach('di:beforeServiceResolve', function (Event $event) {
/* @var Di $di */
$di = $event->getSource();
$data = $event->getData();
$name = $data['name'];
$parameters = $data['parameters'];
if (!isset($di->_services[$name])) {
return false;
}
/* @var Di\Service $service */
$service = $di->_services[$name];
if (!$service->isShared()) {
return false;
}
if (!(($definition = $service->getDefinition()) instanceof Closure)) {
return false;
}
return $parameters ? call_user_func_array($definition, $parameters) : call_user_func($definition);
});
}
}
}
<?php
namespace Phwoolcon;
use Phalcon\Di;
/**
* Class ErrorCodes
*
* Usage:
* 1. Define error codes in a locale file `error_codes.php`
* ```php
* <?php
* return [
* 'foo_error' => 'The foo error message: %param%',
* '1234' => 'Some error message for 1234',
* '2345_with_annotation' => 'Some numeric error code with annotation',
* ];
* ```
* 2. `bin/dump-autoload` to generate the IDE helper
* 3. Enjoy the magic methods:
* ```php
* <?php
*
* use ErrorCodes; // Use the alias instead of Phwoolcon\ErrorCodes;
*
* list($errorCode, $errorMessage) = ErrorCodes::getFooError('bar');
* var_dump($errorCode, $errorMessage); // prints 'foo_error' and 'The foo error message: bar'
*
* throw ErrorCodes::gen1234(RuntimeException::class); // This is identical to:
* // $errorMessage = 'Some error message for 1234';
* // $errorCode = 1234;
* // throw new RuntimeException($errorMessage, $errorCode)
*
* throw ErrorCodes::gen2345WithAnnotation(RuntimeException::class); // This is identical to:
* // $errorMessage = 'Some numeric error code with annotation';
* // $errorCode = 2345; // Annotation removed
* // throw new RuntimeException($errorMessage, $errorCode)
*
* throw ErrorCodes::genFooError(RuntimeException::class, 'bar'); // This is identical to:
* // $errorMessage = 'The foo error message: bar [foo_error]';
* // $errorCode = 0; // Error code in a exception must be a integer
* // throw new RuntimeException($errorMessage, $errorCode)
* ```
*
* @package Phwoolcon
*/
class ErrorCodes
{
/**
* @var Di
*/
protected static $di;
/**
* @var static
*/
protected static $instance;
public static function __callStatic($name, $arguments)
{
static::$instance or static::$instance = static::$di->getShared('error_codes');
if (Text::startsWith($name, 'get', false)) {
$errorCode = Text::uncamelize(substr($name, 3));
return call_user_func([static::$instance, 'getDetails'], $errorCode, $arguments);
}
if (Text::startsWith($name, 'gen', false)) {
$errorCode = Text::uncamelize(substr($name, 3));
$exception = array_shift($arguments);
return call_user_func([static::$instance, 'generateException'], $exception, $errorCode, $arguments);
}
// @codeCoverageIgnoreStart
return call_user_func_array([static::$instance, $name], $arguments);
// @codeCoverageIgnoreEnd
}
public static function getCodeMsg($errorCode,$arguments=[]){
static::$instance or static::$instance = static::$di->getShared('error_codes');
return call_user_func([static::$instance, 'getDetails'], $errorCode, $arguments);
}
protected static function detectPlaceholders($message)
{
$pattern = '/%([^%]*)%/';
preg_match_all($pattern, $message, $matches);
$placeholders = isset($matches[1]) ? $matches[1] : [];
return $placeholders;
}
/**
* @param string $locale
* @return array
*/
public static function getAllErrorCodes($locale = null)
{
// @codeCoverageIgnoreStart
if (!static::$di) {
return [];
}
// @codeCoverageIgnoreEnd
/* @var I18n $i18n */
$i18n = static::$di->getShared('i18n');
$locale or $locale = I18n::getCurrentLocale();
$locales = $i18n->loadLocale($locale);
return fnGet($locales, 'packages.error_codes', []);
}
public static function ideHelperGenerator()
{
$classContent = [];
foreach (static::getAllErrorCodes() as $code => $message) {
$name = Text::camelize((string)$code);
$parameters = array_map(function ($field) {
return '$' . lcfirst(Text::camelize((string)$field));
}, static::detectPlaceholders($message));
$exceptionCode = (int)$code;
$exceptionMessage = $message;
if ($exceptionCode > 0) {
$code = $exceptionCode;
} else {
$exceptionMessage .= ' [' . $code . ']';
}
$exceptionParameters = $parameters;
array_unshift($exceptionParameters, '$exception');
$parameters = implode(', ', $parameters);
$exceptionParameters = implode(', ', $exceptionParameters);
$classContent[] = <<<METHOD
public static function get{$name}({$parameters}) {
return ['{$code}', '{$message}'];
}
public static function gen{$name}({$exceptionParameters}) {
return new \$exception('{$exceptionMessage}', {$exceptionCode});
}
METHOD;
}
return implode(PHP_EOL . PHP_EOL, $classContent);
}
public static function register(Di $di)
{
static::$di = $di;
$di->remove('error_codes');
static::$instance = null;
$di->setShared('error_codes', function () {
return new static;
});
}
protected function generateException($exception, $errorCode, array $arguments)
{
$exceptionMessage = $this->getDetails($errorCode, $arguments)[1];
$exceptionCode = (int)$errorCode;
$exceptionCode > 0 or $exceptionMessage .= ' [' . $errorCode . ']';
return new $exception($exceptionMessage, $exceptionCode);
}
public function getDetails($errorCode, array $arguments)
{
$errorMessage = __($errorCode, null, $package = 'error_codes');
if ($arguments && count($arguments)) {
$params = [];
reset($arguments);
foreach ($this->detectPlaceholders($errorMessage) as $placeholder) {
$params[$placeholder] = current($arguments);
next($arguments);
if (key($arguments) === null) {
break;
}
}
$errorMessage = __($errorCode, $params, $package);
}
intval($errorCode) > 0 and $errorCode = intval($errorCode);
return [$errorCode, $errorMessage];
}
}
<?php
namespace Phwoolcon;
use Phalcon\Di;
use Phalcon\Events\Manager;
/**
* Class Events
* @package Phwoolcon
*
* @method static void detach(string $eventType, object $handler)
* @see Manager::detach()
* @method static void detachAll(string $type = null)
* @see Manager::detachAll()
*/
class Events
{
/**
* @var Di
*/
protected static $di;
/**
* @var Manager
*/
protected static $event;
public static function __callStatic($name, $arguments)
{
static::$event or static::$event = static::$di->getShared('eventsManager');
return call_user_func_array([static::$event, $name], $arguments);
}
public static function attach($eventType, $handler, $priority = 100)
{
static::$event or static::$event = static::$di->getShared('eventsManager');
static::$event->attach($eventType, $handler, $priority);
}
public static function fire($eventType, $source, $data = null, $cancelable = true)
{
static::$event or static::$event = static::$di->getShared('eventsManager');
return static::$event->fire($eventType, $source, $data, $cancelable);
}
public static function register(Di $di)
{
static::$di = $di;
}
}
<?php
namespace Phwoolcon\Exception\Http;
/**
* Throw this exception to terminate execution and response a 403 forbidden when CSRF verification failed
* @package Phwoolcon\Exception\Http
*/
class CsrfException extends ForbiddenException
{
}
<?php
namespace Phwoolcon\Exception\Http;
use Phwoolcon\Exception\HttpException;
/**
* Throw this exception to terminate execution and response a 403 forbidden
* @package Phwoolcon\Exception\Http
*/
class ForbiddenException extends HttpException
{
public function __construct($message, $headers = null)
{
parent::__construct($message, 403, $headers);
}
}
<?php
namespace Phwoolcon\Exception\Http;
use Phwoolcon\Exception\HttpException;
/**
* Throw this exception to terminate execution and response a 404 not found
* @package Phwoolcon\Exception\Http
*/
class NotFoundException extends HttpException
{
public function __construct($message, $headers = null)
{
parent::__construct($message, 404, $headers);
}
}
<?php
namespace Phwoolcon\Exception\Http;
use Phwoolcon\Exception\HttpException;
/**
* Throw this exception to terminate execution and response a 302 redirect
* @package Phwoolcon\Exception\Http
* @codeCoverageIgnore
*/
class RedirectException extends HttpException
{
public function __construct($path, $queries = [], $secure = null)
{
$headers = [
'Location' => url($path, $queries, $secure),
];
parent::__construct('Moved Temporarily', 302, $headers);
}
}
<?php
namespace Phwoolcon\Exception;
use RuntimeException;
class HttpClientException extends RuntimeException
{
}
<?php
namespace Phwoolcon\Exception;
use Phalcon\Http\Response;
use RuntimeException;
class HttpException extends RuntimeException
{
protected $headers = [];
public function __construct($message, $code, $headers = null)
{
parent::__construct($message, $code);
$headers and $this->headers = (array)$headers;
}
public function getHeaders()
{
return $this->headers;
}
/*public function toResponse()
{
$response = new Response($this->getMessage(), $this->getCode());
$headers = $this->getHeaders();
foreach ($headers as $name => $value) {
if (is_numeric($name)) {
list($name, $value) = explode(':', $value);
}
$response->setHeader(trim($name), trim($value));
}
return $response;
}*/
public function toResponse($contents = null, $headers = [])
{
$response = new Response();
$response->setStatusCode($this->getCode());
$headers = empty($headers) ? $this->igetHeaders() : $headers;
foreach ($headers as $name => $value) {
if (is_numeric($name)) {
list($name, $value) = explode(':', $value);
}
$response->setHeader(trim($name), trim($value));
}
$response->setContent(empty($contents) ? $this->getBody() : $contents); //设置响应内容
return $response;
}
public function igetHeaders()
{
return [
'content-type: application/vnd.api+json',
'exception-type: HttpException',
];
}
public function getBody()
{
return json_encode([
'jsonapi' => ['version' => '1.0'],
'error' => $this->getCode(),
'error_reason' => $this->getMessage()
]);
}
}
<?php
namespace Phwoolcon\Exception;
use InvalidArgumentException;
class InvalidConfigException extends InvalidArgumentException
{
}
<?php
namespace Phwoolcon\Exception;
use RuntimeException;
class QueueException extends RuntimeException
{
}
<?php
namespace Phwoolcon\Exception;
use LogicException;
class SessionException extends LogicException
{
}
<?php
/**
* Created by IntelliJ IDEA.
* User: yeran
* Date: 2018/1/22
* Time: 下午3:11
*/
namespace Phwoolcon\Exception;
use Phwoolcon\Log;
use RuntimeException;
use Throwable;
class SysException extends RuntimeException{
public function __construct($message = "", $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
Log::error('-----'.$message);
}
}
<?php
namespace Phwoolcon\Exception;
use UnexpectedValueException;
class WidgetException extends UnexpectedValueException
{
}
<?php
namespace Phwoolcon\Http;
use Phalcon\Di;
use Phwoolcon\Config;
use Phwoolcon\Exception\HttpClientException;
class Client
{
protected static $componentName = 'http-client';
/**
* @var Di
*/
protected static $di;
/**
* @var static
*/
protected static $instance;
protected $lastRequest;
/**
* @var string|null
*/
protected $lastResponse;
protected $lastResponseCode;
protected $lastResponseHeaders;
protected $lastCurlInfo;
protected $options = [
'connect_time_out' => 10,
'time_out' => 10,
'verify_ssl' => true,
// 'user_agent' => 'Phwoolcon HTTP Client; +https://github.com/phwoolcon',
/*
* 'host:port', e.g.: '127.0.0.1:8080'
*/
'proxy' => null,
/*
* Custom DNS: array of 'host:port:ip', e.g.:
* ['www.example.com:443:172.16.1.1', 'abc.example.com:80:172.16.1.1']
*/
'custom_dns' => [],
/*
* Additional cURL options
*/
'curl_options' => [],
];
public function __construct(array $options = [])
{
$this->options = array_replace($this->options, $options);
}
public static function register(Di $di)
{
static::$di = $di;
$di->remove(static::$componentName);
static::$instance = null;
$di->setShared(static::$componentName, function () {
return new static(Config::get(static::$componentName, []));
});
}
public static function getInstance()
{
static::$instance or static::$instance = static::$di->getShared(static::$componentName);
return static::$instance;
}
/**
* @param string $url
* @param array|string $data
* @param array $headers
* @return string|null
* @codeCoverageIgnore
*/
public static function delete($url, $data = '', $headers = [])
{
static::$instance or static::$instance = static::$di->getShared(static::$componentName);
return static::$instance->sendRequest($url, $data, 'DELETE', $headers);
}
/**
* @param string $url
* @param array|string $data
* @param array $headers
* @return string|null
*/
public static function get($url, $data = '', $headers = [])
{
static::$instance or static::$instance = static::$di->getShared(static::$componentName);
return static::$instance->sendRequest($url, $data, 'GET', $headers);
}
/**
* @param string $url
* @param array|string $data
* @param array $headers
* @return string|null
*/
public static function head($url, $data = '', $headers = [])
{
static::$instance or static::$instance = static::$di->getShared(static::$componentName);
return static::$instance->sendRequest($url, $data, 'HEAD', $headers);
}
/**
* @param string $url
* @param array|string $data
* @param array $headers
* @return string|null
*/
public static function post($url, $data = '', $headers = [])
{
static::$instance or static::$instance = static::$di->getShared(static::$componentName);
return static::$instance->sendRequest($url, $data, 'POST', $headers);
}
/**
* @param string $url
* @param array|string $data
* @param array $headers
* @return string|null
* @codeCoverageIgnore
*/
public static function put($url, $data = '', $headers = [])
{
static::$instance or static::$instance = static::$di->getShared(static::$componentName);
return static::$instance->sendRequest($url, $data, 'PUT', $headers);
}
/**
* @param string $url
* @param array|string $data
* @param string $method
* @param array $headers
* @return string|null
*/
public static function request($url, $data = '', $method = 'GET', $headers = [])
{
static::$instance or static::$instance = static::$di->getShared(static::$componentName);
return static::$instance->sendRequest($url, $data, $method, $headers);
}
/**
* @return array|null
*/
public function getLastRequest()
{
return $this->lastRequest;
}
/**
* @return string|null
*/
public function getLastResponse()
{
return $this->lastResponse;
}
/**
* @return int
*/
public function getLastResponseCode()
{
return $this->lastResponseCode;
}
/**
* @return array|null
*/
public function getLastCurlInfo()
{
return $this->lastCurlInfo;
}
/**
* @return array
*/
public function getLastResponseHeaders()
{
if (!is_array($this->lastResponseHeaders)) {
$headers = [];
foreach (explode("\r\n", $this->lastResponseHeaders) as $row) {
if (!$row) {
continue;
}
$parts = explode(':', $row, 2);
if (!isset($parts[1])) {
continue;
}
$headers[strtolower(trim($parts[0]))] = trim($parts[1]);
}
$this->lastResponseHeaders = $headers;
}
return $this->lastResponseHeaders;
}
/**
* @return array
*/
public function getOptions()
{
return $this->options;
}
/**
* @param array $options
* @return static
*/
public function setOptions(array $options)
{
$this->options = $options;
return $this;
}
/**
* @param string $url
* @param array|string $data
* @param string $method
* @param array $headers
* @return string
* @throws HttpClientException
*/
protected function sendRequest($url, $data = '', $method = 'GET', $headers = [])
{
$method = strtoupper($method);
$data = is_array($data) ? http_build_query($data) : (string)$data;
$hasData = isset($data{0});
$this->lastResponseCode = 0;
$this->lastResponse = $this->lastResponseHeaders = $this->lastCurlInfo = null;
$this->lastRequest = compact('url', 'data', 'method', 'headers');
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_HEADER => 1,
CURLOPT_TIMEOUT => $this->options['time_out'],
CURLOPT_USERAGENT => $this->options['user_agent'],
CURLOPT_CONNECTTIMEOUT => $this->options['connect_time_out'],
CURLOPT_SSL_VERIFYPEER => $this->options['verify_ssl'],
CURLOPT_SSL_VERIFYHOST => $this->options['verify_ssl'] ? 2 : 0,
CURLOPT_HTTPHEADER => $headers,
]);
$this->options['custom_dns'] and curl_setopt($ch, CURLOPT_RESOLVE, $this->options['custom_dns']);
$this->options['proxy'] and curl_setopt($ch, CURLOPT_PROXY, $this->options['proxy']);
$this->options['curl_options'] and curl_setopt_array($ch, $this->options['curl_options']);
switch ($method) {
case 'HEAD':
curl_setopt($ch, CURLOPT_NOBODY, true);
// No break here
case 'GET':
$hasData and $url .= (strpos($url, '?') === false ? '?' : '&') . $data;
break;
case 'POST':
curl_setopt($ch, CURLOPT_POST, true);
$hasData and curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
break;
default:
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
$hasData and curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
break;
}
curl_setopt($ch, CURLOPT_URL, $url);
$response = curl_exec($ch);
$this->lastCurlInfo = $curlInfo = curl_getinfo($ch);
if ($errNo = curl_errno($ch)) {
$errMsg = 'cURL error (' . $errNo . '): "' . curl_error($ch) . '", request: ' .
var_export($this->lastRequest, true);
curl_close($ch);
throw new HttpClientException($errMsg, $errNo);
}
curl_close($ch);
$this->lastResponseCode = $curlInfo['http_code'];
$headerSize = $curlInfo['header_size'];
$this->lastResponseHeaders = substr($response, 0, $headerSize);
$this->lastResponse = substr($response, $headerSize);
return $this->lastResponse;
}
}
<?php
namespace Phwoolcon\Http;
use Phalcon\CryptInterface;
use Phalcon\Di;
use Phalcon\Http\Cookie as PhalconCookie;
use Phwoolcon\Cache;
use Phwoolcon\Cookies;
/**
* Class Cookie
* @package Phwoolcon\Http
*
* @property Di $_dependencyInjector
*/
class Cookie extends PhalconCookie
{
public function delete()
{
$this->_value = null;
Cookies::set(
$this->_name,
$this->_value,
time() - Cache::TTL_ONE_MONTH,
$this->_path,
$this->_secure,
$this->_domain,
$this->_httpOnly
)->get($this->_name)->useEncryption(false);
}
public function getResponseValue()
{
if ($this->_useEncryption && $this->_value) {
/* @var CryptInterface $crypt */
$crypt = $this->_dependencyInjector->getShared('crypt');
return $crypt->encryptBase64((string)$this->_value);
}
return $this->_value;
}
}
<?php
namespace Phwoolcon;
use Phalcon\Di;
use Phalcon\Events\Event;
use Phalcon\Translate\Adapter;
use Phwoolcon\Daemon\ServiceAwareInterface;
use Phwoolcon\Http\Cookie;
class I18n extends Adapter implements ServiceAwareInterface
{
/**
* @var Di
*/
protected static $di;
/**
* @var static
*/
protected static $instance;
protected $locale = [];
protected $localePath;
protected $defaultLocale;
protected $currentLocale;
protected $multiLocale = false;
protected $detectClientLocale = false;
protected $options = [];
protected $availableLocales = [];
/**
* @var \Phalcon\Http\Request
*/
protected $request;
protected $undefinedStrings = [];
protected $undefinedStringsLogFile;
public function __construct(array $options = [], $moduleOption = [])
{
parent::__construct($options);
$this->multiLocale = $options['multi_locale'];
$this->multiLocale and $this->availableLocales = $options['available_locales'];
$this->detectClientLocale = $options['detect_client_locale'];
$this->defaultLocale = $this->currentLocale = $options['default_locale'];
$this->localePath = $options['locale_path'];
$this->options = $options;
if (empty($moduleOption)) {
$this->loadLocale($this->defaultLocale);
} else {
$this->loadLocale($moduleOption['locale'], $moduleOption['force'], $moduleOption['files']);
}
$this->undefinedStringsLogFile = storagePath($options['undefined_strings_log']);
$this->loadUndefinedLocaleStrings();
$this->reset();
}
protected function _setLocale($locale)
{
if (strpos($locale, '..') || !is_dir($this->localePath . '/' . $locale)) {
$locale = $this->defaultLocale;
}
// Session::set('current_locale', $this->currentLocale = $locale);
$this->loadLocale($locale);
}
public static function checkMobile($mobile, $country = 'CN')
{
static::$instance or static::$instance = static::$di->getShared('i18n');
$pattern = fnGet(static::$instance->options, "verification_patterns.{$country}.mobile");
return $pattern ? (bool)preg_match($pattern, $mobile) : true;
}
public static function clearCache($memoryOnly = false)
{
static::$instance or static::$instance = static::$di->getShared('i18n');
static::$instance->locale = [];
if ($memoryOnly) {
return;
}
foreach (glob(static::$instance->localePath . '/*', GLOB_ONLYDIR) as $dir) {
$locale = basename($dir);
Cache::delete('locale.' . $locale);
}
is_file(static::$instance->undefinedStringsLogFile) and unlink(static::$instance->undefinedStringsLogFile);
static::$instance->undefinedStrings = [];
}
/**
* @codeCoverageIgnore
*/
public function exists($index)
{
return isset($this->locale[$this->currentLocale]['combined'][$index]);
}
public static function formatPrice($amount, $currency = 'CNY')
{
// TODO Use I18n config
$symbol = '¥';
$precision = 2;
$number = number_format($amount, $precision);
$sequence = 'symbolFirst';
return $sequence == 'symbolFirst' ? $symbol . $number : $number . $symbol;
}
public static function getAvailableLocales()
{
static::$instance or static::$instance = static::$di->getShared('i18n');
return static::$instance->availableLocales;
}
public static function getCurrentLocale()
{
static::$instance or static::$instance = static::$di->getShared('i18n');
//根据COOKIE中locale的值判断语言
if (true === static::$instance->options['detect_client_locale'] and !empty($_COOKIE['locale'])) {
static::$instance->currentLocale = $_COOKIE['locale'];
}
if (!in_array(static::$instance->currentLocale, array_flip(static::$instance->options['available_locales']))) {
static::$instance->currentLocale = static::$instance->options['default_locale'];
}
return static::$instance->currentLocale;
}
public function loadLocale($locale, $force = false, $moduleLocaleFiles = [])
{
if (isset($this->locale[$locale]) && !$force) {
return $this->locale[$locale];
}
$useCache = $this->options['cache_locale'];
$cacheKey = 'locale.' . $locale;
if ((empty($moduleLocaleFiles) || !IS_DEBUG) && $useCache && !$force && $cached = Cache::get($cacheKey)) {
$this->locale[$locale] = $cached;
return $this->locale[$locale];
}
$packages = [];
$combined = [];
$localeDir = $this->localePath . '/' . $this->currentLocale;
$localeFiles = array_merge(glob($localeDir . '/*.php'), glob($localeDir . '/*/*.php'), $moduleLocaleFiles);
foreach ($localeFiles as $file) {
$package = pathinfo($file, PATHINFO_FILENAME);
$package == 'error_code' and $package = 'error_codes';
$packageLocale = (array)include $file;
isset($packages[$package]) or $packages[$package] = [];
$packages[$package] = array_replace($packages[$package], $packageLocale);
$package == 'error_codes' or $combined = array_replace($combined, $packageLocale);
}
$this->locale[$locale] = compact('combined', 'packages');
$useCache and Cache::set($cacheKey, $this->locale[$locale]);
return $this->locale[$locale];
}
protected function loadUndefinedLocaleStrings()
{
is_file($file = $this->undefinedStringsLogFile) and $this->undefinedStrings = include $file;
return $this;
}
protected function logUndefinedLocaleStrings()
{
$file = $this->undefinedStringsLogFile;
fileSaveArray($file, $this->undefinedStrings);
}
public function query($string, $params = null, $package = null)
{
$locale = $this->currentLocale;
$package == 'error_code' and $package = 'error_codes';
if ($package && isset($this->locale[$locale]['packages'][$package][$string])) {
$translation = $this->locale[$locale]['packages'][$package][$string];
} elseif (isset($this->locale[$locale]['combined'][$string])) {
$translation = $this->locale[$locale]['combined'][$string];
} else {
if (!isset($this->undefinedStrings[$locale][$package][$string])) {
Log::debug("I18n: locale string not found: '{$string}'");
$this->loadUndefinedLocaleStrings();
// $this->undefinedStrings[$locale][$package][$string] = true;
$this->logUndefinedLocaleStrings();
}
$translation = $string;
}
return $params ? $this->replacePlaceholders($translation, $params) : $translation;
}
public static function register(Di $di)
{
static::$di = $di;
$di->setShared('i18n', function () {
return new static(Config::get('i18n'));
});
Events::attach('view:generatePhwoolconJsOptions', function (Event $event) {
$options = $event->getData() ?: [];
$options['locale'] = static::getCurrentLocale();
$event->setData($options);
return $options;
});
}
public function reset()
{
if (!$this->multiLocale) {
return;
}
$this->request or $this->request = static::$di->getShared('request');
if ($locale = $this->request->get('_locale')) {
$this->_setLocale($locale);
return;
}
if (($cookie = Cookies::get('locale')) && $locale = $cookie->useEncryption(false)->getValue()) {
$this->_setLocale($locale);
$cookie->setHttpOnly(false)
->delete();
return;
}
/*if ($locale = Session::get('current_locale')) {
$this->loadLocale($this->currentLocale = $locale);
return;
}*/
// @codeCoverageIgnoreStart
if ($this->detectClientLocale) {
if (empty($this->request->getBestLanguage())) {
// $locale = self::getCurrentLocale();
if (!empty($_COOKIE['locale'])) {
$locale = $_COOKIE['locale'];
if (!in_array($locale, array_flip($this->options['available_locales']))) {
$locale = $this->options['default_locale'];
}
} else {
$locale = $this->options['default_locale'];
}
} else {
$locale = $this->request->getBestLanguage();
}
$this->_setLocale($locale);
return;
}
// @codeCoverageIgnoreEnd
$this->loadLocale($this->currentLocale = $this->defaultLocale);
}
public static function staticReset()
{
static::$instance or static::$instance = static::$di->getShared('i18n');
static::$instance->reset();
}
public static function setLocale($locale)
{
static::$instance or static::$instance = static::$di->getShared('i18n');
static::$instance->_setLocale($locale);
}
public static function translate($string, array $params = null, $package = null)
{
static::$instance or static::$instance = static::$di->getShared('i18n');
return static::$instance->query($string, $params, $package);
}
public static function useMultiLocale($flag = null)
{
static::$instance or static::$instance = static::$di->getShared('i18n');
$flag === null or static::$instance->multiLocale = (bool)$flag;
return static::$instance->multiLocale;
}
}
<?php
namespace Phwoolcon;
use Phalcon\Di;
use Phalcon\Logger;
use Phalcon\Logger\Adapter\File;
use Phalcon\Logger\Formatter\Line;
use Phwoolcon\Exception\HttpException;
class Log extends Logger
{
/**
* @var \Phalcon\Logger\Adapter|File
*/
protected static $logger;
protected static $hostname;
/**
* @param string $message
* @param array $context
*/
public static function debug($message = null, array $context = [])
{
static::log(static::DEBUG, $message, $context);
}
/**
* @param string $message
* @param array $context
*/
public static function error($message = null, array $context = [])
{
static::log(static::ERROR, $message, $context);
}
/**
* @param \Throwable $e
*/
public static function exception($e)
{
$message = get_class($e);
$e instanceof HttpException or $message .= "\n" . $e->__toString();
static::error($message);
}
/**
* @param string $message
* @param array $context
*/
public static function info($message = null, array $context = [])
{
static::log(static::INFO, $message, $context);
}
/**
* @param string $type
* @param string $message
* @param array $context
*/
public static function log($type, $message = null, array $context = [])
{
static::$logger or static::$logger = Di::getDefault()->getShared('log');
$context['host'] = static::$hostname;
$context['request'] = fnGet($_SERVER, 'REQUEST_METHOD') . ' ' . fnGet($_SERVER, 'REQUEST_URI');
static::$logger->log($type, $message, $context);
}
public static function register(Di $di)
{
static::$hostname = gethostname();
$di->remove('log');
static::$logger = null;
$di->setShared('log', function () {
$filePath = storagePath('logs');
is_dir($filePath) or mkdir($filePath, 0777, true);
$filePath .= '/' . Config::get('app.log.file', 'phwoolcon.log');
$logger = new File($filePath);
$logger->setLogLevel(Config::get('app.log.level', Logger::NOTICE));
$formatter = $logger->getFormatter();
if ($formatter instanceof Line) {
$formatter->setDateFormat('Y-m-d H:i:s');
$formatter->setFormat('[%date%]{host}[%type%] {request} %message%');
}
return $logger;
});
}
/**
* @codeCoverageIgnore
* @param string $message
* @param array $context
*/
public static function warning($message = null, array $context = [])
{
static::log(static::WARNING, $message, $context);
}
}
<?php
namespace Phwoolcon;
use Phalcon\Di;
use Phwoolcon\Queue\Adapter\JobTrait;
use Swift;
use Swift_Attachment;
use Swift_Image;
use Swift_Mailer;
use Swift_Message;
use Swift_SendmailTransport;
use Swift_SmtpTransport;
use Swift_SwiftException;
class Mailer
{
const CONTENT_TYPE_HTML = 'text/html';
const CONTENT_TYPE_TEXT = 'text/plain';
/**
* @var Di
*/
protected static $di;
/**
* @var static
*/
protected static $instance;
protected static $mailerInitialized = false;
/**
* @var Swift_Mailer
*/
protected $mailerLibrary;
protected $async = false;
protected $enabled = false;
/**
* @var Queue\Adapter\DbQueue
*/
protected $queue;
protected $config;
protected $sender;
protected $logContent = true;
protected function __construct($config)
{
$this->config = $config;
$this->enabled = fnGet($config, 'enabled');
$this->logContent = fnGet($config, 'log_content');
$this->async = fnGet($config, 'async') && ($this->queue = Queue::connection('async_email_sending'));
$this->sender = [fnGet($config, 'sender.address') => fnGet($config, 'sender.name')];
switch (fnGet($config, 'driver')) {
case 'smtp':
$host = fnGet($config, 'smtp_host');
$port = fnGet($config, 'smtp_port');
$encryption = fnGet($config, 'smtp_encryption');
$username = fnGet($config, 'smtp_username');
$password = fnGet($config, 'smtp_password');
$transport = new Swift_SmtpTransport($host, $port, $encryption);
$transport->setUsername($username)->setPassword($password);
break;
// @codeCoverageIgnoreStart
case 'sendmail':
$transport = new Swift_SendmailTransport();
break;
default:
throw new Swift_SwiftException('Please specify available driver in mail config');
// @codeCoverageIgnoreEnd
}
$mailer = new Swift_Mailer($transport);
$this->mailerLibrary = $mailer;
}
/**
* @param array $definitions
* @return Swift_Attachment[]
*/
public function generateAttachments(array $definitions)
{
if (isset($definitions['data']) || isset($definitions['path'])) {
$definitions = [$definitions];
}
$attachments = [];
foreach ($definitions as $definition) {
// @codeCoverageIgnoreStart
if (is_string($definition)) {
$definition = ['path' => $definition];
}
// @codeCoverageIgnoreEnd
$filename = isset($definition['file_name']) ? $definition['file_name'] : null;
$contentType = isset($definition['file_type']) ? $definition['file_type'] : null;
if (isset($definition['data'])) {
$attachments[] = new Swift_Attachment($definition['data'], $filename, $contentType);
} elseif (isset($definition['path']) && is_file($definition['path'])) {
$attachment = Swift_Attachment::fromPath($definition['path'], $contentType);
$filename and $attachment->setFilename($filename);
$attachments[] = $attachment;
}
}
return $attachments;
}
/**
* @param JobTrait $job
* @param array $payload
*/
public static function handleQueueJob($job, array $payload)
{
static::$instance === null and static::$instance = static::$di->getShared('mailer');
call_user_func_array([static::$instance, 'realSend'], $payload);
}
protected function log($to, $subject, $body, $contentType, $cc)
{
$isHtml = $contentType == static::CONTENT_TYPE_HTML;
$fileExt = $isHtml ? '.html' : '.txt';
$firstTo = $to;
if (is_array($to)) {
foreach ($to as $email => $name) {
$firstTo = is_numeric($email) ? $name : $email;
break;
}
}
$dir = dirname($logFile = storagePath('mail/' . date('Ymd-His') . '.' . $firstTo . $fileExt));
is_dir($dir) or mkdir($dir, 0777, true);
$bodyText = is_array($body) ? $body['body'] : $body;
$additionalInfo = json_encode(
compact('to', 'subject', 'cc'),
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
$isHtml and $additionalInfo = "<pre>{$additionalInfo}</pre>";
file_put_contents($logFile, $bodyText . "\n\n" . $additionalInfo);
}
protected function queue($to, $subject, $body, $contentType = self::CONTENT_TYPE_TEXT, $cc = null)
{
$this->queue->push([static::class, 'handleQueueJob'], func_get_args());
}
public function realSend($to, $subject, $body, $contentType = self::CONTENT_TYPE_TEXT, $cc = null)
{
// Fetch body text
$bodyText = isset($body['body']) ? $body['body'] : $body;
$message = new Swift_Message($subject, $bodyText, $contentType);
// Process attachments
if (isset($body['attach'])) {
foreach ($this->generateAttachments((array)$body['attach']) as $attachment) {
$message->attach($attachment);
}
}
// Process inline medias
if (isset($body['embed']) && is_array($body['embed'])) {
$placeholders = [];
$replacements = [];
foreach ($body['embed'] as $placeholder => $embed) {
is_numeric($placeholder) and $placeholder = $embed;
$contentId = $message->embed(Swift_Image::fromPath($embed));
$placeholders[] = '%' . $placeholder . '%';
$replacements[] = $contentId;
}
$bodyText = str_replace($placeholders, $replacements, $bodyText);
$message->setBody($bodyText);
}
// Add receivers and sender
$message->setTo($to);
$cc and $message->setCc($cc);
$message->setFrom($this->sender);
// Send the mail
$sent = $this->mailerLibrary->send($message);
$this->mailerLibrary->getTransport()->stop();
return $sent;
}
public static function register(Di $di)
{
static::$di = $di;
$di->remove('mailer');
static::$instance = null;
if (!static::$mailerInitialized) {
static::$mailerInitialized = true;
Swift::autoload(Swift_Mailer::class);
}
$di->setShared('mailer', function () {
return new static(Config::get('mail'));
});
}
/**
* If `async` is disabled, the mail will be sent immediately (default behaviour)
* If `async` is enabled, the mail will be pushed into queue instead, then send it by a backend process:
* `bin/cli queue:consume async_email_sending`
*
* @param string|array $to Supports 'to@domain.com', ['to@domain.com'] or ['to@domain.com' => 'Display Name']
* @param string $subject
* @param string|array $body If you want to send attachment, or send embed media, please use array form:
* ['body' => 'Body text', 'attach' => $attachments, 'embed' => $embed]
* for the structure of $attachments, @see Mailer::generateAttachments()
* for the structure of $embed, @see Mailer::realSend()
* @param string $contentType
* @param string|array $cc @see $to
* @return int
*/
public static function send($to, $subject, $body, $contentType = self::CONTENT_TYPE_TEXT, $cc = null)
{
static::$instance === null and static::$instance = static::$di->getShared('mailer');
$mailer = static::$instance;
$mailer->logContent and $mailer->log($to, $subject, $body, $contentType, $cc);
if (!$mailer->enabled) {
return 0;
} elseif ($mailer->async && $mailer->queue) {
$mailer->queue($to, $subject, $body, $contentType, $cc);
return 1;
} else {
return $mailer->realSend($to, $subject, $body, $contentType, $cc);
}
}
}
<?php
namespace Phwoolcon\Model;
use Phwoolcon\Cache;
use Phwoolcon\Config as PhwoolconConfig;
use Phwoolcon\Db;
use Phwoolcon\Model;
use Phalcon\Db\Column;
class Config extends Model
{
protected $_table = 'config';
protected $_useDistributedId = false;
protected $_jsonFields = ['value'];
public static function all()
{
$environment = PhwoolconConfig::environment();
if (null === $config = Cache::get($key = 'db_configs_' . $environment)) {
$db = Db::connection();
$db->tableExists('config') or static::createConfigTable();
$config = [];
/* @var static $row */
foreach (static::find() as $row) {
$config[$row->getData('key')] = $row->getData('value');
}
Cache::set($key, $config, Cache::TTL_ONE_MONTH);
}
return $config;
}
protected static function createConfigTable()
{
$db = Db::connection();
$db->createTable('config', null, [
'columns' => [
new Column('key', [
'type' => Column::TYPE_VARCHAR,
'size' => 32,
'notNull' => true,
'primary' => true,
]),
new Column('value', [
'type' => Column::TYPE_TEXT,
]),
],
]);
if (PhwoolconConfig::runningUnitTest()) {
static::saveConfig('_testing', ['k' => 'v']);
}
}
public static function saveConfig($key, $value)
{
if (false === strpos($key, '.')) {
if ($value === null) {
if ($config = static::findFirstSimple(['key' => $key])) {
$config->delete();
PhwoolconConfig::clearCache();
}
return;
}
} // Ability to save a sub key
else {
list($key, $subKeys) = explode('.', $key, 2);
$subValue = $value;
if ($existing = static::findFirstSimple(['key' => $key])) {
$value = $existing->getData('value');
} else {
$value = [];
}
array_set($value, $subKeys, $subValue);
}
/* @var Config $config */
$config = new static;
$config->setData('key', $key)
->setData('value', (array)$value)
->save();
PhwoolconConfig::clearCache();
}
}
<?php
namespace Phwoolcon\Model\DynamicTrait;
trait EmptyTrait
{
}
<?php
namespace Phwoolcon\Model\DynamicTrait;
use Phalcon\Di;
use Phwoolcon\Text;
class Loader
{
protected static $instance;
protected $suffix = 'ModelTrait';
protected function __construct()
{
spl_autoload_register([$this, 'autoLoad']);
}
public static function register(Di $di)
{
static::$instance === null and static::$instance = new static();
}
public function autoLoad($className)
{
if (Text::endsWith($className, $this->suffix, false) && trait_exists(EmptyTrait::class)) {
class_alias(EmptyTrait::class, $className);
return true;
}
// @codeCoverageIgnoreStart
return false;
// @codeCoverageIgnoreEnd
}
}
<?php
namespace Phwoolcon\Model\MetaData;
use Phwoolcon\Cache;
use Phalcon\Mvc\ModelInterface;
use Phalcon\Mvc\Model\MetaData;
use Phalcon\Mvc\Model\Exception;
/**
* Phwoolcon\Model\MetaData\InCache
*
* Stores model meta-data in cache.
*
*<code>
* $metaData = new \Phwoolcon\Model\Metadata\InCache();
*</code>
*/
class InCache extends MetaData
{
protected $_metaData = [];
protected $_cachedData = [];
public function getNonPrimaryKeyAttributes(ModelInterface $model)
{
return $this->getAttributes($model);
}
/**
* Reads meta-data from files
*
* @param string $key
* @return mixed
*/
public function read($key)
{
$this->_cachedData or $this->_cachedData = Cache::get('model-metadata');
return isset($this->_cachedData[$key]) ? $this->_cachedData[$key] : null;
}
/**
* Writes the meta-data to files
*
* @param string $key
* @param array $data
*/
public function write($key, $data)
{
$this->_cachedData[$key] = $data;
Cache::set('model-metadata', $this->_cachedData, Cache::TTL_ONE_MONTH);
}
public function reset()
{
parent::reset();
$this->_cachedData = [];
Cache::delete('model-metadata');
}
}
<?php
namespace Phwoolcon\Model;
use Phwoolcon\Model;
/**
* Class User
* @package Phwoolcon\Model
*
* @method string getEmail()
* @method string getMobile()
* @method string getUsername()
* @method UserProfile|false getUserProfile()
* @method $this setUsername(string $username)
* @method $this setUserProfile(UserProfile $profile)
*/
class User extends Model
{
protected $_table = 'users';
protected function assignUserProfile()
{
return $this->setUserProfile($this->_dependencyInjector->get(UserProfile::class));
}
public function getAvatar()
{
$avatar = $this->getUserProfile() ? $this->getUserProfile()->getAvatar() : '';
return $avatar ? url($avatar) : '';
}
public function getRememberToken()
{
return ($profile = $this->getUserProfile()) ? $profile->getData('remember_token') : null;
}
public function initialize()
{
$class = UserProfile::class;
$this->hasOne('id', $class, 'user_id', ['alias' => 'user_profile']);
parent::initialize();
}
protected function prepareSave()
{
if (!$profile = $this->getUserProfile()) {
$this->assignUserProfile();
}
parent::prepareSave();
}
public function removeRememberToken()
{
if ($this->getRememberToken()) {
$profile = $this->getUserProfile();
$profile->setData('remember_token', null);
$profile->save();
}
return $this;
}
public function setRememberToken($rememberToken)
{
if ($profile = $this->getUserProfile()) {
$profile->setData('remember_token', $rememberToken);
$profile->save();
}
return $this;
}
}
<?php
namespace Phwoolcon\Model;
use Phwoolcon\Model;
use Phwoolcon\Text;
/**
* Class UserProfile
*
* @package Phwoolcon\Model
*
* @method string getAvatar()
* @method int getUserId()
*/
class UserProfile extends Model
{
protected $_table = 'user_profile';
protected $_pk = 'user_id';
protected $_jsonFields = ['extra_data'];
protected $extra_data = [];
public function generateAvatarUrl($id = null, $path = 'uploads/avatar')
{
$id === null and $id = $this->getId();
$subPath = substr(base62encode(crc32($id) + 62), 0, 2);
$fileName = base62encode($id);
$url = $path . '/' . $subPath . '/' . $fileName . '.png';
return $url;
}
public function generateResetPasswordToken()
{
$token = $this->getUserId() . '-' . Text::random();
$this->setExtraData('reset_password_token', $token);
$this->setExtraData('reset_password_token_created_at', time());
$this->save();
return $token;
}
public function getExtraData($key = null, $default = null)
{
return $key === null ? $this->extra_data : fnGet($this->extra_data, $key, $default);
}
public function getResetPasswordToken()
{
return $this->getExtraData('reset_password_token');
}
public function getResetPasswordTokenCreatedAt()
{
return $this->getExtraData('reset_password_token_created_at');
}
public function initialize()
{
$this->belongsTo('user_id', User::class, 'id', ['alias' => 'user']);
parent::initialize();
}
protected function prepareSave()
{
$this->getData('avatar') or $this->setData('avatar', $this->generateAvatarUrl());
$this->extra_data or $this->extra_data = [];
parent::prepareSave();
}
public function removeResetPasswordToken()
{
$this->setExtraData('reset_password_token');
$this->setExtraData('reset_password_token_created_at');
$this->save();
}
public function setExtraData($key, $value = null)
{
is_array($key) ? $this->extra_data = $key : array_set($this->extra_data, $key, $value);
return $this;
}
}
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!