├── CachableQueryServiceProvider.php ├── CacheDecorator.php ├── Console └── Migrations │ ├── FreshCommand.php │ ├── MigrateCommand.php │ ├── Packages.php │ ├── RefreshCommand.php │ ├── ResetCommand.php │ └── RollbackCommand.php ├── ConsoleServiceProvider.php ├── SearchServiceProvider.php └── composer.json /CachableQueryServiceProvider.php: -------------------------------------------------------------------------------- 1 | remember($duration, $key); 18 | }); 19 | 20 | QueryBuilder::macro('rememberForever', function ($key = null) { 21 | return (new CacheDecorator($this, \app('cache.store')))->rememberForever($key); 22 | }); 23 | 24 | EloquentBuilder::macro('remember', function ($duration, $key = null) { 25 | return (new CacheDecorator($this, \app('cache.store')))->remember($duration, $key); 26 | }); 27 | 28 | EloquentBuilder::macro('rememberForever', function ($key = null) { 29 | return (new CacheDecorator($this, \app('cache.store')))->rememberForever($key); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CacheDecorator.php: -------------------------------------------------------------------------------- 1 | repository = $repository; 47 | $this->query = $query; 48 | } 49 | 50 | /** 51 | * Indicate that the query results should be cached. 52 | * 53 | * @param \DateTime|int $minutes 54 | * @param string $key 55 | * 56 | * @return $this 57 | */ 58 | public function remember($minutes, ?string $key = null) 59 | { 60 | $this->cacheMinutes = $minutes; 61 | $this->cacheKey = $key; 62 | 63 | return $this; 64 | } 65 | 66 | /** 67 | * Indicate that the query results should be cached forever. 68 | * 69 | * @param string $key 70 | * 71 | * @return $this 72 | */ 73 | public function rememberForever(?string $key = null) 74 | { 75 | return $this->remember(-1, $key); 76 | } 77 | 78 | /** 79 | * Get an array with the values of a given column. 80 | */ 81 | public function pluck(string $column, ?string $key = null): Collection 82 | { 83 | $results = $this->get(\is_null($key) ? [$column] : [$column, $key]); 84 | 85 | // If the columns are qualified with a table or have an alias, we cannot use 86 | // those directly in the "pluck" operations since the results from the DB 87 | // are only keyed by the column itself. We'll strip the table out here. 88 | return $results->pluck( 89 | $this->stripTableForPluck($column), 90 | $this->stripTableForPluck($key) 91 | ); 92 | } 93 | 94 | /** 95 | * Strip off the table name or alias from a column identifier. 96 | */ 97 | protected function stripTableForPluck(string $column): ?string 98 | { 99 | return \is_null($column) ? $column : \end(\preg_split('~\.| ~', $column)); 100 | } 101 | 102 | /** 103 | * Execute the query and get the first result. 104 | * 105 | * @param array $columns 106 | * 107 | * @return mixed|static 108 | */ 109 | public function first($columns = ['*']) 110 | { 111 | $this->query->take(1); 112 | 113 | $results = $this->get($columns); 114 | 115 | if ($results instanceof Collection) { 116 | return $results->first(); 117 | } 118 | 119 | return \count($results) > 0 ? \reset($results) : null; 120 | } 121 | 122 | /** 123 | * Execute the query as a "select" statement. 124 | * 125 | * @param array $columns 126 | */ 127 | public function get($columns = ['*']): Collection 128 | { 129 | try { 130 | if (! \is_null($this->cacheMinutes)) { 131 | return $this->getCached($columns); 132 | } 133 | } catch (Throwable $e) { 134 | // 135 | } 136 | 137 | return $this->getFresh($columns); 138 | } 139 | 140 | /** 141 | * Execute the query as a cached "select" statement. 142 | * 143 | * @param array $columns 144 | */ 145 | public function getCached($columns = ['*']): Collection 146 | { 147 | // If the query is requested to be cached, we will cache it using a unique key 148 | // for this database connection and query statement, including the bindings 149 | // that are used on this query, providing great convenience when caching. 150 | [$key, $minutes] = $this->getCacheInfo(); 151 | 152 | $cache = $this->getCache(); 153 | 154 | $callback = $this->getCacheCallback($columns); 155 | 156 | // If the "minutes" value is less than zero, we will use that as the indicator 157 | // that the value should be remembered values should be stored indefinitely 158 | // and if we have minutes we will use the typical remember function here. 159 | if ($minutes < 0) { 160 | return Collection::make($cache->rememberForever($key, $callback)); 161 | } 162 | 163 | return Collection::make($cache->remember($key, $minutes, $callback)); 164 | } 165 | 166 | /** 167 | * Execute the query as a fresh "select" statement. 168 | * 169 | * @param array $columns 170 | */ 171 | public function getFresh($columns = ['*']): Collection 172 | { 173 | return $this->query->get($columns); 174 | } 175 | 176 | /** 177 | * Get a unique cache key for the complete query. 178 | */ 179 | public function getCacheKey(): string 180 | { 181 | return $this->cacheKey ?: $this->generateCacheKey(); 182 | } 183 | 184 | /** 185 | * Generate the unique cache key for the query. 186 | */ 187 | public function generateCacheKey(): string 188 | { 189 | $name = $this->getConnection()->getName(); 190 | 191 | return \md5($name.$this->toSql().\serialize($this->getBindings())); 192 | } 193 | 194 | /** 195 | * Get the cache object with tags assigned, if applicable. 196 | */ 197 | protected function getCache(): Repository 198 | { 199 | return $this->repository; 200 | } 201 | 202 | /** 203 | * Get the Closure callback used when caching queries. 204 | * 205 | * @param array $columns 206 | * 207 | * @return \Closure 208 | */ 209 | protected function getCacheCallback($columns) 210 | { 211 | return function () use ($columns) { 212 | return $this->getFresh($columns)->all(); 213 | }; 214 | } 215 | 216 | /** 217 | * Get the cache key and cache minutes as an array. 218 | */ 219 | protected function getCacheInfo(): array 220 | { 221 | return [$this->getCacheKey(), $this->cacheMinutes]; 222 | } 223 | 224 | /** 225 | * Handle dynamic method calls into the method. 226 | * 227 | * @return mixed 228 | */ 229 | public function __call(string $method, array $parameters) 230 | { 231 | return $this->query->{$method}(...$parameters); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /Console/Migrations/FreshCommand.php: -------------------------------------------------------------------------------- 1 | specifyParameters(); 22 | } 23 | 24 | /** 25 | * Get the path to the migration directory. 26 | * 27 | * @return array 28 | */ 29 | protected function getMigrationPaths() 30 | { 31 | // If the package is in the list of migration paths we received we will put 32 | // the migrations in that path. Otherwise, we will assume the package is 33 | // is in the package directories and will place them in that location. 34 | if (! \is_null($package = $this->option('package'))) { 35 | return $this->getPackageMigrationPaths($package); 36 | } 37 | 38 | return parent::getMigrationPaths(); 39 | } 40 | 41 | /** 42 | * Get the console command options. 43 | * 44 | * @return array 45 | */ 46 | protected function getOptions() 47 | { 48 | return [ 49 | ['package', null, InputOption::VALUE_OPTIONAL, 'The package to migrate.', null], 50 | ]; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Console/Migrations/Packages.php: -------------------------------------------------------------------------------- 1 | packagePath = $packagePath; 24 | 25 | return $this; 26 | } 27 | 28 | /** 29 | * Get the path to the package migration directory. 30 | */ 31 | protected function getPackageMigrationPaths(string $package): array 32 | { 33 | $packagePath = $this->packagePath; 34 | 35 | return Collection::make($this->option('path') ?: 'database/migrations') 36 | ->map(static function ($path) use ($packagePath, $package) { 37 | return $packagePath.'/'.$package.'/'.$path; 38 | })->all(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Console/Migrations/RefreshCommand.php: -------------------------------------------------------------------------------- 1 | option('package'))) { 23 | return $this->getPackageMigrationPaths($package); 24 | } 25 | 26 | return parent::getMigrationPaths(); 27 | } 28 | 29 | /** 30 | * Get the console command options. 31 | * 32 | * @return array 33 | */ 34 | protected function getOptions() 35 | { 36 | $options = [ 37 | ['package', null, InputOption::VALUE_OPTIONAL, 'The package to migrate.', null], 38 | ]; 39 | 40 | return \array_merge($options, parent::getOptions()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Console/Migrations/RollbackCommand.php: -------------------------------------------------------------------------------- 1 | option('package'))) { 23 | return $this->getPackageMigrationPaths($package); 24 | } 25 | 26 | return parent::getMigrationPaths(); 27 | } 28 | 29 | /** 30 | * Get the console command options. 31 | * 32 | * @return array 33 | */ 34 | protected function getOptions() 35 | { 36 | $options = [ 37 | ['package', null, InputOption::VALUE_OPTIONAL, 'The package to migrate.', null], 38 | ]; 39 | 40 | return \array_merge($options, parent::getOptions()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ConsoleServiceProvider.php: -------------------------------------------------------------------------------- 1 | {'register'.$command.'Command'}(); 30 | } 31 | 32 | // Once the commands are registered in the application IoC container we will 33 | // register them with the Artisan start event so that these are available 34 | // when the Artisan application actually starts up and is getting used. 35 | $this->commands( 36 | 'command.migrate', 37 | 'command.migrate.fresh', 38 | 'command.migrate.rollback', 39 | 'command.migrate.reset', 40 | 'command.migrate.refresh' 41 | ); 42 | } 43 | 44 | /** 45 | * Register the command. 46 | * 47 | * @return void 48 | */ 49 | protected function registerFreshCommand() 50 | { 51 | $this->app->singleton('command.migrate.fresh', static function () { 52 | return new FreshCommand(); 53 | }); 54 | } 55 | 56 | /** 57 | * Register the "migrate" migration command. 58 | * 59 | * @return void 60 | */ 61 | protected function registerMigrateCommand() 62 | { 63 | $this->app->singleton('command.migrate', function (Container $app) { 64 | return $this->getCommandWithPackage( 65 | new MigrateCommand($app->make('migrator'), $app->make('events')) 66 | ); 67 | }); 68 | } 69 | 70 | /** 71 | * Register the "rollback" migration command. 72 | * 73 | * @return void 74 | */ 75 | protected function registerRollbackCommand() 76 | { 77 | $this->app->singleton('command.migrate.rollback', function (Container $app) { 78 | return $this->getCommandWithPackage(new RollbackCommand($app->make('migrator'))); 79 | }); 80 | } 81 | 82 | /** 83 | * Register the "reset" migration command. 84 | * 85 | * @return void 86 | */ 87 | protected function registerResetCommand() 88 | { 89 | $this->app->singleton('command.migrate.reset', function (Container $app) { 90 | return $this->getCommandWithPackage(new ResetCommand($app->make('migrator'))); 91 | }); 92 | } 93 | 94 | /** 95 | * Register the "refresh" migration command. 96 | * 97 | * @return void 98 | */ 99 | protected function registerRefreshCommand() 100 | { 101 | $this->app->singleton('command.migrate.refresh', static function () { 102 | return new RefreshCommand(); 103 | }); 104 | } 105 | 106 | /** 107 | * Set package path for command. 108 | * 109 | * @param object $command 110 | * 111 | * @return object 112 | */ 113 | protected function getCommandWithPackage($command) 114 | { 115 | $packagePath = $this->app->basePath().'/vendor'; 116 | 117 | return $command->setPackagePath($packagePath); 118 | } 119 | 120 | /** 121 | * Get the services provided by the provider. 122 | * 123 | * @return array 124 | */ 125 | public function provides() 126 | { 127 | return [ 128 | 'command.migrate', 129 | 'command.migrate.rollback', 130 | 'command.migrate.reset', 131 | 'command.migrate.refresh', 132 | ]; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /SearchServiceProvider.php: -------------------------------------------------------------------------------- 1 | apply($this); 22 | }); 23 | 24 | EloquentBuilder::macro('whereLike', static function ($attributes, string $searchTerm) { 25 | return (new Searchable($searchTerm, Arr::wrap($attributes)))->apply($this); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "orchestra/database", 3 | "description": "Database Component for Orchestra Platform", 4 | "keywords": ["orchestra-platform", "orchestral", "database"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Taylor Otwell", 9 | "email": "taylorotwell@gmail.com", 10 | "homepage": "https://github.com/taylorotwell" 11 | }, 12 | { 13 | "name": "Mior Muhammad Zaki", 14 | "email": "crynobone@gmail.com", 15 | "homepage": "https://github.com/crynobone" 16 | } 17 | ], 18 | "autoload": { 19 | "psr-4": { 20 | "Orchestra\\Database\\": "" 21 | } 22 | }, 23 | "require": { 24 | "php": "^7.4 || ^8.0", 25 | "illuminate/contracts": "^9.0", 26 | "illuminate/database": "^9.0", 27 | "illuminate/support": "^9.0", 28 | "laravie/query-filter": "dev-master" 29 | }, 30 | "suggest": { 31 | "laravie/query-filter": "Allow to use whereLike query builder macros with Laravel (dev-master)." 32 | }, 33 | "extra": { 34 | "branch-alias": { 35 | "dev-master": "7.0-dev" 36 | }, 37 | "laravel": { 38 | "providers": [ 39 | "Orchestra\\Database\\CachableQueryServiceProvider", 40 | "Orchestra\\Database\\SearchServiceProvider" 41 | ] 42 | } 43 | }, 44 | "support": { 45 | "issues": "https://github.com/orchestral/kernel/issues" 46 | }, 47 | "config": { 48 | "sort-packages": true 49 | }, 50 | "minimum-stability": "dev" 51 | } 52 | --------------------------------------------------------------------------------