diff --git a/.travis.yml b/.travis.yml index 4040863..ac7d3d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,17 +19,17 @@ install: - export PATH="$HOME/.composer/vendor/bin:$PATH" - travis_retry composer install --prefer-dist --no-interaction -before_script: - - | - if [ $TRAVIS_PHP_VERSION = '5.6' ]; then - PHPUNIT_FLAGS="--coverage-clover=coverage.clover" - fi +#before_script: +# - | +# if [ $TRAVIS_PHP_VERSION = '5.6' ]; then +# PHPUNIT_FLAGS="--coverage-clover=coverage.clover" +# fi script: - ./vendor/bin/phpunit --verbose $PHPUNIT_FLAGS -after_script: - - | - if [ $TRAVIS_PHP_VERSION = '5.6' ]; then - travis_retry wget https://scrutinizer-ci.com/ocular.phar - php ocular.phar code-coverage:upload --format=php-clover coverage.clover - fi \ No newline at end of file +#after_script: +# - | +# if [ $TRAVIS_PHP_VERSION = '5.6' ]; then +# travis_retry wget https://scrutinizer-ci.com/ocular.phar +# php ocular.phar code-coverage:upload --format=php-clover coverage.clover +# fi diff --git a/README.md b/README.md index daa37e7..12be7cb 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ It is based on [ApexWire's](https://github.com/ApexWire) [yii2-restclient](https [![Latest Stable Version](https://poser.pugx.org/simialbi/yii2-rest-client/v/stable?format=flat-square)](https://packagist.org/packages/simialbi/yii2-rest-client) [![Total Downloads](https://poser.pugx.org/simialbi/yii2-rest-client/downloads?format=flat-square)](https://packagist.org/packages/simialbi/yii2-rest-client) [![License](https://poser.pugx.org/simialbi/yii2-rest-client/license?format=flat-square)](https://packagist.org/packages/simialbi/yii2-rest-client) +[![Build Status](https://travis-ci.com/simialbi/yii2-rest-client.svg?branch=master)](https://travis-ci.com/simialbi/yii2-rest-client) ## Resources * [yii2-restclient](https://github.com/ApexWire/yii2-restclient) @@ -135,4 +136,4 @@ The usage how to define the active record (rules, behaviors etc.) is the same li ## Acknowledgments * [ApexWire's](https://github.com/ApexWire) [yii2-restclient](https://github.com/ApexWire/yii2-restclient) * [Yii2 HiArt](https://github.com/hiqdev/yii2-hiart). - * [mikolajzieba](https://github.com/mikolajzieba) \ No newline at end of file + * [mikolajzieba](https://github.com/mikolajzieba) diff --git a/src/ActiveFixture.php b/src/ActiveFixture.php new file mode 100644 index 0000000..615d9d0 --- /dev/null +++ b/src/ActiveFixture.php @@ -0,0 +1,118 @@ +<?php +/** + * @package yii2-rest-client + * @author Simon Karlen <simi.albi@outlook.com> + * @copyright Copyright © 2019 Simon Karlen + */ + +namespace simialbi\yii2\rest; + +use yii\base\InvalidConfigException; +use yii\helpers\Inflector; +use yii\helpers\StringHelper; +use yii\test\BaseActiveFixture; + +class ActiveFixture extends BaseActiveFixture +{ + /** + * @var Connection|array|string the DB connection object or the application component ID of the DB connection. + * After the DbFixture object is created, if you want to change this property, you should only assign it + * with a DB connection object. + * Starting from version 2.0.2, this can also be a configuration array for creating the object. + */ + public $db = 'rest'; + /** + * @var string the name of the model that this fixture is about. If this property is not set, + * the model name will be determined via [[modelClass]]. + * @see modelClass + */ + public $modelName; + + /** + * @var \yii\db\ActiveRecord[] the loaded AR models + */ + private $_models = []; + + + /** + * {@inheritDoc} + * + * @throws InvalidConfigException + */ + public function init() + { + parent::init(); + if ($this->modelClass === null && $this->modelName === null) { + throw new InvalidConfigException('Either "modelClass" or "modelName" must be set.'); + } + if ($this->modelName === null) { + $this->modelName = Inflector::camel2id(StringHelper::basename($this->modelClass), '-'); + } + } + + /** + * {@inheritDoc} + * + * @throws InvalidConfigException + * @throws \ReflectionException + */ + public function load() + { + $this->data = []; + foreach ($this->getData() as $alias => $row) { + $this->data[$alias] = $row; + } + } + + /** + * {@inheritDoc} + * + * @throws InvalidConfigException + * @throws \ReflectionException + */ + protected function getData() + { + if ($this->dataFile === null) { + if ($this->dataDirectory !== null) { + $dataFile = $this->modelName . '.php'; + } else { + $class = new \ReflectionClass($this); + $dataFile = dirname($class->getFileName()) . '/data/' . $this->modelName . '.php'; + } + + return $this->loadData($dataFile, false); + } + return parent::getData(); + } + + /** + * {@inheritDoc} + */ + public function getModel($name) + { + if (!isset($this->data[$name])) { + return null; + } + if (array_key_exists($name, $this->_models)) { + return $this->_models[$name]; + } + + if ($this->modelClass === null) { + throw new InvalidConfigException('The "modelClass" property must be set.'); + } + $row = $this->data[$name]; + /* @var $modelClass ActiveRecord */ + $modelClass = $this->modelClass; + $keys = []; + foreach ($modelClass::primaryKey() as $key) { + $keys[$key] = isset($row[$key]) ? $row[$key] : null; + } + + /* @var $model ActiveRecord */ + $model = new $modelClass(); + $model->setOldAttributes($row); + $model->setAttributes($row, false); + + return $this->_models[$name] = $model; + } +} diff --git a/src/ActiveQuery.php b/src/ActiveQuery.php index 03b6c77..1e2946c 100644 --- a/src/ActiveQuery.php +++ b/src/ActiveQuery.php @@ -27,24 +27,6 @@ class ActiveQuery extends Query implements ActiveQueryInterface */ public $joinWith = []; - /** - * @var array options for search - */ - public $options = []; - - /** - * Constructor. - * - * @param string $modelClass the model class associated with this query - * @param array $config configurations to be applied to the newly created query object - */ - public function __construct($modelClass, $config = []) - { - $this->modelClass = $modelClass; - parent::__construct($config); - } - - /** * Creates a DB command that can be used to execute this query. * @@ -71,10 +53,6 @@ public function createCommand($db = null) $this->from($modelClass::modelName()); } -// if ($this->searchModel === null) { -// $this->searchModel = mb_substr(mb_strrchr($this->modelClass, '\\'), 1) . 'Search'; -// } - return parent::createCommand($db); } @@ -104,6 +82,7 @@ public function one($db = null) /** * {@inheritdoc} + * @throws InvalidConfigException */ public function prepare($builder) { @@ -111,7 +90,48 @@ public function prepare($builder) $this->buildJoinWith(); $this->joinWith = null; } - return $this; + + if ($this->primaryModel === null) { + // eager loading + $query = Query::create($this); + } else { + // lazy loading of a relation + $where = $this->where; + + if ($this->via instanceof self) { + // via junction table + $viaModels = $this->via->findJunctionRows([$this->primaryModel]); + $this->filterByModels($viaModels); + } elseif (is_array($this->via)) { + // via relation + /* @var $viaQuery ActiveQuery */ + list($viaName, $viaQuery) = $this->via; + if ($viaQuery->multiple) { + if ($this->primaryModel->isRelationPopulated($viaName)) { + $viaModels = $this->primaryModel->$viaName; + } else { + $viaModels = $viaQuery->all(); + $this->primaryModel->populateRelation($viaName, $viaModels); + } + } else { + if ($this->primaryModel->isRelationPopulated($viaName)) { + $model = $this->primaryModel->$viaName; + } else { + $model = $viaQuery->one(); + $this->primaryModel->populateRelation($viaName, $model); + } + $viaModels = $model === null ? [] : [$model]; + } + $this->filterByModels($viaModels); + } else { + $this->filterByModels([$this->primaryModel]); + } + + $query = Query::create($this); + $this->andWhere($where); + } + + return $query; } /** diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 36341ff..a64c803 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -65,15 +65,10 @@ public static function primaryKey() * @return ActiveQuery * @throws InvalidConfigException */ - public static function find($options = []) + public static function find() { - $config = [ - 'class' => 'simialbi\yii2\rest\ActiveQuery', - 'options' => $options - ]; - /* @var $query ActiveQuery */ - $query = Yii::createObject($config, [get_called_class()]); + $query = Yii::createObject(ActiveQuery::class, [get_called_class()]); return $query; } diff --git a/src/Command.php b/src/Command.php index d4f99df..004fb33 100644 --- a/src/Command.php +++ b/src/Command.php @@ -54,17 +54,24 @@ public function getRawUrl() } /** - * @return mixed + * Executes the SQL statement and returns ALL rows at once. + * @param int $fetchMode for compatibility with [[\yii\db\Command]] + * @return array all rows of the query result. Each array element is an array representing a row of data. + * An empty array is returned if the query results in nothing. */ - public function queryAll() + public function queryAll($fetchMode = null) { return $this->queryInternal(); } /** - * @return mixed + * Executes the SQL statement and returns the first row of the result. + * This method is best used when only the first row of result is needed for a query. + * @param int $fetchMode for compatibility with [[\yii\db\Command]] + * @return array|false the first row (in terms of an array) of the query result. False is returned if the query + * results in nothing. */ - public function queryOne() + public function queryOne($fetchMode = null) { /* @var $class ActiveRecord */ $class = $this->modelClass; diff --git a/src/Connection.php b/src/Connection.php index f578958..48dbbb7 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -12,7 +12,6 @@ use Yii; use yii\base\Component; use yii\base\InvalidConfigException; -use yii\helpers\Url; use yii\httpclient\Client; use yii\httpclient\Response; use yii\web\HeaderCollection; @@ -177,7 +176,8 @@ public function getQueryBuilder() */ public function get($url, $data = []) { - return $this->request('get', $url, $data); + array_unshift($data, $url); + return $this->request('get', $data); } /** @@ -275,9 +275,20 @@ public function getHandler() */ protected function request($method, $url, $data = []) { + if (is_array($url)) { + $path = array_shift($url); + $query = http_build_query($url); + + array_unshift($url, $path); + + $path .= '?' . $query; + } else { + $path = $url; + } + $headers = []; $method = strtoupper($method); - $profile = $method . ' ' . Url::to($url) . '#' . (is_array($data) ? http_build_query($data) : $data); + $profile = $method . ' ' . $this->handler->baseUrl . '/' . $path . '#' . (is_array($data) ? http_build_query($data) : $data); if ($auth = $this->getAuth()) { $headers['Authorization'] = $auth; @@ -287,7 +298,7 @@ protected function request($method, $url, $data = []) /* @var $request \yii\httpclient\Request */ Yii::debug($method, __METHOD__ . '-method'); - Yii::debug($this->handler->baseUrl . '/' . $url, __METHOD__ . '-url'); + Yii::debug($this->handler->baseUrl . '/' . $path, __METHOD__ . '-url'); Yii::debug($data, __METHOD__ . '-data'); Yii::debug($headers, __METHOD__ . '-headers'); diff --git a/src/Query.php b/src/Query.php index adf33be..1c5c80c 100644 --- a/src/Query.php +++ b/src/Query.php @@ -21,11 +21,22 @@ class Query extends \yii\db\Query implements QueryInterface * @see from() */ public $from; + /** + * @var mixed Value of the primary key (special where) + */ + public $modelClass; /** - * @var ActiveRecord + * Constructor. + * + * @param string $modelClass the model class associated with this query + * @param array $config configurations to be applied to the newly created query object */ - public $searchModel; + public function __construct($modelClass, $config = []) + { + $this->modelClass = $modelClass; + parent::__construct($config); + } /** * Prepares for building query. @@ -118,4 +129,29 @@ public function from($tables) return $this; } + + /** + * {@inheritDoc} + */ + public static function create($from) + { + $modelClass = ($from->hasProperty('modelClass')) ? $from->modelClass : null; + + return new self($modelClass, [ + 'where' => $from->where, + 'limit' => $from->limit, + 'offset' => $from->offset, + 'orderBy' => $from->orderBy, + 'indexBy' => $from->indexBy, + 'select' => $from->select, + 'selectOption' => $from->selectOption, + 'distinct' => $from->distinct, + 'from' => $from->from, + 'groupBy' => $from->groupBy, + 'join' => $from->join, + 'having' => $from->having, + 'union' => $from->union, + 'params' => $from->params, + ]); + } } diff --git a/tests/RelationTest.php b/tests/RelationTest.php new file mode 100644 index 0000000..7067be9 --- /dev/null +++ b/tests/RelationTest.php @@ -0,0 +1,45 @@ +<?php +/** + * @package yii2-rest-client + * @author Simon Karlen <simi.albi@outlook.com> + * @copyright Copyright © 2019 Simon Karlen + */ + +namespace yiiunit\extensions\rest; + + +use Yii; +use yiiunit\extensions\rest\fixtures\RestModelFixture; +use yiiunit\extensions\rest\models\RestModel; + +class RelationTest extends TestCase +{ + protected function setUp() + { + parent::setUp(); + + $this->mockWebApplication(); + Yii::$app->log->logger->flush(); + } + + public function testRelationGet() + { + $fixture = new RestModelFixture(); + $fixture->load(); + + /* @var $model RestModel */ + $model = $fixture->getModel(0); + + $this->assertInstanceOf(RestModel::class, $model); + + Yii::$app->log->logger->flush(); + +// var_dump($model); + $model->getRelatedRests()->all(); + + $logEntry = $this->parseLogs(); + + $this->assertEquals('GET', $logEntry['method']); + $this->assertEquals('https://api.site.com/related-rest-models?filter%5Brest_model_id%5D=1', $logEntry['url']); + } +} diff --git a/tests/fixtures/RelatedRestModelFixture.php b/tests/fixtures/RelatedRestModelFixture.php new file mode 100644 index 0000000..0cef946 --- /dev/null +++ b/tests/fixtures/RelatedRestModelFixture.php @@ -0,0 +1,16 @@ +<?php +/** + * @package yii2-rest-client + * @author Simon Karlen <simi.albi@outlook.com> + * @copyright Copyright © 2019 Simon Karlen + */ + +namespace yiiunit\extensions\rest\fixtures; + +use simialbi\yii2\rest\ActiveFixture; + +class RelatedRestModelFixture extends ActiveFixture +{ + public $modelClass = 'yiiunit\extensions\rest\models\RelatedRestModel'; + public $depends = ['yiiunit\extensions\rest\fixtures\RestModelFixture']; +} diff --git a/tests/fixtures/RestModelFixture.php b/tests/fixtures/RestModelFixture.php new file mode 100644 index 0000000..196ceab --- /dev/null +++ b/tests/fixtures/RestModelFixture.php @@ -0,0 +1,16 @@ +<?php +/** + * @package yii2-rest-client + * @author Simon Karlen <simi.albi@outlook.com> + * @copyright Copyright © 2019 Simon Karlen + */ + +namespace yiiunit\extensions\rest\fixtures; + + +use simialbi\yii2\rest\ActiveFixture; + +class RestModelFixture extends ActiveFixture +{ + public $modelClass = 'yiiunit\extensions\rest\models\RestModel'; +} diff --git a/tests/fixtures/data/related-rest-model.php b/tests/fixtures/data/related-rest-model.php new file mode 100644 index 0000000..459fb02 --- /dev/null +++ b/tests/fixtures/data/related-rest-model.php @@ -0,0 +1,54 @@ +<?php + +return [ + [ + 'id' => 1, + 'rest_model_id' => 1, + 'subject' => 'Related Model 1', + 'message' => 'This is the first related model', + 'created_at' => 1555660035, + 'updated_at' => 1555660035, + 'created_by' => 'simialbi', + 'updated_by' => 'simialbi' + ], + [ + 'id' => 2, + 'rest_model_id' => 1, + 'subject' => 'Related Model 2', + 'message' => 'This is the second related model', + 'created_at' => 1555660035, + 'updated_at' => 1555660035, + 'created_by' => 'simialbi', + 'updated_by' => 'simialbi' + ], + [ + 'id' => 3, + 'rest_model_id' => 1, + 'subject' => 'Related Model 3', + 'message' => 'This is the third related model', + 'created_at' => 1555660035, + 'updated_at' => 1555660035, + 'created_by' => 'simialbi', + 'updated_by' => 'simialbi' + ], + [ + 'id' => 1, + 'rest_model_id' => 2, + 'subject' => 'Related Model 2.1', + 'message' => 'This is the first related model', + 'created_at' => 1555660035, + 'updated_at' => 1555660035, + 'created_by' => 'simialbi', + 'updated_by' => 'simialbi' + ], + [ + 'id' => 1, + 'rest_model_id' => 2, + 'subject' => 'Related Model 2.2', + 'message' => 'This is the second related model', + 'created_at' => 1555660035, + 'updated_at' => 1555660035, + 'created_by' => 'simialbi', + 'updated_by' => 'simialbi' + ] +]; diff --git a/tests/fixtures/data/rest-model.php b/tests/fixtures/data/rest-model.php new file mode 100644 index 0000000..dab7e0d --- /dev/null +++ b/tests/fixtures/data/rest-model.php @@ -0,0 +1,31 @@ +<?php + +return [ + [ + 'id' => 1, + 'name' => 'Model 1', + 'description' => 'This is the representation of model 1', + 'created_at' => 1555660035, + 'updated_at' => 1555660035, + 'created_by' => 'simialbi', + 'updated_by' => 'simialbi' + ], + [ + 'id' => 2, + 'name' => 'Model 2', + 'description' => 'This is the representation of model 2', + 'created_at' => 1555660035, + 'updated_at' => 1555660035, + 'created_by' => 'simialbi', + 'updated_by' => 'simialbi' + ], + [ + 'id' => 3, + 'name' => 'Model 3', + 'description' => 'This is the representation of model 3', + 'created_at' => 1555660035, + 'updated_at' => 1555660035, + 'created_by' => 'simialbi', + 'updated_by' => 'simialbi' + ] +]; diff --git a/tests/models/RelatedRestModel.php b/tests/models/RelatedRestModel.php new file mode 100644 index 0000000..acf86da --- /dev/null +++ b/tests/models/RelatedRestModel.php @@ -0,0 +1,34 @@ +<?php +/** + * @package yii2-rest-client + * @author Simon Karlen <simi.albi@outlook.com> + * @copyright Copyright © 2019 Simon Karlen + */ + +namespace yiiunit\extensions\rest\models; + +use simialbi\yii2\rest\ActiveRecord; + +/** + * Class RelatedRestModel + * @package yiiunit\extensions\rest\models + * + * @property integer $id + * @property integer $rest_model_id + * @property string $subject + * @property string $message + * @property integer $created_at + * @property integer $updated_at + * @property string $created_by + * @property string $updated_by + */ +class RelatedRestModel extends ActiveRecord +{ + /** + * {@inheritDoc} + */ + public static function primaryKey() + { + return ['id']; + } +} diff --git a/tests/models/RestModel.php b/tests/models/RestModel.php index 3da833b..7eab7e9 100644 --- a/tests/models/RestModel.php +++ b/tests/models/RestModel.php @@ -21,6 +21,8 @@ * @property integer $updated_at * @property string $created_by * @property string $updated_by + * + * @property-read RelatedRestModel[] $relatedRests */ class RestModel extends ActiveRecord { @@ -31,4 +33,13 @@ public static function primaryKey() { return ['id']; } -} \ No newline at end of file + + /** + * Get related rests + * @return \yii\db\ActiveQueryInterface + */ + public function getRelatedRests() + { + return $this->hasMany(RelatedRestModel::class, ['rest_model_id' => 'id']); + } +}