Phalcon devtools

Overview

Yarak

Latest Stable Version License Build Status Quality Score StyleCI

yarak - (Falconry) a state of prime fitness in a falcon

Laravel inspired Phalcon devtools

  • Database migrations that rollback step-by-step, reset the database, and refresh the database.
  • Model factories for easy test data creation.
  • Database seeders that fill your database with a single command.
  • Create custom commands in minutes to streamline and personalize your workflow.

Contents

Release Notes

Moving from 1.1.* to 1.2.*

The core command wrapper has been extracted to a separate package (zachleigh/artisanize). Where possible, Yarak classes have been maintained for the time being in order to minimize update issues. However, some interface type declarations may need to be updated after isntalling the new version.

Install

Requirements

This package assumes you have the following:

  • Phalcon >= 3.0
  • PHP >= 5.6.5

Install via composer

composer require zachleigh/yarak

Register the service

use Yarak\Kernel;

$di->setShared('yarak',function () {
    return new Kernel();
});

Yarak requires the following config values in this structure:

'database' => [
    'adapter'     => '',
    'host'        => '',
    'username'    => '',
    'password'    => '',
    'dbname'      => '',
    'charset'     => '',
],
'application' => [
    'appDir'         => APP_PATH.'/',
    'commandsDir'    => APP_PATH.'/console/commands',
    'consoleDir'     => APP_PATH.'/console/',
    'databaseDir'    => APP_PATH.'/database/',
    'migrationsDir'  => APP_PATH.'/database/migrations/',
    'modelsDir'      => APP_PATH.'/models/',
],
'namespaces' => [
    'root' => '',
],

Yarak uses your application's config so if your config is already structured this way, simply add the necessary values. If your config strategy differs from this, you have several options. First, you can create a specific Yarak config key and set all values there:

'yarak' => [
    'database' => [
        'adapter'     => '',
        'host'        => '',
        'username'    => '',
        'password'    => '',
        'dbname'      => '',
        'charset'     => '',
    ],
    'application' => [
        'appDir'         => APP_PATH.'/',
        'commandsDir'    => APP_PATH.'/console/commands',
        'consoleDir'     => APP_PATH.'/console/',
        'databaseDir'    => APP_PATH.'/database/',
        'migrationsDir'  => APP_PATH.'/database/migrations/',
        'modelsDir'      => APP_PATH.'/models/',
    ],
    'namespaces' => [
        'root' => '',
    ],
]

Then, when registering the service pass the config path:

$di->setShared('yarak',function () {
    return new Kernel('yarak');
});

If the config path is multiple levels deep, use dot notation:

'services' => [
    'yarak' => [
        'database' => [
            'adapter'     => '',
            'host'        => '',
            'username'    => '',
            'password'    => '',
            'dbname'      => '',
            'charset'     => '',
        ],
        'application' => [
            'appDir'         => APP_PATH.'/',
            'commandsDir'    => APP_PATH.'/console/commands',
            'consoleDir'     => APP_PATH.'/console/',
            'databaseDir'    => APP_PATH.'/database/',
            'migrationsDir'  => APP_PATH.'/database/migrations/',
            'modelsDir'      => APP_PATH.'/models/',
        ],
        'namespaces' => [
            'root' => '',
        ],
    ]
]
$di->setShared('yarak',function () {
    return new Kernel('services.yarak');
});

If you wish to add config values when registering Yarak, pass them as an array and Yarak will merge them into your existing config:

$di->setShared('yarak',function () {
    return new Kernel([
        'namespaces' => [
            'root' => '',
        ],
    ]);
});

Lastly, you can pass all config values when registering the service. Pass 'false' as the second parameter to the Kernel constructor to turn off config merging.

$di->setShared('yarak',function () {
    return new Kernel([
        'database' => [
            'adapter'     => '',
            'host'        => '',
            'username'    => '',
            'password'    => '',
            'dbname'      => '',
            'charset'     => '',
        ],
        'application' => [
            'appDir'         => APP_PATH.'/',
            'commandsDir'    => APP_PATH.'/console/commands',
            'consoleDir'     => APP_PATH.'/console/',
            'databaseDir'    => APP_PATH.'/database/',
            'migrationsDir'  => APP_PATH.'/database/migrations/',
            'modelsDir'      => APP_PATH.'/models/',
        ],
        'namespaces' => [
            'root' => '',
        ],
    ], false);
});

Create a yarak file

In the project root, create a file called yarak. This file needs to do the following:

  • Autoload all project files and vendor directory files
  • Load the project services
  • Resolve the Yarak kernel from the service container and call the handle method on it

Example:

#!/usr/bin/env php
<?php

use Phalcon\Di\FactoryDefault;

error_reporting(E_ALL);

define('BASE_PATH', __DIR__);
define('APP_PATH', BASE_PATH . '/app');

/*
|--------------------------------------------------------------------------
| Autoload The Application
|--------------------------------------------------------------------------
|
| In order to work properly, Yarak will need both your project files and the
| vendor folder to be autoloaded.
|
*/
include APP_PATH . '/config/loader.php';

/*
|--------------------------------------------------------------------------
| Register The App Services
|--------------------------------------------------------------------------
|
| We need to register the app services in order to spin up Yarak. Be sure you
| have registered Yarak in the services file.
|
*/
$di = new FactoryDefault();

include APP_PATH . '/config/services.php';

/*
|--------------------------------------------------------------------------
| Handle The Incoming Commands
|--------------------------------------------------------------------------
|
| We'll get the Yarak kernel from the dependency injector and defer to it for 
| command handling.
|
*/
$kernel = $di->getYarak();

$kernel->handle();

The above example is included in the project at yarak/src/yarak_example. Copy it into your project with the following command, done from the project root:

cp vendor/zachleigh/yarak/src/yarak_example yarak

Once the yarak file exists, make it executable:

chomd +x yarak

Add the database directory to the composer autoloader

Because migrations do not follow psr-4 naming conventions, load them with a classmap.

"autoload": {
    "classmap": [
        "relative/path/to/database/directory"
    ]
}

You may have to dump the composer autoload cache for the change to take affect.

composer dumpautoload

Test to make sure that it is working in the console:

php yarak

Top

Database

Yarak gives users several helpful database functionalities that make development easier.

Generating Database Directories And Files

All database and migration functionalites require a standardized file hierarchy. To generate this hirearchy, use the db:generate command:

php yarak db:generate

This will create a database directory at the path set in the Yarak config. The database directory will contain migration, seeder, and factory directories and some file stubs to help you get started.

Model Factories

Model factories provide a simple way to create testing data using the Faker library.

Defining Factories

Model factories are located in the /database/factories directory. This directory and a stub factory file can be created using the php yarak db:generate command.

To define a factory, use the define method on a variable called $factory. The define method has the following method signature:

public function define($class, callable $attributes, $name = 'default')

The first argument is the full name/namespace of the class. The second argument is a callback that returns an array. This array must contain the data necessary to create the model. The third optional argument is a name for the factory. Setting the name allows you to define multiple factories for a single model.

To create a simple user model factory:

use App\Models\Users;

$factory->define(Users::class, function (Faker\Generator $faker) {
    return [
        'username' => $faker->userName,
        'email' => $faker->unique()->safeEmail,
        'password' => 'password',
    ];
});

To create a named user model factory:

use App\Models\Users;

$factory->define(Users::class, function (Faker\Generator $faker) {
    return [
        'username' => 'myUsername',
        'email' => 'myEmail',
        'password' => 'myPassword',
    ];
}, 'myUser');

The ModelFactory class responsible for creating model instances extends Phalcon\Mvc\User\Component and has access to the DI and any services registered. To access the ModelFactory class, use the $factory variable in the $attributes closure.

use App\Models\Users;

$factory->define(Users::class, function (Faker\Generator $faker) use ($factory) {
    return [
        'username' => $faker->userName,
        'email' => $faker->unique()->safeEmail,
        'password' => $factory->security->hash('password'),
    ];
});

Using The Factory Helper

Yarak comes with a global factory helper function to make creating model instances simple. The factory function returns an instance of ModelFactoryBuilder which can be used to either make or create models. Calling make on the returned class simply makes the model class, but does not persist the data in the database. Calling create creates the class and persists it in the database.

Make a user model isntance, but don't persist it:

use App\Models\Users;

$user = factory(Users::class)->make();

Create a user model and persist it:

use App\Models\Users;

$user = factory(Users::class)->create();

Making Multiple Model Instances

If you require multiple instances of the model class, pass an integer as the second argument to factory:

use App\Models\Users;

// Make three users
$users = factory(Users::class, 3)->make();

// Create three users
$users = factory(Users::class, 3)->create();

When more than one model is made, an array of models is returned.

Overriding The Default Attributes

To override the default attributes set in the factory definition, pass an array of overrides to make or create:

use App\Models\Users;

// Make a user with username 'bobsmith' and email '[email protected]'
$user = factory(Users::class)->make([
    'username' => 'bobsmith',
    'email'    => '[email protected]'
]);

// Create a user with username 'bobsmith' and email '[email protected]'
$user = factory(Users::class)->create([
    'username' => 'bobsmith',
    'email'    => '[email protected]'
]);

Using Named Factories

To use a name factory, pass the name as the second argument to the factory function:

use App\Models\Users;

// Make a user using the factory named 'myUser'
factory(Users::class, 'myUser')->make()

// Create a user using the factory named 'myUser'
factory(Users::class, 'myUser')->create()

To make multiple instances of a named factory, pass the desired number of instances as the third argument:

use App\Models\Users;

// Make three users using the factory named 'myUser'
$users = factory(Users::class, 'myUser', 3)->make();

// Create three users using the factory named 'myUser'
$users = factory(Users::class, 'myUser', 3)->creates();

Model Relationships

When making model instances that require model relationships to also be built, you have a couple options.

First, you can manually create related models. In this example, we have Posts and Users which have a one-to-many relationship: a post can only belong to one user but a user can have many posts. The posts table contains a users_id column that references the id column on the users table. Posts table migration:

$connection->createTable(
    'posts',
    null,
    [
        'columns' => [
            new Column('id', [
                'type'          => Column::TYPE_INTEGER,
                'size'          => 10,
                'unsigned'      => true,
                'notNull'       => true,
                'autoIncrement' => true,
            ]),
            new Column('title', [
                'type'    => Column::TYPE_VARCHAR,
                'size'    => 200,
                'notNull' => true,
            ]),
            new Column('body', [
                'type'    => Column::TYPE_TEXT,
                'notNull' => true,
            ]),
            new Column('users_id', [
                'type'     => Column::TYPE_INTEGER,
                'size'     => 10,
                'unsigned' => true,
                'notNull'  => true,
            ]),
            new Column('created_at', [
                'type'    => Column::TYPE_TIMESTAMP,
                'notNull' => true,
                'default' => 'CURRENT_TIMESTAMP',
            ]),
        ],
        'indexes' => [
            new Index('PRIMARY', ['id'], 'PRIMARY')
        ],
        'references' => [
            new Reference(
                'user_idfk',
                [
                    'referencedTable'   => 'users',
                    'columns'           => ['users_id'],
                    'referencedColumns' => ['id'],
                ]
            ),
        ],
    ]
);

First, we need to create factories for both users and posts:

use App\Models\Posts;
use App\Models\Users;

$factory->define(Users::class, function (Faker\Generator $faker) use ($factory) {
    return [
        'username' => $faker->userName,
        'email'    => $faker->unique()->safeEmail,
        'password' => $factory->security->hash('password'),
    ];
});

$factory->define(Posts::class, function (Faker\Generator $faker) {
    return [
        'title' => $faker->unique()->sentence(4, true),
        'body'  => $faker->paragraph(4, true),
    ];
});

To create three users with one post each, we could simply loop over newly created users and create a post for each, sending the user id as an attribute override:

use App\Models\Posts;
use App\Models\Users;

$users = factory(Users::class, 3)->create();

foreach ($users as $user) {
    factory(Posts::class)->create([
        'users_id' => $user->id
    ]);
}

For multiple posts, simply pass the desired number as the second variable to the factory helper:

use App\Models\Posts;
use App\Models\Users;

$users = factory(Users::class, 3)->create();

foreach ($users as $user) {
    factory(Posts::class, 3)->create([
        'users_id' => $user->id
    ]);
}

Another way to create relationships is by using a closure returning a relationship in a factory definition:

use App\Models\Posts;
use App\Models\Users;

$factory->define(Users::class, function (Faker\Generator $faker) use ($factory) {
    return [
        'username' => $faker->userName,
        'email'    => $faker->unique()->safeEmail,
        'password' => $factory->security->hash('password'),
    ];
});

$factory->define(Posts::class, function (Faker\Generator $faker) {
    return [
        'title'    => $faker->unique()->sentence(4, true),
        'body'     => $faker->paragraph(4, true),
        'users_id' => function () {
            return factory(Users::class)->create()->id;
        }
    ];
}, 'withUser');

Here we are using a factory within the factory to create a new user for each new post created. We are also naming the factory 'withUser' for convenience. To create 20 posts made by 20 users, we can simply do this:

use App\Models\Posts;

factory(Posts::class, 'withUser', 20)->create();

Database Seeding

Database seeding gives you the ability to fill your database with testing data in a matter of seconds.

Creating Database Seeders

To create an empty database seeder file, use the make:seeder command:

php yarak make:seeder SeederName

This will generate an empty seeder file in /database/seeds. It is recommended to create separate seeder files for individual database tables.

Writing Database Seeders

All database seeders must have a run method where the database seeding logic is defined. In the run method, do whatever is necessary to fill the database table. Using model factories makes this process simple to acheive. An example seeder for a users tables might look like this:

use App\Models\Users;
use Yarak\DB\Seeders\Seeder;

class UsersTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        factory(Users::class, 5)->create();
    }
}

Running this seeder will create five users in the database.

The parent Seeder class has a call method that will call the run method on other seeder files. This allows you to create several seeder files and then make a master DatabaseSeeder that will fill the entire database. We already have a UsersTableSeeder above, so let's now make a PostsTableSeeder:

use App\Models\Posts;
use App\Models\Users;
use Yarak\DB\Seeders\Seeder;

class PostsTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $allUsers = Users::find();

        foreach ($allUsers as $user) {
            factory(Posts::class, 5)->create(['users_id' => $user->getId()]);
        }
    }
}

This will create 5 posts for each of our users. We can then combine our two seeder files in a master DatabaseSeeder file:

use Yarak\DB\Seeders\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $this->call(UsersTableSeeder::class);
        $this->call(PostsTableSeeder::class);
    }
}

This will run each seeder file in the order they are listed. First, we will create five users with the UsersTableSeeder, then for each of those users, we will create five posts with the PostsTableSeeder.

Using Database Seeders

To run database seeder files, use the db:seed command:

php yarak db:seed SeederName

The default seeder name is 'DatabaseSeeder'.

You may also use the --seed flag with the migrate:refresh command:

php yarak migrate:refresh --seed --class=SeederName

Refreshing the database will remove all data from your database. This command will drop all tables, run all the migrations again, then fill the database using the given seeder class name. The default value for the seeder name is 'DatabaseSeeder'.

Top

Migrations

Yarak migrations provide a simple, clean way to manage your database.

Generating Migrations

All migrations are stored in databaseDir/migrations. The databaseDir path may be set when registering the Yarak service.

To generate migrations, use the make:migration command:

php yarak make:migration migration_name --create=table_name

The migration name must be snake_case and will be used to create the migration file name and class name. For example:

php yarak make:migration create_users_table

Using the name create_users_table will generate a migration class called CreateUsersTable. Migration file names are generated using a timestamp and the given name. In this example, the generated file name might look something like this: 2017_03_04_055719_create_users_table.php.

If you are creating a new table, using the --create flag plus the name of the database table will create a migration file with some additional boiler plate to save a little time.

php yarak make:migration create_users_table --create=users

Writing Migrations

Yarak uses Phalcon's Database Abstraction Layer to interact with the database. This guide will only cover the most common operations. For more detailed information about what is possible, please see the API Documentation. Because the official Phalcon migrations also use the database abstraction layer, the Phalcon migration documentation may also be useful.

Creating Tables

To create a table, use the $connection variable's createTable method.

public createTable (mixed $tableName, mixed $schemaName, array $definition)

To create a simple users table, your up method might look something like this:

use Phalcon\Db\Index;
use Phalcon\Db\Column;

//

public function up(Pdo $connection)
{
    $connection->createTable(
        'users',
        null,
        [
            'columns' => [
                new Column('id', [
                    'type'          => Column::TYPE_INTEGER,
                    'size'          => 10,
                    'unsigned'      => true,
                    'notNull'       => true,
                    'autoIncrement' => true
                ]),
                new Column('username', [
                    'type'    => Column::TYPE_VARCHAR,
                    'size'    => 32,
                    'notNull' => true
                ]),
                new Column('password', [
                    'type'    => Column::TYPE_CHAR,
                    'size'    => 40,
                    'notNull' => true
                ]),
                new Column('email', [
                    'type'    => Column::TYPE_VARCHAR,
                    'size'    => 20,
                    'notNull' => true
                ]),
                new Column('created_at', [
                    'type'    => Column::TYPE_TIMESTAMP,
                    'notNull' => true,
                    'default' => 'CURRENT_TIMESTAMP'
                ])
            ],
            'indexes' => [
                new Index('PRIMARY', ['id'], 'PRIMARY'),
                new Index('users_username_unique', ['username'], 'UNIQUE'),
                new Index('users_email_unique', ['email'], 'UNIQUE')
            ]
        ]
    );
}

The definition array must contain a columns array, and can also include indexes, references, and options arrays. To define columns use Phalcon's DB Column class class, for indexes use the DB Index class, and for foreign keys use the DB Reference class.

For more information, see the official documentation.

Updating Tables

To modify a column, use the $connection variable's modifyColumn method:

public modifyColumn (mixed $tableName, mixed $schemaName, Phalcon\Db\ColumnInterface $column, [Phalcon\Db\ColumnInterface $currentColumn])

Continuing the example above, our email column size is currently set to 20 which is clearly not big enough. To modify this, we can create a new migration:

php yarak make:migration increase_user_email_column_size

In the created migration's up method, we can write the following:

public function up(Pdo $connection)
{
    $connection->modifyColumn(
        'users',
        null,
        new Column(
            'email',
            [
                'type' => Column::TYPE_VARCHAR,
                'size' => 70,
            ]
        )
    );
}

Keep in mind that when using the Column class, type is required.

To add additional columns to a table, use the addColumn method:

public addColumn (mixed $tableName, mixed $schemaName, Phalcon\Db\ColumnInterface $column)

So if we want to add an active column to our users table, we create a new migration:

php yarak make:migration add_active_column_to_users_table

And our migration up method could look like this:

public function up(Pdo $connection)
{
    $connection->addColumn(
        'users',
        null,
        new Column(
            'active',
            [
                'type'    => Column::TYPE_CHAR,
                'size'    => 1,
                'notNull' => true,
            ]
        )
    );
}

The official documentation contains some additional examples and information which may be helpful.

The Down Method

In order for migraion rollbacks to work, migrations must contain a down method where the process described in the up method is reversed. To continue our above example, when creating the users table, our down method would use the dropTable method:

public function down(Pdo $connection)
{
    $connection->dropTable('users');
}

When modifying the email column, we could simply modify the column so that it returns to it's previous state:

public function down(Pdo $connection)
{
    $connection->modifyColumn(
        'users',
        null,
        new Column(
            'email',
            [
                'type' => Column::TYPE_VARCHAR,
                'size' => 20,
            ]
        )
    );
}

When adding the active column, use the dropColumn method:

public function down(Pdo $connection)
{
    $connection->dropColumn('users', null, 'active');
}

Running Migrations

To run all pending migrations, simply use the Yarak migrate command:

php yarak migrate

This will run all migrations that have not yet been run. Migrations that are run at the same time will be in the same 'batch' and will be rolled back together.

Rolling Back Migrations

Before rolling back, be aware that all data in the tables you rollback will be lost.

To rollback the last batch of migrations, call migrate:rollback:

php yarak migrate:rollback

Use migrate:rollback with the optional --steps flag to rollback more than one batch.

php yarak migrate:rollback --steps=2

// with shortcut
php yarak migrate:rollback -s=2

This will rollback the last two batches of migrations.

Resetting The Database

Using the migrate:reset command will rollback all migrations.

Resetting the database will remove all data from your database. Be sure any data you wish to keep is backed up before proceeding.

php yarak migrate:reset

Refreshing The Database

Refreshing the database will rollback all migrations and then re-run them all in a single batch.

Refreshing the database will remove all data from your database. Be sure any data you wish to keep is backed up before proceeding.

php yarak migrate:refresh

When using the migrate:refresh command, you may also use the --seed flag to run all your database seeders after the database has been refreshed. See Using Database Seeders for more information.

Top

Custom Commands

Yarak can also be extended and used as a general command line task runner.

Generating Console Directories And Files

To generate all the directories and files necessary for the console component to work, use the console:generate command:

php yarak console:generate

This will create a console directory, a commands directory, an example command, and a Kernel.php file where you can register your custom commands. If the config value namespaces => root is set, Yarak will use file path information and the set root namespace to automatically generate namespaces. If you use a non-standard namespace, set namespaces => console as shown below.

Generating Custom Commands

Before generating a custom command, be sure to include consoleDir in your config. You may also register a console namespace if the automatically generated namespaces are incorrect. By default, custom commands with be in the defined console directory in a folder called commands. You can override this by registering a commandsDir.

'application' => [
    //
    'consoleDir' => APP_PATH.'/console/'
],
'namespaces' => [
    //
    'console' => 'App\Console'
],
});

Do not forget to register your console namespaces with the Phalcon loader.

Once consoleDir is registered, use the make:command command to generate a custom command stub.

php yarak make:command CommandName

Writing Custom Commands

A command class has three components: a signature, a description, and a handle method.

namespace App\Console\Commands;

use Yarak\Console\Command;

class ExampleCommand extends Command
{
    /**
     * The command signature.
     *
     * @var string
     */
    protected $signature = 'namespace:name {argument} {--o|option=default}';

    /**
     * The command description.
     *
     * @var string
     */
    protected $description = 'Command decription.';

    /**
     * Handle the command.
     */
    protected function handle()
    {
        // handle the command
    }
}

signature is where you define your command's name, arguments, and options. This is discussed in detail below. description is where you can set a description message for your command to be displayed when using the console. The handle method will be called when the command is fired and is where you should write the logic for your command. It may be useful to extract the bulk of your logic to a separate service class.

Command Signature

The command signature is written in the same way that the command will be used in the console and consists of three parts: the command name, arguments, and options. The command name must come first in the signature and can be namespaced by prefixing the command name with a namespace followed by a colon (':'):

protected $signature = 'namespace:name';

Arguments and options are enclosed in curly braces and follow the command name. Options are prefixed by two dashes ('--').

Defining Command Arguments

A standard argument consists of the argument name wrapped in curly braces:

protected $signature = 'namespace:name {arg} {--option}'

The argument name, arg in the example above, is used to access the argument value via the argument method.

To make an argument optional, append a question mark ('?') to the argument name:

protected $signature = 'namespace:name {arg?} {--option}'

To give the argument a default value, separate the argument name and the default value with an equals sign ('='):

protected $signature = 'namespace:name {arg=default} {--option}'

If no value is provided for the argument, the default value will be used.

If the argument is in array form, append an asterisk ('*') to the argument name:

protected $signature = 'namespace:name {arg*} {--option}'

Arguments can then be passed to the command by space separating them:

php yarak namespace:name one two three

This will set the value of arg to ['one', 'two', 'three'].

Argument arrays can also be set as optional:

protected $signature = 'namespace:name {arg?*} {--option}'

When accessing optional argument arrays, arguments that have not been passed equal an empty array.

It is often helpful to provide a description with an argument. To do this, add a colon (':') after the argument definition and append the description:

protected $signature = 'namespace:name {arg=default : Argument description} {--option}'
Defining Command Options

A standard option consists of the option, prefixed by two dashes ('--'), wrapped in curly braces:

protected $signature = 'namespace:name {argument} {--opt}'

The option name, opt, is used to access the argument value via the option method. Standard options do not take values and act as true/false flags: the presence of the option when the command is called sets its value to true and if it is not present, the value is false.

To define an option with a required value, append an equals sign ('=') to the option name:

protected $signature = 'namespace:name {argument} {--opt=}'

To set a default value, place it after the equals sign:

protected $signature = 'namespace:name {argument} {--opt=default}'

Options may also have shortcuts to make them easier to remember and use. To set a shortcut, prepend it to the command name and separate the two with a pipe ('|'):

protected $signature = 'namespace:name {argument} {--o|opt}'

Now, the option may be called inthe standard way:

php yarak namespace:name argument --opt

Or by using the shortcut:

php yarak namespace:name argument -o

Options may also be passed as arrays:

protected $signature = 'namespace:name {argument} {--opt=*}'

When passing options arrays, each value must be prefixed by the option name:

php yarak namespace:name argument --opt=one --opt=two --opt=three

The value of opt will be set to ['one', 'two', 'three'].

Just like with arguments, the option description can best by appending a colon (':') and the description to the option name definiton:

protected $signature = 'namespace:name {argument} {--o|opt : option description.}'

Accessing Command Arguments And Options

To access arguments in the handle method, use the argument method:. If an argument name is given, it will return the value of the argument and if nothing is passed, it will return an array of all arguments:

protected function handle()
{
    $arg = $this->argument('arg'); // passed value of arg

    $allArguments = $this->argument(); // array of all arguments
}

The option method works in the exact same way:

protected function handle()
{
    $opt = $this->option('opt'); // passed value of opt

    $allOptions = $this->option(); // array of all options
}

There are also hasArgument and hasOption methods on the command object:

protected function handle()
{
    $argExists = $this->hasArgument('exists');  // true

    $optExists = $this->hasOption('doesntExist');  // false
}

Asking for confirmation

The confirm method can be used to ask the user for a simple confirmation

if ($this->confirm('Do you wish to continue? ')) {
    //
}

Ask a question

In case an open question needs to be prompted the user, the ask method can be used. The second argument provides a default fallback

$name = $this->ask('What is your name?', 'Nobody');

Ask for a password

The user answer can be hidden by using the askPassword method

$password = $this->askPassword('Please type the password');

Choosing from a list

The choose method only allows an answer from a predefined list of choices

$car = $this->choose('What is your favourite car?', ['Ferrari', 'Lamborghini', 'Maserati'], 1);

Auto-completion

The anticipate method can provide the user some auto-completion help when starting to write. The user can still choose any answer, regardless of the auto-completion hints

$food = $this->anticipate('What is your favourite food?', ['Pizza', 'Pasta', 'Lasagna'], 'Mozzarella');

Multi choice

When the user should be allowed to choose more than a single answer, the choice method allows to select them from a list

$colors = $this->choice('What are your favourite colors (defaults to blue and red)?', ['Blue', 'Red', 'Green'], '0,1');

Command Output

Every command has an output variable stored on the object that has several methods to help write output to the console.

The write method outputs plain unformatted text, writeInfo outputs green text, writeError outputs red text, and writeComment outputs yellow text:

protected function handle()
{
    $this->output->write('Message'); // plain text

    $this->output->writeInfo('Message');  // green text

    $this->output->writeError('Message');  // red text

    $this->output->writeComment('Message');  // yellow text
}

The output variable is a simple wrapper around Symfony's output class. To access this class, use the getOutputInterface method:

protected function handle()
{
    $output = $this->getOutputInterface(); // $output is instance of Symfony\Component\Console\Output\OutputInterface
}

Keep in mind that the Yarak command class simply wraps up the Symfony console component. All Symfony command features are available on your custom command object. See the Symfony console component documentation for more details.

Using Custom Commands

Before using your custom command, you must register it in the command Kernel $commands array:

use Yarak\Console\ConsoleKernel;
use App\Console\Commands\ExampleCommand;
use App\Comsone\Commands\YourCustomCommand;

class Kernel extends ConsoleKernel
{
    /**
     * Your custom Yarak commands.
     *
     * @var array
     */
    protected $commands = [
        ExampleCommand::class,
        YourCustomCommand::class
    ];
}

Onces registered, the commands may be used like any other Yarak command:

php yarak namespace:name arg --opt

Top

Calling Yarak In Code

To call a Yarak command from your codebase, use the Yarak::call static method.

public static function call($command, array $arguments = [], \Phalcon\DiInterface $di = null)

For example, to call migrate:rollback --steps=2:

use Yarak\Yarak;

Yarak::call('migrate:rollback', [
    '--steps'    => 2,
]);

Yarak will use the default DI to get its settings. If the resolved default DI is not working, pass an instance of Phalcon\Di as the third argument to the call method:

use Yarak\Yarak;

Yarak::call('migrate:rollback', [
    '--steps'    => 2,
], $di);

If you are running PHP 5.6 or lower, using the static call method may result in the following error message:

Cannot bind an instance to a static closure

To avoid this error, pass the $di as the third variable to Yarak::call as shown above.

Top

Developing

To set up the project locally for development, clone the repo and run composer install from the project root directory. Create the necessary database and enter your database configuration details in both codeception.yml and app/config/config.php. Run the full test suite with php vendor/codeception/codeception/codecept run or use the following composer scripts:

  • composer test: run the full test suite
  • composer testf: run only the functional tests
  • composer testu: run only the unit tests

Top

Credits and Contributing

This project is largely inspired by the Laravel project. Some portions of code in Yarak were taken directly from the Laravel project. Many thanks to @taylorotwell and the rest of the Laravel contributors.

Contributions are more than welcome. Fork, improve and make a pull request. For bugs, ideas for improvement or other, please create an issue.

Comments
  • PDO::__construct(): php_network_getaddresses: getaddrinfo failed: No such host is known.

    PDO::__construct(): php_network_getaddresses: getaddrinfo failed: No such host is known.

    Hi there,

    Firstly nice work.

    Secondly I can't seem to be able to seed my db as the following error occurs... PDO::__construct(): php_network_getaddresses: getaddrinfo failed: No such host is known.

    I am running on Docker so my host is my service container name db. My application has had no trouble connecting to the database and inserting entries, it's all configured in config.php.

    Trace image

    Config file Registering DB Service Registering Yarak Service Yarak file

    Connecting manually seems to work?

    try {
        $pdo = new \PDO('mysql:host=dev_db;dbname=realworlddb;port=3306;charset=utf8', 'root', 'secret');
        $stmt = $pdo->query('SELECT * FROM users');
        $result = $stmt->fetchAll();
        var_dump($result); exit;
    } catch (\Exception $e) {
        var_dump($e); exit;
    }
    

    UPDATE:

    It fails at this line... https://github.com/zachleigh/yarak/blob/master/src/DB/Factories/ModelFactoryBuilder.php#L148 Though I can run the same type of code in a controller so maybe it's a CLI issue?

    Do you know how I can debug this problem?

    Thanks

    opened by linxlad 8
  • Several failing tests

    Several failing tests

    It seems that several tests are failing.

    Time: 9.81 seconds, Memory: 20.00MB
    
    There were 12 errors:
    

    Some of them are due to:

    [PDOException] SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'password' at row 1
    

    Then there are other 5 failing tests that seem to be more serious. Could you please check and fix them?

    Thanks

    opened by micheleangioni 4
  • make:migration create option does NOT take the name of the table

    make:migration create option does NOT take the name of the table

    Hi, first of all, congratulations for the great work on this package.

    The command php yarak make:migration create_users_table --create=users, present in the readme, does NOT work, since create is an option and cannot take values.

    Instead php yarak make:migration create_users_table --create works, but it does not set the table name properly.

    Please continue to develop Yarak, it can be of great value to the Phalcon community :)

    bug 
    opened by micheleangioni 4
  • Added new Abstract Commands

    Added new Abstract Commands

    Hi, this PR first of all fixes #25. Several tests might crash due to the following error

    [PDOException] SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'password' at row 1

    that can be easily solved just raising the length of the varchar user password field in test migrations.

    Then adds several new commands to the Abstract Command Class.

    My inspiration was of course Laravel Artisan and, using Synfony Console I implemented the methods confirm, ask, askPassword, choose, anticipate and choice.

    All methods find proper documentation in the readme.

    opened by micheleangioni 3
  • Consider renaming dd()

    Consider renaming dd()

    Hi, since dd() is a very popular Laravel function, many developers (such as me) have defined their own dd() function in Phalcon. In Unit Tests the Yarak helpers file is called at the very beginning, Yarak's dd() function is always defined before the custom application's dd().

    Since I suppose Yarak's dd() is used only for Yarak's development, could you just rename it?

    enhancement 
    opened by micheleangioni 3
  • Specify how to manually run tests

    Specify how to manually run tests

    Hi, please specify in the readme how to manually run tests, for people who want to contribute:

    1. Run composer install
    2. Specify a correct user/password in the codeception.yml file and /app/config/config.php files
    3. Run php vendor/codeception/codeception/codecept run

    Thanks.

    documentation 
    opened by micheleangioni 2
  • The --refresh and --seed options don't exist in migrate command

    The --refresh and --seed options don't exist in migrate command

    The example provided in the readme

    php yarak migrate --refresh --seed --class=SeederName

    is wrong. Those options don't exist:

    protected $signature = 'make:migration
                                {name : The name of your migration, words separated by underscores.}
                                {--c|create : The name of the table to create.}';
    
    
    documentation 
    opened by micheleangioni 2
  • Unable to detect 'databaseDir' from config when using factory function in phpunit test

    Unable to detect 'databaseDir' from config when using factory function in phpunit test

    Getting the following error when running phpunit:

    Yarak\Exceptions\InvalidConfig: The setting 'application -> databaseDir' can not be found. Please be sure it is set.

    This is what my phpunit bootstrap file looks like:

    <?php
    
    use Phalcon\Di\FactoryDefault;
    use Phalcon\Loader;
    
    ini_set("display_errors", 1);
    error_reporting(E_ALL);
    
    define("ROOT_PATH", __DIR__);
    define("BASE_PATH", __DIR__ . '/../');
    define("APP_PATH", BASE_PATH . 'app');
    
    $di = new FactoryDefault();
    
    include APP_PATH . '/config/services.php';
    
    $config = $di->getConfig();
    
    include APP_PATH . '/config/loader.php';
    
    opened by debasishrowlo 1
  • namespace problems uncaught

    namespace problems uncaught

    After running console:generate, I get the following error:

    PHP Fatal error:  Uncaught Error: Class 'App\Console\Kernel' not found
    

    The root namespace in the project is not App. Should have a root namespace setting to allow for better namespace guessing.

    opened by zachleigh 1
  • If consoleDir not set, get PHP warnings but no exception or helpful message

    If consoleDir not set, get PHP warnings but no exception or helpful message

    Running console:generate' when noconsoleDir` \value is set results in the following output:

    Created commands directory.
    PHP Warning:  file_put_contents(/Kernel.php): failed to open stream: Permission denied in /home/zachleigh/Web/NewSonohini/vendor/zachleigh/yarak/src/Helpers/Filesystem.php on line 40
    Created kernel file.
    PHP Warning:  file_put_contents(/commands/ExampleCommand.php): failed to open stream: No such file or directory in /home/zachleigh/Web/NewSonohini/vendor/zachleigh/yarak/src/Helpers/Filesystem.php on line 40
    Created example command file.
    

    Nothing was actually created. Need to either have a default path or catch this circumstance and throw an appropriate error.

    opened by zachleigh 1
  • Update Phalcon install script

    Update Phalcon install script

    install-phalcon.sh:

    # Temporarilly using zephir for all builds. Once https://github.com/phalcon/cphalcon/issues/11961 
    # gets fixed we can revert to only PHP7 using zephir.
    

    The https://github.com/phalcon/cphalcon/issues/11961 was fixed

    opened by sergeyklay 1
  • Possibility to use existing

    Possibility to use existing "db" instead opening new one

    I need option to use existing "db" connection. I'm using sqlite in-memory option in my unit tests, in-memory option is not shareable between apps. That's why i have to share my apps db connection with yarak. Here is my workaround in ConnectionResolver class

    <?php
    
    namespace Yarak\DB;
    
    use Phalcon\Exception;
    use Phalcon\Di;
    
    class ConnectionResolver
    {
        /**
         * Get connection to database.
         *
         * @param array $dbConfig
         *
         * @throws Exception
         *
         * @return \Phalcon\Db\Adapter\Pdo
         */
        public function getConnection(array $dbConfig)
        {
    
            $di = DI::getDefault();
    
            if($di->has('db'))
                return $di->getShared('db');
    
            $dbClass = sprintf('\Phalcon\Db\Adapter\Pdo\%s', $dbConfig['adapter']);
    
            if (!class_exists($dbClass)) {
                throw new Exception(
                    sprintf('PDO adapter "%s" not found.', $dbClass)
                );
            }
    
            unset($dbConfig['adapter']);
    
            return new $dbClass($dbConfig);
        }
    }
    
    
    opened by zeecher 0
Releases(v1.2.0)
Owner
Zach Leigh
Zach Leigh
A Phalcon paginator adapter for Phalcon Collections

Phalcon Collection Paginator A Phalcon paginator adapter for Phalcon Collections Why create this? Being familiar with the various Pagination data adap

Angel S. Moreno 2 Oct 7, 2022
Phalcon Demo Application

Phalcon Demo Application We use modified Phalcon INVO Application to demonstrate basics of Codeception testing. We expect to implement as many feature

Codeception Testing Framework 42 Oct 7, 2022
Phalcon official Forum

Phosphorum 3 Phosphorum is an engine for building flexible, clear and fast forums. You can adapt it to your own needs or improve it if you want. Pleas

The Phalcon PHP Framework 361 Dec 27, 2022
Phalcon PHP Meta tags Service

About Phalcon meta tags plugin for PhalconPHP. This plugin allows you to easily and flexibly customize the meta tags of your view. If this plugin help

null 12 Oct 7, 2022
Implementation of an API application using the Phalcon Framework

phalcon-api Sample API using Phalcon Implementation of an API application using the Phalcon Framework https://phalcon.io Installation Clone the projec

The Phalcon PHP Framework 81 Dec 15, 2022
A composer package designed to help you create a JSON:API in Phalcon

phalcon-json-api-package A composer package designed to help you create a JSON:API in Phalcon What happens when a PHP developer wants to create an API

Jim 36 Oct 7, 2022
Setupify is a Phalcon provisioning and development tool.

Setupify Provisioning Tool WARNING: Setupify is currently in a state of experimentation. Use tag release. Setupify is a collection of bash scripts for

Perch Labs 3 Oct 7, 2022
RedisPlugin for Phalcon

RedisPlugin for Phalcon (The correspondence of MySQL sharding.) Composer { "require": { "ienaga/phalcon-redis-plugin": "3.*" } } Versio

Toshiyuki Ienaga 16 Oct 7, 2022
Easy Repository pattern for PHP Phalcon framework.

Phalcon Repositories Introduction Phalcon Repositories lets you easily build repositories for your Phalcon models, for both SQL and Mongo drivers. PHP

Michele Angioni 18 Oct 7, 2022
Incubator adapters/functionality for the Phalcon PHP Framework

Phalcon Incubator This is a repository to publish/share/experiment with new adapters, prototypes or functionality that can potentially be incorporated

The Phalcon PHP Framework 735 Dec 27, 2022
PHP Profiler & Developer Toolbar (built for Phalcon)

Prophiler - A PHP Profiler & Developer Toolbar built for Phalcon Demo The demo website has been moved to a separate repository: https://github.com/fab

Fabian Fuelling 444 Dec 27, 2022
A powerful debug and profilers tool for the Phalcon Framework

Phalcon Debugbar Integrates PHP Debug Bar with Phalcon Framework. 中文说明 Features Normal request capturing Ajax request capturing Redirect request chain

Yajie Zhu 162 Oct 7, 2022
Phalcon 3.x BB Debugger Strong and easy install.

Phalcon BB Debugger Phalcon Version: 3.x BB Debugger Version: 1.0.3 Change Log See ChangeLog What is BB Debugger ? The bb debugger, written for the ph

İsmail 6 Oct 7, 2022
Time registration tool build with Phalcon

PhalconTime Application PhalconTime is a timekeeping tool that helps you track hours spend on clients and projects. Please write me if you have any fe

null 6 Oct 7, 2022
phalcon config loader for yaml

Phalcon Config Loarder for Yaml Loads all the yml in the directory of the app/config. Version PHP: 7.0.x, 7.1.x, 7.2.x Phalcon: 3.x Composer { "r

Toshiyuki Ienaga 2 Oct 7, 2022
Phalcon cli template

Phalcon cli template This is just sample boostraping application for command line applications using the outstanding Phalcon Framework. Installation J

Guilherme Viebig 9 Oct 7, 2022
CMS based on Phalcon PHP Framework with modular structure

Yona CMS Yona CMS - open source content management system (CMS). Written in Phalcon PHP Framework (version 3.x supported) Has a convenient modular str

Alexander Torosh 369 Dec 27, 2022
Движок блог-лайк на фрейморвке Phalcon

Skopy Skopy - это простой движок блога на фреймворке Phalcon 3. Для работы блога необходимо скачать и установить Phalcon на сервер. Инструкции можно п

Yuriy Grinev 9 Dec 14, 2022
Phalcon Auth - Guard-Based Authentication

Phalcon Auth You can see an example of an application with authorization and limit here sinbadxiii/phalcon-auth-example Стандартная аутентификация на

Sergey Mukhin 6 Oct 7, 2022