diff --git a/.stickler.yml b/.stickler.yml new file mode 100644 index 00000000..30472b32 --- /dev/null +++ b/.stickler.yml @@ -0,0 +1,3 @@ +linters: + phpcs: + standard: CakePHP diff --git a/.travis.yml b/.travis.yml index d1c2a96b..021f6236 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ install: - composer install --dev script: - - sh -c "if [ '$RUN_TESTS' = '1' ]; then phpunit --stderr; fi" + - sh -c "if [ '$RUN_TESTS' = '1' ]; then vendor/bin/phpunit --stderr; fi" - sh -c "if [ '$PHPCS' = '1' ]; then vendor/bin/phpcs -p --extensions=php --standard=vendor/cakephp/cakephp-codesniffer/CakePHP ./src ./tests; fi" notifications: diff --git a/LICENSE.txt b/LICENSE.txt index d0317668..5849d31d 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,7 +1,7 @@ The MIT License CakePHP(tm) : The Rapid Development PHP Framework (http://cakephp.org) -Copyright (c) 2005-2015, Cake Software Foundation, Inc. +Copyright (c) 2005-2018, Cake Software Foundation, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/README.md b/README.md index 8f2ec37e..253a711a 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ You can install ElasticSearch into your project using following to your `composer.json` file: "require": { - "cakephp/elastic-search": "dev-master" + "cakephp/elastic-search": "^1.0" } And run `php composer.phar update` diff --git a/composer.json b/composer.json index 983e1697..31b23ed8 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "ruflin/elastica": "~3.1" }, "require-dev": { - "cakephp/cakephp-codesniffer": "dev-master", + "cakephp/cakephp-codesniffer": "~2.1", "psr/log": "~1.0", "phpunit/phpunit": "^5.7|^6.0", "cakephp/cakephp": "~3.4.0" diff --git a/src/Datasource/Connection.php b/src/Datasource/Connection.php index 81ba1542..d7223d3a 100644 --- a/src/Datasource/Connection.php +++ b/src/Datasource/Connection.php @@ -43,7 +43,7 @@ class Connection extends Client implements ConnectionInterface * is `_all` * * @param array $config config options - * @param callback $callback Callback function which can be used to be notified + * @param callable $callback Callback function which can be used to be notified * about errors (for example connection down) */ public function __construct(array $config = [], $callback = null) diff --git a/src/Type.php b/src/Type.php index 4e6e7832..abe47918 100644 --- a/src/Type.php +++ b/src/Type.php @@ -467,6 +467,78 @@ public function exists($conditions) return $query->count() > 0; } + /** + * Persists a list of entities based on the fields that are marked as dirty and + * returns the same entity after a successful save or false in case + * of any error. + * Triggers the `Model.beforeSave` and `Model.afterSave` events. + * ## Options + * - `checkRules` Defaults to true. Check deletion rules before deleting the record. + * + * @param array $entities An array of entities + * @param array $options An array of options to be used for the event + * @return bool + */ + public function saveMany($entities, $options = []) + { + $options += ['checkRules' => true]; + $options = new ArrayObject($options); + + $documents = []; + + foreach ($entities as $key => $entity) { + if (!$entity instanceof EntityInterface) { + throw new RuntimeException(sprintf( + 'Invalid items in the list. Found `%s` but expected `%s`', + is_object($entity) ? get_class($entity) : gettype($entity), + EntityInterface::class + )); + } + + $event = $this->dispatchEvent('Model.beforeSave', [ + 'entity' => $entity, + 'options' => $options + ]); + + if ($event->isStopped() || $entity->errors()) { + return false; + } + + $mode = $entity->isNew() ? RulesChecker::CREATE : RulesChecker::UPDATE; + if ($options['checkRules'] && !$this->checkRules($entity, $mode, $options)) { + return false; + } + + $id = $entity->id ?: null; + + $data = $entity->toArray(); + unset($data['id'], $data['_version']); + + $doc = new ElasticaDocument($id, $data); + $doc->setAutoPopulate(true); + + $documents[$key] = $doc; + } + + $type = $this->connection()->getIndex()->getType($this->name()); + $type->addDocuments($documents); + + foreach ($documents as $key => $document) { + $entities[$key]->id = $doc->getId(); + $entities[$key]->_version = $doc->getVersion(); + $entities[$key]->isNew(false); + $entities[$key]->source($this->name()); + $entities[$key]->clean(); + + $this->dispatchEvent('Model.afterSave', [ + 'entity' => $entities[$key], + 'options' => $options + ]); + } + + return true; + } + /** * Persists an entity based on the fields that are marked as dirty and * returns the same entity after a successful save or false in case diff --git a/tests/TestCase/TypeTest.php b/tests/TestCase/TypeTest.php index efc41c4e..c8563a6c 100644 --- a/tests/TestCase/TypeTest.php +++ b/tests/TestCase/TypeTest.php @@ -223,6 +223,32 @@ public function testNewEntities() $this->assertSame($data[1], $result[1]->toArray()); } + /** + * Test saving many entities + * + * @return void + */ + public function testSaveMany() + { + $entities = [ + new Document([ + 'title' => 'First', + 'body' => 'Some new content' + ], [ + 'markNew' => true + ]), + new Document([ + 'title' => 'Second', + 'body' => 'Some new content' + ], [ + 'markNew' => true + ]) + ]; + + $result = $this->type->saveMany($entities); + $this->assertTrue($result); + } + /** * Test saving a new document. * diff --git a/tests/init.php b/tests/init.php index 13a6718a..be465626 100644 --- a/tests/init.php +++ b/tests/init.php @@ -14,6 +14,10 @@ */ require dirname(__DIR__) . '/vendor/autoload.php'; +define('CAKE', dirname(__DIR__) . '/vendor/cakephp/cakephp/src/'); + +require CAKE . 'basics.php'; + define('APP', __DIR__); use Cake\Cache\Cache;