From 66f1d2a20149337b03e24d3b3e82beef531c8e5a Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 14 Dec 2021 23:04:53 +0800 Subject: [PATCH 001/108] Add Larastan, initial code analysis tweaks --- composer.json | 6 +- phpstan.neon | 9 ++ src/Auth/Models/Group.php | 2 +- src/Auth/Models/Preferences.php | 21 ++-- src/Auth/Models/Role.php | 5 +- src/Auth/Models/User.php | 12 ++- src/Config/FileLoader.php | 10 +- src/Config/LoaderInterface.php | 9 ++ src/Cookie/CookieValuePrefix.php | 47 --------- src/Cookie/Middleware/EncryptCookies.php | 122 ----------------------- src/Database/Attach/Resizer.php | 30 ++++-- src/Database/Behaviors/Purgeable.php | 8 +- src/Database/Model.php | 2 +- src/Support/aliases.php | 1 - 14 files changed, 79 insertions(+), 205 deletions(-) create mode 100644 phpstan.neon delete mode 100644 src/Cookie/CookieValuePrefix.php diff --git a/composer.json b/composer.json index f579a07b..b2fdb808 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,7 @@ "linkorb/jsmin-php": "~1.0", "wikimedia/less.php": "~3.0", "scssphp/scssphp": "~1.0", - "symfony/yaml": "^5.1", + "symfony/yaml": "^6.0", "twig/twig": "~2.0", "league/csv": "~9.1", "nesbot/carbon": "^2.0", @@ -52,7 +52,9 @@ "squizlabs/php_codesniffer": "3.*", "php-parallel-lint/php-parallel-lint": "^1.0", "meyfa/phpunit-assert-gd": "^2.0.0|^3.0.0", - "dms/phpunit-arraysubset-asserts": "^0.1.0|^0.2.1" + "dms/phpunit-arraysubset-asserts": "^0.1.0|^0.2.1", + "nunomaduro/larastan": "^1.0", + "orchestra/testbench": "dev-master" }, "suggest": { "ext-pdo_dblib": "Required to use MS SQL Server databases", diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..bb605b85 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,9 @@ +includes: + - ./vendor/nunomaduro/larastan/extension.neon + +parameters: + paths: + - src + level: 5 + excludePaths: + - src/Assetic/* diff --git a/src/Auth/Models/Group.php b/src/Auth/Models/Group.php index 21d5e06c..4d4b9a8b 100644 --- a/src/Auth/Models/Group.php +++ b/src/Auth/Models/Group.php @@ -29,7 +29,7 @@ class Group extends Model ]; /** - * @var array The attributes that aren't mass assignable. + * @var string[]|bool The attributes that aren't mass assignable. */ protected $guarded = []; diff --git a/src/Auth/Models/Preferences.php b/src/Auth/Models/Preferences.php index a2453778..cf65ada9 100644 --- a/src/Auth/Models/Preferences.php +++ b/src/Auth/Models/Preferences.php @@ -26,7 +26,7 @@ class Preferences extends Model protected $jsonable = ['value']; /** - * @var \Winter\Storm\Auth\Models\User A user who owns the preferences + * @var \Winter\Storm\Auth\Models\User|null A user who owns the preferences */ public $userContext; @@ -38,6 +38,7 @@ class Preferences extends Model public function resolveUser($user) { $user = Manager::instance()->getUser(); + if (!$user) { throw new AuthException('User is not logged in'); } @@ -63,7 +64,9 @@ public static function forUser($user = null) */ public function get($key, $default = null) { - if (!($user = $this->userContext)) { + $user = $this->userContext; + + if (!$user) { return $default; } @@ -74,6 +77,7 @@ public function get($key, $default = null) } $record = static::findRecord($key, $user); + if (!$record) { return static::$cache[$cacheKey] = $default; } @@ -91,11 +95,14 @@ public function get($key, $default = null) */ public function set($key, $value) { - if (!$user = $this->userContext) { + $user = $this->userContext; + + if (!$user) { return false; } $record = static::findRecord($key, $user); + if (!$record) { list($namespace, $group, $item) = $this->parseKey($key); $record = new static; @@ -120,11 +127,14 @@ public function set($key, $value) */ public function reset($key) { - if (!$user = $this->userContext) { + $user = $this->userContext; + + if (!$user) { return false; } $record = static::findRecord($key, $user); + if (!$record) { return false; } @@ -139,7 +149,7 @@ public function reset($key) /** * Returns a record - * @return self + * @return self|null */ public static function findRecord($key, $user = null) { @@ -149,7 +159,6 @@ public static function findRecord($key, $user = null) /** * Scope to find a setting record for the specified module (or plugin) name, setting name and user. * @param string $key Specifies the setting key value, for example 'backend:items.perpage' - * @param mixed $default The default value to return if the setting doesn't exist in the DB. * @param mixed $user An optional user object. * @return mixed Returns the found record or null. */ diff --git a/src/Auth/Models/Role.php b/src/Auth/Models/Role.php index 17fd36b7..a9ebb7f3 100644 --- a/src/Auth/Models/Role.php +++ b/src/Auth/Models/Role.php @@ -44,7 +44,7 @@ class Role extends Model protected $allowedPermissionsValues = [0, 1]; /** - * @var array The attributes that aren't mass assignable. + * @var string[]|bool The attributes that aren't mass assignable. */ protected $guarded = []; @@ -161,12 +161,13 @@ public function hasAnyAccess(array $permissions) /** * Validate the permissions when set. - * @param array $permissions + * @param string $permissions * @return void */ public function setPermissionsAttribute($permissions) { $permissions = json_decode($permissions, true); + foreach ($permissions as $permission => $value) { if (!in_array($value = (int) $value, $this->allowedPermissionsValues)) { throw new InvalidArgumentException(sprintf( diff --git a/src/Auth/Models/User.php b/src/Auth/Models/User.php index 48aa1ba9..6b4594ac 100644 --- a/src/Auth/Models/User.php +++ b/src/Auth/Models/User.php @@ -53,7 +53,7 @@ class User extends Model implements \Illuminate\Contracts\Auth\Authenticatable protected $hidden = ['password', 'reset_password_code', 'activation_code', 'persist_code']; /** - * @var array The attributes that aren't mass assignable. + * @var string[]|bool The attributes that aren't mass assignable. */ protected $guarded = ['is_superuser', 'reset_password_code', 'activation_code', 'persist_code', 'role_id']; @@ -150,7 +150,7 @@ public function afterLogin() /** * Delete the user groups - * @return bool + * @return void */ public function afterDelete() { @@ -341,7 +341,7 @@ public function getGroups() /** * Returns the role assigned to this user. - * @return Winter\Storm\Auth\Models\Role + * @return \Winter\Storm\Auth\Models\Role|null */ public function getRole() { @@ -404,8 +404,9 @@ public function getMergedPermissions() { if (!$this->mergedPermissions) { $permissions = []; + $role = $this->getRole(); - if (($role = $this->getRole()) && is_array($role->permissions)) { + if ($role && is_array($role->permissions)) { $permissions = array_merge($permissions, $role->permissions); } @@ -558,12 +559,13 @@ public function hasAnyAccess(array $permissions) /** * Validate any set permissions. - * @param array $permissions + * @param string $permissions * @return void */ public function setPermissionsAttribute($permissions) { $permissions = json_decode($permissions, true) ?: []; + foreach ($permissions as $permission => &$value) { if (!in_array($value = (int) $value, $this->allowedPermissionsValues)) { throw new InvalidArgumentException(sprintf( diff --git a/src/Config/FileLoader.php b/src/Config/FileLoader.php index f1395c9c..5bf7bc2f 100644 --- a/src/Config/FileLoader.php +++ b/src/Config/FileLoader.php @@ -215,9 +215,7 @@ protected function getPackagePath($package, $group, $env = null) */ protected function getPath($namespace) { - if (is_null($namespace)) { - return $this->defaultPath; - } elseif (isset($this->hints[$namespace])) { + if (isset($this->hints[$namespace])) { return $this->hints[$namespace]; } @@ -237,10 +235,10 @@ public function addNamespace($namespace, $hint) } /** - * Add a new namespace to the loader. + * Registers an alias for a given namespace. * - * @param string $namespace - * @param string $alias + * @param string $namespace + * @param string $alias * @return void */ public function registerNamespaceAlias(string $namespace, string $alias) diff --git a/src/Config/LoaderInterface.php b/src/Config/LoaderInterface.php index 7345546f..7b5f4502 100644 --- a/src/Config/LoaderInterface.php +++ b/src/Config/LoaderInterface.php @@ -31,6 +31,15 @@ public function exists($group, $namespace = null); */ public function addNamespace($namespace, $hint); + /** + * Registers an alias for a given namespace. + * + * @param string $namespace + * @param string $alias + * @return void + */ + public function registerNamespaceAlias(string $namespace, string $alias); + /** * Returns all registered namespaces with the config * loader. diff --git a/src/Cookie/CookieValuePrefix.php b/src/Cookie/CookieValuePrefix.php deleted file mode 100644 index b2d10679..00000000 --- a/src/Cookie/CookieValuePrefix.php +++ /dev/null @@ -1,47 +0,0 @@ -disableFor($except); } - - /** - * Shift gracefully to unserialized cookies - * @todo Remove entire method if year >= 2021 or build >= 475 - */ - protected function decryptCookie($name, $cookie) - { - if (is_array($cookie)) { - return $this->decryptArray($cookie); - } - - try { - $result = $this->encrypter->decrypt($cookie, true); - if (!is_string($result)) { - $result = json_encode($result); - } - } - catch (\Exception $ex) { - $result = $this->encrypter->decrypt($cookie, false); - } - - return $result; - } - - /** - * Shift gracefully to unserialized cookies - * @todo Remove entire method if year >= 2021 or build >= 475 - */ - protected function decryptArray(array $cookie) - { - $decrypted = []; - - foreach ($cookie as $key => $value) { - if (is_string($value)) { - try { - $result = $this->encrypter->decrypt($value, true); - if (!is_string($result)) { - $result = json_encode($result); - } - $decrypted[$key] = $result; - } - catch (\Exception $ex) { - $decrypted[$key] = $this->encrypter->decrypt($value, false); - } - } - } - - return $decrypted; - } - - /** - * Decrypt the cookies on the request. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * @return \Symfony\Component\HttpFoundation\Request - */ - protected function decrypt(Request $request) - { - foreach ($request->cookies as $key => $cookie) { - if ($this->isDisabled($key)) { - continue; - } - - try { - // Decrypt the request-provided cookie - $decryptedValue = $this->decryptCookie($key, $cookie); - - // Verify that the decrypted value belongs to this cookie key, use null if it fails - $value = CookieValuePrefix::getVerifiedValue($key, $decryptedValue, $this->encrypter->getKey()); - - /** - * If the cookie is for the session and the value is a valid Session ID, - * then allow it to pass through even if the validation failed (most likely - * because the upgrade just occurred) - * - * The cookie will be adjusted on the next request - * @todo Remove if year >= 2021 or build >= 475 - */ - if (empty($value) && $key === Config::get('session.cookie') && Session::isValidId($decryptedValue)) { - $value = $decryptedValue; - } - - // Set the verified cookie value on the request - $request->cookies->set($key, $value); - } catch (DecryptException $e) { - $request->cookies->set($key, null); - } - } - - return $request; - } - - /** - * Encrypt the cookies on an outgoing response. - * - * @param \Symfony\Component\HttpFoundation\Response $response - * @return \Symfony\Component\HttpFoundation\Response - */ - protected function encrypt(Response $response) - { - foreach ($response->headers->getCookies() as $cookie) { - if ($this->isDisabled($cookie->getName())) { - continue; - } - - $response->headers->setCookie($this->duplicate( - $cookie, - $this->encrypter->encrypt( - // Prefix the cookie value to verify that it belongs to the current cookie - CookieValuePrefix::create($cookie->getName(), $this->encrypter->getKey()) . $cookie->getValue(), - static::serialized($cookie->getName()) - ) - )); - } - - return $response; - } } diff --git a/src/Database/Attach/Resizer.php b/src/Database/Attach/Resizer.php index 777d4b97..f0f19949 100644 --- a/src/Database/Attach/Resizer.php +++ b/src/Database/Attach/Resizer.php @@ -1,7 +1,8 @@ retainImageTransparency($img); break; default: - throw new Exception(sprintf('Invalid mime type: %s. Accepted types: image/jpeg, image/gif, image/png, image/webp.', $this->mime)); - break; + throw new Exception( + sprintf( + 'Invalid mime type: %s. Accepted types: image/jpeg, image/gif, image/png, image/webp.', + $this->mime + ) + ); } return $img; diff --git a/src/Database/Behaviors/Purgeable.php b/src/Database/Behaviors/Purgeable.php index cd37d787..d5584baf 100644 --- a/src/Database/Behaviors/Purgeable.php +++ b/src/Database/Behaviors/Purgeable.php @@ -4,10 +4,14 @@ class Purgeable extends \Winter\Storm\Extension\ExtensionBase { /** * @var array List of attribute names which should not be saved to the database. - * - * public $purgeable = []; */ + public $purgeable = []; + /** + * Model to purge. + * + * @var \Winter\Storm\Database\Model + */ protected $model; public function __construct($parent) diff --git a/src/Database/Model.php b/src/Database/Model.php index 2add8b61..8e7330b0 100644 --- a/src/Database/Model.php +++ b/src/Database/Model.php @@ -613,7 +613,7 @@ public function fromDateTime($value) * Create a new Eloquent query builder for the model. * * @param \Winter\Storm\Database\QueryBuilder $query - * @return \Winter\Storm\Database\Builder|static + * @return \Winter\Storm\Database\Builder */ public function newEloquentBuilder($query) { diff --git a/src/Support/aliases.php b/src/Support/aliases.php index e23a05d3..24a92d3b 100644 --- a/src/Support/aliases.php +++ b/src/Support/aliases.php @@ -100,7 +100,6 @@ class_alias(\Winter\Storm\Config\Repository::class, \October\Rain\Config\Reposit /** * Alias October\Rain\Cookie */ -class_alias(\Winter\Storm\Cookie\CookieValuePrefix::class, \October\Rain\Cookie\CookieValuePrefix::class); class_alias(\Winter\Storm\Cookie\Middleware\EncryptCookies::class, \October\Rain\Cookie\Middleware\EncryptCookies::class); /** From 0226ffe12ce6c1519df0664ca01e70c7cb6cd216 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 17 Dec 2021 13:58:25 +0800 Subject: [PATCH 002/108] Fix code blocks and signatures in Halcyon --- src/Halcyon/Builder.php | 42 +++++++------- src/Halcyon/Datasource/Datasource.php | 57 +++++++++++++++---- .../Datasource/DatasourceInterface.php | 8 +-- src/Halcyon/Datasource/DbDatasource.php | 35 +++++++----- src/Halcyon/Datasource/FileDatasource.php | 33 ++++++----- .../Exception/CreateDirectoryException.php | 2 +- src/Halcyon/Exception/CreateFileException.php | 2 +- src/Halcyon/Exception/DeleteFileException.php | 2 +- src/Halcyon/Exception/FileExistsException.php | 2 +- .../Exception/InvalidExtensionException.php | 2 +- src/Halcyon/MemoryRepository.php | 14 ++--- src/Halcyon/Model.php | 7 ++- src/Halcyon/Processors/Processor.php | 10 ++-- src/Halcyon/Processors/SectionParser.php | 28 ++++----- src/Support/Facades/DB.php | 50 ++++++++++++++++ 15 files changed, 198 insertions(+), 96 deletions(-) create mode 100644 src/Support/Facades/DB.php diff --git a/src/Halcyon/Builder.php b/src/Halcyon/Builder.php index 24bd8502..47c5f24a 100644 --- a/src/Halcyon/Builder.php +++ b/src/Halcyon/Builder.php @@ -6,7 +6,7 @@ use Winter\Storm\Halcyon\Exception\InvalidFileNameException; use Winter\Storm\Halcyon\Exception\InvalidExtensionException; use BadMethodCallException; -use ApplicationException; +use Winter\Storm\Exception\ApplicationException; /** * Query builder @@ -39,84 +39,84 @@ class Builder /** * The columns that should be returned. * - * @var array + * @var array|null */ public $columns; /** * Filter the query by these file extensions. * - * @var array + * @var array|null */ public $extensions; /** * The directory name which the query is targeting. * - * @var string + * @var string|null */ public $from; /** * Query should pluck a single record. * - * @var bool + * @var array|null */ public $selectSingle; /** * Match files using the specified pattern. * - * @var string + * @var string|null */ public $fileMatch; /** * The orderings for the query. * - * @var array + * @var array|null */ public $orders; /** * The maximum number of records to return. * - * @var int + * @var int|null */ public $limit; /** * The number of records to skip. * - * @var int + * @var int|null */ public $offset; /** * The key that should be used when caching the query. * - * @var string + * @var string|null */ protected $cacheKey; /** * The number of minutes to cache the query. * - * @var int + * @var int|null */ protected $cacheMinutes; /** * The tags for the query cache. * - * @var array + * @var array|null */ protected $cacheTags; /** * The cache driver to be used. * - * @var string + * @var string|null */ protected $cacheDriver; @@ -335,12 +335,12 @@ public function get($columns = ['*']) * Insert a new record into the datasource. * * @param array $values - * @return bool + * @return int */ public function insert(array $values) { if (empty($values)) { - return true; + throw new ApplicationException('You must provide values to insert'); } $this->validateFileName(); @@ -392,7 +392,7 @@ public function update(array $values) /** * Delete a record from the database. * - * @return int + * @return bool */ public function delete() { @@ -528,7 +528,7 @@ protected function validateFileNameExtension($fileName, $allowedExtensions) * Template directory and file names can contain only alphanumeric symbols, dashes and dots. * @param string $filePath Specifies a path to validate * @param integer $maxNesting Specifies the maximum allowed nesting level - * @return void + * @return bool */ protected function validateFileNamePath($filePath, $maxNesting = 2) { @@ -697,7 +697,7 @@ protected function isCacheBusted($result) /** * Get the cache object with tags assigned, if applicable. * - * @return \Illuminate\Cache\CacheManager + * @return \Illuminate\Contracts\Cache\Repository */ protected function getCache() { @@ -737,7 +737,7 @@ public function generateCacheKey() /** * Get the Closure callback used when caching queries. * - * @param string $fileName + * @param string|array $columns * @return \Closure */ protected function getCacheCallback($columns) @@ -749,8 +749,8 @@ protected function getCacheCallback($columns) /** * Initialize the cache data of each record. - * @param array $data - * @return array + * @param \Winter\Storm\Halcyon\Collection|array $data + * @return \Winter\Storm\Halcyon\Collection|array */ protected function processInitCacheData($data) { diff --git a/src/Halcyon/Datasource/Datasource.php b/src/Halcyon/Datasource/Datasource.php index 9f232397..d313f9a6 100644 --- a/src/Halcyon/Datasource/Datasource.php +++ b/src/Halcyon/Datasource/Datasource.php @@ -3,7 +3,7 @@ /** * Datasource base class. */ -class Datasource +abstract class Datasource implements DatasourceInterface { use \Winter\Storm\Support\Traits\Emitter; @@ -30,27 +30,64 @@ public function getPostProcessor() } /** - * Force the deletion of a record against the datasource - * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @return void + * @inheritDoc + */ + abstract public function selectOne(string $dirName, string $fileName, string $extension); + + /** + * @inheritDoc + */ + abstract public function select(string $dirName, array $options = []); + + /** + * @inheritDoc + */ + abstract public function insert(string $dirName, string $fileName, string $extension, string $content); + + /** + * @inheritDoc + */ + abstract public function update(string $dirName, string $fileName, string $extension, string $content, $oldFileName = null, $oldExtension = null); + + /** + * @inheritDoc + */ + abstract public function delete(string $dirName, string $fileName, string $extension); + + /** + * @inheritDoc */ public function forceDelete(string $dirName, string $fileName, string $extension) { $this->forceDeleting = true; - $this->delete($dirName, $fileName, $extension); + $success = $this->delete($dirName, $fileName, $extension); $this->forceDeleting = false; + + return $success; } /** - * Generate a cache key unique to this datasource. + * @inheritDoc + */ + abstract public function lastModified(string $dirName, string $fileName, string $extension); + + /** + * @inheritDoc */ public function makeCacheKey($name = '') { - return crc32($name); + return (string) crc32($name); } + + /** + * @inheritDoc + */ + abstract public function getPathsCacheKey(); + + /** + * @inheritDoc + */ + abstract public function getAvailablePaths(); } diff --git a/src/Halcyon/Datasource/DatasourceInterface.php b/src/Halcyon/Datasource/DatasourceInterface.php index 1f328df0..e3dc60ec 100644 --- a/src/Halcyon/Datasource/DatasourceInterface.php +++ b/src/Halcyon/Datasource/DatasourceInterface.php @@ -28,8 +28,8 @@ public function select(string $dirName, array $options = []); * @param string $dirName * @param string $fileName * @param string $extension - * @param array $content - * @return bool + * @param string $content + * @return int */ public function insert(string $dirName, string $fileName, string $extension, string $content); @@ -39,7 +39,7 @@ public function insert(string $dirName, string $fileName, string $extension, str * @param string $dirName * @param string $fileName * @param string $extension - * @param array $content + * @param string $content * @param string $oldFileName Defaults to null * @param string $oldExtension Defaults to null * @return int @@ -72,7 +72,7 @@ public function forceDelete(string $dirName, string $fileName, string $extension * @param string $dirName * @param string $fileName * @param string $extension - * @return int + * @return int|null */ public function lastModified(string $dirName, string $fileName, string $extension); diff --git a/src/Halcyon/Datasource/DbDatasource.php b/src/Halcyon/Datasource/DbDatasource.php index e4bc633b..277fc281 100644 --- a/src/Halcyon/Datasource/DbDatasource.php +++ b/src/Halcyon/Datasource/DbDatasource.php @@ -1,12 +1,12 @@ table)->enableDuplicateCache(); + return DB::table($this->table)->enableDuplicateCache(); } /** * Get the QueryBuilder object * * @param bool $ignoreDeleted Flag to ignore deleted records, defaults to true - * @return QueryBuilder + * @return \Illuminate\Database\Query\Builder */ public function getQuery($ignoreDeleted = true) { @@ -146,16 +148,20 @@ public function select(string $dirName, array $options = []) $result = []; // Prepare query options - extract(array_merge([ + $queryOptions = array_merge([ 'columns' => null, // Only return specific columns (fileName, mtime, content) 'extensions' => null, // Match specified extensions 'fileMatch' => null, // Match the file name using fnmatch() 'orders' => null, // @todo 'limit' => null, // @todo 'offset' => null // @todo - ], $options)); + ], $options); + extract($queryOptions); - if ($columns === ['*'] || !is_array($columns)) { + if ( + isset($columns) + && ($columns === ['*'] || !is_array($columns)) + ) { $columns = null; } @@ -163,7 +169,7 @@ public function select(string $dirName, array $options = []) $query = $this->getQuery()->where('path', 'like', $dirName . '%'); // Apply the extensions filter - if (is_array($extensions) && !empty($extensions)) { + if (!empty($extensions) && is_array($extensions)) { $query->where(function ($query) use ($extensions) { // Get the first extension to query for $query->where('path', 'like', '%' . '.' . array_pop($extensions)); @@ -189,7 +195,7 @@ public function select(string $dirName, array $options = []) } // Apply the columns filter on the data returned - if (is_null($columns)) { + if (!isset($columns)) { $resultItem = [ 'fileName' => $fileName, 'content' => $item->content, @@ -227,7 +233,7 @@ public function select(string $dirName, array $options = []) * @param string $fileName * @param string $extension * @param string $content - * @return bool + * @return int */ public function insert(string $dirName, string $fileName, string $extension, string $content) { @@ -377,7 +383,7 @@ public function delete(string $dirName, string $fileName, string $extension) * @param string $dirName * @param string $fileName * @param string $extension - * @return int + * @return int|null */ public function lastModified(string $dirName, string $fileName, string $extension) { @@ -385,8 +391,7 @@ public function lastModified(string $dirName, string $fileName, string $extensio return Carbon::parse($this->getQuery() ->where('path', $this->makeFilePath($dirName, $fileName, $extension)) ->first()->updated_at)->timestamp; - } - catch (Exception $ex) { + } catch (Exception $ex) { return null; } } @@ -399,7 +404,7 @@ public function lastModified(string $dirName, string $fileName, string $extensio */ public function makeCacheKey($name = '') { - return crc32($this->source . $name); + return (string) crc32($this->source . $name); } /** diff --git a/src/Halcyon/Datasource/FileDatasource.php b/src/Halcyon/Datasource/FileDatasource.php index a6fabbdf..a22703c6 100644 --- a/src/Halcyon/Datasource/FileDatasource.php +++ b/src/Halcyon/Datasource/FileDatasource.php @@ -15,7 +15,7 @@ /** * File based datasource. */ -class FileDatasource extends Datasource implements DatasourceInterface +class FileDatasource extends Datasource { /** * The local path where the datasource can be found. @@ -94,14 +94,16 @@ public function selectOne(string $dirName, string $fileName, string $extension) */ public function select(string $dirName, array $options = []) { - extract(array_merge([ + // Prepare query options + $queryOptions = array_merge([ 'columns' => null, // Only return specific columns (fileName, mtime, content) 'extensions' => null, // Match specified extensions 'fileMatch' => null, // Match the file name using fnmatch() 'orders' => null, // @todo 'limit' => null, // @todo 'offset' => null // @todo - ], $options)); + ], $options); + extract($queryOptions); $result = []; $dirPath = $this->makeDirectoryPath($dirName); @@ -110,11 +112,12 @@ public function select(string $dirName, array $options = []) return $result; } - if ($columns === ['*'] || !is_array($columns)) { - $columns = null; - } - else { - $columns = array_flip($columns); + if (isset($columns)) { + if ($columns === ['*'] || !is_array($columns)) { + $columns = null; + } else { + $columns = array_flip($columns); + } } $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dirPath)); @@ -131,7 +134,7 @@ public function select(string $dirName, array $options = []) * Filter by extension */ $fileExt = $it->getExtension(); - if ($extensions !== null && !in_array($fileExt, $extensions)) { + if (isset($extensions) && !in_array($fileExt, $extensions)) { $it->next(); continue; } @@ -144,7 +147,7 @@ public function select(string $dirName, array $options = []) /* * Filter by file name match */ - if ($fileMatch !== null && !fnmatch($fileMatch, $fileName)) { + if (isset($fileMatch) && !fnmatch($fileMatch, $fileName)) { $it->next(); continue; } @@ -155,11 +158,11 @@ public function select(string $dirName, array $options = []) $item['fileName'] = $fileName; - if (!$columns || array_key_exists('content', $columns)) { + if (!isset($columns) || array_key_exists('content', $columns)) { $item['content'] = $this->files->get($path); } - if (!$columns || array_key_exists('mtime', $columns)) { + if (!isset($columns) || array_key_exists('mtime', $columns)) { $item['mtime'] = $this->files->lastModified($path); } @@ -178,7 +181,7 @@ public function select(string $dirName, array $options = []) * @param string $fileName * @param string $extension * @param string $content - * @return bool + * @return int */ public function insert(string $dirName, string $fileName, string $extension, string $content) { @@ -270,7 +273,7 @@ public function delete(string $dirName, string $fileName, string $extension) * @param string $dirName * @param string $fileName * @param string $extension - * @return int + * @return int|null */ public function lastModified(string $dirName, string $fileName, string $extension) { @@ -368,7 +371,7 @@ protected function makeFilePath(string $dirName, string $fileName, string $exten */ public function makeCacheKey($name = '') { - return crc32($this->basePath . $name); + return (string) crc32($this->basePath . $name); } /** diff --git a/src/Halcyon/Exception/CreateDirectoryException.php b/src/Halcyon/Exception/CreateDirectoryException.php index f76e6800..822a30fe 100644 --- a/src/Halcyon/Exception/CreateDirectoryException.php +++ b/src/Halcyon/Exception/CreateDirectoryException.php @@ -14,7 +14,7 @@ class CreateDirectoryException extends RuntimeException /** * Set the affected directory path. * - * @param string $model + * @param string $path * @return $this */ public function setInvalidPath($path) diff --git a/src/Halcyon/Exception/CreateFileException.php b/src/Halcyon/Exception/CreateFileException.php index 43227c7a..500277b8 100644 --- a/src/Halcyon/Exception/CreateFileException.php +++ b/src/Halcyon/Exception/CreateFileException.php @@ -14,7 +14,7 @@ class CreateFileException extends RuntimeException /** * Set the affected file path. * - * @param string $model + * @param string $path * @return $this */ public function setInvalidPath($path) diff --git a/src/Halcyon/Exception/DeleteFileException.php b/src/Halcyon/Exception/DeleteFileException.php index 2c1228d0..4a3b1348 100644 --- a/src/Halcyon/Exception/DeleteFileException.php +++ b/src/Halcyon/Exception/DeleteFileException.php @@ -14,7 +14,7 @@ class DeleteFileException extends RuntimeException /** * Set the affected file path. * - * @param string $model + * @param string $path * @return $this */ public function setInvalidPath($path) diff --git a/src/Halcyon/Exception/FileExistsException.php b/src/Halcyon/Exception/FileExistsException.php index 908d0c12..fc3440b0 100644 --- a/src/Halcyon/Exception/FileExistsException.php +++ b/src/Halcyon/Exception/FileExistsException.php @@ -14,7 +14,7 @@ class FileExistsException extends RuntimeException /** * Set the affected directory path. * - * @param string $model + * @param string $path * @return $this */ public function setInvalidPath($path) diff --git a/src/Halcyon/Exception/InvalidExtensionException.php b/src/Halcyon/Exception/InvalidExtensionException.php index 5846d671..f71350c5 100644 --- a/src/Halcyon/Exception/InvalidExtensionException.php +++ b/src/Halcyon/Exception/InvalidExtensionException.php @@ -59,7 +59,7 @@ public function setAllowedExtensions(array $allowedExtensions) /** * Get the list of allowed extensions. * - * @return string + * @return array */ public function getAllowedExtensions() { diff --git a/src/Halcyon/MemoryRepository.php b/src/Halcyon/MemoryRepository.php index 7d443860..d6529cf1 100644 --- a/src/Halcyon/MemoryRepository.php +++ b/src/Halcyon/MemoryRepository.php @@ -44,7 +44,7 @@ public function get($key, $default = null) /** * Store an item in the cache. * - * @param string $key + * @param string|array $key * @param mixed $value * @param \DateTimeInterface|\DateInterval|int $seconds * @return bool @@ -92,12 +92,12 @@ public function decrement($key, $value = 1) * * @param string $key * @param mixed $value - * @return void + * @return bool */ public function forever($key, $value) { $this->putInMemoryCache($key, $value); - parent::forever($key, $value); + return parent::forever($key, $value); } /** @@ -128,8 +128,8 @@ public function flush() * Retrieve an item from the internal memory cache without trying the external driver. * Used in testing * - * @param $key - * @return mixed + * @param string $key + * @return mixed|null */ public function getFromMemoryCache($key) { @@ -140,8 +140,8 @@ public function getFromMemoryCache($key) * Puts an item in the memory cache, but not in the external cache. * Used in testing * - * @param $key - * @param $value + * @param string $key + * @param mixed $value */ public function putInMemoryCache($key, $value) { diff --git a/src/Halcyon/Model.php b/src/Halcyon/Model.php index d3cb0d69..830f52fc 100644 --- a/src/Halcyon/Model.php +++ b/src/Halcyon/Model.php @@ -15,6 +15,11 @@ /** * This is a base template object. Equivalent to a Model in ORM. * + * The following properties and methods may be available: + * + * @property string|null $fileName Halcyon models generally provide a filename of the model being manipulated. + * @method \Illuminate\Support\MessageBag|null errors() If the Validation trait is attached to the model, this method will provide the validation errors. + * * @author Alexey Bobkov, Samuel Georges */ class Model extends Extendable implements ArrayAccess, Arrayable, Jsonable, JsonSerializable @@ -406,7 +411,7 @@ public function isLoadedFromCache() /** * Returns true if the object was loaded from the cache. - * @return boolean + * @return void */ public function setLoadedFromCache($value) { diff --git a/src/Halcyon/Processors/Processor.php b/src/Halcyon/Processors/Processor.php index 3253bfba..813d0c4e 100644 --- a/src/Halcyon/Processors/Processor.php +++ b/src/Halcyon/Processors/Processor.php @@ -8,9 +8,8 @@ class Processor * Process the results of a singular "select" query. * * @param \Winter\Storm\Halcyon\Builder $query - * @param array $result - * @param string $fileName - * @return array + * @param array|null $result + * @return array|null */ public function processSelectOne(Builder $query, $result) { @@ -48,8 +47,9 @@ public function processSelect(Builder $query, $results) /** * Helper to break down template content in to a useful array. - * @param int $mtime - * @param string $content + * @param \Winter\Storm\Halcyon\Builder $query + * @param array|null $result + * @param string $fileName * @return array */ protected function parseTemplateContent($query, $result, $fileName) diff --git a/src/Halcyon/Processors/SectionParser.php b/src/Halcyon/Processors/SectionParser.php index 16b839a3..4a08d919 100644 --- a/src/Halcyon/Processors/SectionParser.php +++ b/src/Halcyon/Processors/SectionParser.php @@ -21,12 +21,13 @@ class SectionParser */ public static function render($data, $options = []) { - extract(array_merge([ + $sectionOptions = array_merge([ 'wrapCodeInPhpTags' => true, 'isCompoundObject' => true - ], $options)); + ], $options); + extract($sectionOptions); - if (!$isCompoundObject) { + if (!isset($isCompoundObject) || $isCompoundObject === false) { return array_get($data, 'content'); } @@ -58,7 +59,7 @@ public static function render($data, $options = []) } if ($code) { - if ($wrapCodeInPhpTags) { + if (isset($wrapCodeInPhpTags) && $wrapCodeInPhpTags === true) { $code = preg_replace('/^\<\?php/', '', $code); $code = preg_replace('/^\<\?/', '', $code); $code = preg_replace('/\?>$/', '', $code); @@ -98,9 +99,10 @@ public static function render($data, $options = []) */ public static function parse($content, $options = []) { - extract(array_merge([ + $sectionOptions = array_merge([ 'isCompoundObject' => true - ], $options)); + ], $options); + extract($sectionOptions); $result = [ 'settings' => [], @@ -108,7 +110,7 @@ public static function parse($content, $options = []) 'markup' => null ]; - if (!$isCompoundObject || !strlen($content)) { + if (!isset($isCompoundObject) || $isCompoundObject === false || !strlen($content)) { return $result; } @@ -120,7 +122,7 @@ public static function parse($content, $options = []) } if ($count >= 3) { - $result['settings'] = @$iniParser->parse($sections[0], true) + $result['settings'] = @$iniParser->parse($sections[0]) ?: [self::ERROR_INI => $sections[0]]; $result['code'] = $sections[1]; @@ -132,7 +134,7 @@ public static function parse($content, $options = []) $result['markup'] = $sections[2]; } elseif ($count == 2) { - $result['settings'] = @$iniParser->parse($sections[0], true) + $result['settings'] = @$iniParser->parse($sections[0]) ?: [self::ERROR_INI => $sections[0]]; $result['markup'] = $sections[1]; @@ -182,9 +184,9 @@ public static function parseOffset($content) * Returns the line number of a found instance of CMS object section separator (==). * @param string $content Object content * @param int $instance Which instance to look for - * @return int The line number the instance was found. + * @return int|null The line number the instance was found. */ - private static function calculateLinePosition($content, $instance = 1) + public static function calculateLinePosition($content, $instance = 1) { $count = 0; $lines = explode(PHP_EOL, $content); @@ -193,7 +195,7 @@ private static function calculateLinePosition($content, $instance = 1) $count++; } - if ($count == $instance) { + if ($count === $instance) { return static::adjustLinePosition($content, $number); } } @@ -209,7 +211,7 @@ private static function calculateLinePosition($content, $instance = 1) * @param int $startLine The calculated starting line from calculateLinePosition() * @return int The adjusted line number. */ - private static function adjustLinePosition($content, $startLine = -1) + public static function adjustLinePosition($content, $startLine = -1) { // Account for the separator itself. $startLine++; diff --git a/src/Support/Facades/DB.php b/src/Support/Facades/DB.php new file mode 100644 index 00000000..f5738678 --- /dev/null +++ b/src/Support/Facades/DB.php @@ -0,0 +1,50 @@ + Date: Fri, 17 Dec 2021 14:01:48 +0800 Subject: [PATCH 003/108] Fix default config namespaces not loading --- src/Config/FileLoader.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Config/FileLoader.php b/src/Config/FileLoader.php index 5bf7bc2f..52c59f09 100644 --- a/src/Config/FileLoader.php +++ b/src/Config/FileLoader.php @@ -210,11 +210,14 @@ protected function getPackagePath($package, $group, $env = null) /** * Get the configuration path for a namespace. * - * @param string $namespace + * @param string|null $namespace * @return string|null */ - protected function getPath($namespace) + protected function getPath($namespace = null) { + if (is_null($namespace)) { + return $this->defaultPath; + } if (isset($this->hints[$namespace])) { return $this->hints[$namespace]; } From 68f7ab3fb6fe933cc1d72c76a624ff84998b4712 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 17 Dec 2021 14:34:57 +0800 Subject: [PATCH 004/108] Further fixes to docblocks and signatures --- src/Halcyon/Builder.php | 2 +- src/Halcyon/Datasource/Datasource.php | 4 +- .../Datasource/DatasourceInterface.php | 6 +++ src/Halcyon/Model.php | 44 ++++++++++--------- src/Support/Traits/Emitter.php | 3 +- 5 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/Halcyon/Builder.php b/src/Halcyon/Builder.php index 47c5f24a..bc0cd0f8 100644 --- a/src/Halcyon/Builder.php +++ b/src/Halcyon/Builder.php @@ -315,7 +315,7 @@ protected function runSelect() * Execute the query as a "select" statement. * * @param array $columns - * @return \Winter\Storm\Halcyon\Collection|static[] + * @return \Winter\Storm\Halcyon\Collection */ public function get($columns = ['*']) { diff --git a/src/Halcyon/Datasource/Datasource.php b/src/Halcyon/Datasource/Datasource.php index d313f9a6..a8e3d83e 100644 --- a/src/Halcyon/Datasource/Datasource.php +++ b/src/Halcyon/Datasource/Datasource.php @@ -20,9 +20,7 @@ abstract class Datasource implements DatasourceInterface protected $postProcessor; /** - * Get the query post processor used by the connection. - * - * @return \Winter\Storm\Halcyon\Processors\Processor + * @inheritDoc */ public function getPostProcessor() { diff --git a/src/Halcyon/Datasource/DatasourceInterface.php b/src/Halcyon/Datasource/DatasourceInterface.php index e3dc60ec..ceffa5ab 100644 --- a/src/Halcyon/Datasource/DatasourceInterface.php +++ b/src/Halcyon/Datasource/DatasourceInterface.php @@ -2,6 +2,12 @@ interface DatasourceInterface { + /** + * Get the query post processor used by the connection. + * + * @return \Winter\Storm\Halcyon\Processors\Processor + */ + public function getPostProcessor(); /** * Returns a single template. diff --git a/src/Halcyon/Model.php b/src/Halcyon/Model.php index 830f52fc..1802ece7 100644 --- a/src/Halcyon/Model.php +++ b/src/Halcyon/Model.php @@ -15,9 +15,8 @@ /** * This is a base template object. Equivalent to a Model in ORM. * - * The following properties and methods may be available: - * * @property string|null $fileName Halcyon models generally provide a filename of the model being manipulated. + * @property int|null $mtime Halcyon models generally provide a timestamp of last modification. * @method \Illuminate\Support\MessageBag|null errors() If the Validation trait is attached to the model, this method will provide the validation errors. * * @author Alexey Bobkov, Samuel Georges @@ -27,12 +26,12 @@ class Model extends Extendable implements ArrayAccess, Arrayable, Jsonable, Json use \Winter\Storm\Support\Traits\Emitter; /** - * @var string The data source for the model, a directory path. + * @var string|null The data source for the model, a directory path. */ protected $datasource; /** - * @var string The container name associated with the model, eg: pages. + * @var string|null The container name associated with the model, eg: pages. */ protected $dirName; @@ -110,21 +109,21 @@ class Model extends Extendable implements ArrayAccess, Arrayable, Jsonable, Json /** * The cache manager instance. * - * @var \Illuminate\Cache\CacheManager + * @var \Illuminate\Cache\CacheManager|null */ protected static $cache; /** * The datasource resolver instance. * - * @var \Winter\Storm\Halcyon\Datasource\ResolverInterface + * @var \Winter\Storm\Halcyon\Datasource\ResolverInterface|null */ protected static $resolver; /** * The event dispatcher instance. * - * @var \Illuminate\Contracts\Events\Dispatcher + * @var \Winter\Storm\Events\Dispatcher|null */ protected static $dispatcher; @@ -561,7 +560,7 @@ public static function on($datasource = null) /** * Get all of the models from the datasource. * - * @return \Winter\Storm\Halcyon\Collection|static[] + * @return \Winter\Storm\Halcyon\Collection */ public static function all() { @@ -686,7 +685,8 @@ public function getAttribute($key) /** * @see Winter\Storm\Database\Model::getAttributeValue */ - if (($attr = $this->fireEvent('model.beforeGetAttribute', [$key], true)) !== null) { + $attr = $this->fireEvent('model.beforeGetAttribute', [$key], true); + if ($attr !== null) { return $attr; } @@ -702,7 +702,8 @@ public function getAttribute($key) /** * @see Winter\Storm\Database\Model::getAttributeValue */ - if (($_attr = $this->fireEvent('model.getAttribute', [$key, $value], true)) !== null) { + $_attr = $this->fireEvent('model.getAttribute', [$key, $value], true); + if ($_attr !== null) { return $_attr; } @@ -985,12 +986,12 @@ public function delete() */ protected function performDeleteOnModel() { - $this->newQuery()->delete($this->fileName); + $this->newQuery()->delete(); } /** * Create a new native event for handling beforeFetch(). - * @param Closure|string $callback + * @param \Closure|string $callback * @return void */ public static function fetching($callback) @@ -1000,7 +1001,7 @@ public static function fetching($callback) /** * Create a new native event for handling afterFetch(). - * @param Closure|string $callback + * @param \Closure|string $callback * @return void */ public static function fetched($callback) @@ -1279,7 +1280,7 @@ protected function finishSave(array $options) /** * Perform a model update operation. * - * @param Winter\Storm\Halcyon\Builder $query + * @param \Winter\Storm\Halcyon\Builder $query * @param array $options * @return bool */ @@ -1312,7 +1313,7 @@ protected function performUpdate(Builder $query, array $options = []) /** * Perform a model insert operation. * - * @param Winter\Storm\Halcyon\Builder $query + * @param \Winter\Storm\Halcyon\Builder $query * @param array $options * @return bool */ @@ -1409,7 +1410,7 @@ public function getFileNameParts($fileName = null) /** * Get the datasource for the model. * - * @return \Winter\Storm\Halcyon\Datasource + * @return \Winter\Storm\Halcyon\Datasource\DatasourceInterface */ public function getDatasource() { @@ -1443,7 +1444,7 @@ public function setDatasource($name) * Resolve a datasource instance. * * @param string|null $datasource - * @return \Winter\Storm\Halcyon\Datasource + * @return \Winter\Storm\Halcyon\Datasource\DatasourceInterface */ public static function resolveDatasource($datasource = null) { @@ -1453,7 +1454,7 @@ public static function resolveDatasource($datasource = null) /** * Get the datasource resolver instance. * - * @return \Winter\Storm\Halcyon\DatasourceResolverInterface + * @return \Winter\Storm\Halcyon\Datasource\ResolverInterface */ public static function getDatasourceResolver() { @@ -1484,7 +1485,7 @@ public static function unsetDatasourceResolver() /** * Get the event dispatcher instance. * - * @return \Illuminate\Contracts\Events\Dispatcher + * @return \Winter\Storm\Events\Dispatcher */ public static function getEventDispatcher() { @@ -1494,7 +1495,7 @@ public static function getEventDispatcher() /** * Set the event dispatcher instance. * - * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher + * @param \Winter\Storm\Events\Dispatcher $dispatcher * @return void */ public static function setEventDispatcher(Dispatcher $dispatcher) @@ -1546,7 +1547,8 @@ public static function unsetCacheManager() /** * Initializes the object properties from the cached data. The extra data * set here becomes available as attributes set on the model after fetch. - * @param array $cached The cached data array. + * + * @param mixed $item */ public static function initCacheItem(&$item) { diff --git a/src/Support/Traits/Emitter.php b/src/Support/Traits/Emitter.php index be2b9837..7a3ed923 100644 --- a/src/Support/Traits/Emitter.php +++ b/src/Support/Traits/Emitter.php @@ -139,7 +139,8 @@ public function unbindEvent($event = null) * @param string $event Event name * @param array $params Event parameters * @param boolean $halt Halt after first non-null result - * @return array Collection of event results / Or single result (if halted) + * @return array|mixed|null Collection of event results as an array if not halted, if not halted. Otherwise, the first non-null result, or null + * if no listeners returned a result. */ public function fireEvent($event, $params = [], $halt = false) { From c97331865a2a694a9496002fc3caccc4385b7733 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 17 Dec 2021 14:48:34 +0800 Subject: [PATCH 005/108] Documenting magic methods and properties of Auth models --- src/Auth/Models/Group.php | 2 ++ src/Auth/Models/Preferences.php | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/src/Auth/Models/Group.php b/src/Auth/Models/Group.php index 4d4b9a8b..2c3da8fa 100644 --- a/src/Auth/Models/Group.php +++ b/src/Auth/Models/Group.php @@ -4,6 +4,8 @@ /** * Group model + * + * @method \Winter\Storm\Database\Relations\BelongsToMany users() Users relation. */ class Group extends Model { diff --git a/src/Auth/Models/Preferences.php b/src/Auth/Models/Preferences.php index cf65ada9..8e21119e 100644 --- a/src/Auth/Models/Preferences.php +++ b/src/Auth/Models/Preferences.php @@ -6,6 +6,15 @@ /** * User Preferences model + * + * @property string|array|null $value Represents the value of the preference. + * @property string|null $namespace Represents the namespace of the preference. + * @property string|null $group Represents the group of the preference. + * @property string|null $item Represents the item name of the preference. + * @property int|null $user_id Represents the user ID that this preference belongs to. + * + * @method static \Winter\Storm\Database\QueryBuilder applyKeyAndUser($key, $user = null) Scope to find a setting record + * for the specified module (or plugin) name, setting name and user. */ class Preferences extends Model { @@ -158,6 +167,7 @@ public static function findRecord($key, $user = null) /** * Scope to find a setting record for the specified module (or plugin) name, setting name and user. + * @param \Winter\Storm\Database\QueryBuilder $query * @param string $key Specifies the setting key value, for example 'backend:items.perpage' * @param mixed $user An optional user object. * @return mixed Returns the found record or null. From c99be36471e01ac8ef93e5c0258a514ac850b866 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 17 Dec 2021 16:46:57 +0800 Subject: [PATCH 006/108] Initial work on strict typing and return types --- src/Database/QueryBuilder.php | 8 +- src/Halcyon/Datasource/Datasource.php | 34 ++--- .../Datasource/DatasourceInterface.php | 142 ++++++++++------- src/Halcyon/Datasource/DbDatasource.php | 125 ++++----------- src/Halcyon/Datasource/FileDatasource.php | 144 +++++------------- src/Halcyon/Datasource/Resolver.php | 53 +++---- src/Halcyon/Datasource/ResolverInterface.php | 42 +++-- .../Exception/MissingDatasourceException.php | 7 + src/Halcyon/Model.php | 17 ++- src/Halcyon/ModelInterface.php | 20 +++ 10 files changed, 263 insertions(+), 329 deletions(-) create mode 100644 src/Halcyon/Exception/MissingDatasourceException.php create mode 100644 src/Halcyon/ModelInterface.php diff --git a/src/Database/QueryBuilder.php b/src/Database/QueryBuilder.php index ecdaea81..fecf4bb6 100644 --- a/src/Database/QueryBuilder.php +++ b/src/Database/QueryBuilder.php @@ -389,10 +389,8 @@ public function flushDuplicateCache() /** * Enable the memory cache on the query. - * - * @return \Illuminate\Database\Query\Builder|static */ - public function enableDuplicateCache() + public function enableDuplicateCache(): static { $this->cachingDuplicateQueries = true; @@ -401,10 +399,8 @@ public function enableDuplicateCache() /** * Disable the memory cache on the query. - * - * @return \Illuminate\Database\Query\Builder|static */ - public function disableDuplicateCache() + public function disableDuplicateCache(): static { $this->cachingDuplicateQueries = false; diff --git a/src/Halcyon/Datasource/Datasource.php b/src/Halcyon/Datasource/Datasource.php index a8e3d83e..42bc1640 100644 --- a/src/Halcyon/Datasource/Datasource.php +++ b/src/Halcyon/Datasource/Datasource.php @@ -1,5 +1,7 @@ postProcessor; } @@ -30,32 +30,32 @@ public function getPostProcessor() /** * @inheritDoc */ - abstract public function selectOne(string $dirName, string $fileName, string $extension); + abstract public function selectOne(string $dirName, string $fileName, string $extension): ?array; /** * @inheritDoc */ - abstract public function select(string $dirName, array $options = []); + abstract public function select(string $dirName, array $options = []): array; /** * @inheritDoc */ - abstract public function insert(string $dirName, string $fileName, string $extension, string $content); + abstract public function insert(string $dirName, string $fileName, string $extension, string $content): int; /** * @inheritDoc */ - abstract public function update(string $dirName, string $fileName, string $extension, string $content, $oldFileName = null, $oldExtension = null); + abstract public function update(string $dirName, string $fileName, string $extension, string $content, ?string $oldFileName = null, ?string $oldExtension = null): int; /** * @inheritDoc */ - abstract public function delete(string $dirName, string $fileName, string $extension); + abstract public function delete(string $dirName, string $fileName, string $extension): bool; /** * @inheritDoc */ - public function forceDelete(string $dirName, string $fileName, string $extension) + public function forceDelete(string $dirName, string $fileName, string $extension): bool { $this->forceDeleting = true; @@ -69,23 +69,23 @@ public function forceDelete(string $dirName, string $fileName, string $extension /** * @inheritDoc */ - abstract public function lastModified(string $dirName, string $fileName, string $extension); + abstract public function lastModified(string $dirName, string $fileName, string $extension): ?int; /** * @inheritDoc */ - public function makeCacheKey($name = '') + public function makeCacheKey(string $name = ''): string { - return (string) crc32($name); + return hash('crc32b', $name); } /** * @inheritDoc */ - abstract public function getPathsCacheKey(); + abstract public function getPathsCacheKey(): string; /** * @inheritDoc */ - abstract public function getAvailablePaths(); + abstract public function getAvailablePaths(): array; } diff --git a/src/Halcyon/Datasource/DatasourceInterface.php b/src/Halcyon/Datasource/DatasourceInterface.php index ceffa5ab..1c1fc25a 100644 --- a/src/Halcyon/Datasource/DatasourceInterface.php +++ b/src/Halcyon/Datasource/DatasourceInterface.php @@ -4,103 +4,133 @@ interface DatasourceInterface { /** * Get the query post processor used by the connection. - * - * @return \Winter\Storm\Halcyon\Processors\Processor */ - public function getPostProcessor(); + public function getPostProcessor(): \Winter\Storm\Halcyon\Processors\Processor; /** - * Returns a single template. + * Returns a single Halcyon model (template). * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @return mixed + * @param string $dirName The directory in which the model is stored. + * @param string $fileName The filename of the model. + * @param string $extension The file extension of the model. + * @return array|null An array of template data (`fileName`, `mtime` and `content`), or `null` if the model does + * not exist. */ - public function selectOne(string $dirName, string $fileName, string $extension); + public function selectOne(string $dirName, string $fileName, string $extension): ?array; /** - * Returns all templates. + * Returns all Halcyon models (templates) within a given directory. + * + * You can provide multiple options with the `$options` property, in order to filter the retrieved records: + * - `columns`: Only retrieve certain columns. Must be an array with any combination of `fileName`, `mtime` and + * `content`. + * - `extensions`: Defines the accepted extensions as an array. Eg: `['htm', 'md', 'twig']` + * - `fileMatch`: Defines a glob string to match filenames against. Eg: `'*gr[ae]y'` + * - `orders`: Not implemented + * - `limit`: Not implemented + * - `offset`: Not implemented * - * @param string $dirName - * @param array $options - * @return array + * @todo Implement support for `orders`, `limit` and `offset` options. + * @param string $dirName The directory in which the model is stored. + * @param array $options Defines the options for this query. + * @return array An array of models found, with the columns defined as per the `columns` parameter for `$options`. */ - public function select(string $dirName, array $options = []); + public function select(string $dirName, array $options = []): array; /** - * Creates a new template. + * Creates a new Halcyon model (template). * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @param string $content - * @return int + * @param string $dirName The directory in which the model is stored. + * @param string $fileName The filename of the model. + * @param string $extension The file extension of the model. + * @param string $content The content to store for the model. + * @return int The filesize of the created model. */ public function insert(string $dirName, string $fileName, string $extension, string $content); /** - * Updates an existing template. + * Updates an existing Halcyon model (template). * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @param string $content - * @param string $oldFileName Defaults to null - * @param string $oldExtension Defaults to null - * @return int + * @param string $dirName The directory in which the model is stored. + * @param string $fileName The filename of the model. + * @param string $extension The file extension of the model. + * @param string $content The content to store for the model. + * @param string|null $oldFileName Used for renaming templates. If specified, this will delete the "old" path. + * @param string|null $oldExtension Used for renaming templates. If specified, this will delete the "old" path. + * @return int The filesize of the updated model. */ - public function update(string $dirName, string $fileName, string $extension, string $content, $oldFileName = null, $oldExtension = null); + public function update( + string $dirName, + string $fileName, + string $extension, + string $content, + ?string $oldFileName = null, + ?string $oldExtension = null + ): int; /** - * Run a delete statement against the datasource. + * Runs a delete statement against the datasource. * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @return bool + * @param string $dirName The directory in which the model is stored. + * @param string $fileName The filename of the model. + * @param string $extension The file extension of the model. + * @return bool If the delete operation completed successfully. */ - public function delete(string $dirName, string $fileName, string $extension); + public function delete(string $dirName, string $fileName, string $extension): bool; /** - * Run a delete statement against the datasource, forcing the complete removal of the template + * Runs a delete statement against the datasource, forcing the complete removal of the model (template). * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @return bool + * @param string $dirName The directory in which the model is stored. + * @param string $fileName The filename of the model. + * @param string $extension The file extension of the model. + * @return bool If the delete operation completed successfully. */ - public function forceDelete(string $dirName, string $fileName, string $extension); + public function forceDelete(string $dirName, string $fileName, string $extension): bool; /** - * Return the last modified date of an object + * Returns the last modified date of a model (template). * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @return int|null + * @param string $dirName The directory in which the model is stored. + * @param string $fileName The filename of the model. + * @param string $extension The file extension of the model. + * @return int|null The last modified time as a timestamp, or `null` if the object doesn't exist. */ - public function lastModified(string $dirName, string $fileName, string $extension); + public function lastModified(string $dirName, string $fileName, string $extension): ?int; /** * Generate a cache key unique to this datasource. * - * @param string $name - * @return string + * @param string $name The name of the key. + * @return string The hashed key. */ - public function makeCacheKey($name = ''); + public function makeCacheKey(string $name = ''): string; /** - * Generate a paths cache key unique to this datasource + * Gets the prefix of the cache keys. + * + * This is based off a prefix including the base path for the model. * - * @return string + * @return string The cache key prefix. */ - public function getPathsCacheKey(); + public function getPathsCacheKey(): string; /** - * Get all available paths within this datastore + * Get all available paths within this datasource. + * + * This method returns an array, with all available paths as the key, and a boolean that represents whether the path + * can be handled or modified. + * + * Example: + * + * ```php + * [ + * 'path/to/file.md' => true, // (this path is available, and can be handled) + * 'path/to/file2.md' => false // (this path is available, but cannot be handled) + * ] + * ``` * - * @return array $paths ['path/to/file1.md' => true (path can be handled and exists), 'path/to/file2.md' => false (path can be handled but doesn't exist)] + * @return array An array of available paths alongside whether they can be handled. */ - public function getAvailablePaths(); + public function getAvailablePaths(): array; } diff --git a/src/Halcyon/Datasource/DbDatasource.php b/src/Halcyon/Datasource/DbDatasource.php index 277fc281..b4654f3e 100644 --- a/src/Halcyon/Datasource/DbDatasource.php +++ b/src/Halcyon/Datasource/DbDatasource.php @@ -23,48 +23,42 @@ class DbDatasource extends Datasource { /** - * @var string The identifier for this datasource instance + * The identifier for this datasource instance */ - protected $source; + protected string $source; /** - * @var string The table name of the datasource + * The table name of the datasource */ - protected $table; + protected string $table; /** - * Create a new datasource instance. + * Create a new database datasource instance. * * @param string $source The source identifier for this datasource instance * @param string $table The table for this database datasource - * @return void */ public function __construct(string $source, string $table) { $this->source = $source; - $this->table = $table; - $this->postProcessor = new Processor; } /** * Get the base QueryBuilder object. - * - * @return \Illuminate\Database\Query\Builder */ - public function getBaseQuery() + public function getBaseQuery(): \Winter\Storm\Database\QueryBuilder { return DB::table($this->table)->enableDuplicateCache(); } /** - * Get the QueryBuilder object + * Get the QueryBuilder object. * - * @param bool $ignoreDeleted Flag to ignore deleted records, defaults to true - * @return \Illuminate\Database\Query\Builder + * @param bool $ignoreDeleted Ignore deleted records. Defaults to `true`. */ - public function getQuery($ignoreDeleted = true) + public function getQuery(bool $ignoreDeleted = true): \Winter\Storm\Database\QueryBuilder { $query = $this->getBaseQuery(); @@ -92,27 +86,22 @@ public function getQuery($ignoreDeleted = true) } /** - * Helper to make file path. + * Helper method to make the full file path to the model. * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @return string + * @param string $dirName The directory in which the model is stored. + * @param string $fileName The filename of the model. + * @param string $extension The file extension of the model. + * @return string The full file path. */ - protected function makeFilePath(string $dirName, string $fileName, string $extension) + protected function makeFilePath(string $dirName, string $fileName, string $extension): string { return $dirName . '/' . $fileName . '.' . $extension; } /** - * Returns a single template. - * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @return mixed + * @inheritDoc */ - public function selectOne(string $dirName, string $fileName, string $extension) + public function selectOne(string $dirName, string $fileName, string $extension): ?array { $result = $this->getQuery()->where('path', $this->makeFilePath($dirName, $fileName, $extension))->first(); @@ -129,20 +118,9 @@ public function selectOne(string $dirName, string $fileName, string $extension) } /** - * Returns all templates. - * - * @param string $dirName - * @param array $options Array of options, [ - * 'columns' => ['fileName', 'mtime', 'content'], // Only return specific columns - * 'extensions' => ['htm', 'md', 'twig'], // Extensions to search for - * 'fileMatch' => '*gr[ae]y', // Shell matching pattern to match the filename against using the fnmatch function - * 'orders' => false // Not implemented - * 'limit' => false // Not implemented - * 'offset' => false // Not implemented - * ]; - * @return array + * @inheritDoc */ - public function select(string $dirName, array $options = []) + public function select(string $dirName, array $options = []): array { // Initialize result set $result = []; @@ -227,15 +205,9 @@ public function select(string $dirName, array $options = []) } /** - * Creates a new template. - * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @param string $content - * @return int + * @inheritDoc */ - public function insert(string $dirName, string $fileName, string $extension, string $content) + public function insert(string $dirName, string $fileName, string $extension, string $content): int { $path = $this->makeFilePath($dirName, $fileName, $extension); @@ -284,17 +256,9 @@ public function insert(string $dirName, string $fileName, string $extension, str } /** - * Updates an existing template. - * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @param string $content - * @param string $oldFileName Defaults to null - * @param string $oldExtension Defaults to null - * @return int + * @inheritDoc */ - public function update(string $dirName, string $fileName, string $extension, string $content, $oldFileName = null, $oldExtension = null) + public function update(string $dirName, string $fileName, string $extension, string $content, ?string $oldFileName = null, ?string $oldExtension = null): int { $path = $this->makeFilePath($dirName, $fileName, $extension); @@ -344,14 +308,9 @@ public function update(string $dirName, string $fileName, string $extension, str } /** - * Run a delete statement against the datasource. - * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @return bool + * @inheritDoc */ - public function delete(string $dirName, string $fileName, string $extension) + public function delete(string $dirName, string $fileName, string $extension): bool { try { // Get the existing record @@ -378,14 +337,9 @@ public function delete(string $dirName, string $fileName, string $extension) } /** - * Return the last modified date of an object - * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @return int|null + * @inheritDoc */ - public function lastModified(string $dirName, string $fileName, string $extension) + public function lastModified(string $dirName, string $fileName, string $extension): ?int { try { return Carbon::parse($this->getQuery() @@ -397,32 +351,17 @@ public function lastModified(string $dirName, string $fileName, string $extensio } /** - * Generate a cache key unique to this datasource. - * - * @param string $name - * @return string + * @inheritDoc */ - public function makeCacheKey($name = '') - { - return (string) crc32($this->source . $name); - } - - /** - * Generate a paths cache key unique to this datasource - * - * @return string - */ - public function getPathsCacheKey() + public function getPathsCacheKey(): string { return 'halcyon-datastore-db-' . $this->table . '-' . $this->source; } /** - * Get all available paths within this datastore - * - * @return array $paths ['path/to/file1.md' => true (path can be handled and exists), 'path/to/file2.md' => false (path can be handled but doesn't exist)] - */ - public function getAvailablePaths() + * @inheritDoc + **/ + public function getAvailablePaths(): array { /** * @event halcyon.datasource.db.beforeGetAvailablePaths diff --git a/src/Halcyon/Datasource/FileDatasource.php b/src/Halcyon/Datasource/FileDatasource.php index a22703c6..b8cd5927 100644 --- a/src/Halcyon/Datasource/FileDatasource.php +++ b/src/Halcyon/Datasource/FileDatasource.php @@ -1,5 +1,8 @@ basePath = $basePath; - $this->files = $files; - $this->postProcessor = new Processor; } /** - * Returns a single template. - * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @return mixed + * @inheritDoc */ - public function selectOne(string $dirName, string $fileName, string $extension) + public function selectOne(string $dirName, string $fileName, string $extension): ?array { try { $path = $this->makeFilePath($dirName, $fileName, $extension); @@ -72,27 +61,15 @@ public function selectOne(string $dirName, string $fileName, string $extension) 'content' => $this->files->get($path), 'mtime' => $this->files->lastModified($path) ]; - } - catch (Exception $ex) { + } catch (Exception $ex) { return null; } } /** - * Returns all templates. - * - * @param string $dirName - * @param array $options Array of options, [ - * 'columns' => ['fileName', 'mtime', 'content'], // Only return specific columns - * 'extensions' => ['htm', 'md', 'twig'], // Extensions to search for - * 'fileMatch' => '*gr[ae]y', // Shell matching pattern to match the filename against using the fnmatch function - * 'orders' => false // Not implemented - * 'limit' => false // Not implemented - * 'offset' => false // Not implemented - * ]; - * @return array + * @inheritDoc */ - public function select(string $dirName, array $options = []) + public function select(string $dirName, array $options = []): array { // Prepare query options $queryOptions = array_merge([ @@ -175,15 +152,9 @@ public function select(string $dirName, array $options = []) } /** - * Creates a new template. - * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @param string $content - * @return int + * @inheritDoc */ - public function insert(string $dirName, string $fileName, string $extension, string $content) + public function insert(string $dirName, string $fileName, string $extension, string $content): int { $this->validateDirectoryForSave($dirName, $fileName, $extension); @@ -195,24 +166,15 @@ public function insert(string $dirName, string $fileName, string $extension, str try { return $this->files->put($path, $content); - } - catch (Exception $ex) { + } catch (Exception $ex) { throw (new CreateFileException)->setInvalidPath($path); } } /** - * Updates an existing template. - * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @param string $content - * @param string $oldFileName Defaults to null - * @param string $oldExtension Defaults to null - * @return int + * @inheritDoc */ - public function update(string $dirName, string $fileName, string $extension, string $content, $oldFileName = null, $oldExtension = null) + public function update(string $dirName, string $fileName, string $extension, string $content, ?string $oldFileName = null, ?string $oldExtension = null): int { $this->validateDirectoryForSave($dirName, $fileName, $extension); @@ -241,48 +203,35 @@ public function update(string $dirName, string $fileName, string $extension, str try { return $this->files->put($path, $content); - } - catch (Exception $ex) { + } catch (Exception $ex) { throw (new CreateFileException)->setInvalidPath($path); } } /** - * Run a delete statement against the datasource. - * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @return bool + * @inheritDoc */ - public function delete(string $dirName, string $fileName, string $extension) + public function delete(string $dirName, string $fileName, string $extension): bool { $path = $this->makeFilePath($dirName, $fileName, $extension); try { return $this->files->delete($path); - } - catch (Exception $ex) { + } catch (Exception $ex) { throw (new DeleteFileException)->setInvalidPath($path); } } /** - * Run a delete statement against the datasource. - * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @return int|null + * @inheritDoc */ - public function lastModified(string $dirName, string $fileName, string $extension) + public function lastModified(string $dirName, string $fileName, string $extension): ?int { try { $path = $this->makeFilePath($dirName, $fileName, $extension); return $this->files->lastModified($path); - } - catch (Exception $ex) { + } catch (Exception $ex) { return null; } } @@ -290,12 +239,11 @@ public function lastModified(string $dirName, string $fileName, string $extensio /** * Ensure the requested file can be created in the requested directory. * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @return void + * @param string $dirName The directory in which the model is stored. + * @param string $fileName The filename of the model. + * @param string $extension The file extension of the model. */ - protected function validateDirectoryForSave(string $dirName, string $fileName, string $extension) + protected function validateDirectoryForSave(string $dirName, string $fileName, string $extension): void { $path = $this->makeFilePath($dirName, $fileName, $extension); $dirPath = $this->makeDirectoryPath($dirName); @@ -333,7 +281,7 @@ protected function validateDirectoryForSave(string $dirName, string $fileName, s * @throws InvalidFileNameException If the path is outside of the basePath of the datasource * @return string */ - protected function makeDirectoryPath($dirName, $relativePath = '') + protected function makeDirectoryPath(string $dirName, string $relativePath = ''): string { $base = $this->basePath . '/' . $dirName; $path = !empty($relativePath) ? $base . '/' . $relativePath : $base; @@ -351,54 +299,38 @@ protected function makeDirectoryPath($dirName, $relativePath = '') } /** - * Helper to make file path. + * Helper method to make the full file path to the model. * - * @param string $dirName - * @param string $fileName - * @param string $extension - * @return string + * @param string $dirName The directory in which the model is stored. + * @param string $fileName The filename of the model. + * @param string $extension The file extension of the model. + * @return string The full file path. */ - protected function makeFilePath(string $dirName, string $fileName, string $extension) + protected function makeFilePath(string $dirName, string $fileName, string $extension): string { return $this->makeDirectoryPath($dirName, $fileName . '.' . $extension); } - /** - * Generate a cache key unique to this datasource. - * - * @param string $name - * @return string - */ - public function makeCacheKey($name = '') - { - return (string) crc32($this->basePath . $name); - } - /** * Returns the base path for this datasource. - * @return string */ - public function getBasePath() + public function getBasePath(): string { return $this->basePath; } /** - * Generate a paths cache key unique to this datasource - * - * @return string + * @inheritDoc */ - public function getPathsCacheKey() + public function getPathsCacheKey(): string { return 'halcyon-datastore-file-' . $this->basePath; } /** - * Get all available paths within this datastore - * - * @return array $paths ['path/to/file1.md' => true (path can be handled and exists), 'path/to/file2.md' => false (path can be handled but doesn't exist)] + * @inheritDoc */ - public function getAvailablePaths() + public function getAvailablePaths(): array { $pathsCache = []; $it = (is_dir($this->basePath)) diff --git a/src/Halcyon/Datasource/Resolver.php b/src/Halcyon/Datasource/Resolver.php index 2d4dd11e..82042992 100644 --- a/src/Halcyon/Datasource/Resolver.php +++ b/src/Halcyon/Datasource/Resolver.php @@ -1,5 +1,7 @@ getDefaultDatasource(); } + if (!array_key_exists($name, $this->datasources)) { + throw new MissingDatasourceException( + sprintf('The Halcyon datasource "%s" does not exist.', $name) + ); + } return $this->datasources[$name]; } /** - * Add a datasource to the resolver. - * - * @param string $name - * @param \Winter\Storm\Halcyon\Datasource\DatasourceInterface $datasource - * @return void + * @inheritDoc */ - public function addDatasource($name, DatasourceInterface $datasource) + public function addDatasource(string $name, DatasourceInterface $datasource): void { $this->datasources[$name] = $datasource; } /** - * Check if a datasource has been registered. - * - * @param string $name - * @return bool + * @inheritDoc */ - public function hasDatasource($name) + public function hasDatasource(string $name): bool { - return isset($this->datasources[$name]); + return array_key_exists($name, $this->datasources); } /** - * Get the default datasource name. - * - * @return string + * @inheritDoc */ - public function getDefaultDatasource() + public function getDefaultDatasource(): string { return $this->default; } /** - * Set the default datasource name. - * - * @param string $name - * @return void + * @inheritDoc */ - public function setDefaultDatasource($name) + public function setDefaultDatasource(string $name): void { $this->default = $name; } diff --git a/src/Halcyon/Datasource/ResolverInterface.php b/src/Halcyon/Datasource/ResolverInterface.php index 0f5a0373..2753599b 100644 --- a/src/Halcyon/Datasource/ResolverInterface.php +++ b/src/Halcyon/Datasource/ResolverInterface.php @@ -1,28 +1,48 @@ driver()->flushInternalCache(); + if (!MemoryCacheManager::isEnabled() || self::getCacheManager() === null) { + return; } + + /** @var \Winter\Storm\Halcyon\MemoryRepository */ + $cacheDriver = self::getCacheManager()->driver(); + $cacheDriver->flushInternalCache(); } /** diff --git a/src/Halcyon/ModelInterface.php b/src/Halcyon/ModelInterface.php new file mode 100644 index 00000000..4da27073 --- /dev/null +++ b/src/Halcyon/ModelInterface.php @@ -0,0 +1,20 @@ + Date: Sat, 18 Dec 2021 17:46:08 +0800 Subject: [PATCH 007/108] Apply suggestions from code review Co-authored-by: Luke Towers --- src/Halcyon/Model.php | 6 +++--- src/Support/Traits/Emitter.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Halcyon/Model.php b/src/Halcyon/Model.php index d0b2b457..e86035d1 100644 --- a/src/Halcyon/Model.php +++ b/src/Halcyon/Model.php @@ -683,7 +683,7 @@ public function getAttribute($key) * @see Winter\Storm\Database\Model::getAttributeValue */ $attr = $this->fireEvent('model.beforeGetAttribute', [$key], true); - if ($attr !== null) { + if (!is_null($attr)) { return $attr; } @@ -700,7 +700,7 @@ public function getAttribute($key) * @see Winter\Storm\Database\Model::getAttributeValue */ $_attr = $this->fireEvent('model.getAttribute', [$key, $value], true); - if ($_attr !== null) { + if (!is_null($_attr)) { return $_attr; } @@ -1557,7 +1557,7 @@ public static function initCacheItem(&$item) */ public static function flushDuplicateCache() { - if (!MemoryCacheManager::isEnabled() || self::getCacheManager() === null) { + if (!MemoryCacheManager::isEnabled() || is_null(self::getCacheManager())) { return; } diff --git a/src/Support/Traits/Emitter.php b/src/Support/Traits/Emitter.php index 7a3ed923..b7b27116 100644 --- a/src/Support/Traits/Emitter.php +++ b/src/Support/Traits/Emitter.php @@ -139,8 +139,8 @@ public function unbindEvent($event = null) * @param string $event Event name * @param array $params Event parameters * @param boolean $halt Halt after first non-null result - * @return array|mixed|null Collection of event results as an array if not halted, if not halted. Otherwise, the first non-null result, or null - * if no listeners returned a result. + * @return array|mixed|null If halted, the first non-null result. If not halted, an array of event results. Returns + * null if no listeners returned a result. */ public function fireEvent($event, $params = [], $halt = false) { From 1968d0685d75cb8ac38507046dd91c120a956a65 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 09:40:37 +0800 Subject: [PATCH 008/108] Set strict types on Argon --- src/Argon/Argon.php | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/Argon/Argon.php b/src/Argon/Argon.php index 84348500..c5af57c5 100644 --- a/src/Argon/Argon.php +++ b/src/Argon/Argon.php @@ -28,8 +28,17 @@ class Argon extends DateBase */ protected static $parseFunction = 'parseWithCurrentLocale'; - public static function parseWithCurrentLocale($time = null, $timezone = null) - { + /** + * Locale-aware parsing callback. + * + * This will ensure that the current locale is used when parsing dates. + * + * @throws \Carbon\Exceptions\InvalidFormatException If the format provided is invalid. + */ + public static function parseWithCurrentLocale( + string|\DateTimeInterface|null $time = null, + string|\DateTimeZone|null $timezone = null + ): DateBase { if (is_string($time)) { $time = static::translateTimeString($time, static::getLocale(), 'en'); } @@ -37,8 +46,18 @@ public static function parseWithCurrentLocale($time = null, $timezone = null) return parent::rawParse($time, $timezone); } - public static function createFromFormatWithCurrentLocale($format, $time = null, $timezone = null) - { + /** + * Locale-aware instance creation callback. + * + * This will ensure that the current locale is used when creating a new Argon/Carbon object. + * + * @throws \Carbon\Exceptions\InvalidFormatException If the format provided is invalid. + */ + public static function createFromFormatWithCurrentLocale( + string $format, + string $time = null, + \DateTimeZone|string|false|null $timezone = null + ) { if (is_string($time)) { $time = static::translateTimeString($time, static::getLocale(), 'en'); } @@ -52,7 +71,7 @@ public static function createFromFormatWithCurrentLocale($format, $time = null, * @param string $locale * @return string */ - public static function getLanguageFromLocale($locale) + public static function getLanguageFromLocale(string $locale): string { $parts = explode('_', str_replace('-', '_', $locale)); From cb1b0b3ac851b803f243672ed2affeb5bcbfcb3d Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 09:41:17 +0800 Subject: [PATCH 009/108] Fix comment on makeFilePath method in DbDatasource --- src/Halcyon/Datasource/DbDatasource.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Halcyon/Datasource/DbDatasource.php b/src/Halcyon/Datasource/DbDatasource.php index b4654f3e..a91de342 100644 --- a/src/Halcyon/Datasource/DbDatasource.php +++ b/src/Halcyon/Datasource/DbDatasource.php @@ -86,12 +86,12 @@ public function getQuery(bool $ignoreDeleted = true): \Winter\Storm\Database\Que } /** - * Helper method to make the full file path to the model. + * Helper method to combine the provided directory, filename and extension into a single path. * * @param string $dirName The directory in which the model is stored. * @param string $fileName The filename of the model. * @param string $extension The file extension of the model. - * @return string The full file path. + * @return string The combined path. */ protected function makeFilePath(string $dirName, string $fileName, string $extension): string { From 3fed80ad849254b0e9c3108180c7a37c55f36203 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 09:41:54 +0800 Subject: [PATCH 010/108] Use proper exception for insert, update docs --- src/Halcyon/Builder.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Halcyon/Builder.php b/src/Halcyon/Builder.php index bc0cd0f8..1e0bfdd2 100644 --- a/src/Halcyon/Builder.php +++ b/src/Halcyon/Builder.php @@ -1,12 +1,12 @@ validateFileName(); @@ -360,10 +361,10 @@ public function insert(array $values) /** * Update a record in the datasource. * - * @param array $values - * @return int + * @param array $values The values to store in the model. + * @return int The filesize of the created model file. */ - public function update(array $values) + public function update(array $values = []): int { $this->validateFileName(); From 78af61f27b6d3777b4c3583f68261c7d3d3c36e1 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 09:42:33 +0800 Subject: [PATCH 011/108] Fix code smell --- src/Support/Traits/Emitter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Support/Traits/Emitter.php b/src/Support/Traits/Emitter.php index b7b27116..8148d3e0 100644 --- a/src/Support/Traits/Emitter.php +++ b/src/Support/Traits/Emitter.php @@ -139,7 +139,7 @@ public function unbindEvent($event = null) * @param string $event Event name * @param array $params Event parameters * @param boolean $halt Halt after first non-null result - * @return array|mixed|null If halted, the first non-null result. If not halted, an array of event results. Returns + * @return array|mixed|null If halted, the first non-null result. If not halted, an array of event results. Returns * null if no listeners returned a result. */ public function fireEvent($event, $params = [], $halt = false) From d7dd396b36f772b759edb73b0e5ec2a843e92517 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 09:49:20 +0800 Subject: [PATCH 012/108] Model/Pivot constructor changes. - Enforce only the "$attributes" parameter to be available to the constructor via an Interface. - Build Pivot models using a static "fromAttributes" / "fromRawAttributes" method as opposed to a custom constructor. --- src/Database/Model.php | 10 +++--- src/Database/ModelInterface.php | 20 ++++++++++++ src/Database/Pivot.php | 57 ++++++++++++++++++++++++--------- 3 files changed, 67 insertions(+), 20 deletions(-) create mode 100644 src/Database/ModelInterface.php diff --git a/src/Database/Model.php b/src/Database/Model.php index 8e7330b0..9701916e 100644 --- a/src/Database/Model.php +++ b/src/Database/Model.php @@ -1,13 +1,13 @@ parent = $parent; + + $instance->timestamps = $instance->hasTimestampAttributes($attributes); // The pivot model is a "dynamic" model since we will set the tables dynamically // for the instance. This allows it work for any intermediate tables for the // many to many relationship that are defined by this developer's classes. - $this->setRawAttributes($attributes, true); + $instance->setConnection($parent->getConnectionName()) + ->setTable($table) + ->forceFill($attributes) + ->syncOriginal(); + + $instance->exists = $exists; - $this->setTable($table); + return $instance; + } - $this->setConnection($parent->getConnectionName()); + /** + * Create a new pivot model from raw values returned from a query. + * + * @param \Illuminate\Database\Eloquent\Model $parent + * @param array $attributes + * @param string $table + * @param bool $exists + * @return static + */ + public static function fromRawAttributes(Model $parent, $attributes, $table, $exists = false) + { + $instance = static::fromAttributes($parent, [], $table, $exists); - // We store off the parent instance so we will access the timestamp column names - // for the model, since the pivot model timestamps aren't easily configurable - // from the developer's point of view. We can use the parents to get these. - $this->parent = $parent; + $instance->timestamps = $instance->hasTimestampAttributes($attributes); - $this->exists = $exists; + $instance->setRawAttributes($attributes, $exists); - $this->timestamps = $this->hasTimestampAttributes(); + return $instance; } /** From 6c96d9721b10c669a2d82adb8c3e5ecf3b090257 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 10:32:22 +0800 Subject: [PATCH 013/108] Adjust users table migration This should hint that extensions to the User framework should have an "is_superuser" field available for super-user functionality that is expected in Winter. --- src/Auth/Migrations/2013_10_01_000001_Db_Users.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Auth/Migrations/2013_10_01_000001_Db_Users.php b/src/Auth/Migrations/2013_10_01_000001_Db_Users.php index 2d957959..b0976951 100644 --- a/src/Auth/Migrations/2013_10_01_000001_Db_Users.php +++ b/src/Auth/Migrations/2013_10_01_000001_Db_Users.php @@ -23,6 +23,7 @@ public function up() $table->timestamp('activated_at')->nullable(); $table->timestamp('last_login')->nullable(); $table->integer('role_id')->unsigned()->nullable()->index(); + $table->boolean('is_superuser')->default(0); $table->timestamps(); }); } From b2f103b188ec897f6beb982514a8ad066435db7c Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 10:32:58 +0800 Subject: [PATCH 014/108] Improve docblocks in Auth module --- src/Auth/Models/Role.php | 2 ++ src/Auth/Models/Throttle.php | 5 ++++- src/Auth/Models/User.php | 6 ++++++ src/Database/Relations/BelongsToMany.php | 6 +++--- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Auth/Models/Role.php b/src/Auth/Models/Role.php index a9ebb7f3..efa8f335 100644 --- a/src/Auth/Models/Role.php +++ b/src/Auth/Models/Role.php @@ -5,6 +5,8 @@ /** * Role model + * + * @property array $permissions Permissions array. */ class Role extends Model { diff --git a/src/Auth/Models/Throttle.php b/src/Auth/Models/Throttle.php index cfa4c16d..a4cdbea5 100644 --- a/src/Auth/Models/Throttle.php +++ b/src/Auth/Models/Throttle.php @@ -6,6 +6,9 @@ /** * Throttle model + * + * @property \Winter\Storm\Auth\Models\User|null $user Related user. + * @method \Winter\Storm\Database\Relations\BelongsTo user() User relation. */ class Throttle extends Model { @@ -50,7 +53,7 @@ class Throttle extends Model /** * Returns the associated user with the throttler. - * @return User + * @return \Illuminate\Database\Eloquent\Model|null */ public function getUser() { diff --git a/src/Auth/Models/User.php b/src/Auth/Models/User.php index 6b4594ac..75528e16 100644 --- a/src/Auth/Models/User.php +++ b/src/Auth/Models/User.php @@ -8,6 +8,12 @@ /** * User model + * + * @property array|null $groups Related groups. + * @property \Winter\Storm\Auth\Models\Role|null $role Related role. + * @property array $permissions Permissions array. + * @method \Winter\Storm\Database\Relations\BelongsToMany groups() Group relation. + * @method \Winter\Storm\Database\Relations\BelongsTo role() Role relation. */ class User extends Model implements \Illuminate\Contracts\Auth\Authenticatable { diff --git a/src/Database/Relations/BelongsToMany.php b/src/Database/Relations/BelongsToMany.php index f498c364..7350ab9b 100644 --- a/src/Database/Relations/BelongsToMany.php +++ b/src/Database/Relations/BelongsToMany.php @@ -97,9 +97,9 @@ public function save(Model $model, array $pivotData = [], $sessionKey = null) public function sync($ids, $detaching = true) { $changed = parent::sync($ids, $detaching); - + $this->flushDuplicateCache(); - + return $changed; } @@ -171,7 +171,7 @@ public function attach($id, array $attributes = [], $touch = true) /** * Override detach() method of BelongToMany relation. * This is necessary in order to fire 'model.relation.beforeDetach', 'model.relation.afterDetach' events - * @param null $ids + * @param CollectionBase|Model|array|null $ids * @param bool $touch * @return int|void */ From 4a4755adb486da77cc2409c591f2b43625562ea2 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 10:41:47 +0800 Subject: [PATCH 015/108] Add database migration paths for PHPStan --- phpstan.neon | 3 +++ 1 file changed, 3 insertions(+) diff --git a/phpstan.neon b/phpstan.neon index bb605b85..76c63b5f 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,3 +7,6 @@ parameters: level: 5 excludePaths: - src/Assetic/* + databaseMigrationsPath: + - src/Auth/Migrations + - src/Database/Migrations From 2956fc40885d67974baaef371ce06ce9b59e71d2 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 10:43:57 +0800 Subject: [PATCH 016/108] Extend Laravel Config repo in our own repo - Also added the "getMany" method that Laravel now contains. While we have customised our own Config repository implementation, we still follow Laravel in many aspects, so we should extend their class to allow classes which resolve to Laravel's Config repository directly (as opposed to the contract) to still work. --- src/Config/Repository.php | 111 ++++++++------------------------------ 1 file changed, 23 insertions(+), 88 deletions(-) diff --git a/src/Config/Repository.php b/src/Config/Repository.php index 68caddbe..2e0815bd 100644 --- a/src/Config/Repository.php +++ b/src/Config/Repository.php @@ -2,6 +2,7 @@ use Closure; use ArrayAccess; +use Illuminate\Config\Repository as BaseRepository; use Illuminate\Contracts\Config\Repository as RepositoryContract; /** @@ -9,7 +10,7 @@ * * @author Alexey Bobkov, Samuel Georges */ -class Repository implements ArrayAccess, RepositoryContract +class Repository extends BaseRepository implements ArrayAccess, RepositoryContract { use \Winter\Storm\Support\Traits\KeyParser; @@ -115,6 +116,27 @@ public function get($key, $default = null) return array_get($this->items[$collection], $item, $default); } + /** + * Get many configuration values. + * + * @param array $keys + * @return array + */ + public function getMany($keys) + { + $config = []; + + foreach ($keys as $key => $default) { + if (is_numeric($key)) { + [$key, $default] = [$default, null]; + } + + $config[$key] = $this->get($key, $default); + } + + return $config; + } + /** * Set a given configuration value. * @@ -148,48 +170,6 @@ public function set($key, $value = null) } } - /** - * Prepend a value onto an array configuration value. - * - * @param string $key - * @param mixed $value - * @return void - */ - public function prepend($key, $value) - { - $array = $this->get($key); - - array_unshift($array, $value); - - $this->set($key, $array); - } - - /** - * Push a value onto an array configuration value. - * - * @param string $key - * @param mixed $value - * @return void - */ - public function push($key, $value) - { - $array = $this->get($key); - - $array[] = $value; - - $this->set($key, $array); - } - - /** - * Get all of the configuration items for the application. - * - * @return array - */ - public function all() - { - return $this->items; - } - /** * Load the configuration group for the key. * @@ -455,49 +435,4 @@ public function getItems() { return $this->items; } - - /** - * Determine if the given configuration option exists. - * - * @param string $key - * @return bool - */ - public function offsetExists($key) - { - return $this->has($key); - } - - /** - * Get a configuration option. - * - * @param string $key - * @return mixed - */ - public function offsetGet($key) - { - return $this->get($key); - } - - /** - * Set a configuration option. - * - * @param string $key - * @param mixed $value - * @return void - */ - public function offsetSet($key, $value) - { - $this->set($key, $value); - } - - /** - * Unset a configuration option. - * - * @param string $key - * @return void - */ - public function offsetUnset($key) - { - $this->set($key, null); - } } From d628e2676da28a05fc2c0d966bb92289f1936a6a Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 11:12:08 +0800 Subject: [PATCH 017/108] Ignore Auth Manager in analysis Our Auth Manager implementation clashes a lot with Illuminate's Authenticable implementation. We'll need to revisit this at some point. --- phpstan.neon | 1 + 1 file changed, 1 insertion(+) diff --git a/phpstan.neon b/phpstan.neon index 76c63b5f..2ab55e79 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,6 +6,7 @@ parameters: - src level: 5 excludePaths: + - src/Auth/Manager.php - src/Assetic/* databaseMigrationsPath: - src/Auth/Migrations From cfd2e4ac9c1c544f518f8329c7eb2571ce25f7e9 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 11:14:58 +0800 Subject: [PATCH 018/108] Re-add ApplicationException use case --- src/Halcyon/Builder.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Halcyon/Builder.php b/src/Halcyon/Builder.php index 1e0bfdd2..de7313be 100644 --- a/src/Halcyon/Builder.php +++ b/src/Halcyon/Builder.php @@ -1,6 +1,7 @@ Date: Mon, 20 Dec 2021 11:22:58 +0800 Subject: [PATCH 019/108] Inherit docs --- src/Cookie/Middleware/EncryptCookies.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Cookie/Middleware/EncryptCookies.php b/src/Cookie/Middleware/EncryptCookies.php index 640afbda..57a74ff9 100644 --- a/src/Cookie/Middleware/EncryptCookies.php +++ b/src/Cookie/Middleware/EncryptCookies.php @@ -6,6 +6,9 @@ class EncryptCookies extends EncryptCookiesBase { + /** + * @inheritDoc + */ public function __construct(EncrypterContract $encrypter) { parent::__construct($encrypter); From 773f02f86ff3ada4d9c8b76852fce4d7729b9d35 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 11:23:10 +0800 Subject: [PATCH 020/108] Fix return types in dispatcher --- src/Events/Dispatcher.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Events/Dispatcher.php b/src/Events/Dispatcher.php index 712278d2..81e1eb3a 100644 --- a/src/Events/Dispatcher.php +++ b/src/Events/Dispatcher.php @@ -43,9 +43,9 @@ public function listen($events, $listener = null, $priority = 0) } } if ($events instanceof Closure) { - return $this->listen($this->firstClosureParameterType($events), $events, $priority); + $this->listen($this->firstClosureParameterType($events), $events, $priority); } elseif ($events instanceof QueuedClosure) { - return $this->listen($this->firstClosureParameterType($events->closure), $events->resolve(), $priority); + $this->listen($this->firstClosureParameterType($events->closure), $events->resolve(), $priority); } elseif ($listener instanceof QueuedClosure) { $listener = $listener->resolve(); } @@ -184,8 +184,8 @@ public function getListeners($eventName) /** * Sort the listeners for a given event by priority. * - * @param string $eventName - * @return array + * @param string $eventName + * @return void */ protected function sortListeners($eventName) { From 3fc190279f7a30b82a0761c4148e6f148650d2a6 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 11:35:27 +0800 Subject: [PATCH 021/108] Fix docblocks and signatures in Exception module --- src/Exception/ErrorHandler.php | 8 ++++---- src/Exception/ExceptionBase.php | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Exception/ErrorHandler.php b/src/Exception/ErrorHandler.php index 84e83c0e..8b52b22d 100644 --- a/src/Exception/ErrorHandler.php +++ b/src/Exception/ErrorHandler.php @@ -14,7 +14,7 @@ class ErrorHandler { /** - * @var ExceptionBase A prepared mask exception used to mask any exception fired. + * @var Throwable|null A prepared mask exception used to mask any exception fired. */ protected static $activeMask; @@ -61,7 +61,7 @@ public function handleException(Throwable $proposedException) $exception = $proposedException; } // If there is an active mask prepared, use that. - elseif (static::$activeMask !== null) { + elseif (static::$activeMask !== null && static::$activeMask instanceof ExceptionBase) { $exception = static::$activeMask; $exception->setMask($proposedException); } @@ -149,9 +149,9 @@ public function handleCustomError() /** * Displays the detailed system exception page. - * @return View Object containing the error page. + * @return \Illuminate\View\View|string Object containing the error page. */ - public function handleDetailedError($exception) + public function handleDetailedError(Throwable $exception) { return 'Error: ' . $exception->getMessage(); } diff --git a/src/Exception/ExceptionBase.php b/src/Exception/ExceptionBase.php index c48b0c52..72aa6a27 100644 --- a/src/Exception/ExceptionBase.php +++ b/src/Exception/ExceptionBase.php @@ -14,7 +14,7 @@ class ExceptionBase extends Exception { /** - * @var Exception If this exception is acting as a mask, this property stores the face exception. + * @var Throwable If this exception is acting as a mask, this property stores the face exception. */ protected $mask; @@ -39,7 +39,7 @@ class ExceptionBase extends Exception protected $errorType; /** - * @var stdObject Cached code information for highlighting code. + * @var object Cached code information for highlighting code. */ protected $highlight; @@ -147,7 +147,7 @@ public function getTrueException() /** * Generates information used for highlighting the area of code in context of the exception line number. * The highlighted block of code will be six (6) lines before and after the problem line number. - * @return array Highlight information as an array, the following keys are supplied: + * @return object Highlight information as an object, the following keys are supplied: * startLine - The starting line number, 6 lines before the error line. * endLine - The ending line number, 6 lines after the error line. * errorLine - The focused error line number. @@ -235,7 +235,7 @@ public function getCallStack() $args = null; if (isset($event['args']) && count($event['args'])) { - $args = $this->formatStackArguments($event['args'], false); + $args = $this->formatStackArguments($event['args']); } $result[] = (object)[ From a60425fdac50bde3e860371fe05063e49b173a1f Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 11:37:02 +0800 Subject: [PATCH 022/108] Fix tests --- src/Events/Dispatcher.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Events/Dispatcher.php b/src/Events/Dispatcher.php index 81e1eb3a..e2f0117d 100644 --- a/src/Events/Dispatcher.php +++ b/src/Events/Dispatcher.php @@ -44,11 +44,14 @@ public function listen($events, $listener = null, $priority = 0) } if ($events instanceof Closure) { $this->listen($this->firstClosureParameterType($events), $events, $priority); + return; } elseif ($events instanceof QueuedClosure) { $this->listen($this->firstClosureParameterType($events->closure), $events->resolve(), $priority); + return; } elseif ($listener instanceof QueuedClosure) { $listener = $listener->resolve(); } + $listener = Serialisation::wrapClosure($listener); foreach ((array) $events as $event) { From df0061dac5cb879dd0c11508024870e904372094 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 11:54:39 +0800 Subject: [PATCH 023/108] Adjust docs and signatures for Extendable trait --- phpstan.neon | 3 +++ src/Extension/ExtendableTrait.php | 19 ++++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 2ab55e79..269b5c04 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -11,3 +11,6 @@ parameters: databaseMigrationsPath: - src/Auth/Migrations - src/Database/Migrations + ignoreErrors: + - message: '#calls parent::__#' + path: src/Extension/ExtendableTrait.php diff --git a/src/Extension/ExtendableTrait.php b/src/Extension/ExtendableTrait.php index 879663bf..25705a4b 100644 --- a/src/Extension/ExtendableTrait.php +++ b/src/Extension/ExtendableTrait.php @@ -48,7 +48,7 @@ trait ExtendableTrait protected static $extendableGuardProperties = true; /** - * @var ClassLoader Class loader instance. + * @var ClassLoader|null Class loader instance. */ protected static $extendableClassLoader = null; @@ -214,13 +214,16 @@ public function addDynamicProperty($dynamicName, $value = null) /** * Dynamically extend a class with a specified behavior - * @param string $extensionName + * @param string $extensionName * @return void */ public function extendClassWith($extensionName) { if (!strlen($extensionName)) { - return $this; + throw new Exception(sprintf( + 'You must provide an extension name to extend class %s with.', + get_class($this) + )); } $extensionName = $this->extensionNormalizeClassName($extensionName); @@ -364,8 +367,8 @@ protected function extendableIsAccessible($class, $propertyName) /** * Magic method for `__get()` - * @param string $name - * @return string + * @param string $name + * @return mixed|null */ public function extendableGet($name) { @@ -382,13 +385,15 @@ public function extendableGet($name) if ($parent !== false && method_exists($parent, '__get')) { return parent::__get($name); } + + return null; } /** * Magic method for `__set()` * @param string $name - * @param string $value - * @return string + * @param mixed $value + * @return void */ public function extendableSet($name, $value) { From 1bd7fb2fd1ae1a237a03a5ab92ab23a9e380106c Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 12:12:37 +0800 Subject: [PATCH 024/108] Return null if no default datasource is set --- src/Halcyon/Datasource/Resolver.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Halcyon/Datasource/Resolver.php b/src/Halcyon/Datasource/Resolver.php index 82042992..8ce7d1f6 100644 --- a/src/Halcyon/Datasource/Resolver.php +++ b/src/Halcyon/Datasource/Resolver.php @@ -64,9 +64,9 @@ public function hasDatasource(string $name): bool /** * @inheritDoc */ - public function getDefaultDatasource(): string + public function getDefaultDatasource(): ?string { - return $this->default; + return $this->default ?? null; } /** From c7339d90911869dead01e083b9467f488fab3da0 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 14:43:37 +0800 Subject: [PATCH 025/108] Fix signatures and docblocks of Filesystem module --- src/Filesystem/Definitions.php | 77 +++++--- src/Filesystem/Filesystem.php | 189 +++++++++---------- src/Filesystem/FilesystemManager.php | 1 - src/Filesystem/FilesystemServiceProvider.php | 2 + src/Filesystem/PathResolver.php | 38 +--- src/Filesystem/Zip.php | 93 ++++----- src/Support/Facades/File.php | 16 +- 7 files changed, 198 insertions(+), 218 deletions(-) diff --git a/src/Filesystem/Definitions.php b/src/Filesystem/Definitions.php index a11a73c0..c63982b9 100644 --- a/src/Filesystem/Definitions.php +++ b/src/Filesystem/Definitions.php @@ -1,6 +1,6 @@ getDefinitions($type); + return (new static)->getDefinitions($type); } /** * Returns a definition set from config or from the default sets. - * @param $type string - * @return array + * + * @throws Exception If the provided definition type does not exist. */ - public function getDefinitions($type) + public function getDefinitions(string $type): array { if (!method_exists($this, $type)) { throw new Exception(sprintf('No such definition set exists for "%s"', $type)); @@ -37,13 +42,13 @@ public function getDefinitions($type) } /** - * Determines if a path should be ignored, sourced from the ignoreFiles - * and ignorePatterns definitions. + * Determines if a path should be ignored based on the ignoreFiles and ignorePatterns definitions. + * + * Returns `true` if the path is visible, `false` otherwise. + * * @todo Efficiency of this method can be improved. - * @param string $path Specifies a path to check. - * @return boolean Returns TRUE if the path is visible. */ - public static function isPathIgnored($path) + public static function isPathIgnored(string $path): bool { $ignoreNames = self::get('ignoreFiles'); $ignorePatterns = self::get('ignorePatterns'); @@ -63,10 +68,12 @@ public static function isPathIgnored($path) /** * Files that can be safely ignored. - * This list can be customized with config: - * - cms.fileDefinitions.ignoreFiles + * + * This list can be customized with the config: + * + * `cms.fileDefinitions.ignoreFiles` */ - protected function ignoreFiles() + protected function ignoreFiles(): array { return [ '.svn', @@ -78,10 +85,12 @@ protected function ignoreFiles() /** * File patterns that can be safely ignored. - * This list can be customized with config: - * - cms.fileDefinitions.ignorePatterns + * + * This list can be customized with the config: + * + * `cms.fileDefinitions.ignorePatterns` */ - protected function ignorePatterns() + protected function ignorePatterns(): array { return [ '^\..*' @@ -90,10 +99,12 @@ protected function ignorePatterns() /** * Extensions that are particularly benign. + * * This list can be customized with config: - * - cms.fileDefinitions.defaultExtensions + * + * `cms.fileDefinitions.defaultExtensions` */ - protected function defaultExtensions() + protected function defaultExtensions(): array { return [ 'jpg', @@ -142,10 +153,12 @@ protected function defaultExtensions() /** * Extensions seen as public assets. + * * This list can be customized with config: - * - cms.fileDefinitions.assetExtensions + * + * `cms.fileDefinitions.assetExtensions` */ - protected function assetExtensions() + protected function assetExtensions(): array { return [ 'jpg', @@ -171,10 +184,12 @@ protected function assetExtensions() /** * Extensions typically used as images. + * * This list can be customized with config: - * - cms.fileDefinitions.imageExtensions + * + * `cms.fileDefinitions.imageExtensions` */ - protected function imageExtensions() + protected function imageExtensions(): array { return [ 'jpg', @@ -188,10 +203,12 @@ protected function imageExtensions() /** * Extensions typically used as video files. + * * This list can be customized with config: - * - cms.fileDefinitions.videoExtensions + * + * `cms.fileDefinitions.videoExtensions` */ - protected function videoExtensions() + protected function videoExtensions(): array { return [ 'mp4', @@ -206,10 +223,12 @@ protected function videoExtensions() /** * Extensions typically used as audio files. + * * This list can be customized with config: - * - cms.fileDefinitions.audioExtensions + * + * `cms.fileDefinitions.audioExtensions` */ - protected function audioExtensions() + protected function audioExtensions(): array { return [ 'mp3', diff --git a/src/Filesystem/Filesystem.php b/src/Filesystem/Filesystem.php index 640d73ca..d0a651fc 100644 --- a/src/Filesystem/Filesystem.php +++ b/src/Filesystem/Filesystem.php @@ -1,9 +1,9 @@ = 1073741824) { return number_format($bytes / 1073741824, 2) . ' GB'; @@ -87,12 +86,13 @@ public function sizeToString($bytes) } /** - * Returns a public file path from an absolute one - * eg: /home/mysite/public_html/welcome -> /welcome - * @param string $path Absolute path - * @return string + * Returns a public file path from an absolute path. + * + * Eg: `/home/mysite/public_html/welcome` -> `/welcome` + * + * Returns `null` if the path cannot be converted. */ - public function localToPublic($path) + public function localToPublic(string $path): ?string { $result = null; $publicPath = public_path(); @@ -124,12 +124,15 @@ public function localToPublic($path) } /** - * Returns true if the specified path is within the path of the application - * @param string $path The path to - * @param boolean $realpath Default true, uses realpath() to resolve the provided path before checking location. Set to false if you need to check if a potentially non-existent path would be within the application path - * @return boolean + * Determines if the given path is a local path. + * + * Returns `true` if the path is local, `false` otherwise. + * + * @param string $path The path to check + * @param boolean $realpath If `true` (default), the `realpath()` method will be used to resolve symlinks before checking if + * the path is local. Set to `false` if you are looking up non-existent paths. */ - public function isLocalPath($path, $realpath = true) + public function isLocalPath(string $path, bool $realpath = true): bool { $base = base_path(); @@ -141,34 +144,33 @@ public function isLocalPath($path, $realpath = true) } /** - * Returns true if the provided disk is using the "local" driver + * Determines if the given disk is using the "local" driver. * - * @param Illuminate\Filesystem\FilesystemAdapter $disk + * @param \Illuminate\Filesystem\FilesystemAdapter $disk * @return boolean */ - public function isLocalDisk($disk) + public function isLocalDisk(\Illuminate\Filesystem\FilesystemAdapter $disk): bool { return ($disk->getAdapter() instanceof \League\Flysystem\Local\LocalFilesystemAdapter); } /** - * Finds the path to a class - * @param mixed $className Class name or object - * @return string The file path + * Finds the path of a given class. + * + * Returns `false` if the path cannot be determined. + * + * @param string|object $className Class name or object */ - public function fromClass($className) + public function fromClass(string|object $className): string|false { $reflector = new ReflectionClass($className); return $reflector->getFileName(); } /** - * Determine if a file exists with case insensitivity - * supported for the file only. - * @param string $path - * @return mixed Sensitive path or false + * Determines if a file exists (ignoring the case for the filename only). */ - public function existsInsensitive($path) + public function existsInsensitive(string $path): string|false { if ($this->exists($path)) { return $path; @@ -191,52 +193,48 @@ public function existsInsensitive($path) } /** - * Normalizes the directory separator, often used by Win systems. - * @param string $path Path name - * @return string Normalized path + * Normalizes the directory separator, often used by Windows systems. */ - public function normalizePath($path) + public function normalizePath(string $path): string { return str_replace('\\', '/', $path); } /** - * Converts a path using path symbol. Returns the original path if - * no symbol is used and no default is specified. - * @param string $path - * @param mixed $default - * @return string + * Converts a path using path symbol. + * + * Returns the original path if no symbol is used, and no default is specified. */ - public function symbolizePath($path, $default = false) + public function symbolizePath(string $path, string|bool|null $default = null): string { - if (!$firstChar = $this->isPathSymbol($path)) { - return $default === false ? $path : $default; + if (!$this->isPathSymbol($path)) { + return (is_null($default)) ? $path : $default; } + $firstChar = substr($path, 0, 1); $_path = substr($path, 1); return $this->pathSymbols[$firstChar] . $_path; } /** - * Returns true if the path uses a symbol. - * @param string $path - * @return boolean + * Determines if the given path is using a path symbol. */ - public function isPathSymbol($path) + public function isPathSymbol(string $path): bool { - $firstChar = substr($path, 0, 1); - if (isset($this->pathSymbols[$firstChar])) { - return $firstChar; - } - - return false; + return array_key_exists(substr($path, 0, 1), $this->pathSymbols); } /** * Write the contents of a file. - * @param string $path - * @param string $contents - * @return int + * + * This method will also set the permissions based on the given chmod() mask in use. + * + * Returns the number of bytes written to the file, or `false` on failure. + * + * @param string $path + * @param string $contents + * @param bool|int $lock + * @return bool|int */ public function put($path, $contents, $lock = false) { @@ -247,8 +245,13 @@ public function put($path, $contents, $lock = false) /** * Copy a file to a new location. - * @param string $path - * @param string $target + * + * This method will also set the permissions based on the given chmod() mask in use. + * + * Returns `true` if successful, or `false` on failure. + * + * @param string $path + * @param string $target * @return bool */ public function copy($path, $target) @@ -260,26 +263,28 @@ public function copy($path, $target) /** * Create a directory. - * @param string $path - * @param int $mode - * @param bool $recursive - * @param bool $force + * + * @param string $path + * @param int $mode + * @param bool $recursive + * @param bool $force * @return bool */ public function makeDirectory($path, $mode = 0777, $recursive = false, $force = false) { - if ($mask = $this->getFolderPermissions()) { + $mask = $this->getFolderPermissions(); + if (!is_null($mask)) { $mode = $mask; } /* * Find the green leaves */ - if ($recursive && $mask) { + if ($recursive === true && !is_null($mask)) { $chmodPath = $path; while (true) { $basePath = dirname($chmodPath); - if ($chmodPath == $basePath) { + if ($chmodPath === $basePath) { break; } if ($this->isDirectory($basePath)) { @@ -287,8 +292,7 @@ public function makeDirectory($path, $mode = 0777, $recursive = false, $force = } $chmodPath = $basePath; } - } - else { + } else { $chmodPath = $path; } @@ -312,10 +316,11 @@ public function makeDirectory($path, $mode = 0777, $recursive = false, $force = } /** - * Modify file/folder permissions - * @param string $path - * @param octal $mask - * @return void + * Modify file/folder permissions. + * + * @param string $path + * @param int|float|null $mask + * @return bool */ public function chmod($path, $mask = null) { @@ -326,20 +331,16 @@ public function chmod($path, $mask = null) } if (!$mask) { - return; + return false; } return @chmod($path, $mask); } /** - * Modify file/folder permissions recursively - * @param string $path - * @param octal $fileMask - * @param octal $directoryMask - * @return void + * Modify file/folder permissions recursively in a given path. */ - public function chmodRecursive($path, $fileMask = null, $directoryMask = null) + public function chmodRecursive(string $path, int|float|null $fileMask = null, int|float|null $directoryMask = null): void { if (!$fileMask) { $fileMask = $this->getFilePermissions(); @@ -354,7 +355,8 @@ public function chmodRecursive($path, $fileMask = null, $directoryMask = null) } if (!$this->isDirectory($path)) { - return $this->chmod($path, $fileMask); + $this->chmod($path, $fileMask); + return; } $items = new FilesystemIterator($path, FilesystemIterator::SKIP_DOTS); @@ -372,9 +374,8 @@ public function chmodRecursive($path, $fileMask = null, $directoryMask = null) /** * Returns the default file permission mask to use. - * @return string Permission mask as octal (0777) or null */ - public function getFilePermissions() + public function getFilePermissions(): int|float|null { return $this->filePermissions ? octdec($this->filePermissions) @@ -383,9 +384,8 @@ public function getFilePermissions() /** * Returns the default folder permission mask to use. - * @return string Permission mask as octal (0777) or null */ - public function getFolderPermissions() + public function getFolderPermissions(): int|float|null { return $this->folderPermissions ? octdec($this->folderPermissions) @@ -394,11 +394,8 @@ public function getFolderPermissions() /** * Match filename against a pattern. - * @param string|array $fileName - * @param string $pattern - * @return bool */ - public function fileNameMatch($fileName, $pattern) + public function fileNameMatch(string $fileName, string $pattern): bool { if ($pattern === $fileName) { return true; @@ -411,10 +408,8 @@ public function fileNameMatch($fileName, $pattern) /** * Finds symlinks within the base path and provides a source => target array of symlinks. - * - * @return void */ - protected function findSymlinks() + protected function findSymlinks(): void { $restrictBaseDir = Config::get('cms.restrictBaseDir', true); $deep = Config::get('develop.allowDeepSymlinks', false); diff --git a/src/Filesystem/FilesystemManager.php b/src/Filesystem/FilesystemManager.php index 330ac14e..6a7118a8 100644 --- a/src/Filesystem/FilesystemManager.php +++ b/src/Filesystem/FilesystemManager.php @@ -1,6 +1,5 @@ prefixer->prefixPath(''); }); FilesystemAdapter::macro('setPathPrefix', function (string $prefix) { + /** @phpstan-ignore-next-line */ $this->prefixer = new PathPrefixer($prefix, $this->config['directory_separator'] ?? DIRECTORY_SEPARATOR); }); } diff --git a/src/Filesystem/PathResolver.php b/src/Filesystem/PathResolver.php index 6d5d036b..31377d26 100644 --- a/src/Filesystem/PathResolver.php +++ b/src/Filesystem/PathResolver.php @@ -19,11 +19,8 @@ class PathResolver * and directories. * * Returns canonical path if it can be resolved, otherwise `false`. - * - * @param string $path The path to resolve - * @return string|bool */ - public static function resolve($path) + public static function resolve(string $path): string|bool { // Check if path is within any "open_basedir" restrictions if (!static::withinOpenBaseDir($path)) { @@ -100,12 +97,8 @@ public static function resolve($path) /** * Determines if the path is within the given directory. - * - * @param string $path - * @param string $directory - * @return bool */ - public static function within($path, $directory) + public static function within(string $path, string $directory): bool { $directory = static::resolve($directory); $path = static::resolve($path); @@ -115,12 +108,8 @@ public static function within($path, $directory) /** * Join two paths, making sure they use the correct directory separators. - * - * @param string $prefix - * @param string $path The path to add to the prefix. - * @return string */ - public static function join($prefix, $path = '') + public static function join(string $prefix, string $path = ''): string { $fullPath = rtrim(static::normalize($prefix, false) . '/' . static::normalize($path, false), '/'); @@ -133,11 +122,9 @@ public static function join($prefix, $path = '') * Converts any type of path (Unix or Windows) into a Unix-style path, so that we have a consistent format to work * with internally. All paths will be returned with no trailing path separator. * - * @param string $path - * @param bool $applyCwd If true, the current working directory will be appended if the path is relative. - * @return string + * If `$applyCwd` is true, the current working directory will be prepended if the path is relative. */ - protected static function normalize($path, $applyCwd = true) + protected static function normalize(string $path, bool $applyCwd = true): string { // Change directory separators to Unix-based $path = rtrim(str_replace('\\', '/', $path), '/'); @@ -159,11 +146,8 @@ protected static function normalize($path, $applyCwd = true) /** * Standardizes the path separators of a path back to the expected separator for the operating system. - * - * @param string $path - * @return string */ - public static function standardize($path) + public static function standardize(string $path): string { return str_replace('/', DIRECTORY_SEPARATOR, static::normalize($path, false)); } @@ -171,10 +155,9 @@ public static function standardize($path) /** * Resolves a symlink target. * - * @param mixed $path The symlink source's path. - * @return string|bool + * Returns the resolved symlink path, or `false` if it cannot be resolved. */ - protected static function resolveSymlink($symlink) + protected static function resolveSymlink($symlink): string|bool { // Check that the symlink is valid and the target exists $stat = linkinfo($symlink); @@ -205,11 +188,8 @@ protected static function resolveSymlink($symlink) /** * Checks if a given path is within "open_basedir" restrictions. - * - * @param string $path - * @return bool */ - protected static function withinOpenBaseDir($path) + protected static function withinOpenBaseDir(string $path): bool { $baseDirs = ini_get('open_basedir'); diff --git a/src/Filesystem/Zip.php b/src/Filesystem/Zip.php index ef993855..6ee82d35 100644 --- a/src/Filesystem/Zip.php +++ b/src/Filesystem/Zip.php @@ -50,22 +50,23 @@ class Zip extends ZipArchive { /** - * @var string Folder prefix + * Folder prefix */ - protected $folderPrefix = ''; + protected string $folderPrefix = ''; /** - * Extract an existing zip file. - * @param string $source Path for the existing zip - * @param string $destination Path to extract the zip files - * @param array $options - * @return bool + * Lock down the constructor for this class. */ - public static function extract($source, $destination, $options = []) + final public function __construct() { - extract(array_merge([ - 'mask' => 0777 - ], $options)); + } + + /** + * Extracts an existing ZIP file. + */ + public static function extract(string $source, string $destination, array $options = []): bool + { + $mask = $options['mask'] ?? 0777; if (file_exists($destination) || mkdir($destination, $mask, true)) { $zip = new ZipArchive; @@ -80,24 +81,20 @@ public static function extract($source, $destination, $options = []) } /** - * Creates a new empty zip file. - * @param string $destination Path for the new zip - * @param mixed $source - * @param array $options - * @return self + * Creates a new empty Zip file, optionally populating it with given source files. + * + * Source can be a single path, an array of paths or a callback which allows you to manipulate the Zip file. */ - public static function make($destination, $source, $options = []) + public static function make(string $destination, string|callable|array|null $source = null, array $options = []): static { - $zip = new self; + $zip = new static; $zip->open($destination, ZIPARCHIVE::CREATE | ZipArchive::OVERWRITE); if (is_string($source)) { $zip->add($source, $options); - } - elseif (is_callable($source)) { + } elseif (is_callable($source)) { $source($zip); - } - elseif (is_array($source)) { + } elseif (is_array($source)) { foreach ($source as $_source) { $zip->add($_source, $options); } @@ -108,13 +105,13 @@ public static function make($destination, $source, $options = []) } /** - * Includes a source to the Zip - * @param mixed $source - * @param array $options - * @return self + * Adds a source file or directory to a Zip file. */ - public function add($source, $options = []) + public function add(string $source, array $options = []): self { + $recursive = (bool) ($options['recursive'] ?? true); + $includeHidden = isset($options['includeHidden']) && $options['includeHidden'] === true; + /* * A directory has been supplied, convert it to a useful glob * @@ -124,23 +121,18 @@ public function add($source, $options = []) * - starts with '..' but has at least one character after it */ if (is_dir($source)) { - $includeHidden = isset($options['includeHidden']) && $options['includeHidden']; $wildcard = $includeHidden ? '{*,.[!.]*,..?*}' : '*'; $source = implode('/', [dirname($source), basename($source), $wildcard]); } - extract(array_merge([ - 'recursive' => true, - 'includeHidden' => false, - 'basedir' => dirname($source), - 'baseglob' => basename($source) - ], $options)); + $basedir = dirname($source); + $baseglob = basename($source); if (is_file($source)) { $files = [$source]; + $folders = []; $recursive = false; - } - else { + } else { $files = glob($source, GLOB_BRACE); $folders = glob(dirname($source) . '/*', GLOB_ONLYDIR); } @@ -173,12 +165,11 @@ public function add($source, $options = []) } /** - * Creates a new folder inside the Zip and adds source files (optional) - * @param string $name Folder name - * @param mixed $source - * @return self + * Creates a new folder inside the Zip file, and optionally adds the given source files/folders to this folder. + * + * Source can be a single path, an array of paths or a callback which allows you to manipulate the Zip file. */ - public function folder($name, $source = null) + public function folder(string $name, string|callable|array|null $source = null): self { $prefix = $this->folderPrefix; $this->addEmptyDir($prefix . $name); @@ -190,11 +181,9 @@ public function folder($name, $source = null) if (is_string($source)) { $this->add($source); - } - elseif (is_callable($source)) { + } elseif (is_callable($source)) { $source($this); - } - elseif (is_array($source)) { + } elseif (is_array($source)) { foreach ($source as $_source) { $this->add($_source); } @@ -205,12 +194,11 @@ public function folder($name, $source = null) } /** - * Removes a file or folder from the zip collection. + * Removes file(s) or folder(s) from the Zip file. + * * Does not support wildcards. - * @param string $source - * @return self */ - public function remove($source) + public function remove(array|string $source): self { if (is_array($source)) { foreach ($source as $_source) { @@ -237,12 +225,9 @@ public function remove($source) } /** - * Removes a prefix from a path. - * @param string $prefix /var/sites/ - * @param string $path /var/sites/moo/cow/ - * @return string moo/cow/ + * Removes a prefix from a given path. */ - protected function removePathPrefix($prefix, $path) + protected function removePathPrefix(string $prefix, string $path): string { return (strpos($path, $prefix) === 0) ? substr($path, strlen($prefix)) diff --git a/src/Support/Facades/File.php b/src/Support/Facades/File.php index 8e9f7bca..cd43cd75 100644 --- a/src/Support/Facades/File.php +++ b/src/Support/Facades/File.php @@ -39,19 +39,19 @@ * @method static bool copyDirectory(string $directory, string $destination, int $options = null) * @method static bool deleteDirectory(string $directory, bool $preserve = false) * @method static bool cleanDirectory(string $directory) - * @method static bool isDirectoryEmpty(string $directory) + * @method static bool|null isDirectoryEmpty(string $directory) * @method static string sizeToString(int $bytes) - * @method static string localToPublic(string $path) + * @method static string|null localToPublic(string $path) * @method static bool isLocalPath(string $path, bool $realpath = true) - * @method static string fromClass($classname) + * @method static string|null fromClass(string|object $classname) * @method static string|false existsInsensitive(string $path) * @method static string normalizePath(string $path) - * @method static string symbolizePath(string $path, bool $default = false) + * @method static string symbolizePath(string $path, string|bool|null $default = null) * @method static bool isPathSymbol(string $path) - * @method static bool|void chmodRecursive(string $path, $fileMask = null, $directoryMask = null) - * @method static string|null getFilePermissions() - * @method static string|null getFolderPermissions() - * @method static bool fileNameMatch(string|array $fileName, string $pattern) + * @method static void chmodRecursive(string $path, int|float|null $fileMask = null, int|float|null $directoryMask = null) + * @method static int|float|null getFilePermissions() + * @method static int|float|null getFolderPermissions() + * @method static bool fileNameMatch(string $fileName, string $pattern) * * @see \Winter\Storm\Filesystem\Filesystem */ From efd145f4289457f4bede52dd8cfe405f161f4e3e Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 15:34:25 +0800 Subject: [PATCH 026/108] Fix Foundation module docblocks and signatures --- src/Foundation/Application.php | 14 +++---- .../Bootstrap/LoadConfiguration.php | 25 +++++------- .../Bootstrap/RegisterClassLoader.php | 9 ++--- src/Foundation/Bootstrap/RegisterWinter.php | 8 ++-- .../Console/ClearCompiledCommand.php | 1 + src/Foundation/Console/Kernel.php | 12 +++--- src/Foundation/Console/KeyGenerateCommand.php | 8 +++- src/Foundation/Exception/Handler.php | 37 ++++++++++++------ src/Foundation/Http/Kernel.php | 4 +- .../Middleware/CheckForTrustedProxies.php | 9 +++-- src/Foundation/Maker.php | 38 +++++-------------- 11 files changed, 80 insertions(+), 85 deletions(-) diff --git a/src/Foundation/Application.php b/src/Foundation/Application.php index 77953433..517abe0d 100644 --- a/src/Foundation/Application.php +++ b/src/Foundation/Application.php @@ -214,7 +214,7 @@ public function tempPath() /** * Set the temp path for the application. * - * @return string + * @return static */ public function setTempPath($path) { @@ -237,7 +237,7 @@ public function uploadsPath() /** * Set the uploads path for the application. * - * @return string + * @return static */ public function setUploadsPath($path) { @@ -260,7 +260,7 @@ public function mediaPath() /** * Set the media path for the application. * - * @return string + * @return static */ public function setMediaPath($path) { @@ -301,7 +301,7 @@ public function make($abstract, array $parameters = []) */ public function before($callback) { - return $this['router']->before($callback); + $this['router']->before($callback); } /** @@ -312,7 +312,7 @@ public function before($callback) */ public function after($callback) { - return $this['router']->after($callback); + $this['router']->after($callback); } /** @@ -380,12 +380,12 @@ public function setLocale($locale) /** * Register all of the configured providers. * - * @var bool $isRetry If true, this is a second attempt without the cached packages. + * @param bool $isRetry If true, this is a second attempt without the cached packages. * @return void */ public function registerConfiguredProviders($isRetry = false) { - $providers = Collection::make($this->config['app.providers']) + $providers = Collection::make($this->get('config')['app.providers']) ->partition(function ($provider) { return Str::startsWith($provider, 'Illuminate\\'); }); diff --git a/src/Foundation/Bootstrap/LoadConfiguration.php b/src/Foundation/Bootstrap/LoadConfiguration.php index ae384fa1..3c26852f 100644 --- a/src/Foundation/Bootstrap/LoadConfiguration.php +++ b/src/Foundation/Bootstrap/LoadConfiguration.php @@ -1,24 +1,22 @@ detectEnvironment(function () use ($app) { - return $this->getEnvironmentFromHost($app); + $app->detectEnvironment(function () { + return $this->getEnvironmentFromHost(); }); $app->instance('config', $config = new Repository($fileLoader, $app['env'])); @@ -28,15 +26,13 @@ public function bootstrap(Application $app) mb_internal_encoding('UTF-8'); // Fix for XDebug aborting threads > 100 nested - ini_set('xdebug.max_nesting_level', 1000); + ini_set('xdebug.max_nesting_level', '1000'); } /** * Returns the environment based on hostname. - * @param array $config - * @return void */ - protected function getEnvironmentFromHost(Application $app) + protected function getEnvironmentFromHost(): string { $config = $this->getEnvironmentConfiguration(); @@ -51,15 +47,14 @@ protected function getEnvironmentFromHost(Application $app) /** * Load the environment configuration. - * @return array */ - protected function getEnvironmentConfiguration() + protected function getEnvironmentConfiguration(): array { $config = []; $environment = env('APP_ENV'); - if ($environment && file_exists($configPath = base_path().'/config/'.$environment.'/environment.php')) { + if ($environment && file_exists($configPath = base_path() . '/config/' . $environment . '/environment.php')) { try { $config = require $configPath; } @@ -67,7 +62,7 @@ protected function getEnvironmentConfiguration() // } } - elseif (file_exists($configPath = base_path().'/config/environment.php')) { + elseif (file_exists($configPath = base_path() . '/config/environment.php')) { try { $config = require $configPath; } diff --git a/src/Foundation/Bootstrap/RegisterClassLoader.php b/src/Foundation/Bootstrap/RegisterClassLoader.php index 4df803f6..21dd8c73 100644 --- a/src/Foundation/Bootstrap/RegisterClassLoader.php +++ b/src/Foundation/Bootstrap/RegisterClassLoader.php @@ -2,17 +2,14 @@ use Winter\Storm\Support\ClassLoader; use Winter\Storm\Filesystem\Filesystem; -use Illuminate\Contracts\Foundation\Application; +use Winter\Storm\Foundation\Application; class RegisterClassLoader { /** - * Register The Winter Auto Loader - * - * @param \Illuminate\Contracts\Foundation\Application $app - * @return void + * Register the Winter class loader service. */ - public function bootstrap(Application $app) + public function bootstrap(Application $app): void { $loader = new ClassLoader( new Filesystem, diff --git a/src/Foundation/Bootstrap/RegisterWinter.php b/src/Foundation/Bootstrap/RegisterWinter.php index da6719cc..19581851 100644 --- a/src/Foundation/Bootstrap/RegisterWinter.php +++ b/src/Foundation/Bootstrap/RegisterWinter.php @@ -1,16 +1,16 @@ laravel->getCachedClassesPath())) { @unlink($classesPath); } diff --git a/src/Foundation/Console/Kernel.php b/src/Foundation/Console/Kernel.php index c5770f23..3c61d060 100644 --- a/src/Foundation/Console/Kernel.php +++ b/src/Foundation/Console/Kernel.php @@ -8,17 +8,17 @@ class Kernel extends ConsoleKernel /** * The bootstrap classes for the application. * - * @var array + * @var string[] */ protected $bootstrappers = [ - '\Winter\Storm\Foundation\Bootstrap\RegisterClassLoader', - '\Winter\Storm\Foundation\Bootstrap\LoadEnvironmentVariables', - '\Winter\Storm\Foundation\Bootstrap\LoadConfiguration', - '\Winter\Storm\Foundation\Bootstrap\LoadTranslation', + \Winter\Storm\Foundation\Bootstrap\RegisterClassLoader::class, + \Winter\Storm\Foundation\Bootstrap\LoadEnvironmentVariables::class, + \Winter\Storm\Foundation\Bootstrap\LoadConfiguration::class, + \Winter\Storm\Foundation\Bootstrap\LoadTranslation::class, \Illuminate\Foundation\Bootstrap\HandleExceptions::class, \Illuminate\Foundation\Bootstrap\RegisterFacades::class, \Illuminate\Foundation\Bootstrap\SetRequestForConsole::class, - '\Winter\Storm\Foundation\Bootstrap\RegisterWinter', + \Winter\Storm\Foundation\Bootstrap\RegisterWinter::class, \Illuminate\Foundation\Bootstrap\RegisterProviders::class, \Illuminate\Foundation\Bootstrap\BootProviders::class, ]; diff --git a/src/Foundation/Console/KeyGenerateCommand.php b/src/Foundation/Console/KeyGenerateCommand.php index 84ffd084..3fab6227 100644 --- a/src/Foundation/Console/KeyGenerateCommand.php +++ b/src/Foundation/Console/KeyGenerateCommand.php @@ -5,6 +5,11 @@ class KeyGenerateCommand extends KeyGenerateCommandBase { + /** + * Filesystem instance. + */ + protected \Illuminate\Filesystem\Filesystem $files; + /** * Create a new key generator command. * @@ -28,7 +33,8 @@ public function handle() $key = $this->generateRandomKey(); if ($this->option('show')) { - return $this->line(''.$key.''); + $this->line(''.$key.''); + return; } // Next, we will replace the application key in the config file so it is diff --git a/src/Foundation/Exception/Handler.php b/src/Foundation/Exception/Handler.php index 02762687..f7f139e5 100644 --- a/src/Foundation/Exception/Handler.php +++ b/src/Foundation/Exception/Handler.php @@ -15,7 +15,7 @@ class Handler extends ExceptionHandler /** * A list of the exception types that should not be reported. * - * @var array + * @var string[] */ protected $dontReport = [ \Winter\Storm\Exception\AjaxException::class, @@ -84,7 +84,7 @@ public function report(Throwable $throwable) * * @param \Illuminate\Http\Request $request * @param \Throwable $throwable - * @return \Illuminate\Http\Response + * @return \Symfony\Component\HttpFoundation\Response */ public function render($request, Throwable $throwable) { @@ -157,7 +157,7 @@ public function error(Closure $callback) * * @param \Throwable $throwable * @param bool $fromConsole - * @return void + * @return mixed|null */ protected function callCustomHandlers($throwable, $fromConsole = false) { @@ -176,14 +176,14 @@ protected function callCustomHandlers($throwable, $fromConsole = false) // at least some errors, and avoid errors with no data or not log writes. try { $response = $handler($throwable, $code, $fromConsole); + } catch (Throwable $t) { + $response = $this->convertExceptionToResponse($t); } - catch (Throwable $t) { - $response = $this->convertThrowableToResponse($t); - } + // If this handler returns a "non-null" response, we will return it so it will // get sent back to the browsers. Once the handler returns a valid response // we will cease iterating through them and calling these other handlers. - if (isset($response) && ! is_null($response)) { + if (isset($response)) { return $response; } } @@ -214,11 +214,24 @@ protected function hints(ReflectionFunction $reflection, $throwable) $parameters = $reflection->getParameters(); $expected = $parameters[0]; - try { - return (new ReflectionClass($expected->getType()->getName())) - ->isInstance($throwable); - } catch (\Throwable $t) { - return false; + if ($expected->getType() instanceof \ReflectionNamedType) { + try { + return (new ReflectionClass($expected->getType()->getName())) + ->isInstance($throwable); + } catch (\Throwable $t) { + return false; + } + } else if ($expected->getType() instanceof \ReflectionUnionType) { + foreach ($expected->getType()->getTypes() as $type) { + try { + return (new ReflectionClass($type->getName())) + ->isInstance($throwable); + } catch (\Throwable $t) { + return false; + } + } } + + return false; } } diff --git a/src/Foundation/Http/Kernel.php b/src/Foundation/Http/Kernel.php index c915c13e..3d3e5bd2 100644 --- a/src/Foundation/Http/Kernel.php +++ b/src/Foundation/Http/Kernel.php @@ -7,7 +7,7 @@ class Kernel extends HttpKernel /** * The bootstrap classes for the application. * - * @var array + * @var string[] */ protected $bootstrappers = [ \Winter\Storm\Foundation\Bootstrap\RegisterClassLoader::class, @@ -71,7 +71,7 @@ class Kernel extends HttpKernel * * Forces the listed middleware to always be in the given order. * - * @var array + * @var string[] */ protected $middlewarePriority = [ \Illuminate\Session\Middleware\StartSession::class, diff --git a/src/Foundation/Http/Middleware/CheckForTrustedProxies.php b/src/Foundation/Http/Middleware/CheckForTrustedProxies.php index 9a7b391e..d8f445d0 100644 --- a/src/Foundation/Http/Middleware/CheckForTrustedProxies.php +++ b/src/Foundation/Http/Middleware/CheckForTrustedProxies.php @@ -1,6 +1,6 @@ allowProxies($request, [ + $this->allowProxies($request, [ $request->server->get('REMOTE_ADDR') ]); + return; } // Support comma-separated strings as well as arrays @@ -104,7 +105,7 @@ protected function setTrustedProxies(Request $request) : $proxies; if (is_array($proxies)) { - return $this->allowProxies($request, $proxies); + $this->allowProxies($request, $proxies); } } diff --git a/src/Foundation/Maker.php b/src/Foundation/Maker.php index d7ef8c8b..629fb39d 100644 --- a/src/Foundation/Maker.php +++ b/src/Foundation/Maker.php @@ -5,6 +5,8 @@ use Illuminate\Contracts\Container\BindingResolutionException; use ReflectionClass; use ReflectionParameter; +use ReflectionNamedType; +use ReflectionUnionType; class Maker { @@ -22,20 +24,13 @@ class Maker * Maker constructor. * * @param Container $container - * @return void */ public function __construct(Container $container) { $this->container = $container; } - /** - * @param $abstract - * @param array $parameters - * - * @return mixed - */ - public function make($abstract, $parameters = []) + public function make($abstract, array $parameters = []) { return $this->build( $this->getBinding($abstract), @@ -43,12 +38,6 @@ public function make($abstract, $parameters = []) ); } - /** - * @param $abstract - * @param $concrete - * - * @return void - */ public function bind($abstract, Closure $concrete) { $this->bindings[$abstract] = $concrete; @@ -125,11 +114,9 @@ protected function getDependencies(array $parameters, array $primitives = []) if (array_key_exists($parameter->name, $primitives)) { $dependencies[] = $primitives[$parameter->name]; - } - elseif (is_null($dependency)) { + } elseif (is_null($dependency)) { $dependencies[] = $this->resolvePrimitive($parameter); - } - else { + } elseif ($dependency instanceof ReflectionUnionType === false) { $dependencies[] = $this->resolveClass($parameter); } } @@ -145,10 +132,12 @@ protected function getDependencies(array $parameters, array $primitives = []) */ protected function resolveClass(ReflectionParameter $parameter) { + /** @var ReflectionNamedType */ + $type = $parameter->getType(); + try { - return $this->getFromContainer($parameter->getType()->getName()); - } - catch (BindingResolutionException $e) { + return $this->getFromContainer($type->getName()); + } catch (BindingResolutionException $e) { if ($parameter->isOptional()) { return $parameter->getDefaultValue(); } @@ -156,19 +145,12 @@ protected function resolveClass(ReflectionParameter $parameter) } } - /** - * @param $abstract - * - * @return mixed - */ protected function getBinding($abstract) { return $this->isBound($abstract) ? $this->bindings[$abstract] : $abstract; } /** - * @param $abstract - * * @return bool */ protected function isBound($abstract) From 38819dfd7b9d63369d410410e1a3ca755d591069 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 17:10:13 +0800 Subject: [PATCH 027/108] WIP FormBuilder --- src/Html/FormBuilder.php | 154 ++++++++------------------------------- 1 file changed, 31 insertions(+), 123 deletions(-) diff --git a/src/Html/FormBuilder.php b/src/Html/FormBuilder.php index c42816d0..dcdeed48 100644 --- a/src/Html/FormBuilder.php +++ b/src/Html/FormBuilder.php @@ -43,7 +43,7 @@ class FormBuilder /** * The current model instance for the form. * - * @var mixed + * @var object|array|null */ protected $model; @@ -105,10 +105,8 @@ public function __construct(HtmlBuilder $html, UrlGeneratorBase $url, $csrfToken /** * Open up a new HTML form and includes a session key. - * @param array $options - * @return string */ - public function open(array $options = []) + public function open(array $options = []): string { $method = strtoupper(array_get($options, 'method', 'post')); $request = array_get($options, 'request'); @@ -162,11 +160,8 @@ public function open(array $options = []) /** * Helper for opening a form used for an AJAX call. - * @param string $handler Request handler name, eg: onUpdate - * @param array $options - * @return string */ - public function ajax($handler, array $options = []) + public function ajax(string|array $handler, array $options = []): string { if (is_array($handler)) { $handler = implode('::', $handler); @@ -194,12 +189,8 @@ public function ajax($handler, array $options = []) /** * Create a new model based form builder. - * - * @param mixed $model - * @param array $options - * @return string */ - public function model($model, array $options = []) + public function model(object|array $model, array $options = []): string { $this->model = $model; @@ -208,21 +199,16 @@ public function model($model, array $options = []) /** * Set the model instance on the form builder. - * - * @param mixed $model - * @return void */ - public function setModel($model) + public function setModel(object|array|null $model): void { $this->model = $model; } /** * Close the current form. - * - * @return string */ - public function close() + public function close(): string { $this->labels = []; @@ -233,10 +219,8 @@ public function close() /** * Generate a hidden field with the current CSRF token. - * - * @return string */ - public function token() + public function token(): string { $token = !empty($this->csrfToken) ? $this->csrfToken @@ -247,13 +231,8 @@ public function token() /** * Create a form label element. - * - * @param string $name - * @param string $value - * @param array $options - * @return string */ - public function label($name, $value = null, $options = []) + public function label(string $name, string $value = '', array $options = []): string { $this->labels[] = $name; @@ -266,26 +245,16 @@ public function label($name, $value = null, $options = []) /** * Format the label value. - * - * @param string $name - * @param string|null $value - * @return string */ - protected function formatLabel($name, $value) + protected function formatLabel(string $name, string $value = ''): string { return $value ?: ucwords(str_replace('_', ' ', $name)); } /** * Create a form input field. - * - * @param string $type - * @param string $name - * @param string $value - * @param array $options - * @return string */ - public function input($type, $name, $value = null, $options = []) + public function input(string $type, string $name, ?string $value = null, array $options = []): string { if (!isset($options['name'])) { $options['name'] = $name; @@ -312,76 +281,48 @@ public function input($type, $name, $value = null, $options = []) /** * Create a text input field. - * - * @param string $name - * @param string $value - * @param array $options - * @return string */ - public function text($name, $value = null, $options = []) + public function text(string $name, ?string $value = null, array $options = []): string { return $this->input('text', $name, $value, $options); } /** * Create a password input field. - * - * @param string $name - * @param array $options - * @return string */ - public function password($name, $options = []) + public function password(string $name, array $options = []): string { return $this->input('password', $name, '', $options); } /** * Create a hidden input field. - * - * @param string $name - * @param string $value - * @param array $options - * @return string */ - public function hidden($name, $value = null, $options = []) + public function hidden(string $name, ?string $value = null, array $options = []): string { return $this->input('hidden', $name, $value, $options); } /** - * Create an e-mail input field. - * - * @param string $name - * @param string $value - * @param array $options - * @return string + * Create an email input field. */ - public function email($name, $value = null, $options = []) + public function email(string $name, ?string $value = null, array $options = []): string { return $this->input('email', $name, $value, $options); } /** - * Create a url input field. - * - * @param string $name - * @param string $value - * @param array $options - * @return string + * Create a URL input field. */ - public function url($name, $value = null, $options = []) + public function url(string $name, ?string $value = null, array $options = []): string { return $this->input('url', $name, $value, $options); } /** * Create a file input field. - * - * @param string $name - * @param array $options - * @return string */ - public function file($name, $options = []) + public function file(string $name, array $options = []): string { return $this->input('file', $name, null, $options); } @@ -392,13 +333,8 @@ public function file($name, $options = []) /** * Create a textarea input field. - * - * @param string $name - * @param string $value - * @param array $options - * @return string */ - public function textarea($name, $value = null, $options = []) + public function textarea(string $name, ?string $value = null, array $options = []): string { if (!isset($options['name'])) { $options['name'] = $name; @@ -425,11 +361,8 @@ public function textarea($name, $value = null, $options = []) /** * Set the text area size on the attributes. - * - * @param array $options - * @return array */ - protected function setTextAreaSize($options) + protected function setTextAreaSize(array $options): array { if (isset($options['size'])) { return $this->setQuickTextAreaSize($options); @@ -447,11 +380,8 @@ protected function setTextAreaSize($options) /** * Set the text area size using the quick "size" attribute. - * - * @param array $options - * @return array */ - protected function setQuickTextAreaSize($options) + protected function setQuickTextAreaSize(array $options): array { $segments = explode('x', $options['size']); @@ -464,13 +394,8 @@ protected function setQuickTextAreaSize($options) /** * Create a select box field with empty option support. - * @param string $name - * @param array $list - * @param string $selected - * @param array $options - * @return string */ - public function select($name, $list = [], $selected = null, $options = []) + public function select(string $name, array $list = [], ?string $selected = null, array $options = []): string { if (array_key_exists('emptyOption', $options)) { $list = ['' => $options['emptyOption']] + $list; @@ -508,15 +433,8 @@ public function select($name, $list = [], $selected = null, $options = []) /** * Create a select range field. - * - * @param string $name - * @param string $begin - * @param string $end - * @param string $selected - * @param array $options - * @return string */ - public function selectRange($name, $begin, $end, $selected = null, $options = []) + public function selectRange(string $name, string|int|float $begin, string|int|float $end, ?string $selected = null, array $options = []): string { $range = array_combine($range = range($begin, $end), $range); @@ -525,34 +443,24 @@ public function selectRange($name, $begin, $end, $selected = null, $options = [] /** * Create a select year field. - * - * @param string $name - * @param string $begin - * @param string $end - * @param string $selected - * @param array $options - * @return string */ - public function selectYear() + public function selectYear(string $name, int $begin = 1900, ?int $end = null, ?string $selected = null, array $options = []): string { - return call_user_func_array([$this, 'selectRange'], func_get_args()); + if (is_null($end)) { + $end = (int) date('Y'); + } + return $this->selectRange($name, $begin, $end, $selected, $options); } /** * Create a select month field. - * - * @param string $name - * @param string $selected - * @param array $options - * @param string $format - * @return string */ - public function selectMonth($name, $selected = null, $options = [], $format = '%B') + public function selectMonth(string $name, ?string $selected = null, array $options = [], $format = '%B'): string { $months = []; foreach (range(1, 12) as $month) { - $months[$month] = strftime($format, mktime(0, 0, 0, $month, 1)); + $months[$month] = strftime($format, mktime(12, 0, 0, $month, 1)); } return $this->select($name, $months, $selected, $options); @@ -566,7 +474,7 @@ public function selectMonth($name, $selected = null, $options = [], $format = '% * @param string $selected * @return string */ - public function getSelectOption($display, $value, $selected) + public function getSelectOption(string|array $display, string $value, ?string $selected = null) { if (is_array($display)) { return $this->optionGroup($display, $value, $selected); From 1a46e9e9ecea3e71c15cbe8174708724466bcc15 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Dec 2021 21:24:02 +0800 Subject: [PATCH 028/108] WIP Html module analysis --- src/Html/BlockBuilder.php | 81 ++++++++----------- src/Html/FormBuilder.php | 162 ++++++++++++++++---------------------- 2 files changed, 100 insertions(+), 143 deletions(-) diff --git a/src/Html/BlockBuilder.php b/src/Html/BlockBuilder.php index d072bd25..575071ea 100644 --- a/src/Html/BlockBuilder.php +++ b/src/Html/BlockBuilder.php @@ -9,42 +9,44 @@ */ class BlockBuilder { - protected $blockStack = []; - protected $blocks = []; + /** + * The block stack. + */ + protected array $blockStack = []; /** - * Helper for startBlock - * - * @param string $name Specifies the block name. - * @return void + * Registered block contents, keyed by block name. */ - public function put($name) + protected array $blocks = []; + + /** + * Helper method for the "startBlock" templating function. + */ + public function put(string $name): void { $this->startBlock($name); } /** - * Begins the layout block. + * Begins the layout block for a given block name. * * This method enables output buffering, so all output will be captured as a part of this block. - * - * @param string $name Specifies the block name. - * @return void */ - public function startBlock($name) + public function startBlock(string $name): void { array_push($this->blockStack, $name); ob_start(); } /** - * Helper for endBlock and also clears the output buffer. + * Helper method for the "endBlock" templating function. + * + * If `$append` is `true`, the new content should be appended to an existing block, as opposed to overwriting any + * previous content. * - * @param boolean $append Indicates that the new content should be appended to the existing block content. - * @return void * @throws \Exception if there are no items in the block stack */ - public function endPut($append = false) + public function endPut(bool $append = false): void { $this->endBlock($append); } @@ -54,11 +56,9 @@ public function endPut($append = false) * * This captures all buffered output as the block's content, and ends output buffering. * - * @param boolean $append Indicates that the new content should be appended to the existing block content. - * @return void * @throws \Exception if there are no items in the block stack */ - public function endBlock($append = false) + public function endBlock(bool $append = false): void { if (!count($this->blockStack)) { throw new Exception('Invalid block nesting'); @@ -75,30 +75,21 @@ public function endBlock($append = false) } /** - * Sets a content of the layout block. + * Sets a content of the layout block, overwriting any previous content for that block. * * Output buffering is not used for this method. - * - * @param string $name Specifies the block name. - * @param string $content Specifies the block content. - * @return void - * @throws \Exception if there are no items in the block stack */ - public function set($name, $content) + public function set(string $name, string $content): void { $this->blocks[$name] = $content; } /** - * Appends a content of the layout block. + * Appends content to a layout block. * * Output buffering is not used for this method. - * - * @param string $name Specifies the block name. - * @param string $content Specifies the block content. - * @return void */ - public function append($name, $content) + public function append(string $name, string $content): void { if (!isset($this->blocks[$name])) { $this->blocks[$name] = ''; @@ -108,13 +99,11 @@ public function append($name, $content) } /** - * Returns the layout block contents and deletes the block from memory. + * Returns the layout block contents of a given block name and deletes the block from memory. * - * @param string $name Specifies the block name. - * @param string $default Specifies a default block value to use if the block requested is not exists. - * @return string + * If the block does not exist, then the `$default` content will be returned instead. */ - public function placeholder($name, $default = null) + public function placeholder(string $name, string $default = ''): string { $result = $this->get($name, $default); unset($this->blocks[$name]); @@ -127,16 +116,14 @@ public function placeholder($name, $default = null) } /** - * Returns the layout block contents but not deletes the block from memory. + * Returns the layout block contents of a given name, but does not delete it from memory. * - * @param string $name Specifies the block name. - * @param string $default Specifies a default block value to use if the block requested is not exists. - * @return string + * If the block does not exist, then the `$default` content will be returned instead. */ - public function get($name, $default = null) + public function get(string $name, string $default = ''): string { if (!isset($this->blocks[$name])) { - return $default; + return $default; } return $this->blocks[$name]; @@ -144,10 +131,8 @@ public function get($name, $default = null) /** * Clears all the registered blocks. - * - * @return void */ - public function reset() + public function reset(): void { $this->blockStack = []; $this->blocks = []; @@ -155,10 +140,8 @@ public function reset() /** * Gets the block stack at this point. - * - * @return array */ - public function getBlockStack() + public function getBlockStack(): array { return $this->blockStack; } diff --git a/src/Html/FormBuilder.php b/src/Html/FormBuilder.php index dcdeed48..5e48bb6f 100644 --- a/src/Html/FormBuilder.php +++ b/src/Html/FormBuilder.php @@ -14,88 +14,93 @@ class FormBuilder /** * The HTML builder instance. - * - * @var \Winter\Storm\Html\HtmlBuilder */ - protected $html; + protected \Winter\Storm\Html\HtmlBuilder $html; /** * The URL generator instance. - * - * @var \Illuminate\Routing\UrlGenerator $url */ - protected $url; + protected \Illuminate\Routing\UrlGenerator $url; /** * The CSRF token used by the form builder. - * - * @var string */ - protected $csrfToken; + protected string $csrfToken; /** * The session store implementation. - * - * @var \Illuminate\Session\Store */ - protected $session; + protected \Illuminate\Session\Store $session; /** * The current model instance for the form. - * - * @var object|array|null */ - protected $model; + protected object|array|null $model = null; /** * An array of label names we've created. - * - * @var array - */ - protected $labels = []; - - /** - * The reserved form open attributes. - * @var array */ - protected $reserved = ['method', 'url', 'route', 'action', 'files', 'request', 'model', 'sessionKey']; + protected array $labels = []; /** * The reserved form open attributes. - * @var array */ - protected $reservedAjax = ['request', 'success', 'error', 'complete', 'confirm', 'redirect', 'update', 'data', 'validate', 'flash']; + protected array $reserved = [ + 'method', + 'url', + 'route', + 'action', + 'files', + 'request', + 'model', + 'sessionKey' + ]; + + /** + * The reserved form AJAX attributes. + */ + protected array $reservedAjax = [ + 'request', + 'success', + 'error', + 'complete', + 'confirm', + 'redirect', + 'update', + 'data', + 'validate', + 'flash' + ]; /** * The form methods that should be spoofed, in uppercase. - * - * @var array */ - protected $spoofedMethods = ['DELETE', 'PATCH', 'PUT']; + protected array $spoofedMethods = [ + 'DELETE', + 'PATCH', + 'PUT' + ]; /** * The types of inputs to not fill values on by default. - * - * @var array */ - protected $skipValueTypes = ['file', 'password', 'checkbox', 'radio']; + protected array $skipValueTypes = [ + 'file', + 'password', + 'checkbox', + 'radio' + ]; /** * The session key used by the form builder. * @var string */ - protected $sessionKey; + protected string $sessionKey; /** * Create a new form builder instance. - * - * @param \Winter\Storm\Html\HtmlBuilder $html - * @param \Illuminate\Routing\UrlGenerator $url - * @param string $csrfToken - * @param string $sessionKey - * @return void */ - public function __construct(HtmlBuilder $html, UrlGeneratorBase $url, $csrfToken, $sessionKey) + public function __construct(HtmlBuilder $html, UrlGeneratorBase $url, string $csrfToken, string $sessionKey) { $this->url = $url; $this->html = $html; @@ -395,7 +400,7 @@ protected function setQuickTextAreaSize(array $options): array /** * Create a select box field with empty option support. */ - public function select(string $name, array $list = [], ?string $selected = null, array $options = []): string + public function select(string $name, array $list = [], string|array|null $selected = null, array $options = []): string { if (array_key_exists('emptyOption', $options)) { $list = ['' => $options['emptyOption']] + $list; @@ -434,7 +439,7 @@ public function select(string $name, array $list = [], ?string $selected = null, /** * Create a select range field. */ - public function selectRange(string $name, string|int|float $begin, string|int|float $end, ?string $selected = null, array $options = []): string + public function selectRange(string $name, string|int|float $begin, string|int|float $end, string|array|null $selected = null, array $options = []): string { $range = array_combine($range = range($begin, $end), $range); @@ -444,7 +449,7 @@ public function selectRange(string $name, string|int|float $begin, string|int|fl /** * Create a select year field. */ - public function selectYear(string $name, int $begin = 1900, ?int $end = null, ?string $selected = null, array $options = []): string + public function selectYear(string $name, int $begin = 1900, ?int $end = null, string|array|null $selected = null, array $options = []): string { if (is_null($end)) { $end = (int) date('Y'); @@ -455,7 +460,7 @@ public function selectYear(string $name, int $begin = 1900, ?int $end = null, ?s /** * Create a select month field. */ - public function selectMonth(string $name, ?string $selected = null, array $options = [], $format = '%B'): string + public function selectMonth(string $name, string|array|null $selected = null, array $options = [], $format = '%B'): string { $months = []; @@ -468,13 +473,8 @@ public function selectMonth(string $name, ?string $selected = null, array $optio /** * Get the select option for the given value. - * - * @param string $display - * @param string $value - * @param string $selected - * @return string */ - public function getSelectOption(string|array $display, string $value, ?string $selected = null) + public function getSelectOption(string|array $display, string $value, string|array|null $selected = null): string { if (is_array($display)) { return $this->optionGroup($display, $value, $selected); @@ -485,13 +485,8 @@ public function getSelectOption(string|array $display, string $value, ?string $s /** * Create an option group form element. - * - * @param array $list - * @param string $label - * @param string $selected - * @return string */ - protected function optionGroup($list, $label, $selected) + protected function optionGroup(array $list, string $label, string|array|null $selected = null): string { $html = []; @@ -499,40 +494,38 @@ protected function optionGroup($list, $label, $selected) $html[] = $this->option($display, $value, $selected); } - return ''.implode('', $html).''; + return '' . implode('', $html) . ''; } /** * Create a select element option. - * - * @param string $display - * @param string $value - * @param string $selected - * @return string */ - protected function option($display, $value, $selected) + protected function option(string $display, string $value, string|array|null $selected = null): string { - $selected = $this->getSelectedValue($value, $selected); + $selectedAttr = $this->getSelectedValue($value, $selected); - $options = ['value' => e($value), 'selected' => $selected]; + $options = [ + 'value' => e($value), + 'selected' => $selectedAttr + ]; - return 'html->attributes($options).'>'.e($display).''; + return 'html->attributes($options) . '>' . e($display) . ''; } /** * Determine if the value is selected. - * - * @param string $value - * @param string $selected - * @return string */ - protected function getSelectedValue($value, $selected) + protected function getSelectedValue(string $value, string|array|null $selected): string|null { + if (is_null($selected)) { + return null; + } + if (is_array($selected)) { return in_array($value, $selected) ? 'selected' : null; } - return ((string) $value == (string) $selected) ? 'selected' : null; + return ((string) $value === (string) $selected) ? 'selected' : null; } // @@ -541,28 +534,16 @@ protected function getSelectedValue($value, $selected) /** * Create a checkbox input field. - * - * @param string $name - * @param mixed $value - * @param bool $checked - * @param array $options - * @return string */ - public function checkbox($name, $value = 1, $checked = null, $options = []) + public function checkbox(string $name, string $value = '1', bool $checked = false, array $options = []): string { return $this->checkable('checkbox', $name, $value, $checked, $options); } /** * Create a radio button input field. - * - * @param string $name - * @param mixed $value - * @param bool $checked - * @param array $options - * @return string */ - public function radio($name, $value = null, $checked = null, $options = []) + public function radio(string $name, ?string $value = null, bool $checked = false, array $options = []): string { if (is_null($value)) { $value = $name; @@ -573,15 +554,8 @@ public function radio($name, $value = null, $checked = null, $options = []) /** * Create a checkable input field. - * - * @param string $type - * @param string $name - * @param mixed $value - * @param bool $checked - * @param array $options - * @return string */ - protected function checkable($type, $name, $value, $checked, $options) + protected function checkable(string $type, string $name, string $value, bool $checked = false, array $options = []): string { $checked = $this->getCheckedState($type, $name, $value, $checked); From 33328e44b3d1f90fbb026fc0dcdab9a2f10904f0 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 24 Feb 2022 06:52:30 +0800 Subject: [PATCH 029/108] Update Composer deps, small tweaks --- composer.json | 4 ++-- src/Foundation/Console/ClearCompiledCommand.php | 2 +- src/Scaffold/GeneratorCommand.php | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index fc0ee3af..22fd67f0 100644 --- a/composer.json +++ b/composer.json @@ -55,8 +55,8 @@ "php-parallel-lint/php-parallel-lint": "^1.0", "meyfa/phpunit-assert-gd": "^2.0.0|^3.0.0", "dms/phpunit-arraysubset-asserts": "^0.1.0|^0.2.1", - "nunomaduro/larastan": "^1.0", - "orchestra/testbench": "dev-master" + "nunomaduro/larastan": "^2.0.1", + "orchestra/testbench": "^7.1.0" }, "suggest": { "ext-pdo_dblib": "Required to use MS SQL Server databases", diff --git a/src/Foundation/Console/ClearCompiledCommand.php b/src/Foundation/Console/ClearCompiledCommand.php index d0c39e7e..08dce128 100644 --- a/src/Foundation/Console/ClearCompiledCommand.php +++ b/src/Foundation/Console/ClearCompiledCommand.php @@ -16,6 +16,6 @@ public function handle() @unlink($classesPath); } - return parent::handle(); + parent::handle(); } } diff --git a/src/Scaffold/GeneratorCommand.php b/src/Scaffold/GeneratorCommand.php index a68ba219..3e6dd982 100644 --- a/src/Scaffold/GeneratorCommand.php +++ b/src/Scaffold/GeneratorCommand.php @@ -1,11 +1,11 @@ files->isDirectory(dirname($path))) { + if (!$this->files->isDirectory(dirname($path))) { $this->files->makeDirectory(dirname($path), 0777, true, true); } } From 0a714528d6189fd8e6476bbdf8f90335fe3cd1a6 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 24 Feb 2022 08:48:31 +0800 Subject: [PATCH 030/108] Add class comment --- src/Scaffold/GeneratorCommand.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Scaffold/GeneratorCommand.php b/src/Scaffold/GeneratorCommand.php index 3e6dd982..7d236382 100644 --- a/src/Scaffold/GeneratorCommand.php +++ b/src/Scaffold/GeneratorCommand.php @@ -7,6 +7,12 @@ use Winter\Storm\Support\Facades\Twig; use Winter\Storm\Support\Str; +/** + * Generator command. + * + * This class is used as a base for scaffolding commands, modifying stub files and copying them over to a specific + * location. + */ abstract class GeneratorCommand extends Command { /** From b1fd55455f5221b61a0757de50ad6697ff4d83b8 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 24 Feb 2022 08:48:44 +0800 Subject: [PATCH 031/108] Fix Stan issues in Translator --- src/Translation/Translator.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Translation/Translator.php b/src/Translation/Translator.php index ff20d530..f089ea51 100644 --- a/src/Translation/Translator.php +++ b/src/Translation/Translator.php @@ -18,7 +18,7 @@ class Translator extends TranslatorBase /** * The event dispatcher instance. * - * @var \Illuminate\Contracts\Events\Dispatcher|\Winter\Storm\Events\Dispatcher + * @var \Illuminate\Contracts\Events\Dispatcher|\Winter\Storm\Events\Dispatcher|null */ protected $events; @@ -142,7 +142,7 @@ public function transChoice($key, $number, array $replace = [], $locale = null) * @param string $key * @param array $replace * @param string $locale - * @return string + * @return string|null */ protected function getValidationSpecific($key, $replace, $locale) { @@ -181,7 +181,7 @@ public function choice($key, $number, array $replace = [], $locale = null) // If the given "number" is actually an array or countable we will simply count the // number of elements in an instance. This allows developers to pass an array of // items without having to count it on their end first which gives bad syntax. - if (is_array($number) || $number instanceof Countable) { + if (is_array($number) || $number instanceof \Countable) { $number = count($number); } From d174113ac19710f328d45f0a9ef50b00712cdeb3 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 24 Feb 2022 10:27:19 +0800 Subject: [PATCH 032/108] Fix Validation and Support PHPStan issues --- phpstan.neon | 4 +++- src/Support/ClassLoader.php | 26 ++++++++++++++------- src/Support/Facade.php | 27 ---------------------- src/Support/Facades/Form.php | 2 +- src/Support/Facades/Input.php | 15 ++++++++++++ src/Support/Facades/Schema.php | 2 +- src/Support/ModuleServiceProvider.php | 4 ++-- src/Support/Serialization.php | 4 ++-- src/Support/Testing/Fakes/MailFake.php | 10 ++++---- src/Support/helpers-array.php | 2 +- src/Support/helpers.php | 16 ++++++------- src/Support/polyfills.php | 2 +- src/Validation/Concerns/ValidatesEmail.php | 2 +- src/Validation/Factory.php | 7 ++++++ src/Validation/Validator.php | 2 +- 15 files changed, 65 insertions(+), 60 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 269b5c04..36518d27 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,10 +7,12 @@ parameters: level: 5 excludePaths: - src/Auth/Manager.php - - src/Assetic/* databaseMigrationsPath: - src/Auth/Migrations - src/Database/Migrations ignoreErrors: - message: '#calls parent::__#' path: src/Extension/ExtendableTrait.php + # Ignore incorrect docs from Laravel's Validation Factory + - message: '#\$resolver is not covariant#' + path: src/Validation/Factory.php diff --git a/src/Support/ClassLoader.php b/src/Support/ClassLoader.php index da80d04f..824f1738 100644 --- a/src/Support/ClassLoader.php +++ b/src/Support/ClassLoader.php @@ -1,8 +1,8 @@ ensureManifestIsLoaded(); - $this->registered = spl_autoload_register([$this, 'load']); + $callback = [$this, 'load']; + if (is_callable($callback)) { + $this->registered = spl_autoload_register(); + } + + throw new Exception('The "load" method is missing from the class loader'); } /** @@ -222,8 +227,13 @@ public function unregister() return; } - spl_autoload_unregister([$this, 'load']); - $this->registered = false; + $callback = [$this, 'load']; + if (is_callable($callback)) { + spl_autoload_unregister([$this, 'load']); + $this->registered = false; + } + + throw new Exception('The "load" method is missing from the class loader'); } /** @@ -309,7 +319,7 @@ public function addAliases(array $aliases) * Aliases are first-come, first-served. If a real class already exists with the same name as an alias, the real * class is used over the alias. * - * @param array $aliases + * @param array $namespaceAliases * @return void */ public function addNamespaceAliases(array $namespaceAliases) @@ -409,7 +419,7 @@ protected static function normalizeClass($class) * Get the possible paths for a class. * * @param string $class - * @return string + * @return array */ protected static function getPathsForClass($class) { diff --git a/src/Support/Facade.php b/src/Support/Facade.php index 84b05071..ec68bd14 100644 --- a/src/Support/Facade.php +++ b/src/Support/Facade.php @@ -10,31 +10,4 @@ */ class Facade extends FacadeParent { - - /** - * @inheritDoc - */ - protected static function resolveFacadeInstance($name) - { - if ( - !is_object($name) && - !is_null(static::$app) && - !static::$app->bound($name) && - ($instance = static::getFacadeInstance()) !== null - ) { - static::$app->instance($name, $instance); - } - - return parent::resolveFacadeInstance($name); - } - - /** - * If the accessor is not found via getFacadeAccessor, use this instance as a fallback. - * - * @return mixed - */ - protected static function getFacadeInstance() - { - return null; - } } diff --git a/src/Support/Facades/Form.php b/src/Support/Facades/Form.php index f7c18871..10edf557 100644 --- a/src/Support/Facades/Form.php +++ b/src/Support/Facades/Form.php @@ -31,7 +31,7 @@ * @method static string getIdAttribute(string $name, array $attributes) * @method static string getValueAttribute(string $name, string $value = null) * @method static string old(string $name) - * @method static string bool oldInputIsEmpty() + * @method static bool oldInputIsEmpty() * @method static \Illuminate\Session\Store getSessionStore() * @method static \Winter\Storm\Html\FormBuilder setSessionStore(\Illuminate\Session\Store $session) * @method static string value(string $name, string $value = null) diff --git a/src/Support/Facades/Input.php b/src/Support/Facades/Input.php index f2a1e57b..1f997e25 100644 --- a/src/Support/Facades/Input.php +++ b/src/Support/Facades/Input.php @@ -2,6 +2,9 @@ use Winter\Storm\Support\Facade; +/** + * @see \Illuminate\Http\Request + */ class Input extends Facade { /** @@ -18,6 +21,18 @@ public static function get($key = null, $default = null) return static::$app['request']->input($key, $default); } + /** + * Gets all input data items. + * + * This method is used for all request verbs (GET, POST, PUT, and DELETE) + * + * @return array|null + */ + public static function all() + { + return static::$app['request']->input(); + } + /** * Get the registered name of the component. * diff --git a/src/Support/Facades/Schema.php b/src/Support/Facades/Schema.php index 6a4de871..b3c38ce2 100644 --- a/src/Support/Facades/Schema.php +++ b/src/Support/Facades/Schema.php @@ -45,7 +45,7 @@ public static function connection($name) /** * Get a schema builder instance for the default connection. * - * @return \Illuminate\Database\Schema\Builder + * @return string */ protected static function getFacadeAccessor() { diff --git a/src/Support/ModuleServiceProvider.php b/src/Support/ModuleServiceProvider.php index 318ec396..3cc8ca60 100644 --- a/src/Support/ModuleServiceProvider.php +++ b/src/Support/ModuleServiceProvider.php @@ -46,8 +46,8 @@ public function getModule($args) /** * Registers a new console (artisan) command - * @param $key The command name - * @param $class The command class + * @param string $key The command name + * @param string $class The command class * @return void */ public function registerConsoleCommand($key, $class) diff --git a/src/Support/Serialization.php b/src/Support/Serialization.php index 45422571..54587bc7 100644 --- a/src/Support/Serialization.php +++ b/src/Support/Serialization.php @@ -12,11 +12,11 @@ class Serialization * Wraps a closure in a SerializableClosure, returns the provided object if it's not a closure. * * @param Closure|mixed $callable provided callable to be wrapped if it's a closure - * @return mixed|SerializableClosure + * @return SerializableClosure|mixed */ public static function wrapClosure($callable) { - if ($callable instanceof Closure && !($callable instanceof SerializableClosure)) { + if ($callable instanceof Closure) { $callable = new SerializableClosure($callable); } return $callable; diff --git a/src/Support/Testing/Fakes/MailFake.php b/src/Support/Testing/Fakes/MailFake.php index ddfee476..cb206fbc 100644 --- a/src/Support/Testing/Fakes/MailFake.php +++ b/src/Support/Testing/Fakes/MailFake.php @@ -35,7 +35,7 @@ protected function queuedMailablesOf($type) /** * Send a new message using a view. * - * @param string|array $view + * @param Mailable|string|array $view * @param array $data * @param \Closure|string $callback * @return void @@ -52,19 +52,19 @@ public function send($view, $data = [], $callback = null) /** * Queue a new e-mail message for sending. * - * @param string|array $view + * @param Mailable|string|array $view + * @param string|null $queue * @param array $data * @param \Closure|string $callback - * @param string|null $queue * @return mixed */ - public function queue($view, $data = null, $callback = null, $queue = null) + public function queue($view, $queue = null, $data = null, $callback = null) { if (!$view instanceof Mailable) { $view = $this->buildMailable($view, $data, $callback, true); } - return parent::queue($view, $data = null, $callback = null, $queue = null); + return parent::queue($view, $queue = null); } /** diff --git a/src/Support/helpers-array.php b/src/Support/helpers-array.php index 78efc93d..e07c42f0 100644 --- a/src/Support/helpers-array.php +++ b/src/Support/helpers-array.php @@ -152,7 +152,7 @@ function array_last($array, callable $callback = null, $default = null) * Flatten a multi-dimensional array into a single level. * * @param array $array - * @param int $depth + * @param float|int $depth * @return array */ function array_flatten($array, $depth = INF) diff --git a/src/Support/helpers.php b/src/Support/helpers.php index d96964aa..354d6084 100644 --- a/src/Support/helpers.php +++ b/src/Support/helpers.php @@ -116,7 +116,7 @@ function post($name = null, $default = null) function input($name = null, $default = null) { if ($name === null) { - return Input::all(); + return \Winter\Storm\Support\Facades\Input::all(); } /* @@ -126,21 +126,18 @@ function input($name = null, $default = null) $name = implode('.', Winter\Storm\Html\Helper::nameToArray($name)); } - return Input::get($name, $default); + return \Winter\Storm\Support\Facades\Input::get($name, $default); } } if (!function_exists('trace_log')) { /** * Writes a trace message to a log file. - * @param mixed $message Specifies a message to log. The message can be an object, array or string. - * @param string $level Specifies a level to use. If this parameter is omitted, the default listener will be used (info). + * @param Exception|array|object|string... $messages * @return void */ - function trace_log() + function trace_log(...$messages) { - $messages = func_get_args(); - foreach ($messages as $message) { $level = 'info'; @@ -159,11 +156,12 @@ function trace_log() if (!function_exists('traceLog')) { /** * Alias for trace_log() + * @param Exception|array|object|string... $messages * @return void */ - function traceLog() + function traceLog(...$messages) { - call_user_func_array('trace_log', func_get_args()); + call_user_func_array('trace_log', $messages); } } diff --git a/src/Support/polyfills.php b/src/Support/polyfills.php index 58f564b2..67b85bb6 100644 --- a/src/Support/polyfills.php +++ b/src/Support/polyfills.php @@ -21,7 +21,7 @@ function str_contains($haystack, $needles) /** * Polyfill for `is_countable` method provided in PHP >= 7.3 * - * @param mixed $var + * @param mixed $value * @return boolean */ function is_countable($value) diff --git a/src/Validation/Concerns/ValidatesEmail.php b/src/Validation/Concerns/ValidatesEmail.php index 8487c9a6..1f4c5ffc 100644 --- a/src/Validation/Concerns/ValidatesEmail.php +++ b/src/Validation/Concerns/ValidatesEmail.php @@ -5,7 +5,7 @@ use Egulias\EmailValidator\Validation\MultipleValidationWithAnd; use Egulias\EmailValidator\Validation\NoRFCWarningsValidation; use Egulias\EmailValidator\Validation\RFCValidation; -use Egulias\EmailValidator\Validation\SpoofCheckValidation; +use Egulias\EmailValidator\Validation\Extra\SpoofCheckValidation; use Illuminate\Validation\Concerns\FilterEmailValidation; trait ValidatesEmail diff --git a/src/Validation/Factory.php b/src/Validation/Factory.php index e1ae2b11..eda200dc 100644 --- a/src/Validation/Factory.php +++ b/src/Validation/Factory.php @@ -10,6 +10,13 @@ */ class Factory extends BaseFactory implements FactoryContract { + /** + * The Validator resolver instance. + * + * @var \Closure|null + */ + protected $resolver; + /** * Resolve a new Validator instance. * diff --git a/src/Validation/Validator.php b/src/Validation/Validator.php index b26b0eee..c6d175a9 100644 --- a/src/Validation/Validator.php +++ b/src/Validation/Validator.php @@ -13,7 +13,7 @@ */ class Validator extends BaseValidator implements ValidatorContract { - use \Winter\Storm\Validation\Concerns\ValidatesEmail; + use Concerns\ValidatesEmail; use Concerns\FormatsMessages; /** From 4cf0199a82e296c8799db0171ca0f7b995af5285 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 24 Feb 2022 10:49:00 +0800 Subject: [PATCH 033/108] Fix Router PHPStan issues --- src/Router/CoreRouter.php | 2 +- src/Router/Helper.php | 14 +++++--------- src/Router/Router.php | 15 ++++++++------- src/Router/Rule.php | 22 +++++++++++++--------- src/Router/UrlGenerator.php | 7 ++++--- 5 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/Router/CoreRouter.php b/src/Router/CoreRouter.php index 59a402cf..bbc905a7 100644 --- a/src/Router/CoreRouter.php +++ b/src/Router/CoreRouter.php @@ -9,7 +9,7 @@ class CoreRouter extends RouterBase * Dispatch the request to the application. * * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse + * @return \Symfony\Component\HttpFoundation\Response */ public function dispatch(Request $request) { diff --git a/src/Router/Helper.php b/src/Router/Helper.php index 2b636bce..a610657c 100644 --- a/src/Router/Helper.php +++ b/src/Router/Helper.php @@ -72,7 +72,7 @@ public static function rebuildUrl(array $urlArray) /** * Replaces :column_name with it's object value. Example: /some/link/:id/:name -> /some/link/1/Joe * - * @param stdObject $object Object containing the data + * @param object|array $object Object containing the data * @param array $columns Expected key names to parse * @param string $string URL template * @return string Built string @@ -102,7 +102,7 @@ public static function parseValues($object, array $columns, $string) /** * Replaces :column_name with object value without requiring a list of names. Example: /some/link/:id/:name -> /some/link/1/Joe * - * @param stdObject $object Object containing the data + * @param object|array $object Object containing the data * @param string $string URL template * @return string Built string */ @@ -144,11 +144,7 @@ public static function segmentIsOptional($segment) return true; } - if ($optMarkerPos !== false && $regexMarkerPos !== false) { - return $optMarkerPos < $regexMarkerPos; - } - - return false; + return $optMarkerPos < $regexMarkerPos; } /** @@ -194,7 +190,7 @@ public static function getParameterName($segment) /** * Extracts the regular expression from a URL pattern segment definition. * @param string $segment The segment definition. - * @return string Returns the regular expression string or false if the expression is not defined. + * @return string|false Returns the regular expression string or false if the expression is not defined. */ public static function getSegmentRegExp($segment) { @@ -213,7 +209,7 @@ public static function getSegmentRegExp($segment) /** * Extracts the default parameter value from a URL pattern segment definition. * @param string $segment The segment definition. - * @return string Returns the default value if it is provided. Returns false otherwise. + * @return string|false Returns the default value if it is provided. Returns false otherwise. */ public static function getSegmentDefaultValue($segment) { diff --git a/src/Router/Router.php b/src/Router/Router.php index b3fd5fd0..de4b9e31 100644 --- a/src/Router/Router.php +++ b/src/Router/Router.php @@ -20,7 +20,7 @@ class Router protected $routeMap = []; /** - * @var \Winter\Storm\Router\Rule A referred to the matched router rule + * @var \Winter\Storm\Router\Rule|null A referred to the matched router rule */ protected $matchedRouteRule; @@ -41,7 +41,7 @@ public function route($name, $route) * Match given URL string * * @param string $url Request URL to match for - * @return array $parameters A reference to a PHP array variable to return the parameter list fetched from URL. + * @return bool */ public function match($url) { @@ -73,9 +73,9 @@ public function match($url) } // Success - if ($this->matchedRouteRule) { + if (!is_null($this->matchedRouteRule)) { // If this route has a match callback, run it - $matchCallback = $routeRule->afterMatch(); + $matchCallback = $this->matchedRouteRule->afterMatch(); if ($matchCallback !== null) { $parameters = call_user_func($matchCallback, $parameters, $url); } @@ -83,7 +83,7 @@ public function match($url) $this->parameters = $parameters; - return $this->matchedRouteRule ? true : false; + return !is_null($this->matchedRouteRule); } /** @@ -91,7 +91,7 @@ public function match($url) * * @param string $name Name of the route previously defined. * @param array $parameters Parameter name => value items to fill in for given route. - * @return string Full matched URL as string with given values put in place of named parameters + * @return string|null Full matched URL as string with given values put in place of named parameters. Returns `null` if no route map is specified. */ public function url($name, $parameters = []) { @@ -224,7 +224,8 @@ public function getParameters() /** * Returns the matched route rule name. - * @return \Winter\Storm\Router\Rule The matched rule object. + * + * @return \Winter\Storm\Router\Rule|false The matched rule object. If no rule was matched, returns `false`. */ public function matchedRoute() { diff --git a/src/Router/Rule.php b/src/Router/Rule.php index 0c46681a..39519849 100644 --- a/src/Router/Rule.php +++ b/src/Router/Rule.php @@ -20,12 +20,12 @@ class Rule protected $rulePattern; /** - * @var function Custom condition used when matching this rule. + * @var callable Custom condition used when matching this rule. */ protected $conditionCallback; /** - * @var function Called when this rule is matched. + * @var callable Called when this rule is matched. */ protected $afterMatchCallback; @@ -197,7 +197,7 @@ public function resolveUrl($url, &$parameters) /* * Determine if wildcard and add stored parameters as a suffix */ - if (Helper::segmentIsWildcard($patternSegment) && count($wildSegments)) { + if (Helper::segmentIsWildcard($patternSegment) && isset($wildSegments) && count($wildSegments)) { $parameters[$paramName] .= Helper::rebuildUrl($wildSegments); } } @@ -248,8 +248,10 @@ protected function captureWildcardSegments(&$urlSegments) /** * Unique route name * + * This is a getter and setter method. + * * @param string $name Unique name for the router object - * @return object Self + * @return object|string */ public function name($name = null) { @@ -265,8 +267,10 @@ public function name($name = null) /** * Route match pattern * + * This is a getter and setter method. + * * @param string $pattern Pattern used to match this rule - * @return object Self + * @return object|string */ public function pattern($pattern = null) { @@ -282,9 +286,9 @@ public function pattern($pattern = null) /** * Condition callback * - * @param callback $callback Callback function to be used when providing custom route match conditions + * @param callable $callback Callback function to be used when providing custom route match conditions * @throws InvalidArgumentException When supplied argument is not a valid callback - * @return callback + * @return callable */ public function condition($callback = null) { @@ -307,9 +311,9 @@ public function condition($callback = null) /** * After match callback * - * @param callback $callback Callback function to be used to modify params after a successful match + * @param callable $callback Callback function to be used to modify params after a successful match * @throws InvalidArgumentException When supplied argument is not a valid callback - * @return callback + * @return callable */ public function afterMatch($callback = null) { diff --git a/src/Router/UrlGenerator.php b/src/Router/UrlGenerator.php index b86ac47a..bb58154b 100644 --- a/src/Router/UrlGenerator.php +++ b/src/Router/UrlGenerator.php @@ -265,9 +265,10 @@ public static function buildUrl($url, $replace = [], $flags = HTTP_URL_REPLACE, // Populate the query section if (isset($url['query']) && $url['query'] !== '') { + $queryParams = []; + if (is_string($url['query'])) { - $queryParams = []; - $pairs = explode(ini_get('arg_separator.output') ?? '&', $url['query']); + $pairs = explode(ini_get('arg_separator.output') ?: '&', $url['query']); foreach ($pairs as $pair) { $key = Str::before($pair, '='); $value = Str::after($pair, '='); @@ -318,7 +319,7 @@ public static function buildUrl($url, $replace = [], $flags = HTTP_URL_REPLACE, public static function buildStr(array $query, string $prefix = '', $argSeparator = null): string { if (is_null($argSeparator)) { - $argSeparator = ini_get('arg_separator.output') ?? '&'; + $argSeparator = ini_get('arg_separator.output') ?: '&'; } $result = []; From 3fe31dff6e94cf6729ce33bd019204db6f2d4ebd Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 24 Feb 2022 13:56:31 +0800 Subject: [PATCH 034/108] Add Event facade methods --- src/Support/Facades/Event.php | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Support/Facades/Event.php b/src/Support/Facades/Event.php index 2dd5d437..1546e7a0 100755 --- a/src/Support/Facades/Event.php +++ b/src/Support/Facades/Event.php @@ -5,8 +5,26 @@ use Winter\Storm\Support\Testing\Fakes\EventFake; /** - * @see \Illuminate\Support\Facades\Event - * @see \Winter\Storm\Support\Testing\Fakes\EventFake + * @method static \Closure createClassListener(string $listener, bool $wildcard = false) + * @method static \Closure makeListener(\Closure|string $listener, bool $wildcard = false) + * @method static \Illuminate\Events\Dispatcher setQueueResolver(callable $resolver) + * @method static array getListeners(string $eventName) + * @method static array|null dispatch(string|object $event, mixed $payload = [], bool $halt = false) + * @method static array|null until(string|object $event, mixed $payload = []) + * @method static bool hasListeners(string $eventName) + * @method static void assertDispatched(string|\Closure $event, callable|int $callback = null) + * @method static void assertDispatchedTimes(string $event, int $times = 1) + * @method static void assertNotDispatched(string|\Closure $event, callable|int $callback = null) + * @method static void assertNothingDispatched() + * @method static void assertListening(string $expectedEvent, string $expectedListener) + * @method static void flush(string $event) + * @method static void forget(string $event) + * @method static void forgetPushed() + * @method static void listen(\Closure|string|array $events, \Closure|string|array $listener = null) + * @method static void push(string $event, array $payload = []) + * @method static void subscribe(object|string $subscriber) + * + * @see \Winter\Storm\Events\Dispatcher */ class Event extends \Illuminate\Support\Facades\Event { From ff29e6ac0b49bf28a7096de161c0fde9b5dff024 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 24 Feb 2022 13:56:37 +0800 Subject: [PATCH 035/108] Fix Parse PHPStan issues --- .../Assetic/Filter/JavascriptImporter.php | 11 +-- src/Parse/Bracket.php | 11 +-- src/Parse/Ini.php | 4 +- src/Parse/Markdown.php | 69 +++++++++++-------- src/Parse/Syntax/FieldParser.php | 26 +++---- src/Parse/Syntax/Parser.php | 31 +++++---- src/Parse/Yaml.php | 18 +++-- 7 files changed, 94 insertions(+), 76 deletions(-) diff --git a/src/Parse/Assetic/Filter/JavascriptImporter.php b/src/Parse/Assetic/Filter/JavascriptImporter.php index 56fff306..bf5754a3 100644 --- a/src/Parse/Assetic/Filter/JavascriptImporter.php +++ b/src/Parse/Assetic/Filter/JavascriptImporter.php @@ -3,7 +3,7 @@ use File; use RuntimeException; use Assetic\Filter\BaseFilter; -use Assetic\Asset\AssetInterface; +use Assetic\Contracts\Asset\AssetInterface; /** * Importer JS Filter @@ -40,6 +40,9 @@ class JavascriptImporter extends BaseFilter */ protected $definedVars = []; + /** + * @inheritDoc + */ public function filterDump(AssetInterface $asset) { $this->scriptPath = dirname($asset->getSourceRoot() . '/' . $asset->getSourcePath()); @@ -49,13 +52,13 @@ public function filterDump(AssetInterface $asset) } /** - * Process JS imports inside a string of javascript - * @param $content string JS code to process. + * Process JS imports inside a string of JavaScript + * + * @param string $content JS code to process. * @return string Processed JS. */ protected function parse($content) { - $macros = []; $imported = ''; // Look for: /* comments */ diff --git a/src/Parse/Bracket.php b/src/Parse/Bracket.php index c4a05d76..951d8f7e 100644 --- a/src/Parse/Bracket.php +++ b/src/Parse/Bracket.php @@ -19,7 +19,7 @@ class Bracket 'filters' => [] ]; - public function __construct($options = []) + final public function __construct($options = []) { $this->setOptions($options); } @@ -34,7 +34,7 @@ public function setOptions($options = []) * @param string $template * @param array $vars * @param array $options - * @return self + * @return string */ public static function parse($template, $vars = [], $options = []) { @@ -43,15 +43,16 @@ public static function parse($template, $vars = [], $options = []) } /** - * Parse a string against data + * Parse a string against data. + * * @param string $string * @param array $data * @return string */ public function parseString($string, $data) { - if (!is_string($string) || !strlen(trim($string))) { - return false; + if (!strlen(trim($string))) { + return ''; } foreach ($data as $key => $value) { diff --git a/src/Parse/Ini.php b/src/Parse/Ini.php index 60e15bba..82e112de 100644 --- a/src/Parse/Ini.php +++ b/src/Parse/Ini.php @@ -110,11 +110,11 @@ protected function parsePostProcess($array) * Expands a single array property from traditional INI syntax. * If no key is given to the method, the entire array will be replaced. * @param array $array - * @param string $key + * @param string|null $key * @param mixed $value * @return array */ - public function expandProperty(&$array, $key, $value) + public function expandProperty(array &$array, $key = null, $value = null) { if (is_null($key)) { return $array = $value; diff --git a/src/Parse/Markdown.php b/src/Parse/Markdown.php index 7e31f5e1..f621a6ac 100644 --- a/src/Parse/Markdown.php +++ b/src/Parse/Markdown.php @@ -1,6 +1,6 @@ parserClass; + } + + /** + * Sets the Markdown parser. + * + * @param Parsedown $parser + * @return void + */ + public function setParser(Parsedown $parser) + { + $this->parserClass = $parser; + } /** * Parse text using Markdown and Markdown-Extra - * @param string $text Markdown text to parse - * @return string Resulting HTML + * @param string $text Markdown text to parse + * @return string Resulting HTML */ public function parse($text) { @@ -52,13 +75,9 @@ public function parse($text) */ public function parseClean($text) { - $this->getParser()->setSafeMode(true); - - $result = $this->parse($text); - - $this->parser = null; + $parser = $this->getParser()->setSafeMode(true); - return $result; + return $this->parseInternal($text, 'text', $parser); } /** @@ -68,13 +87,9 @@ public function parseClean($text) */ public function parseSafe($text) { - $this->getParser()->setUnmarkedBlockTypes([]); + $parser = $this->getParser()->setUnmarkedBlockTypes([]); - $result = $this->parse($text); - - $this->parser = null; - - return $result; + return $this->parseInternal($text, 'text', $parser); } /** @@ -90,16 +105,19 @@ public function parseLine($text) /** * Internal method for parsing */ - protected function parseInternal($text, $method = 'text') + protected function parseInternal($text, $method = 'text', Parsedown $parser = null) { + if (is_null($parser)) { + $parser = $this->getParser(); + } $data = new MarkdownData($text); - $this->fireEvent('beforeParse', $data, false); - Event::fire('markdown.beforeParse', $data, false); + $this->fireEvent('beforeParse', [$data], false); + Event::fire('markdown.beforeParse', [$data], false); $result = $data->text; - $result = $this->getParser()->$method($result); + $result = $parser->$method($result); $data->text = $result; @@ -110,13 +128,4 @@ protected function parseInternal($text, $method = 'text') return $data->text; } - - protected function getParser() - { - if ($this->parser === null) { - $this->parser = new Parsedown; - } - - return $this->parser; - } } diff --git a/src/Parse/Syntax/FieldParser.php b/src/Parse/Syntax/FieldParser.php index 9b389c81..38362857 100644 --- a/src/Parse/Syntax/FieldParser.php +++ b/src/Parse/Syntax/FieldParser.php @@ -59,21 +59,23 @@ class FieldParser ]; /** - * Constructor + * Constructor. + * * @param string $template Template to parse. + * @param array $options */ - public function __construct($template = null, $options = []) + final public function __construct($template, $options = []) { - if ($template) { - $this->tagPrefix = array_get($options, 'tagPrefix', ''); - $this->template = $template; - $this->processTemplate($template); - } + $this->tagPrefix = array_get($options, 'tagPrefix', ''); + $this->template = $template; + $this->processTemplate($template); } /** * Processes repeating tags first, then registered tags and assigns * the results to local object properties. + * + * @param string $template * @return void */ protected function processTemplate($template) @@ -103,7 +105,7 @@ protected function processTemplate($template) * Static helper for new instances of this class. * @param string $template * @param array $options - * @return FieldParser + * @return static */ public static function parse($template, $options = []) { @@ -181,7 +183,7 @@ public function getDefaultParams($fields = null) * Processes all repeating tags against a template, this will strip * any repeaters from the template for further processing. * @param string $template - * @return void + * @return array */ protected function processRepeaterTags($template) { @@ -213,8 +215,8 @@ protected function processRepeaterTags($template) /** * Processes all registered tags against a template. * @param string $template - * @param bool $usingTags - * @return void + * @param array $usingTags + * @return array */ protected function processTags($template, $usingTags = null) { @@ -375,7 +377,7 @@ protected function processParamsRegex($string) * 2 - The default text inside the tag (optional), eg: Foobar * * @param string $string - * @param string $tags + * @param array $tags * @return array */ protected function processTagsRegex($string, $tags) diff --git a/src/Parse/Syntax/Parser.php b/src/Parse/Syntax/Parser.php index ab842f17..6a9509f0 100644 --- a/src/Parse/Syntax/Parser.php +++ b/src/Parse/Syntax/Parser.php @@ -10,6 +10,11 @@ class Parser const CHAR_OPEN = '{'; const CHAR_CLOSE = '}'; + /** + * @var string The template content to parse. + */ + protected $template = ''; + /** * @var \Winter\Storm\Parse\Syntax\FieldParser Field parser instance. */ @@ -28,33 +33,33 @@ class Parser /** * Constructor. + * * Available options: * - varPrefix: Prefix to add to every top level parameter. * - tagPrefix: Prefix to add to all tags, in addition to tags without a prefix. - * @param array $options + * * @param string $template Template to parse. + * @param array $options */ - public function __construct($template = null, $options = []) + final public function __construct($template, $options = []) { - if ($template) { - $this->template = $template; - $this->varPrefix = array_get($options, 'varPrefix', ''); - $this->fieldParser = new FieldParser($template, $options); + $this->template = $template; + $this->varPrefix = array_get($options, 'varPrefix', ''); + $this->fieldParser = new FieldParser($template, $options); - $textFilters = [ - 'md' => ['Markdown', 'parse'], - 'media' => ['System\Classes\MediaLibrary', 'url'] - ]; + $textFilters = [ + 'md' => ['Markdown', 'parse'], + 'media' => ['System\Classes\MediaLibrary', 'url'] + ]; - $this->textParser = new TextParser(['filters' => $textFilters]); - } + $this->textParser = new TextParser(['filters' => $textFilters]); } /** * Static helper for new instances of this class. * @param string $template * @param array $options - * @return self + * @return static */ public static function parse($template, $options = []) { diff --git a/src/Parse/Yaml.php b/src/Parse/Yaml.php index a8ae88e8..e7b359db 100644 --- a/src/Parse/Yaml.php +++ b/src/Parse/Yaml.php @@ -1,12 +1,12 @@ 20, - 'exceptionOnInvalidType' => false, - 'objectSupport' => true, - ], $options)); + $inline = (int) ($options['inline'] ?? 20); + $exceptionOnInvalidType = (bool) ($options['exceptionOnInvalidType'] ?? false); + $objectSupport = (bool) ($options['objectSupport'] ?? true); $flags = null; - if ($exceptionOnInvalidType) { + if ($exceptionOnInvalidType === true) { $flags |= YamlComponent::DUMP_EXCEPTION_ON_INVALID_TYPE; } - if ($objectSupport) { + if ($objectSupport === true) { $flags |= YamlComponent::DUMP_OBJECT; } From 6ea0cf925b15012f21feff39d9ddcc6cb66284ce Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 24 Feb 2022 14:07:33 +0800 Subject: [PATCH 036/108] Fix Network PHPStan issues --- src/Network/Http.php | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Network/Http.php b/src/Network/Http.php index e5cb3a37..a58bd9d2 100644 --- a/src/Network/Http.php +++ b/src/Network/Http.php @@ -94,7 +94,7 @@ class Http public $rawBody = ''; /** - * @var array The last returned HTTP code. + * @var int The last returned HTTP code. */ public $code; @@ -331,6 +331,18 @@ public function send() stream_filter_append($stream, $this->streamFilter, STREAM_FILTER_WRITE); } + if ($headerStream === false) { + throw new ApplicationException('Unable to create a temporary header stream'); + } + if ($stream === false) { + throw new ApplicationException( + sprintf( + 'Unable to stream file contents from HTTP response to "%s". Please check your permissions.', + $this->streamFile + ) + ); + } + curl_setopt($curl, CURLOPT_HEADER, false); curl_setopt($curl, CURLOPT_WRITEHEADER, $headerStream); curl_setopt($curl, CURLOPT_FILE, $stream); @@ -356,7 +368,7 @@ public function send() */ curl_close($curl); - if ($this->streamFile) { + if ($this->streamFile && !empty($stream) && !empty($headerStream)) { rewind($headerStream); $this->headers = $this->headerToArray(stream_get_contents($headerStream)); fclose($headerStream); @@ -556,9 +568,13 @@ public function toFile($path, $filter = null) } /** - * Add a single option to the request. - * @param string $option - * @param string $value + * Add single or multiple CURL options to this request. + * + * You must either provide a constant or string that represents a CURL_* constant as the $option, + * and a $value to set a single option, or you may provide an array of CURL_* constants and values instead. + * + * @param array|string|int $option + * @param mixed $value * @return self */ public function setOption($option, $value = null) From 0899bb892a4188fbc812be6e24037d4a1dc8b8be Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 24 Feb 2022 15:27:58 +0800 Subject: [PATCH 037/108] Fix return type of fired events --- src/Events/Dispatcher.php | 3 +-- src/Support/Facades/Event.php | 5 ++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Events/Dispatcher.php b/src/Events/Dispatcher.php index 06ca767e..78860682 100644 --- a/src/Events/Dispatcher.php +++ b/src/Events/Dispatcher.php @@ -1,7 +1,6 @@ Date: Thu, 24 Feb 2022 15:28:10 +0800 Subject: [PATCH 038/108] Fix minor issue in MailManager --- src/Mail/MailManager.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mail/MailManager.php b/src/Mail/MailManager.php index 5626e08b..145a820a 100644 --- a/src/Mail/MailManager.php +++ b/src/Mail/MailManager.php @@ -20,6 +20,7 @@ class MailManager extends BaseMailManager */ protected function resolve($name) { + /** @var array|null */ $config = $this->getConfig($name); if (is_null($config)) { From f87d5943606f92b50ffb0ef9310eec18c587440f Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 24 Feb 2022 15:28:48 +0800 Subject: [PATCH 039/108] WIP Mailer fixes --- src/Mail/Mailer.php | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/Mail/Mailer.php b/src/Mail/Mailer.php index 39cd692b..86971ec3 100644 --- a/src/Mail/Mailer.php +++ b/src/Mail/Mailer.php @@ -1,9 +1,10 @@ fireEvent('mailer.beforeSend', [$view, $data, $callback], true) === false) || (Event::fire('mailer.beforeSend', [$view, $data, $callback], true) === false) ) { - return; + return null; } if ($view instanceof MailableContract) { @@ -141,7 +142,7 @@ public function send($view, array $data = [], $callback = null) ($this->fireEvent('mailer.prepareSend', [$view, $message, $data], true) === false) || (Event::fire('mailer.prepareSend', [$this, $view, $message, $data], true) === false) ) { - return; + return null; } // Next we will determine if the message should be sent. We give the developer @@ -153,29 +154,29 @@ public function send($view, array $data = [], $callback = null) if ($this->shouldSendMessage($symfonyMessage, $data)) { $sentMessage = $this->sendSymfonyMessage($symfonyMessage); - $this->dispatchSentEvent($message, $data); - $sentMessage = new SentMessage($sentMessage); + $this->dispatchSentEvent($sentMessage, $data); + /** * @event mailer.send * Fires after the message has been sent * * Example usage (logs the message): * - * Event::listen('mailer.send', function ((\Winter\Storm\Mail\Mailer) $mailerInstance, (string) $view, (\Illuminate\Mail\Message) $message, (array) $data) { + * Event::listen('mailer.send', function ((\Winter\Storm\Mail\Mailer) $mailerInstance, (string) $view, (\Illuminate\Mail\SentMessage) $message, (array) $data) { * \Log::info("Message was rendered with $view and sent"); * }); * * Or * - * $mailerInstance->bindEvent('mailer.send', function ((string) $view, (\Illuminate\Mail\Message) $message, (array) $data) { + * $mailerInstance->bindEvent('mailer.send', function ((string) $view, (\Illuminate\Mail\SentMessage) $message, (array) $data) { * \Log::info("Message was rendered with $view and sent"); * }); * */ - $this->fireEvent('mailer.send', [$view, $message, $data]); - Event::fire('mailer.send', [$this, $view, $message, $data]); + $this->fireEvent('mailer.send', [$view, $sentMessage, $data]); + Event::fire('mailer.send', [$this, $view, $sentMessage, $data]); } return $sentMessage; @@ -190,13 +191,13 @@ public function send($view, array $data = [], $callback = null) * - Support for the Winter MailParser * * @param \Illuminate\Mail\Message $message - * @param string $view - * @param string $plain - * @param string $raw - * @param array $data + * @param string|null $view + * @param string|null $plain + * @param string|null $raw + * @param array|null $data * @return void */ - protected function addContent($message, $view, $plain, $raw, $data) + protected function addContent($message, $view = null, $plain = null, $raw = null, $data = null) { /** * @event mailer.beforeAddContent @@ -281,11 +282,11 @@ protected function addContent($message, $view, $plain, $raw, $data) * Add the raw content to the provided message. * * @param \Illuminate\Mail\Message $message - * @param string $html - * @param string $text + * @param string|null $html + * @param string|null $text * @return void */ - protected function addContentRaw($message, $html, $text) + protected function addContentRaw($message, $html = null, $text = null) { if (isset($html)) { $message->html($html); From 2ef32346682c768a9d457c4dba6796f34b644eab Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 24 Feb 2022 15:59:32 +0800 Subject: [PATCH 040/108] Fix HTML PHPStand issues --- src/Html/FormBuilder.php | 58 +++++++++++++++++++++++++--------------- src/Html/Helper.php | 4 +-- src/Html/HtmlBuilder.php | 22 +++++++-------- 3 files changed, 47 insertions(+), 37 deletions(-) diff --git a/src/Html/FormBuilder.php b/src/Html/FormBuilder.php index 5e48bb6f..3392ba77 100644 --- a/src/Html/FormBuilder.php +++ b/src/Html/FormBuilder.php @@ -30,7 +30,7 @@ class FormBuilder /** * The session store implementation. */ - protected \Illuminate\Session\Store $session; + protected ?\Illuminate\Session\Store $session; /** * The current model instance for the form. @@ -258,30 +258,40 @@ protected function formatLabel(string $name, string $value = ''): string /** * Create a form input field. + * + * @param string $type + * @param string|null $name + * @param string|null $value + * @param array $options + * @return string */ - public function input(string $type, string $name, ?string $value = null, array $options = []): string + public function input(string $type, ?string $name = null, ?string $value = null, array $options = []): string { if (!isset($options['name'])) { $options['name'] = $name; } - // We will get the appropriate value for the given field. We will look for the - // value in the session for the value in the old input data then we'll look - // in the model instance if one is set. Otherwise we will just use empty. - $id = $this->getIdAttribute($name, $options); + if (!empty($name)) { + // We will get the appropriate value for the given field. We will look for the + // value in the session for the value in the old input data then we'll look + // in the model instance if one is set. Otherwise we will just use empty. + $id = $this->getIdAttribute($name, $options); - if (!in_array($type, $this->skipValueTypes)) { - $value = $this->getValueAttribute($name, $value); - } + if (!in_array($type, $this->skipValueTypes)) { + $value = $this->getValueAttribute($name, $value); + } - // Once we have the type, value, and ID we can merge them into the rest of the - // attributes array so we can convert them into their HTML attribute format - // when creating the HTML element. Then, we will return the entire input. - $merge = compact('type', 'value', 'id'); + // Once we have the type, value, and ID we can merge them into the rest of the + // attributes array so we can convert them into their HTML attribute format + // when creating the HTML element. Then, we will return the entire input. + $merge = compact('type', 'value', 'id'); - $options = array_merge($options, $merge); + $options = array_filter(array_merge($options, $merge), function ($item) { + return !is_null($item); + }); + } - return 'html->attributes($options).'>'; + return 'html->attributes($options) . '>'; } /** @@ -818,7 +828,7 @@ protected function getAppendage($method) * * @param string $name * @param array $attributes - * @return string + * @return string|null */ public function getIdAttribute($name, $attributes) { @@ -829,18 +839,20 @@ public function getIdAttribute($name, $attributes) if (in_array($name, $this->labels)) { return $name; } + + return ''; } /** * Get the value that should be assigned to the field. * * @param string $name - * @param string $value - * @return string + * @param string|array $value + * @return string|array|null */ public function getValueAttribute($name, $value = null) { - if (is_null($name)) { + if (empty($name)) { return $value; } @@ -861,7 +873,7 @@ public function getValueAttribute($name, $value = null) * Get the model value that should be assigned to the field. * * @param string $name - * @return string + * @return string|array|null */ protected function getModelValueAttribute($name) { @@ -877,13 +889,15 @@ protected function getModelValueAttribute($name) * Get a value from the session's old input. * * @param string $name - * @return string + * @return string|array|null */ public function old($name) { if (isset($this->session)) { return $this->session->getOldInput($this->transformKey($name)); } + + return null; } /** @@ -939,7 +953,7 @@ public function setSessionStore(Session $session) */ public function value($name, $value = null) { - if (is_null($name)) { + if (empty($name)) { return $value; } diff --git a/src/Html/Helper.php b/src/Html/Helper.php index 8df245c3..b521b9b1 100644 --- a/src/Html/Helper.php +++ b/src/Html/Helper.php @@ -11,7 +11,7 @@ class Helper * Converts a HTML array string to an identifier string. * HTML: user[location][city] * Result: user-location-city - * @param $string String to process + * @param string $string String to process * @return string */ public static function nameToId($string) @@ -23,7 +23,7 @@ public static function nameToId($string) * Converts a HTML named array string to a PHP array. Empty values are removed. * HTML: user[location][city] * PHP: ['user', 'location', 'city'] - * @param $string String to process + * @param string $string String to process * @return array */ public static function nameToArray($string) diff --git a/src/Html/HtmlBuilder.php b/src/Html/HtmlBuilder.php index 0f93f880..6e5ef330 100644 --- a/src/Html/HtmlBuilder.php +++ b/src/Html/HtmlBuilder.php @@ -108,7 +108,7 @@ public function image($url, $alt = null, $attributes = [], $secure = null) * Generate a HTML link. * * @param string $url - * @param string $title + * @param string|false|null $title * @param array $attributes * @param bool $secure * @return string @@ -281,7 +281,7 @@ protected function listing($type, $list, $attributes = []) * * @param mixed $key * @param string $type - * @param string $value + * @param string|array $value * @return string */ protected function listingElement($key, $type, $value) @@ -338,17 +338,17 @@ public function attributes($attributes) * Build a single attribute element. * * @param string $key - * @param string $value - * @return string|void + * @param string|array|null $value + * @return string|null */ - protected function attributeElement($key, $value) + protected function attributeElement($key, $value = null) { if (is_numeric($key)) { $key = $value; } if (is_null($value)) { - return; + return null; } if (is_array($value)) { @@ -395,7 +395,7 @@ public function obfuscate($value) /** * Removes HTML from a string - * @param $string String to strip HTML from + * @param string $string String to strip HTML from * @return string */ public static function strip($string) @@ -412,15 +412,11 @@ public static function strip($string) */ public static function limit($html, $maxLength = 100, $end = '...') { - $isUtf8 = true; $printedLength = 0; $position = 0; $tags = []; - $regex = $isUtf8 - ? '{]*>|&#?[a-zA-Z0-9]+;|[\x80-\xFF][\x80-\xBF]*}' - : '{]*>|&#?[a-zA-Z0-9]+;}'; - + $regex = '{]*>|&#?[a-zA-Z0-9]+;|[\x80-\xFF][\x80-\xBF]*}'; $result = ''; while ($printedLength < $maxLength && preg_match($regex, $html, $match, PREG_OFFSET_CAPTURE, $position)) { @@ -447,7 +443,7 @@ public static function limit($html, $maxLength = 100, $end = '...') else { $tagName = $match[1][0]; if ($tag[1] == '/') { - $openingTag = array_pop($tags); + array_pop($tags); $result .= $tag; } elseif ($tag[strlen($tag) - 2] == '/') { From b679f20284619f468b0d8c91fe8f0f794757df1a Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 24 Feb 2022 16:25:28 +0800 Subject: [PATCH 041/108] Fix unit tests - Mail ones are not passing now, but pass on main branch --- src/Parse/Syntax/Parser.php | 2 +- src/Support/ClassLoader.php | 6 ++++-- tests/Html/BlockBuilderTest.php | 4 ++-- tests/Parse/SyntaxFieldParserTest.php | 6 +++--- tests/Support/ClassLoaderTest.php | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Parse/Syntax/Parser.php b/src/Parse/Syntax/Parser.php index 6a9509f0..61de6745 100644 --- a/src/Parse/Syntax/Parser.php +++ b/src/Parse/Syntax/Parser.php @@ -48,7 +48,7 @@ final public function __construct($template, $options = []) $this->fieldParser = new FieldParser($template, $options); $textFilters = [ - 'md' => ['Markdown', 'parse'], + 'md' => ['Winter\Storm\Parse\Markdown', 'parse'], 'media' => ['System\Classes\MediaLibrary', 'url'] ]; diff --git a/src/Support/ClassLoader.php b/src/Support/ClassLoader.php index 824f1738..b72429ae 100644 --- a/src/Support/ClassLoader.php +++ b/src/Support/ClassLoader.php @@ -210,7 +210,8 @@ public function register() $callback = [$this, 'load']; if (is_callable($callback)) { - $this->registered = spl_autoload_register(); + $this->registered = spl_autoload_register($callback); + return; } throw new Exception('The "load" method is missing from the class loader'); @@ -229,8 +230,9 @@ public function unregister() $callback = [$this, 'load']; if (is_callable($callback)) { - spl_autoload_unregister([$this, 'load']); + spl_autoload_unregister($callback); $this->registered = false; + return; } throw new Exception('The "load" method is missing from the class loader'); diff --git a/tests/Html/BlockBuilderTest.php b/tests/Html/BlockBuilderTest.php index 51e82a09..eede6e32 100644 --- a/tests/Html/BlockBuilderTest.php +++ b/tests/Html/BlockBuilderTest.php @@ -121,7 +121,7 @@ public function testPlaceholderBlock() . '', $this->Block->placeholder('test') ); - $this->assertNull($this->Block->get('test')); + $this->assertEquals('', $this->Block->get('test')); } public function testResetBlocks() @@ -137,7 +137,7 @@ public function testResetBlocks() $this->Block->reset(); - $this->assertNull($this->Block->get('test')); + $this->assertEquals('', $this->Block->get('test')); } public function testNestedBlocks() diff --git a/tests/Parse/SyntaxFieldParserTest.php b/tests/Parse/SyntaxFieldParserTest.php index 4f5915d2..a61808f9 100644 --- a/tests/Parse/SyntaxFieldParserTest.php +++ b/tests/Parse/SyntaxFieldParserTest.php @@ -256,13 +256,13 @@ public function testParseRepeater() public function testProcessTag() { - $parser = new FieldParser; $content = ''; $content .= '{text name="websiteName" label="Website Name" size="large"}{/text}'.PHP_EOL; $content .= '{text name="blogName" label="Blog Name" color="re\"d"}WinterCMS{/text}'.PHP_EOL; $content .= '{text name="storeName" label="Store Name" shape="circle"}{/text}'; $content .= '{text label="Unnamed" distance="400m"}Foobar{/text}'; $content .= '{foobar name="nullName" label="Valid tag, not searched by this test"}{/foobar}'; + $parser = new FieldParser($content); list($tags, $fields) = self::callProtectedMethod($parser, 'processTags', [$content]); $unnamedTag = md5('{text label="Unnamed" distance="400m"}Foobar{/text}'); @@ -328,11 +328,11 @@ public function testProcessTag() public function testProcessTagsRegex() { - $parser = new FieldParser; $content = ''; $content .= '{text name="websiteName" label="Website Name"}{/text}'.PHP_EOL; $content .= '{text name="blogName" label="Blog Name"}WinterCMS{/text}'.PHP_EOL; $content .= '{text name="storeName" label="Store Name"}{/text}'; + $parser = new FieldParser($content); $result = self::callProtectedMethod($parser, 'processTagsRegex', [$content, ['text']]); $this->assertArrayHasKey(0, $result[2]); @@ -346,8 +346,8 @@ public function testProcessTagsRegex() public function testProcessParamsRegex() { - $parser = new FieldParser; $content = 'name="test" comment="This is a test"'; + $parser = new FieldParser($content); $result = self::callProtectedMethod($parser, 'processParamsRegex', [$content]); $this->assertArrayHasKey(0, $result[1]); diff --git a/tests/Support/ClassLoaderTest.php b/tests/Support/ClassLoaderTest.php index 827c5f70..9f1ffb23 100644 --- a/tests/Support/ClassLoaderTest.php +++ b/tests/Support/ClassLoaderTest.php @@ -44,7 +44,7 @@ public function testAliases() ]); // Check that class identifies as both original and alias - $newInstance = new Winter\Plugin\Classes\TestClass; + $newInstance = new \Winter\Plugin\Classes\TestClass; $this->assertTrue($newInstance instanceof Winter\Plugin\Classes\TestClass); $this->assertTrue($newInstance instanceof OldOrg\Plugin\Classes\TestClass); From 3b7d4f29536774bf477dbd39334313bc401c4ec3 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 25 Feb 2022 09:01:33 +0800 Subject: [PATCH 042/108] Implement changes from Luke's review --- src/Extension/ExtendableTrait.php | 2 +- src/Filesystem/Definitions.php | 2 +- src/Filesystem/Filesystem.php | 3 --- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Extension/ExtendableTrait.php b/src/Extension/ExtendableTrait.php index 4caf38fe..507bc916 100644 --- a/src/Extension/ExtendableTrait.php +++ b/src/Extension/ExtendableTrait.php @@ -219,7 +219,7 @@ public function addDynamicProperty($dynamicName, $value = null) */ public function extendClassWith($extensionName) { - if (!strlen($extensionName)) { + if (empty($extensionName)) { throw new Exception(sprintf( 'You must provide an extension name to extend class %s with.', get_class($this) diff --git a/src/Filesystem/Definitions.php b/src/Filesystem/Definitions.php index c63982b9..934bb7f7 100644 --- a/src/Filesystem/Definitions.php +++ b/src/Filesystem/Definitions.php @@ -44,7 +44,7 @@ public function getDefinitions(string $type): array /** * Determines if a path should be ignored based on the ignoreFiles and ignorePatterns definitions. * - * Returns `true` if the path is visible, `false` otherwise. + * Returns `true` if the path is ignored, `false` otherwise. * * @todo Efficiency of this method can be improved. */ diff --git a/src/Filesystem/Filesystem.php b/src/Filesystem/Filesystem.php index d0a651fc..4b121547 100644 --- a/src/Filesystem/Filesystem.php +++ b/src/Filesystem/Filesystem.php @@ -145,9 +145,6 @@ public function isLocalPath(string $path, bool $realpath = true): bool /** * Determines if the given disk is using the "local" driver. - * - * @param \Illuminate\Filesystem\FilesystemAdapter $disk - * @return boolean */ public function isLocalDisk(\Illuminate\Filesystem\FilesystemAdapter $disk): bool { From 2370eaa247fb720dac03b6ca5bada4f9f8f908c5 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 25 Feb 2022 09:16:03 +0800 Subject: [PATCH 043/108] Finalise Mail PHPStan fixes --- phpstan.neon | 3 +++ src/Mail/Mailer.php | 12 +++++------- src/Support/Testing/Fakes/MailFake.php | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 36518d27..446d0dce 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -16,3 +16,6 @@ parameters: # Ignore incorrect docs from Laravel's Validation Factory - message: '#\$resolver is not covariant#' path: src/Validation/Factory.php + # Ignore incompatible signature for Mailer methods + - message: '#(queue|queueOn|later|laterOn)\(\) should be compatible#' + path: src/Mail/Mailer.php diff --git a/src/Mail/Mailer.php b/src/Mail/Mailer.php index 86971ec3..7748f852 100644 --- a/src/Mail/Mailer.php +++ b/src/Mail/Mailer.php @@ -300,7 +300,7 @@ protected function addContentRaw($message, $html = null, $text = null) /** * Queue a new e-mail message for sending. * - * @param string|array $view + * @param MailableContract|string|array $view * @param array $data * @param \Closure|string $callback * @param string|null $queue @@ -337,7 +337,7 @@ public function queueOn($queue, $view, $data = null, $callback = null) * Queue a new e-mail message for sending after (n) seconds. * * @param int $delay - * @param string|array $view + * @param MailableContract|string|array $view * @param array $data * @param \Closure|string $callback * @param string|null $queue @@ -397,7 +397,7 @@ protected function buildQueueMailable($view, $data, $callback, $queueName = null /** * Helper for raw() method, send a new message when only a raw text part. * @param array $recipients - * @param string $view + * @param array|string $view * @param mixed $callback * @param array $options * @return \Illuminate\Mail\SentMessage|null @@ -434,10 +434,8 @@ public function sendTo($recipients, $view, array $data = [], $callback = null, $ $queue = $options; $bcc = false; } else { - extract(array_merge([ - 'queue' => false, - 'bcc' => false - ], $options)); + $queue = (bool) ($options['queue'] ?? false); + $bcc = (bool) ($options['bcc'] ?? false); } $method = $queue === true ? 'queue' : 'send'; diff --git a/src/Support/Testing/Fakes/MailFake.php b/src/Support/Testing/Fakes/MailFake.php index cb206fbc..b9f909a4 100644 --- a/src/Support/Testing/Fakes/MailFake.php +++ b/src/Support/Testing/Fakes/MailFake.php @@ -53,12 +53,12 @@ public function send($view, $data = [], $callback = null) * Queue a new e-mail message for sending. * * @param Mailable|string|array $view - * @param string|null $queue * @param array $data * @param \Closure|string $callback + * @param string|null $queue * @return mixed */ - public function queue($view, $queue = null, $data = null, $callback = null) + public function queue($view, $data = null, $callback = null, $queue = null) { if (!$view instanceof Mailable) { $view = $this->buildMailable($view, $data, $callback, true); From 4f1db25dabc41eb477c1569603d37f76456dd11f Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 25 Feb 2022 11:57:40 +0800 Subject: [PATCH 044/108] Fix Command PHPStan issues --- src/Console/Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Console/Command.php b/src/Console/Command.php index f1b11718..5d73ad21 100644 --- a/src/Console/Command.php +++ b/src/Console/Command.php @@ -76,7 +76,7 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti $dataType = 'Argument'; $suggestionType = 'Values'; break; - case 'options': + default: $dataType = 'Option'; $suggestionType = 'Options'; break; From ab137a1e3b7047f58de22c3e73336070761ad7fa Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 25 Feb 2022 15:16:48 +0800 Subject: [PATCH 045/108] Tweak extendable trait --- src/Extension/ExtendableTrait.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Extension/ExtendableTrait.php b/src/Extension/ExtendableTrait.php index 507bc916..b9579dc4 100644 --- a/src/Extension/ExtendableTrait.php +++ b/src/Extension/ExtendableTrait.php @@ -193,8 +193,10 @@ public function addDynamicMethod($dynamicName, $method, $extension = null) /** * Programmatically adds a property to the extendable class - * @param string $dynamicName - * @param string $value + * + * @param string $dynamicName The name of the property to add + * @param mixed $value The value of the property + * @return void */ public function addDynamicProperty($dynamicName, $value = null) { From 7918a1396a047a570937335f0b2f998c8767789a Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 25 Feb 2022 15:17:06 +0800 Subject: [PATCH 046/108] WIP fixes of Database PHPStan issues --- phpstan.neon | 1 + src/Database/Attach/File.php | 101 ++++++++++-------- src/Database/Behaviors/Purgeable.php | 22 ++-- src/Database/Behaviors/Sortable.php | 1 + src/Database/Builder.php | 38 +++++-- src/Database/Concerns/HasRelationships.php | 14 ++- src/Database/Connections/MySqlConnection.php | 6 +- .../Connections/PostgresConnection.php | 6 +- src/Database/Connections/SQLiteConnection.php | 6 +- .../Connections/SqlServerConnection.php | 6 +- src/Database/Updater.php | 12 +-- 11 files changed, 124 insertions(+), 89 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 446d0dce..c6e11141 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,6 +7,7 @@ parameters: level: 5 excludePaths: - src/Auth/Manager.php + - src/Database/Behaviors/Purgeable.php databaseMigrationsPath: - src/Auth/Migrations - src/Database/Migrations diff --git a/src/Database/Attach/File.php b/src/Database/Attach/File.php index 8455e0bb..658a62f4 100644 --- a/src/Database/Attach/File.php +++ b/src/Database/Attach/File.php @@ -1,19 +1,24 @@ [], ]; /** - * @var array The attributes that are mass assignable. + * @var string[] The attributes that are mass assignable. */ protected $fillable = [ 'file_name', @@ -47,12 +52,12 @@ class File extends Model ]; /** - * @var array The attributes that aren't mass assignable. + * @var string[] The attributes that aren't mass assignable. */ protected $guarded = []; /** - * @var array Known image extensions. + * @var string[] Known image extensions. */ public static $imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp']; @@ -93,14 +98,10 @@ class File extends Model /** * Creates a file object from a file an uploaded file. - * @param Symfony\Component\HttpFoundation\File\UploadedFile $uploadedFile + * @param \Symfony\Component\HttpFoundation\File\UploadedFile $uploadedFile */ public function fromPost($uploadedFile) { - if ($uploadedFile === null) { - return; - } - $this->file_name = $uploadedFile->getClientOriginalName(); $this->file_size = $uploadedFile->getSize(); $this->content_type = $uploadedFile->getMimeType(); @@ -141,17 +142,13 @@ public function fromFile($filePath) /** * Creates a file object from raw data. * - * @param $data string Raw data - * @param $filename string Filename + * @param string $data Raw data + * @param string $filename Filename * * @return $this */ public function fromData($data, $filename) { - if ($data === null) { - return; - } - $tempPath = temp_path($filename); FileHelper::put($tempPath, $data); @@ -163,8 +160,9 @@ public function fromData($data, $filename) /** * Creates a file object from url - * @param $url string URL - * @param $filename string Filename + * + * @param string $url URL to the file + * @param string|null $filename The filename * @return $this */ public function fromUrl($url, $filename = null) @@ -230,7 +228,10 @@ public function setDataAttribute($value) /** * Helper attribute for get image width. - * @return string + * + * Returns `null` if this file is not an image. + * + * @return string|null */ public function getWidthAttribute() { @@ -239,11 +240,16 @@ public function getWidthAttribute() return $dimensions[0]; } + + return null; } /** * Helper attribute for get image height. - * @return string + * + * Returns `null` if this file is not an image. + * + * @return string|null */ public function getHeightAttribute() { @@ -252,6 +258,8 @@ public function getHeightAttribute() return $dimensions[1]; } + + return null; } /** @@ -272,7 +280,7 @@ public function getSizeAttribute() * * @param string $disposition The Content-Disposition to set, defaults to inline * @param bool $returnResponse Defaults to false, returns a Response object instead of directly outputting to the browser - * @return Response | void + * @return \Illuminate\Http\Response|void */ public function output($disposition = 'inline', $returnResponse = false) { @@ -286,10 +294,10 @@ public function output($disposition = 'inline', $returnResponse = false) if ($returnResponse) { return $response; - } else { - $response->sendHeaders(); - $response->sendContent(); } + + $response->sendHeaders(); + $response->sendContent(); } /** @@ -307,7 +315,7 @@ public function output($disposition = 'inline', $returnResponse = false) * 'disposition' => 'inline', * ] * @param bool $returnResponse Defaults to false, returns a Response object instead of directly outputting to the browser - * @return Response | void + * @return \Illuminate\Http\Response|void */ public function outputThumb($width, $height, $options = [], $returnResponse = false) { @@ -327,10 +335,10 @@ public function outputThumb($width, $height, $options = [], $returnResponse = fa if ($returnResponse) { return $response; - } else { - $response->sendHeaders(); - $response->sendContent(); } + + $response->sendHeaders(); + $response->sendContent(); } // @@ -739,7 +747,7 @@ protected function getDiskName() $ext = $this->data->guessExtension(); } - $name = str_replace('.', '', uniqid(null, true)); + $name = str_replace('.', '', uniqid('', true)); return $this->disk_name = !empty($ext) ? $name.'.'.$ext : $name; } @@ -786,10 +794,9 @@ protected function putFile($sourcePath, $destinationFileName = null) */ if ( !FileHelper::isDirectory($destinationPath) && - !FileHelper::makeDirectory($destinationPath, 0777, true, true) && - !FileHelper::isDirectory($destinationPath) + !FileHelper::makeDirectory($destinationPath, 0777, true, true) ) { - trigger_error(error_get_last(), E_USER_WARNING); + trigger_error(error_get_last()['message'], E_USER_WARNING); } return FileHelper::copy($sourcePath, $destinationPath . $destinationFileName); @@ -818,7 +825,7 @@ protected function deleteFile($fileName = null) /** * Check file exists on storage device. - * @return void + * @return bool */ protected function hasFile($fileName = null) { @@ -866,14 +873,13 @@ protected function deleteEmptyDirectory($dir = null) /** * Returns true if a directory contains no files. - * @return void + * + * @param string $dir The path to the directory. + * + * @return bool|null */ protected function isDirectoryEmpty($dir) { - if (!$dir) { - return null; - } - return count($this->storageCmd('allFiles', $dir)) === 0; } @@ -933,7 +939,8 @@ protected function copyLocalToStorage($localPath, $storagePath) /** * Returns the maximum size of an uploaded file as configured in php.ini - * @return int The maximum size of an uploaded file in kilobytes + * + * @return float The maximum size of an uploaded file in kilobytes (rounded) */ public static function getMaxFilesize() { @@ -980,7 +987,8 @@ public function getTempPath() /** * Returns the storage disk the file is stored on - * @return FilesystemAdapter + * + * @return \Illuminate\Filesystem\FilesystemAdapter */ public function getDisk() { @@ -999,9 +1007,8 @@ protected function isLocalStorage() /** * Generates a partition for the file. * return /ABC/DE1/234 for an name of ABCDE1234. - * @param Attachment $attachment - * @param string $styleName - * @return mixed + * + * @return string */ protected function getPartitionDirectory() { diff --git a/src/Database/Behaviors/Purgeable.php b/src/Database/Behaviors/Purgeable.php index d5584baf..0ba34668 100644 --- a/src/Database/Behaviors/Purgeable.php +++ b/src/Database/Behaviors/Purgeable.php @@ -1,16 +1,15 @@ originalPurgeableValues)) { - $this->originalPurgeableValues = array_merge($this->originalPurgeableValues, $originalAttributes); - } - else { - $this->originalPurgeableValues = $originalAttributes; - } + $this->originalPurgeableValues = array_merge($this->originalPurgeableValues, $originalAttributes); return $this->model->attributes = $cleanAttributes; } @@ -116,6 +110,8 @@ public function getOriginalPurgeValue($attribute) /** * Restores the original values of any purged attributes. + * + * @return \Winter\Storm\Database\Model */ public function restorePurgedValues() { diff --git a/src/Database/Behaviors/Sortable.php b/src/Database/Behaviors/Sortable.php index c88c5ccb..7fcd8d83 100644 --- a/src/Database/Behaviors/Sortable.php +++ b/src/Database/Behaviors/Sortable.php @@ -23,6 +23,7 @@ * * const SORT_ORDER = 'my_sort_order'; * + * @deprecated 1.2.0. We recommend using the \Winter\Storm\Database\Traits\Sortable trait instead. */ class Sortable extends ExtensionBase { diff --git a/src/Database/Builder.php b/src/Database/Builder.php index c3926405..2d760194 100644 --- a/src/Database/Builder.php +++ b/src/Database/Builder.php @@ -10,9 +10,24 @@ * Extends Eloquent builder class. * * @author Alexey Bobkov, Samuel Georges + * @mixin \Winter\Storm\Database\QueryBuilder */ class Builder extends BuilderModel { + /** + * The base query builder instance. + * + * @var \Winter\Storm\Database\QueryBuilder + */ + protected $query; + + /** + * The model being queried. + * + * @var \Winter\Storm\Database\Model + */ + protected $model; + /** * Get an array with the values of a given column. * @@ -103,10 +118,14 @@ protected function searchWhereInternal($term, $columns, $mode, $boolean) /** * Paginate the given query. * - * @param int $perPage - * @param int $currentPage - * @param array $columns - * @param string $pageName + * This method also accepts the Laravel signature: + * + * `paginate(int|null $perPage, array $columns, string $pageName, int|null $page)` + * + * @param int|null $perPage + * @param array|int|null $currentPage + * @param array|string $columns + * @param string|int|null $pageName * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator */ public function paginate($perPage = null, $currentPage = null, $columns = ['*'], $pageName = 'page') @@ -146,9 +165,14 @@ public function paginate($perPage = null, $currentPage = null, $columns = ['*'], /** * Paginate the given query into a simple paginator. * - * @param int $perPage - * @param int $currentPage - * @param array $columns + * This method also accepts the Laravel signature: + * + * `simplePaginate(int|null $perPage, array $columns, string $pageName, int|null $page)` + * + * @param int|null $perPage + * @param array|int|null $currentPage + * @param array|string $columns + * @param string|int|null $pageName * @return \Illuminate\Contracts\Pagination\Paginator */ public function simplePaginate($perPage = null, $currentPage = null, $columns = ['*'], $pageName = 'page') diff --git a/src/Database/Concerns/HasRelationships.php b/src/Database/Concerns/HasRelationships.php index 9ce32d4a..1dbadd87 100644 --- a/src/Database/Concerns/HasRelationships.php +++ b/src/Database/Concerns/HasRelationships.php @@ -152,13 +152,15 @@ public function hasRelation($name) /** * Returns relationship details from a supplied name. * @param string $name Relation name - * @return array + * @return array|null */ public function getRelationDefinition($name) { if (($type = $this->getRelationType($name)) !== null) { return (array) $this->getRelationTypeDefinition($type, $name) + $this->getRelationDefaults($type); } + + return null; } /** @@ -179,7 +181,7 @@ public function getRelationTypeDefinitions($type) * Returns the given relation definition. * @param string $type Relation type * @param string $name Relation name - * @return array + * @return string|null */ public function getRelationTypeDefinition($type, $name) { @@ -188,6 +190,8 @@ public function getRelationTypeDefinition($type, $name) if (isset($definitions[$name])) { return $definitions[$name]; } + + return null; } /** @@ -217,7 +221,7 @@ public function getRelationDefinitions() /** * Returns a relationship type based on a supplied name. * @param string $name Relation name - * @return string + * @return string|null */ public function getRelationType($name) { @@ -226,12 +230,14 @@ public function getRelationType($name) return $type; } } + + return null; } /** * Returns a relation class object * @param string $name Relation name - * @return string + * @return \Winter\Storm\Database\Relations\Relation|null */ public function makeRelation($name) { diff --git a/src/Database/Connections/MySqlConnection.php b/src/Database/Connections/MySqlConnection.php index 8082c739..58e2def2 100644 --- a/src/Database/Connections/MySqlConnection.php +++ b/src/Database/Connections/MySqlConnection.php @@ -12,7 +12,7 @@ class MySqlConnection extends Connection /** * Get the default query grammar instance. * - * @return \Illuminate\Database\Query\Grammars\MySqlGrammar + * @return \Illuminate\Database\Grammar */ protected function getDefaultQueryGrammar() { @@ -26,7 +26,7 @@ protected function getDefaultQueryGrammar() */ public function getSchemaBuilder() { - if (is_null($this->schemaGrammar)) { + if (!isset($this->schemaGrammar)) { $this->useDefaultSchemaGrammar(); } @@ -36,7 +36,7 @@ public function getSchemaBuilder() /** * Get the default schema grammar instance. * - * @return \Illuminate\Database\Schema\Grammars\MySqlGrammar + * @return \Illuminate\Database\Grammar */ protected function getDefaultSchemaGrammar() { diff --git a/src/Database/Connections/PostgresConnection.php b/src/Database/Connections/PostgresConnection.php index e012a260..788b6bf6 100644 --- a/src/Database/Connections/PostgresConnection.php +++ b/src/Database/Connections/PostgresConnection.php @@ -11,7 +11,7 @@ class PostgresConnection extends Connection /** * Get the default query grammar instance. * - * @return \Illuminate\Database\Query\Grammars\PostgresGrammar + * @return \Illuminate\Database\Grammar */ protected function getDefaultQueryGrammar() { @@ -25,7 +25,7 @@ protected function getDefaultQueryGrammar() */ public function getSchemaBuilder() { - if (is_null($this->schemaGrammar)) { + if (!isset($this->schemaGrammar)) { $this->useDefaultSchemaGrammar(); } @@ -35,7 +35,7 @@ public function getSchemaBuilder() /** * Get the default schema grammar instance. * - * @return \Illuminate\Database\Schema\Grammars\PostgresGrammar + * @return \Illuminate\Database\Grammar */ protected function getDefaultSchemaGrammar() { diff --git a/src/Database/Connections/SQLiteConnection.php b/src/Database/Connections/SQLiteConnection.php index eaf4e49e..d1eab6dc 100644 --- a/src/Database/Connections/SQLiteConnection.php +++ b/src/Database/Connections/SQLiteConnection.php @@ -11,7 +11,7 @@ class SQLiteConnection extends Connection /** * Get the default query grammar instance. * - * @return \Winter\Storm\Database\Query\Grammars\SQLiteGrammar + * @return \Illuminate\Database\Grammar */ protected function getDefaultQueryGrammar() { @@ -25,7 +25,7 @@ protected function getDefaultQueryGrammar() */ public function getSchemaBuilder() { - if (is_null($this->schemaGrammar)) { + if (!isset($this->schemaGrammar)) { $this->useDefaultSchemaGrammar(); } @@ -35,7 +35,7 @@ public function getSchemaBuilder() /** * Get the default schema grammar instance. * - * @return \Winter\Storm\Database\Query\Grammars\SQLiteGrammar + * @return \Illuminate\Database\Grammar */ protected function getDefaultSchemaGrammar() { diff --git a/src/Database/Connections/SqlServerConnection.php b/src/Database/Connections/SqlServerConnection.php index e48c4516..4e00151e 100644 --- a/src/Database/Connections/SqlServerConnection.php +++ b/src/Database/Connections/SqlServerConnection.php @@ -57,7 +57,7 @@ public function transaction(Closure $callback, $attempts = 1) /** * Get the default query grammar instance. * - * @return \Illuminate\Database\Query\Grammars\SqlServerGrammar + * @return \Illuminate\Database\Grammar */ protected function getDefaultQueryGrammar() { @@ -71,7 +71,7 @@ protected function getDefaultQueryGrammar() */ public function getSchemaBuilder() { - if (is_null($this->schemaGrammar)) { + if (!isset($this->schemaGrammar)) { $this->useDefaultSchemaGrammar(); } @@ -81,7 +81,7 @@ public function getSchemaBuilder() /** * Get the default schema grammar instance. * - * @return \Illuminate\Database\Schema\Grammars\SqlServerGrammar + * @return \Illuminate\Database\Grammar */ protected function getDefaultSchemaGrammar() { diff --git a/src/Database/Updater.php b/src/Database/Updater.php index 7ab55707..c4811673 100644 --- a/src/Database/Updater.php +++ b/src/Database/Updater.php @@ -29,10 +29,10 @@ public function setUp($file) Eloquent::unguard(); - if ($object instanceof Updates\Migration) { + if ($object instanceof Updates\Migration && method_exists($object, 'up')) { $object->up(); } - elseif ($object instanceof Updates\Seeder) { + elseif ($object instanceof Updates\Seeder && method_exists($object, 'run')) { $object->run(); } @@ -56,7 +56,7 @@ public function packDown($file) Eloquent::unguard(); - if ($object instanceof Updates\Migration) { + if ($object instanceof Updates\Migration && method_exists($object, 'down')) { $object->down(); } @@ -68,12 +68,12 @@ public function packDown($file) /** * Resolve a migration instance from a file. * @param string $file - * @return object + * @return object|null */ public function resolve($file) { if (!File::isFile($file)) { - return; + return null; } $instance = require_once $file; @@ -107,7 +107,7 @@ protected function isValidScript($object, $file) /** * Extracts the namespace and class name from a file. * @param string $file - * @return string + * @return string|false */ public function getClassFromFile($file) { From 35b36b7d686d82bc0eeead5fee1d8f3ee55bbb21 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sat, 26 Feb 2022 08:42:35 +0800 Subject: [PATCH 047/108] Defensive coding tweak --- src/Console/Command.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Console/Command.php b/src/Console/Command.php index 5d73ad21..cb8ba1fb 100644 --- a/src/Console/Command.php +++ b/src/Console/Command.php @@ -76,12 +76,12 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti $dataType = 'Argument'; $suggestionType = 'Values'; break; - default: + case 'options': $dataType = 'Option'; $suggestionType = 'Options'; break; } - if (!empty($data)) { + if (!empty($data) && isset($dataType) && isset($suggestionType)) { foreach ($data as $name => $value) { // Skip the command argument since that's handled by Artisan directly if ( From 6fb9ca9b47720d215a77c01ffa2b8b3d3fcee144 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 28 Feb 2022 14:10:42 +0800 Subject: [PATCH 048/108] Adjust Command logic path for input type parsing --- src/Console/Command.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Console/Command.php b/src/Console/Command.php index cb8ba1fb..06560a68 100644 --- a/src/Console/Command.php +++ b/src/Console/Command.php @@ -80,8 +80,10 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti $dataType = 'Option'; $suggestionType = 'Options'; break; + default: + throw new \Exception('Invalid input type being parsed during completion'); } - if (!empty($data) && isset($dataType) && isset($suggestionType)) { + if (!empty($data)) { foreach ($data as $name => $value) { // Skip the command argument since that's handled by Artisan directly if ( From bfcc68fdf7accccbeb7a37183163065f91a77936 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 28 Feb 2022 15:27:46 +0800 Subject: [PATCH 049/108] Fix some issues picked up by PHPStan when global scan is used --- src/Database/Models/DeferredBinding.php | 8 ++++++ src/Database/Traits/Nullable.php | 14 +++++------ src/Database/Traits/Validation.php | 8 +++--- src/Halcyon/Processors/SectionParser.php | 6 ++--- src/Support/ClassLoader.php | 31 +++++++++--------------- src/Support/Facades/Event.php | 2 +- src/Support/Traits/Emitter.php | 8 +++--- 7 files changed, 38 insertions(+), 39 deletions(-) diff --git a/src/Database/Models/DeferredBinding.php b/src/Database/Models/DeferredBinding.php index 96e485cf..277b1ab1 100644 --- a/src/Database/Models/DeferredBinding.php +++ b/src/Database/Models/DeferredBinding.php @@ -8,6 +8,14 @@ /** * Deferred Binding Model * + * @property string $master_type The class name of the master record. + * @property string $master_field The field name of the master record. + * @property string $slave_type The class name of the slave record. + * @property int $slave_id The ID of the slave record. + * @property array $pivot_data The pivot data recorded in the deferred binding data. + * @property string $session_key The session key that this deferred binding record belongs to. + * @property bool $is_bind If this record belonds to a bound record. + * * @author Alexey Bobkov, Samuel Georges */ class DeferredBinding extends Model diff --git a/src/Database/Traits/Nullable.php b/src/Database/Traits/Nullable.php index eefb6939..233b24c5 100644 --- a/src/Database/Traits/Nullable.php +++ b/src/Database/Traits/Nullable.php @@ -2,14 +2,14 @@ use Exception; +/** + * Enables nullification of empty values on model attributes. + * + * A model that uses this class must provide a property `$nullable`, that defines as an array all columns that will be + * set to `null` if they contain an empty value. + */ trait Nullable { - /** - * @var array List of attribute names which should be set to null when empty. - * - * protected $nullable = []; - */ - /** * Boot the nullable trait for a model * @@ -47,7 +47,7 @@ public function addNullable($attributes = null) /** * Checks if the supplied value is empty, excluding zero. - * @param string $value Value to check + * @param mixed $value Value to check * @return bool */ public function checkNullableValue($value) diff --git a/src/Database/Traits/Validation.php b/src/Database/Traits/Validation.php index a58d99e1..8634539a 100644 --- a/src/Database/Traits/Validation.php +++ b/src/Database/Traits/Validation.php @@ -1,13 +1,13 @@ parse($sections[0], true) + $result['settings'] = @$iniParser->parse($sections[0]) ?: [self::ERROR_INI => $sections[0]]; $result['markup'] = $sections[1]; @@ -274,7 +274,7 @@ public static function parseOffset(string $content): array * @param int $instance Which instance to look for * @return int|null The line number the instance was found. */ - private static function calculateLinePosition(string $content, int $instance = 1): int + private static function calculateLinePosition(string $content, int $instance = 1): ?int { $count = 0; $lines = explode(PHP_EOL, $content); @@ -284,7 +284,7 @@ private static function calculateLinePosition(string $content, int $instance = 1 } if ($count === $instance) { - return static::adjustLinePosition($content, $number); + return self::adjustLinePosition($content, $number); } } diff --git a/src/Support/ClassLoader.php b/src/Support/ClassLoader.php index b72429ae..f9ffd932 100644 --- a/src/Support/ClassLoader.php +++ b/src/Support/ClassLoader.php @@ -55,11 +55,11 @@ class ClassLoader protected $directories = []; /** - * Indicates if a ClassLoader has been registered. + * The registered callback for loading plugins. * - * @var bool + * @var callable|null */ - protected $registered = false; + protected $registered = null; /** * Class alias array. @@ -202,19 +202,16 @@ protected function includeClass($class, $path) */ public function register() { - if ($this->registered) { + if (!is_null($this->registered)) { return; } $this->ensureManifestIsLoaded(); - $callback = [$this, 'load']; - if (is_callable($callback)) { - $this->registered = spl_autoload_register($callback); - return; - } - - throw new Exception('The "load" method is missing from the class loader'); + $this->registered = function ($class) { + $this->load($class); + }; + spl_autoload_register($this->registered); } /** @@ -224,18 +221,12 @@ public function register() */ public function unregister() { - if (!$this->registered) { - return; - } - - $callback = [$this, 'load']; - if (is_callable($callback)) { - spl_autoload_unregister($callback); - $this->registered = false; + if (is_null($this->registered)) { return; } - throw new Exception('The "load" method is missing from the class loader'); + spl_autoload_unregister($this->registered); + $this->registered = null; } /** diff --git a/src/Support/Facades/Event.php b/src/Support/Facades/Event.php index 0ea9a6bc..e9e62305 100755 --- a/src/Support/Facades/Event.php +++ b/src/Support/Facades/Event.php @@ -20,7 +20,7 @@ * @method static void flush(string $event) * @method static void forget(string $event) * @method static void forgetPushed() - * @method static void listen(\Illuminate\Events\QueuedClosure|\Closure|string|array $events, \Illuminate\Events\QueuedClosure,\Closure|string|array $listener = null) + * @method static void listen(\Illuminate\Events\QueuedClosure|\Closure|string|array $events, \Illuminate\Events\QueuedClosure|\Closure|string|array $listener = null) * @method static void push(string $event, array $payload = []) * @method static void subscribe(object|string $subscriber) * @method static string firing() diff --git a/src/Support/Traits/Emitter.php b/src/Support/Traits/Emitter.php index b72b887a..d52e14b9 100644 --- a/src/Support/Traits/Emitter.php +++ b/src/Support/Traits/Emitter.php @@ -60,7 +60,7 @@ public function bindEvent($event, $callback = null, $priority = 0) /** * Create a new event binding that fires once only * @param string|Closure|QueuedClosure $event - * @param Closure|null $callback When a Closure or QueuedClosure is provided as the first parameter + * @param QueuedClosure|Closure|null $callback When a Closure or QueuedClosure is provided as the first parameter * this parameter can be omitted * @return self */ @@ -81,7 +81,7 @@ public function bindEventOnce($event, $callback = null) * Sort the listeners for a given event by priority. * * @param string $eventName - * @return array + * @return void */ protected function emitterEventSortEvents($eventName) { @@ -96,7 +96,7 @@ protected function emitterEventSortEvents($eventName) /** * Destroys an event binding. - * @param string $event Event to destroy + * @param string|array|object $event Event to destroy * @return self */ public function unbindEvent($event = null) @@ -108,7 +108,7 @@ public function unbindEvent($event = null) foreach ($event as $_event) { $this->unbindEvent($_event); } - return; + return $this; } if (is_object($event)) { From 15ed858473ad2f318a5861f81ef399d7176a98ea Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 29 Apr 2022 15:36:12 +0800 Subject: [PATCH 050/108] Fix tests --- src/Mail/Mailer.php | 3 +-- tests/Database/QueryBuilderTest.php | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Mail/Mailer.php b/src/Mail/Mailer.php index ec5c96c0..e2c534a0 100644 --- a/src/Mail/Mailer.php +++ b/src/Mail/Mailer.php @@ -2,11 +2,10 @@ use Winter\Storm\Support\Facades\Config; use Winter\Storm\Support\Facades\Event; -use Illuminate\Mail\Mailer as MailerBase; use Illuminate\Contracts\Mail\Mailable as MailableContract; +use Illuminate\Mail\Mailer as MailerBase; use Illuminate\Mail\SentMessage; use Illuminate\Support\Collection; -use Illuminate\Mail\SentMessage; /** * Mailer class for sending mail. diff --git a/tests/Database/QueryBuilderTest.php b/tests/Database/QueryBuilderTest.php index 56d67aed..089210df 100644 --- a/tests/Database/QueryBuilderTest.php +++ b/tests/Database/QueryBuilderTest.php @@ -210,11 +210,13 @@ protected function getConnection($connection = null) 'rollBack', 'transactionLevel', 'pretend', - 'getDatabaseName' + 'getDatabaseName', + 'getConfig', ]) ->getMock(); $connection->method('getDatabaseName')->willReturn('database'); + $connection->method('getConfig')->with('use_upsert_alias')->willReturn(false); return $connection; } From f9cc4feefe475653ff17b12e83689de5af909a71 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 3 May 2022 08:44:51 +0800 Subject: [PATCH 051/108] Synchronise docblocks --- src/Auth/Models/User.php | 2 +- src/Database/Attach/File.php | 2 +- src/Foundation/Exception/Handler.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Auth/Models/User.php b/src/Auth/Models/User.php index 75528e16..1306035b 100644 --- a/src/Auth/Models/User.php +++ b/src/Auth/Models/User.php @@ -54,7 +54,7 @@ class User extends Model implements \Illuminate\Contracts\Auth\Authenticatable protected $dates = ['activated_at', 'last_login']; /** - * @var array The attributes that should be hidden for arrays. + * @var array The attributes that should be hidden for arrays. */ protected $hidden = ['password', 'reset_password_code', 'activation_code', 'persist_code']; diff --git a/src/Database/Attach/File.php b/src/Database/Attach/File.php index 658a62f4..53990f24 100644 --- a/src/Database/Attach/File.php +++ b/src/Database/Attach/File.php @@ -62,7 +62,7 @@ class File extends Model public static $imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp']; /** - * @var array Hidden fields from array/json access + * @var array Hidden fields from array/json access */ protected $hidden = ['attachment_type', 'attachment_id', 'is_public']; diff --git a/src/Foundation/Exception/Handler.php b/src/Foundation/Exception/Handler.php index f7f139e5..9498af70 100644 --- a/src/Foundation/Exception/Handler.php +++ b/src/Foundation/Exception/Handler.php @@ -15,7 +15,7 @@ class Handler extends ExceptionHandler /** * A list of the exception types that should not be reported. * - * @var string[] + * @var array> */ protected $dontReport = [ \Winter\Storm\Exception\AjaxException::class, From 37865911772e770d3513d31098f093ed5bbea985 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 3 May 2022 08:46:01 +0800 Subject: [PATCH 052/108] Drop unnecessary overwrite in hasOne relation. "getResults" overwrite is already handled correctly in Laravel. --- src/Database/Relations/HasOne.php | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/Database/Relations/HasOne.php b/src/Database/Relations/HasOne.php index 1e9ba292..7c975db5 100644 --- a/src/Database/Relations/HasOne.php +++ b/src/Database/Relations/HasOne.php @@ -10,7 +10,7 @@ class HasOne extends HasOneBase use DefinedConstraints; /** - * Create a new has many relationship instance. + * Create a new "hasOne" relationship. * @return void */ public function __construct(Builder $query, Model $parent, $foreignKey, $localKey, $relationName = null) @@ -22,21 +22,6 @@ public function __construct(Builder $query, Model $parent, $foreignKey, $localKe $this->addDefinedConstraints(); } - /** - * Get the results of the relationship. - * @return mixed - */ - public function getResults() - { - // New models have no possibility of having a relationship here - // so prevent the first orphaned relation from being used. - if (!$this->parent->exists) { - return null; - } - - return parent::getResults(); - } - /** * Helper for setting this relationship using various expected * values. For example, $model->relation = $value; From f79eac45357cbd2e18c8b9ba83991bf3a9d6fb5a Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 3 May 2022 08:46:21 +0800 Subject: [PATCH 053/108] Simplify "key:generate" command overwrite --- src/Foundation/Providers/ArtisanServiceProvider.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Foundation/Providers/ArtisanServiceProvider.php b/src/Foundation/Providers/ArtisanServiceProvider.php index 7d23dc97..3b6d348b 100644 --- a/src/Foundation/Providers/ArtisanServiceProvider.php +++ b/src/Foundation/Providers/ArtisanServiceProvider.php @@ -141,9 +141,7 @@ public function register() */ protected function registerKeyGenerateCommand() { - $this->app->singleton(KeyGenerateCommand::class, function ($app) { - return new KeyGenerateCommand($app['files']); - }); + $this->app->singleton(KeyGenerateCommand::class); } /** From 369709482e6c507b6306c909b0190e3d80252750 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 3 May 2022 09:50:46 +0800 Subject: [PATCH 054/108] Tweak checks for URL generator values --- src/Router/UrlGenerator.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Router/UrlGenerator.php b/src/Router/UrlGenerator.php index bb58154b..f0b4d416 100644 --- a/src/Router/UrlGenerator.php +++ b/src/Router/UrlGenerator.php @@ -80,14 +80,15 @@ public static function buildUrl($url, $replace = [], $flags = HTTP_URL_REPLACE, foreach ($url as $key => &$value) { // Remove invalid segments if ( - (!in_array($key, $urlSegments) || !isset($value)) || - (is_array($value) && empty($value)) + !in_array($key, $urlSegments) + || !isset($value) + || empty($value) ) { unset($url[$key]); continue; } - // Trim strings and remove empty strings + // Trim strings if (!is_array($value)) { $value = trim((string) $value); } From e3f2bac29145186737434eea25e8fef426990942 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 3 May 2022 10:05:29 +0800 Subject: [PATCH 055/108] Refactor database relations. - Move all traits into a concerns folder, to keep only supported relations in the base relation folder. Stubs for previously available traits have been kept for BC, but are deprecated. - Abstract BelongsToMany functionality into a trait to share with this class as well as MorphsToMany. - Change MorphToMany to extend the base MorphToMany class from Laravel, to maintain parity and covariance. - Fix multiple PHPStan issues --- src/Database/Concerns/HasRelationships.php | 32 +- src/Database/Relations/AttachMany.php | 4 +- src/Database/Relations/AttachOne.php | 4 +- src/Database/Relations/AttachOneOrMany.php | 302 +------------ src/Database/Relations/BelongsTo.php | 4 +- src/Database/Relations/BelongsToMany.php | 423 +----------------- .../Relations/Concerns/AttachOneOrMany.php | 303 +++++++++++++ .../Concerns/BelongsOrMorphsToMany.php | 423 ++++++++++++++++++ .../Relations/Concerns/DeferOneOrMany.php | 133 ++++++ .../Relations/Concerns/DefinedConstraints.php | 124 +++++ .../Relations/Concerns/HasOneOrMany.php | 134 ++++++ .../Relations/Concerns/MorphOneOrMany.php | 101 +++++ src/Database/Relations/DeferOneOrMany.php | 127 +----- src/Database/Relations/DefinedConstraints.php | 121 +---- src/Database/Relations/HasMany.php | 4 +- src/Database/Relations/HasManyThrough.php | 2 +- src/Database/Relations/HasOne.php | 4 +- src/Database/Relations/HasOneOrMany.php | 132 +----- src/Database/Relations/HasOneThrough.php | 2 +- src/Database/Relations/MorphMany.php | 4 +- src/Database/Relations/MorphOne.php | 4 +- src/Database/Relations/MorphOneOrMany.php | 100 +---- src/Database/Relations/MorphTo.php | 2 +- src/Database/Relations/MorphToMany.php | 93 +--- 24 files changed, 1287 insertions(+), 1295 deletions(-) create mode 100644 src/Database/Relations/Concerns/AttachOneOrMany.php create mode 100644 src/Database/Relations/Concerns/BelongsOrMorphsToMany.php create mode 100644 src/Database/Relations/Concerns/DeferOneOrMany.php create mode 100644 src/Database/Relations/Concerns/DefinedConstraints.php create mode 100644 src/Database/Relations/Concerns/HasOneOrMany.php create mode 100644 src/Database/Relations/Concerns/MorphOneOrMany.php diff --git a/src/Database/Concerns/HasRelationships.php b/src/Database/Concerns/HasRelationships.php index ec01cb16..4ca042cf 100644 --- a/src/Database/Concerns/HasRelationships.php +++ b/src/Database/Concerns/HasRelationships.php @@ -471,7 +471,7 @@ public function belongsTo($related, $foreignKey = null, $parentKey = null, $rela /** * Define an polymorphic, inverse one-to-one or many relationship. * Overridden from {@link Eloquent\Model} to allow the usage of the intermediary methods to handle the relation. - * @return \Winter\Storm\Database\Relations\BelongsTo + * @return \Winter\Storm\Database\Relations\MorphTo */ public function morphTo($name = null, $type = null, $id = null, $ownerKey = null) { @@ -493,7 +493,7 @@ public function morphTo($name = null, $type = null, $id = null, $ownerKey = null * @param string $type * @param string $id * @param string $ownerKey - * @return \Illuminate\Database\Eloquent\Relations\MorphTo + * @return \Winter\Storm\Database\Relations\MorphTo */ protected function morphEagerTo($name, $type, $id, $ownerKey) { @@ -514,10 +514,10 @@ protected function morphEagerTo($name, $type, $id, $ownerKey) * @param string $name * @param string $type * @param string $id - * @param string $ownerKey - * @return \Illuminate\Database\Eloquent\Relations\MorphTo + * @param string|null $ownerKey + * @return \Winter\Storm\Database\Relations\MorphTo */ - protected function morphInstanceTo($target, $name, $type, $id, $ownerKey) + protected function morphInstanceTo($target, $name, $type, $id, $ownerKey = null) { $instance = $this->newRelatedInstance( static::getActualClassNameForMorph($target) @@ -724,7 +724,7 @@ public function morphedByMany($related, $name, $table = null, $primaryKey = null /** * Define an attachment one-to-one relationship. * This code is a duplicate of Eloquent but uses a Storm relation class. - * @return \Winter\Storm\Database\Relations\MorphOne + * @return \Winter\Storm\Database\Relations\AttachOne */ public function attachOne($related, $isPublic = true, $localKey = null, $relationName = null) { @@ -746,7 +746,7 @@ public function attachOne($related, $isPublic = true, $localKey = null, $relatio /** * Define an attachment one-to-many relationship. * This code is a duplicate of Eloquent but uses a Storm relation class. - * @return \Winter\Storm\Database\Relations\MorphMany + * @return \Winter\Storm\Database\Relations\AttachMany */ public function attachMany($related, $isPublic = null, $localKey = null, $relationName = null) { @@ -770,7 +770,7 @@ public function attachMany($related, $isPublic = null, $localKey = null, $relati */ protected function getRelationCaller() { - $backtrace = debug_backtrace(false); + $backtrace = debug_backtrace(0); $caller = ($backtrace[2]['function'] == 'handleRelation') ? $backtrace[4] : $backtrace[2]; return $caller['function']; } @@ -814,8 +814,7 @@ protected function addRelation(string $type, string $name, array $config): void sprintf( 'Cannot add the "%s" relation to %s, it conflicts with an existing relation, attribute, or property.', $name, - get_class($this), - $name + get_class($this) ) ); } @@ -952,4 +951,17 @@ public function addHasManyThroughRelation(string $name, array $config): void { $this->addRelation('HasManyThrough', $name, $config); } + + /** + * Get the polymorphic relationship columns. + * + * @param string $name + * @param string|null $type + * @param string|null $id + * @return array + */ + protected function getMorphs($name, $type = null, $id = null) + { + return [$type ?: $name.'_type', $id ?: $name.'_id']; + } } diff --git a/src/Database/Relations/AttachMany.php b/src/Database/Relations/AttachMany.php index 856978f6..6079f9b4 100644 --- a/src/Database/Relations/AttachMany.php +++ b/src/Database/Relations/AttachMany.php @@ -7,8 +7,8 @@ class AttachMany extends MorphManyBase { - use AttachOneOrMany; - use DefinedConstraints; + use Concerns\AttachOneOrMany; + use Concerns\DefinedConstraints; /** * Create a new has many relationship instance. diff --git a/src/Database/Relations/AttachOne.php b/src/Database/Relations/AttachOne.php index c8687cc2..d4545962 100644 --- a/src/Database/Relations/AttachOne.php +++ b/src/Database/Relations/AttachOne.php @@ -7,8 +7,8 @@ class AttachOne extends MorphOneBase { - use AttachOneOrMany; - use DefinedConstraints; + use Concerns\AttachOneOrMany; + use Concerns\DefinedConstraints; /** * Create a new has many relationship instance. diff --git a/src/Database/Relations/AttachOneOrMany.php b/src/Database/Relations/AttachOneOrMany.php index 0dec41d2..67e5bdf9 100644 --- a/src/Database/Relations/AttachOneOrMany.php +++ b/src/Database/Relations/AttachOneOrMany.php @@ -1,303 +1,9 @@ public) && $this->public !== null) { - return $this->public; - } - - return true; - } - - /** - * Set the field (relation name) constraint on the query. - * @return void - */ - public function addConstraints() - { - if (static::$constraints) { - $this->query->where($this->morphType, $this->morphClass); - - $this->query->where($this->foreignKey, '=', $this->getParentKey()); - - $this->query->where('field', $this->relationName); - - $this->query->whereNotNull($this->foreignKey); - } - } - - /** - * Add the constraints for a relationship count query. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder - */ - public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) - { - if ($parentQuery->getQuery()->from == $query->getQuery()->from) { - $query = $this->getRelationExistenceQueryForSelfJoin($query, $parentQuery, $columns); - } - else { - $key = DbDongle::cast($this->getQualifiedParentKeyName(), 'TEXT'); - - $query = $query->select($columns)->whereColumn($this->getExistenceCompareKey(), '=', $key); - } - - $query = $query->where($this->morphType, $this->morphClass); - - return $query->where('field', $this->relationName); - } - - /** - * Add the constraints for a relationship query on the same table. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder - */ - public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) - { - $query->select($columns)->from( - $query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash() - ); - - $query->getModel()->setTable($hash); - - $key = DbDongle::cast($this->getQualifiedParentKeyName(), 'TEXT'); - - return $query->whereColumn($hash.'.'.$this->getForeignKeyName(), '=', $key); - } - - /** - * Set the field constraint for an eager load of the relation. - * - * @param array $models - * @return void - */ - public function addEagerConstraints(array $models) - { - parent::addEagerConstraints($models); - - $this->query->where('field', $this->relationName); - } - - /** - * Save the supplied related model. - */ - public function save(Model $model, $sessionKey = null) - { - // Delete siblings for single attachments - if ($sessionKey === null && $this instanceof AttachOne) { - $this->delete(); - } - - if (!array_key_exists('is_public', $model->attributes)) { - $model->setAttribute('is_public', $this->isPublic()); - } - - $model->setAttribute('field', $this->relationName); - - if ($sessionKey === null) { - return parent::save($model); - } - - $this->add($model, $sessionKey); - return $model->save() ? $model : false; - } - - /** - * Create a new instance of this related model. - */ - public function create(array $attributes = [], $sessionKey = null) - { - // Delete siblings for single attachments - if ($sessionKey === null && $this instanceof AttachOne) { - $this->delete(); - } - - if (!array_key_exists('is_public', $attributes)) { - $attributes = array_merge(['is_public' => $this->isPublic()], $attributes); - } - - $attributes['field'] = $this->relationName; - - $model = parent::create($attributes); - - if ($sessionKey !== null) { - $this->add($model, $sessionKey); - } - - return $model; - } - - /** - * Adds a model to this relationship type. - */ - public function add(Model $model, $sessionKey = null) - { - if (!array_key_exists('is_public', $model->attributes)) { - $model->is_public = $this->isPublic(); - } - - if ($sessionKey === null) { - // Delete siblings for single attachments - if ($this instanceof AttachOne) { - $this->delete(); - } - - $model->setAttribute($this->getForeignKeyName(), $this->parent->getKey()); - $model->setAttribute($this->getMorphType(), $this->morphClass); - $model->setAttribute('field', $this->relationName); - $model->save(); - - /* - * Use the opportunity to set the relation in memory - */ - if ($this instanceof AttachOne) { - $this->parent->setRelation($this->relationName, $model); - } - else { - $this->parent->reloadRelations($this->relationName); - } - } - else { - $this->parent->bindDeferred($this->relationName, $model, $sessionKey); - } - } - - /** - * Attach an array of models to the parent instance with deferred binding support. - * @param array $models - * @return void - */ - public function addMany($models, $sessionKey = null) - { - foreach ($models as $model) { - $this->add($model, $sessionKey); - } - } - - /** - * Removes a model from this relationship type. - */ - public function remove(Model $model, $sessionKey = null) - { - if ($sessionKey === null) { - $options = $this->parent->getRelationDefinition($this->relationName); - - if (array_get($options, 'delete', false)) { - $model->delete(); - } - else { - /* - * Make this model an orphan ;~( - */ - $model->setAttribute($this->getForeignKeyName(), null); - $model->setAttribute($this->getMorphType(), null); - $model->setAttribute('field', null); - $model->save(); - } - - /* - * Use the opportunity to set the relation in memory - */ - if ($this instanceof AttachOne) { - $this->parent->setRelation($this->relationName, null); - } - else { - $this->parent->reloadRelations($this->relationName); - } - } - else { - $this->parent->unbindDeferred($this->relationName, $model, $sessionKey); - } - } - - /** - * Returns true if the specified value can be used as the data attribute. - */ - protected function isValidFileData($value) - { - if ($value instanceof UploadedFile) { - return true; - } - - if (is_string($value) && file_exists($value)) { - return true; - } - - return false; - } - - /** - * Creates a file object suitable for validation, called from - * the `getValidationValue` method. Value can be a file model, - * UploadedFile object (expected) or potentially a string. - * - * @param mixed $value - * @return UploadedFile - */ - public function makeValidationFile($value) - { - if ($value instanceof FileModel) { - return new UploadedFile( - $value->getLocalPath(), - $value->file_name, - $value->content_type, - $value->file_size, - null, - true - ); - } - - /* - * @todo `$value` might be a string, may not validate - */ - - return $value; - } - - /** - * Get the foreign key for the relationship. - * @return string - */ - public function getForeignKey() - { - return $this->foreignKey; - } - - /** - * Get the associated "other" key of the relationship. - * @return string - */ - public function getOtherKey() - { - return $this->localKey; - } + use Concerns\AttachOneOrMany; } diff --git a/src/Database/Relations/BelongsTo.php b/src/Database/Relations/BelongsTo.php index 370102ee..98ab8271 100644 --- a/src/Database/Relations/BelongsTo.php +++ b/src/Database/Relations/BelongsTo.php @@ -6,8 +6,8 @@ class BelongsTo extends BelongsToBase { - use DeferOneOrMany; - use DefinedConstraints; + use Concerns\DeferOneOrMany; + use Concerns\DefinedConstraints; /** * @var string The "name" of the relationship. diff --git a/src/Database/Relations/BelongsToMany.php b/src/Database/Relations/BelongsToMany.php index 7350ab9b..ec140847 100644 --- a/src/Database/Relations/BelongsToMany.php +++ b/src/Database/Relations/BelongsToMany.php @@ -1,427 +1,10 @@ addDefinedConstraints(); - } - - /** - * Get the select columns for the relation query. - * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany - */ - protected function shouldSelect(array $columns = ['*']) - { - if ($this->countMode) { - return $this->table.'.'.$this->foreignPivotKey.' as pivot_'.$this->foreignPivotKey; - } - - if ($columns == ['*']) { - $columns = [$this->related->getTable().'.*']; - } - - if ($this->orphanMode) { - return $columns; - } - - return array_merge($columns, $this->aliasedPivotColumns()); - } - - /** - * Save the supplied related model with deferred binding support. - */ - public function save(Model $model, array $pivotData = [], $sessionKey = null) - { - $model->save(); - $this->add($model, $sessionKey, $pivotData); - return $model; - } - - /** - * Override sync() method of BelongToMany relation in order to flush the query cache. - * @param array $ids - * @param bool $detaching - * @return array - */ - public function sync($ids, $detaching = true) - { - $changed = parent::sync($ids, $detaching); - - $this->flushDuplicateCache(); - - return $changed; - } - - /** - * Create a new instance of this related model with deferred binding support. - */ - public function create(array $attributes = [], array $pivotData = [], $sessionKey = null) - { - $model = $this->related->create($attributes); - - $this->add($model, $sessionKey, $pivotData); - - return $model; - } - - /** - * Override attach() method of BelongToMany relation. - * This is necessary in order to fire 'model.relation.beforeAttach', 'model.relation.afterAttach' events - * @param mixed $id - * @param array $attributes - * @param bool $touch - */ - public function attach($id, array $attributes = [], $touch = true) - { - $insertData = $this->formatAttachRecords($this->parseIds($id), $attributes); - $attachedIdList = array_pluck($insertData, $this->relatedPivotKey); - - /** - * @event model.relation.beforeAttach - * Called before creating a new relation between models (only for BelongsToMany relation) - * - * Example usage: - * - * $model->bindEvent('model.relation.beforeAttach', function (string $relationName, array $attachedIdList, array $insertData) use (\Winter\Storm\Database\Model $model) { - * if (!$model->isRelationValid($attachedIdList)) { - * throw new \Exception("Invalid relation!"); - * return false; - * } - * }); - * - */ - if ($this->parent->fireEvent('model.relation.beforeAttach', [$this->relationName, $attachedIdList, $insertData], true) === false) { - return; - } - - // Here we will insert the attachment records into the pivot table. Once we have - // inserted the records, we will touch the relationships if necessary and the - // function will return. We can parse the IDs before inserting the records. - $this->newPivotStatement()->insert($insertData); - - if ($touch) { - $this->touchIfTouching(); - } - - /** - * @event model.relation.afterAttach - * Called after creating a new relation between models (only for BelongsToMany relation) - * - * Example usage: - * - * $model->bindEvent('model.relation.afterAttach', function (string $relationName, array $attachedIdList, array $insertData) use (\Winter\Storm\Database\Model $model) { - * traceLog("New relation {$relationName} was created", $attachedIdList); - * }); - * - */ - $this->parent->fireEvent('model.relation.afterAttach', [$this->relationName, $attachedIdList, $insertData]); - } - - /** - * Override detach() method of BelongToMany relation. - * This is necessary in order to fire 'model.relation.beforeDetach', 'model.relation.afterDetach' events - * @param CollectionBase|Model|array|null $ids - * @param bool $touch - * @return int|void - */ - public function detach($ids = null, $touch = true) - { - $attachedIdList = $this->parseIds($ids); - if (empty($attachedIdList)) { - $attachedIdList = $this->newPivotQuery()->lists($this->relatedPivotKey); - } - - /** - * @event model.relation.beforeDetach - * Called before removing a relation between models (only for BelongsToMany relation) - * - * Example usage: - * - * $model->bindEvent('model.relation.beforeDetach', function (string $relationName, array $attachedIdList) use (\Winter\Storm\Database\Model $model) { - * if (!$model->isRelationValid($attachedIdList)) { - * throw new \Exception("Invalid relation!"); - * return false; - * } - * }); - * - */ - if ($this->parent->fireEvent('model.relation.beforeDetach', [$this->relationName, $attachedIdList], true) === false) { - return; - } - - /** - * @see Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithPivotTable - */ - parent::detach($attachedIdList, $touch); - - /** - * @event model.relation.afterDetach - * Called after removing a relation between models (only for BelongsToMany relation) - * - * Example usage: - * - * $model->bindEvent('model.relation.afterDetach', function (string $relationName, array $attachedIdList) use (\Winter\Storm\Database\Model $model) { - * traceLog("Relation {$relationName} was removed", $attachedIdList); - * }); - * - */ - $this->parent->fireEvent('model.relation.afterDetach', [$this->relationName, $attachedIdList]); - } - - /** - * Adds a model to this relationship type. - */ - public function add(Model $model, $sessionKey = null, $pivotData = []) - { - if (is_array($sessionKey)) { - $pivotData = $sessionKey; - $sessionKey = null; - } - - if ($sessionKey === null || $sessionKey === false) { - $this->attach($model->getKey(), $pivotData); - $this->parent->reloadRelations($this->relationName); - } - else { - $this->parent->bindDeferred($this->relationName, $model, $sessionKey, $pivotData); - } - } - - /** - * Removes a model from this relationship type. - */ - public function remove(Model $model, $sessionKey = null) - { - if ($sessionKey === null) { - $this->detach($model->getKey()); - $this->parent->reloadRelations($this->relationName); - } - else { - $this->parent->unbindDeferred($this->relationName, $model, $sessionKey); - } - } - - /** - * Get a paginator for the "select" statement. Complies with Winter Storm. - * - * @param int $perPage - * @param int $currentPage - * @param array $columns - * @param string $pageName - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - */ - public function paginate($perPage = 15, $currentPage = null, $columns = ['*'], $pageName = 'page') - { - $this->query->addSelect($this->shouldSelect($columns)); - - $paginator = $this->query->paginate($perPage, $currentPage, $columns); - - $this->hydratePivotRelation($paginator->items()); - - return $paginator; - } - - /** - * Create a new pivot model instance. - * - * @param array $attributes - * @param bool $exists - * @return \Illuminate\Database\Eloquent\Relations\Pivot - */ - public function newPivot(array $attributes = [], $exists = false) - { - /* - * Winter looks to the relationship parent - */ - $pivot = $this->parent->newRelationPivot($this->relationName, $this->parent, $attributes, $this->table, $exists); - - /* - * Laravel looks to the related model - */ - if (empty($pivot)) { - $pivot = $this->related->newPivot($this->parent, $attributes, $this->table, $exists); - } - - return $pivot->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey); - } - - /** - * Helper for setting this relationship using various expected - * values. For example, $model->relation = $value; - */ - public function setSimpleValue($value) - { - $relationModel = $this->getRelated(); - - /* - * Nulling the relationship - */ - if (!$value) { - // Disassociate in memory immediately - $this->parent->setRelation($this->relationName, $relationModel->newCollection()); - - // Perform sync when the model is saved - $this->parent->bindEventOnce('model.afterSave', function () use ($value) { - $this->detach(); - }); - return; - } - - /* - * Convert models to keys - */ - if ($value instanceof Model) { - $value = $value->getKey(); - } - elseif (is_array($value)) { - foreach ($value as $_key => $_value) { - if ($_value instanceof Model) { - $value[$_key] = $_value->getKey(); - } - } - } - - /* - * Convert scalar to array - */ - if (!is_array($value) && !$value instanceof CollectionBase) { - $value = [$value]; - } - - /* - * Setting the relationship - */ - $relationCollection = $value instanceof CollectionBase - ? $value - : $relationModel->whereIn($relationModel->getKeyName(), $value)->get(); - - // Associate in memory immediately - $this->parent->setRelation($this->relationName, $relationCollection); - - // Perform sync when the model is saved - $this->parent->bindEventOnce('model.afterSave', function () use ($value) { - $this->sync($value); - }); - } - - /** - * Helper for getting this relationship simple value, - * generally useful with form values. - */ - public function getSimpleValue() - { - $value = []; - - $relationName = $this->relationName; - - $sessionKey = $this->parent->sessionKey; - - if ($this->parent->relationLoaded($relationName)) { - $related = $this->getRelated(); - - $value = $this->parent->getRelation($relationName)->pluck($related->getKeyName())->all(); - } - else { - $value = $this->allRelatedIds($sessionKey)->all(); - } - - return $value; - } - - /** - * Get all of the IDs for the related models, with deferred binding support - * - * @param string $sessionKey - * @return \Winter\Storm\Support\Collection - */ - public function allRelatedIds($sessionKey = null) - { - $related = $this->getRelated(); - - $fullKey = $related->getQualifiedKeyName(); - - $query = $sessionKey ? $this->withDeferred($sessionKey) : $this; - - return $query->getQuery()->select($fullKey)->pluck($related->getKeyName()); - } - - /** - * Get the fully qualified foreign key for the relation. - * - * @return string - */ - public function getForeignKey() - { - return $this->table.'.'.$this->foreignPivotKey; - } - - /** - * Get the fully qualified "other key" for the relation. - * - * @return string - */ - public function getOtherKey() - { - return $this->table.'.'.$this->relatedPivotKey; - } - - /** - * @deprecated Use allRelatedIds instead. Remove if year >= 2018. - */ - public function getRelatedIds($sessionKey = null) - { - traceLog('Method BelongsToMany::getRelatedIds has been deprecated, use BelongsToMany::allRelatedIds instead.'); - return $this->allRelatedIds($sessionKey)->all(); - } + use Concerns\BelongsOrMorphsToMany; + use Concerns\DeferOneOrMany; + use Concerns\DefinedConstraints; } diff --git a/src/Database/Relations/Concerns/AttachOneOrMany.php b/src/Database/Relations/Concerns/AttachOneOrMany.php new file mode 100644 index 00000000..094ac2d8 --- /dev/null +++ b/src/Database/Relations/Concerns/AttachOneOrMany.php @@ -0,0 +1,303 @@ +public)) { + return $this->public; + } + + return true; + } + + /** + * Set the field (relation name) constraint on the query. + * @return void + */ + public function addConstraints() + { + if (static::$constraints) { + $this->query->where($this->morphType, $this->morphClass); + + $this->query->where($this->foreignKey, '=', $this->getParentKey()); + + $this->query->where('field', $this->relationName); + + $this->query->whereNotNull($this->foreignKey); + } + } + + /** + * Add the constraints for a relationship count query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) + { + if ($parentQuery->getQuery()->from == $query->getQuery()->from) { + $query = $this->getRelationExistenceQueryForSelfJoin($query, $parentQuery, $columns); + } + else { + $key = DbDongle::cast($this->getQualifiedParentKeyName(), 'TEXT'); + + $query = $query->select($columns)->whereColumn($this->getExistenceCompareKey(), '=', $key); + } + + $query = $query->where($this->morphType, $this->morphClass); + + return $query->where('field', $this->relationName); + } + + /** + * Add the constraints for a relationship query on the same table. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $parentQuery + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) + { + $query->select($columns)->from( + $query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash() + ); + + $query->getModel()->setTable($hash); + + $key = DbDongle::cast($this->getQualifiedParentKeyName(), 'TEXT'); + + return $query->whereColumn($hash.'.'.$this->getForeignKeyName(), '=', $key); + } + + /** + * Set the field constraint for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + parent::addEagerConstraints($models); + + $this->query->where('field', $this->relationName); + } + + /** + * Save the supplied related model. + */ + public function save(Model $model, $sessionKey = null) + { + // Delete siblings for single attachments + if ($sessionKey === null && $this instanceof AttachOne) { + $this->delete(); + } + + if (!array_key_exists('is_public', $model->attributes)) { + $model->setAttribute('is_public', $this->isPublic()); + } + + $model->setAttribute('field', $this->relationName); + + if ($sessionKey === null) { + return parent::save($model); + } + + $this->add($model, $sessionKey); + return $model->save() ? $model : false; + } + + /** + * Create a new instance of this related model. + */ + public function create(array $attributes = [], $sessionKey = null) + { + // Delete siblings for single attachments + if ($sessionKey === null && $this instanceof AttachOne) { + $this->delete(); + } + + if (!array_key_exists('is_public', $attributes)) { + $attributes = array_merge(['is_public' => $this->isPublic()], $attributes); + } + + $attributes['field'] = $this->relationName; + + $model = parent::create($attributes); + + if ($sessionKey !== null) { + $this->add($model, $sessionKey); + } + + return $model; + } + + /** + * Adds a model to this relationship type. + */ + public function add(Model $model, $sessionKey = null) + { + if (!array_key_exists('is_public', $model->attributes)) { + $model->is_public = $this->isPublic(); + } + + if ($sessionKey === null) { + // Delete siblings for single attachments + if ($this instanceof AttachOne) { + $this->delete(); + } + + $model->setAttribute($this->getForeignKeyName(), $this->parent->getKey()); + $model->setAttribute($this->getMorphType(), $this->morphClass); + $model->setAttribute('field', $this->relationName); + $model->save(); + + /* + * Use the opportunity to set the relation in memory + */ + if ($this instanceof AttachOne) { + $this->parent->setRelation($this->relationName, $model); + } + else { + $this->parent->reloadRelations($this->relationName); + } + } + else { + $this->parent->bindDeferred($this->relationName, $model, $sessionKey); + } + } + + /** + * Attach an array of models to the parent instance with deferred binding support. + * @param array $models + * @return void + */ + public function addMany($models, $sessionKey = null) + { + foreach ($models as $model) { + $this->add($model, $sessionKey); + } + } + + /** + * Removes a model from this relationship type. + */ + public function remove(Model $model, $sessionKey = null) + { + if ($sessionKey === null) { + $options = $this->parent->getRelationDefinition($this->relationName); + + if (array_get($options, 'delete', false)) { + $model->delete(); + } + else { + /* + * Make this model an orphan ;~( + */ + $model->setAttribute($this->getForeignKeyName(), null); + $model->setAttribute($this->getMorphType(), null); + $model->setAttribute('field', null); + $model->save(); + } + + /* + * Use the opportunity to set the relation in memory + */ + if ($this instanceof AttachOne) { + $this->parent->setRelation($this->relationName, null); + } + else { + $this->parent->reloadRelations($this->relationName); + } + } + else { + $this->parent->unbindDeferred($this->relationName, $model, $sessionKey); + } + } + + /** + * Returns true if the specified value can be used as the data attribute. + */ + protected function isValidFileData($value) + { + if ($value instanceof UploadedFile) { + return true; + } + + if (is_string($value) && file_exists($value)) { + return true; + } + + return false; + } + + /** + * Creates a file object suitable for validation, called from + * the `getValidationValue` method. Value can be a file model, + * UploadedFile object (expected) or potentially a string. + * + * @param mixed $value + * @return UploadedFile + */ + public function makeValidationFile($value) + { + if ($value instanceof FileModel) { + return new UploadedFile( + $value->getLocalPath(), + $value->file_name, + $value->content_type, + null, + true + ); + } + + /* + * @todo `$value` might be a string, may not validate + */ + + return $value; + } + + /** + * Get the foreign key for the relationship. + * @return string + */ + public function getForeignKey() + { + return $this->foreignKey; + } + + /** + * Get the associated "other" key of the relationship. + * @return string + */ + public function getOtherKey() + { + return $this->localKey; + } +} diff --git a/src/Database/Relations/Concerns/BelongsOrMorphsToMany.php b/src/Database/Relations/Concerns/BelongsOrMorphsToMany.php new file mode 100644 index 00000000..839bc44a --- /dev/null +++ b/src/Database/Relations/Concerns/BelongsOrMorphsToMany.php @@ -0,0 +1,423 @@ +addDefinedConstraints(); + } + + /** + * Get the select columns for the relation query. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + protected function shouldSelect(array $columns = ['*']) + { + if ($this->countMode) { + return $this->table.'.'.$this->foreignPivotKey.' as pivot_'.$this->foreignPivotKey; + } + + if ($columns == ['*']) { + $columns = [$this->related->getTable().'.*']; + } + + if ($this->orphanMode) { + return $columns; + } + + return array_merge($columns, $this->aliasedPivotColumns()); + } + + /** + * Save the supplied related model with deferred binding support. + */ + public function save(Model $model, array $pivotData = [], $sessionKey = null) + { + $model->save(); + $this->add($model, $sessionKey, $pivotData); + return $model; + } + + /** + * Override sync() method of BelongToMany relation in order to flush the query cache. + * @param array $ids + * @param bool $detaching + * @return array + */ + public function sync($ids, $detaching = true) + { + $changed = parent::sync($ids, $detaching); + + $this->flushDuplicateCache(); + + return $changed; + } + + /** + * Create a new instance of this related model with deferred binding support. + */ + public function create(array $attributes = [], array $pivotData = [], $sessionKey = null) + { + $model = $this->related->create($attributes); + + $this->add($model, $sessionKey, $pivotData); + + return $model; + } + + /** + * Override attach() method of BelongToMany relation. + * This is necessary in order to fire 'model.relation.beforeAttach', 'model.relation.afterAttach' events + * @param mixed $id + * @param array $attributes + * @param bool $touch + */ + public function attach($id, array $attributes = [], $touch = true) + { + $insertData = $this->formatAttachRecords($this->parseIds($id), $attributes); + $attachedIdList = array_pluck($insertData, $this->relatedPivotKey); + + /** + * @event model.relation.beforeAttach + * Called before creating a new relation between models (only for BelongsToMany relation) + * + * Example usage: + * + * $model->bindEvent('model.relation.beforeAttach', function (string $relationName, array $attachedIdList, array $insertData) use (\Winter\Storm\Database\Model $model) { + * if (!$model->isRelationValid($attachedIdList)) { + * throw new \Exception("Invalid relation!"); + * return false; + * } + * }); + * + */ + if ($this->parent->fireEvent('model.relation.beforeAttach', [$this->relationName, $attachedIdList, $insertData], true) === false) { + return; + } + + // Here we will insert the attachment records into the pivot table. Once we have + // inserted the records, we will touch the relationships if necessary and the + // function will return. We can parse the IDs before inserting the records. + $this->newPivotStatement()->insert($insertData); + + if ($touch) { + $this->touchIfTouching(); + } + + /** + * @event model.relation.afterAttach + * Called after creating a new relation between models (only for BelongsToMany relation) + * + * Example usage: + * + * $model->bindEvent('model.relation.afterAttach', function (string $relationName, array $attachedIdList, array $insertData) use (\Winter\Storm\Database\Model $model) { + * traceLog("New relation {$relationName} was created", $attachedIdList); + * }); + * + */ + $this->parent->fireEvent('model.relation.afterAttach', [$this->relationName, $attachedIdList, $insertData]); + } + + /** + * Override detach() method of BelongToMany relation. + * This is necessary in order to fire 'model.relation.beforeDetach', 'model.relation.afterDetach' events + * @param CollectionBase|Model|array|null $ids + * @param bool $touch + * @return int|void + */ + public function detach($ids = null, $touch = true) + { + $attachedIdList = $this->parseIds($ids); + if (empty($attachedIdList)) { + $attachedIdList = $this->newPivotQuery()->lists($this->relatedPivotKey); + } + + /** + * @event model.relation.beforeDetach + * Called before removing a relation between models (only for BelongsToMany relation) + * + * Example usage: + * + * $model->bindEvent('model.relation.beforeDetach', function (string $relationName, array $attachedIdList) use (\Winter\Storm\Database\Model $model) { + * if (!$model->isRelationValid($attachedIdList)) { + * throw new \Exception("Invalid relation!"); + * return false; + * } + * }); + * + */ + if ($this->parent->fireEvent('model.relation.beforeDetach', [$this->relationName, $attachedIdList], true) === false) { + return; + } + + /** + * @see Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithPivotTable + */ + parent::detach($attachedIdList, $touch); + + /** + * @event model.relation.afterDetach + * Called after removing a relation between models (only for BelongsToMany relation) + * + * Example usage: + * + * $model->bindEvent('model.relation.afterDetach', function (string $relationName, array $attachedIdList) use (\Winter\Storm\Database\Model $model) { + * traceLog("Relation {$relationName} was removed", $attachedIdList); + * }); + * + */ + $this->parent->fireEvent('model.relation.afterDetach', [$this->relationName, $attachedIdList]); + } + + /** + * Adds a model to this relationship type. + */ + public function add(Model $model, $sessionKey = null, $pivotData = []) + { + if (is_array($sessionKey)) { + $pivotData = $sessionKey; + $sessionKey = null; + } + + if ($sessionKey === null || $sessionKey === false) { + $this->attach($model->getKey(), $pivotData); + $this->parent->reloadRelations($this->relationName); + } + else { + $this->parent->bindDeferred($this->relationName, $model, $sessionKey, $pivotData); + } + } + + /** + * Removes a model from this relationship type. + */ + public function remove(Model $model, $sessionKey = null) + { + if ($sessionKey === null) { + $this->detach($model->getKey()); + $this->parent->reloadRelations($this->relationName); + } + else { + $this->parent->unbindDeferred($this->relationName, $model, $sessionKey); + } + } + + /** + * Get a paginator for the "select" statement. Complies with Winter Storm. + * + * @param int $perPage + * @param int $currentPage + * @param array $columns + * @param string $pageName + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + public function paginate($perPage = 15, $currentPage = null, $columns = ['*'], $pageName = 'page') + { + $this->query->addSelect($this->shouldSelect($columns)); + + $paginator = $this->query->paginate($perPage, $currentPage, $columns); + + $this->hydratePivotRelation($paginator->items()); + + return $paginator; + } + + /** + * Create a new pivot model instance. + * + * @param array $attributes + * @param bool $exists + * @return \Illuminate\Database\Eloquent\Relations\Pivot + */ + public function newPivot(array $attributes = [], $exists = false) + { + /* + * Winter looks to the relationship parent + */ + $pivot = $this->parent->newRelationPivot($this->relationName, $this->parent, $attributes, $this->table, $exists); + + /* + * Laravel looks to the related model + */ + if (empty($pivot)) { + $pivot = $this->related->newPivot($this->parent, $attributes, $this->table, $exists); + } + + return $pivot->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey); + } + + /** + * Helper for setting this relationship using various expected + * values. For example, $model->relation = $value; + */ + public function setSimpleValue($value) + { + $relationModel = $this->getRelated(); + + /* + * Nulling the relationship + */ + if (!$value) { + // Disassociate in memory immediately + $this->parent->setRelation($this->relationName, $relationModel->newCollection()); + + // Perform sync when the model is saved + $this->parent->bindEventOnce('model.afterSave', function () use ($value) { + $this->detach(); + }); + return; + } + + /* + * Convert models to keys + */ + if ($value instanceof Model) { + $value = $value->getKey(); + } + elseif (is_array($value)) { + foreach ($value as $_key => $_value) { + if ($_value instanceof Model) { + $value[$_key] = $_value->getKey(); + } + } + } + + /* + * Convert scalar to array + */ + if (!is_array($value) && !$value instanceof Collection) { + $value = [$value]; + } + + /* + * Setting the relationship + */ + $relationCollection = $value instanceof Collection + ? $value + : $relationModel->whereIn($relationModel->getKeyName(), $value)->get(); + + // Associate in memory immediately + $this->parent->setRelation($this->relationName, $relationCollection); + + // Perform sync when the model is saved + $this->parent->bindEventOnce('model.afterSave', function () use ($value) { + $this->sync($value); + }); + } + + /** + * Helper for getting this relationship simple value, + * generally useful with form values. + */ + public function getSimpleValue() + { + $value = []; + + $relationName = $this->relationName; + + $sessionKey = $this->parent->sessionKey; + + if ($this->parent->relationLoaded($relationName)) { + $related = $this->getRelated(); + + $value = $this->parent->getRelation($relationName)->pluck($related->getKeyName())->all(); + } + else { + $value = $this->allRelatedIds($sessionKey)->all(); + } + + return $value; + } + + /** + * Get all of the IDs for the related models, with deferred binding support + * + * @param string $sessionKey + * @return \Winter\Storm\Support\Collection + */ + public function allRelatedIds($sessionKey = null) + { + $related = $this->getRelated(); + + $fullKey = $related->getQualifiedKeyName(); + + $query = $sessionKey ? $this->withDeferred($sessionKey) : $this; + + return $query->getQuery()->select($fullKey)->pluck($related->getKeyName()); + } + + /** + * Get the fully qualified foreign key for the relation. + * + * @return string + */ + public function getForeignKey() + { + return $this->table.'.'.$this->foreignPivotKey; + } + + /** + * Get the fully qualified "other key" for the relation. + * + * @return string + */ + public function getOtherKey() + { + return $this->table.'.'.$this->relatedPivotKey; + } + + /** + * @deprecated Use allRelatedIds instead. Remove if year >= 2018. + */ + public function getRelatedIds($sessionKey = null) + { + traceLog('Method BelongsToMany::getRelatedIds has been deprecated, use BelongsToMany::allRelatedIds instead.'); + return $this->allRelatedIds($sessionKey)->all(); + } +} diff --git a/src/Database/Relations/Concerns/DeferOneOrMany.php b/src/Database/Relations/Concerns/DeferOneOrMany.php new file mode 100644 index 00000000..60586746 --- /dev/null +++ b/src/Database/Relations/Concerns/DeferOneOrMany.php @@ -0,0 +1,133 @@ +query; + + $newQuery = $modelQuery->getQuery()->newQuery(); + + $newQuery->from($this->related->getTable()); + + /* + * No join table will be used, strip the selected "pivot_" columns + */ + if ($this instanceof BelongsToManyBase) { + $this->orphanMode = true; + } + + $newQuery->where(function ($query) use ($sessionKey) { + + if ($this->parent->exists) { + if ($this instanceof MorphToMany) { + /* + * Custom query for MorphToMany since a "join" cannot be used + */ + $query->whereExists(function ($query) { + $query + ->select($this->parent->getConnection()->raw(1)) + ->from($this->table) + ->where($this->getOtherKey(), DbDongle::raw( + DbDongle::getTablePrefix().$this->related->getQualifiedKeyName() + )) + ->where($this->getForeignKey(), $this->parent->getKey()) + ->where($this->getMorphType(), $this->getMorphClass()); + }); + } + elseif ($this instanceof BelongsToManyBase) { + /* + * Custom query for BelongsToManyBase since a "join" cannot be used + */ + $query->whereExists(function ($query) { + $query + ->select($this->parent->getConnection()->raw(1)) + ->from($this->table) + ->where($this->getOtherKey(), DbDongle::raw( + DbDongle::getTablePrefix().$this->related->getQualifiedKeyName() + )) + ->where($this->getForeignKey(), $this->parent->getKey()); + }); + } + else { + /* + * Trick the relation to add constraints to this nested query + */ + $this->query = $query; + $this->addConstraints(); + } + + $this->addDefinedConstraintsToQuery($this); + } + + /* + * Bind (Add) + */ + $query = $query->orWhereIn($this->getWithDeferredQualifiedKeyName(), function ($query) use ($sessionKey) { + $query + ->select('slave_id') + ->from('deferred_bindings') + ->where('master_field', $this->relationName) + ->where('master_type', get_class($this->parent)) + ->where('session_key', $sessionKey) + ->where('is_bind', 1); + }); + }); + + /* + * Unbind (Remove) + */ + $newQuery->whereNotIn($this->getWithDeferredQualifiedKeyName(), function ($query) use ($sessionKey) { + $query + ->select('slave_id') + ->from('deferred_bindings') + ->where('master_field', $this->relationName) + ->where('master_type', get_class($this->parent)) + ->where('session_key', $sessionKey) + ->where('is_bind', 0) + ->whereRaw(DbDongle::parse('id > ifnull((select max(id) from '.DbDongle::getTablePrefix().'deferred_bindings where + slave_id = '.$this->getWithDeferredQualifiedKeyName().' and + master_field = ? and + master_type = ? and + session_key = ? and + is_bind = ? + ), 0)'), [ + $this->relationName, + get_class($this->parent), + $sessionKey, + 1 + ]); + }); + + $modelQuery->setQuery($newQuery); + + /* + * Apply global scopes + */ + foreach ($this->related->getGlobalScopes() as $identifier => $scope) { + $modelQuery->withGlobalScope($identifier, $scope); + } + + return $this->query = $modelQuery; + } + + /** + * Returns the related "slave id" key in a database friendly format. + * @return \Illuminate\Database\Query\Expression + */ + protected function getWithDeferredQualifiedKeyName() + { + return $this->parent->getConnection()->raw(DbDongle::cast( + DbDongle::getTablePrefix() . $this->related->getQualifiedKeyName(), + 'TEXT' + )); + } +} diff --git a/src/Database/Relations/Concerns/DefinedConstraints.php b/src/Database/Relations/Concerns/DefinedConstraints.php new file mode 100644 index 00000000..b539efce --- /dev/null +++ b/src/Database/Relations/Concerns/DefinedConstraints.php @@ -0,0 +1,124 @@ + 'is_published = 1' + */ +trait DefinedConstraints +{ + /** + * Set the defined constraints on the relation query. + * + * @return void + */ + public function addDefinedConstraints() + { + $args = $this->parent->getRelationDefinition($this->relationName); + + $this->addDefinedConstraintsToRelation($this, $args); + + $this->addDefinedConstraintsToQuery($this, $args); + } + + /** + * Add relation based constraints. + * + * @param Illuminate\Database\Eloquent\Relations\Relation $relation + * @param array $args + */ + public function addDefinedConstraintsToRelation($relation, $args = null) + { + if ($args === null) { + $args = $this->parent->getRelationDefinition($this->relationName); + } + + /* + * Default models (belongsTo) + */ + if ($defaultData = array_get($args, 'default')) { + $relation->withDefault($defaultData === true ? null : $defaultData); + } + + /* + * Pivot data (belongsToMany, morphToMany, morphByMany) + */ + if ($pivotData = array_get($args, 'pivot')) { + $relation->withPivot($pivotData); + } + + /* + * Pivot timestamps (belongsToMany, morphToMany, morphByMany) + */ + if (array_get($args, 'timestamps')) { + $relation->withTimestamps(); + } + + /* + * Count "helper" relation + */ + if ($count = array_get($args, 'count')) { + if ($relation instanceof BelongsToManyBase) { + $relation->countMode = true; + } + + $countSql = $this->parent->getConnection()->raw('count(*) as count'); + + $relation + ->select($relation->getForeignKey(), $countSql) + ->groupBy($relation->getForeignKey()) + ->orderBy($relation->getForeignKey()) + ; + } + } + + /** + * Add query based constraints. + * + * @param Winter\Storm\Database\QueryBuilder $query + * @param array $args + */ + public function addDefinedConstraintsToQuery($query, $args = null) + { + if ($args === null) { + $args = $this->parent->getRelationDefinition($this->relationName); + } + + /* + * Conditions + */ + if ($conditions = array_get($args, 'conditions')) { + $query->whereRaw($conditions); + } + + /* + * Sort order + */ + $hasCountArg = array_get($args, 'count') !== null; + if (($orderBy = array_get($args, 'order')) && !$hasCountArg) { + if (!is_array($orderBy)) { + $orderBy = [$orderBy]; + } + + foreach ($orderBy as $order) { + $column = $order; + $direction = 'asc'; + + $parts = explode(' ', $order); + if (count($parts) > 1) { + list($column, $direction) = $parts; + } + + $query->orderBy($column, $direction); + } + } + + /* + * Scope + */ + if ($scope = array_get($args, 'scope')) { + $query->$scope($this->parent); + } + } +} diff --git a/src/Database/Relations/Concerns/HasOneOrMany.php b/src/Database/Relations/Concerns/HasOneOrMany.php new file mode 100644 index 00000000..24cb271e --- /dev/null +++ b/src/Database/Relations/Concerns/HasOneOrMany.php @@ -0,0 +1,134 @@ +add($model, $sessionKey); + return $model->save() ? $model : false; + } + + /** + * Alias for the addMany() method. + * @param array $models + * @return array + */ + public function saveMany($models, $sessionKey = null) + { + $this->addMany($models, $sessionKey); + + return $models; + } + + /** + * Create a new instance of this related model with deferred binding support. + */ + public function create(array $attributes = [], $sessionKey = null) + { + $model = parent::create($attributes); + + if ($sessionKey !== null) { + $this->add($model, $sessionKey); + } + + return $model; + } + + /** + * Adds a model to this relationship type. + */ + public function add(Model $model, $sessionKey = null) + { + if ($sessionKey === null) { + $model->setAttribute($this->getForeignKeyName(), $this->getParentKey()); + + if (!$model->exists || $model->isDirty()) { + $model->save(); + } + + /* + * Use the opportunity to set the relation in memory + */ + if ($this instanceof HasOne) { + $this->parent->setRelation($this->relationName, $model); + } + else { + $this->parent->reloadRelations($this->relationName); + } + } + else { + $this->parent->bindDeferred($this->relationName, $model, $sessionKey); + } + } + + /** + * Attach an array of models to the parent instance with deferred binding support. + * @param array $models + * @return void + */ + public function addMany($models, $sessionKey = null) + { + foreach ($models as $model) { + $this->add($model, $sessionKey); + } + } + + /** + * Removes a model from this relationship type. + */ + public function remove(Model $model, $sessionKey = null) + { + if ($sessionKey === null) { + $model->setAttribute($this->getForeignKeyName(), null); + $model->save(); + + /* + * Use the opportunity to set the relation in memory + */ + if ($this instanceof HasOne) { + $this->parent->setRelation($this->relationName, null); + } + else { + $this->parent->reloadRelations($this->relationName); + } + } + else { + $this->parent->unbindDeferred($this->relationName, $model, $sessionKey); + } + } + + /** + * Get the foreign key for the relationship. + * @return string + */ + public function getForeignKey() + { + return $this->foreignKey; + } + + /** + * Get the associated "other" key of the relationship. + * @return string + */ + public function getOtherKey() + { + return $this->localKey; + } +} diff --git a/src/Database/Relations/Concerns/MorphOneOrMany.php b/src/Database/Relations/Concerns/MorphOneOrMany.php new file mode 100644 index 00000000..b7166aee --- /dev/null +++ b/src/Database/Relations/Concerns/MorphOneOrMany.php @@ -0,0 +1,101 @@ +add($model, $sessionKey); + return $model->save() ? $model : false; + } + + /** + * Create a new instance of this related model with deferred binding support. + */ + public function create(array $attributes = [], $sessionKey = null) + { + $model = parent::create($attributes); + + if ($sessionKey !== null) { + $this->add($model, $sessionKey); + } + + return $model; + } + + /** + * Adds a model to this relationship type. + */ + public function add(Model $model, $sessionKey = null) + { + if ($sessionKey === null) { + $model->setAttribute($this->getForeignKeyName(), $this->getParentKey()); + $model->setAttribute($this->getMorphType(), $this->morphClass); + $model->save(); + + /* + * Use the opportunity to set the relation in memory + */ + if ($this instanceof MorphOne) { + $this->parent->setRelation($this->relationName, $model); + } + else { + $this->parent->reloadRelations($this->relationName); + } + } + else { + $this->parent->bindDeferred($this->relationName, $model, $sessionKey); + } + } + + /** + * Removes a model from this relationship type. + */ + public function remove(Model $model, $sessionKey = null) + { + if ($sessionKey === null) { + $options = $this->parent->getRelationDefinition($this->relationName); + + if (array_get($options, 'delete', false)) { + $model->delete(); + } + else { + /* + * Make this model an orphan ;~( + */ + $model->setAttribute($this->getForeignKeyName(), null); + $model->setAttribute($this->getMorphType(), null); + $model->save(); + } + + /* + * Use the opportunity to set the relation in memory + */ + if ($this instanceof MorphOne) { + $this->parent->setRelation($this->relationName, null); + } + else { + $this->parent->reloadRelations($this->relationName); + } + } + else { + $this->parent->unbindDeferred($this->relationName, $model, $sessionKey); + } + } +} diff --git a/src/Database/Relations/DeferOneOrMany.php b/src/Database/Relations/DeferOneOrMany.php index 2ff605b7..abc6a672 100644 --- a/src/Database/Relations/DeferOneOrMany.php +++ b/src/Database/Relations/DeferOneOrMany.php @@ -1,128 +1,9 @@ query; - - $newQuery = $modelQuery->getQuery()->newQuery(); - - $newQuery->from($this->related->getTable()); - - /* - * No join table will be used, strip the selected "pivot_" columns - */ - if ($this instanceof BelongsToManyBase) { - $this->orphanMode = true; - } - - $newQuery->where(function ($query) use ($sessionKey) { - - if ($this->parent->exists) { - if ($this instanceof MorphToMany) { - /* - * Custom query for MorphToMany since a "join" cannot be used - */ - $query->whereExists(function ($query) { - $query - ->select($this->parent->getConnection()->raw(1)) - ->from($this->table) - ->where($this->getOtherKey(), DbDongle::raw(DbDongle::getTablePrefix().$this->related->getQualifiedKeyName())) - ->where($this->getForeignKey(), $this->parent->getKey()) - ->where($this->getMorphType(), $this->getMorphClass()); - }); - } - elseif ($this instanceof BelongsToManyBase) { - /* - * Custom query for BelongsToManyBase since a "join" cannot be used - */ - $query->whereExists(function ($query) { - $query - ->select($this->parent->getConnection()->raw(1)) - ->from($this->table) - ->where($this->getOtherKey(), DbDongle::raw(DbDongle::getTablePrefix().$this->related->getQualifiedKeyName())) - ->where($this->getForeignKey(), $this->parent->getKey()); - }); - } - else { - /* - * Trick the relation to add constraints to this nested query - */ - $this->query = $query; - $this->addConstraints(); - } - - $this->addDefinedConstraintsToQuery($this); - } - - /* - * Bind (Add) - */ - $query = $query->orWhereIn($this->getWithDeferredQualifiedKeyName(), function ($query) use ($sessionKey) { - $query - ->select('slave_id') - ->from('deferred_bindings') - ->where('master_field', $this->relationName) - ->where('master_type', get_class($this->parent)) - ->where('session_key', $sessionKey) - ->where('is_bind', 1); - }); - }); - - /* - * Unbind (Remove) - */ - $newQuery->whereNotIn($this->getWithDeferredQualifiedKeyName(), function ($query) use ($sessionKey) { - $query - ->select('slave_id') - ->from('deferred_bindings') - ->where('master_field', $this->relationName) - ->where('master_type', get_class($this->parent)) - ->where('session_key', $sessionKey) - ->where('is_bind', 0) - ->whereRaw(DbDongle::parse('id > ifnull((select max(id) from '.DbDongle::getTablePrefix().'deferred_bindings where - slave_id = '.$this->getWithDeferredQualifiedKeyName().' and - master_field = ? and - master_type = ? and - session_key = ? and - is_bind = ? - ), 0)'), [ - $this->relationName, - get_class($this->parent), - $sessionKey, - 1 - ]); - }); - - $modelQuery->setQuery($newQuery); - - /* - * Apply global scopes - */ - foreach ($this->related->getGlobalScopes() as $identifier => $scope) { - $modelQuery->withGlobalScope($identifier, $scope); - } - - return $this->query = $modelQuery; - } - - /** - * Returns the related "slave id" key in a database friendly format. - * @return \Illuminate\Database\Query\Expression - */ - protected function getWithDeferredQualifiedKeyName() - { - return $this->parent->getConnection()->raw(DbDongle::cast( - DbDongle::getTablePrefix() . $this->related->getQualifiedKeyName(), - 'TEXT' - )); - } + use Concerns\DeferOneOrMany; } diff --git a/src/Database/Relations/DefinedConstraints.php b/src/Database/Relations/DefinedConstraints.php index 21b1bf7f..82bb7712 100644 --- a/src/Database/Relations/DefinedConstraints.php +++ b/src/Database/Relations/DefinedConstraints.php @@ -1,124 +1,9 @@ 'is_published = 1' +/** + * @deprecated v1.2.0 Use `\Winter\Storm\Database\Relations\Concerns\DefinedConstraints` instead. */ trait DefinedConstraints { - /** - * Set the defined constraints on the relation query. - * - * @return void - */ - public function addDefinedConstraints() - { - $args = $this->parent->getRelationDefinition($this->relationName); - - $this->addDefinedConstraintsToRelation($this, $args); - - $this->addDefinedConstraintsToQuery($this, $args); - } - - /** - * Add relation based constraints. - * - * @param Illuminate\Database\Eloquent\Relations\Relation $relation - * @param array $args - */ - public function addDefinedConstraintsToRelation($relation, $args = null) - { - if ($args === null) { - $args = $this->parent->getRelationDefinition($this->relationName); - } - - /* - * Default models (belongsTo) - */ - if ($defaultData = array_get($args, 'default')) { - $relation->withDefault($defaultData === true ? null : $defaultData); - } - - /* - * Pivot data (belongsToMany, morphToMany, morphByMany) - */ - if ($pivotData = array_get($args, 'pivot')) { - $relation->withPivot($pivotData); - } - - /* - * Pivot timestamps (belongsToMany, morphToMany, morphByMany) - */ - if (array_get($args, 'timestamps')) { - $relation->withTimestamps(); - } - - /* - * Count "helper" relation - */ - if ($count = array_get($args, 'count')) { - if ($relation instanceof BelongsToManyBase) { - $relation->countMode = true; - } - - $countSql = $this->parent->getConnection()->raw('count(*) as count'); - - $relation - ->select($relation->getForeignKey(), $countSql) - ->groupBy($relation->getForeignKey()) - ->orderBy($relation->getForeignKey()) - ; - } - } - - /** - * Add query based constraints. - * - * @param Winter\Storm\Database\QueryBuilder $query - * @param array $args - */ - public function addDefinedConstraintsToQuery($query, $args = null) - { - if ($args === null) { - $args = $this->parent->getRelationDefinition($this->relationName); - } - - /* - * Conditions - */ - if ($conditions = array_get($args, 'conditions')) { - $query->whereRaw($conditions); - } - - /* - * Sort order - */ - $hasCountArg = array_get($args, 'count') !== null; - if (($orderBy = array_get($args, 'order')) && !$hasCountArg) { - if (!is_array($orderBy)) { - $orderBy = [$orderBy]; - } - - foreach ($orderBy as $order) { - $column = $order; - $direction = 'asc'; - - $parts = explode(' ', $order); - if (count($parts) > 1) { - list($column, $direction) = $parts; - } - - $query->orderBy($column, $direction); - } - } - - /* - * Scope - */ - if ($scope = array_get($args, 'scope')) { - $query->$scope($this->parent); - } - } + use Concerns\DefinedConstraints; } diff --git a/src/Database/Relations/HasMany.php b/src/Database/Relations/HasMany.php index cfa1656b..f93da9e1 100644 --- a/src/Database/Relations/HasMany.php +++ b/src/Database/Relations/HasMany.php @@ -8,8 +8,8 @@ class HasMany extends HasManyBase { - use HasOneOrMany; - use DefinedConstraints; + use Concerns\HasOneOrMany; + use Concerns\DefinedConstraints; /** * Create a new has many relationship instance. diff --git a/src/Database/Relations/HasManyThrough.php b/src/Database/Relations/HasManyThrough.php index 02f05f1e..ff0a8c55 100644 --- a/src/Database/Relations/HasManyThrough.php +++ b/src/Database/Relations/HasManyThrough.php @@ -6,7 +6,7 @@ class HasManyThrough extends HasManyThroughBase { - use DefinedConstraints; + use Concerns\DefinedConstraints; /** * @var string The "name" of the relationship. diff --git a/src/Database/Relations/HasOne.php b/src/Database/Relations/HasOne.php index 7c975db5..4b304a5e 100644 --- a/src/Database/Relations/HasOne.php +++ b/src/Database/Relations/HasOne.php @@ -6,8 +6,8 @@ class HasOne extends HasOneBase { - use HasOneOrMany; - use DefinedConstraints; + use Concerns\HasOneOrMany; + use Concerns\DefinedConstraints; /** * Create a new "hasOne" relationship. diff --git a/src/Database/Relations/HasOneOrMany.php b/src/Database/Relations/HasOneOrMany.php index 82ff7444..40affdbc 100644 --- a/src/Database/Relations/HasOneOrMany.php +++ b/src/Database/Relations/HasOneOrMany.php @@ -1,133 +1,9 @@ add($model, $sessionKey); - return $model->save() ? $model : false; - } - - /** - * Alias for the addMany() method. - * @param array $models - * @return array - */ - public function saveMany($models, $sessionKey = null) - { - $this->addMany($models, $sessionKey); - - return $models; - } - - /** - * Create a new instance of this related model with deferred binding support. - */ - public function create(array $attributes = [], $sessionKey = null) - { - $model = parent::create($attributes); - - if ($sessionKey !== null) { - $this->add($model, $sessionKey); - } - - return $model; - } - - /** - * Adds a model to this relationship type. - */ - public function add(Model $model, $sessionKey = null) - { - if ($sessionKey === null) { - $model->setAttribute($this->getForeignKeyName(), $this->getParentKey()); - - if (!$model->exists || $model->isDirty()) { - $model->save(); - } - - /* - * Use the opportunity to set the relation in memory - */ - if ($this instanceof HasOne) { - $this->parent->setRelation($this->relationName, $model); - } - else { - $this->parent->reloadRelations($this->relationName); - } - } - else { - $this->parent->bindDeferred($this->relationName, $model, $sessionKey); - } - } - - /** - * Attach an array of models to the parent instance with deferred binding support. - * @param array $models - * @return void - */ - public function addMany($models, $sessionKey = null) - { - foreach ($models as $model) { - $this->add($model, $sessionKey); - } - } - - /** - * Removes a model from this relationship type. - */ - public function remove(Model $model, $sessionKey = null) - { - if ($sessionKey === null) { - $model->setAttribute($this->getForeignKeyName(), null); - $model->save(); - - /* - * Use the opportunity to set the relation in memory - */ - if ($this instanceof HasOne) { - $this->parent->setRelation($this->relationName, null); - } - else { - $this->parent->reloadRelations($this->relationName); - } - } - else { - $this->parent->unbindDeferred($this->relationName, $model, $sessionKey); - } - } - - /** - * Get the foreign key for the relationship. - * @return string - */ - public function getForeignKey() - { - return $this->foreignKey; - } - - /** - * Get the associated "other" key of the relationship. - * @return string - */ - public function getOtherKey() - { - return $this->localKey; - } + use Concerns\HasOneOrMany; } diff --git a/src/Database/Relations/HasOneThrough.php b/src/Database/Relations/HasOneThrough.php index 8a4497a5..12f54f0a 100644 --- a/src/Database/Relations/HasOneThrough.php +++ b/src/Database/Relations/HasOneThrough.php @@ -6,7 +6,7 @@ class HasOneThrough extends HasOneThroughBase { - use DefinedConstraints; + use Concerns\DefinedConstraints; /** * @var string The "name" of the relationship. diff --git a/src/Database/Relations/MorphMany.php b/src/Database/Relations/MorphMany.php index 10d4df8d..d42b7f3d 100644 --- a/src/Database/Relations/MorphMany.php +++ b/src/Database/Relations/MorphMany.php @@ -8,8 +8,8 @@ class MorphMany extends MorphManyBase { - use MorphOneOrMany; - use DefinedConstraints; + use Concerns\MorphOneOrMany; + use Concerns\DefinedConstraints; /** * Create a new has many relationship instance. diff --git a/src/Database/Relations/MorphOne.php b/src/Database/Relations/MorphOne.php index 77afefe7..830aedee 100644 --- a/src/Database/Relations/MorphOne.php +++ b/src/Database/Relations/MorphOne.php @@ -6,8 +6,8 @@ class MorphOne extends MorphOneBase { - use MorphOneOrMany; - use DefinedConstraints; + use Concerns\MorphOneOrMany; + use Concerns\DefinedConstraints; /** * Create a new has many relationship instance. diff --git a/src/Database/Relations/MorphOneOrMany.php b/src/Database/Relations/MorphOneOrMany.php index 43c1b3c5..619cc371 100644 --- a/src/Database/Relations/MorphOneOrMany.php +++ b/src/Database/Relations/MorphOneOrMany.php @@ -1,101 +1,9 @@ add($model, $sessionKey); - return $model->save() ? $model : false; - } - - /** - * Create a new instance of this related model with deferred binding support. - */ - public function create(array $attributes = [], $sessionKey = null) - { - $model = parent::create($attributes); - - if ($sessionKey !== null) { - $this->add($model, $sessionKey); - } - - return $model; - } - - /** - * Adds a model to this relationship type. - */ - public function add(Model $model, $sessionKey = null) - { - if ($sessionKey === null) { - $model->setAttribute($this->getForeignKeyName(), $this->getParentKey()); - $model->setAttribute($this->getMorphType(), $this->morphClass); - $model->save(); - - /* - * Use the opportunity to set the relation in memory - */ - if ($this instanceof MorphOne) { - $this->parent->setRelation($this->relationName, $model); - } - else { - $this->parent->reloadRelations($this->relationName); - } - } - else { - $this->parent->bindDeferred($this->relationName, $model, $sessionKey); - } - } - - /** - * Removes a model from this relationship type. - */ - public function remove(Model $model, $sessionKey = null) - { - if ($sessionKey === null) { - $options = $this->parent->getRelationDefinition($this->relationName); - - if (array_get($options, 'delete', false)) { - $model->delete(); - } - else { - /* - * Make this model an orphan ;~( - */ - $model->setAttribute($this->getForeignKeyName(), null); - $model->setAttribute($this->getMorphType(), null); - $model->save(); - } - - /* - * Use the opportunity to set the relation in memory - */ - if ($this instanceof MorphOne) { - $this->parent->setRelation($this->relationName, null); - } - else { - $this->parent->reloadRelations($this->relationName); - } - } - else { - $this->parent->unbindDeferred($this->relationName, $model, $sessionKey); - } - } + use Concerns\MorphOneOrMany; } diff --git a/src/Database/Relations/MorphTo.php b/src/Database/Relations/MorphTo.php index 92e567ea..fcc9263d 100644 --- a/src/Database/Relations/MorphTo.php +++ b/src/Database/Relations/MorphTo.php @@ -6,7 +6,7 @@ class MorphTo extends MorphToBase { - use DefinedConstraints; + use Concerns\DefinedConstraints; /** * @var string The "name" of the relationship. diff --git a/src/Database/Relations/MorphToMany.php b/src/Database/Relations/MorphToMany.php index b2771ca0..d256bf43 100644 --- a/src/Database/Relations/MorphToMany.php +++ b/src/Database/Relations/MorphToMany.php @@ -1,19 +1,21 @@ addDefinedConstraints(); } - /** - * Set the where clause for the relation query. - * - * @return $this - */ - protected function addWhereConstraints() - { - parent::addWhereConstraints(); - - $this->query->where($this->table.'.'.$this->morphType, $this->morphClass); - - return $this; - } - - /** - * Set the constraints for an eager load of the relation. - * - * @param array $models - * @return void - */ - public function addEagerConstraints(array $models) - { - parent::addEagerConstraints($models); - - $this->query->where($this->table.'.'.$this->morphType, $this->morphClass); - } - - /** - * Create a new pivot attachment record. - * - * @param int $id - * @param bool $timed - * @return array - */ - protected function baseAttachRecord($id, $timed) - { - return Arr::add( - parent::baseAttachRecord($id, $timed), - $this->morphType, - $this->morphClass - ); - } - - /** - * Add the constraints for a relationship count query. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param \Illuminate\Database\Eloquent\Builder $parentQuery - * @param array|mixed $columns - * @return \Illuminate\Database\Eloquent\Builder - */ - public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) - { - return parent::getRelationExistenceQuery($query, $parentQuery, $columns)->where( - $this->table.'.'.$this->morphType, - $this->morphClass - ); - } - /** * Create a new query builder for the pivot table. * @@ -172,24 +115,4 @@ public function newPivot(array $attributes = [], $exists = false) return $pivot; } - - /** - * Get the foreign key "type" name. - * - * @return string - */ - public function getMorphType() - { - return $this->morphType; - } - - /** - * Get the class name of the parent model. - * - * @return string - */ - public function getMorphClass() - { - return $this->morphClass; - } } From cf251aa41a54638405f2a8a242fdb05b0a40e184 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 3 May 2022 13:28:14 +0800 Subject: [PATCH 056/108] Make Argon extend Illuminate's Carbon wrapper --- src/Argon/Argon.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Argon/Argon.php b/src/Argon/Argon.php index c5af57c5..81d2535b 100644 --- a/src/Argon/Argon.php +++ b/src/Argon/Argon.php @@ -1,11 +1,12 @@ Date: Tue, 3 May 2022 13:28:32 +0800 Subject: [PATCH 057/108] Numerous fixes to docblocks and signatures --- src/Database/Connections/Connection.php | 29 ++++++++++++++----- src/Database/Connections/MySqlConnection.php | 8 ++--- .../Connections/PostgresConnection.php | 6 ++-- src/Database/Connections/SQLiteConnection.php | 6 ++-- .../Connections/SqlServerConnection.php | 8 ++--- src/Database/Connectors/ConnectionFactory.php | 4 ++- src/Database/DataFeed.php | 23 +++++++-------- src/Database/Model.php | 12 ++++---- src/Database/Pivot.php | 12 ++++---- src/Database/QueryBuilder.php | 15 +++------- src/Database/Relations/AttachMany.php | 11 ++++--- src/Database/Relations/AttachOne.php | 9 +++--- .../Relations/Concerns/AttachOneOrMany.php | 6 ++-- .../Concerns/BelongsOrMorphsToMany.php | 15 +++++----- .../Relations/Concerns/DefinedConstraints.php | 4 +-- src/Database/Relations/MorphToMany.php | 23 --------------- src/Database/Traits/Purgeable.php | 10 +++---- src/Parse/EnvFile.php | 7 ++++- src/Parse/PHP/ArrayFile.php | 2 +- src/Parse/PHP/ArrayPrinter.php | 2 +- src/Router/UrlGenerator.php | 11 ++++--- 21 files changed, 106 insertions(+), 117 deletions(-) diff --git a/src/Database/Connections/Connection.php b/src/Database/Connections/Connection.php index dcd804cb..45698669 100644 --- a/src/Database/Connections/Connection.php +++ b/src/Database/Connections/Connection.php @@ -39,9 +39,7 @@ public static function flushDuplicateCache() */ public function logQuery($query, $bindings, $time = null) { - if (isset($this->events)) { - $this->events->fire('illuminate.query', [$query, $bindings, $time, $this->getName()]); - } + $this->fireEvent('illuminate.query', [$query, $bindings, $time, $this->getName()]); parent::logQuery($query, $bindings, $time); } @@ -50,14 +48,31 @@ public function logQuery($query, $bindings, $time = null) * Fire an event for this connection. * * @param string $event - * @return void + * @return array|null */ protected function fireConnectionEvent($event) { - if (isset($this->events)) { - $this->events->fire('connection.'.$this->getName().'.'.$event, $this); - } + $this->fireEvent('connection.'.$this->getName().'.'.$event, $this); parent::fireConnectionEvent($event); } + + /** + * Fire the given event if possible. + * + * @param string $event + * @param array|object $attributes + * @return void + */ + protected function fireEvent($event, $attributes = []) + { + /** @var \Winter\Storm\Events\Dispatcher|null */ + $eventManager = $this->events; + + if (!isset($eventManager)) { + return; + } + + $eventManager->fire($event, $attributes); + } } diff --git a/src/Database/Connections/MySqlConnection.php b/src/Database/Connections/MySqlConnection.php index 58e2def2..d3ec20d0 100644 --- a/src/Database/Connections/MySqlConnection.php +++ b/src/Database/Connections/MySqlConnection.php @@ -1,11 +1,11 @@ processCollection(); $bindings = $query->bindings; $records = sprintf("(%s) as records", $query->toSql()); - $result = Db::table(Db::raw($records))->selectRaw("COUNT(*) as total"); + $result = DB::table(DB::raw($records))->selectRaw("COUNT(*) as total"); // Set the bindings, if present foreach ($bindings as $type => $params) { @@ -134,8 +133,8 @@ public function get() */ $mixedArray = []; foreach ($records as $record) { - $tagName = $record->{$this->tagVar}; - $mixedArray[$tagName][] = $record->id; + $tagName = $record->getAttribute($this->tagVar); + $mixedArray[$tagName][] = $record->getKey(); } /* @@ -155,8 +154,8 @@ public function get() foreach ($records as $record) { $tagName = $record->{$this->tagVar}; - $obj = $collectionArray[$tagName]->find($record->id); - $obj->{$this->tagVar} = $tagName; + $obj = $collectionArray[$tagName]->find($record->getKey()); + $obj->setAttribute($this->tagVar, $tagName); $dataArray[] = $obj; } diff --git a/src/Database/Model.php b/src/Database/Model.php index f466c582..cee80f77 100644 --- a/src/Database/Model.php +++ b/src/Database/Model.php @@ -476,7 +476,7 @@ public static function fetched($callback) /** * Checks if an attribute is jsonable or not. * - * @return array + * @return bool */ public function isJsonable($key) { @@ -529,7 +529,7 @@ public function getObservableEvents() /** * Get a fresh timestamp for the model. * - * @return \Winter\Storm\Argon\Argon + * @return \Illuminate\Support\Carbon */ public function freshTimestamp() { @@ -604,10 +604,10 @@ protected function asDateTime($value) /** * Convert a DateTime to a storable string. * - * @param \DateTime|int $value - * @return string + * @param \DateTime|int|null $value + * @return string|null */ - public function fromDateTime($value) + public function fromDateTime($value = null) { if (is_null($value)) { return $value; @@ -669,7 +669,7 @@ public function __get($name) public function __set($name, $value) { - return $this->extendableSet($name, $value); + $this->extendableSet($name, $value); } public function __call($name, $params) diff --git a/src/Database/Pivot.php b/src/Database/Pivot.php index 66184bde..0339596b 100644 --- a/src/Database/Pivot.php +++ b/src/Database/Pivot.php @@ -28,7 +28,7 @@ class Pivot extends Model /** * The attributes that aren't mass assignable. * - * @var array + * @var string[]|bool */ protected $guarded = []; @@ -48,7 +48,7 @@ class Pivot extends Model * @param bool $exists * @return static */ - public static function fromAttributes(Model $parent, $attributes, $table, $exists = false) + public static function fromAttributes(ModelBase $parent, $attributes, $table, $exists = false) { $instance = new static; @@ -57,7 +57,7 @@ public static function fromAttributes(Model $parent, $attributes, $table, $exist // from the developer's point of view. We can use the parents to get these. $instance->parent = $parent; - $instance->timestamps = $instance->hasTimestampAttributes($attributes); + $instance->timestamps = $instance->hasTimestampAttributes(); // The pivot model is a "dynamic" model since we will set the tables dynamically // for the instance. This allows it work for any intermediate tables for the @@ -81,11 +81,11 @@ public static function fromAttributes(Model $parent, $attributes, $table, $exist * @param bool $exists * @return static */ - public static function fromRawAttributes(Model $parent, $attributes, $table, $exists = false) + public static function fromRawAttributes(ModelBase $parent, $attributes, $table, $exists = false) { $instance = static::fromAttributes($parent, [], $table, $exists); - $instance->timestamps = $instance->hasTimestampAttributes($attributes); + $instance->timestamps = $instance->hasTimestampAttributes(); $instance->setRawAttributes($attributes, $exists); @@ -108,7 +108,7 @@ protected function setKeysForSaveQuery($query) /** * Delete the pivot model record from the database. * - * @return int + * @return mixed */ public function delete() { diff --git a/src/Database/QueryBuilder.php b/src/Database/QueryBuilder.php index def80bae..f0b33e99 100644 --- a/src/Database/QueryBuilder.php +++ b/src/Database/QueryBuilder.php @@ -2,6 +2,7 @@ use App; use Winter\Storm\Support\Arr; +use Illuminate\Support\Collection as BaseCollection; use Illuminate\Database\Query\Builder as QueryBuilderBase; use Illuminate\Database\Query\Expression; @@ -17,7 +18,7 @@ class QueryBuilder extends QueryBuilderBase /** * The number of minutes to cache the query. * - * @var int + * @var int|null */ protected $cacheMinutes; @@ -112,14 +113,10 @@ public function get($columns = ['*']) * Check the memory cache before executing the query * * @param array $columns - * @return array + * @return BaseCollection */ protected function getDuplicateCached($columns = ['*']) { - if (is_null($this->columns)) { - $this->columns = $columns; - } - $cache = MemoryCache::instance(); if ($cache->has($this)) { @@ -140,14 +137,10 @@ protected function getDuplicateCached($columns = ['*']) * Execute the query as a cached "select" statement. * * @param array $columns - * @return array + * @return BaseCollection */ public function getCached($columns = ['*']) { - if (is_null($this->columns)) { - $this->columns = $columns; - } - // If the query is requested to be cached, we will cache it using a unique key // for this database connection and query statement, including the bindings // that are used on this query, providing great convenience when caching. diff --git a/src/Database/Relations/AttachMany.php b/src/Database/Relations/AttachMany.php index 6079f9b4..6442a89d 100644 --- a/src/Database/Relations/AttachMany.php +++ b/src/Database/Relations/AttachMany.php @@ -14,12 +14,11 @@ class AttachMany extends MorphManyBase * Create a new has many relationship instance. * @param Builder $query * @param Model $parent - * @param $type - * @param $id - * @param $isPublic - * @param $localKey + * @param string $type + * @param string $id + * @param bool $isPublic + * @param string $localKey * @param null|string $relationName - * @param null|string $keyType */ public function __construct(Builder $query, Model $parent, $type, $id, $isPublic, $localKey, $relationName = null) { @@ -81,7 +80,7 @@ public function getSimpleValue() if ($files) { $value = []; - foreach ($value as $file) { + foreach ($files as $file) { $value[] = $file->getPath(); } } diff --git a/src/Database/Relations/AttachOne.php b/src/Database/Relations/AttachOne.php index d4545962..a72231ae 100644 --- a/src/Database/Relations/AttachOne.php +++ b/src/Database/Relations/AttachOne.php @@ -14,12 +14,11 @@ class AttachOne extends MorphOneBase * Create a new has many relationship instance. * @param Builder $query * @param Model $parent - * @param $type - * @param $id - * @param $isPublic - * @param $localKey + * @param string $type + * @param string $id + * @param bool $isPublic + * @param string $localKey * @param null|string $relationName - * @param null|string $keyType */ public function __construct(Builder $query, Model $parent, $type, $id, $isPublic, $localKey, $relationName = null) { diff --git a/src/Database/Relations/Concerns/AttachOneOrMany.php b/src/Database/Relations/Concerns/AttachOneOrMany.php index 094ac2d8..441e6b21 100644 --- a/src/Database/Relations/Concerns/AttachOneOrMany.php +++ b/src/Database/Relations/Concerns/AttachOneOrMany.php @@ -118,7 +118,7 @@ public function save(Model $model, $sessionKey = null) $this->delete(); } - if (!array_key_exists('is_public', $model->attributes)) { + if (!array_key_exists('is_public', $model->getAttributes())) { $model->setAttribute('is_public', $this->isPublic()); } @@ -162,8 +162,8 @@ public function create(array $attributes = [], $sessionKey = null) */ public function add(Model $model, $sessionKey = null) { - if (!array_key_exists('is_public', $model->attributes)) { - $model->is_public = $this->isPublic(); + if (!array_key_exists('is_public', $model->getAttributes())) { + $model->setAttribute('is_public', $this->isPublic()); } if ($sessionKey === null) { diff --git a/src/Database/Relations/Concerns/BelongsOrMorphsToMany.php b/src/Database/Relations/Concerns/BelongsOrMorphsToMany.php index 839bc44a..6506f1ff 100644 --- a/src/Database/Relations/Concerns/BelongsOrMorphsToMany.php +++ b/src/Database/Relations/Concerns/BelongsOrMorphsToMany.php @@ -1,6 +1,5 @@ parent->setRelation($this->relationName, $relationModel->newCollection()); // Perform sync when the model is saved - $this->parent->bindEventOnce('model.afterSave', function () use ($value) { + $this->parent->bindEventOnce('model.afterSave', function () { $this->detach(); }); return; diff --git a/src/Database/Relations/Concerns/DefinedConstraints.php b/src/Database/Relations/Concerns/DefinedConstraints.php index b539efce..93271128 100644 --- a/src/Database/Relations/Concerns/DefinedConstraints.php +++ b/src/Database/Relations/Concerns/DefinedConstraints.php @@ -25,7 +25,7 @@ public function addDefinedConstraints() /** * Add relation based constraints. * - * @param Illuminate\Database\Eloquent\Relations\Relation $relation + * @param \Illuminate\Database\Eloquent\Relations\Relation $relation * @param array $args */ public function addDefinedConstraintsToRelation($relation, $args = null) @@ -76,7 +76,7 @@ public function addDefinedConstraintsToRelation($relation, $args = null) /** * Add query based constraints. * - * @param Winter\Storm\Database\QueryBuilder $query + * @param \Winter\Storm\Database\QueryBuilder $query * @param array $args */ public function addDefinedConstraintsToQuery($query, $args = null) diff --git a/src/Database/Relations/MorphToMany.php b/src/Database/Relations/MorphToMany.php index d256bf43..02a5633d 100644 --- a/src/Database/Relations/MorphToMany.php +++ b/src/Database/Relations/MorphToMany.php @@ -17,29 +17,6 @@ class MorphToMany extends BaseMorphToMany use Concerns\BelongsOrMorphsToMany; use Concerns\DefinedConstraints; - /** - * The type of the polymorphic relation. - * - * @var string - */ - protected $morphType; - - /** - * The class name of the morph type constraint. - * - * @var string - */ - protected $morphClass; - - /** - * Indicates if we are connecting the inverse of the relation. - * - * This primarily affects the morphClass constraint. - * - * @var bool - */ - protected $inverse; - /** * Create a new morph to many relationship instance. * diff --git a/src/Database/Traits/Purgeable.php b/src/Database/Traits/Purgeable.php index b30a1827..5bbd6792 100644 --- a/src/Database/Traits/Purgeable.php +++ b/src/Database/Traits/Purgeable.php @@ -54,15 +54,14 @@ public function addPurgeable($attributes = null) /** * Removes purged attributes from the dataset, used before saving. - * @param $attributes mixed Attribute(s) to purge, if unspecified, $purgable property is used + * @param array|string $attributesToPurge Attribute(s) to purge, if unspecified, $purgable property is used * @return array Current attribute set */ public function purgeAttributes($attributesToPurge = null) { if ($attributesToPurge !== null) { $purgeable = is_array($attributesToPurge) ? $attributesToPurge : [$attributesToPurge]; - } - else { + } else { $purgeable = $this->getPurgeableAttributes(); } @@ -70,10 +69,9 @@ public function purgeAttributes($attributesToPurge = null) $cleanAttributes = array_diff_key($attributes, array_flip($purgeable)); $originalAttributes = array_diff_key($attributes, $cleanAttributes); - if (is_array($this->originalPurgeableValues)) { + if (count($this->originalPurgeableValues)) { $this->originalPurgeableValues = array_merge($this->originalPurgeableValues, $originalAttributes); - } - else { + } else { $this->originalPurgeableValues = $originalAttributes; } diff --git a/src/Parse/EnvFile.php b/src/Parse/EnvFile.php index 6276885d..dd590d62 100644 --- a/src/Parse/EnvFile.php +++ b/src/Parse/EnvFile.php @@ -168,7 +168,12 @@ protected function escapeValue($value): string */ protected function parse(string $filePath): array { - if (!file_exists($filePath) || !($contents = file($filePath)) || !count($contents)) { + if (!is_file($filePath)) { + return [[], []]; + } + + $contents = file($filePath); + if (empty($contents)) { return [[], []]; } diff --git a/src/Parse/PHP/ArrayFile.php b/src/Parse/PHP/ArrayFile.php index fe18024a..c0b2e7a1 100644 --- a/src/Parse/PHP/ArrayFile.php +++ b/src/Parse/PHP/ArrayFile.php @@ -309,7 +309,7 @@ protected function getAstReturnIndex(array $ast): ?int * If the path cannot be found completely, return the nearest parent and the remainder of the path * * @param array $path - * @param $pointer + * @param mixed $pointer * @param int $depth * @throws SystemException if trying to set a position that is already occupied by a value */ diff --git a/src/Parse/PHP/ArrayPrinter.php b/src/Parse/PHP/ArrayPrinter.php index 04bf3893..81f967da 100644 --- a/src/Parse/PHP/ArrayPrinter.php +++ b/src/Parse/PHP/ArrayPrinter.php @@ -81,7 +81,7 @@ protected function pMaybeMultiline(array $nodes, bool $trailingComma = false) * * The result includes a leading newline and one level of indentation (same as pStmts). * - * @param Node[] $nodes Array of Nodes to be printed + * @param array $nodes Array of Nodes to be printed * @param bool $trailingComma Whether to use a trailing comma * * @return string Comma separated pretty printed nodes in multiline style diff --git a/src/Router/UrlGenerator.php b/src/Router/UrlGenerator.php index f0b4d416..40267ad7 100644 --- a/src/Router/UrlGenerator.php +++ b/src/Router/UrlGenerator.php @@ -82,7 +82,7 @@ public static function buildUrl($url, $replace = [], $flags = HTTP_URL_REPLACE, if ( !in_array($key, $urlSegments) || !isset($value) - || empty($value) + || (is_array($value) && !count($value)) ) { unset($url[$key]); continue; @@ -160,10 +160,13 @@ public static function buildUrl($url, $replace = [], $flags = HTTP_URL_REPLACE, $rQuery = str_replace(array('[', '%5B'), '{{{', $rQuery); $rQuery = str_replace(array(']', '%5D'), '}}}', $rQuery); - parse_str($uQuery, $uQuery); - parse_str($rQuery, $rQuery); + $parsedUQuery = []; + $parsedRQuery = []; - $query = static::buildStr(array_merge($uQuery, $rQuery)); + parse_str($uQuery, $parsedUQuery); + parse_str($rQuery, $parsedRQuery); + + $query = static::buildStr(array_merge($parsedUQuery, $parsedRQuery)); $query = str_replace(array('{{{', '%7B%7B%7B'), '%5B', $query); $query = str_replace(array('}}}', '%7D%7D%7D'), '%5D', $query); From 298fea5e87ed7da2172f6d5d057ee935218399c9 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 3 May 2022 13:28:47 +0800 Subject: [PATCH 058/108] Exclude some errors that are not helping --- phpstan.neon | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/phpstan.neon b/phpstan.neon index c6e11141..af941a30 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -8,10 +8,16 @@ parameters: excludePaths: - src/Auth/Manager.php - src/Database/Behaviors/Purgeable.php + # Exclude PHP Parser files + - src/Parse/PHP/ArrayFile.php + - src/Parse/PHP/ArrayPrinter.php databaseMigrationsPath: - src/Auth/Migrations - src/Database/Migrations ignoreErrors: + - "#Access to undefined constant#" + - "#Access to an undefined property#" + - "#Call to an undefined method#" - message: '#calls parent::__#' path: src/Extension/ExtendableTrait.php # Ignore incorrect docs from Laravel's Validation Factory From 206edcd851f458af96099de3ce9d2167493effa8 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 5 May 2022 14:35:49 +0800 Subject: [PATCH 059/108] Final pass of code analysis tweaks --- phpstan.neon | 14 -------------- src/Auth/Manager.php | 6 +++--- src/Database/Model.php | 11 ++++++----- src/Database/Models/Revision.php | 4 ++-- src/Database/QueryBuilder.php | 4 ++-- src/Database/Traits/Sortable.php | 2 +- src/Extension/ExtendableTrait.php | 2 +- src/Foundation/Console/ClearCompiledCommand.php | 1 - 8 files changed, 15 insertions(+), 29 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index af941a30..917cd7d0 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,23 +6,9 @@ parameters: - src level: 5 excludePaths: - - src/Auth/Manager.php - - src/Database/Behaviors/Purgeable.php # Exclude PHP Parser files - src/Parse/PHP/ArrayFile.php - src/Parse/PHP/ArrayPrinter.php databaseMigrationsPath: - src/Auth/Migrations - src/Database/Migrations - ignoreErrors: - - "#Access to undefined constant#" - - "#Access to an undefined property#" - - "#Call to an undefined method#" - - message: '#calls parent::__#' - path: src/Extension/ExtendableTrait.php - # Ignore incorrect docs from Laravel's Validation Factory - - message: '#\$resolver is not covariant#' - path: src/Validation/Factory.php - # Ignore incompatible signature for Mailer methods - - message: '#(queue|queueOn|later|laterOn)\(\) should be compatible#' - path: src/Mail/Mailer.php diff --git a/src/Auth/Manager.php b/src/Auth/Manager.php index 9a9f7203..1803a17b 100644 --- a/src/Auth/Manager.php +++ b/src/Auth/Manager.php @@ -1,8 +1,8 @@ hasRelation($key) && !$this->hasSetMutator($key)) { - return $this->setRelationValue($key, $value); + $this->setRelationValue($key, $value); + return; } /** diff --git a/src/Database/Models/Revision.php b/src/Database/Models/Revision.php index f1a73fdb..3e9df85d 100644 --- a/src/Database/Models/Revision.php +++ b/src/Database/Models/Revision.php @@ -21,7 +21,7 @@ class Revision extends Model */ public function getNewValueAttribute($value) { - if ($this->cast == 'date' && !is_null($value)) { + if ($this->getAttribute('cast') === 'date' && !is_null($value)) { return $this->asDateTime($value); } @@ -34,7 +34,7 @@ public function getNewValueAttribute($value) */ public function getOldValueAttribute($value) { - if ($this->cast == 'date' && !is_null($value)) { + if ($this->getAttribute('cast') === 'date' && !is_null($value)) { return $this->asDateTime($value); } diff --git a/src/Database/QueryBuilder.php b/src/Database/QueryBuilder.php index f0b33e99..9126576a 100644 --- a/src/Database/QueryBuilder.php +++ b/src/Database/QueryBuilder.php @@ -1,8 +1,8 @@ groups || $this->havings) { $clone = $this->cloneForPaginationCount(); - if (is_null($clone->columns) && !empty($this->joins)) { + if (empty($clone->columns) && !empty($this->joins)) { $clone->select($this->from . '.*'); } diff --git a/src/Database/Traits/Sortable.php b/src/Database/Traits/Sortable.php index 96499c99..86d31dc2 100644 --- a/src/Database/Traits/Sortable.php +++ b/src/Database/Traits/Sortable.php @@ -75,6 +75,6 @@ public function setSortableOrder($itemIds, $itemOrders = null) */ public function getSortOrderColumn() { - return defined('static::SORT_ORDER') ? static::SORT_ORDER : 'sort_order'; + return constant('static::SORT_ORDER') ?? 'sort_order'; } } diff --git a/src/Extension/ExtendableTrait.php b/src/Extension/ExtendableTrait.php index b9579dc4..d8dbf998 100644 --- a/src/Extension/ExtendableTrait.php +++ b/src/Extension/ExtendableTrait.php @@ -1,12 +1,12 @@ laravel->getCachedClassesPath())) { @unlink($classesPath); } From 82a79963c8ae564c00260d223e0174f4f6d1ea0a Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 5 May 2022 14:40:49 +0800 Subject: [PATCH 060/108] Fix sortable constant reference and test --- src/Database/Traits/Sortable.php | 2 +- tests/Database/SortableTest.php | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Database/Traits/Sortable.php b/src/Database/Traits/Sortable.php index 86d31dc2..03ca6030 100644 --- a/src/Database/Traits/Sortable.php +++ b/src/Database/Traits/Sortable.php @@ -75,6 +75,6 @@ public function setSortableOrder($itemIds, $itemOrders = null) */ public function getSortOrderColumn() { - return constant('static::SORT_ORDER') ?? 'sort_order'; + return defined('static::SORT_ORDER') ? constant('static::SORT_ORDER') : 'sort_order'; } } diff --git a/tests/Database/SortableTest.php b/tests/Database/SortableTest.php index fc6f0d75..8b16990f 100644 --- a/tests/Database/SortableTest.php +++ b/tests/Database/SortableTest.php @@ -10,6 +10,14 @@ public function testOrderByIsAutomaticallyAdded() $this->assertEquals('select * from "test" order by "sort_order" asc', $query); } + public function testCustomSortOrderByIsAutomaticallyAdded() + { + $model = new TestCustomSortableModel(); + $query = $model->newQuery()->toSql(); + + $this->assertEquals('select * from "test" order by "rank" asc', $query); + } + public function testOrderByCanBeOverridden() { $model = new TestSortableModel(); @@ -18,6 +26,13 @@ public function testOrderByCanBeOverridden() $this->assertEquals('select * from "test" order by "name" asc, "email" desc', $query1); $this->assertEquals('select * from "test" order by "sort_order" asc, "name" asc', $query2); + + $model = new TestCustomSortableModel(); + $query1 = $model->newQuery()->orderBy('name')->orderBy('email', 'desc')->toSql(); + $query2 = $model->newQuery()->orderBy('sort_order')->orderBy('name')->toSql(); + + $this->assertEquals('select * from "test" order by "name" asc, "email" desc', $query1); + $this->assertEquals('select * from "test" order by "sort_order" asc, "name" asc', $query2); } } @@ -27,3 +42,12 @@ class TestSortableModel extends \Winter\Storm\Database\Model protected $table = 'test'; } + +class TestCustomSortableModel extends \Winter\Storm\Database\Model +{ + use \Winter\Storm\Database\Traits\Sortable; + + const SORT_ORDER = 'rank'; + + protected $table = 'test'; +} From 03e95b668a6c87daa50de1ce246026d233ea84db Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 5 May 2022 14:58:35 +0800 Subject: [PATCH 061/108] Convert private methods to protected in Section Parser --- src/Halcyon/Processors/SectionParser.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Halcyon/Processors/SectionParser.php b/src/Halcyon/Processors/SectionParser.php index 9e2fef45..d7615548 100644 --- a/src/Halcyon/Processors/SectionParser.php +++ b/src/Halcyon/Processors/SectionParser.php @@ -274,7 +274,7 @@ public static function parseOffset(string $content): array * @param int $instance Which instance to look for * @return int|null The line number the instance was found. */ - private static function calculateLinePosition(string $content, int $instance = 1): ?int + protected static function calculateLinePosition(string $content, int $instance = 1): ?int { $count = 0; $lines = explode(PHP_EOL, $content); @@ -284,7 +284,7 @@ private static function calculateLinePosition(string $content, int $instance = 1 } if ($count === $instance) { - return self::adjustLinePosition($content, $number); + return static::adjustLinePosition($content, $number); } } @@ -296,7 +296,7 @@ private static function calculateLinePosition(string $content, int $instance = 1 * after the separator (==). There can be an opening tag or white space in between * where the section really begins. */ - private static function adjustLinePosition(string $content, int $startLine = -1): int + protected static function adjustLinePosition(string $content, int $startLine = -1): int { // Account for the separator itself. $startLine++; From 51cec2fb832746ca3d3434021ccf6d681e5f4b36 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 11 May 2022 16:11:28 +0800 Subject: [PATCH 062/108] Change return type in Argon --- src/Argon/Argon.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Argon/Argon.php b/src/Argon/Argon.php index 81d2535b..a4cff366 100644 --- a/src/Argon/Argon.php +++ b/src/Argon/Argon.php @@ -1,6 +1,5 @@ Date: Fri, 13 May 2022 14:26:58 +0800 Subject: [PATCH 063/108] Synchronise Pivot model with Laravel 9 --- src/Database/Pivot.php | 217 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 190 insertions(+), 27 deletions(-) diff --git a/src/Database/Pivot.php b/src/Database/Pivot.php index 0339596b..c20aebba 100644 --- a/src/Database/Pivot.php +++ b/src/Database/Pivot.php @@ -1,16 +1,24 @@ parent = $parent; - $instance->timestamps = $instance->hasTimestampAttributes(); // The pivot model is a "dynamic" model since we will set the tables dynamically @@ -67,6 +70,11 @@ public static function fromAttributes(ModelBase $parent, $attributes, $table, $e ->forceFill($attributes) ->syncOriginal(); + // We store off the parent instance so we will access the timestamp column names + // for the model, since the pivot model timestamps aren't easily configurable + // from the developer's point of view. We can use the parents to get these. + $instance->pivotParent = $instance->parent = $parent; + $instance->exists = $exists; return $instance; @@ -87,32 +95,70 @@ public static function fromRawAttributes(ModelBase $parent, $attributes, $table, $instance->timestamps = $instance->hasTimestampAttributes(); - $instance->setRawAttributes($attributes, $exists); + $instance->setRawAttributes( + array_merge($instance->getRawOriginal(), $attributes), + $exists + ); return $instance; } + /** + * Set the keys for a select query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function setKeysForSelectQuery($query) + { + if (isset($this->attributes[$this->getKeyName()])) { + return parent::setKeysForSelectQuery($query); + } + + $query->where($this->foreignKey, $this->getOriginal( + $this->foreignKey, + $this->getAttribute($this->foreignKey) + )); + + return $query->where($this->relatedKey, $this->getOriginal( + $this->relatedKey, + $this->getAttribute($this->relatedKey) + )); + } + /** * Set the keys for a save update query. * - * @param \Illuminate\Database\Eloquent\Builder $query + * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ protected function setKeysForSaveQuery($query) { - $query->where($this->foreignKey, $this->getAttribute($this->foreignKey)); - - return $query->where($this->otherKey, $this->getAttribute($this->otherKey)); + return $this->setKeysForSelectQuery($query); } /** * Delete the pivot model record from the database. * - * @return mixed + * @return int */ public function delete() { - return $this->getDeleteQuery()->delete(); + if (isset($this->attributes[$this->getKeyName()])) { + return (int) parent::delete(); + } + + if ($this->fireModelEvent('deleting') === false) { + return 0; + } + + $this->touchOwners(); + + return tap($this->getDeleteQuery()->delete(), function () { + $this->exists = false; + + $this->fireModelEvent('deleted', false); + }); } /** @@ -122,11 +168,28 @@ public function delete() */ protected function getDeleteQuery() { - $foreign = $this->getAttribute($this->foreignKey); + return $this->newQueryWithoutRelationships()->where([ + $this->foreignKey => $this->getOriginal($this->foreignKey, $this->getAttribute($this->foreignKey)), + $this->relatedKey => $this->getOriginal($this->relatedKey, $this->getAttribute($this->relatedKey)), + ]); + } - $query = $this->newQuery()->where($this->foreignKey, $foreign); + /** + * Get the table associated with the model. + * + * @return string + */ + public function getTable() + { + if (!isset($this->table)) { + $this->setTable(str_replace( + '\\', + '', + Str::snake(Str::singular(class_basename($this))) + )); + } - return $query->where($this->otherKey, $this->getAttribute($this->otherKey)); + return $this->table; } /** @@ -140,39 +203,50 @@ public function getForeignKey() } /** - * Get the "other key" column name. + * Get the "related key" column name. + * + * @return string + */ + public function getRelatedKey() + { + return $this->relatedKey; + } + + /** + * Get the "related key" column name. * * @return string */ public function getOtherKey() { - return $this->otherKey; + return $this->getRelatedKey(); } /** * Set the key names for the pivot model instance. * * @param string $foreignKey - * @param string $otherKey + * @param string $relatedKey * @return $this */ - public function setPivotKeys($foreignKey, $otherKey) + public function setPivotKeys($foreignKey, $relatedKey) { $this->foreignKey = $foreignKey; - $this->otherKey = $otherKey; + $this->relatedKey = $relatedKey; return $this; } /** - * Determine if the pivot model has timestamp attributes. + * Determine if the pivot model or given attributes has timestamp attributes. * + * @param array|null $attributes * @return bool */ - public function hasTimestampAttributes() + public function hasTimestampAttributes($attributes = null) { - return array_key_exists($this->getCreatedAtColumn(), $this->attributes); + return array_key_exists($this->getCreatedAtColumn(), $attributes ?? $this->attributes); } /** @@ -182,7 +256,9 @@ public function hasTimestampAttributes() */ public function getCreatedAtColumn() { - return $this->parent->getCreatedAtColumn(); + return $this->pivotParent + ? $this->pivotParent->getCreatedAtColumn() + : parent::getCreatedAtColumn(); } /** @@ -192,6 +268,93 @@ public function getCreatedAtColumn() */ public function getUpdatedAtColumn() { - return $this->parent->getUpdatedAtColumn(); + return $this->pivotParent + ? $this->pivotParent->getUpdatedAtColumn() + : parent::getUpdatedAtColumn(); + } + + /** + * Get the queueable identity for the entity. + * + * @return mixed + */ + public function getQueueableId() + { + if (isset($this->attributes[$this->getKeyName()])) { + return $this->getKey(); + } + + return sprintf( + '%s:%s:%s:%s', + $this->foreignKey, + $this->getAttribute($this->foreignKey), + $this->relatedKey, + $this->getAttribute($this->relatedKey) + ); + } + + /** + * Get a new query to restore one or more models by their queueable IDs. + * + * @param int[]|string[]|string $ids + * @return \Illuminate\Database\Eloquent\Builder + */ + public function newQueryForRestoration($ids) + { + if (is_array($ids)) { + return $this->newQueryForCollectionRestoration($ids); + } + + if (!str_contains($ids, ':')) { + return parent::newQueryForRestoration($ids); + } + + $segments = explode(':', $ids); + + return $this->newQueryWithoutScopes() + ->where($segments[0], $segments[1]) + ->where($segments[2], $segments[3]); + } + + /** + * Get a new query to restore multiple models by their queueable IDs. + * + * @param int[]|string[] $ids + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function newQueryForCollectionRestoration(array $ids) + { + $ids = array_values($ids); + + if (!str_contains($ids[0], ':')) { + return parent::newQueryForRestoration($ids); + } + + $query = $this->newQueryWithoutScopes(); + + foreach ($ids as $id) { + $segments = explode(':', $id); + + $query->orWhere(function ($query) use ($segments) { + return $query->where($segments[0], $segments[1]) + ->where($segments[2], $segments[3]); + }); + } + + return $query; + } + + /** + * Unset all the loaded relations for the instance. + * + * @return $this + */ + public function unsetRelations() + { + $this->pivotParent = null; + $this->parent = null; + $this->relations = []; + + return $this; } } From 6a9022e74c01cad1ad9b2a4b01f7abe11ea1776a Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 13 May 2022 14:35:43 +0800 Subject: [PATCH 064/108] Add MorphPivot support --- src/Database/MorphPivot.php | 185 +++++++++++++++++++++++++ src/Database/Relations/MorphToMany.php | 2 +- 2 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 src/Database/MorphPivot.php diff --git a/src/Database/MorphPivot.php b/src/Database/MorphPivot.php new file mode 100644 index 00000000..f7a03c87 --- /dev/null +++ b/src/Database/MorphPivot.php @@ -0,0 +1,185 @@ +where($this->morphType, $this->morphClass); + + return parent::setKeysForSaveQuery($query); + } + + /** + * Set the keys for a select query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function setKeysForSelectQuery($query) + { + $query->where($this->morphType, $this->morphClass); + + return parent::setKeysForSelectQuery($query); + } + + /** + * Delete the pivot model record from the database. + * + * @return int + */ + public function delete() + { + if (isset($this->attributes[$this->getKeyName()])) { + return (int) parent::delete(); + } + + if ($this->fireModelEvent('deleting') === false) { + return 0; + } + + $query = $this->getDeleteQuery(); + + $query->where($this->morphType, $this->morphClass); + + return tap($query->delete(), function () { + $this->fireModelEvent('deleted', false); + }); + } + + /** + * Get the morph type for the pivot. + * + * @return string + */ + public function getMorphType() + { + return $this->morphType; + } + + + /** + * Set the morph type for the pivot. + * + * @param string $morphType + * @return $this + */ + public function setMorphType($morphType) + { + $this->morphType = $morphType; + + return $this; + } + + /** + * Set the morph class for the pivot. + * + * @param string $morphClass + * @return \Winter\Storm\Database\MorphPivot + */ + public function setMorphClass($morphClass) + { + $this->morphClass = $morphClass; + + return $this; + } + + + /** + * Get the queueable identity for the entity. + * + * @return mixed + */ + public function getQueueableId() + { + if (isset($this->attributes[$this->getKeyName()])) { + return $this->getKey(); + } + + return sprintf( + '%s:%s:%s:%s:%s:%s', + $this->foreignKey, + $this->getAttribute($this->foreignKey), + $this->relatedKey, + $this->getAttribute($this->relatedKey), + $this->morphType, + $this->morphClass + ); + } + + /** + * Get a new query to restore one or more models by their queueable IDs. + * + * @param array|int $ids + * @return \Illuminate\Database\Eloquent\Builder + */ + public function newQueryForRestoration($ids) + { + if (is_array($ids)) { + return $this->newQueryForCollectionRestoration($ids); + } + + if (!str_contains($ids, ':')) { + return parent::newQueryForRestoration($ids); + } + + $segments = explode(':', $ids); + + return $this->newQueryWithoutScopes() + ->where($segments[0], $segments[1]) + ->where($segments[2], $segments[3]) + ->where($segments[4], $segments[5]); + } + + /** + * Get a new query to restore multiple models by their queueable IDs. + * + * @param array $ids + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function newQueryForCollectionRestoration(array $ids) + { + $ids = array_values($ids); + + if (!str_contains($ids[0], ':')) { + return parent::newQueryForRestoration($ids); + } + + $query = $this->newQueryWithoutScopes(); + + foreach ($ids as $id) { + $segments = explode(':', $id); + + $query->orWhere(function ($query) use ($segments) { + return $query->where($segments[0], $segments[1]) + ->where($segments[2], $segments[3]) + ->where($segments[4], $segments[5]); + }); + } + + return $query; + } +} diff --git a/src/Database/Relations/MorphToMany.php b/src/Database/Relations/MorphToMany.php index 02a5633d..a40fcffb 100644 --- a/src/Database/Relations/MorphToMany.php +++ b/src/Database/Relations/MorphToMany.php @@ -1,8 +1,8 @@ Date: Sat, 14 May 2022 13:27:35 +0800 Subject: [PATCH 065/108] Update src/Database/Attach/File.php --- src/Database/Attach/File.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Attach/File.php b/src/Database/Attach/File.php index 53990f24..408871a8 100644 --- a/src/Database/Attach/File.php +++ b/src/Database/Attach/File.php @@ -876,7 +876,7 @@ protected function deleteEmptyDirectory($dir = null) * * @param string $dir The path to the directory. * - * @return bool|null + * @return bool */ protected function isDirectoryEmpty($dir) { From 1ba308aaad6456852f75ba0193800c350104ecf7 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 22 May 2022 12:33:21 +0800 Subject: [PATCH 066/108] Update src/Console/Command.php Co-authored-by: Luke Towers --- src/Console/Command.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Console/Command.php b/src/Console/Command.php index fa7b1233..b3a3c3af 100644 --- a/src/Console/Command.php +++ b/src/Console/Command.php @@ -98,6 +98,7 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti $suggestionType = 'Options'; break; default: + // This should not be possible to ever be triggered given the type is hardcoded above throw new \Exception('Invalid input type being parsed during completion'); } if (!empty($data)) { From 5645a5b9ada5252a1f0a0d4d3bdfedc9034b0456 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 22 May 2022 12:36:04 +0800 Subject: [PATCH 067/108] Update src/Database/MorphPivot.php Co-authored-by: Luke Towers --- src/Database/MorphPivot.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Database/MorphPivot.php b/src/Database/MorphPivot.php index f7a03c87..de1b324e 100644 --- a/src/Database/MorphPivot.php +++ b/src/Database/MorphPivot.php @@ -1,5 +1,10 @@ Date: Sun, 22 May 2022 21:26:08 +0800 Subject: [PATCH 068/108] Add type hints to all File attachment methods --- src/Database/Attach/File.php | 214 ++++++++++++++++------------------- 1 file changed, 96 insertions(+), 118 deletions(-) diff --git a/src/Database/Attach/File.php b/src/Database/Attach/File.php index 408871a8..c2443c24 100644 --- a/src/Database/Attach/File.php +++ b/src/Database/Attach/File.php @@ -1,6 +1,7 @@ file_name = $uploadedFile->getClientOriginalName(); $this->file_size = $uploadedFile->getSize(); @@ -122,12 +122,8 @@ public function fromPost($uploadedFile) /** * Creates a file object from a file on the disk. */ - public function fromFile($filePath) + public function fromFile(string $filePath): static { - if ($filePath === null) { - return; - } - $file = new FileObj($filePath); $this->file_name = $file->getFilename(); $this->file_size = $file->getSize(); @@ -141,13 +137,8 @@ public function fromFile($filePath) /** * Creates a file object from raw data. - * - * @param string $data Raw data - * @param string $filename Filename - * - * @return $this */ - public function fromData($data, $filename) + public function fromData(string $data, string $filename): static { $tempPath = temp_path($filename); FileHelper::put($tempPath, $data); @@ -160,12 +151,8 @@ public function fromData($data, $filename) /** * Creates a file object from url - * - * @param string $url URL to the file - * @param string|null $filename The filename - * @return $this */ - public function fromUrl($url, $filename = null) + public function fromUrl(string $url, ?string $filename = null): static { $data = Http::get($url); @@ -180,7 +167,8 @@ public function fromUrl($url, $filename = null) // Get the filename from the path $filename = pathinfo($filePath)['filename']; - // Attempt to detect the extension from the reported Content-Type, fall back to the original path extension if not able to guess + // Attempt to detect the extension from the reported Content-Type, fall back to the original path extension + // if not able to guess $mimesToExt = array_flip($this->autoMimeTypes); if (!empty($data->headers['Content-Type']) && isset($mimesToExt[$data->headers['Content-Type']])) { $ext = $mimesToExt[$data->headers['Content-Type']]; @@ -201,27 +189,24 @@ public function fromUrl($url, $filename = null) /** * Helper attribute for getPath. - * @return string */ - public function getPathAttribute() + public function getPathAttribute(): string { return $this->getPath(); } /** * Helper attribute for getExtension. - * @return string */ - public function getExtensionAttribute() + public function getExtensionAttribute(): string { return $this->getExtension(); } /** * Used only when filling attributes. - * @return void */ - public function setDataAttribute($value) + public function setDataAttribute($value): void { $this->data = $value; } @@ -230,10 +215,8 @@ public function setDataAttribute($value) * Helper attribute for get image width. * * Returns `null` if this file is not an image. - * - * @return string|null */ - public function getWidthAttribute() + public function getWidthAttribute(): string|int|null { if ($this->isImage()) { $dimensions = $this->getImageDimensions(); @@ -248,10 +231,8 @@ public function getWidthAttribute() * Helper attribute for get image height. * * Returns `null` if this file is not an image. - * - * @return string|null */ - public function getHeightAttribute() + public function getHeightAttribute(): string|int|null { if ($this->isImage()) { $dimensions = $this->getImageDimensions(); @@ -264,9 +245,8 @@ public function getHeightAttribute() /** * Helper attribute for file size in human format. - * @return string */ - public function getSizeAttribute() + public function getSizeAttribute(): string { return $this->sizeToString(); } @@ -278,16 +258,17 @@ public function getSizeAttribute() /** * Outputs the raw file contents. * - * @param string $disposition The Content-Disposition to set, defaults to inline - * @param bool $returnResponse Defaults to false, returns a Response object instead of directly outputting to the browser + * @param string $disposition The Content-Disposition to set, defaults to `inline` + * @param bool $returnResponse Defaults to `false`, returns a Response object instead of directly outputting to the + * browser * @return \Illuminate\Http\Response|void */ - public function output($disposition = 'inline', $returnResponse = false) + public function output(string $disposition = 'inline', bool $returnResponse = false) { $response = response($this->getContents())->withHeaders([ 'Content-type' => $this->getContentType(), 'Content-Disposition' => $disposition . '; filename="' . $this->file_name . '"', - 'Cache-Control' => 'private, no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0', + 'Cache-Control' => 'private, no-store, no-cache, must-revalidate, max-age=0', 'Accept-Ranges' => 'bytes', 'Content-Length' => $this->file_size, ]); @@ -303,8 +284,8 @@ public function output($disposition = 'inline', $returnResponse = false) /** * Outputs the raw thumbfile contents. * - * @param integer $width - * @param integer $height + * @param int $width + * @param int $height * @param array $options [ * 'mode' => 'auto', * 'offset' => [0, 0], @@ -314,10 +295,11 @@ public function output($disposition = 'inline', $returnResponse = false) * 'extension' => 'auto', * 'disposition' => 'inline', * ] - * @param bool $returnResponse Defaults to false, returns a Response object instead of directly outputting to the browser + * @param bool $returnResponse Defaults to `false`, returns a Response object instead of directly outputting to the + * browser * @return \Illuminate\Http\Response|void */ - public function outputThumb($width, $height, $options = [], $returnResponse = false) + public function outputThumb(int $width, int $height, array $options = [], bool $returnResponse = false) { $disposition = array_get($options, 'disposition', 'inline'); $options = $this->getDefaultThumbOptions($options); @@ -328,7 +310,7 @@ public function outputThumb($width, $height, $options = [], $returnResponse = fa $response = response($contents)->withHeaders([ 'Content-type' => $this->getContentType(), 'Content-Disposition' => $disposition . '; filename="' . basename($thumbFile) . '"', - 'Cache-Control' => 'private, no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0', + 'Cache-Control' => 'private, no-store, no-cache, must-revalidate, max-age=0', 'Accept-Ranges' => 'bytes', 'Content-Length' => mb_strlen($contents, '8bit'), ]); @@ -347,11 +329,8 @@ public function outputThumb($width, $height, $options = [], $returnResponse = fa /** * Returns the cache key used for the hasFile method - * - * @param string $path The path to get the cache key for - * @return string */ - public function getCacheKey($path = null) + public function getCacheKey(?string $path = null): string { if (empty($path)) { $path = $this->getDiskPath(); @@ -363,7 +342,7 @@ public function getCacheKey($path = null) /** * Returns the file name without path */ - public function getFilename() + public function getFilename(): string { return $this->file_name; } @@ -371,24 +350,25 @@ public function getFilename() /** * Returns the file extension. */ - public function getExtension() + public function getExtension(): string { return FileHelper::extension($this->file_name); } /** * Returns the last modification date as a UNIX timestamp. - * @return int */ - public function getLastModified($fileName = null) + public function getLastModified(?string $fileName = null): int { return $this->storageCmd('lastModified', $this->getDiskPath($fileName)); } /** * Returns the file content type. + * + * Returns `null` if the file content type cannot be determined. */ - public function getContentType() + public function getContentType(): ?string { if ($this->content_type !== null) { return $this->content_type; @@ -405,7 +385,7 @@ public function getContentType() /** * Get file contents from storage device. */ - public function getContents($fileName = null) + public function getContents(?string $fileName = null): string { return $this->storageCmd('get', $this->getDiskPath($fileName)); } @@ -413,7 +393,7 @@ public function getContents($fileName = null) /** * Returns the public address to access the file. */ - public function getPath($fileName = null) + public function getPath(?string $fileName = null): string { if (empty($fileName)) { $fileName = $this->disk_name; @@ -425,7 +405,7 @@ public function getPath($fileName = null) * Returns a local path to this file. If the file is stored remotely, * it will be downloaded to a temporary directory. */ - public function getLocalPath() + public function getLocalPath(): string { if ($this->isLocalStorage()) { return $this->getLocalRootPath() . '/' . $this->getDiskPath(); @@ -446,7 +426,7 @@ public function getLocalPath() * Returns the path to the file, relative to the storage disk. * @return string */ - public function getDiskPath($fileName = null) + public function getDiskPath(?string $fileName = null): string { if (empty($fileName)) { $fileName = $this->disk_name; @@ -457,14 +437,14 @@ public function getDiskPath($fileName = null) /** * Determines if the file is flagged "public" or not. */ - public function isPublic() + public function isPublic(): bool { if (array_key_exists('is_public', $this->attributes)) { - return $this->attributes['is_public']; + return (bool) $this->attributes['is_public']; } if (isset($this->is_public)) { - return $this->is_public; + return (bool) $this->is_public; } return true; @@ -472,9 +452,8 @@ public function isPublic() /** * Returns the file size as string. - * @return string Returns the size as string. */ - public function sizeToString() + public function sizeToString(): string { return FileHelper::sizeToString($this->file_size); } @@ -525,16 +504,15 @@ public function afterDelete() /** * Checks if the file extension is an image and returns true or false. */ - public function isImage() + public function isImage(): bool { return in_array(strtolower($this->getExtension()), static::$imageExtensions); } /** * Get image dimensions - * @return array|bool */ - protected function getImageDimensions() + protected function getImageDimensions(): array|false { return getimagesize($this->getLocalPath()); } @@ -554,7 +532,7 @@ protected function getImageDimensions() * ] * @return string The URL to the generated thumbnail */ - public function getThumb($width, $height, $options = []) + public function getThumb(int $width, int $height, array $options = []): string { if (!$this->isImage()) { return $this->getPath(); @@ -583,19 +561,25 @@ public function getThumb($width, $height, $options = []) /** * Generates a thumbnail filename. - * @return string */ - public function getThumbFilename($width, $height, $options) + public function getThumbFilename(int $width, int $height, array $options): string { $options = $this->getDefaultThumbOptions($options); - return 'thumb_' . $this->id . '_' . $width . '_' . $height . '_' . $options['offset'][0] . '_' . $options['offset'][1] . '_' . $options['mode'] . '.' . $options['extension']; + return implode('_', [ + 'thumb', + (string) $this->id, + (string) $width, + (string) $height, + (string) $options['offset'][0], + (string) $options['offset'][1], + (string) $options['mode'] . '.' . (string) $options['extension'], + ]); } /** * Returns the default thumbnail options. - * @return array */ - protected function getDefaultThumbOptions($overrideOptions = []) + protected function getDefaultThumbOptions(array $overrideOptions = []): array { $defaultOptions = [ 'mode' => 'auto', @@ -622,12 +606,18 @@ protected function getDefaultThumbOptions($overrideOptions = []) } /** - * Generate the thumbnail based on the local file system. This step is necessary - * to simplify things and ensure the correct file permissions are given + * Generate the thumbnail based on the local file system. + * + * This step is necessary to simplify things and ensure the correct file permissions are given * to the local files. */ - protected function makeThumbLocal($thumbFile, $thumbPath, $width, $height, $options) - { + protected function makeThumbLocal( + string $thumbFile, + string $thumbPath, + int $width, + int $height, + array $options + ): void { $rootPath = $this->getLocalRootPath(); $filePath = $rootPath.'/'.$this->getDiskPath(); $thumbPath = $rootPath.'/'.$thumbPath; @@ -660,8 +650,13 @@ protected function makeThumbLocal($thumbFile, $thumbPath, $width, $height, $opti /** * Generate the thumbnail based on a remote storage engine. */ - protected function makeThumbStorage($thumbFile, $thumbPath, $width, $height, $options) - { + protected function makeThumbStorage( + string $thumbFile, + string $thumbPath, + int $width, + int $height, + array $options + ): void { $tempFile = $this->getLocalTempPath(); $tempThumb = $this->getLocalTempPath($thumbFile); @@ -701,7 +696,7 @@ protected function makeThumbStorage($thumbFile, $thumbPath, $width, $height, $op /* * Delete all thumbnails for this file. */ - public function deleteThumbs() + public function deleteThumbs(): void { $pattern = 'thumb_'.$this->id.'_'; @@ -734,7 +729,7 @@ public function deleteThumbs() /** * Generates a disk name from the supplied file name. */ - protected function getDiskName() + protected function getDiskName(): string { if ($this->disk_name !== null) { return $this->disk_name; @@ -755,7 +750,7 @@ protected function getDiskName() /** * Returns a temporary local path to work from. */ - protected function getLocalTempPath($path = null) + protected function getLocalTempPath(?string $path = null): string { if (!$path) { return $this->getTempPath() . '/' . md5($this->getDiskPath()) . '.' . $this->getExtension(); @@ -769,7 +764,7 @@ protected function getLocalTempPath($path = null) * @param string $sourcePath An absolute local path to a file name to read from. * @param string $destinationFileName A storage file name to save to. */ - protected function putFile($sourcePath, $destinationFileName = null) + protected function putFile(string $sourcePath, ?string $destinationFileName = null): bool { if (!$destinationFileName) { $destinationFileName = $this->disk_name; @@ -804,9 +799,8 @@ protected function putFile($sourcePath, $destinationFileName = null) /** * Delete file contents from storage device. - * @return void */ - protected function deleteFile($fileName = null) + protected function deleteFile(?string $fileName = null): void { if (!$fileName) { $fileName = $this->disk_name; @@ -825,9 +819,8 @@ protected function deleteFile($fileName = null) /** * Check file exists on storage device. - * @return bool */ - protected function hasFile($fileName = null) + protected function hasFile(?string $fileName = null): bool { $filePath = $this->getDiskPath($fileName); @@ -844,11 +837,9 @@ protected function hasFile($fileName = null) } /** - * Checks if directory is empty then deletes it, - * three levels up to match the partition directory. - * @return void + * Checks if directory is empty then deletes it, three levels up to match the partition directory. */ - protected function deleteEmptyDirectory($dir = null) + protected function deleteEmptyDirectory(?string $dir = null): void { if (!$this->isDirectoryEmpty($dir)) { return; @@ -873,12 +864,8 @@ protected function deleteEmptyDirectory($dir = null) /** * Returns true if a directory contains no files. - * - * @param string $dir The path to the directory. - * - * @return bool */ - protected function isDirectoryEmpty($dir) + protected function isDirectoryEmpty(?string $dir = null): bool { return count($this->storageCmd('allFiles', $dir)) === 0; } @@ -889,11 +876,9 @@ protected function isDirectoryEmpty($dir) /** * Calls a method against File or Storage depending on local storage. - * This allows local storage outside the storage/app folder and is - * also good for performance. For local storage, *every* argument - * is prefixed with the local root path. Props to Laravel for - * the unified interface. - * @return mixed + * + * This allows local storage outside the storage/app folder and is also good for performance. For local storage, + * *every* argument is prefixed with the local root path. Props to Laravel for the unified interface. */ protected function storageCmd() { @@ -920,7 +905,7 @@ protected function storageCmd() /** * Copy the Storage to local file */ - protected function copyStorageToLocal($storagePath, $localPath) + protected function copyStorageToLocal(string $storagePath, string $localPath): int { return FileHelper::put($localPath, $this->getDisk()->get($storagePath)); } @@ -928,7 +913,7 @@ protected function copyStorageToLocal($storagePath, $localPath) /** * Copy the local file to Storage */ - protected function copyLocalToStorage($localPath, $storagePath) + protected function copyLocalToStorage(string $localPath, string $storagePath): string|bool { return $this->getDisk()->put($storagePath, FileHelper::get($localPath), $this->isPublic() ? 'public' : null); } @@ -938,11 +923,9 @@ protected function copyLocalToStorage($localPath, $storagePath) // /** - * Returns the maximum size of an uploaded file as configured in php.ini - * - * @return float The maximum size of an uploaded file in kilobytes (rounded) + * Returns the maximum size of an uploaded file as configured in php.ini in kilobytes (rounded) */ - public static function getMaxFilesize() + public static function getMaxFilesize(): float { return round(UploadedFile::getMaxFilesize() / 1024); } @@ -950,7 +933,7 @@ public static function getMaxFilesize() /** * Define the internal storage path, override this method to define. */ - public function getStorageDirectory() + public function getStorageDirectory(): string { if ($this->isPublic()) { return 'uploads/public/'; @@ -962,7 +945,7 @@ public function getStorageDirectory() /** * Define the public address for the storage path. */ - public function getPublicPath() + public function getPublicPath(): string { if ($this->isPublic()) { return 'http://localhost/uploads/public/'; @@ -974,7 +957,7 @@ public function getPublicPath() /** * Define the internal working path, override this method to define. */ - public function getTempPath() + public function getTempPath(): string { $path = temp_path() . '/uploads'; @@ -987,40 +970,35 @@ public function getTempPath() /** * Returns the storage disk the file is stored on - * - * @return \Illuminate\Filesystem\FilesystemAdapter */ - public function getDisk() + public function getDisk(): Filesystem { return Storage::disk(); } /** * Returns true if the storage engine is local. - * @return bool */ - protected function isLocalStorage() + protected function isLocalStorage(): bool { return FileHelper::isLocalDisk($this->getDisk()); } /** * Generates a partition for the file. - * return /ABC/DE1/234 for an name of ABCDE1234. - * - * @return string + + * For example, returns `/ABC/DE1/234` for an name of `ABCDE1234`. */ - protected function getPartitionDirectory() + protected function getPartitionDirectory(): string { return implode('/', array_slice(str_split($this->disk_name, 3), 0, 3)) . '/'; } /** * If working with local storage, determine the absolute local path. - * @return string */ - protected function getLocalRootPath() + protected function getLocalRootPath(): string { - return storage_path().'/app'; + return storage_path() . '/app'; } } From 02b5b98ec0dd20cfe0008f211ce3e498f4b5db8f Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 22 May 2022 21:27:21 +0800 Subject: [PATCH 069/108] Add type hints --- src/Database/Connections/Connection.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Database/Connections/Connection.php b/src/Database/Connections/Connection.php index 45698669..062c6205 100644 --- a/src/Database/Connections/Connection.php +++ b/src/Database/Connections/Connection.php @@ -59,12 +59,8 @@ protected function fireConnectionEvent($event) /** * Fire the given event if possible. - * - * @param string $event - * @param array|object $attributes - * @return void */ - protected function fireEvent($event, $attributes = []) + protected function fireEvent(string $event, array|object $attributes = []): void { /** @var \Winter\Storm\Events\Dispatcher|null */ $eventManager = $this->events; From a78075898ff70e6114c3339bfd690b4c0a99b085 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 22 May 2022 21:47:30 +0800 Subject: [PATCH 070/108] Make the Pivot use the AsPivot trait, add parent accessor --- src/Database/Pivot.php | 340 ++--------------------------------------- 1 file changed, 14 insertions(+), 326 deletions(-) diff --git a/src/Database/Pivot.php b/src/Database/Pivot.php index c20aebba..01538273 100644 --- a/src/Database/Pivot.php +++ b/src/Database/Pivot.php @@ -1,37 +1,10 @@ timestamps = $instance->hasTimestampAttributes(); - - // The pivot model is a "dynamic" model since we will set the tables dynamically - // for the instance. This allows it work for any intermediate tables for the - // many to many relationship that are defined by this developer's classes. - $instance->setConnection($parent->getConnectionName()) - ->setTable($table) - ->forceFill($attributes) - ->syncOriginal(); - - // We store off the parent instance so we will access the timestamp column names - // for the model, since the pivot model timestamps aren't easily configurable - // from the developer's point of view. We can use the parents to get these. - $instance->pivotParent = $instance->parent = $parent; - - $instance->exists = $exists; - - return $instance; - } - - /** - * Create a new pivot model from raw values returned from a query. - * - * @param \Illuminate\Database\Eloquent\Model $parent - * @param array $attributes - * @param string $table - * @param bool $exists - * @return static - */ - public static function fromRawAttributes(ModelBase $parent, $attributes, $table, $exists = false) - { - $instance = static::fromAttributes($parent, [], $table, $exists); - - $instance->timestamps = $instance->hasTimestampAttributes(); - - $instance->setRawAttributes( - array_merge($instance->getRawOriginal(), $attributes), - $exists - ); - - return $instance; - } - - /** - * Set the keys for a select query. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder - */ - protected function setKeysForSelectQuery($query) - { - if (isset($this->attributes[$this->getKeyName()])) { - return parent::setKeysForSelectQuery($query); - } - - $query->where($this->foreignKey, $this->getOriginal( - $this->foreignKey, - $this->getAttribute($this->foreignKey) - )); - - return $query->where($this->relatedKey, $this->getOriginal( - $this->relatedKey, - $this->getAttribute($this->relatedKey) - )); - } - - /** - * Set the keys for a save update query. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder - */ - protected function setKeysForSaveQuery($query) - { - return $this->setKeysForSelectQuery($query); - } - - /** - * Delete the pivot model record from the database. - * - * @return int - */ - public function delete() - { - if (isset($this->attributes[$this->getKeyName()])) { - return (int) parent::delete(); - } - - if ($this->fireModelEvent('deleting') === false) { - return 0; - } - - $this->touchOwners(); - - return tap($this->getDeleteQuery()->delete(), function () { - $this->exists = false; - - $this->fireModelEvent('deleted', false); - }); - } - - /** - * Get the query builder for a delete operation on the pivot. - * - * @return \Illuminate\Database\Eloquent\Builder - */ - protected function getDeleteQuery() - { - return $this->newQueryWithoutRelationships()->where([ - $this->foreignKey => $this->getOriginal($this->foreignKey, $this->getAttribute($this->foreignKey)), - $this->relatedKey => $this->getOriginal($this->relatedKey, $this->getAttribute($this->relatedKey)), - ]); - } - - /** - * Get the table associated with the model. - * - * @return string - */ - public function getTable() - { - if (!isset($this->table)) { - $this->setTable(str_replace( - '\\', - '', - Str::snake(Str::singular(class_basename($this))) - )); - } - - return $this->table; - } - - /** - * Get the foreign key column name. - * - * @return string - */ - public function getForeignKey() - { - return $this->foreignKey; - } - - /** - * Get the "related key" column name. - * - * @return string - */ - public function getRelatedKey() - { - return $this->relatedKey; - } - - /** - * Get the "related key" column name. - * - * @return string - */ - public function getOtherKey() - { - return $this->getRelatedKey(); - } - - /** - * Set the key names for the pivot model instance. - * - * @param string $foreignKey - * @param string $relatedKey - * @return $this - */ - public function setPivotKeys($foreignKey, $relatedKey) - { - $this->foreignKey = $foreignKey; - - $this->relatedKey = $relatedKey; - - return $this; - } - - /** - * Determine if the pivot model or given attributes has timestamp attributes. - * - * @param array|null $attributes - * @return bool - */ - public function hasTimestampAttributes($attributes = null) - { - return array_key_exists($this->getCreatedAtColumn(), $attributes ?? $this->attributes); - } - - /** - * Get the name of the "created at" column. + * Gets the parent attribute. * - * @return string - */ - public function getCreatedAtColumn() - { - return $this->pivotParent - ? $this->pivotParent->getCreatedAtColumn() - : parent::getCreatedAtColumn(); - } - - /** - * Get the name of the "updated at" column. - * - * @return string - */ - public function getUpdatedAtColumn() - { - return $this->pivotParent - ? $this->pivotParent->getUpdatedAtColumn() - : parent::getUpdatedAtColumn(); - } - - /** - * Get the queueable identity for the entity. - * - * @return mixed - */ - public function getQueueableId() - { - if (isset($this->attributes[$this->getKeyName()])) { - return $this->getKey(); - } - - return sprintf( - '%s:%s:%s:%s', - $this->foreignKey, - $this->getAttribute($this->foreignKey), - $this->relatedKey, - $this->getAttribute($this->relatedKey) - ); - } - - /** - * Get a new query to restore one or more models by their queueable IDs. + * Provided for backwards-compatibility. * - * @param int[]|string[]|string $ids - * @return \Illuminate\Database\Eloquent\Builder + * @param mixed $value + * @return \Illuminate\Database\Eloquent\Model|null */ - public function newQueryForRestoration($ids) + public function getParentAttribute($value) { - if (is_array($ids)) { - return $this->newQueryForCollectionRestoration($ids); - } - - if (!str_contains($ids, ':')) { - return parent::newQueryForRestoration($ids); - } - - $segments = explode(':', $ids); - - return $this->newQueryWithoutScopes() - ->where($segments[0], $segments[1]) - ->where($segments[2], $segments[3]); + return $this->pivotParent; } /** - * Get a new query to restore multiple models by their queueable IDs. + * Sets the parent attribute. * - * @param int[]|string[] $ids - * @return \Illuminate\Database\Eloquent\Builder - */ - protected function newQueryForCollectionRestoration(array $ids) - { - $ids = array_values($ids); - - if (!str_contains($ids[0], ':')) { - return parent::newQueryForRestoration($ids); - } - - $query = $this->newQueryWithoutScopes(); - - foreach ($ids as $id) { - $segments = explode(':', $id); - - $query->orWhere(function ($query) use ($segments) { - return $query->where($segments[0], $segments[1]) - ->where($segments[2], $segments[3]); - }); - } - - return $query; - } - - /** - * Unset all the loaded relations for the instance. + * Provided for backwards-compatibility. * - * @return $this + * @param \Illuminate\Database\Eloquent\Model $value + * @return void */ - public function unsetRelations() + public function setParentAttribute($value) { - $this->pivotParent = null; - $this->parent = null; - $this->relations = []; - - return $this; + $this->pivotParent = $value; } } From e8f880f572e18c641019c643461da63178fe3be0 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 22 May 2022 21:53:04 +0800 Subject: [PATCH 071/108] Update src/Database/Behaviors/Purgeable.php Co-authored-by: Luke Towers --- src/Database/Behaviors/Purgeable.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Database/Behaviors/Purgeable.php b/src/Database/Behaviors/Purgeable.php index 0ba34668..caf96396 100644 --- a/src/Database/Behaviors/Purgeable.php +++ b/src/Database/Behaviors/Purgeable.php @@ -1,8 +1,5 @@ Date: Sun, 22 May 2022 21:53:12 +0800 Subject: [PATCH 072/108] Update src/Database/Behaviors/Sortable.php Co-authored-by: Luke Towers --- src/Database/Behaviors/Sortable.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Database/Behaviors/Sortable.php b/src/Database/Behaviors/Sortable.php index 7fcd8d83..c88c5ccb 100644 --- a/src/Database/Behaviors/Sortable.php +++ b/src/Database/Behaviors/Sortable.php @@ -23,7 +23,6 @@ * * const SORT_ORDER = 'my_sort_order'; * - * @deprecated 1.2.0. We recommend using the \Winter\Storm\Database\Traits\Sortable trait instead. */ class Sortable extends ExtensionBase { From 6356bd28c3298430dcae597301e4e0ccb2cf5dd0 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Sun, 22 May 2022 11:26:19 -0600 Subject: [PATCH 073/108] Update src/Filesystem/Filesystem.php --- src/Filesystem/Filesystem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Filesystem/Filesystem.php b/src/Filesystem/Filesystem.php index 4b121547..ba8c3df9 100644 --- a/src/Filesystem/Filesystem.php +++ b/src/Filesystem/Filesystem.php @@ -404,7 +404,7 @@ public function fileNameMatch(string $fileName, string $pattern): bool } /** - * Finds symlinks within the base path and provides a source => target array of symlinks. + * Finds symlinks within the base path and populates the local symlinks property with an array of source => target symlinks. */ protected function findSymlinks(): void { From 361e17e746a4229ab0e80b87b14b023c551bb20f Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 26 Jun 2022 12:38:57 +0800 Subject: [PATCH 074/108] Remove relation traits that have been moved to concerns --- src/Database/Relations/AttachOneOrMany.php | 9 --------- src/Database/Relations/DeferOneOrMany.php | 9 --------- src/Database/Relations/DefinedConstraints.php | 9 --------- src/Database/Relations/HasOneOrMany.php | 9 --------- src/Database/Relations/MorphOneOrMany.php | 9 --------- 5 files changed, 45 deletions(-) delete mode 100644 src/Database/Relations/AttachOneOrMany.php delete mode 100644 src/Database/Relations/DeferOneOrMany.php delete mode 100644 src/Database/Relations/DefinedConstraints.php delete mode 100644 src/Database/Relations/HasOneOrMany.php delete mode 100644 src/Database/Relations/MorphOneOrMany.php diff --git a/src/Database/Relations/AttachOneOrMany.php b/src/Database/Relations/AttachOneOrMany.php deleted file mode 100644 index 67e5bdf9..00000000 --- a/src/Database/Relations/AttachOneOrMany.php +++ /dev/null @@ -1,9 +0,0 @@ - Date: Sun, 26 Jun 2022 12:58:27 +0800 Subject: [PATCH 075/108] Update all facade use cases to use full facade class --- src/Auth/AuthenticationException.php | 6 +++--- src/Auth/AuthorizationException.php | 3 --- src/Auth/Models/User.php | 8 ++++---- src/Cookie/Middleware/EncryptCookies.php | 2 +- src/Database/Attach/BrokenImage.php | 2 +- src/Database/Models/DeferredBinding.php | 3 +-- src/Database/Models/Revision.php | 1 - src/Database/Traits/Encryptable.php | 2 +- src/Database/Traits/Hashable.php | 2 +- src/Database/Traits/NestedTree.php | 4 ++-- src/Database/Traits/Revisionable.php | 6 +++--- src/Database/Traits/Validation.php | 2 +- src/Database/Updater.php | 9 ++++----- src/Exception/ErrorHandler.php | 7 +++---- src/Exception/ExceptionBase.php | 2 +- src/Flash/FlashBag.php | 2 +- src/Foundation/Application.php | 12 ++++++------ src/Foundation/Exception/Handler.php | 8 ++++---- .../Http/Middleware/CheckForMaintenanceMode.php | 6 +++--- .../Http/Middleware/CheckForTrustedHost.php | 2 +- src/Halcyon/MemoryCacheManager.php | 4 ++-- src/Mail/Mailable.php | 2 +- src/Parse/Assetic/Cache/FilesystemCache.php | 2 +- src/Parse/Assetic/Filter/JavascriptImporter.php | 2 +- src/Parse/Assetic/Filter/ScssCompiler.php | 2 +- src/Parse/Syntax/SyntaxModelTrait.php | 2 +- src/Parse/Twig.php | 2 +- src/Support/Singleton.php | 2 +- 28 files changed, 50 insertions(+), 57 deletions(-) diff --git a/src/Auth/AuthenticationException.php b/src/Auth/AuthenticationException.php index 21c473f6..55ec3b44 100644 --- a/src/Auth/AuthenticationException.php +++ b/src/Auth/AuthenticationException.php @@ -1,9 +1,9 @@ getTable())->insert($toSave); + DB::table($revisionModel->getTable())->insert($toSave); $this->revisionableCleanUp(); } diff --git a/src/Database/Traits/Validation.php b/src/Database/Traits/Validation.php index 8634539a..94dca33d 100644 --- a/src/Database/Traits/Validation.php +++ b/src/Database/Traits/Validation.php @@ -1,8 +1,8 @@ isValidScript($object, $file); - Eloquent::unguard(); + Model::unguard(); if ($object instanceof Updates\Migration && method_exists($object, 'up')) { $object->up(); @@ -36,7 +35,7 @@ public function setUp($file) $object->run(); } - Eloquent::reguard(); + Model::reguard(); return true; } diff --git a/src/Exception/ErrorHandler.php b/src/Exception/ErrorHandler.php index 8b52b22d..b429cf99 100644 --- a/src/Exception/ErrorHandler.php +++ b/src/Exception/ErrorHandler.php @@ -1,10 +1,9 @@ getType() instanceof \ReflectionUnionType) { + } elseif ($expected->getType() instanceof \ReflectionUnionType) { foreach ($expected->getType()->getTypes() as $type) { try { return (new ReflectionClass($type->getName())) diff --git a/src/Foundation/Http/Middleware/CheckForMaintenanceMode.php b/src/Foundation/Http/Middleware/CheckForMaintenanceMode.php index 851b21cc..6a82797b 100644 --- a/src/Foundation/Http/Middleware/CheckForMaintenanceMode.php +++ b/src/Foundation/Http/Middleware/CheckForMaintenanceMode.php @@ -2,11 +2,11 @@ namespace Winter\Storm\Foundation\Http\Middleware; -use Lang; -use View; use Closure; -use Response; use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode as Middleware; +use Illuminate\Support\Facades\Lang; +use Illuminate\Support\Facades\View; +use Illuminate\Support\Facades\Response; class CheckForMaintenanceMode extends Middleware { diff --git a/src/Foundation/Http/Middleware/CheckForTrustedHost.php b/src/Foundation/Http/Middleware/CheckForTrustedHost.php index be181df8..12826410 100644 --- a/src/Foundation/Http/Middleware/CheckForTrustedHost.php +++ b/src/Foundation/Http/Middleware/CheckForTrustedHost.php @@ -1,6 +1,6 @@ Date: Sun, 26 Jun 2022 13:11:39 +0800 Subject: [PATCH 076/108] Add options docs for Zip class, fix BC break --- src/Filesystem/Zip.php | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Filesystem/Zip.php b/src/Filesystem/Zip.php index 6ee82d35..d8ca7a62 100644 --- a/src/Filesystem/Zip.php +++ b/src/Filesystem/Zip.php @@ -63,6 +63,11 @@ final public function __construct() /** * Extracts an existing ZIP file. + * + * @param string $source Path to the ZIP file. + * @param string $destination Path to the destination directory. + * @param array $options Optional. An array of options. Only one option is currently supported: + * `mask`, which defines the permission mask to use when creating the destination folder. */ public static function extract(string $source, string $destination, array $options = []): bool { @@ -83,7 +88,12 @@ public static function extract(string $source, string $destination, array $optio /** * Creates a new empty Zip file, optionally populating it with given source files. * - * Source can be a single path, an array of paths or a callback which allows you to manipulate the Zip file. + * Source can be a single path, an array of paths or a callback which allows you to manipulate + * the Zip file. + * + * @param string $destination Path to the destination ZIP file. + * @param string|callable|array|null $source Optional. Path to the source file(s) or a callback. + * @param array $options Optional. An array of options. Uses the same options as `Zip::add()`. */ public static function make(string $destination, string|callable|array|null $source = null, array $options = []): static { @@ -106,6 +116,15 @@ public static function make(string $destination, string|callable|array|null $sou /** * Adds a source file or directory to a Zip file. + * + * @param string $source Path to the source file or directory. + * @param array $options Optional. An array of options. Supports the following options: + * - `recursive`, which determines whether to add subdirectories and files recursively. + * Defaults to `true`. + * - `includeHidden`, which determines whether to add hidden files and directories. + * Defaults to `false`. + * - `baseDir`, which determines the base directory to use when adding files. + * - `baseglob`, which defines a glob pattern to match files and directories to add. */ public function add(string $source, array $options = []): self { @@ -125,8 +144,8 @@ public function add(string $source, array $options = []): self $source = implode('/', [dirname($source), basename($source), $wildcard]); } - $basedir = dirname($source); - $baseglob = basename($source); + $basedir = $options['basedir'] ?? dirname($source); + $baseglob = $options['baseglob'] ?? basename($source); if (is_file($source)) { $files = [$source]; From 1ef7cfca616ad461c5e6573e7b11336782a99495 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 26 Jun 2022 13:18:48 +0800 Subject: [PATCH 077/108] Re-add default value --- src/Halcyon/Processors/SectionParser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Halcyon/Processors/SectionParser.php b/src/Halcyon/Processors/SectionParser.php index 608d4034..9b5cf53a 100644 --- a/src/Halcyon/Processors/SectionParser.php +++ b/src/Halcyon/Processors/SectionParser.php @@ -72,7 +72,7 @@ public static function render(array $data, array $options = []): string extract($sectionOptions); if (!isset($isCompoundObject) || $isCompoundObject === false) { - return array_get($data, 'content'); + return array_get($data, 'content', ''); } // Prepare settings section for saving From 1154159f90cd38fdd9fdef3e7aed81c99f234be4 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 26 Jun 2022 19:13:21 +0800 Subject: [PATCH 078/108] Remove overwritten definition for resolver --- src/Validation/Factory.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Validation/Factory.php b/src/Validation/Factory.php index eda200dc..e1ae2b11 100644 --- a/src/Validation/Factory.php +++ b/src/Validation/Factory.php @@ -10,13 +10,6 @@ */ class Factory extends BaseFactory implements FactoryContract { - /** - * The Validator resolver instance. - * - * @var \Closure|null - */ - protected $resolver; - /** * Resolve a new Validator instance. * From 05698c8f990d657b19207cfb69632bac39032578 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 29 Jun 2022 14:12:46 +0800 Subject: [PATCH 079/108] Fix moved class names in aliases --- src/Support/aliases.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Support/aliases.php b/src/Support/aliases.php index f0f7e7e0..a3debf09 100644 --- a/src/Support/aliases.php +++ b/src/Support/aliases.php @@ -140,19 +140,19 @@ class_alias(\Winter\Storm\Database\Query\Grammars\SqlServerGrammar::class, \Octo class_alias(\Winter\Storm\Database\QueryBuilder::class, \October\Rain\Database\QueryBuilder::class); class_alias(\Winter\Storm\Database\Relations\AttachMany::class, \October\Rain\Database\Relations\AttachMany::class); class_alias(\Winter\Storm\Database\Relations\AttachOne::class, \October\Rain\Database\Relations\AttachOne::class); -class_alias(\Winter\Storm\Database\Relations\AttachOneOrMany::class, \October\Rain\Database\Relations\AttachOneOrMany::class); +class_alias(\Winter\Storm\Database\Relations\Concerns\AttachOneOrMany::class, \October\Rain\Database\Relations\AttachOneOrMany::class); class_alias(\Winter\Storm\Database\Relations\BelongsTo::class, \October\Rain\Database\Relations\BelongsTo::class); class_alias(\Winter\Storm\Database\Relations\BelongsToMany::class, \October\Rain\Database\Relations\BelongsToMany::class); -class_alias(\Winter\Storm\Database\Relations\DeferOneOrMany::class, \October\Rain\Database\Relations\DeferOneOrMany::class); -class_alias(\Winter\Storm\Database\Relations\DefinedConstraints::class, \October\Rain\Database\Relations\DefinedConstraints::class); +class_alias(\Winter\Storm\Database\Relations\Concerns\DeferOneOrMany::class, \October\Rain\Database\Relations\DeferOneOrMany::class); +class_alias(\Winter\Storm\Database\Relations\Concerns\DefinedConstraints::class, \October\Rain\Database\Relations\DefinedConstraints::class); class_alias(\Winter\Storm\Database\Relations\HasMany::class, \October\Rain\Database\Relations\HasMany::class); class_alias(\Winter\Storm\Database\Relations\HasManyThrough::class, \October\Rain\Database\Relations\HasManyThrough::class); class_alias(\Winter\Storm\Database\Relations\HasOne::class, \October\Rain\Database\Relations\HasOne::class); -class_alias(\Winter\Storm\Database\Relations\HasOneOrMany::class, \October\Rain\Database\Relations\HasOneOrMany::class); +class_alias(\Winter\Storm\Database\Relations\Concerns\HasOneOrMany::class, \October\Rain\Database\Relations\HasOneOrMany::class); class_alias(\Winter\Storm\Database\Relations\HasOneThrough::class, \October\Rain\Database\Relations\HasOneThrough::class); class_alias(\Winter\Storm\Database\Relations\MorphMany::class, \October\Rain\Database\Relations\MorphMany::class); class_alias(\Winter\Storm\Database\Relations\MorphOne::class, \October\Rain\Database\Relations\MorphOne::class); -class_alias(\Winter\Storm\Database\Relations\MorphOneOrMany::class, \October\Rain\Database\Relations\MorphOneOrMany::class); +class_alias(\Winter\Storm\Database\Relations\Concerns\MorphOneOrMany::class, \October\Rain\Database\Relations\MorphOneOrMany::class); class_alias(\Winter\Storm\Database\Relations\MorphTo::class, \October\Rain\Database\Relations\MorphTo::class); class_alias(\Winter\Storm\Database\Relations\MorphToMany::class, \October\Rain\Database\Relations\MorphToMany::class); class_alias(\Winter\Storm\Database\Relations\Relation::class, \October\Rain\Database\Relations\Relation::class); From 15b359b0e01cfe246b49a6959457d66fe6db4e3c Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 29 Jun 2022 14:30:33 +0800 Subject: [PATCH 080/108] Add MorphPivot test cases --- tests/Database/MorphPivotTest.php | 147 ++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 tests/Database/MorphPivotTest.php diff --git a/tests/Database/MorphPivotTest.php b/tests/Database/MorphPivotTest.php new file mode 100644 index 00000000..f5d1d30c --- /dev/null +++ b/tests/Database/MorphPivotTest.php @@ -0,0 +1,147 @@ +createTables(); + } + + public function testCreateMorphyToManyRelationAndCheckForMorphPivot() + { + // Create a couple of tags + $cool = Tag::create([ + 'name' => 'Cool', + ]); + $awesome = Tag::create([ + 'name' => 'Awesome', + ]); + + // Create a post + $post = Post::create([ + 'title' => 'Check this out', + 'body' => 'It is pretty cool and pretty awesome too', + ]); + + // Attach tags + $post->tags()->attach($cool); + $post->tags()->attach($awesome); + + // Get first tag and get a pivot instance + $pivot = $post->tags()->first()->pivot; + + $this->assertInstanceOf(MorphPivot::class, $pivot); + $this->assertEquals('0', $pivot->hidden); + } + + public function testCreateMorphyToManyRelationAndCheckForCustomMorphPivot() + { + // Create a couple of tags + $cool = Tag::create([ + 'name' => 'Cool', + ]); + $awesome = Tag::create([ + 'name' => 'Awesome', + ]); + + // Create a post + $post = CustomPost::create([ + 'title' => 'Check this out', + 'body' => 'It is pretty cool and pretty awesome too', + ]); + + // Attach tags + $post->tags()->attach($cool); + $post->tags()->attach($awesome); + + // Get first tag and get a pivot instance + $pivot = $post->tags()->first()->pivot; + + $this->assertInstanceOf(CustomMorphPivot::class, $pivot); + } + + protected function createTables() + { + $this->getBuilder()->create('posts', function ($table) { + $table->increments('id'); + $table->string('title'); + $table->text('body')->nullable(); + $table->timestamps(); + }); + + $this->getBuilder()->create('tags', function ($table) { + $table->increments('id'); + $table->string('name'); + $table->timestamps(); + }); + + $this->getBuilder()->create('taggings', function ($table) { + $table->increments('id'); + $table->integer('tag_id')->unsigned(); + $table->morphs('taggable'); + $table->boolean('hidden')->default(0); + $table->timestamps(); + }); + } +} + +class Post extends Model +{ + public $table = 'posts'; + + public $fillable = [ + 'title', + 'body', + ]; + + public $morphToMany = [ + 'tags' => [ + Tag::class, + 'table' => 'taggings', + 'name' => 'taggable', + 'pivot' => ['hidden'], + ], + ]; +} + +class CustomPost extends Post +{ + public $morphToMany = [ + 'tags' => [ + Tag::class, + 'table' => 'taggings', + 'name' => 'taggable', + 'pivot' => ['hidden'], + 'pivotModel' => CustomPivot::class, + ], + ]; +} + +class Tagging extends Model +{ + public $table = 'taggings'; + + protected $casts = [ + 'hidden' => 'boolean', + ]; +} + +class Tag extends Model +{ + public $table = 'tags'; + + public $fillable = [ + 'name', + ]; +} + +class CustomMorphPivot extends MorphPivot +{ +} From e374dce524feea6b7f9ac2a21c2d41bcedb07f76 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 29 Jun 2022 14:30:48 +0800 Subject: [PATCH 081/108] Fix constructor for MorphToPivot --- src/Database/Relations/MorphToMany.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Database/Relations/MorphToMany.php b/src/Database/Relations/MorphToMany.php index a40fcffb..244d3da6 100644 --- a/src/Database/Relations/MorphToMany.php +++ b/src/Database/Relations/MorphToMany.php @@ -51,6 +51,7 @@ public function __construct( parent::__construct( $query, $parent, + $name, $table, $foreignKey, $otherKey, From c168945542e711706954e4e1302539c0a4aeb12f Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 29 Jun 2022 14:37:18 +0800 Subject: [PATCH 082/108] Revert hour value change --- src/Html/FormBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Html/FormBuilder.php b/src/Html/FormBuilder.php index 3392ba77..f3c0eef3 100644 --- a/src/Html/FormBuilder.php +++ b/src/Html/FormBuilder.php @@ -475,7 +475,7 @@ public function selectMonth(string $name, string|array|null $selected = null, ar $months = []; foreach (range(1, 12) as $month) { - $months[$month] = strftime($format, mktime(12, 0, 0, $month, 1)); + $months[$month] = strftime($format, mktime(0, 0, 0, $month, 1)); } return $this->select($name, $months, $selected, $options); From a8da8cd1debf239f19629fc195476389ca5bc82a Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 29 Jun 2022 14:41:27 +0800 Subject: [PATCH 083/108] Correct docblock return --- src/Html/FormBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Html/FormBuilder.php b/src/Html/FormBuilder.php index f3c0eef3..79ed2811 100644 --- a/src/Html/FormBuilder.php +++ b/src/Html/FormBuilder.php @@ -828,7 +828,7 @@ protected function getAppendage($method) * * @param string $name * @param array $attributes - * @return string|null + * @return string */ public function getIdAttribute($name, $attributes) { From 08b75fc97459816854154c8c98713536d4cf68d1 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 30 Jun 2022 10:22:08 +0800 Subject: [PATCH 084/108] Match PHPCS config from main repo --- phpcs.xml.dist | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index f7d16d94..48ed245f 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -9,12 +9,13 @@ + + + - + */src/Auth/Migrations/*\.php */src/Database/Migrations/*\.php */tests/* @@ -28,6 +29,9 @@ */tests/* + + + src/ tests/ From 0ec2ac532f8d98c71e66a19b37c0dec064a79d59 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 30 Jun 2022 10:31:06 +0800 Subject: [PATCH 085/108] Use lang path in Translator registration --- src/Foundation/Application.php | 1 + src/Translation/TranslationServiceProvider.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Foundation/Application.php b/src/Foundation/Application.php index 7132ab86..00112b69 100644 --- a/src/Foundation/Application.php +++ b/src/Foundation/Application.php @@ -161,6 +161,7 @@ protected function bindPathsInContainer() $this->instance('path.temp', $this->tempPath()); $this->instance('path.uploads', $this->uploadsPath()); $this->instance('path.media', $this->mediaPath()); + $this->instance('path.lang', $this->langPath()); } /** diff --git a/src/Translation/TranslationServiceProvider.php b/src/Translation/TranslationServiceProvider.php index 05fc9bd4..0cbfc4ab 100644 --- a/src/Translation/TranslationServiceProvider.php +++ b/src/Translation/TranslationServiceProvider.php @@ -35,7 +35,7 @@ public function register() protected function registerLoader() { $this->app->singleton('translation.loader', function ($app) { - return new FileLoader($app['files'], $app['path'].'/lang'); + return new FileLoader($app['files'], $app['path.lang']); }); } From 892b8906bca4fcbe62261b9a694daeae9d46d3a3 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 30 Jun 2022 10:31:28 +0800 Subject: [PATCH 086/108] Fix possible BC break, cleanup for PHPStan --- src/Translation/Translator.php | 10 +++++++--- tests/Translation/TranslatorTest.php | 3 +-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Translation/Translator.php b/src/Translation/Translator.php index 77de1c75..aef6454f 100644 --- a/src/Translation/Translator.php +++ b/src/Translation/Translator.php @@ -132,7 +132,7 @@ protected function localeForChoice($locale) { $locale = parent::localeForChoice($locale); - if (!is_null($locale) && str_contains($locale, '-')) { + if (str_contains($locale, '-')) { $localeParts = explode('-', $locale, 2); $locale = $localeParts[0] . '_' . strtoupper($localeParts[1]); } @@ -167,9 +167,13 @@ public function parseKey($key) */ protected function localeArray($locale) { - $locales = array_values(array_filter([$locale ?: $this->locale, $this->fallback, static::CORE_LOCALE])); + $locales = array_values(parent::localeArray($locale)); - return call_user_func($this->determineLocalesUsing ?: fn () => $locales, $locales); + if (!in_array(static::CORE_LOCALE, $locales)) { + $locales[] = static::CORE_LOCALE; + } + + return $locales; } /** diff --git a/tests/Translation/TranslatorTest.php b/tests/Translation/TranslatorTest.php index 1ab7b0fc..29b6f8cd 100644 --- a/tests/Translation/TranslatorTest.php +++ b/tests/Translation/TranslatorTest.php @@ -5,7 +5,6 @@ use Illuminate\Support\Collection; use Illuminate\Translation\MessageSelector; use Mockery as m; -use Winter\Storm\Events\Dispatcher; use Winter\Storm\Translation\FileLoader; use Winter\Storm\Translation\Translator; @@ -323,7 +322,7 @@ public function testDetermineLocalesUsingMethod() { $t = new Translator($this->getLoader(), 'en'); $t->determineLocalesUsing(function ($locales) { - $this->assertSame(['en', 'en'], $locales); + $this->assertSame(['en'], $locales); return ['en', 'lz']; }); From 4301383062efcd9ed2ecafb971ef9e9b55427eaf Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 30 Jun 2022 10:42:23 +0800 Subject: [PATCH 087/108] Code tidying --- src/Foundation/Providers/ExecutionContextProvider.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Foundation/Providers/ExecutionContextProvider.php b/src/Foundation/Providers/ExecutionContextProvider.php index 2bc2282d..ac2099f2 100644 --- a/src/Foundation/Providers/ExecutionContextProvider.php +++ b/src/Foundation/Providers/ExecutionContextProvider.php @@ -19,8 +19,7 @@ public function register() if (starts_with($requestPath, $backendUri)) { return 'back-end'; - } - else { + } else { return 'front-end'; } }); @@ -34,14 +33,10 @@ public function register() */ protected function normalizeUrl($url) { - if (substr($url, 0, 1) != '/') { - $url = '/'.$url; - } - if (!strlen($url)) { - $url = '/'; + return '/'; } - return $url; + return (substr($url, 0, 1) !== '/') ? '/' . $url : $url; } } From cbec8d9f76f1bdca9b425cc5dac6a3557b70f749 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 30 Jun 2022 11:40:19 +0800 Subject: [PATCH 088/108] Try out @phpstan-property definitions to fix remaining issues --- src/Database/Behaviors/Purgeable.php | 1 - src/Database/Connections/MySqlConnection.php | 3 +++ src/Database/Connections/PostgresConnection.php | 3 +++ src/Database/Connections/SQLiteConnection.php | 3 +++ src/Database/Connections/SqlServerConnection.php | 3 +++ src/Database/Model.php | 2 ++ src/Database/Relations/AttachMany.php | 5 ++++- src/Database/Relations/AttachOne.php | 3 +++ src/Database/Relations/BelongsTo.php | 3 +++ src/Database/Relations/HasMany.php | 3 +++ src/Database/Relations/HasManyThrough.php | 4 ++++ src/Database/Relations/HasOne.php | 3 +++ src/Database/Relations/HasOneThrough.php | 4 ++++ src/Database/Relations/MorphMany.php | 3 +++ src/Database/Relations/MorphOne.php | 3 +++ src/Database/Relations/MorphTo.php | 3 +++ src/Database/Relations/MorphToMany.php | 2 ++ 17 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/Database/Behaviors/Purgeable.php b/src/Database/Behaviors/Purgeable.php index caf96396..dd1c84d6 100644 --- a/src/Database/Behaviors/Purgeable.php +++ b/src/Database/Behaviors/Purgeable.php @@ -6,7 +6,6 @@ class Purgeable extends \Winter\Storm\Extension\ExtensionBase * Model to purge. * * @var \Winter\Storm\Database\Model - * @property array $purgeable */ protected $model; diff --git a/src/Database/Connections/MySqlConnection.php b/src/Database/Connections/MySqlConnection.php index d3ec20d0..2458a9ee 100644 --- a/src/Database/Connections/MySqlConnection.php +++ b/src/Database/Connections/MySqlConnection.php @@ -7,6 +7,9 @@ use Illuminate\Database\Schema\Grammars\MySqlGrammar as SchemaGrammar; use Winter\Storm\Database\Query\Grammars\MySqlGrammar as QueryGrammar; +/** + * @phpstan-property \Illuminate\Database\Schema\Grammars\Grammar|null $schemaGrammar + */ class MySqlConnection extends Connection { /** diff --git a/src/Database/Connections/PostgresConnection.php b/src/Database/Connections/PostgresConnection.php index 433edef7..d768d69e 100644 --- a/src/Database/Connections/PostgresConnection.php +++ b/src/Database/Connections/PostgresConnection.php @@ -6,6 +6,9 @@ use Winter\Storm\Database\Query\Grammars\PostgresGrammar as QueryGrammar; use Illuminate\Database\Schema\Grammars\PostgresGrammar as SchemaGrammar; +/** + * @phpstan-property \Illuminate\Database\Schema\Grammars\Grammar|null $schemaGrammar + */ class PostgresConnection extends Connection { /** diff --git a/src/Database/Connections/SQLiteConnection.php b/src/Database/Connections/SQLiteConnection.php index 133d8898..1d6e3db1 100644 --- a/src/Database/Connections/SQLiteConnection.php +++ b/src/Database/Connections/SQLiteConnection.php @@ -6,6 +6,9 @@ use Winter\Storm\Database\Query\Grammars\SQLiteGrammar as QueryGrammar; use Illuminate\Database\Schema\Grammars\SQLiteGrammar as SchemaGrammar; +/** + * @phpstan-property \Illuminate\Database\Schema\Grammars\Grammar|null $schemaGrammar + */ class SQLiteConnection extends Connection { /** diff --git a/src/Database/Connections/SqlServerConnection.php b/src/Database/Connections/SqlServerConnection.php index 0baf3a17..07a6af21 100644 --- a/src/Database/Connections/SqlServerConnection.php +++ b/src/Database/Connections/SqlServerConnection.php @@ -9,6 +9,9 @@ use Illuminate\Database\Schema\Grammars\SqlServerGrammar as SchemaGrammar; use Winter\Storm\Database\Query\Grammars\SqlServerGrammar as QueryGrammar; +/** + * @phpstan-property \Illuminate\Database\Schema\Grammars\Grammar|null $schemaGrammar + */ class SqlServerConnection extends Connection { /** diff --git a/src/Database/Model.php b/src/Database/Model.php index b37c2e78..a180a987 100644 --- a/src/Database/Model.php +++ b/src/Database/Model.php @@ -15,6 +15,8 @@ * Extends Eloquent with added extendability and deferred bindings. * * @author Alexey Bobkov, Samuel Georges + * + * @phpstan-property \Illuminate\Contracts\Events\Dispatcher|null $dispatcher */ class Model extends EloquentModel implements ModelInterface { diff --git a/src/Database/Relations/AttachMany.php b/src/Database/Relations/AttachMany.php index 6442a89d..06e0005b 100644 --- a/src/Database/Relations/AttachMany.php +++ b/src/Database/Relations/AttachMany.php @@ -1,10 +1,13 @@ Date: Fri, 1 Jul 2022 11:07:39 +0800 Subject: [PATCH 089/108] Docs and commenting changes to fix a whole slew of issues --- src/Auth/Manager.php | 38 ++++++++++++++++++++++++++------------ src/Auth/Models/User.php | 2 +- src/Database/Model.php | 29 +++++++++++------------------ src/Halcyon/Model.php | 17 +++++++---------- 4 files changed, 45 insertions(+), 41 deletions(-) diff --git a/src/Auth/Manager.php b/src/Auth/Manager.php index 1803a17b..e6c4285a 100644 --- a/src/Auth/Manager.php +++ b/src/Auth/Manager.php @@ -13,12 +13,12 @@ class Manager implements \Illuminate\Contracts\Auth\StatefulGuard use \Winter\Storm\Support\Traits\Singleton; /** - * @var Models\User The currently logged in user + * @var Models\User|null The currently logged in user */ protected $user; /** - * @var Models\User The user that is impersonating the currently logged in user when applicable + * @var Models\User|null The user that is impersonating the currently logged in user when applicable */ protected $impersonator; @@ -103,6 +103,7 @@ public function createUserModel() protected function createUserModelQuery() { $model = $this->createUserModel(); + /** @var \Winter\Storm\Database\Builder */ $query = $model->newQuery(); $this->extendUserQuery($query); @@ -139,6 +140,7 @@ public function register(array $credentials, $activate = false, $autoLogin = tru // Prevents revalidation of the password field // on subsequent saves to this model object + /** @phpstan-ignore-next-line */ $user->password = null; if ($autoLogin) { @@ -159,6 +161,7 @@ public function hasUser() /** * Sets the user + * @phpstan-param Models\User $user */ public function setUser(Authenticatable $user) { @@ -244,6 +247,7 @@ public function findUserByCredentials(array $credentials) } } + /** @var Models\User */ $user = $query->first(); if (!$this->validateUserModel($user)) { throw new AuthenticationException('A user was not found with the given credentials.'); @@ -338,7 +342,10 @@ public function findThrottleByUserId($userId, $ipAddress = null) }); } - if (!$throttle = $query->first()) { + /** @var Models\Throttle|null */ + $throttle = $query->first(); + + if (!$throttle) { $throttle = $this->createThrottleModel(); $throttle->user_id = $userId; if ($ipAddress) { @@ -361,7 +368,7 @@ public function findThrottleByUserId($userId, $ipAddress = null) * @param array $credentials The user login details * @param bool $remember Store a non-expire cookie for the user * @throws AuthenticationException If authentication fails - * @return Models\User The successfully logged in user + * @return bool If authentication was successful */ public function attempt(array $credentials = [], $remember = false) { @@ -383,7 +390,7 @@ public function validate(array $credentials = []) * Validate a user's credentials, method used internally. * * @param array $credentials - * @return User + * @return Models\User|null */ protected function validateInternal(array $credentials = []) { @@ -413,7 +420,9 @@ protected function validateInternal(array $credentials = []) /* * If throttling is enabled, check they are not locked out first and foremost. */ - if ($this->useThrottle) { + $useThrottle = $this->useThrottle; + + if ($useThrottle) { $throttle = $this->findThrottleByLogin($credentials[$loginName], $this->ipAddress); $throttle->check(); } @@ -425,14 +434,15 @@ protected function validateInternal(array $credentials = []) $user = $this->findUserByCredentials($credentials); } catch (AuthenticationException $ex) { - if ($this->useThrottle) { + if ($useThrottle) { $throttle->addLoginAttempt(); } + $user = null; throw $ex; } - if ($this->useThrottle) { + if ($useThrottle) { $throttle->clearLoginAttempts(); } @@ -621,6 +631,7 @@ public function onceUsingId($id) * Logs in the given user and sets properties * in the session. * @throws AuthenticationException If the user is not activated and $this->requireActivation = true + * @phpstan-param Models\User $user */ public function login(Authenticatable $user, $remember = true) { @@ -656,7 +667,7 @@ public function login(Authenticatable $user, $remember = true) * * @param mixed $id * @param bool $remember - * @return \Illuminate\Contracts\Auth\Authenticatable + * @return \Illuminate\Contracts\Auth\Authenticatable|false */ public function loginUsingId($id, $remember = false) { @@ -714,7 +725,7 @@ public function logout() * Impersonates the given user and sets properties in the session but not the cookie. * * @param Models\User $impersonatee - * @throws Exception If the current user is not permitted to impersonate the provided user + * @throws AuthorizationException If the current user is not permitted to impersonate the provided user * @return void */ public function impersonate($impersonatee) @@ -828,7 +839,7 @@ public function isImpersonator() /** * Get the original user doing the impersonation * - * @return mixed Returns the User model for the impersonator if able, false if not + * @return Models\User|false Returns the User model for the impersonator if able, `false` if not */ public function getImpersonator() { @@ -845,7 +856,10 @@ public function getImpersonator() return $this->impersonator; } - return $this->impersonator = $this->createUserModel()->find($impersonatorId); + /** @var Models\User|false */ + $impersonator = $this->createUserModel()->find($impersonatorId) ?? false; + + return $this->impersonator = $impersonator; } /** diff --git a/src/Auth/Models/User.php b/src/Auth/Models/User.php index a4ae7ba6..8e7f020a 100644 --- a/src/Auth/Models/User.php +++ b/src/Auth/Models/User.php @@ -640,7 +640,7 @@ public function getRememberToken() /** * Set the token value for the "remember me" session. - * @param string $value + * @param string|null $value * @return void */ public function setRememberToken($value) diff --git a/src/Database/Model.php b/src/Database/Model.php index a180a987..e708566f 100644 --- a/src/Database/Model.php +++ b/src/Database/Model.php @@ -91,7 +91,7 @@ public static function create(array $attributes = [], $sessionKey = null) { $model = new static($attributes); - $model->save(null, $sessionKey); + $model->save([], $sessionKey); return $model; } @@ -764,7 +764,7 @@ public function newRelationPivot($relationName, $parent, $attributes, $table, $e * @param array $options * @return bool */ - protected function saveInternal($options = []) + protected function saveInternal(array $options = []) { /** * @event model.saveInternal @@ -805,15 +805,6 @@ protected function saveInternal($options = []) return $result; } - /* - * If there is nothing to update, Eloquent will not fire afterSave(), - * events should still fire for consistency. - */ - if ($result === null) { - $this->fireModelEvent('updated', false); - $this->fireModelEvent('saved', false); - } - // Apply post deferred bindings if ($this->sessionKey !== null) { $this->commitDeferredAfter($this->sessionKey); @@ -824,11 +815,11 @@ protected function saveInternal($options = []) /** * Save the model to the database. - * @param array|null $options + * @param array $options * @param string|null $sessionKey * @return bool */ - public function save(array $options = null, $sessionKey = null) + public function save(?array $options = [], $sessionKey = null) { $this->sessionKey = $sessionKey; return $this->saveInternal(['force' => false] + (array) $options); @@ -836,15 +827,16 @@ public function save(array $options = null, $sessionKey = null) /** * Save the model and all of its relationships. + * * @param array $options - * @param null $sessionKey + * @param string|null $sessionKey * @return bool */ - public function push($options = null, $sessionKey = null) + public function push(?array $options = [], $sessionKey = null) { $always = Arr::get($options, 'always', false); - if (!$this->save(null, $sessionKey) && !$always) { + if (!$this->save([], $sessionKey) && !$always) { return false; } @@ -876,11 +868,12 @@ public function push($options = null, $sessionKey = null) /** * Pushes the first level of relations even if the parent * model has no changes. + * * @param array $options - * @param string $sessionKey + * @param string|null $sessionKey * @return bool */ - public function alwaysPush($options, $sessionKey) + public function alwaysPush(?array $options = [], $sessionKey = null) { return $this->push(['always' => true] + (array) $options, $sessionKey); } diff --git a/src/Halcyon/Model.php b/src/Halcyon/Model.php index abf04a03..427607cd 100644 --- a/src/Halcyon/Model.php +++ b/src/Halcyon/Model.php @@ -1214,7 +1214,7 @@ public function update(array $attributes = []) * @param array $options * @return bool */ - public function save(array $options = null) + public function save(?array $options = []) { return $this->saveInternal(['force' => false] + (array) $options); } @@ -1244,14 +1244,14 @@ public function saveInternal(array $options = []) } if ($this->exists) { - $saved = $this->performUpdate($query, $options); + $saved = $this->performUpdate($query); } else { - $saved = $this->performInsert($query, $options); + $saved = $this->performInsert($query); } if ($saved) { - $this->finishSave($options); + $this->finishSave(); } return $saved; @@ -1260,10 +1260,9 @@ public function saveInternal(array $options = []) /** * Finish processing on a successful save operation. * - * @param array $options * @return void */ - protected function finishSave(array $options) + protected function finishSave() { $this->fireModelEvent('saved', false); @@ -1276,10 +1275,9 @@ protected function finishSave(array $options) * Perform a model update operation. * * @param \Winter\Storm\Halcyon\Builder $query - * @param array $options * @return bool */ - protected function performUpdate(Builder $query, array $options = []) + protected function performUpdate(Builder $query) { $dirty = $this->getDirty(); @@ -1309,10 +1307,9 @@ protected function performUpdate(Builder $query, array $options = []) * Perform a model insert operation. * * @param \Winter\Storm\Halcyon\Builder $query - * @param array $options * @return bool */ - protected function performInsert(Builder $query, array $options = []) + protected function performInsert(Builder $query) { if ($this->fireModelEvent('creating') === false) { return false; From 79761b59369020aa76761fda45f252f931619c80 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 1 Jul 2022 11:07:45 +0800 Subject: [PATCH 090/108] Generate baseline --- phpstan-baseline.neon | 921 ++++++++++++++++++++++++++++++++++++++++++ phpstan.neon | 1 + 2 files changed, 922 insertions(+) create mode 100644 phpstan-baseline.neon diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 00000000..03b31dce --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,921 @@ +parameters: + ignoreErrors: + - + message: "#^Call to an undefined method \\$this\\(Winter\\\\Storm\\\\Auth\\\\Models\\\\Group\\)\\:\\:getOriginalEncryptableValues\\(\\)\\.$#" + count: 1 + path: src/Auth/Models/Group.php + + - + message: "#^Call to an undefined method \\$this\\(Winter\\\\Storm\\\\Auth\\\\Models\\\\Group\\)\\:\\:getOriginalHashValues\\(\\)\\.$#" + count: 1 + path: src/Auth/Models/Group.php + + - + message: "#^Call to an undefined method Winter\\\\Storm\\\\Auth\\\\Models\\\\Group\\:\\:afterValidate\\(\\)\\.$#" + count: 1 + path: src/Auth/Models/Group.php + + - + message: "#^Call to an undefined method Winter\\\\Storm\\\\Auth\\\\Models\\\\Group\\:\\:beforeValidate\\(\\)\\.$#" + count: 1 + path: src/Auth/Models/Group.php + + - + message: "#^Call to an undefined method \\$this\\(Winter\\\\Storm\\\\Auth\\\\Models\\\\Role\\)\\:\\:getOriginalEncryptableValues\\(\\)\\.$#" + count: 1 + path: src/Auth/Models/Role.php + + - + message: "#^Call to an undefined method \\$this\\(Winter\\\\Storm\\\\Auth\\\\Models\\\\Role\\)\\:\\:getOriginalHashValues\\(\\)\\.$#" + count: 1 + path: src/Auth/Models/Role.php + + - + message: "#^Call to an undefined method Winter\\\\Storm\\\\Auth\\\\Models\\\\Role\\:\\:afterValidate\\(\\)\\.$#" + count: 1 + path: src/Auth/Models/Role.php + + - + message: "#^Call to an undefined method Winter\\\\Storm\\\\Auth\\\\Models\\\\Role\\:\\:beforeValidate\\(\\)\\.$#" + count: 1 + path: src/Auth/Models/Role.php + + - + message: "#^Call to an undefined method \\$this\\(Winter\\\\Storm\\\\Auth\\\\Models\\\\User\\)\\:\\:getOriginalEncryptableValues\\(\\)\\.$#" + count: 1 + path: src/Auth/Models/User.php + + - + message: "#^Call to an undefined method Winter\\\\Storm\\\\Auth\\\\Models\\\\User\\:\\:afterValidate\\(\\)\\.$#" + count: 1 + path: src/Auth/Models/User.php + + - + message: "#^Call to an undefined method Winter\\\\Storm\\\\Auth\\\\Models\\\\User\\:\\:beforeValidate\\(\\)\\.$#" + count: 1 + path: src/Auth/Models/User.php + + - + message: "#^Parameter \\#1 \\$disk of static method Winter\\\\Storm\\\\Filesystem\\\\Filesystem\\:\\:isLocalDisk\\(\\) expects Illuminate\\\\Filesystem\\\\FilesystemAdapter, Illuminate\\\\Contracts\\\\Filesystem\\\\Filesystem given\\.$#" + count: 1 + path: src/Database/Attach/File.php + + - + message: "#^Access to an undefined property Winter\\\\Storm\\\\Database\\\\Model\\:\\:\\$purgeable\\.$#" + count: 4 + path: src/Database/Behaviors/Purgeable.php + + - + message: "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Model\\:\\:purgeAttributes\\(\\)\\.$#" + count: 1 + path: src/Database/Behaviors/Purgeable.php + + - + message: "#^Parameter \\#1 \\$app of class Illuminate\\\\Database\\\\DatabaseManager constructor expects Illuminate\\\\Contracts\\\\Foundation\\\\Application, Illuminate\\\\Contracts\\\\Container\\\\Container given\\.$#" + count: 1 + path: src/Database/Capsule/Manager.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\ConnectionInterface\\:\\:getName\\(\\)\\.$#" + count: 1 + path: src/Database/MemoryCache.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:\\$is_bind\\.$#" + count: 1 + path: src/Database/Model.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:\\$master_field\\.$#" + count: 1 + path: src/Database/Model.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:\\$pivot_data\\.$#" + count: 1 + path: src/Database/Model.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:\\$slave_id\\.$#" + count: 1 + path: src/Database/Model.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:\\$slave_type\\.$#" + count: 1 + path: src/Database/Model.php + + - + message: "#^Method Winter\\\\Storm\\\\Database\\\\Model\\:\\:getDeferredBindingRecords\\(\\) should return Winter\\\\Storm\\\\Database\\\\Collection but returns Illuminate\\\\Database\\\\Eloquent\\\\Collection\\\\.$#" + count: 1 + path: src/Database/Model.php + + - + message: "#^Return type \\(Winter\\\\Storm\\\\Database\\\\Pivot\\) of method Winter\\\\Storm\\\\Database\\\\Model\\:\\:newPivot\\(\\) should be compatible with return type \\(Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Pivot\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:newPivot\\(\\)$#" + count: 1 + path: src/Database/Model.php + + - + message: "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Model\\:\\:errors\\(\\)\\.$#" + count: 1 + path: src/Database/ModelException.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:deleteCancel\\(\\)\\.$#" + count: 2 + path: src/Database/Models/DeferredBinding.php + + - + message: "#^Parameter \\#1 \\$haystack of function str_contains expects string, int given\\.$#" + count: 1 + path: src/Database/MorphPivot.php + + - + message: "#^Parameter \\#1 \\$ids of method Winter\\\\Storm\\\\Database\\\\Pivot\\:\\:newQueryForRestoration\\(\\) expects array\\\\|string, int given\\.$#" + count: 1 + path: src/Database/MorphPivot.php + + - + message: "#^Parameter \\#2 \\$string of function explode expects string, int given\\.$#" + count: 1 + path: src/Database/MorphPivot.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:getLeftColumnName\\(\\)\\.$#" + count: 1 + path: src/Database/NestedTreeScope.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Query\\\\Builder\\:\\:\\$concats\\.$#" + count: 2 + path: src/Database/Query/Grammars/MySqlGrammar.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Query\\\\Builder\\:\\:\\$concats\\.$#" + count: 2 + path: src/Database/Query/Grammars/PostgresGrammar.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Query\\\\Builder\\:\\:\\$concats\\.$#" + count: 2 + path: src/Database/Query/Grammars/SQLiteGrammar.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Query\\\\Builder\\:\\:\\$concats\\.$#" + count: 2 + path: src/Database/Query/Grammars/SqlServerGrammar.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\ConnectionInterface\\:\\:getName\\(\\)\\.$#" + count: 1 + path: src/Database/QueryBuilder.php + + - + message: "#^Property Illuminate\\\\Database\\\\Query\\\\Builder\\:\\:\\$orders \\(array\\) does not accept null\\.$#" + count: 1 + path: src/Database/QueryBuilder.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" + count: 1 + path: src/Database/Relations/AttachMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:getForeignKey\\(\\)\\.$#" + count: 3 + path: src/Database/Relations/AttachMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:select\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/AttachMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/AttachMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/AttachMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/AttachMany.php + + - + message: "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Relations\\\\AttachMany\\:\\:getRelationExistenceQueryForSelfJoin\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/AttachMany.php + + - + message: "#^Method Winter\\\\Storm\\\\Database\\\\Relations\\\\AttachMany\\:\\:withDeferred\\(\\) should return Illuminate\\\\Database\\\\Query\\\\Builder but returns Illuminate\\\\Database\\\\Eloquent\\\\Builder\\.$#" + count: 1 + path: src/Database/Relations/AttachMany.php + + - + message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\AttachMany\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\AttachMany\\) given\\.$#" + count: 2 + path: src/Database/Relations/AttachMany.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" + count: 1 + path: src/Database/Relations/AttachOne.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:getForeignKey\\(\\)\\.$#" + count: 3 + path: src/Database/Relations/AttachOne.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:select\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/AttachOne.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/AttachOne.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/AttachOne.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/AttachOne.php + + - + message: "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Relations\\\\AttachOne\\:\\:getRelationExistenceQueryForSelfJoin\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/AttachOne.php + + - + message: "#^Call to private method delete\\(\\) of parent class Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\MorphOne\\\\.$#" + count: 3 + path: src/Database/Relations/AttachOne.php + + - + message: "#^Method Winter\\\\Storm\\\\Database\\\\Relations\\\\AttachOne\\:\\:withDeferred\\(\\) should return Illuminate\\\\Database\\\\Query\\\\Builder but returns Illuminate\\\\Database\\\\Eloquent\\\\Builder\\.$#" + count: 1 + path: src/Database/Relations/AttachOne.php + + - + message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\AttachOne\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\AttachOne\\) given\\.$#" + count: 2 + path: src/Database/Relations/AttachOne.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" + count: 1 + path: src/Database/Relations/BelongsTo.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:bindEventOnce\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/BelongsTo.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:getRelationDefinition\\(\\)\\.$#" + count: 3 + path: src/Database/Relations/BelongsTo.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:getForeignKey\\(\\)\\.$#" + count: 3 + path: src/Database/Relations/BelongsTo.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:select\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/BelongsTo.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/BelongsTo.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/BelongsTo.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/BelongsTo.php + + - + message: "#^Method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsTo\\:\\:withDeferred\\(\\) should return Illuminate\\\\Database\\\\Query\\\\Builder but returns Illuminate\\\\Database\\\\Eloquent\\\\Builder\\.$#" + count: 1 + path: src/Database/Relations/BelongsTo.php + + - + message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsTo\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsTo\\) given\\.$#" + count: 2 + path: src/Database/Relations/BelongsTo.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:\\$sessionKey\\.$#" + count: 1 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" + count: 1 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:bindDeferred\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:bindEventOnce\\(\\)\\.$#" + count: 2 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:fireEvent\\(\\)\\.$#" + count: 4 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:getRelationDefinition\\(\\)\\.$#" + count: 3 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:newRelationPivot\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:reloadRelations\\(\\)\\.$#" + count: 2 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:unbindDeferred\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:getForeignKey\\(\\)\\.$#" + count: 3 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:select\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Query\\\\Builder\\:\\:lists\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsToMany\\:\\:flushDuplicateCache\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsToMany\\:\\:allRelatedIds\\(\\) should return Winter\\\\Storm\\\\Support\\\\Collection but returns Illuminate\\\\Support\\\\Collection\\.$#" + count: 1 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsToMany\\:\\:withDeferred\\(\\) should return Illuminate\\\\Database\\\\Query\\\\Builder but returns Illuminate\\\\Database\\\\Eloquent\\\\Builder\\.$#" + count: 1 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsToMany\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsToMany\\) given\\.$#" + count: 2 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Parameter \\#2 \\$columns of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\:\\:paginate\\(\\) expects array, int\\|null given\\.$#" + count: 1 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Parameter \\#2 \\$currentPage \\(int\\) of method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsToMany\\:\\:paginate\\(\\) should be compatible with parameter \\$columns \\(array\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\\\:\\:paginate\\(\\)$#" + count: 1 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Parameter \\#3 \\$columns \\(array\\) of method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsToMany\\:\\:paginate\\(\\) should be compatible with parameter \\$pageName \\(string\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\\\:\\:paginate\\(\\)$#" + count: 1 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Parameter \\#3 \\$pageName of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\:\\:paginate\\(\\) expects string, array given\\.$#" + count: 1 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Parameter \\#4 \\$pageName \\(string\\) of method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsToMany\\:\\:paginate\\(\\) should be compatible with parameter \\$page \\(int\\|null\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\\\:\\:paginate\\(\\)$#" + count: 1 + path: src/Database/Relations/BelongsToMany.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" + count: 1 + path: src/Database/Relations/HasMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:getForeignKey\\(\\)\\.$#" + count: 3 + path: src/Database/Relations/HasMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:select\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/HasMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/HasMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/HasMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/HasMany.php + + - + message: "#^Call to private method update\\(\\) of parent class Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\HasMany\\\\.$#" + count: 1 + path: src/Database/Relations/HasMany.php + + - + message: "#^Call to private method whereNotIn\\(\\) of parent class Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\HasMany\\\\.$#" + count: 1 + path: src/Database/Relations/HasMany.php + + - + message: "#^If condition is always true\\.$#" + count: 1 + path: src/Database/Relations/HasMany.php + + - + message: "#^Method Winter\\\\Storm\\\\Database\\\\Relations\\\\HasMany\\:\\:withDeferred\\(\\) should return Illuminate\\\\Database\\\\Query\\\\Builder but returns Illuminate\\\\Database\\\\Eloquent\\\\Builder\\.$#" + count: 1 + path: src/Database/Relations/HasMany.php + + - + message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\HasMany\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\HasMany\\) given\\.$#" + count: 2 + path: src/Database/Relations/HasMany.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" + count: 1 + path: src/Database/Relations/HasManyThrough.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:getForeignKey\\(\\)\\.$#" + count: 3 + path: src/Database/Relations/HasManyThrough.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:select\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/HasManyThrough.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/HasManyThrough.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/HasManyThrough.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/HasManyThrough.php + + - + message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\HasManyThrough\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\HasManyThrough\\) given\\.$#" + count: 1 + path: src/Database/Relations/HasManyThrough.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" + count: 1 + path: src/Database/Relations/HasOne.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:getForeignKey\\(\\)\\.$#" + count: 3 + path: src/Database/Relations/HasOne.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:select\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/HasOne.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/HasOne.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/HasOne.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/HasOne.php + + - + message: "#^Call to private method update\\(\\) of parent class Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\HasOne\\\\.$#" + count: 2 + path: src/Database/Relations/HasOne.php + + - + message: "#^Method Winter\\\\Storm\\\\Database\\\\Relations\\\\HasOne\\:\\:withDeferred\\(\\) should return Illuminate\\\\Database\\\\Query\\\\Builder but returns Illuminate\\\\Database\\\\Eloquent\\\\Builder\\.$#" + count: 1 + path: src/Database/Relations/HasOne.php + + - + message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\HasOne\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\HasOne\\) given\\.$#" + count: 2 + path: src/Database/Relations/HasOne.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" + count: 1 + path: src/Database/Relations/HasOneThrough.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:getForeignKey\\(\\)\\.$#" + count: 3 + path: src/Database/Relations/HasOneThrough.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:select\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/HasOneThrough.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/HasOneThrough.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/HasOneThrough.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/HasOneThrough.php + + - + message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\HasOneThrough\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\HasOneThrough\\) given\\.$#" + count: 1 + path: src/Database/Relations/HasOneThrough.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" + count: 1 + path: src/Database/Relations/MorphMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:getForeignKey\\(\\)\\.$#" + count: 3 + path: src/Database/Relations/MorphMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:select\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphMany.php + + - + message: "#^Call to private method update\\(\\) of parent class Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\MorphMany\\\\.$#" + count: 1 + path: src/Database/Relations/MorphMany.php + + - + message: "#^Call to private method whereNotIn\\(\\) of parent class Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\MorphMany\\\\.$#" + count: 1 + path: src/Database/Relations/MorphMany.php + + - + message: "#^If condition is always true\\.$#" + count: 1 + path: src/Database/Relations/MorphMany.php + + - + message: "#^Method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphMany\\:\\:withDeferred\\(\\) should return Illuminate\\\\Database\\\\Query\\\\Builder but returns Illuminate\\\\Database\\\\Eloquent\\\\Builder\\.$#" + count: 1 + path: src/Database/Relations/MorphMany.php + + - + message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphMany\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphMany\\) given\\.$#" + count: 2 + path: src/Database/Relations/MorphMany.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" + count: 1 + path: src/Database/Relations/MorphOne.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:getForeignKey\\(\\)\\.$#" + count: 3 + path: src/Database/Relations/MorphOne.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:select\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphOne.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphOne.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphOne.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphOne.php + + - + message: "#^Call to private method update\\(\\) of parent class Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\MorphOne\\\\.$#" + count: 2 + path: src/Database/Relations/MorphOne.php + + - + message: "#^Method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphOne\\:\\:withDeferred\\(\\) should return Illuminate\\\\Database\\\\Query\\\\Builder but returns Illuminate\\\\Database\\\\Eloquent\\\\Builder\\.$#" + count: 1 + path: src/Database/Relations/MorphOne.php + + - + message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphOne\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphOne\\) given\\.$#" + count: 2 + path: src/Database/Relations/MorphOne.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" + count: 1 + path: src/Database/Relations/MorphTo.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:bindEventOnce\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphTo.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:getForeignKey\\(\\)\\.$#" + count: 3 + path: src/Database/Relations/MorphTo.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:select\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphTo.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphTo.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphTo.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphTo.php + + - + message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphTo\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphTo\\) given\\.$#" + count: 1 + path: src/Database/Relations/MorphTo.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" + count: 1 + path: src/Database/Relations/MorphToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:getForeignKey\\(\\)\\.$#" + count: 3 + path: src/Database/Relations/MorphToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:select\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withDefault\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withPivot\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\Relation\\:\\:withTimestamps\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Query\\\\Builder\\:\\:lists\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphToMany.php + + - + message: "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphToMany\\:\\:flushDuplicateCache\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphToMany.php + + - + message: "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphToMany\\:\\:withDeferred\\(\\)\\.$#" + count: 1 + path: src/Database/Relations/MorphToMany.php + + - + message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphToMany\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphToMany\\) given\\.$#" + count: 1 + path: src/Database/Relations/MorphToMany.php + + - + message: "#^Parameter \\#2 \\$columns of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\:\\:paginate\\(\\) expects array, int\\|null given\\.$#" + count: 1 + path: src/Database/Relations/MorphToMany.php + + - + message: "#^Parameter \\#2 \\$currentPage \\(int\\) of method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphToMany\\:\\:paginate\\(\\) should be compatible with parameter \\$columns \\(array\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\\\:\\:paginate\\(\\)$#" + count: 1 + path: src/Database/Relations/MorphToMany.php + + - + message: "#^Parameter \\#3 \\$columns \\(array\\) of method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphToMany\\:\\:paginate\\(\\) should be compatible with parameter \\$pageName \\(string\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\\\:\\:paginate\\(\\)$#" + count: 1 + path: src/Database/Relations/MorphToMany.php + + - + message: "#^Parameter \\#3 \\$pageName of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\:\\:paginate\\(\\) expects string, array given\\.$#" + count: 1 + path: src/Database/Relations/MorphToMany.php + + - + message: "#^Parameter \\#4 \\$pageName \\(string\\) of method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphToMany\\:\\:paginate\\(\\) should be compatible with parameter \\$page \\(int\\|null\\) of method Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\\\:\\:paginate\\(\\)$#" + count: 1 + path: src/Database/Relations/MorphToMany.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:getSortOrderColumn\\(\\)\\.$#" + count: 1 + path: src/Database/SortableScope.php + + - + message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:\\$children\\.$#" + count: 1 + path: src/Database/TreeCollection.php + + - + message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:getParentId\\(\\)\\.$#" + count: 1 + path: src/Database/TreeCollection.php + + - + message: "#^Call to static method reguard\\(\\) on an unknown class Winter\\\\Storm\\\\Database\\\\Eloquent\\.$#" + count: 1 + path: src/Database/Updater.php + + - + message: "#^Call to static method unguard\\(\\) on an unknown class Winter\\\\Storm\\\\Database\\\\Eloquent\\.$#" + count: 1 + path: src/Database/Updater.php + + - + message: "#^Winter\\\\Storm\\\\Extension\\\\Extendable\\:\\:extendableCall\\(\\) calls parent\\:\\:__call\\(\\) but Winter\\\\Storm\\\\Extension\\\\Extendable does not extend any class\\.$#" + count: 1 + path: src/Extension/Extendable.php + + - + message: "#^Winter\\\\Storm\\\\Extension\\\\Extendable\\:\\:extendableGet\\(\\) calls parent\\:\\:__get\\(\\) but Winter\\\\Storm\\\\Extension\\\\Extendable does not extend any class\\.$#" + count: 1 + path: src/Extension/Extendable.php + + - + message: "#^Winter\\\\Storm\\\\Extension\\\\Extendable\\:\\:extendableSet\\(\\) calls parent\\:\\:__set\\(\\) but Winter\\\\Storm\\\\Extension\\\\Extendable does not extend any class\\.$#" + count: 1 + path: src/Extension/Extendable.php + + - + message: "#^Call to an undefined method Illuminate\\\\Contracts\\\\Foundation\\\\Application\\:\\:getCachedClassesPath\\(\\)\\.$#" + count: 1 + path: src/Foundation/Console/ClearCompiledCommand.php + + - + message: "#^Parameter \\#2 \\$data \\(array\\) of method Winter\\\\Storm\\\\Mail\\\\Mailer\\:\\:queue\\(\\) should be compatible with parameter \\$queue \\(string\\|null\\) of method Illuminate\\\\Contracts\\\\Mail\\\\MailQueue\\:\\:queue\\(\\)$#" + count: 1 + path: src/Mail/Mailer.php + + - + message: "#^Parameter \\#2 \\$data \\(array\\) of method Winter\\\\Storm\\\\Mail\\\\Mailer\\:\\:queue\\(\\) should be compatible with parameter \\$queue \\(string\\|null\\) of method Illuminate\\\\Mail\\\\Mailer\\:\\:queue\\(\\)$#" + count: 1 + path: src/Mail/Mailer.php + + - + message: "#^Parameter \\#2 \\$view \\(array\\|string\\) of method Winter\\\\Storm\\\\Mail\\\\Mailer\\:\\:queueOn\\(\\) should be compatible with parameter \\$view \\(Illuminate\\\\Contracts\\\\Mail\\\\Mailable\\) of method Illuminate\\\\Mail\\\\Mailer\\:\\:queueOn\\(\\)$#" + count: 1 + path: src/Mail/Mailer.php + + - + message: "#^Parameter \\#3 \\$data \\(array\\) of method Winter\\\\Storm\\\\Mail\\\\Mailer\\:\\:later\\(\\) should be compatible with parameter \\$queue \\(string\\|null\\) of method Illuminate\\\\Contracts\\\\Mail\\\\MailQueue\\:\\:later\\(\\)$#" + count: 1 + path: src/Mail/Mailer.php + + - + message: "#^Parameter \\#3 \\$data \\(array\\) of method Winter\\\\Storm\\\\Mail\\\\Mailer\\:\\:later\\(\\) should be compatible with parameter \\$queue \\(string\\|null\\) of method Illuminate\\\\Mail\\\\Mailer\\:\\:later\\(\\)$#" + count: 1 + path: src/Mail/Mailer.php + + - + message: "#^Parameter \\#3 \\$view \\(array\\|string\\) of method Winter\\\\Storm\\\\Mail\\\\Mailer\\:\\:laterOn\\(\\) should be compatible with parameter \\$view \\(Illuminate\\\\Contracts\\\\Mail\\\\Mailable\\) of method Illuminate\\\\Mail\\\\Mailer\\:\\:laterOn\\(\\)$#" + count: 1 + path: src/Mail/Mailer.php + + - + message: "#^Parameter \\#2 \\$data \\(array\\) of method Winter\\\\Storm\\\\Support\\\\Testing\\\\Fakes\\\\MailFake\\:\\:queue\\(\\) should be compatible with parameter \\$queue \\(string\\|null\\) of method Illuminate\\\\Contracts\\\\Mail\\\\MailQueue\\:\\:queue\\(\\)$#" + count: 1 + path: src/Support/Testing/Fakes/MailFake.php + + - + message: "#^Parameter \\#2 \\$data \\(array\\) of method Winter\\\\Storm\\\\Support\\\\Testing\\\\Fakes\\\\MailFake\\:\\:queue\\(\\) should be compatible with parameter \\$queue \\(string\\|null\\) of method Illuminate\\\\Support\\\\Testing\\\\Fakes\\\\MailFake\\:\\:queue\\(\\)$#" + count: 1 + path: src/Support/Testing/Fakes/MailFake.php + + - + message: "#^Return type \\(void\\) of method Winter\\\\Storm\\\\Support\\\\Testing\\\\Fakes\\\\MailFake\\:\\:send\\(\\) should be compatible with return type \\(Illuminate\\\\Mail\\\\SentMessage\\|null\\) of method Illuminate\\\\Contracts\\\\Mail\\\\Mailer\\:\\:send\\(\\)$#" + count: 1 + path: src/Support/Testing/Fakes/MailFake.php + + - + message: "#^Call to function is_null\\(\\) with Closure will always evaluate to false\\.$#" + count: 1 + path: src/Validation/Factory.php diff --git a/phpstan.neon b/phpstan.neon index 917cd7d0..d36bb09c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,6 @@ includes: - ./vendor/nunomaduro/larastan/extension.neon + - phpstan-baseline.neon parameters: paths: From 8ac383fb2950d219445b4f87ae46289905a3f47b Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 1 Jul 2022 11:52:56 +0800 Subject: [PATCH 091/108] Allow pivot model to be provided to MorphTo relations --- src/Database/Concerns/HasRelationships.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Database/Concerns/HasRelationships.php b/src/Database/Concerns/HasRelationships.php index 4ca042cf..ecf581b9 100644 --- a/src/Database/Concerns/HasRelationships.php +++ b/src/Database/Concerns/HasRelationships.php @@ -342,6 +342,11 @@ protected function handleRelation($relationName) case 'morphToMany': $relation = $this->validateRelationArgs($relationName, ['table', 'key', 'otherKey', 'parentKey', 'relatedKey', 'pivot', 'timestamps'], ['name']); $relationObj = $this->$relationType($relation[0], $relation['name'], $relation['table'], $relation['key'], $relation['otherKey'], $relation['parentKey'], $relation['relatedKey'], false, $relationName); + + if (isset($relation['pivotModel'])) { + $relationObj->using($relation['pivotModel']); + } + break; case 'morphedByMany': From af294928ce97f55cfad3e55d1b9d199edecc32d7 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 1 Jul 2022 11:53:14 +0800 Subject: [PATCH 092/108] Fix test, allow namespaced files in tests folder --- composer.json | 3 ++- tests/Database/Fixtures/CustomMorphPivot.php | 9 +++++++++ tests/Database/MorphPivotTest.php | 7 ++----- 3 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 tests/Database/Fixtures/CustomMorphPivot.php diff --git a/composer.json b/composer.json index 9415d03b..db17d4e9 100644 --- a/composer.json +++ b/composer.json @@ -77,7 +77,8 @@ "src/Html/helpers.php" ], "psr-4": { - "Winter\\Storm\\": "src/" + "Winter\\Storm\\": "src/", + "Winter\\Storm\\Tests\\": "tests/" } }, "autoload-dev": { diff --git a/tests/Database/Fixtures/CustomMorphPivot.php b/tests/Database/Fixtures/CustomMorphPivot.php new file mode 100644 index 00000000..a3ff91ca --- /dev/null +++ b/tests/Database/Fixtures/CustomMorphPivot.php @@ -0,0 +1,9 @@ + 'taggings', 'name' => 'taggable', 'pivot' => ['hidden'], - 'pivotModel' => CustomPivot::class, + 'pivotModel' => CustomMorphPivot::class, ], ]; } @@ -141,7 +142,3 @@ class Tag extends Model 'name', ]; } - -class CustomMorphPivot extends MorphPivot -{ -} From ee73adfefb79b066ccfe4691496e891fdc52df97 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 1 Jul 2022 11:53:53 +0800 Subject: [PATCH 093/108] Move tests namespace into autoload-dev --- composer.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index db17d4e9..163a5a27 100644 --- a/composer.json +++ b/composer.json @@ -77,15 +77,17 @@ "src/Html/helpers.php" ], "psr-4": { - "Winter\\Storm\\": "src/", - "Winter\\Storm\\Tests\\": "tests/" + "Winter\\Storm\\": "src/" } }, "autoload-dev": { "classmap": [ "tests/TestCase.php", "tests/DbTestCase.php" - ] + ], + "psr-4": { + "Winter\\Storm\\Tests\\": "tests/" + } }, "scripts": { "test": [ From 76835e631c5fe52f94090ca010c0ea712a4fa7d6 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 1 Jul 2022 12:01:13 +0800 Subject: [PATCH 094/108] Add automated PHPStan test, tweak PHPUnit test --- .github/workflows/code-analysis.yaml | 63 ++++++++++++++++++++++++++++ .github/workflows/tests.yml | 6 ++- 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/code-analysis.yaml diff --git a/.github/workflows/code-analysis.yaml b/.github/workflows/code-analysis.yaml new file mode 100644 index 00000000..1a5a0cf8 --- /dev/null +++ b/.github/workflows/code-analysis.yaml @@ -0,0 +1,63 @@ +name: Tests + +on: + push: + branches: + - "wip/1.2" + pull_request: + branches: + - "wip/1.2" + +jobs: + codeAnalysis: + runs-on: ubuntu-latest + name: Code Analysis + env: + extensions: curl, fileinfo, gd, mbstring, openssl, pdo, pdo_sqlite, sqlite3, xml, zip + key: winter-storm-cache-v1.2 + steps: + - name: Cancel previous incomplete runs + uses: styfle/cancel-workflow-action@0.8.0 + with: + access_token: ${{ github.token }} + + - name: Checkout changes + uses: actions/checkout@v2 + + - name: Setup extension cache + id: extcache + uses: shivammathur/cache-extensions@v1 + with: + php-version: ${{ matrix.phpVersion }} + extensions: ${{ env.extensions }} + key: ${{ env.key }} + + - name: Cache extensions + uses: actions/cache@v2 + with: + path: ${{ steps.extcache.outputs.dir }} + key: ${{ steps.extcache.outputs.key }} + restore-keys: ${{ steps.extcache.outputs.key }} + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.phpVersion }} + extensions: ${{ env.extensions }} + + - name: Setup dependency cache + id: composercache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composercache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install Composer dependencies + run: composer install --no-interaction --no-progress --no-scripts + + - name: Analyse code + run: ./vendor/bin/phpstan analyse diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 72810423..59d2e7f9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,6 +22,11 @@ jobs: extensions: curl, fileinfo, gd, mbstring, openssl, pdo, pdo_sqlite, sqlite3, xml, zip key: winter-storm-cache-v1.2 steps: + - name: Cancel previous incomplete runs + uses: styfle/cancel-workflow-action@0.8.0 + with: + access_token: ${{ github.token }} + - name: Checkout changes uses: actions/checkout@v2 @@ -44,7 +49,6 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.phpVersion }} - tools: composer:v2 extensions: ${{ env.extensions }} - name: Setup dependency cache From 9f57231b1fe842c41c994fec8ca3279c2e2395af Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 1 Jul 2022 12:02:51 +0800 Subject: [PATCH 095/108] Replace missing matrix vars --- .github/workflows/code-analysis.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code-analysis.yaml b/.github/workflows/code-analysis.yaml index 1a5a0cf8..04c516d8 100644 --- a/.github/workflows/code-analysis.yaml +++ b/.github/workflows/code-analysis.yaml @@ -28,7 +28,7 @@ jobs: id: extcache uses: shivammathur/cache-extensions@v1 with: - php-version: ${{ matrix.phpVersion }} + php-version: '8.0' extensions: ${{ env.extensions }} key: ${{ env.key }} @@ -42,7 +42,7 @@ jobs: - name: Install PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ matrix.phpVersion }} + php-version: '8.0' extensions: ${{ env.extensions }} - name: Setup dependency cache From 6273bc604af5459e9150a336aec3d8567fdd40ae Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 1 Jul 2022 13:49:43 +0800 Subject: [PATCH 096/108] Fix Markdown parser class name storage and use --- src/Parse/Markdown.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Parse/Markdown.php b/src/Parse/Markdown.php index f621a6ac..5146257d 100644 --- a/src/Parse/Markdown.php +++ b/src/Parse/Markdown.php @@ -31,9 +31,9 @@ class Markdown use \Winter\Storm\Support\Traits\Emitter; /** - * @var \Winter\Storm\Parse\Parsedown\Parsedown Parsedown instance + * @var string Parsedown class */ - protected $parserClass; + protected $parserClass = \Winter\Storm\Parse\Parsedown\Parsedown::class; /** * Gets an instance of the parser. @@ -50,12 +50,16 @@ protected function getParser() /** * Sets the Markdown parser. * - * @param Parsedown $parser + * @param string|object $parserClass * @return void */ - public function setParser(Parsedown $parser) + public function setParser(string|object $parserClass) { - $this->parserClass = $parser; + if (is_object($parserClass)) { + $this->parserClass = get_class($parserClass); + } else { + $this->parserClass = $parserClass; + } } /** From c096e92377f76cbab09afc3c8bf79694c9247505 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 1 Jul 2022 13:50:27 +0800 Subject: [PATCH 097/108] Remove use of Eloquent facade in down migrations --- src/Database/Updater.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Database/Updater.php b/src/Database/Updater.php index da525740..da497fd6 100644 --- a/src/Database/Updater.php +++ b/src/Database/Updater.php @@ -53,13 +53,13 @@ public function packDown($file) $this->isValidScript($object, $file); - Eloquent::unguard(); + Model::unguard(); if ($object instanceof Updates\Migration && method_exists($object, 'down')) { $object->down(); } - Eloquent::reguard(); + Model::reguard(); return true; } From 696d4a76d15e61c3fe094128f672a70e49d97094 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 1 Jul 2022 14:24:54 +0800 Subject: [PATCH 098/108] Convert all type hints to PHPDocs to maintain BC --- src/Database/Attach/File.php | 295 ++++++++++++++++++++++++----------- 1 file changed, 207 insertions(+), 88 deletions(-) diff --git a/src/Database/Attach/File.php b/src/Database/Attach/File.php index c2443c24..7419afd8 100644 --- a/src/Database/Attach/File.php +++ b/src/Database/Attach/File.php @@ -99,8 +99,11 @@ class File extends Model /** * Creates a file object from a file an uploaded file. + * + * @param UploadedFile $uploadedFile The uploaded file. + * @return static */ - public function fromPost(UploadedFile $uploadedFile): static + public function fromPost($uploadedFile) { $this->file_name = $uploadedFile->getClientOriginalName(); $this->file_size = $uploadedFile->getSize(); @@ -121,8 +124,11 @@ public function fromPost(UploadedFile $uploadedFile): static /** * Creates a file object from a file on the disk. + * + * @param string $filePath The path to the file. + * @return static */ - public function fromFile(string $filePath): static + public function fromFile($filePath) { $file = new FileObj($filePath); $this->file_name = $file->getFilename(); @@ -137,8 +143,12 @@ public function fromFile(string $filePath): static /** * Creates a file object from raw data. + * + * @param string $data The raw data. + * @param string $filename The name of the file. + * @return static */ - public function fromData(string $data, string $filename): static + public function fromData($data, $filename) { $tempPath = temp_path($filename); FileHelper::put($tempPath, $data); @@ -151,8 +161,12 @@ public function fromData(string $data, string $filename): static /** * Creates a file object from url + * + * @param string $url The URL to retrieve and store. + * @param string|null $filename The name of the file. If null, the filename will be extracted from the URL. + * @return static */ - public function fromUrl(string $url, ?string $filename = null): static + public function fromUrl($url, $filename = null) { $data = Http::get($url); @@ -189,24 +203,31 @@ public function fromUrl(string $url, ?string $filename = null): static /** * Helper attribute for getPath. + * + * @return string */ - public function getPathAttribute(): string + public function getPathAttribute() { return $this->getPath(); } /** * Helper attribute for getExtension. + * + * @return string */ - public function getExtensionAttribute(): string + public function getExtensionAttribute() { return $this->getExtension(); } /** * Used only when filling attributes. + * + * @param mixed $value + * @return void */ - public function setDataAttribute($value): void + public function setDataAttribute($value) { $this->data = $value; } @@ -215,8 +236,10 @@ public function setDataAttribute($value): void * Helper attribute for get image width. * * Returns `null` if this file is not an image. + * + * @return string|int|null */ - public function getWidthAttribute(): string|int|null + public function getWidthAttribute() { if ($this->isImage()) { $dimensions = $this->getImageDimensions(); @@ -231,8 +254,10 @@ public function getWidthAttribute(): string|int|null * Helper attribute for get image height. * * Returns `null` if this file is not an image. + * + * @return string|int|null */ - public function getHeightAttribute(): string|int|null + public function getHeightAttribute() { if ($this->isImage()) { $dimensions = $this->getImageDimensions(); @@ -245,8 +270,10 @@ public function getHeightAttribute(): string|int|null /** * Helper attribute for file size in human format. + * + * @return string */ - public function getSizeAttribute(): string + public function getSizeAttribute() { return $this->sizeToString(); } @@ -263,7 +290,7 @@ public function getSizeAttribute(): string * browser * @return \Illuminate\Http\Response|void */ - public function output(string $disposition = 'inline', bool $returnResponse = false) + public function output($disposition = 'inline', $returnResponse = false) { $response = response($this->getContents())->withHeaders([ 'Content-type' => $this->getContentType(), @@ -299,7 +326,7 @@ public function output(string $disposition = 'inline', bool $returnResponse = fa * browser * @return \Illuminate\Http\Response|void */ - public function outputThumb(int $width, int $height, array $options = [], bool $returnResponse = false) + public function outputThumb($width, $height, $options = [], $returnResponse = false) { $disposition = array_get($options, 'disposition', 'inline'); $options = $this->getDefaultThumbOptions($options); @@ -329,8 +356,11 @@ public function outputThumb(int $width, int $height, array $options = [], bool $ /** * Returns the cache key used for the hasFile method + * + * @param string|null $path The path to get the cache key for + * @return string */ - public function getCacheKey(?string $path = null): string + public function getCacheKey($path = null) { if (empty($path)) { $path = $this->getDiskPath(); @@ -341,24 +371,31 @@ public function getCacheKey(?string $path = null): string /** * Returns the file name without path + * + * @return string */ - public function getFilename(): string + public function getFilename() { return $this->file_name; } /** * Returns the file extension. + * + * @return string */ - public function getExtension(): string + public function getExtension() { return FileHelper::extension($this->file_name); } /** * Returns the last modification date as a UNIX timestamp. + * + * @param string|null $fileName + * @return int */ - public function getLastModified(?string $fileName = null): int + public function getLastModified($fileName = null) { return $this->storageCmd('lastModified', $this->getDiskPath($fileName)); } @@ -367,8 +404,10 @@ public function getLastModified(?string $fileName = null): int * Returns the file content type. * * Returns `null` if the file content type cannot be determined. + * + * @return string|null */ - public function getContentType(): ?string + public function getContentType() { if ($this->content_type !== null) { return $this->content_type; @@ -384,16 +423,22 @@ public function getContentType(): ?string /** * Get file contents from storage device. + * + * @param string|null $fileName + * @return string */ - public function getContents(?string $fileName = null): string + public function getContents($fileName = null) { return $this->storageCmd('get', $this->getDiskPath($fileName)); } /** * Returns the public address to access the file. + * + * @param string|null $fileName + * @return string */ - public function getPath(?string $fileName = null): string + public function getPath($fileName = null) { if (empty($fileName)) { $fileName = $this->disk_name; @@ -404,8 +449,10 @@ public function getPath(?string $fileName = null): string /** * Returns a local path to this file. If the file is stored remotely, * it will be downloaded to a temporary directory. + * + * @return string */ - public function getLocalPath(): string + public function getLocalPath() { if ($this->isLocalStorage()) { return $this->getLocalRootPath() . '/' . $this->getDiskPath(); @@ -424,9 +471,11 @@ public function getLocalPath(): string /** * Returns the path to the file, relative to the storage disk. + * + * @param string|null $fileName * @return string */ - public function getDiskPath(?string $fileName = null): string + public function getDiskPath($fileName = null) { if (empty($fileName)) { $fileName = $this->disk_name; @@ -436,8 +485,10 @@ public function getDiskPath(?string $fileName = null): string /** * Determines if the file is flagged "public" or not. + * + * @return bool */ - public function isPublic(): bool + public function isPublic() { if (array_key_exists('is_public', $this->attributes)) { return (bool) $this->attributes['is_public']; @@ -452,8 +503,10 @@ public function isPublic(): bool /** * Returns the file size as string. + * + * @return string */ - public function sizeToString(): string + public function sizeToString() { return FileHelper::sizeToString($this->file_size); } @@ -465,6 +518,8 @@ public function sizeToString(): string /** * Before the model is saved * - check if new file data has been supplied, eg: $model->data = Input::file('something'); + * + * @return void */ public function beforeSave() { @@ -474,8 +529,7 @@ public function beforeSave() if ($this->data !== null) { if ($this->data instanceof UploadedFile) { $this->fromPost($this->data); - } - else { + } else { $this->fromFile($this->data); } @@ -486,6 +540,8 @@ public function beforeSave() /** * After model is deleted * - clean up it's thumbnails + * + * @return void */ public function afterDelete() { @@ -503,16 +559,20 @@ public function afterDelete() /** * Checks if the file extension is an image and returns true or false. + * + * @return bool */ - public function isImage(): bool + public function isImage() { return in_array(strtolower($this->getExtension()), static::$imageExtensions); } /** * Get image dimensions + * + * @return array|false */ - protected function getImageDimensions(): array|false + protected function getImageDimensions() { return getimagesize($this->getLocalPath()); } @@ -532,7 +592,7 @@ protected function getImageDimensions(): array|false * ] * @return string The URL to the generated thumbnail */ - public function getThumb(int $width, int $height, array $options = []): string + public function getThumb($width, $height, $options = []) { if (!$this->isImage()) { return $this->getPath(); @@ -550,8 +610,7 @@ public function getThumb(int $width, int $height, array $options = []): string if (!$this->hasFile($thumbFile)) { if ($this->isLocalStorage()) { $this->makeThumbLocal($thumbFile, $thumbPath, $width, $height, $options); - } - else { + } else { $this->makeThumbStorage($thumbFile, $thumbPath, $width, $height, $options); } } @@ -561,8 +620,20 @@ public function getThumb(int $width, int $height, array $options = []): string /** * Generates a thumbnail filename. + * + * @param integer $width + * @param integer $height + * @param array $options [ + * 'mode' => 'auto', + * 'offset' => [0, 0], + * 'quality' => 90, + * 'sharpen' => 0, + * 'interlace' => false, + * 'extension' => 'auto', + * ] + * @return string The filename of the thumbnail */ - public function getThumbFilename(int $width, int $height, array $options): string + public function getThumbFilename($width, $height, $options = []): string { $options = $this->getDefaultThumbOptions($options); return implode('_', [ @@ -578,8 +649,11 @@ public function getThumbFilename(int $width, int $height, array $options): strin /** * Returns the default thumbnail options. + * + * @param array $overrideOptions Overridden options + * @return array */ - protected function getDefaultThumbOptions(array $overrideOptions = []): array + protected function getDefaultThumbOptions($overrideOptions = []) { $defaultOptions = [ 'mode' => 'auto', @@ -610,14 +684,16 @@ protected function getDefaultThumbOptions(array $overrideOptions = []): array * * This step is necessary to simplify things and ensure the correct file permissions are given * to the local files. + * + * @param string $thumbFile + * @param string $thumbPath + * @param int $width + * @param int $height + * @param array $options + * @return void */ - protected function makeThumbLocal( - string $thumbFile, - string $thumbPath, - int $width, - int $height, - array $options - ): void { + protected function makeThumbLocal($thumbFile, $thumbPath, $width, $height, $options) + { $rootPath = $this->getLocalRootPath(); $filePath = $rootPath.'/'.$this->getDiskPath(); $thumbPath = $rootPath.'/'.$thumbPath; @@ -627,18 +703,16 @@ protected function makeThumbLocal( */ if (!$this->hasFile($this->disk_name)) { BrokenImage::copyTo($thumbPath); - } - /* - * Generate thumbnail - */ - else { + } else { + /* + * Generate thumbnail + */ try { Resizer::open($filePath) ->resize($width, $height, $options) ->save($thumbPath) ; - } - catch (Exception $ex) { + } catch (Exception $ex) { Log::error($ex); BrokenImage::copyTo($thumbPath); } @@ -649,14 +723,16 @@ protected function makeThumbLocal( /** * Generate the thumbnail based on a remote storage engine. + * + * @param string $thumbFile + * @param string $thumbPath + * @param int $width + * @param int $height + * @param array $options + * @return void */ - protected function makeThumbStorage( - string $thumbFile, - string $thumbPath, - int $width, - int $height, - array $options - ): void { + protected function makeThumbStorage($thumbFile, $thumbPath, $width, $height, $options) + { $tempFile = $this->getLocalTempPath(); $tempThumb = $this->getLocalTempPath($thumbFile); @@ -665,11 +741,10 @@ protected function makeThumbStorage( */ if (!$this->hasFile($this->disk_name)) { BrokenImage::copyTo($tempThumb); - } - /* - * Generate thumbnail - */ - else { + } else { + /* + * Generate thumbnail + */ $this->copyStorageToLocal($this->getDiskPath(), $tempFile); try { @@ -677,8 +752,7 @@ protected function makeThumbStorage( ->resize($width, $height, $options) ->save($tempThumb) ; - } - catch (Exception $ex) { + } catch (Exception $ex) { Log::error($ex); BrokenImage::copyTo($tempThumb); } @@ -693,10 +767,12 @@ protected function makeThumbStorage( FileHelper::delete($tempThumb); } - /* + /** * Delete all thumbnails for this file. + * + * @return void */ - public function deleteThumbs(): void + public function deleteThumbs() { $pattern = 'thumb_'.$this->id.'_'; @@ -715,8 +791,7 @@ public function deleteThumbs(): void if (!empty($collection)) { if ($this->isLocalStorage()) { FileHelper::delete($collection); - } - else { + } else { $this->getDisk()->delete($collection); } } @@ -728,8 +803,10 @@ public function deleteThumbs(): void /** * Generates a disk name from the supplied file name. + * + * @return string */ - protected function getDiskName(): string + protected function getDiskName() { if ($this->disk_name !== null) { return $this->disk_name; @@ -749,8 +826,11 @@ protected function getDiskName(): string /** * Returns a temporary local path to work from. + * + * @param string|null $path Optional path to append to the temp path + * @return string */ - protected function getLocalTempPath(?string $path = null): string + protected function getLocalTempPath($path = null) { if (!$path) { return $this->getTempPath() . '/' . md5($this->getDiskPath()) . '.' . $this->getExtension(); @@ -761,10 +841,12 @@ protected function getLocalTempPath(?string $path = null): string /** * Saves a file + * * @param string $sourcePath An absolute local path to a file name to read from. - * @param string $destinationFileName A storage file name to save to. + * @param string|null $destinationFileName A storage file name to save to. + * @return bool */ - protected function putFile(string $sourcePath, ?string $destinationFileName = null): bool + protected function putFile($sourcePath, $destinationFileName = null) { if (!$destinationFileName) { $destinationFileName = $this->disk_name; @@ -799,8 +881,11 @@ protected function putFile(string $sourcePath, ?string $destinationFileName = nu /** * Delete file contents from storage device. + * + * @param string|null $fileName + * @return void */ - protected function deleteFile(?string $fileName = null): void + protected function deleteFile($fileName = null) { if (!$fileName) { $fileName = $this->disk_name; @@ -819,8 +904,11 @@ protected function deleteFile(?string $fileName = null): void /** * Check file exists on storage device. + * + * @param string|null $fileName + * @return bool */ - protected function hasFile(?string $fileName = null): bool + protected function hasFile($fileName = null) { $filePath = $this->getDiskPath($fileName); @@ -838,8 +926,11 @@ protected function hasFile(?string $fileName = null): bool /** * Checks if directory is empty then deletes it, three levels up to match the partition directory. + * + * @param string|null $dir Directory to check and delete if empty. + * @return void */ - protected function deleteEmptyDirectory(?string $dir = null): void + protected function deleteEmptyDirectory($dir = null) { if (!$this->isDirectoryEmpty($dir)) { return; @@ -864,8 +955,11 @@ protected function deleteEmptyDirectory(?string $dir = null): void /** * Returns true if a directory contains no files. + * + * @param string|null $dir Directory to check. + * @return bool */ - protected function isDirectoryEmpty(?string $dir = null): bool + protected function isDirectoryEmpty($dir = null) { return count($this->storageCmd('allFiles', $dir)) === 0; } @@ -879,6 +973,8 @@ protected function isDirectoryEmpty(?string $dir = null): bool * * This allows local storage outside the storage/app folder and is also good for performance. For local storage, * *every* argument is prefixed with the local root path. Props to Laravel for the unified interface. + * + * @return mixed */ protected function storageCmd() { @@ -894,8 +990,7 @@ protected function storageCmd() }, $args); $result = forward_static_call_array([$interface, $command], $args); - } - else { + } else { $result = call_user_func_array([$this->getDisk(), $command], $args); } @@ -904,16 +999,24 @@ protected function storageCmd() /** * Copy the Storage to local file + * + * @param string $storagePath + * @param string $localPath + * @return int The filesize of the copied file. */ - protected function copyStorageToLocal(string $storagePath, string $localPath): int + protected function copyStorageToLocal($storagePath, $localPath) { return FileHelper::put($localPath, $this->getDisk()->get($storagePath)); } /** * Copy the local file to Storage + * + * @param string $storagePath + * @param string $localPath + * @return string|bool */ - protected function copyLocalToStorage(string $localPath, string $storagePath): string|bool + protected function copyLocalToStorage($localPath, $storagePath) { return $this->getDisk()->put($storagePath, FileHelper::get($localPath), $this->isPublic() ? 'public' : null); } @@ -924,16 +1027,20 @@ protected function copyLocalToStorage(string $localPath, string $storagePath): s /** * Returns the maximum size of an uploaded file as configured in php.ini in kilobytes (rounded) + * + * @return float */ - public static function getMaxFilesize(): float + public static function getMaxFilesize() { return round(UploadedFile::getMaxFilesize() / 1024); } /** * Define the internal storage path, override this method to define. + * + * @return string */ - public function getStorageDirectory(): string + public function getStorageDirectory() { if ($this->isPublic()) { return 'uploads/public/'; @@ -944,8 +1051,10 @@ public function getStorageDirectory(): string /** * Define the public address for the storage path. + * + * @return string */ - public function getPublicPath(): string + public function getPublicPath() { if ($this->isPublic()) { return 'http://localhost/uploads/public/'; @@ -956,8 +1065,10 @@ public function getPublicPath(): string /** * Define the internal working path, override this method to define. + * + * @return string */ - public function getTempPath(): string + public function getTempPath() { $path = temp_path() . '/uploads'; @@ -970,34 +1081,42 @@ public function getTempPath(): string /** * Returns the storage disk the file is stored on + * + * @return Filesystem */ - public function getDisk(): Filesystem + public function getDisk() { return Storage::disk(); } /** * Returns true if the storage engine is local. + * + * @return bool */ - protected function isLocalStorage(): bool + protected function isLocalStorage() { return FileHelper::isLocalDisk($this->getDisk()); } /** - * Generates a partition for the file. - - * For example, returns `/ABC/DE1/234` for an name of `ABCDE1234`. - */ - protected function getPartitionDirectory(): string + * Generates a partition for the file. + * + * For example, returns `/ABC/DE1/234` for an name of `ABCDE1234`. + * + * @return string + */ + protected function getPartitionDirectory() { return implode('/', array_slice(str_split($this->disk_name, 3), 0, 3)) . '/'; } /** * If working with local storage, determine the absolute local path. + * + * @return string */ - protected function getLocalRootPath(): string + protected function getLocalRootPath() { return storage_path() . '/app'; } From 45765b5b6e9c0479131ffa988c155a7e06658bef Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 1 Jul 2022 14:25:30 +0800 Subject: [PATCH 099/108] Small tweak --- src/Database/Attach/File.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Database/Attach/File.php b/src/Database/Attach/File.php index 7419afd8..f3480d97 100644 --- a/src/Database/Attach/File.php +++ b/src/Database/Attach/File.php @@ -548,8 +548,7 @@ public function afterDelete() try { $this->deleteThumbs(); $this->deleteFile(); - } - catch (Exception $ex) { + } catch (Exception $ex) { } } From 67a3b643ee0fbd1e4c1d7b3e8dafab7a72d66ff6 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 1 Jul 2022 14:26:23 +0800 Subject: [PATCH 100/108] Ensure morph to supports deferred records and constraints --- .../Relations/Concerns/BelongsOrMorphsToMany.php | 2 +- src/Database/Relations/Concerns/DeferOneOrMany.php | 14 ++++++-------- .../Relations/Concerns/DefinedConstraints.php | 10 +++++----- src/Database/Relations/MorphTo.php | 1 + src/Database/Relations/MorphToMany.php | 1 + 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Database/Relations/Concerns/BelongsOrMorphsToMany.php b/src/Database/Relations/Concerns/BelongsOrMorphsToMany.php index 6506f1ff..7e346d3d 100644 --- a/src/Database/Relations/Concerns/BelongsOrMorphsToMany.php +++ b/src/Database/Relations/Concerns/BelongsOrMorphsToMany.php @@ -378,7 +378,7 @@ public function getSimpleValue() * Get all of the IDs for the related models, with deferred binding support * * @param string $sessionKey - * @return \Winter\Storm\Support\Collection + * @return \Illuminate\Support\Collection */ public function allRelatedIds($sessionKey = null) { diff --git a/src/Database/Relations/Concerns/DeferOneOrMany.php b/src/Database/Relations/Concerns/DeferOneOrMany.php index 60586746..5f5979fb 100644 --- a/src/Database/Relations/Concerns/DeferOneOrMany.php +++ b/src/Database/Relations/Concerns/DeferOneOrMany.php @@ -1,14 +1,14 @@ orphanMode = true; } @@ -42,10 +42,9 @@ public function withDeferred($sessionKey) ->where($this->getForeignKey(), $this->parent->getKey()) ->where($this->getMorphType(), $this->getMorphClass()); }); - } - elseif ($this instanceof BelongsToManyBase) { + } elseif ($this instanceof BelongsToMany) { /* - * Custom query for BelongsToManyBase since a "join" cannot be used + * Custom query for BelongsToMany since a "join" cannot be used */ $query->whereExists(function ($query) { $query @@ -56,8 +55,7 @@ public function withDeferred($sessionKey) )) ->where($this->getForeignKey(), $this->parent->getKey()); }); - } - else { + } else { /* * Trick the relation to add constraints to this nested query */ diff --git a/src/Database/Relations/Concerns/DefinedConstraints.php b/src/Database/Relations/Concerns/DefinedConstraints.php index 93271128..3df5fd83 100644 --- a/src/Database/Relations/Concerns/DefinedConstraints.php +++ b/src/Database/Relations/Concerns/DefinedConstraints.php @@ -26,9 +26,9 @@ public function addDefinedConstraints() * Add relation based constraints. * * @param \Illuminate\Database\Eloquent\Relations\Relation $relation - * @param array $args + * @param array|null $args */ - public function addDefinedConstraintsToRelation($relation, $args = null) + public function addDefinedConstraintsToRelation($relation, ?array $args = null) { if ($args === null) { $args = $this->parent->getRelationDefinition($this->relationName); @@ -76,10 +76,10 @@ public function addDefinedConstraintsToRelation($relation, $args = null) /** * Add query based constraints. * - * @param \Winter\Storm\Database\QueryBuilder $query - * @param array $args + * @param \Illuminate\Database\Eloquent\Relations\Relation|\Winter\Storm\Database\QueryBuilder $query + * @param array|null $args */ - public function addDefinedConstraintsToQuery($query, $args = null) + public function addDefinedConstraintsToQuery($query, ?array $args = null) { if ($args === null) { $args = $this->parent->getRelationDefinition($this->relationName); diff --git a/src/Database/Relations/MorphTo.php b/src/Database/Relations/MorphTo.php index 53c1392b..661cdc33 100644 --- a/src/Database/Relations/MorphTo.php +++ b/src/Database/Relations/MorphTo.php @@ -9,6 +9,7 @@ */ class MorphTo extends MorphToBase { + use Concerns\DeferOneOrMany; use Concerns\DefinedConstraints; /** diff --git a/src/Database/Relations/MorphToMany.php b/src/Database/Relations/MorphToMany.php index 53189655..e8eb53b9 100644 --- a/src/Database/Relations/MorphToMany.php +++ b/src/Database/Relations/MorphToMany.php @@ -17,6 +17,7 @@ class MorphToMany extends BaseMorphToMany { use Concerns\BelongsOrMorphsToMany; + use Concerns\DeferOneOrMany; use Concerns\DefinedConstraints; /** From 8951f40e81bda721b769bdddfdf59bdc3171cfdb Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 1 Jul 2022 14:26:48 +0800 Subject: [PATCH 101/108] Remove fixed errors from baseline --- phpstan-baseline.neon | 120 ------------------------------------------ 1 file changed, 120 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 03b31dce..fd99a24a 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -210,16 +210,6 @@ parameters: count: 1 path: src/Database/Relations/AttachMany.php - - - message: "#^Method Winter\\\\Storm\\\\Database\\\\Relations\\\\AttachMany\\:\\:withDeferred\\(\\) should return Illuminate\\\\Database\\\\Query\\\\Builder but returns Illuminate\\\\Database\\\\Eloquent\\\\Builder\\.$#" - count: 1 - path: src/Database/Relations/AttachMany.php - - - - message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\AttachMany\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\AttachMany\\) given\\.$#" - count: 2 - path: src/Database/Relations/AttachMany.php - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" count: 1 @@ -260,16 +250,6 @@ parameters: count: 3 path: src/Database/Relations/AttachOne.php - - - message: "#^Method Winter\\\\Storm\\\\Database\\\\Relations\\\\AttachOne\\:\\:withDeferred\\(\\) should return Illuminate\\\\Database\\\\Query\\\\Builder but returns Illuminate\\\\Database\\\\Eloquent\\\\Builder\\.$#" - count: 1 - path: src/Database/Relations/AttachOne.php - - - - message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\AttachOne\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\AttachOne\\) given\\.$#" - count: 2 - path: src/Database/Relations/AttachOne.php - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" count: 1 @@ -310,16 +290,6 @@ parameters: count: 1 path: src/Database/Relations/BelongsTo.php - - - message: "#^Method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsTo\\:\\:withDeferred\\(\\) should return Illuminate\\\\Database\\\\Query\\\\Builder but returns Illuminate\\\\Database\\\\Eloquent\\\\Builder\\.$#" - count: 1 - path: src/Database/Relations/BelongsTo.php - - - - message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsTo\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsTo\\) given\\.$#" - count: 2 - path: src/Database/Relations/BelongsTo.php - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:\\$sessionKey\\.$#" count: 1 @@ -400,21 +370,6 @@ parameters: count: 1 path: src/Database/Relations/BelongsToMany.php - - - message: "#^Method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsToMany\\:\\:allRelatedIds\\(\\) should return Winter\\\\Storm\\\\Support\\\\Collection but returns Illuminate\\\\Support\\\\Collection\\.$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsToMany\\:\\:withDeferred\\(\\) should return Illuminate\\\\Database\\\\Query\\\\Builder but returns Illuminate\\\\Database\\\\Eloquent\\\\Builder\\.$#" - count: 1 - path: src/Database/Relations/BelongsToMany.php - - - - message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsToMany\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\BelongsToMany\\) given\\.$#" - count: 2 - path: src/Database/Relations/BelongsToMany.php - - message: "#^Parameter \\#2 \\$columns of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\:\\:paginate\\(\\) expects array, int\\|null given\\.$#" count: 1 @@ -485,16 +440,6 @@ parameters: count: 1 path: src/Database/Relations/HasMany.php - - - message: "#^Method Winter\\\\Storm\\\\Database\\\\Relations\\\\HasMany\\:\\:withDeferred\\(\\) should return Illuminate\\\\Database\\\\Query\\\\Builder but returns Illuminate\\\\Database\\\\Eloquent\\\\Builder\\.$#" - count: 1 - path: src/Database/Relations/HasMany.php - - - - message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\HasMany\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\HasMany\\) given\\.$#" - count: 2 - path: src/Database/Relations/HasMany.php - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" count: 1 @@ -525,11 +470,6 @@ parameters: count: 1 path: src/Database/Relations/HasManyThrough.php - - - message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\HasManyThrough\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\HasManyThrough\\) given\\.$#" - count: 1 - path: src/Database/Relations/HasManyThrough.php - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" count: 1 @@ -565,16 +505,6 @@ parameters: count: 2 path: src/Database/Relations/HasOne.php - - - message: "#^Method Winter\\\\Storm\\\\Database\\\\Relations\\\\HasOne\\:\\:withDeferred\\(\\) should return Illuminate\\\\Database\\\\Query\\\\Builder but returns Illuminate\\\\Database\\\\Eloquent\\\\Builder\\.$#" - count: 1 - path: src/Database/Relations/HasOne.php - - - - message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\HasOne\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\HasOne\\) given\\.$#" - count: 2 - path: src/Database/Relations/HasOne.php - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" count: 1 @@ -605,11 +535,6 @@ parameters: count: 1 path: src/Database/Relations/HasOneThrough.php - - - message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\HasOneThrough\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\HasOneThrough\\) given\\.$#" - count: 1 - path: src/Database/Relations/HasOneThrough.php - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" count: 1 @@ -655,16 +580,6 @@ parameters: count: 1 path: src/Database/Relations/MorphMany.php - - - message: "#^Method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphMany\\:\\:withDeferred\\(\\) should return Illuminate\\\\Database\\\\Query\\\\Builder but returns Illuminate\\\\Database\\\\Eloquent\\\\Builder\\.$#" - count: 1 - path: src/Database/Relations/MorphMany.php - - - - message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphMany\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphMany\\) given\\.$#" - count: 2 - path: src/Database/Relations/MorphMany.php - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" count: 1 @@ -700,16 +615,6 @@ parameters: count: 2 path: src/Database/Relations/MorphOne.php - - - message: "#^Method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphOne\\:\\:withDeferred\\(\\) should return Illuminate\\\\Database\\\\Query\\\\Builder but returns Illuminate\\\\Database\\\\Eloquent\\\\Builder\\.$#" - count: 1 - path: src/Database/Relations/MorphOne.php - - - - message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphOne\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphOne\\) given\\.$#" - count: 2 - path: src/Database/Relations/MorphOne.php - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" count: 1 @@ -745,11 +650,6 @@ parameters: count: 1 path: src/Database/Relations/MorphTo.php - - - message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphTo\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphTo\\) given\\.$#" - count: 1 - path: src/Database/Relations/MorphTo.php - - message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Relations\\\\BelongsToMany\\:\\:\\$countMode\\.$#" count: 1 @@ -790,16 +690,6 @@ parameters: count: 1 path: src/Database/Relations/MorphToMany.php - - - message: "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphToMany\\:\\:withDeferred\\(\\)\\.$#" - count: 1 - path: src/Database/Relations/MorphToMany.php - - - - message: "#^Parameter \\#1 \\$query of method Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphToMany\\:\\:addDefinedConstraintsToQuery\\(\\) expects Winter\\\\Storm\\\\Database\\\\QueryBuilder, \\$this\\(Winter\\\\Storm\\\\Database\\\\Relations\\\\MorphToMany\\) given\\.$#" - count: 1 - path: src/Database/Relations/MorphToMany.php - - message: "#^Parameter \\#2 \\$columns of method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\\\:\\:paginate\\(\\) expects array, int\\|null given\\.$#" count: 1 @@ -840,16 +730,6 @@ parameters: count: 1 path: src/Database/TreeCollection.php - - - message: "#^Call to static method reguard\\(\\) on an unknown class Winter\\\\Storm\\\\Database\\\\Eloquent\\.$#" - count: 1 - path: src/Database/Updater.php - - - - message: "#^Call to static method unguard\\(\\) on an unknown class Winter\\\\Storm\\\\Database\\\\Eloquent\\.$#" - count: 1 - path: src/Database/Updater.php - - message: "#^Winter\\\\Storm\\\\Extension\\\\Extendable\\:\\:extendableCall\\(\\) calls parent\\:\\:__call\\(\\) but Winter\\\\Storm\\\\Extension\\\\Extendable does not extend any class\\.$#" count: 1 From 3558833a513bb0f901ed17902dcd9f964edeaa65 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 1 Jul 2022 14:29:25 +0800 Subject: [PATCH 102/108] Allow CSRF token and session key to be null --- src/Html/FormBuilder.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Html/FormBuilder.php b/src/Html/FormBuilder.php index 79ed2811..056c4d3b 100644 --- a/src/Html/FormBuilder.php +++ b/src/Html/FormBuilder.php @@ -25,7 +25,7 @@ class FormBuilder /** * The CSRF token used by the form builder. */ - protected string $csrfToken; + protected ?string $csrfToken = null; /** * The session store implementation. @@ -93,14 +93,13 @@ class FormBuilder /** * The session key used by the form builder. - * @var string */ - protected string $sessionKey; + protected ?string $sessionKey = null; /** * Create a new form builder instance. */ - public function __construct(HtmlBuilder $html, UrlGeneratorBase $url, string $csrfToken, string $sessionKey) + public function __construct(HtmlBuilder $html, UrlGeneratorBase $url, ?string $csrfToken = null, ?string $sessionKey = null) { $this->url = $url; $this->html = $html; From ba709ce51a9bcf53607b13f01b6c4c0a83104e4a Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 1 Jul 2022 14:38:43 +0800 Subject: [PATCH 103/108] Fix return type --- src/Html/FormBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Html/FormBuilder.php b/src/Html/FormBuilder.php index 056c4d3b..a671a558 100644 --- a/src/Html/FormBuilder.php +++ b/src/Html/FormBuilder.php @@ -999,7 +999,7 @@ public function sessionKey($sessionKey = null) /** * Returns the active session key, used fr deferred bindings. - * @return string + * @return string|null */ public function getSessionKey() { From cb93b802fb1073bc5f2344e0767ef41c16acb96a Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 1 Jul 2022 14:46:40 +0800 Subject: [PATCH 104/108] Ignore lines in PHPStan - it can't seem to work out the relations here --- src/Database/Relations/Concerns/DeferOneOrMany.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Database/Relations/Concerns/DeferOneOrMany.php b/src/Database/Relations/Concerns/DeferOneOrMany.php index 5f5979fb..2edddeee 100644 --- a/src/Database/Relations/Concerns/DeferOneOrMany.php +++ b/src/Database/Relations/Concerns/DeferOneOrMany.php @@ -21,6 +21,7 @@ public function withDeferred($sessionKey) /* * No join table will be used, strip the selected "pivot_" columns */ + /** @phpstan-ignore-next-line */ if ($this instanceof BelongsToMany || $this instanceof MorphToMany) { $this->orphanMode = true; } @@ -28,6 +29,7 @@ public function withDeferred($sessionKey) $newQuery->where(function ($query) use ($sessionKey) { if ($this->parent->exists) { + /** @phpstan-ignore-next-line */ if ($this instanceof MorphToMany) { /* * Custom query for MorphToMany since a "join" cannot be used @@ -42,6 +44,7 @@ public function withDeferred($sessionKey) ->where($this->getForeignKey(), $this->parent->getKey()) ->where($this->getMorphType(), $this->getMorphClass()); }); + /** @phpstan-ignore-next-line */ } elseif ($this instanceof BelongsToMany) { /* * Custom query for BelongsToMany since a "join" cannot be used From 7a8559416a1959f5d6a3a41d08f2a511e7ab6365 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 1 Jul 2022 16:13:59 +0800 Subject: [PATCH 105/108] Update src/Database/Relations/Concerns/DeferOneOrMany.php Co-authored-by: Luke Towers --- src/Database/Relations/Concerns/DeferOneOrMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Relations/Concerns/DeferOneOrMany.php b/src/Database/Relations/Concerns/DeferOneOrMany.php index 2edddeee..14be77bd 100644 --- a/src/Database/Relations/Concerns/DeferOneOrMany.php +++ b/src/Database/Relations/Concerns/DeferOneOrMany.php @@ -39,7 +39,7 @@ public function withDeferred($sessionKey) ->select($this->parent->getConnection()->raw(1)) ->from($this->table) ->where($this->getOtherKey(), DbDongle::raw( - DbDongle::getTablePrefix().$this->related->getQualifiedKeyName() + DbDongle::getTablePrefix() . $this->related->getQualifiedKeyName() )) ->where($this->getForeignKey(), $this->parent->getKey()) ->where($this->getMorphType(), $this->getMorphClass()); From 784ab77d150c828a0782ec9327b15902ab7b9257 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 1 Jul 2022 16:14:08 +0800 Subject: [PATCH 106/108] Update src/Database/Relations/Concerns/DeferOneOrMany.php Co-authored-by: Luke Towers --- src/Database/Relations/Concerns/DeferOneOrMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Relations/Concerns/DeferOneOrMany.php b/src/Database/Relations/Concerns/DeferOneOrMany.php index 14be77bd..519c9d8b 100644 --- a/src/Database/Relations/Concerns/DeferOneOrMany.php +++ b/src/Database/Relations/Concerns/DeferOneOrMany.php @@ -54,7 +54,7 @@ public function withDeferred($sessionKey) ->select($this->parent->getConnection()->raw(1)) ->from($this->table) ->where($this->getOtherKey(), DbDongle::raw( - DbDongle::getTablePrefix().$this->related->getQualifiedKeyName() + DbDongle::getTablePrefix() . $this->related->getQualifiedKeyName() )) ->where($this->getForeignKey(), $this->parent->getKey()); }); From fe190c6c4f05d2ca64f7cd7acc22639c2c138734 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sat, 2 Jul 2022 08:15:59 +0800 Subject: [PATCH 107/108] Remove return type missed in last pass --- src/Database/Attach/File.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Attach/File.php b/src/Database/Attach/File.php index f3480d97..5ecf7b5a 100644 --- a/src/Database/Attach/File.php +++ b/src/Database/Attach/File.php @@ -632,7 +632,7 @@ public function getThumb($width, $height, $options = []) * ] * @return string The filename of the thumbnail */ - public function getThumbFilename($width, $height, $options = []): string + public function getThumbFilename($width, $height, $options = []) { $options = $this->getDefaultThumbOptions($options); return implode('_', [ From 422e58e83a7879f675625131f58f53c5a529746a Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sat, 2 Jul 2022 08:45:09 +0800 Subject: [PATCH 108/108] Change to Str call --- src/Foundation/Providers/ExecutionContextProvider.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Foundation/Providers/ExecutionContextProvider.php b/src/Foundation/Providers/ExecutionContextProvider.php index ac2099f2..16178f0f 100644 --- a/src/Foundation/Providers/ExecutionContextProvider.php +++ b/src/Foundation/Providers/ExecutionContextProvider.php @@ -1,6 +1,7 @@