adhocore/phalcon-ext

杂项phalcon适配器、扩展和实用工具

0.1.0 2020-01-03 14:37 UTC

README

有用的phalcon适配器、中间件、扩展和实用工具!

支持phalcon v4。

Travis Build Latest Version Scrutinizer CI Codecov branch StyleCI Software License Tweet Support

安装

composer require adhocore/phalcon-ext

包含内容

缓存

Cli

数据库

依赖注入

Http

日志记录器

邮件

Util

验证

视图

Cache.Redis

扩展 Phalcon\Cache\Backend\Redis 以允许通过底层的redis绑定进行访问。

设置

$di->setShared('redis', function () {
    return new \PhalconExt\Cache\Redis(new \Phalcon\Cache\Frontend\None(['lifetime' => 0]));
});

// Call native \Redis methods like:
$di->get('redis')->getConnection()->hGet();
$di->get('redis')->getConnection()->info();

Cli.Extension

请务必检查它在 adhocore/cli 中的工作方式以及它如何在 example/cliexample/MainTask.php 中集成和使用。

设置

$di = new PhalconExt\Di\FactoryDefault;

$di->setShared('dispatcher', Phalcon\Cli\Dispatcher::class);
$di->setShared('router', Phalcon\Cli\Router::class);

$di->setShared('config', new Phalcon\Config([
    'console' => [
        'tasks' => [
            // Register your tasks here: 'name' => class
            // You will define their options/arguments in respective `onConstruct()`
            'main' => Your\MainTask::class,
        ],
    ],
]));

$console = new PhalconExt\Cli\Console($di, 'MyApp', 'v1.0.1');

// Or if you have your own Console extends, just use the trait
class YourConsole extends \Phalcon\Cli\Console
{
    use PhalconExt\Cli\Extension;
}

command(string $command, string $descr = '', bool $allowUnknown = false): Ahc\Cli\Command

您可以在引导中注册命令,或者您可以将它们组织在 SomeTask::onConstruct() 中,如下所示

class MainTask extends Phalcon\Cli\Task
{
    public function onConstruct()
    {
        ($console = $this->getDI()->get('console'))
            ->command('main', 'Main task ...', false)
                ->arguments('<requiredPath> [optional:default]');
                ->option('-s --stuff', 'Description', 'callable:filter', 'default')
                ->tap($console) // for fluency
            ->schedule('7-9 * */9 * *')
            ->command('main:other', ...)
                ->option('')
                ->option('')
                ->tap($console)
            ->schedule('@5mintues') // @10minutes, @15minutes, @weekly, @daily, @yearly, @hourly ...
        ;
    }

    public function mainAction()
    {
        $io = $this->interactor;

        // Access defined args/opts for writing to terminal:
        $io->write($this->command->requiredPath, true);
        $io->write($this->command->stuff, true);
    }
}

现在每次您运行命令 php cli.php main main the-path --stuff whatever,它将打印 the-pathwhatever! cli.php 可以是您选择的任何内容,应与 example/cli 相当。

initTasks(void): self

初始化可加载的任务。如果您在 console.tasks 配置中列出了它们(请参阅上面的设置部分),则自动执行。如果您在过程中动态或较晚加载任务,请手动调用它:$console->initTasks()

Cli.MiddlewareTrait

允许您以最简单的方式定义、注册和触发CLI中的中间件!默认注册 PhalconExt/Cli/Middleware/Factory 以方便使用。它将相关的命令实例(Ahc\Cli\Input\Command)注入到DI中,并在 --help--version 时自动触发。

定义中间件

class HelloConsole
{
    // Prints hello every time you run a console cmd, just before execution
    public function before(PhalconExt\Cli\Console $console)
    {
        $console->getDI()->get('interactor')->bgGreenBold('Hello', true);

        return true; // Indicates success and no-objection!
    }
}

注册/检索中间件

// Single:
$console->middleware(HelloConsole::class);

// Multiple:
$console->middlewares([HelloConsole::class, Another::class]);

// Get em: (It already contains PhalconExt/Cli/Middleware/Factory)
$console->middlewares(); // array

触发中间件

您不必这样做。所有中间件的 beforeafter 方法都自动作为控制台生命周期事件调用。

Cli.Task.ScheduleTask

作为 adhocore/phalcon-ext 的工厂特性,它是自动加载的,因此您不需要将其放入配置的 console.tasks 数组中。

它提供了 schedule:list(或 schedule list)和 schedule:runschedule run 命令,分别用于列出所有计划的任务和运行所有在该特定时间到期的任务。

将任务注册为计划的任务也很简单。检查上面的 command() 部分,您可以通过流畅接口以这种方式安排一个任务

$console
    ->command('task:action', ...)
        ->arguments(...)->option(...)
        ->tap($console)
    ->schedule('crontab expression') // You can also use humanly phrases: @daily, @hourly
;

如您所见,您在crontab中需要做的所有事情就是添加以下条目:* * * * * php /path/to/your/phalcon-app-using-phalcon-ext/src/cli.php schedule:run ... 并在这里管理所有内容!

注意

任何通过自动化方式安排运行的作业,最好定义形式为<name>的必需参数或选项,因为首先,在作为计划任务运行时它们不会被验证,其次,计划的理念是注册单个crontab脚本* * * * * php /app/src/cli.php schedule:run,没有任何参数或选项。(如果你想要第三点,这些可以在无人值守的情况下运行!)

然而,如果你坚持这样做,可以在schedule:run部分之后附加--option-name value,但这个值会作用于当前所有可运行的待办任务。它们都可以通过$this->command->optionName读取。

Db.Extension

设置

$di->setShared('config', new \Phalcon\Config([
    'database' => [
        'driver' => 'sqlite',
        'dbname' => __DIR__ . '/.var/db.db',
    // ... other options (see phalcon &/or pdo docs)
    ],
]);

$di->setShared('db', function () {
    // Can use Mysql or Postgresql too
    return (new \PhalconExt\Db\Sqlite($this->get('config')->toArray()['database']));
});

// Or if you have your own already, just use the trait
class YourDb extends \Phalcon\Db\Adapter
{
    use PhalconExt\Db\Extension;
}

upsert(string $table, array $data, array $criteria): bool

根据给定的条件在指定表中插入或更新数据行。

$di->get('db')->upsert('users', ['name' => 'John'], ['username' => 'johnny']);

insertAsBulk(string $table, array $data): bool

一次性插入多个项目 - 在一个查询中 - 无循环。

$di->get('db')->insertAsBulk('table', [
    ['name' => 'name1', 'status' => 'status1'],
    ['details' => 'detail2', 'name' => 'name2'], // columns dont need to be ordered or balanced
]);

countBy(string $table, array $criteria): int

根据条件计算表中的行数。

$di->get('db')->countBy('table', ['name' => 'name1', 'status' => 'ok']);

Db.Logger

作为事件监听器挂钩到数据库,记录所有SQL查询 - 绑定是内插的。

$di->setShared('config', new \Phalcon\Config([
    'sqllogger' => [
        'enabled'        => true,
        'logPath'        => __DIR__ . '/.var/sql/', // directory
        'addHeader'      => true,
        'backtraceLevel' => 5,
        'skipFirst'      => 2,
    ],
]);

$di->get('db')->registerLogger($di->get('config')->toArray()['sqllogger']);

Di.Extension

前言 整个示例和整个 phalcon-ext 包几乎总是使用 $di->get('service') 而不是 $di->getShared('service'),这是因为如果你将 'service' 设置为共享的,则 get() 将会一次又一次地返回相同的共享实例,如果没有,则将生成新的实例 - 重点是,如果我们不想要新的实例,为什么不使用 setShared()?必须自觉地思考是使用 setShared() 还是 set() 而不是使用 getShared()get()

设置

$di = new \PhalconExt\Di\FactoryDefault;

// Or if you have your own already, just use the trait
class YourDi extends \Phalcon\Di
{
    use PhalconExt\Di\Extension;
}

registerAliases(array $aliases): self

为di服务注册别名,以便它们可以通过名称 &/ 或类型提示自动解析。

$di->registerAliases([
    'TheAlias'                 => 'service',
    \Phalcon\Db\Adapter::class => 'db',
]);

resolve(string $class, array $parameters = []): mixed

递归解析给定类的FQCN的所有依赖项,并返回新实例。

$instance = $di->resolve(\Some\Complex\ClassName::class, $parameters);

replace(array $services): self

覆盖di服务,但保留备份以便在需要时可以恢复(非常适合测试)

$di->replace(['service' => new \MockedService]);

restore(?string $service)

将覆盖的服务恢复到它们的默认值。

$di->restore();          // All
$di->restore(['service']); // One

Di.ProvidesDi

di(?string $service): mixed

使用此快捷方式轻松解析di服务。

class AnyClass
{
    use \PhalconExt\Di\ProviesDi;

    public function anyFn()
    {
        $di = $this->di();
        $db = $this->di('db');
    }
}

Http.BaseMiddleware

中间件的基础实现,你可以在此基础上创建自己的中间件。你只需实现一个或两个接收requestresponse对象的before() &/ 或 after()方法。请参见Ajax中间件的示例

$di->setShared('config', new \Phalcon\Config([
    'ajax' => [
        'uriPrefix' => '/ajax',
    ],
]);

class Ajax extends \PhalconExt\Http\BaseMiddleware
{
    /** @var string The root key in config having settings for Ajax middleware */
    protected $configKey = 'ajax';

    /**
     * For any uri starting with `/ajax`, allow if only it is real ajax request.
     *
     * Register as before handler because we will abort before actual exceution if not ajax.
     *
     * @return bool
     */
    public function before(Phalcon\Http\Request $request, Phalcon\Http\Response $response): bool
    {
        list(, $uri) = $this->getRouteNameUri();

        if (\stripos($uri, $this->config['uriPrefix']) !== 0) {
            return true;
        }

        if (!$request->isAjax()) {
            // Aborts/stops the app. All other middlewares down the line are skipped
            return $this->abort(400);
        }

        return true;
    }
}

// Usage is pretty simple:
// Create an app!
$app = new Phalcon\Mvc\Application($di);
// OR micro
$app = new Phalcon\Mvc\Micro($di);

// Wrap the app with middleware and run it
(new PhalconExt\Http\Middlewares([Ajax::class]))->wrap($app);

Http.Middleware.ApiAuth

基于JWT的API身份验证中间件,它拦截POST /api/auth请求,并根据grant_type生成或刷新access_token。对于所有其他请求,它检查Authorization: Bearer <JWT>,并且只有在它是有效的并且范围满足条件时才允许。你可以根据端点配置范围。你可以通过在整个应用程序中使用以下方式访问当前认证的用户

$di->get('authenticator')->getSubject();

设置

$di->setShared('config', new \Phalcon\Config([
    'apiAuth' => [
        // 14 days in seconds (http://stackoverflow.com/questions/15564486/why-do-refresh-tokens-expire-after-14-days)
        'refreshMaxAge'  => 1209600,
        // Prefix to use in stored tokens (max 4 chars)
        'tokenPrefix'    => 'RF/',
        // The route to generate/refresh access tokens.
        // genrerate: curl -XPOST -d 'grant_type=password&username=&password=' /api/auth
        // refresh:   curl -XPOST -d 'grant_type=refresh_token&refresh_token=' /api/auth
        // It can also accept json payload:
        //   -H 'content-type: application/json' -d {"grant_type":"refresh_token","refresh_token":""}
        'authUri' => '/api/auth',

        // The permission scopes required for a route
        'scopes' => [
            '/some/uri' => 'admin',
            '/next/uri' => 'user',
        ],

        // Json Web tokens configuration.
        'jwt'            => [
            'keys'       => [
                // kid => key (first one is default always)
                'default' => '*((**@$#@@KJJNN!!#D^G&(U)KOIHIYGTFD',
            ],
            'algo'       => 'HS256',
            // 15 minutes in seconds.
            'maxAge'     => 900,
            // Grace time in seconds.
            'leeway'     => 10,
            // Only for RS algo.
            'passphrase' => '',
            // Name of the app/project.
            'issuer'     => '',
        ],
    ],
]);

// Usage:
(new PhalconExt\Http\Middlewares([
    PhalconExt\Http\Middleware\ApiAuth::class,
]))->wrap(new Phalcon\Mvc\Micro($di));

Http.Middleware.Cache

通过缓存请求的输出以大幅提高性能。需要redis服务。目前,根据设计,只有GET请求被缓存,这可能会改变。

设置

$di->setShared('config', new \Phalcon\Config([
    'httpCache' => [
        // cache life- time to live in mintues
        'ttl'       => 60,
        // White listed uri/routes to enable caching
        'routes'    => [
            // for absolute uri, prepend forward `/`
            '/content/about-us',
            // or you can use route name without a `/`
            'home',
        ],
    ],
]);

Http.Middleware.Cors

为配置的源和请求选项启用cors预检。

设置

$di->setShared('config', new \Phalcon\Config([
    'cors' => [
        'exposedHeaders' => [],
        // Should be in lowercases.
        'allowedHeaders' => ['x-requested-with', 'content-type', 'authorization'],
        // Should be in uppercase.
        'allowedMethods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
        // Requests originating from here can entertain CORS.
        'allowedOrigins' => [
            'http://127.0.0.1:1234',
        ],
        // Cache preflight for 7 days (expressed in seconds).
        'maxAge'         => 604800,
    ],
]);

Http.Middleware.Throttle

根据你选择的时间和配额限制流量。需要redis服务。

设置

$di->setShared('config', new \Phalcon\Config([
    'throttle' => [
        'maxHits' => [
            // Mintues => Max Hits
            1    => 10,
            60   => 250,
            1440 => 4500,
        ],
        'checkUserAgent' => false,
        // Cache key prefix
        'prefix'         => '_',
    ],
]);

使用方法

可以使用PhalconExt\Http\Middlewares管理器作为应用程序的包装器来使用中间件。

$app = new Phalcon\Mvc\Micro($di);

// Set all your middlewares in an array using class FQCN, they are lazily loaded
// They are executed in order of their presence
// If a middleware returns `false` from its `before()` or `after()` events,
// all other middlewares down the line are skipped
$middlewares = new PhalconExt\Http\Middlewares([
    PhalconExt\Http\Middleware\Throttle::class,
    PhalconExt\Http\Middleware\ApiAuth::class,
    PhalconExt\Http\Middleware\Cors::class,
    PhalconExt\Http\Middleware\Cache::class,
]);

// Wrap and run the app!
$middlewares->wrap($app);

// The app is wrapped and run automatically so you dont have to do:
// $app->handle();

Logger.EchoLogger

log(string $message, int $type, array $context = [])

立即回显任何内容 - 但你可以控制格式和日志级别。

$echo = $this->di(\PhalconExt\Logger\EchoLogger::class, ['config' => ['level' => Logger::INFO]]);
$echo->log('Message {a}', \Phalcon\Logger::INFO, ['a' => 'ok']);

Logger.LogsToFile

log(string $message, int $type, array $context = [])

将平凡的文件记录任务委托给此特质,从而减少了样板代码。

class AnyClass
{
    use \PhalconExt\Logger\LogsToFile;

    protected $fileExtension = '.log';

    public function anyFn()
    {
        $this->activate('/path/to/log/dir/');

        $this->log('Some message', \Phalcon\Logger::INFO);
    }
}

Mail.Mailer

Phalcon适配器/桥梁/容器/代理(读取:abcd)到swiftmailer。

设置

$di->setShared('config', new \Phalcon\Config([
    'mail' => [
        'driver' => 'null',
        'from'   => [
            'name'  => 'Test',
            'email' => 'test@localhost',
        ],

        // for driver 'smtp':
        'host'       => 'smtp.server.com',
        'port'       => 425,
        'encryption' => true,
        'username'   => 'user',
        'password'   => 'pass',

        // for driver sendmail only (optional)
        'sendmail' => '/sendmail/binary',
    ],
]);

$di->setShared('mailer', function () {
    return new \PhalconExt\Mail\Mailer($this->get('config')->toArray()['mail']);
});

Mail.Mail

Swiftmail消息的子类,允许轻松附加附件。

$mail = $di->get('mailer')->newMail();
// Or from view template
$mail = $di->get('mailer')->newTemplateMail('view/file.twig', ['view' => 'params']);

$mail->setTo('test@localhost')->setSubject('Hi')->setBody('Hello')->mail();

// Attachments:
$mail->attachFile('/path/to/file', 'optional attachment name');

$mail->attachFiles(['/path/to/file1', '/path/to/file2']);
// OR
$mail->attachFiles([
    'attachment name 1' => '/path/to/file1',
    'attachment name 2' => '/path/to/file2',
]);

$mail->attachRaw('Raw plain text data', 'rawtext.txt', 'text/plain');

Mail.Mailable

mail()

类似于Logger.LogsToFile,但用于邮件。

class AnyClass
{
    use \PhalconExt\Mail\Mailable;

    public function anyFn()
    {
        $this->mail('test@local', 'Hi', ['body' => 'Hello']);
        $this->mail('test@local', 'Hi', ['template' => 'view/file.twig', 'params' => ['key' => 'value']]);
    }
}

Mail.Logger

自动将所有发送的邮件记录到文件,作为Swiftmailer的事件监听器-您可以选择日志格式:eml | html | json

设置

$di->setShared('config', new \Phalcon\Config([
    'mail' => [
        'driver' => 'null',
        'from'   => [
            'name'  => 'Test',
            'email' => 'test@localhost',
        ],
        'logger' => [
            'enabled' => true,
            'logPath' => __DIR__ . '/.var/mail/', // directory
            'type'    => 'eml', // options: json, html, eml
        ],
    ],
]);

// When setting mailer, include config `mail>logger` and it is auto set up.
$di->setShared('mailer', function () {
    return new \PhalconExt\Mail\Mailer($this->get('config')->toArray()['mail']);
});

Util.OpcachePrimer

prime(array $paths): int

确保在文件执行之前很好地预热给定路径中的所有文件的opcache。Opcache缓存特定于运行它的sapi。因此,对于Web,您需要有一个端点

$primer = new \PhalconExt\Util\OpcachePrimer;

$total = $primer->prime(['/path/to/project/src', '/path/to/project/app/', '/path/to/project/vendor/']);

Validation.Validation

验证数据,就像我们在其他地方做的那样-将规则设置为.well-known字符串或key=>value对(数组)。

设置

$di->setShared('validation', \PhalconExt\Validation\Validation::class);

register(string $ruleName, $handler, string $message = ''): self

注册一个新的验证规则。

$di->get('validation')->register('gmail', function ($data) {
    // You can access current validation instance with `$this`
    // You can also access current validator options with `$this->getOption(...)`
    return stripos($this->getCurrentValue(), '@gmail.com') > 0;
}, 'Field :field must be an email with @gmail.com');

registerRules(array $ruleHandlers, array $messages = []): self

一次注册多个新的验证规则。

$di->get('validation')->registerRules([
    'rule1' => function($data) { return true; },
    'rule1' => function($data) { return false; },
], [
    'rule1' => 'message1',
    'rule2' => 'message2'
]);

使用方法

$validation = $this->di('validation');

$rules = [
    // Can be string (With `abort` if the field `id` is invalid, following validations are aborted)
    'id'    => 'required|length:min:1;max:2;|in:domain:1,12,30|abort',
    // Can be an array too
    'email' => [
        'required' => true,
        'gmail'    => true,
        // With `abort` if the field `email` is invalid, following validations are aborted
        'abort'   => true,
    ],
    // validate if only exist in dataset
    'xyz' => 'length:5|if_exist',
];

// Validate against empty data (can be array or object)
$data = []; // OR $data = new \stdClas OR $data = new SomeClass($someData)
$validation->run($rules, $data);

$pass = $validation->pass(); // false
$fail = $validation->fail(); // true

$errors = $validation->getErrorMessages(); // array

Validation.Existence

验证是否在数据库中存在某物。您可以可选地设置要检查的表和列。

// Checks `users` table for `id` column with value 1
$rules = ['users' => 'exist'];
$data  = ['users' => 1]; // Data can be array

// Checks `users` table for `username` column with value 'admin'
$rules = ['username' => 'exist:table:users'];
$data  = new User(['username' => 'admin']); // Data can be model/entity

// Checks `users` table for `login` column with value 'admin@localhost'
$rules = ['email' => 'exist:table:users;column:login'];
$data  = (object) ['email' => 'admin@localhost']; // Data can be any Object

// Run the rules
$validation->run($rules, $data);

View.Twig

在Phalcon中原生使用twig视图。

设置

$di->setShared('config', new \Phalcon\Config([
    'view' => [
        'dir' => __DIR__ . '/view/',
    ],
    // Required
    'twig' => [
        'view_dirs'   => [__DIR__ . '/view/'], // array
        'auto_reload' => getenv('APP_ENV') !== 'prod',
        'cache'       => __DIR__ . '/.var/view/',
        // ... other options (see twig docs)
    ],
]);

// You must have view setup with twig engine enabled.
$di->setShared('view', function () {
    return (new View)
        ->setViewsDir($this->get('config')->toArray()['view']['dir'])
        ->registerEngines([
            '.twig' => 'twig',
        ]);
});

$di->setShared('twig', function () {
    $twig = new PhalconExt\View\Twig($this->get('view'), $this);

    // Here you can:
    // $twig->addFilter(...)
    // $twig->addExtension(...)

    return $twig;
});

使用方法

// standalone
$di->get('twig')->render('template.twig', ['view' => 'params']);
// or as view
$di->get('view')->render('template.twig', ['view' => 'params']); // .twig is optional

您还可以查看示例代码。阅读更多

相关项目

许可

© 2017-2020, Jitendra Adhikari | MIT

鸣谢

此项目由please管理。