Skip to content

Commit

Permalink
release version
Browse files Browse the repository at this point in the history
  • Loading branch information
spoltar committed Dec 23, 2016
1 parent 4d7ba3e commit 8ca0c8b
Show file tree
Hide file tree
Showing 4 changed files with 409 additions and 3 deletions.
235 changes: 235 additions & 0 deletions FluentComponentBehavior.php
@@ -0,0 +1,235 @@
<?php
/**
* @link https://github.com/consik/yii2-fluent
*
* @author Sergey Poltaranin <consigliere.kz@gmail.com>
* @copyright Copyright (c) 2016
*/

namespace consik\yii2fluent;

use yii\base\Behavior;
use yii\base\Component;
use yii\base\Event;
use yii\base\InvalidCallException;
use yii\base\UnknownPropertyException;
use yii\caching\Cache;

/**
* Class FluentComponentBehavior
* Behavior implements fluent interface methods for any yii2 Component
*
* Fluent methods:
* add*Property*(mixed $item) - adds item to array property
* set*Property*(mixed $val) - sets property value
* unset*Property*() - unsets property
*
* universal fluent methods, can be accessed through component object:
* unsetProperty(string $property)
* setProperty(string $property)
* addItemTo(string $arrProperty, mixed $val, $initOnEmpty = true)
*
* @property array $attributes - enumeration of attributes which will be available through fluent methods. You can define alias of attribute for fluent interface, using associative array
* @property bool $initArraysIfEmpty - init empty properties as array using fluent method add*AttributeName* if they are not defined as array.
*
* Your component code:
* ```
* public function behaviors()
* {
* return [
* FluentComponentBehavior::className(), //simple definition. Behavior will implement all fluent methods for all attributes
* [ //extended definition of behavior
* 'class' => FluentComponentBehavior::className(),
* 'attributes' => ['new' => 'isNewRecord', 'id'] //behavior will implement fluent methods only for 'isNewRecord' and 'id'.
* //fluent methods for attribute isNewRecords will be implemented using alias 'new'. for example: $component->setNew($value) equals $component->isNewRecord = $value;
* 'initArraysIfEmpty' => true
* ]
* ];
* }
* ```
*
* @package consik\yii2fluent
*/
class FluentComponentBehavior extends Behavior
{
/**
* @var Component
*/
public $owner;

/**
* Array of attributes that can be changed using fluent interface.
* It can be associative, where `key` is method suffix and `value` is attributeName
* Or simple enumeration of attributes name
*
* Example 1:
* [ 'new' => 'isNewRecord' ]
* calling $component->setNew(false) changes $component->isNewRecord attribute and returns $component object
*
* Example 2. You can combine associative and simple syntax:
* [ 'new' => 'isNewRecord', 'id', ...]
* available methods: $component->setNew($value)->setId($id)
*
* @var array
*/
public $attributes = [];

/**
* @var bool initialize property as array or not if property is empty and !is_array
*/
public $initArraysIfEmpty = true;

/**
* Return associative array of fluent interface methods, key = fluent method name, value - behavior method name
* @return array
*/
protected function getMethodsMap()
{
return [
'add' => 'addItemTo',
'set' => 'setProperty',
'unset' => 'unsetProperty'
];
}

/**
* @param string $name
* @param array $params
* @return Component|mixed
* @throws UnknownPropertyException
* @throws InvalidCallException
*/
public function __call($name, $params)
{
$action = $this->getActionFromMethod($name);
$property = $this->getPropertyFromMethod($name);
if ($action && $property && $this->owner->canSetProperty($property)) {
$method = $this->getMethodsMap()[$action];
switch ($action) {
case 'unset':
return $this->unsetProperty($property);
case 'add':
return $this->addItemTo($property, $params[0], $this->initArraysIfEmpty);
default:
return $this->{$method}($property, ...$params);
}
}
return parent::__call($name, $params);
}

/**
* Sets value to component property and returns component object
*
* @param $name
* @param $value
* @return Component
* @throws UnknownPropertyException
* @throws InvalidCallException
*/
public function setProperty($name, $value)
{
if ($this->owner->canSetProperty($name)) {
$this->owner->{$name} = $value;
return $this->owner;
}
if ($this->owner->canGetProperty($name)) {
throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
} else {
throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
}
}

/**
* Sets component property to null and returns component object. Equal for setProperty($name, null)
*
* @param $name
* @return Component
* @throws UnknownPropertyException
* @throws InvalidCallException
*/
public function unsetProperty($name)
{
unset($this->owner->{$name});
return $this->owner;
}

/**
* Adds $item to the component property $arrName and return component object
*
* @param $arrName
* @param $item
* @param bool $initOnEmpty initialize property as array or not if property is empty and !is_array
* @return Component
* @throws UnknownPropertyException
* @throws InvalidCallException
*/
public function addItemTo($arrName, $item, $initOnEmpty = true)
{
if ($this->owner->canSetProperty($arrName)) {
if (!is_array($this->owner->{$arrName})) {
if (empty($this->owner->{$arrName}) && $initOnEmpty) {
$this->owner->{$arrName} = [];
} else {
throw new InvalidCallException('Cannot add item to the non-array property: ' . get_class($this) . '::' . $arrName);
}
};
$array = $this->owner->{$arrName};
$array[] = $item;
$this->owner->{$arrName} = $array;
return $this->owner;
}
if ($this->owner->canGetProperty($arrName)) {
throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $arrName);
} else {
throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $arrName);
}
}

/**
* @param $name - name of the called method
* @return string - name of the action if behavior has this fluent method
*/
protected function getActionFromMethod($name)
{
foreach ($this->methodsMap as $action => $method) {
if (strncmp($name, $action, strlen($action)) === 0) {
return $action;
}
};
return '';
}

/**
* @param $name - name of the called method
* @return string - name of the property if behavior has fluent method for the property
*/
protected function getPropertyFromMethod($name)
{
$property = '';
$property = '';
if ($action = $this->getActionFromMethod($name)) {
$property = lcfirst(substr($name, strlen($action)));
if (!empty($this->attributes)) {
if (array_key_exists($property, $this->attributes)) {
$property = $this->attributes[$property];
} elseif (in_array($property, $this->attributes)) {
$key = array_search($property, $this->attributes);
if (is_string($key)) {
$property = '';
}
} else {
$property = '';
}
}
}
return $property;
}

/**
* @inheritdoc
*/
public function hasMethod($name)
{
return $this->getPropertyFromMethod($name) ? true : parent::hasMethod($name);
}
}
2 changes: 1 addition & 1 deletion LICENSE
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2016 consik
Copyright (c) 2016 Sergey Poltaranin <consigliere.kz@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
153 changes: 151 additions & 2 deletions README.md
@@ -1,2 +1,151 @@
# yii2-fluent
Adds fluent interface for changing any property in yii2 component
# Yii2 Fluent Interface Behavior

Behavior that implements fluent interface methods for component attributes.

[![Latest Stable Version](https://poser.pugx.org/consik/yii2-fluent/v/stable)](https://packagist.org/packages/consik/yii2-fluent)
[![Total Downloads](https://poser.pugx.org/consik/yii2-fluent/downloads)](https://packagist.org/packages/consik/yii2-fluent)
[![License](https://poser.pugx.org/consik/yii2-fluent/license)](https://packagist.org/packages/consik/yii2-fluent)

## Installation

The preferred way to install this extension is through [composer](http://getcomposer.org/download/).

Either run

```
composer require consik/yii2-fluent
```

or add

```json
"consik/yii2-fluent": "^1.0"
```

## FluentComponentBehavior class description

### Properties

* ``` array $attributes = []```

Associative or simple array of attributes that can be changed using fluent interface.
For associative definition `key` is alias for attributes methods;
Value is always component attribute name;

* ``` string $initArraysIfEmpty = true```

Defines need bahavior to initialize property as array if it's empty and !is_array() when calling array-access fluent methods(like ```add*Property*($item)```)

### Public methods

Universal fluent methods for owner component

* ``` $this setProperty(string $property, mixed $value)```

Sets value to component property

* ``` $this unsetProperty(string $property)```

Unsets component property

* ``` $this addItemTo(string $arrName, mixed $value, $initOnEmpty = true)```

Adds ```$item``` to array property with name ```$arrName```;

Throws exception if ```$component->{$arrName}``` is ```!empty() && !is_array()```;

initializes ```$component->{$arrName}``` as empty array if ```($initOnEmpty && empty($component->{$arrName}) && !is_array($component->{$arrName}))``` ;

## Examples

### Short definition of behavior. Behavior will implement all available fluent methods for ALL component attributes
```php
<?php
use consik\yii2fluent\FluentComponentBehavior;

class Test extends \yii\base\Component
{
public $isNew;
public $comments;

public function behaviors()
{
return [
FluentComponentBehavior::className()
];
}
}
```
Available fluent methods for this definition:
* (new Test())
* ->setProperty($name, $value)
* ->unsetProperty($name)
* ->addItemTo($arrName, $arrayItem)
* ->setIsNew($value)
* ->unsetIsNew()
* ->addIsNew($arrayItem)
* ->setComments($value)
* ->unsetComments()
* ->addComments($arrayItem)

### Extended definition of behavior, for enumerated properties, with alias for one of property.
```php
<?php
use consik\yii2fluent\FluentComponentBehavior;

class Test extends \yii\base\Component
{
public $isNew;
public $comments;
public $fluentUnaccessable;

public function behaviors()
{
return [
[
'class' => FluentComponentBehavior::className(),
'attributes' => [
'new' => 'isNew',
'comments'
]
];
}
}
```
Available fluent methods for this definition:
* (new Test())
* ->setProperty($name, $value)
* ->unsetProperty($name)
* ->addItemTo($arrName, $arrayItem)
* ->setNew($value)
* ->unsetNew()
* ->addNew($arrayItem)
* ->setComments($value)
* ->unsetComments()
* ->addComments($arrayItem)

## Be helpful!

Don't forget about other developers and write comments for your classes!

Basic comment for all components with attached FluentInterfaceBehavior
```
@method $this setProperty(string $name, $value)
@method $this unsetProperty(string $name)
@method $this addItemTo(string $arrName, mixed $item)
```
```php
<?php
/*
* Class YourClass
* @method $this setProperty(string $name, $value)
* @method $this unsetProperty(string $name)
* @method $this addItemTo(string $arrName, mixed $item)
*/
class YourClass extends \yii\base\components { ... }
```

And, please, don't forget writing comments about defined fluent methods for your component properties!!!

Best regards,
Sergey Poltaranin.

0 comments on commit 8ca0c8b

Please sign in to comment.