noone4rever/axessors

Generator of getters and setters for PHP


Keywords
properties, fields, getters, setters, accessors, accessor, generator, php
License
Other

Documentation

Axessors

Build Status Scrutinizer Code Quality Code Coverage Software License

Generator of getters and setters for PHP.

Installation

composer require noone4rever/axessors or just download AxessorsPHAR.

You can see this package on packagist.

System requirements

You need to use PHP 7.1 or newer to run code with standard Axessors version or PHP 7.0 for another one.

Problem

When you write your code in object-oriented style, you need to provide almost every class with getters and setters for private fields. For example, you have a class named Email. It stores email address and can parse it, e.g. to get an email domain.

class Email
{
    public $email;
    
    public function __construct()
    {
        /* ... */
    }
    
    public function getEmailDomain()
    {
        /* ... */
    }
}

Field $email is public, and user's code can put there any data, so your code probably won't work correctly.

$emailAddress->email = new \stdclass();

We can add methods getEmail() and setEmail() to the class to restrict possible range of data, that can be stored in the field $email.

public function setEmail(string $val): void
{
    $this->email = $val;
}

public function getEmail(): string
{
    return $this->email;
}

The more fields we have in our class, the more getters and setters we have to write. If our class have five fields, it will have up to ten accessors. Getters and setters always have the same structure (getter returns field value, setter rewrites field value), so accessors belong to one of the sorts code duplication.

To avoid writing getters and setters manually we can:

  • make all the fields public (it is not safe enough)
  • generate accessors with IDE (a class will still have a lot of duplicated code)
  • use properties (this can help not to write getters and setters without any additional logic only)
  • use special libraries for generating accessors (in fact there are almost no such libraries for PHP, that can help you to write less code)

Axessors introduce a short syntax to describe getters and setters.

Structure of getters and setters

We can extend setter for the field $email.

public function setEmail(string $val): void
{
    if (preg_match('/[a-z][a-z\d_\.]*@[a-z]+\.[a-z]+/i', $email)) {
        $this->email = strtolower($val);
    } else {
        throw new ValidationException('not a valid email address given');
    }
}

In this case setter:

  • runs conditions checkout
    • checks type compatibility
    • ensures, that given string matches a regex
  • runs some callbacks
    • casts a string to lowercase
  • rewrites field value

Any accessor with additional logic has the same structure, excluding rewriting of the field - getter returns field value. The same structure of accessors allows me to make a short syntax for getters and setters.

Usage

To start using Axessors, use Axessors trait in your class.

use NoOne4rever\Axessors\Axessors;

class WithTrait
{
    use Axessors;

    /* ... */
}

Fields, that should have accessors ought to be commented with a special Axessors comment. The library will use your comments to generate accessors.

private $field; #: +axs mixed

File structure

To use Axessors you should follow this file structure:

  • include of vendor/autoload.php or Axessors.phar
  • declaration of all your classes
  • call to AxessorsStartup::run()
include 'Axessors.phar';

include 'MyClassA.php';
include 'MyClassB.php';
include 'MyClassC.php';

AxessorsStartup::run();

Axessors comments

Axessors comment has simple syntax. It starts with #: and contains:

  • special keywords
  • access modifiers
  • data types declarations
  • conditions
  • callbacks
  • field aliases

Keywords

Axessors comments should contain special keywords. Keywords are used to generate accessors. Write readable or rdb for getter, writable or wrt for setter and accessible or axs for both methods.

class WithKeywords
{
    use Axessors;

    private $a; #: +rdb int
    private $b; #: +wrt int
    private $c; #: +axs int
}

$test = new WithKeywords();
$test->getA();
$test->setB(1);
$test->getC();
$test->setC(1);

Generated methods have automatically formed names: get{Field} and set{Field}.

Access modifiers

Access modifiers should be written before the keywords. To shorten syntax public, protected and private are replaced with +, ~ and - symbols.

class WithAccessModifiers
{
    use Axessors;

    private $publicAccess; #: +axs int
    private $protectedAccess; #: ~axs int
    private $privateAccess; #: -axs int
}

$test = new WithAccessModifiers();
$test->getPublicAccess(); // OK
$test->getPrivateAccess(); // Error

We can define different access modifiers for getter and setter:

private $field = 'smth'; #: ~wrt +rdb

Type declarations

Type declaration should be placed after the keyword. Axessors support standard PHP types: int, float, bool, string, array, object, resourceand callable. Type can also be described with class' name or mixed.

class WithTypeDeclarations
{
    use Axessors;

    private $email; #: +axs string
}

It is possible to write several types, that are separated by |.

class WithMultipleTypeDeclarations
{
    use Axessors;

    private $bigNumber; #: +axs int|string
}

In this case a number can be represented in scientific form (like "1e15") and then can be parsed in some way.

If the field has default value, you don't have to declare its type manually.

class WithDefaultFieldValue
{
    use Axessors;

    private $epsilon = 1e-9; #: +rdb
}

If the field has array-compatible (iterateable) type, you can specify array's content. For example, array of strings is written as array[string]. Iterateable type might have any depth, e.g. array of array of integer is written as array[array[integer]]. Arrays can contain elements with different types: array[int|string].

class Config
{
    private static $settings; #: +axs array[int|string]
}

Axessors also support extended types, that have additional methods. Extended types have capitalized names. See axessors methods.

Conditions

Sometimes we need to make a short condition for getter or setter. For example, field $age can contain any integer in range [1..120]. To perform a checkout, we will write something like this:

class WithConditionalSetter
{
    private $age;

    public function setAge(int $val): void
    {
        if ($val >= 1 && $val <= 120) {
            $this->age = $val;
        } else {
            throw new ValidationException('given age is not possible');
        }
    }
}

Axessors can perform such checkouts too. The library support mathematical expressions for int, float, array and string. Strings are represented as strlen($var), arrays are represented as count($var).

You can write in Axessors comment next operators:

  • range, e.g. 1..10
  • <, >
  • <=, >=
  • ==, !=

The last example will look like this, using the library:

class WithConditionalSetter
{
    use Axessors;

    private $age; #: +wrt int 1..120
}

Axessors support injected conditions too. Such expressions are written between the backquotes. For example, we need to check if the new value of field matches regex. This code will perform this checkout.

class Email
{
    use Axessors;

    private $email; #: +axs string `preg_match('/[a-z][a-z\d_\.]*@[a-z]+\.[a-z]+/i', $var)`
}

In this case $var means argument for setter. $var is reserved identifier, it always contain setter's argument (if we write conditions for writable statement) or actual value of field (if we write conditions for readable statement).

Axessors support multiple conditions. You can group your conditional expressions using && and || signs.

class Email
{
    use Axessors;

    private $email; #: +axs string < 120 && `preg_match('/[a-z][a-z\d_\.]*@[a-z]+\.[a-z]+/i', $var)`
}

Here we can ensure, that $email will contain a string with length less than 120 symbols and this string will match our regex.

Conditions can also be grouped by brackets.

class WithGroupedConditions
{
    use Axessors;
    
    private $exactInt; #: +axs int (!= 4 && (1..10 || > 100))
}

Callbacks

Axessors support short callbacks in getters and setters. Callback expressions are written after conditions and callback sign: ->.

Most of standard types have their own predefined callbacks:

  • string
    • lower
    • upper
    • reverse
  • int, float
    • inc
    • dec
  • array
    • flip
    • shuffle
  • bool
    • inverse

We can use lower to cast email address to lowercase.

class Email
{
    use Axessors;

    private $email; #: +axs string `preg_match('/[a-z][a-z\d_\.]*@[a-z]+\.[a-z]+/i', $var)` -> lower
}

Axessors support injected callbacks too.

class WithInjectedCallback
{
    use Axessors;

    private $system; #: +axs string <= 100 -> `system('explorer %APPDATA%')`
}

You can write in the injected callback anything you want. In the last example setter will open folder with applications data on Windows =). $var can be modified in the injected callback too: $var += 16.

You can write several callbacks separated by commas.

class WithNumber
{
    use Axessors;

    private $number; #: +axs int -> inc, dec
}

Resolving class names

If you use relative class names in the injected callback or condition, write : before the class' name to use current namespace.

class WithRelativeNames
{
    use Axessors;
    
    private $field; #: +axs `:CurrentNamespaceClass::doSmth()` -> `globalNamespaceClass::doSmthElse()`
}

Axessors recognize relative class names as absolute. It is not a bug, but maybe such behavior will be removed in next versions of library.

Using short "$." syntax

You can use $. instead of $this-> in injected strings to shorten Axessors comment.

class WithShortThisSyntax
{
    use Axessors;
    
    private $field; #: +axs int -> `$.doSmth()`
    
    private function doSmth() {...}
}

Using code blocks in injected strings

Sometimes you may need to write multiple statements in one injected string. To do this you can use code blocks.

class WithCodeBlocks
{
    use Axessors;
    
    private $field; #: +axs int `{$a = 0; $b = 255; return $var >= $a && $var <= $b;}`
}

Code blocks works as closures: {...} turns into function($var) {...}. You can use code blocks to write non-expression statements like echo.

class WithEcho
{
    use Axessors;
    
    private $field; #: +axs int -> `{echo 'ok';}`
}

Fields' aliases

You can choose, which name of the field will be used in getter or setter signature. For example, you have a field with really long name:

class WithLongField
{
    private $thisFieldWithReallyLooooongName;
}

So, default name for getter will be getThisFieldWithReallyLooooongName. We can fix this using field alias.

Field alias is written after callbacks section and alias sign: =>.

class WithLongField
{
    private $thisFieldWithReallyLooooongName; #: +rdb mixed => shortName
}

Now getter name is getShortName.

This feature can help you to avoid collisions between child and parent class methods:

class ParentClass
{
    use Axessors;

    private $field; #: +axs int => parentField
}

class ChildClass extends ParentClass
{
    use Axessors;

    private $field; #: +axs int => childField
}

So, automatically generated methods won't collide.

Axessors methods

Axessors can generate not only getters and setters. The library can emulate different standard methods, that are defined in extended library types. For example, extended Array type has methods add{Field}, delete{Field} and count{Field}.

For example, we have an indexed array with strings.

class WithArrayOfStrings
{
    use Axessors;

    private $strings; #: +axs Array[string]
}

Now our class have methods addStrings(), deleteStrings() - this methods take index as argument - and countStrings().

Implementing interfaces

Axessors support implementation of interfaces. You can comment a method in interface or abstract class in UNIX style, - Axessors will check class hierarchy and stop the program if there are not implemented methods.

interface Locatable
{
    # public function getX(): int;
    # public function getY(): int;
    # public function setX(int $val): void;
    # public function setY(int $val): void;
}

abstract class Shape implements Locatable
{
    use Axs;

    # abstract public fucntion getId(): int;
}

class Triangle extends Shape
{
    use Axessors;

    private $id; #: +rdb int
    private $x, $y; #: +axs int
}

Abstract classes with abstract Axessors methods should use trait Axs.

Multiple Axessors declarations

Sometimes it is sensible to write fields of the same type in one line. For example, class Color stores three fields with rgb values. Axessors can process such declarations.

class Color
{
    use Axessors;
    
    private $red, $green, $blue; #: +axs int 0..255
    
    /* ... */
}

$color = new Color(255, 255, 0);

$color->getRed();
$color->getGreen();
$color->getBlue();

The only restriction is you can't use field aliases in multiple declarations to avoid signature collisions.

Integration with IDE

Axessors methods are generated by the library and usually marked as non-existing by default. You can solve this problem in three ways.

Installing Axessors plugin for PhpStorm

You can download special plugin and install it. Now the library has a plugin for PhpStorm only, but in the future other IDEs will be supported too.

Writing PHPdoc comments

You can write PHPdoc comments , using tag @method before the class definition:

/**
 * Class with Axessors methods.
 * 
 * @method int getInstanceField() getter for instace field
 * @methdd static int getClassField() getter for class field
 */
class WithAxessorsMethods
{
    use Axessors;
    
    private static $classField; #: +axs int
    
    private $instanceField; #: +axs int
}

So your IDE will recognize generated accessors as magic methods.

Disabling inspection called "undefined method"

This is the easiest way, but I would recommend you to download the plugin.

Conclusions

With Axessors you can shorten description of every getter and setter in your code. The most complex Axessors comment have this structure:

  1. #:
  2. setter access modifier
  3. wrt or writable
  4. type declaration
  5. conditions for input value
  6. ->
  7. callbacks for input value
  8. getter access modifier
  9. rdb or readable
  10. conditions for field value
  11. ->
  12. callbacks for field value
  13. =>
  14. field alias

This comment structure can implement almost all the possible getters and setters in one line, and the library can halve your code by removing duplicated methods.

You can see examples in the special directory.

Ask me any questions about the library, and I will be glad to answer.