{"id":13405387,"url":"https://github.com/adhocore/phalcon-ext","last_synced_at":"2025-04-10T16:51:20.005Z","repository":{"id":49147976,"uuid":"136166947","full_name":"adhocore/phalcon-ext","owner":"adhocore","description":"Foundations, adapters, extensions, middlewares and utilities for Phalcon v4","archived":false,"fork":false,"pushed_at":"2023-05-21T15:21:19.000Z","size":335,"stargazers_count":47,"open_issues_count":5,"forks_count":3,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-03-24T14:44:08.687Z","etag":null,"topics":["adhocore","argv-parser","console-application","cors","logger","middleware","outputcache","phalcon","phalcon-adapters","phalcon-ext","phalcon-jwt","php7","swiftmailer","task-scheduler","throttle","twig","validation"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/adhocore.png","metadata":{"files":{"readme":"readme.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null},"funding":{"github":"adhocore","custom":["https://paypal.me/ji10"]}},"created_at":"2018-06-05T11:26:07.000Z","updated_at":"2024-10-02T00:54:53.000Z","dependencies_parsed_at":"2024-01-18T23:15:27.848Z","dependency_job_id":null,"html_url":"https://github.com/adhocore/phalcon-ext","commit_stats":{"total_commits":350,"total_committers":4,"mean_commits":87.5,"dds":0.02857142857142858,"last_synced_commit":"3f61ec6a6e77044e49e99d36f84bbfea364b23a5"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adhocore%2Fphalcon-ext","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adhocore%2Fphalcon-ext/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adhocore%2Fphalcon-ext/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adhocore%2Fphalcon-ext/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/adhocore","download_url":"https://codeload.github.com/adhocore/phalcon-ext/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248255943,"owners_count":21073423,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["adhocore","argv-parser","console-application","cors","logger","middleware","outputcache","phalcon","phalcon-adapters","phalcon-ext","phalcon-jwt","php7","swiftmailer","task-scheduler","throttle","twig","validation"],"created_at":"2024-07-30T19:02:00.629Z","updated_at":"2025-04-10T16:51:19.973Z","avatar_url":"https://github.com/adhocore.png","language":"PHP","readme":"## adhocore/phalcon-ext\n\nUseful phalcon adapters, middlewares, extensions and utilities!\n\u003e Supports phalcon v4.\n\n[![Travis Build](https://travis-ci.com/adhocore/phalcon-ext.svg?branch=master)](https://travis-ci.com/adhocore/phalcon-ext?branch=master)\n[![Latest Version](https://img.shields.io/github/release/adhocore/phalcon-ext.svg?style=flat-square)](https://github.com/adhocore/phalcon-ext/releases)\n[![Scrutinizer CI](https://img.shields.io/scrutinizer/g/adhocore/phalcon-ext.svg?style=flat-square)](https://scrutinizer-ci.com/g/adhocore/phalcon-ext/?branch=master)\n[![Codecov branch](https://img.shields.io/codecov/c/github/adhocore/phalcon-ext/master.svg?style=flat-square)](https://codecov.io/gh/adhocore/phalcon-ext)\n[![StyleCI](https://styleci.io/repos/136166947/shield)](https://styleci.io/repos/136166947)\n[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Adapters+middlewares+extensions+and+utilities+missing+in+phalcon+framework\u0026url=https://github.com/adhocore/phalcon-ext\u0026hashtags=php,phalcon,extension,middleware)\n[![Support](https://img.shields.io/static/v1?label=Support\u0026message=%E2%9D%A4\u0026logo=GitHub)](https://github.com/sponsors/adhocore)\n\u003c!-- [![Donate 15](https://img.shields.io/badge/donate-paypal-blue.svg?style=flat-square\u0026label=donate+15)](https://www.paypal.me/ji10/15usd)\n[![Donate 25](https://img.shields.io/badge/donate-paypal-blue.svg?style=flat-square\u0026label=donate+25)](https://www.paypal.me/ji10/25usd)\n[![Donate 50](https://img.shields.io/badge/donate-paypal-blue.svg?style=flat-square\u0026label=donate+50)](https://www.paypal.me/ji10/50usd) --\u003e\n\n\n## Installation\n\n```sh\ncomposer require adhocore/phalcon-ext\n```\n\n### What's included\n\n**Cache**\n- [Redis](#cacheredis)\n\n**Cli**\n- [Extension](#cliextension)\n- [Middleware](#climiddlewaretrait)\n- [Scheduling](#clitaskscheduletask)\n\n**Db**\n- [Extension](#dbextension)\n- [Logger](#dblogger)\n\n**Di**\n- [Extension](#diextension)\n- [Provider](#diprovidesdi)\n\n**Http**\n- [Base Middleware](#httpbasemiddleware)\n- [ApiAuth Middleware](#httpmiddlewareapiauth)\n- [Cache Middleware](#httpmiddlewarecache)\n- [Throttle Middleware](#httpmiddlewarethrottle)\n\n**Logger**\n- [Echo Logger](#loggerechologger)\n- [File Logging](#loggerlogstofile)\n\n**Mail**\n- [Mailer](#mailmailer)\n- [Mail](#mailmail)\n- [Mailable](#mailmailable)\n- [Logger](#maillogger)\n\n**Util**\n- [Opcache Primer](#utilopcacheprimer)\n\n**Validation**\n- [Validation Wrapper](#validationvalidation)\n- [Db Existence Validator](#validationexistence)\n\n**View**\n- [Twig](#viewtwig)\n\n---\n### Cache.Redis\n\nExtends `Phalcon\\Cache\\Backend\\Redis` to allow access over the underlying redis binding.\n\n#### Setup\n\n```php\n$di-\u003esetShared('redis', function () {\n    return new \\PhalconExt\\Cache\\Redis(new \\Phalcon\\Cache\\Frontend\\None(['lifetime' =\u003e 0]));\n});\n\n// Call native \\Redis methods like:\n$di-\u003eget('redis')-\u003egetConnection()-\u003ehGet();\n$di-\u003eget('redis')-\u003egetConnection()-\u003einfo();\n```\n\n---\n### Cli.Extension\n\nDefinitely check how it works in [adhocore/cli](https://github.com/adhocore/cli) and how it is integrated \u0026 used in [example/cli](./example/cli), [example/MainTask.php](./example/MainTask.php)\n\n#### Setup\n\n```php\n$di = new PhalconExt\\Di\\FactoryDefault;\n\n$di-\u003esetShared('dispatcher', Phalcon\\Cli\\Dispatcher::class);\n$di-\u003esetShared('router', Phalcon\\Cli\\Router::class);\n\n$di-\u003esetShared('config', new Phalcon\\Config([\n    'console' =\u003e [\n        'tasks' =\u003e [\n            // Register your tasks here: 'name' =\u003e class\n            // You will define their options/arguments in respective `onConstruct()`\n            'main' =\u003e Your\\MainTask::class,\n        ],\n    ],\n]));\n\n$console = new PhalconExt\\Cli\\Console($di, 'MyApp', 'v1.0.1');\n\n// Or if you have your own Console extends, just use the trait\nclass YourConsole extends \\Phalcon\\Cli\\Console\n{\n    use PhalconExt\\Cli\\Extension;\n}\n```\n\n#### command(string $command, string $descr = '', bool $allowUnknown = false): Ahc\\Cli\\Command\n\nYou can register command in the bootstrap if few or you can organise them in `SomeTask::onConstruct()` like so:\n\n```php\nclass MainTask extends Phalcon\\Cli\\Task\n{\n    public function onConstruct()\n    {\n        ($console = $this-\u003egetDI()-\u003eget('console'))\n            -\u003ecommand('main', 'Main task ...', false)\n                -\u003earguments('\u003crequiredPath\u003e [optional:default]');\n                -\u003eoption('-s --stuff', 'Description', 'callable:filter', 'default')\n                -\u003etap($console) // for fluency\n            -\u003eschedule('7-9 * */9 * *')\n            -\u003ecommand('main:other', ...)\n                -\u003eoption('')\n                -\u003eoption('')\n                -\u003etap($console)\n            -\u003eschedule('@5mintues') // @10minutes, @15minutes, @weekly, @daily, @yearly, @hourly ...\n        ;\n    }\n\n    public function mainAction()\n    {\n        $io = $this-\u003einteractor;\n\n        // Access defined args/opts for writing to terminal:\n        $io-\u003ewrite($this-\u003ecommand-\u003erequiredPath, true);\n        $io-\u003ewrite($this-\u003ecommand-\u003estuff, true);\n    }\n}\n```\n\nNow everytime you run command `php cli.php main main the-path --stuff whatever`, it will print `the-path` and `whatever`!\n`cli.php` can be anything of your choosing that should be equivalent to [example/cli](./example/cli).\n\n#### initTasks(void): self\n\nInits the loadable tasks. It is done automatically if you have listed them in `console.tasks` config (see Setup section above).\nIf you have loaded tasks dyanamically or late in the process call it manually: `$console-\u003einitTasks()`.\n\n#### Cli.MiddlewareTrait\n\nEnables you to define, register and fire middlewares for the cli in simplest possible way!\n`PhalconExt/Cli/Middleware/Factory` is registered by default for convenience. It injects relevant command instance (`Ahc\\Cli\\Input\\Command`) to DI and is auto triggered for `--help`, `--version`.\n\n**Define middleware**\n```php\nclass HelloConsole\n{\n    // Prints hello every time you run a console cmd, just before execution\n    public function before(PhalconExt\\Cli\\Console $console)\n    {\n        $console-\u003egetDI()-\u003eget('interactor')-\u003ebgGreenBold('Hello', true);\n\n        return true; // Indicates success and no-objection!\n    }\n}\n```\n\n**Register/retrieve middleware(s)**\n\n```php\n// Single:\n$console-\u003emiddleware(HelloConsole::class);\n\n// Multiple:\n$console-\u003emiddlewares([HelloConsole::class, Another::class]);\n\n// Get em: (It already contains PhalconExt/Cli/Middleware/Factory)\n$console-\u003emiddlewares(); // array\n```\n\n**Firing middlewares**\n\nYou dont have to. The `before` and `after` methods of all middlewares are automatically invoked as console lifecycle event.\n\n### Cli.Task.ScheduleTask\n\nBeing factory feature of `adhocore/phalcon-ext` it is auto loaded so you dont have to put in config's `console.tasks` array.\n\nIt provides for commands `schedule:list` (or `schedule list`) and `schedule:run` or `schedule run` to respectively list all scheduled commands and run all commands due at that specific moment.\n\nRegistering tasks to be scheduled is a cheese too. Check `command()` section above, you can schedule a task in fluent interface like so:\n\n```php\n$console\n    -\u003ecommand('task:action', ...)\n        -\u003earguments(...)-\u003eoption(...)\n        -\u003etap($console)\n    -\u003eschedule('crontab expression') // You can also use humanly phrases: @daily, @hourly\n;\n```\n\nAs you can see, all you need to do in crontab is add the entry:\n`* * * * * php /path/to/your/phalcon-app-using-phalcon-ext/src/cli.php schedule:run`\n... and manage everything here in the code!\n\n#### Caution\n\nAny tasks scheduled to run by automation like this should preferably **not** define required arguments or options of the form `\u003cname\u003e`, as firstly they are not validated when being run as scheduled task, and secondly the philosphy of scheduling is to register single crontab script `* * * * * php /app/src/cli.php schedule:run` with no any args/options. (and these are made to run unattended if you want third point!)\n\nHowever if you insist, it is possible to append `--option-name value` after `schedule:run` segment but this value goes to all of the due tasks runnable at the moment. They all can read it with `$this-\u003ecommand-\u003eoptionName`.\n\n---\n### Db.Extension\n\n#### Setup\n\n```php\n$di-\u003esetShared('config', new \\Phalcon\\Config([\n    'database' =\u003e [\n        'driver' =\u003e 'sqlite',\n        'dbname' =\u003e __DIR__ . '/.var/db.db',\n    // ... other options (see phalcon \u0026/or pdo docs)\n    ],\n]);\n\n$di-\u003esetShared('db', function () {\n    // Can use Mysql or Postgresql too\n    return (new \\PhalconExt\\Db\\Sqlite($this-\u003eget('config')-\u003etoArray()['database']));\n});\n\n// Or if you have your own already, just use the trait\nclass YourDb extends \\Phalcon\\Db\\Adapter\n{\n    use PhalconExt\\Db\\Extension;\n}\n```\n\n#### upsert(string $table, array $data, array $criteria): bool\n\nInsert or update data row in given table as per given criteria.\n```php\n$di-\u003eget('db')-\u003eupsert('users', ['name' =\u003e 'John'], ['username' =\u003e 'johnny']);\n```\n\n#### insertAsBulk(string $table, array $data): bool\n\nInsert many items at once - in one query - no loop.\n```php\n$di-\u003eget('db')-\u003einsertAsBulk('table', [\n    ['name' =\u003e 'name1', 'status' =\u003e 'status1'],\n    ['details' =\u003e 'detail2', 'name' =\u003e 'name2'], // columns dont need to be ordered or balanced\n]);\n```\n\n#### countBy(string $table, array $criteria): int\n\nCount rows in table by criteria.\n\n```php\n$di-\u003eget('db')-\u003ecountBy('table', ['name' =\u003e 'name1', 'status' =\u003e 'ok']);\n```\n\n### Db.Logger\n\nHook into the db as an event listener and log all the sql queries- binds are interpolated.\n```php\n$di-\u003esetShared('config', new \\Phalcon\\Config([\n    'sqllogger' =\u003e [\n        'enabled'        =\u003e true,\n        'logPath'        =\u003e __DIR__ . '/.var/sql/', // directory\n        'addHeader'      =\u003e true,\n        'backtraceLevel' =\u003e 5,\n        'skipFirst'      =\u003e 2,\n    ],\n]);\n\n$di-\u003eget('db')-\u003eregisterLogger($di-\u003eget('config')-\u003etoArray()['sqllogger']);\n```\n\n---\n### Di.Extension\n\n**Foreword** This whole example and the entire `phalcon-ext` package almost always used `$di-\u003eget('service')` and not `$di-\u003egetShared('service')`, this is because if you have set 'service' as shared, `get()` will return that same shared instance again and again and if not then it will spawn new instance- the point is if we dont want new instance why dont we `setShared()` it? One has to consciously think whether to `setShared()` or `set()` instead of `getShared()` or `get()`.\n\n#### Setup\n\n```php\n$di = new \\PhalconExt\\Di\\FactoryDefault;\n\n// Or if you have your own already, just use the trait\nclass YourDi extends \\Phalcon\\Di\n{\n    use PhalconExt\\Di\\Extension;\n}\n```\n\n#### registerAliases(array $aliases): self\n\nRegister aliases for di service so they can be resolved automatically by name \u0026/or typehints.\n\n```php\n$di-\u003eregisterAliases([\n    'TheAlias'                 =\u003e 'service',\n    \\Phalcon\\Db\\Adapter::class =\u003e 'db',\n]);\n```\n\n#### resolve(string $class, array $parameters = []): mixed\n\nRecursively resolve all dependencies of a given class FQCN and return new instance.\n\n```php\n$instance = $di-\u003eresolve(\\Some\\Complex\\ClassName::class, $parameters);\n```\n\n#### replace(array $services): self\n\nOverride a di service but keep backup so it may be restored if needed (great for tests)\n\n```php\n$di-\u003ereplace(['service' =\u003e new \\MockedService]);\n```\n\n#### restore(?string $service)\n\nRestore the overridden services to their usual defaults.\n\n```php\n$di-\u003erestore();          // All\n$di-\u003erestore(['service']); // One\n```\n\n### Di.ProvidesDi\n\n#### di(?string $service): mixed\n\nEasily resolve di services with this shortcut.\n\n```php\nclass AnyClass\n{\n    use \\PhalconExt\\Di\\ProviesDi;\n\n    public function anyFn()\n    {\n        $di = $this-\u003edi();\n        $db = $this-\u003edi('db');\n    }\n}\n```\n\n---\n### Http.BaseMiddleware\n\nA base implementation for middlewares on top of which you can create your own middlewares.\nYou just have to implement one or both of `before()` \u0026/or `after()` methods that recieves `request` and `response` objects.\nSee an example for Ajax middleware:\n\n```php\n$di-\u003esetShared('config', new \\Phalcon\\Config([\n    'ajax' =\u003e [\n        'uriPrefix' =\u003e '/ajax',\n    ],\n]);\n\nclass Ajax extends \\PhalconExt\\Http\\BaseMiddleware\n{\n    /** @var string The root key in config having settings for Ajax middleware */\n    protected $configKey = 'ajax';\n\n    /**\n     * For any uri starting with `/ajax`, allow if only it is real ajax request.\n     *\n     * Register as before handler because we will abort before actual exceution if not ajax.\n     *\n     * @return bool\n     */\n    public function before(Phalcon\\Http\\Request $request, Phalcon\\Http\\Response $response): bool\n    {\n        list(, $uri) = $this-\u003egetRouteNameUri();\n\n        if (\\stripos($uri, $this-\u003econfig['uriPrefix']) !== 0) {\n            return true;\n        }\n\n        if (!$request-\u003eisAjax()) {\n            // Aborts/stops the app. All other middlewares down the line are skipped\n            return $this-\u003eabort(400);\n        }\n\n        return true;\n    }\n}\n\n// Usage is pretty simple:\n// Create an app!\n$app = new Phalcon\\Mvc\\Application($di);\n// OR micro\n$app = new Phalcon\\Mvc\\Micro($di);\n\n// Wrap the app with middleware and run it\n(new PhalconExt\\Http\\Middlewares([Ajax::class]))-\u003ewrap($app);\n```\n\n#### Http.Middleware.ApiAuth\n\nJWT based api authentication middleware that intercepts `POST /api/auth` request and generates or refreshes `access_token` based on `grant_type`.\nFor all other requests it checks `Authorization: Bearer \u003cJWT\u003e` and only allows if that is valid and the scopes are met. You can configure scopes on per endpoint basis.\nYou can access currently authenticated user through out the app using:\n```php\n$di-\u003eget('authenticator')-\u003egetSubject();\n```\n\n#### Setup\n\n```php\n$di-\u003esetShared('config', new \\Phalcon\\Config([\n    'apiAuth' =\u003e [\n        // 14 days in seconds (http://stackoverflow.com/questions/15564486/why-do-refresh-tokens-expire-after-14-days)\n        'refreshMaxAge'  =\u003e 1209600,\n        // Prefix to use in stored tokens (max 4 chars)\n        'tokenPrefix'    =\u003e 'RF/',\n        // The route to generate/refresh access tokens.\n        // genrerate: curl -XPOST -d 'grant_type=password\u0026username=\u0026password=' /api/auth\n        // refresh:   curl -XPOST -d 'grant_type=refresh_token\u0026refresh_token=' /api/auth\n        // It can also accept json payload:\n        //   -H 'content-type: application/json' -d {\"grant_type\":\"refresh_token\",\"refresh_token\":\"\"}\n        'authUri' =\u003e '/api/auth',\n\n        // The permission scopes required for a route\n        'scopes' =\u003e [\n            '/some/uri' =\u003e 'admin',\n            '/next/uri' =\u003e 'user',\n        ],\n\n        // Json Web tokens configuration.\n        'jwt'            =\u003e [\n            'keys'       =\u003e [\n                // kid =\u003e key (first one is default always)\n                'default' =\u003e '*((**@$#@@KJJNN!!#D^G\u0026(U)KOIHIYGTFD',\n            ],\n            'algo'       =\u003e 'HS256',\n            // 15 minutes in seconds.\n            'maxAge'     =\u003e 900,\n            // Grace time in seconds.\n            'leeway'     =\u003e 10,\n            // Only for RS algo.\n            'passphrase' =\u003e '',\n            // Name of the app/project.\n            'issuer'     =\u003e '',\n        ],\n    ],\n]);\n\n// Usage:\n(new PhalconExt\\Http\\Middlewares([\n    PhalconExt\\Http\\Middleware\\ApiAuth::class,\n]))-\u003ewrap(new Phalcon\\Mvc\\Micro($di));\n```\n\n#### Http.Middleware.Cache\n\nCaches output for requests to boost performance heavily. Requires redis service.\nCurrently by design only GET requests are cached and this might change.\n\n#### Setup\n\n```php\n$di-\u003esetShared('config', new \\Phalcon\\Config([\n    'httpCache' =\u003e [\n        // cache life- time to live in mintues\n        'ttl'       =\u003e 60,\n        // White listed uri/routes to enable caching\n        'routes'    =\u003e [\n            // for absolute uri, prepend forward `/`\n            '/content/about-us',\n            // or you can use route name without a `/`\n            'home',\n        ],\n    ],\n]);\n```\n\n#### Http.Middleware.Cors\n\nEnables cors with preflight for configured origins and request options.\n\n#### Setup\n\n```php\n$di-\u003esetShared('config', new \\Phalcon\\Config([\n    'cors' =\u003e [\n        'exposedHeaders' =\u003e [],\n        // Should be in lowercases.\n        'allowedHeaders' =\u003e ['x-requested-with', 'content-type', 'authorization'],\n        // Should be in uppercase.\n        'allowedMethods' =\u003e ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],\n        // Requests originating from here can entertain CORS.\n        'allowedOrigins' =\u003e [\n            'http://127.0.0.1:1234',\n        ],\n        // Cache preflight for 7 days (expressed in seconds).\n        'maxAge'         =\u003e 604800,\n    ],\n]);\n\n```\n\n#### Http.Middleware.Throttle\n\nThrottles the flooded requests as per time and quota of your choosing. Requires redis service.\n\n#### Setup\n\n```php\n$di-\u003esetShared('config', new \\Phalcon\\Config([\n    'throttle' =\u003e [\n        'maxHits' =\u003e [\n            // Mintues =\u003e Max Hits\n            1    =\u003e 10,\n            60   =\u003e 250,\n            1440 =\u003e 4500,\n        ],\n        'checkUserAgent' =\u003e false,\n        // Cache key prefix\n        'prefix'         =\u003e '_',\n    ],\n]);\n\n```\n\n#### Usage\n\nMiddlewares can be used as a wrapper to app using `PhalconExt\\Http\\Middlewares` manager.\n\n```php\n$app = new Phalcon\\Mvc\\Micro($di);\n\n// Set all your middlewares in an array using class FQCN, they are lazily loaded\n// They are executed in order of their presence\n// If a middleware returns `false` from its `before()` or `after()` events,\n// all other middlewares down the line are skipped\n$middlewares = new PhalconExt\\Http\\Middlewares([\n    PhalconExt\\Http\\Middleware\\Throttle::class,\n    PhalconExt\\Http\\Middleware\\ApiAuth::class,\n    PhalconExt\\Http\\Middleware\\Cors::class,\n    PhalconExt\\Http\\Middleware\\Cache::class,\n]);\n\n// Wrap and run the app!\n$middlewares-\u003ewrap($app);\n\n// The app is wrapped and run automatically so you dont have to do:\n// $app-\u003ehandle();\n```\n\n---\n### Logger.EchoLogger\n\n#### log(string $message, int $type, array $context = [])\n\nEchoes anything right away - but you can control formatting and log level.\n\n```php\n$echo = $this-\u003edi(\\PhalconExt\\Logger\\EchoLogger::class, ['config' =\u003e ['level' =\u003e Logger::INFO]]);\n$echo-\u003elog('Message {a}', \\Phalcon\\Logger::INFO, ['a' =\u003e 'ok']);\n```\n\n### Logger.LogsToFile\n\n#### log(string $message, int $type, array $context = [])\n\nDelegate mundane file logging task to this trait thereby cutting down boilerplate codes.\n\n```php\nclass AnyClass\n{\n    use \\PhalconExt\\Logger\\LogsToFile;\n\n    protected $fileExtension = '.log';\n\n    public function anyFn()\n    {\n        $this-\u003eactivate('/path/to/log/dir/');\n\n        $this-\u003elog('Some message', \\Phalcon\\Logger::INFO);\n    }\n}\n```\n\n---\n### Mail.Mailer\n\nA Phalcon adapter/bridge/container/delegator (read: abcd) to swiftmailer.\n\n#### Setup\n\n```php\n$di-\u003esetShared('config', new \\Phalcon\\Config([\n    'mail' =\u003e [\n        'driver' =\u003e 'null',\n        'from'   =\u003e [\n            'name'  =\u003e 'Test',\n            'email' =\u003e 'test@localhost',\n        ],\n\n        // for driver 'smtp':\n        'host'       =\u003e 'smtp.server.com',\n        'port'       =\u003e 425,\n        'encryption' =\u003e true,\n        'username'   =\u003e 'user',\n        'password'   =\u003e 'pass',\n\n        // for driver sendmail only (optional)\n        'sendmail' =\u003e '/sendmail/binary',\n    ],\n]);\n\n$di-\u003esetShared('mailer', function () {\n    return new \\PhalconExt\\Mail\\Mailer($this-\u003eget('config')-\u003etoArray()['mail']);\n});\n```\n\n### Mail.Mail\n\nA child of swiftmail message to allow attaching attachments without much ado.\n\n```php\n$mail = $di-\u003eget('mailer')-\u003enewMail();\n// Or from view template\n$mail = $di-\u003eget('mailer')-\u003enewTemplateMail('view/file.twig', ['view' =\u003e 'params']);\n\n$mail-\u003esetTo('test@localhost')-\u003esetSubject('Hi')-\u003esetBody('Hello')-\u003email();\n\n// Attachments:\n$mail-\u003eattachFile('/path/to/file', 'optional attachment name');\n\n$mail-\u003eattachFiles(['/path/to/file1', '/path/to/file2']);\n// OR\n$mail-\u003eattachFiles([\n    'attachment name 1' =\u003e '/path/to/file1',\n    'attachment name 2' =\u003e '/path/to/file2',\n]);\n\n$mail-\u003eattachRaw('Raw plain text data', 'rawtext.txt', 'text/plain');\n```\n\n### Mail.Mailable\n\n#### mail()\n\nLike Logger.LogsToFile above, but for mails.\n\n```php\nclass AnyClass\n{\n    use \\PhalconExt\\Mail\\Mailable;\n\n    public function anyFn()\n    {\n        $this-\u003email('test@local', 'Hi', ['body' =\u003e 'Hello']);\n        $this-\u003email('test@local', 'Hi', ['template' =\u003e 'view/file.twig', 'params' =\u003e ['key' =\u003e 'value']]);\n    }\n}\n```\n\n### Mail.Logger\n\nAutomatically logs all sent mails into file as a swiftmailer event listener- you can choose log formats: `eml | html | json`.\n\n#### Setup\n\n```php\n$di-\u003esetShared('config', new \\Phalcon\\Config([\n    'mail' =\u003e [\n        'driver' =\u003e 'null',\n        'from'   =\u003e [\n            'name'  =\u003e 'Test',\n            'email' =\u003e 'test@localhost',\n        ],\n        'logger' =\u003e [\n            'enabled' =\u003e true,\n            'logPath' =\u003e __DIR__ . '/.var/mail/', // directory\n            'type'    =\u003e 'eml', // options: json, html, eml\n        ],\n    ],\n]);\n\n// When setting mailer, include config `mail\u003elogger` and it is auto set up.\n$di-\u003esetShared('mailer', function () {\n    return new \\PhalconExt\\Mail\\Mailer($this-\u003eget('config')-\u003etoArray()['mail']);\n});\n```\n\n---\n### Util.OpcachePrimer\n\n#### prime(array $paths): int\n\nEnsures to warm up opcache for all files in given path well before file exceution.\nOpcache caches are specific to the sapi it is run. So for web, you need to have an endpoint\n\n```php\n$primer = new \\PhalconExt\\Util\\OpcachePrimer;\n\n$total = $primer-\u003eprime(['/path/to/project/src', '/path/to/project/app/', '/path/to/project/vendor/']);\n```\n\n---\n### Validation.Validation\n\nValidate data like we did in elsewhere- setting rules as .well-known strings or key=\u003evalue pairs (array).\n\n#### Setup\n\n```php\n$di-\u003esetShared('validation', \\PhalconExt\\Validation\\Validation::class);\n```\n\n#### register(string $ruleName, $handler, string $message = ''): self\n\nRegister a new validation rule.\n\n```php\n$di-\u003eget('validation')-\u003eregister('gmail', function ($data) {\n    // You can access current validation instance with `$this`\n    // You can also access current validator options with `$this-\u003egetOption(...)`\n    return stripos($this-\u003egetCurrentValue(), '@gmail.com') \u003e 0;\n}, 'Field :field must be an email with @gmail.com');\n```\n\n#### registerRules(array $ruleHandlers, array $messages = []): self\n\nRegister many new validation rules at once.\n\n```php\n$di-\u003eget('validation')-\u003eregisterRules([\n    'rule1' =\u003e function($data) { return true; },\n    'rule1' =\u003e function($data) { return false; },\n], [\n    'rule1' =\u003e 'message1',\n    'rule2' =\u003e 'message2'\n]);\n```\n\n#### Usage\n\n```php\n$validation = $this-\u003edi('validation');\n\n$rules = [\n    // Can be string (With `abort` if the field `id` is invalid, following validations are aborted)\n    'id'    =\u003e 'required|length:min:1;max:2;|in:domain:1,12,30|abort',\n    // Can be an array too\n    'email' =\u003e [\n        'required' =\u003e true,\n        'gmail'    =\u003e true,\n        // With `abort` if the field `email` is invalid, following validations are aborted\n        'abort'   =\u003e true,\n    ],\n    // validate if only exist in dataset\n    'xyz' =\u003e 'length:5|if_exist',\n];\n\n// Validate against empty data (can be array or object)\n$data = []; // OR $data = new \\stdClas OR $data = new SomeClass($someData)\n$validation-\u003erun($rules, $data);\n\n$pass = $validation-\u003epass(); // false\n$fail = $validation-\u003efail(); // true\n\n$errors = $validation-\u003egetErrorMessages(); // array\n```\n\n#### Validation.Existence\n\nValidates if something exists in database. You can optionally set table and column to check.\n\n```php\n// Checks `users` table for `id` column with value 1\n$rules = ['users' =\u003e 'exist'];\n$data  = ['users' =\u003e 1]; // Data can be array\n\n// Checks `users` table for `username` column with value 'admin'\n$rules = ['username' =\u003e 'exist:table:users'];\n$data  = new User(['username' =\u003e 'admin']); // Data can be model/entity\n\n// Checks `users` table for `login` column with value 'admin@localhost'\n$rules = ['email' =\u003e 'exist:table:users;column:login'];\n$data  = (object) ['email' =\u003e 'admin@localhost']; // Data can be any Object\n\n// Run the rules\n$validation-\u003erun($rules, $data);\n```\n\n---\n### View.Twig\n\nUse twig view natively in Phalcon\n\n#### Setup\n\n```php\n$di-\u003esetShared('config', new \\Phalcon\\Config([\n    'view' =\u003e [\n        'dir' =\u003e __DIR__ . '/view/',\n    ],\n    // Required\n    'twig' =\u003e [\n        'view_dirs'   =\u003e [__DIR__ . '/view/'], // array\n        'auto_reload' =\u003e getenv('APP_ENV') !== 'prod',\n        'cache'       =\u003e __DIR__ . '/.var/view/',\n        // ... other options (see twig docs)\n    ],\n]);\n\n// You must have view setup with twig engine enabled.\n$di-\u003esetShared('view', function () {\n    return (new View)\n        -\u003esetViewsDir($this-\u003eget('config')-\u003etoArray()['view']['dir'])\n        -\u003eregisterEngines([\n            '.twig' =\u003e 'twig',\n        ]);\n});\n\n$di-\u003esetShared('twig', function () {\n    $twig = new PhalconExt\\View\\Twig($this-\u003eget('view'), $this);\n\n    // Here you can:\n    // $twig-\u003eaddFilter(...)\n    // $twig-\u003eaddExtension(...)\n\n    return $twig;\n});\n```\n\n#### Usage\n\n```php\n// standalone\n$di-\u003eget('twig')-\u003erender('template.twig', ['view' =\u003e 'params']);\n// or as view\n$di-\u003eget('view')-\u003erender('template.twig', ['view' =\u003e 'params']); // .twig is optional\n```\n\n---\n\nYou can also see the [example](./example) codes for all these. Read [more](./example/readme.md).\n\n\n### Related Projects\n\n- [adhocore/cli](https://github.com/adhocore/cli)\n- [adhocore/jwt](https://github.com/adhocore/jwt)\n- [adhocore/cron-expr](https://github.com/adhocore/cron-expr)\n\n## License\n\n\u003e \u0026copy; 2017-2020, [Jitendra Adhikari](https://github.com/adhocore) | [MIT](./LICENSE)\n\n### Credits\n\nThis project is release managed by [please](https://github.com/adhocore/please).\n","funding_links":["https://github.com/sponsors/adhocore","https://paypal.me/ji10","https://www.paypal.me/ji10/15usd","https://www.paypal.me/ji10/25usd","https://www.paypal.me/ji10/50usd"],"categories":["PHP","Uncategorized"],"sub_categories":["Uncategorized"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadhocore%2Fphalcon-ext","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadhocore%2Fphalcon-ext","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadhocore%2Fphalcon-ext/lists"}