From 79abdb5060d7491cdfa5deea7728cc58f75b689a Mon Sep 17 00:00:00 2001 From: Danil Lytvyn Date: Fri, 3 Feb 2023 17:14:46 +0200 Subject: [PATCH] DP-460 Upgrade to PHP 8.1 / Laravel 9 - Update requires and replace array_get method - Refactor blob streaming in order to support file caching added in df-file v0.8 (see RemoteFileSystem::streamBlobIfModified). Vendor details implemented in GridFsSystem::getBlobInChunks --- composer.json | 5 +- src/Components/GridFsSystem.php | 93 ++++++--------------------------- src/Database/Schema/Schema.php | 5 +- src/Models/MongoDbConfig.php | 7 +-- src/Resources/Table.php | 57 ++++++++++---------- src/Services/MongoDb.php | 23 ++++---- 6 files changed, 68 insertions(+), 122 deletions(-) diff --git a/composer.json b/composer.json index 67aa52b..4ffd65e 100644 --- a/composer.json +++ b/composer.json @@ -31,8 +31,9 @@ "minimum-stability": "dev", "prefer-stable": true, "require": { - "dreamfactory/df-database": "~0.11", - "jenssegers/mongodb": "~3.3.0|~3.4.0|~3.5.0|~3.6.0" + "dreamfactory/df-database": "~1.0", + "dreamfactory/df-file": "~0.8", + "jenssegers/mongodb": "~3.9.2" }, "autoload": { "psr-4": { diff --git a/src/Components/GridFsSystem.php b/src/Components/GridFsSystem.php index 722c2c0..22eb58e 100644 --- a/src/Components/GridFsSystem.php +++ b/src/Components/GridFsSystem.php @@ -12,6 +12,7 @@ use MongoDB\Client as MongoDBClient; use Illuminate\Http\Request; use MongoDB\Driver\Exception\ConnectionTimeoutException; +use \Illuminate\Support\Arr; class GridFsSystem extends RemoteFileSystem { @@ -45,7 +46,7 @@ public function __construct($config, $name) $this->request = Request::capture(); $config['driver'] = 'mongodb'; - if (!empty($dsn = strval(array_get($config, 'dsn')))) { + if (!empty($dsn = strval(Arr::get($config, 'dsn')))) { // add prefix if not there // NOTE: We may want to change this to match the code in MondoDB.php as a regex check // if this breaks for calling Atlas / replica sets with the +srv option @@ -56,13 +57,13 @@ public function __construct($config, $name) } // laravel database config requires options to be [], not null - if (empty($options = array_get($config, 'options', []))) { + if (empty($options = Arr::get($config, 'options', []))) { $config['options'] = []; } - if (empty($db = array_get($config, 'database'))) { - if (!empty($db = array_get($config, 'options.db'))) { + if (empty($db = Arr::get($config, 'database'))) { + if (!empty($db = Arr::get($config, 'options.db'))) { $config['database'] = $db; - } elseif (!empty($db = array_get($config, 'options.database'))) { + } elseif (!empty($db = Arr::get($config, 'options.database'))) { $config['database'] = $db; } else { // Attempt to find db in connection string @@ -81,18 +82,18 @@ public function __construct($config, $name) throw new InternalServerErrorException("No MongoDb database selected in configuration."); } - $driverOptions = (array)array_get($config, 'driver_options'); - if (null !== $context = array_get($driverOptions, 'context')) { + $driverOptions = (array)Arr::get($config, 'driver_options'); + if (null !== $context = Arr::get($driverOptions, 'context')) { // Automatically creates a stream from context $config['driver_options']['context'] = stream_context_create($context); } - if (empty($prefix = array_get($config, 'dsn'))) { + if (empty($prefix = Arr::get($config, 'dsn'))) { $connectionOptions = []; - $host = array_get($config, 'host'); - $port = array_get($config, 'port'); - $username = array_get($config, 'username'); - $password = array_get($config, 'password'); + $host = Arr::get($config, 'host'); + $port = Arr::get($config, 'port'); + $username = Arr::get($config, 'username'); + $password = Arr::get($config, 'password'); $connectionStr = sprintf("mongodb://%s:%s", $host, $port, $db); @@ -111,7 +112,7 @@ public function __construct($config, $name) $this->blobConn = $this->createConnection($dsn); } - $bucketName = array_get($config, 'bucket_name'); + $bucketName = Arr::get($config, 'bucket_name'); if (!empty($bucketName)) { $this->gridFS = $this->blobConn->$db->selectGridFSBucket(['bucketName' => $bucketName]); @@ -222,7 +223,7 @@ protected function getBlobMeta($obj) $return = [ 'oid' => (string)$obj->_id, 'name' => $obj->filename, - 'content_type' => (isset($obj->contentType)) ? $obj->contentType : '', + 'content_type' => $obj->contentType ?? '', 'content_length' => $obj->length, 'last_modified' => $date->format(\DateTime::ATOM), 'path' => $obj->filename, @@ -360,75 +361,15 @@ public function getBlobProperties($container, $name) return $this->getBlobMeta($obj); } - /** - * - * @param string $container - * @param string $name - * @param array $params - * - * @throws \DreamFactory\Core\Exceptions\DfException - */ - public function streamBlob($container, $name, $params = []) + protected function getBlobInChunks($container, $name, $chunkSize): \Generator { try { $fileObj = $this->gridFindOne($name); - $range = (isset($params['range'])) ? $params['range'] : null; - $start = $end = -1; $stream = $this->gridFS->openDownloadStream($fileObj->_id); - $date = $fileObj->uploadDate->toDateTime(); - $size = $fullsize = intval($fileObj->length); - - header('Last-Modified: ' . $date->format(\DateTime::ATOM)); - - /** If content type is not set, try to determine it. */ - if (!isset($fileObj->contentType)) { - $ext = FileUtilities::getFileExtension($name); - $contentType = FileUtilities::determineContentType($ext); - header('Content-Type: ' . $contentType); - } else { - header('Content-Type: ' . $fileObj->contentType); - } - - $disposition = - (isset($params['disposition']) && !empty($params['disposition'])) ? $params['disposition'] - : 'inline'; - - header('Content-Disposition: ' . $disposition . '; filename="' . $name . '";'); - - // All this for range header being passed. - if ($range != null) { - $eqPos = strpos($range, "="); - $toPos = strpos($range, "-"); - $unit = substr($range, 0, $eqPos); - $start = intval(substr($range, $eqPos + 1, $toPos)); - $end = intval(substr($range, $toPos + 1)); - $success = fseek($stream, $start); - if ($success == 0) { - $size = $end - $start; - // Don't let the passed size exceed the actual. - if ($fullsize <= $size) { - $size = $fullsize; - } - $response_code = 206; - header('Accept-Ranges: ' . $unit); - header('Content-Range: ' . $unit . " " . $start . "-" . ($fullsize - 1) . "/" . $fullsize, true, - $response_code); - } - } - - header('Content-Length:' . $size); while (!feof($stream)) { - if ($start = -1 && $end = -1) { - /** if entire file is requested... */ - echo stream_get_contents($stream, \Config::get('df.file_chunk_size')); - } else { - /** if Bit of file, in chunks */ - echo stream_get_contents($stream, $end, $start); - } + yield stream_get_contents($stream, $chunkSize); } - } catch (ConnectionTimeoutException $ex) { - throw new ConnectionTimeoutException($ex->getMessage()); } catch (\Exception $ex) { throw new DfException('Failed to retrieve GridFS file "' . $name . '": ' . $ex->getMessage()); } diff --git a/src/Database/Schema/Schema.php b/src/Database/Schema/Schema.php index 1310918..5b4c220 100644 --- a/src/Database/Schema/Schema.php +++ b/src/Database/Schema/Schema.php @@ -5,6 +5,7 @@ use DreamFactory\Core\Database\Schema\TableSchema; use Jenssegers\Mongodb\Connection; use MongoDB\Model\CollectionInfo; +use \Illuminate\Support\Arr; /** * Schema is the class for retrieving metadata information from a MongoDB database (version 4.1.x and 5.x). @@ -58,12 +59,12 @@ protected function getTableNames($schema = '') */ public function createTable($table, $options) { - if (empty($tableName = array_get($table, 'name'))) { + if (empty($tableName = Arr::get($table, 'name'))) { throw new \Exception("No valid name exist in the received table schema."); } $options = []; - if (!empty($native = array_get($table, 'native'))) { + if (!empty($native = Arr::get($table, 'native'))) { } return $this->connection->getMongoDB()->createCollection($tableName, $options); diff --git a/src/Models/MongoDbConfig.php b/src/Models/MongoDbConfig.php index 361fdb6..ab43d38 100644 --- a/src/Models/MongoDbConfig.php +++ b/src/Models/MongoDbConfig.php @@ -6,6 +6,7 @@ use DreamFactory\Core\Exceptions\BadRequestException; use DreamFactory\Core\Models\BaseServiceConfigModel; use DreamFactory\Core\MongoDb\Services\MongoDb; +use \Illuminate\Support\Arr; /** * MongoDbConfig @@ -57,11 +58,11 @@ public function validate($data, $throwException = true) { static::checkExtensions(['mongodb']); - if (empty(array_get($data, 'database')) && empty(array_get($data, 'options.db')) && - empty(array_get($data, 'options.database')) + if (empty(Arr::get($data, 'database')) && empty(Arr::get($data, 'options.db')) && + empty(Arr::get($data, 'options.database')) ) { // Attempt to find db in connection string - $dsn = strval(array_get($data, 'dsn')); + $dsn = strval(Arr::get($data, 'dsn')); $db = strstr(preg_replace('/mongodb(\+srv)?\:\/\//', '', $dsn), '/'); if (false !== $pos = strpos($db, '?')) { $db = substr($db, 0, $pos); diff --git a/src/Resources/Table.php b/src/Resources/Table.php index a92b47e..069629b 100644 --- a/src/Resources/Table.php +++ b/src/Resources/Table.php @@ -25,6 +25,7 @@ use MongoDB\Model\BSONDocument; use MongoDB\Operation\FindOneAndReplace; use MongoDB\Operation\FindOneAndUpdate; +use \Illuminate\Support\Arr; class Table extends BaseNoSqlDbTableResource { @@ -89,7 +90,7 @@ public function updateRecordsByFilter($table, $record, $filter = null, $params = $record = static::validateAsArray($record, null, false, 'There are no fields in the record.'); $coll = $this->selectTable($table); - $ssFilters = array_get($extras, 'ss_filters'); + $ssFilters = Arr::get($extras, 'ss_filters'); static::removeIds($record, static::DEFAULT_ID_FIELD); $fieldsInfo = $this->getFieldsInfo($table); @@ -121,7 +122,7 @@ public function patchRecordsByFilter($table, $record, $filter = null, $params = $record = static::validateAsArray($record, null, false, 'There are no fields in the record.'); $coll = $this->selectTable($table); - $ssFilters = array_get($extras, 'ss_filters'); + $ssFilters = Arr::get($extras, 'ss_filters'); static::removeIds($record, static::DEFAULT_ID_FIELD); $fieldsInfo = $this->getFieldsInfo($table); @@ -162,7 +163,7 @@ public function truncateTable($table, $extras = []) $coll = $this->selectTable($table); try { // build filter string if necessary, add server-side filters if necessary - $ssFilters = array_get($extras, 'ss_filters'); + $ssFilters = Arr::get($extras, 'ss_filters'); $criteria = $this->buildCriteriaArray([], null, $ssFilters); $coll->deleteMany($criteria); @@ -185,7 +186,7 @@ public function deleteRecordsByFilter($table, $filter, $params = [], $extras = [ $coll = $this->selectTable($table); - $ssFilters = array_get($extras, 'ss_filters'); + $ssFilters = Arr::get($extras, 'ss_filters'); // build criteria from filter parameters $criteria = static::buildCriteriaArray($filter, $params, $ssFilters); @@ -205,7 +206,7 @@ public function deleteRecordsByFilter($table, $filter, $params = [], $extras = [ */ public function retrieveRecordsByFilter($table, $filter = null, $params = [], $extras = []) { - $ssFilters = array_get($extras, 'ss_filters'); + $ssFilters = Arr::get($extras, 'ss_filters'); $criteria = static::buildCriteriaArray($filter, $params, $ssFilters); try { @@ -223,7 +224,7 @@ protected function getIdsInfo($table, $fields_info = null, &$requested_fields = $requested_fields = static::DEFAULT_ID_FIELD; // can only be this $requested_types = (empty($requested_types) ? [] : (!is_array($requested_types) ? [$requested_types] : $requested_types)); - $type = array_get($requested_types, 0, 'string'); + $type = Arr::get($requested_types, 0, 'string'); $type = (empty($type)) ? 'string' : $type; return [new ColumnSchema(['name' => static::DEFAULT_ID_FIELD, 'type' => $type, 'required' => false])]; @@ -546,22 +547,22 @@ protected static function buildSSFilterArray($ss_filters) } // build the server side criteria - $filters = array_get($ss_filters, 'filters'); + $filters = Arr::get($ss_filters, 'filters'); if (empty($filters)) { return null; } $criteria = []; - $combiner = array_get($ss_filters, 'filter_op', 'and'); + $combiner = Arr::get($ss_filters, 'filter_op', 'and'); foreach ($filters as $filter) { - $name = array_get($filter, 'name'); - $op = array_get($filter, 'operator'); + $name = Arr::get($filter, 'name'); + $op = Arr::get($filter, 'operator'); if (empty($name) || empty($op)) { // log and bail throw new InternalServerErrorException('Invalid server-side filter configuration detected.'); } - $value = array_get($filter, 'value'); + $value = Arr::get($filter, 'value'); $value = static::interpretFilterValue($value); $criteria[] = static::buildFilterArray("$name $op $value"); @@ -767,9 +768,9 @@ protected static function idToMongoId($value) { if (is_array($value)) { if (array_key_exists('$id', $value)) { - $value = array_get($value, '$id'); + $value = Arr::get($value, '$id'); } elseif (array_key_exists('$oid', $value)) { - $value = array_get($value, '$oid'); + $value = Arr::get($value, '$oid'); } } @@ -883,9 +884,9 @@ protected function addToTransaction( $continue = false, $single = false ) { - $ssFilters = array_get($extras, 'ss_filters'); - $fields = array_get($extras, ApiOptions::FIELDS); - $related = array_get($extras, 'related'); + $ssFilters = Arr::get($extras, 'ss_filters'); + $fields = Arr::get($extras, ApiOptions::FIELDS); + $related = Arr::get($extras, 'related'); $requireMore = array_get_bool($extras, 'require_more') || !empty($related); $allowRelatedDelete = array_get_bool($extras, 'allow_related_delete'); $relatedInfo = $this->describeTableRelated($this->transactionTable); @@ -934,7 +935,7 @@ protected function addToTransaction( if (!empty($relatedInfo)) { $this->updatePreRelations($record, $relatedInfo); } - if (!empty($updates = array_get($extras, 'updates'))) { + if (!empty($updates = Arr::get($extras, 'updates'))) { $parsed = $this->parseRecord($updates, $this->tableFieldsInfo, $ssFilters, true); $updates = $parsed; } else { @@ -1002,7 +1003,7 @@ protected function addToTransaction( if (!empty($relatedInfo)) { $this->updatePreRelations($record, $relatedInfo); } - if (!empty($updates = array_get($extras, 'updates'))) { + if (!empty($updates = Arr::get($extras, 'updates'))) { $parsed = $this->parseRecord($updates, $this->tableFieldsInfo, $ssFilters, true); $updates = $parsed; } else { @@ -1124,9 +1125,9 @@ protected function commitTransaction($extras = null) return null; } - $updates = array_get($extras, 'updates'); - $ssFilters = array_get($extras, 'ss_filters'); - $requireMore = array_get($extras, 'require_more'); + $updates = Arr::get($extras, 'updates'); + $ssFilters = Arr::get($extras, 'ss_filters'); + $requireMore = Arr::get($extras, 'require_more'); $out = []; switch ($this->getAction()) { @@ -1263,7 +1264,7 @@ protected function rollbackTransaction() protected function findByIds($ids, $extras) { $filter = [static::DEFAULT_ID_FIELD => ['$in' => $ids]]; - $ssFilters = array_get($extras, 'ss_filters'); + $ssFilters = Arr::get($extras, 'ss_filters'); $criteria = static::buildCriteriaArray($filter, null, $ssFilters); $result = $this->runQuery($this->collection->getCollectionName(), $criteria, $extras); if (empty($result)) { @@ -1276,7 +1277,7 @@ protected function findByIds($ids, $extras) foreach ($this->batchIds as $index => $id) { $found = false; foreach ($result as $record) { - if ($id == array_get($record, static::DEFAULT_ID_FIELD)) { + if ($id == Arr::get($record, static::DEFAULT_ID_FIELD)) { $out[$index] = $record; $found = true; continue; @@ -1307,8 +1308,8 @@ protected function runQuery($table, $criteria, $extras) throw new NotFoundException("Table '$table' does not exist in the database."); } - $fields = array_get($extras, ApiOptions::FIELDS); - $related = array_get($extras, ApiOptions::RELATED); + $fields = Arr::get($extras, ApiOptions::FIELDS); + $related = Arr::get($extras, ApiOptions::RELATED); /** @type RelationSchema[] $availableRelations */ $availableRelations = $schema->getRelations(true); // see if we need to add anymore fields to select for related retrieval @@ -1322,9 +1323,9 @@ protected function runQuery($table, $criteria, $extras) } } - $limit = intval(array_get($extras, ApiOptions::LIMIT, 0)); - $offset = intval(array_get($extras, ApiOptions::OFFSET, 0)); - $sort = static::buildSortArray(array_get($extras, ApiOptions::ORDER)); + $limit = intval(Arr::get($extras, ApiOptions::LIMIT, 0)); + $offset = intval(Arr::get($extras, ApiOptions::OFFSET, 0)); + $sort = static::buildSortArray(Arr::get($extras, ApiOptions::ORDER)); $countOnly = array_get_bool($extras, ApiOptions::COUNT_ONLY); $includeCount = array_get_bool($extras, ApiOptions::INCLUDE_COUNT); $maxAllowed = $this->getMaxRecordsReturnedLimit(); diff --git a/src/Services/MongoDb.php b/src/Services/MongoDb.php index 60e29cd..97cf3f2 100644 --- a/src/Services/MongoDb.php +++ b/src/Services/MongoDb.php @@ -9,6 +9,7 @@ use DreamFactory\Core\MongoDb\Resources\Table; use Illuminate\Database\DatabaseManager; use Jenssegers\Mongodb\Connection; +use \Illuminate\Support\Arr; /** * MongoDb @@ -59,7 +60,7 @@ public function __construct($settings = []) parent::__construct($settings); $this->config['driver'] = 'mongodb'; - if (!empty($dsn = strval(array_get($this->config, 'dsn')))) { + if (!empty($dsn = strval(Arr::get($this->config, 'dsn')))) { // add prefix if not there if (!preg_match('/mongodb(\+srv)?\:\/\//', $dsn)) { $dsn = static::DSN_PREFIX . $dsn; @@ -68,13 +69,13 @@ public function __construct($settings = []) } // laravel database config requires options to be [], not null - if (empty($options = array_get($this->config, 'options', []))) { + if (empty($options = Arr::get($this->config, 'options', []))) { $this->config['options'] = []; } - if (empty($db = array_get($this->config, 'database'))) { - if (!empty($db = array_get($this->config, 'options.db'))) { + if (empty($db = Arr::get($this->config, 'database'))) { + if (!empty($db = Arr::get($this->config, 'options.db'))) { $this->config['database'] = $db; - } elseif (!empty($db = array_get($this->config, 'options.database'))) { + } elseif (!empty($db = Arr::get($this->config, 'options.database'))) { $this->config['database'] = $db; } else { // Attempt to find db in connection string @@ -91,16 +92,16 @@ public function __construct($settings = []) throw new InternalServerErrorException("No MongoDb database selected in configuration."); } - $driverOptions = (array)array_get($this->config, 'driver_options'); - if (null !== $context = array_get($driverOptions, 'context')) { + $driverOptions = (array)Arr::get($this->config, 'driver_options'); + if (null !== $context = Arr::get($driverOptions, 'context')) { // Automatically creates a stream from context $this->config['driver_options']['context'] = stream_context_create($context); } - if (empty($prefix = array_get($this->config, 'dsn'))) { - $host = array_get($this->config, 'host'); - $port = array_get($this->config, 'port'); - $username = array_get($this->config, 'username'); + if (empty($prefix = Arr::get($this->config, 'dsn'))) { + $host = Arr::get($this->config, 'host'); + $port = Arr::get($this->config, 'port'); + $username = Arr::get($this->config, 'username'); $prefix = $host . $port . $username . $db; } $this->setConfigBasedCachePrefix($prefix . ':');