├── src ├── MicheleAngioni │ └── Support │ │ ├── Presenters │ │ ├── PresentableInterface.php │ │ ├── Presenter.php │ │ └── AbstractPresenter.php │ │ ├── Exceptions │ │ ├── DatabaseException.php │ │ ├── PermissionsException.php │ │ └── DbClientRequestException.php │ │ ├── Repos │ │ ├── XMLRepositoryInterface.php │ │ ├── RepositoryCacheableQueriesInterface.php │ │ ├── AbstractSimpleXMLRepository.php │ │ ├── RepositoryInterface.php │ │ └── AbstractEloquentRepository.php │ │ ├── Facades │ │ └── Helpers.php │ │ ├── Cache │ │ ├── CacheInterface.php │ │ ├── LaravelCache.php │ │ ├── KeyManagerInterface.php │ │ ├── KeyManager.php │ │ ├── AbstractCacheSimpleXMLRepositoryDecorator.php │ │ └── AbstractCacheRepositoryDecorator.php │ │ ├── SupportServiceProvider.php │ │ ├── CustomValidators.php │ │ ├── Semaphores │ │ └── SemaphoresManager.php │ │ └── Helpers.php └── config │ └── config.php ├── .gitignore ├── .travis.yml ├── phpunit.xml ├── docs.php ├── composer.json ├── LICENSE ├── tests ├── SemaphoreTest.php └── HelpersTest.php └── README.md /src/MicheleAngioni/Support/Presenters/PresentableInterface.php: -------------------------------------------------------------------------------- 1 | 600, // 10 minutes 15 | 16 | ]; 17 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs.php: -------------------------------------------------------------------------------- 1 | files() 11 | ->name('*.php') 12 | ->in($dir = __DIR__ . '/src'); 13 | 14 | $versions = GitVersionCollection::create($dir) 15 | ->addFromTags('v1.*') 16 | ->add('master', 'master branch'); 17 | 18 | $options = array( 19 | 'versions' => $versions, 20 | 'title' => 'Support API', 21 | 'build_dir' => __DIR__ . '/build/docs/%version%', 22 | 'cache_dir' => __DIR__ . '/build/cache/docs/%version%', 23 | 'default_opened_level' => 2, 24 | ); 25 | 26 | return new Sami($iterator, $options); 27 | -------------------------------------------------------------------------------- /src/MicheleAngioni/Support/Cache/CacheInterface.php: -------------------------------------------------------------------------------- 1 | =7.1.3 <8.0.0", 15 | "illuminate/cache": "5.8.x", 16 | "illuminate/console": "5.8.x", 17 | "illuminate/database": "5.8.x", 18 | "illuminate/pagination": "5.8.x", 19 | "illuminate/support": "5.8.x", 20 | "illuminate/validation": "5.8.x" 21 | }, 22 | "require-dev": { 23 | "mockery/mockery": "^1.1", 24 | "phpunit/phpunit": "^7.5", 25 | "sami/sami": "~3.3|~4.0" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "MicheleAngioni\\Support\\": "src/MicheleAngioni/Support" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2016 Michele Angioni 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/MicheleAngioni/Support/Repos/AbstractSimpleXMLRepository.php: -------------------------------------------------------------------------------- 1 | xmlPath = app_path() . $this->xmlPath; 29 | 30 | if ($this->autoload) { 31 | $this->loadFile(); 32 | } 33 | } 34 | 35 | /* 36 | * Return file path. 37 | * 38 | * @return string 39 | */ 40 | public function getFilePath(): string 41 | { 42 | return $this->xmlPath; 43 | } 44 | 45 | /** 46 | * Return the xml file as an instance of SimpleXML 47 | * 48 | * @throws UnexpectedValueException 49 | * @return \SimpleXMLElement 50 | */ 51 | public function getFile() 52 | { 53 | if (!$this->xmlFile) { 54 | $this->loadFile(); 55 | } 56 | 57 | return $this->xmlFile; 58 | } 59 | 60 | /** 61 | * Load the file and save it into the class var 62 | */ 63 | public function loadFile() 64 | { 65 | if (!$this->xmlFile) { 66 | $this->xmlFile = simplexml_load_file($this->xmlPath); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/MicheleAngioni/Support/Cache/LaravelCache.php: -------------------------------------------------------------------------------- 1 | cache = $cache; 27 | $this->seconds = config('ma_support.cache_time'); 28 | } 29 | 30 | /** 31 | * Get 32 | * 33 | * @param string $key 34 | * @param array $tags 35 | * 36 | * @return mixed 37 | */ 38 | public function get(string $key, array $tags) 39 | { 40 | return $this->cache->tags($tags)->get($key); 41 | } 42 | 43 | /** 44 | * Put 45 | * 46 | * @param string $key 47 | * @param mixed $value 48 | * @param array $tags 49 | * @param int $seconds 50 | * 51 | * @return mixed 52 | */ 53 | public function put(string $key, $value, array $tags, int $seconds = null) 54 | { 55 | if (is_null($seconds)) { 56 | $seconds = $this->seconds; 57 | } 58 | 59 | return $this->cache->tags($tags)->put($key, $value, $seconds); 60 | } 61 | 62 | /** 63 | * Has 64 | * 65 | * @param string $key 66 | * @param array $tags 67 | * 68 | * @return bool 69 | */ 70 | public function has(string $key, array $tags): bool 71 | { 72 | return $this->cache->tags($tags)->has($key); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/MicheleAngioni/Support/Presenters/Presenter.php: -------------------------------------------------------------------------------- 1 | set($model); 24 | 25 | return $object; 26 | } 27 | 28 | /** 29 | * Return an instance of a Collection with each value wrapped in a presenter object 30 | * 31 | * @param Collection $collection 32 | * @param PresentableInterface $presenter 33 | * 34 | * @return Collection 35 | */ 36 | public function collection(Collection $collection, PresentableInterface $presenter) 37 | { 38 | foreach ($collection as $key => $value) { 39 | $collection->put($key, $this->model($value, $presenter)); 40 | } 41 | 42 | return $collection; 43 | } 44 | 45 | /** 46 | * Return an instance of a Paginator with each value wrapped in a presenter object 47 | * 48 | * @param Paginator $paginator 49 | * @param PresentableInterface $presenter 50 | * 51 | * @return Paginator 52 | */ 53 | public function paginator(Paginator $paginator, PresentableInterface $presenter) 54 | { 55 | $items = []; 56 | 57 | foreach ($paginator->getItems() as $item) { 58 | $items[] = $this->model($item, $presenter); 59 | } 60 | 61 | $paginator->setItems($items); 62 | 63 | return $paginator; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/MicheleAngioni/Support/Presenters/AbstractPresenter.php: -------------------------------------------------------------------------------- 1 | object[$key]); 26 | } 27 | 28 | /** 29 | * Retrieve the key from the object as if it were an array 30 | * 31 | * @param string $key 32 | * 33 | * @return boolean 34 | */ 35 | public function offsetGet($key) 36 | { 37 | return $this->__get($key); 38 | } 39 | 40 | /** 41 | * Set a property on the object as if it were any array 42 | * 43 | * @param string $key 44 | * @param mixed $value 45 | */ 46 | public function offsetSet($key, $value) 47 | { 48 | $this->object[$key] = $value; 49 | } 50 | 51 | /** 52 | * Unset a key on the object as if it were an array 53 | * 54 | * @param string $key 55 | */ 56 | public function offsetUnset($key) 57 | { 58 | unset($this->object[$key]); 59 | } 60 | 61 | /** 62 | * Inject the object to be presented 63 | * 64 | * @param mixed 65 | */ 66 | public function set($object) 67 | { 68 | $this->object = $object; 69 | } 70 | 71 | /** 72 | * Check to see if there is a presenter method. If not pass to the object 73 | * 74 | * @param string $key 75 | */ 76 | public function __get($key) 77 | { 78 | if (method_exists($this, $key)) { 79 | return $this->{$key}(); 80 | } 81 | 82 | return $this->object->$key; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/MicheleAngioni/Support/Cache/KeyManagerInterface.php: -------------------------------------------------------------------------------- 1 | publishes([ 18 | __DIR__ . '/../../config/config.php' => config_path('ma_support.php'), 19 | ]); 20 | 21 | $this->mergeConfigFrom( 22 | __DIR__ . '/../../config/config.php', 'ma_support' 23 | ); 24 | 25 | $this->registerCustomValidators(); 26 | } 27 | 28 | /** 29 | * Register the service provider. 30 | * 31 | * @return void 32 | */ 33 | public function register() 34 | { 35 | $this->registerHelpers(); 36 | } 37 | 38 | /** 39 | * Get the services provided by the provider. 40 | * 41 | * @return array 42 | */ 43 | public function provides() 44 | { 45 | return []; 46 | } 47 | 48 | 49 | public function registerHelpers() 50 | { 51 | $this->app->bind('helpers', function () { 52 | return new Helpers; 53 | }); 54 | } 55 | 56 | public function registerCustomValidators() 57 | { 58 | $validator = $this->app->make('validator'); 59 | 60 | $validator->resolver(function ($translator, $data, $rules, $messages) { 61 | $messages = [ 62 | 'alpha_complete' => 'Only the following characters are allowed: alphabetic, numbers, spaces, slashes and several punctuation characters.', 63 | 'alpha_space' => 'Only the following characters are allowed: alphabetic, numbers and spaces.', 64 | 'alpha_underscore' => 'Only the following characters are allowed: alphabetic, numbers and underscores.', 65 | 'alphanumeric_names' => 'Only the following characters are allowed: letters, numbers, menus, apostrophes, underscores and spaces.', 66 | 'alphanumeric_dotted_names' => 'Only the following characters are allowed: letters, numbers, menus, apostrophes, underscores, dots and spaces.', 67 | 'alpha_names' => 'Only the following characters are allowed: alphabetic, menus, apostrophes, underscores and spaces.', 68 | ]; 69 | 70 | return new CustomValidators($translator, $data, $rules, $messages); 71 | }); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/MicheleAngioni/Support/CustomValidators.php: -------------------------------------------------------------------------------- 1 | mock('MicheleAngioni\Support\Cache\CacheInterface'); 17 | $keyManagerInterface = $this->mock('MicheleAngioni\Support\Cache\KeyManagerInterface'); 18 | 19 | $semaphoreManager = new SemaphoresManager($cacheInterface, 20 | $keyManagerInterface); 21 | 22 | $semaphoreManager->setLockingTime($lockingTime); 23 | 24 | $this->assertEquals($lockingTime, $semaphoreManager->getLockingTime()); 25 | } 26 | 27 | public function testGetSemaphoreKey() 28 | { 29 | $cacheInterface = $this->mock('MicheleAngioni\Support\Cache\CacheInterface'); 30 | $keyManagerInterface = $this->mock('MicheleAngioni\Support\Cache\KeyManagerInterface'); 31 | 32 | $keyManagerInterface->shouldReceive('getKey') 33 | ->andReturn(true); 34 | 35 | $semaphoreManager = new SemaphoresManager($cacheInterface, 36 | $keyManagerInterface); 37 | 38 | $semaphoreManager->getSemaphoreKey('id', 'section'); 39 | } 40 | 41 | public function testLockSemaphore() 42 | { 43 | $cacheInterface = $this->mock('MicheleAngioni\Support\Cache\CacheInterface'); 44 | $keyManagerInterface = $this->mock('MicheleAngioni\Support\Cache\KeyManagerInterface'); 45 | 46 | $semaphoreManager = Mockery::mock('MicheleAngioni\Support\Semaphores\SemaphoresManager[getSemaphoreKey]', 47 | [$cacheInterface, $keyManagerInterface]); 48 | 49 | $semaphoreManager->shouldReceive('getSemaphoreKey') 50 | ->andReturn('string'); 51 | 52 | $cacheInterface->shouldReceive('put') 53 | ->andReturn(true); 54 | 55 | $semaphoreManager->lockSemaphore('id', 'section'); 56 | } 57 | 58 | public function testUnlockSemaphore() 59 | { 60 | $cacheInterface = $this->mock('MicheleAngioni\Support\Cache\CacheInterface'); 61 | $keyManagerInterface = $this->mock('MicheleAngioni\Support\Cache\KeyManagerInterface'); 62 | 63 | $semaphoreManager = Mockery::mock('MicheleAngioni\Support\Semaphores\SemaphoresManager[getSemaphoreKey]', 64 | [$cacheInterface, $keyManagerInterface]); 65 | 66 | $semaphoreManager->shouldReceive('getSemaphoreKey') 67 | ->andReturn('string'); 68 | 69 | $cacheInterface->shouldReceive('put') 70 | ->andReturn(true); 71 | 72 | $semaphoreManager->unlockSemaphore('id', 'section'); 73 | } 74 | 75 | 76 | public function mock($class) 77 | { 78 | $mock = Mockery::mock($class); 79 | 80 | return $mock; 81 | } 82 | 83 | public function tearDown() 84 | { 85 | Mockery::close(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/MicheleAngioni/Support/Semaphores/SemaphoresManager.php: -------------------------------------------------------------------------------- 1 | cache = $cache; 38 | $this->keyManager = $keyManager; 39 | } 40 | 41 | /** 42 | * Return the locking time. 43 | * 44 | * @return int 45 | */ 46 | public function getLockingTime(): int 47 | { 48 | return $this->lockingTime; 49 | } 50 | 51 | /** 52 | * Set the locking time. 53 | * 54 | * @param int $seconds 55 | * @return int 56 | */ 57 | public function setLockingTime(int $seconds): int 58 | { 59 | return $this->lockingTime = $seconds; 60 | } 61 | 62 | /** 63 | * Return the key of the semaphore connected with input $id and $section. 64 | * 65 | * @param string|bool $id 66 | * @param string $section 67 | * 68 | * @return string 69 | */ 70 | public function getSemaphoreKey($id, string $section) 71 | { 72 | return $this->keyManager->getKey($id, [], $section); 73 | } 74 | 75 | /** 76 | * Check if a semaphore is locked or free. Return 1 if locked or 0 if unlocked. 77 | * 78 | * @param string $id 79 | * @param string $section 80 | * 81 | * @return int 82 | */ 83 | public function checkIfSemaphoreIsLocked($id, string $section): int 84 | { 85 | $key = $this->getSemaphoreKey($id, $section); 86 | 87 | if (!$this->cache->has($key, ['semaphore', $section])) { 88 | return 0; 89 | } 90 | 91 | return (int)$this->cache->get($key, ['semaphore', $section]); 92 | } 93 | 94 | /** 95 | * Lock the semaphore with input key and section. 96 | * 97 | * @param string $id 98 | * @param string $section 99 | */ 100 | public function lockSemaphore($id, string $section) 101 | { 102 | $key = $this->getSemaphoreKey($id, $section); 103 | 104 | $this->cache->put($key, 1, ['semaphore', $section], $this->lockingTime); 105 | } 106 | 107 | /** 108 | * Unlock the semaphore with input key and section. 109 | * 110 | * @param string $id 111 | * @param string $section 112 | */ 113 | public function unlockSemaphore($id, string $section) 114 | { 115 | $key = $this->getSemaphoreKey($id, $section); 116 | 117 | $this->cache->put($key, 0, ['semaphore', $section], $this->lockingTime); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /tests/HelpersTest.php: -------------------------------------------------------------------------------- 1 | assertFalse(H::isInt($callable)); 21 | $this->assertFalse(H::isInt($object)); 22 | $this->assertFalse(H::isInt(['value'])); 23 | $this->assertFalse(H::isInt(['key' => 'value'])); 24 | $this->assertFalse(H::isInt(['key' => 10])); 25 | 26 | $this->assertTrue(H::isInt(3)); 27 | $this->assertTrue(H::isInt('3')); 28 | $this->assertFalse(H::isInt(3.2)); 29 | $this->assertFalse(H::isInt('3.2')); 30 | $this->assertFalse(H::isInt('a')); 31 | $this->assertFalse(H::isInt('3a')); 32 | $this->assertFalse(H::isInt('__')); 33 | } 34 | 35 | public function testRandInArray() 36 | { 37 | $array = ['1', 1, 2, 3, '5']; 38 | 39 | $value = H::randInArray($array); 40 | 41 | $this->assertTrue(in_array($value, $array)); 42 | } 43 | 44 | public function testCheckDate() 45 | { 46 | date_default_timezone_set('UTC'); 47 | $this->assertTrue(H::checkDate('2014-12-24')); 48 | $this->assertTrue(H::checkDate('2014-24-12', 'Y-d-m')); 49 | $this->assertFalse(H::checkDate('2014-12-32')); 50 | $this->assertFalse(H::checkDate('2014-24-12')); 51 | $this->assertFalse(H::checkDate('aa')); 52 | $this->assertFalse(H::checkDate('2014--12-24')); 53 | } 54 | 55 | public function testCheckDateTime() 56 | { 57 | date_default_timezone_set('UTC'); 58 | $this->assertTrue(H::checkDateTime('2014-12-24 18:24:02')); 59 | $this->assertFalse(H::checkDateTime('2014-12-24 18:24:61')); 60 | $this->assertFalse(H::checkDateTime('2014-12-24')); 61 | $this->assertFalse(H::checkDateTime('2014-12-32')); 62 | $this->assertFalse(H::checkDateTime('2014-24-12')); 63 | $this->assertFalse(H::checkDateTime('aa')); 64 | $this->assertFalse(H::checkDateTime('2014--12-24')); 65 | } 66 | 67 | public function testCheckSplitDates() 68 | { 69 | date_default_timezone_set('UTC'); 70 | $dates = H::splitDates('2014-12-24', '2014-12-26'); 71 | $this->assertEquals(3, count($dates)); 72 | 73 | $dates = H::splitDates('2014-12-24', '2014-12-24'); 74 | $this->assertEquals(1, count($dates)); 75 | 76 | $this->assertNull(H::splitDates('2014-12-26', '2014-12-24', 1)); 77 | $this->assertNull(H::splitDates('2014-12-24', '2014-12-26', 1)); 78 | $this->assertNull(H::splitDates('2014-12-24', '2014-12-26x')); 79 | } 80 | 81 | public function testDaysBetweenDates() 82 | { 83 | date_default_timezone_set('UTC'); 84 | $this->assertEquals(2, H::daysBetweenDates('2014-12-24', '2014-12-26')); 85 | $this->assertEquals(0, H::daysBetweenDates('2014-12-24', '2014-12-24')); 86 | $this->assertNull(H::daysBetweenDates('2014-12-24', '2014-12-23')); 87 | $this->assertNull(H::daysBetweenDates('2014-12-23', '2014-12-24x')); 88 | } 89 | 90 | public function testGetUniqueRandomValues() 91 | { 92 | $numbers = H::getUniqueRandomValues(1, 100, 4); 93 | $this->assertEquals(4, count($numbers)); 94 | 95 | $this->assertNotEquals($numbers[0], $numbers[1]); 96 | $this->assertNotEquals($numbers[0], $numbers[2]); 97 | $this->assertNotEquals($numbers[0], $numbers[3]); 98 | $this->assertNotEquals($numbers[1], $numbers[2]); 99 | $this->assertNotEquals($numbers[1], $numbers[3]); 100 | $this->assertNotEquals($numbers[2], $numbers[3]); 101 | $this->assertInternalType('int', $numbers[0]); 102 | $this->assertInternalType('int', $numbers[1]); 103 | $this->assertInternalType('int', $numbers[2]); 104 | $this->assertInternalType('int', $numbers[3]); 105 | } 106 | 107 | public function tearDown() 108 | { 109 | Mockery::close(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/MicheleAngioni/Support/Cache/KeyManager.php: -------------------------------------------------------------------------------- 1 | getString($id, $array, $section, $modelClass); 21 | 22 | return md5($string); 23 | } 24 | 25 | /** 26 | * Return the ready-to-be-encrypted-string key generated by using $id and $with array, 27 | * as well $section and $modelClass inputs. 28 | * 29 | * @param string|bool $id 30 | * @param array $array 31 | * @param string $section 32 | * @param string $modelClass 33 | * 34 | * @return string 35 | */ 36 | protected function getString($id = false, array $array = [], $section, $modelClass = __CLASS__) 37 | { 38 | $string = $section . $modelClass; 39 | 40 | if ($id) { 41 | $string = $string . 'id' . $id; 42 | } 43 | 44 | if ($array) { 45 | foreach ($array as $key => $value) { 46 | $string = $string . $key; 47 | } 48 | } 49 | 50 | return $string; 51 | } 52 | 53 | /** 54 | * Return Cache tags generated by using $id and $with array, as well $section and $modelClass inputs. 55 | * 56 | * @param bool|int $id 57 | * @param array $array 58 | * @param string $section 59 | * @param string $modelClass 60 | * 61 | * @return array 62 | */ 63 | public function getTags($id = false, array $array = [], $section, $modelClass = __CLASS__) 64 | { 65 | $tags = [$section, $modelClass]; 66 | 67 | if ($id) { 68 | $tags[] = $modelClass . 'id' . $id; 69 | } 70 | 71 | if ($array) { 72 | foreach ($array as $relation) { 73 | $tags[] = $relation; 74 | } 75 | } 76 | 77 | return $tags; 78 | } 79 | 80 | /** 81 | * Return a method-customized Cache key generated by using $id and $with array, 82 | * as well $section and $modelClass inputs. 83 | * If not customName is provided, the name of the calling method will be used. 84 | * 85 | * @param string|bool $customName 86 | * @param string|bool $id 87 | * @param array $array 88 | * @param string $section 89 | * @param string $modelClass 90 | * 91 | * @return string 92 | */ 93 | public function getCustomMethodKey( 94 | $customName = false, 95 | $id = false, 96 | array $array = [], 97 | $section, 98 | $modelClass = __CLASS__ 99 | ) { 100 | if (!$customName) { 101 | $customName = debug_backtrace()[2]['function']; 102 | } 103 | 104 | $string = $this->getString($id, $array, $section, $modelClass) . $modelClass . $customName; 105 | 106 | return md5($string); 107 | } 108 | 109 | /** 110 | * Return Cache tags generated by using $id and $with array, as well $section and $modelClass inputs. 111 | * It has an additional method-customized tag. 112 | * If not customName is provided, the name of the calling method will be used. 113 | * 114 | * @param string|bool $customName 115 | * @param string|bool $id 116 | * @param array $array 117 | * @param string $section 118 | * @param string $modelClass 119 | * 120 | * @return array 121 | */ 122 | public function getCustomMethodTags( 123 | $customName = false, 124 | $id = false, 125 | array $array = [], 126 | $section, 127 | $modelClass = __CLASS__ 128 | ) { 129 | if (!$customName) { 130 | $customName = debug_backtrace()[2]['function']; 131 | } 132 | 133 | $tags = $this->getTags($id, $array, $section, $modelClass); 134 | $tags[] = $modelClass . $customName; 135 | 136 | return $tags; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/MicheleAngioni/Support/Cache/AbstractCacheSimpleXMLRepositoryDecorator.php: -------------------------------------------------------------------------------- 1 | xmlRepo = $repo; 51 | $this->cache = $cache; 52 | 53 | $this->xmlFilePath = $this->xmlRepo->getFilePath(); 54 | } 55 | 56 | 57 | public function __call($method, $parameters) 58 | { 59 | if (method_exists($this, $method)) { 60 | return call_user_func_array([$this, $method], $parameters); 61 | } 62 | 63 | if (method_exists($this->xmlRepo, $method)) { 64 | return call_user_func_array([$this->xmlRepo, $method], $parameters); 65 | } 66 | 67 | throw new BadMethodCallException('RuntimeException in ' . __METHOD__ . ' at line ' . __LINE__ . ': Called method does NOT exist.'); 68 | } 69 | 70 | /** 71 | * Return the xml file as an instance of SimpleXML from the Cache. 72 | * If the file is not present in the Cache, call loadFile(). 73 | * 74 | * @return \SimpleXMLElement 75 | */ 76 | public function getFile() 77 | { 78 | $key = $this->getKey(); 79 | 80 | $tags = $this->getTags(); 81 | 82 | if (!$this->cache->has($key, $tags)) { 83 | $this->loadFile(); 84 | } 85 | 86 | return simplexml_load_string($this->cache->get($key, $tags)); 87 | } 88 | 89 | /** 90 | * If not already loaded, load the file into the Cache. 91 | * Since SimpleXML objects (as many others) can't be serialized, loaded xml is formatted as XML before putting in the Cache. 92 | */ 93 | public function loadFile() 94 | { 95 | $key = $this->getKey(); 96 | 97 | $tags = $this->getTags(); 98 | 99 | if (!$this->cache->has($key, $tags)) { 100 | $this->xmlRepo->loadFile(); 101 | 102 | $XMLFile = $this->xmlRepo->getFile()->asXML(); 103 | 104 | $this->cache->put($key, $XMLFile, $tags, $this->seconds); 105 | } 106 | } 107 | 108 | /** 109 | * Return Cache key generated by using the current active class, id and $with array 110 | * 111 | * @return string 112 | */ 113 | protected function getKey() 114 | { 115 | $string = $this->getString(); 116 | 117 | return md5($string); 118 | } 119 | 120 | /** 121 | * Return the ready-to-be-encrypted-string key generated by using the current active class, id and $with array 122 | * 123 | * @return string 124 | */ 125 | protected function getString() 126 | { 127 | $string = $this->section . $this->xmlFilePath; 128 | 129 | return $string; 130 | } 131 | 132 | /** 133 | * Return Cache tags generated by using the current active class, id and with array 134 | * 135 | * @return array 136 | */ 137 | protected function getTags() 138 | { 139 | $tags = [$this->section, $this->xmlFilePath]; 140 | 141 | return $tags; 142 | } 143 | 144 | /** 145 | * Return a method-customized Cache key generated by using the current active class, id and $with array. 146 | * If not customName is provided, the name of the calling method will be used. 147 | * 148 | * @param string|bool $customName 149 | * 150 | * @return string 151 | */ 152 | public function getCustomMethodKey($customName = false) 153 | { 154 | if (!$customName) { 155 | $customName = debug_backtrace()[1]['function']; 156 | } 157 | 158 | $string = $this->getString() . $this->xmlFilePath . $customName; 159 | 160 | return md5($string); 161 | } 162 | 163 | /** 164 | * Return Cache tags generated by using the current active class, id and with array. 165 | * It has an additional method-customized tag. 166 | * If no customName is provided, the name of the calling method will be used. 167 | * 168 | * @param string|bool $customName 169 | * 170 | * @return array 171 | */ 172 | public function getCustomMethodTags($customName = false) 173 | { 174 | if (!$customName) { 175 | $customName = debug_backtrace()[1]['function']; 176 | } 177 | 178 | $tags = $this->getTags(); 179 | $tags[] = $this->xmlFilePath . $customName; 180 | 181 | return $tags; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/MicheleAngioni/Support/Cache/AbstractCacheRepositoryDecorator.php: -------------------------------------------------------------------------------- 1 | repo = $repo; 54 | $this->cache = $cache; 55 | $this->keyManager = $keyManager; 56 | 57 | $reflection = new \ReflectionClass($repo); 58 | $constructor = $reflection->getConstructor(); 59 | $this->modelClass = $constructor->getParameters()[0]->getClass()->name; 60 | } 61 | 62 | public function __call($method, $parameters) 63 | { 64 | if (method_exists($this, $method)) { 65 | return call_user_func_array([$this, $method], $parameters); 66 | } 67 | 68 | if (method_exists($this->repo, $method)) { 69 | return call_user_func_array([$this->repo, $method], $parameters); 70 | } 71 | 72 | throw new BadMethodCallException('RuntimeException in ' . __METHOD__ . ' at line ' . __LINE__ . ": Called not existent method $method by class " . get_class($this)); 73 | } 74 | 75 | /** 76 | * All 77 | * 78 | * @return \Illuminate\Database\Eloquent\Collection 79 | */ 80 | public function all() 81 | { 82 | $key = $this->getKey(); 83 | 84 | $tags = $this->getTags(); 85 | 86 | if ($this->cache->has($key, $tags)) { 87 | return $this->cache->get($key, $tags); 88 | } 89 | 90 | $collection = $this->repo->all(); 91 | 92 | $this->cache->put($key, $collection, $tags); 93 | 94 | return $collection; 95 | } 96 | 97 | /** 98 | * Find 99 | * 100 | * @param string $id 101 | * @param array $with 102 | * 103 | * @return \Illuminate\Database\Eloquent\Model 104 | */ 105 | public function find($id, array $with = []) 106 | { 107 | $key = $this->getKey($id, $with); 108 | 109 | $tags = $this->getTags($id, $with); 110 | 111 | if ($this->cache->has($key, $tags)) { 112 | return $this->cache->get($key, $tags); 113 | } 114 | 115 | $model = $this->repo->find($id, $with); 116 | 117 | $this->cache->put($key, $model, $tags); 118 | 119 | return $model; 120 | } 121 | 122 | /** 123 | * Find or throws exception 124 | * 125 | * @param string $id 126 | * @param array $with 127 | * 128 | * @return \Illuminate\Database\Eloquent\Model 129 | */ 130 | public function findOrFail($id, array $with = []) 131 | { 132 | $key = $this->getKey($id, $with); 133 | 134 | $tags = $this->getTags($id, $with); 135 | 136 | if ($this->cache->has($key, $tags)) { 137 | return $this->cache->get($key, $tags); 138 | } 139 | 140 | $model = $this->repo->findOrFail($id, $with); 141 | 142 | $this->cache->put($key, $model, $tags); 143 | 144 | return $model; 145 | } 146 | 147 | /** 148 | * Call the Key Manager to get the Cache key. 149 | * 150 | * @param string|bool $id 151 | * @param array $array 152 | * 153 | * @return string 154 | */ 155 | public function getKey($id = false, array $array = []) 156 | { 157 | return $this->keyManager->getKey($id, $array, $this->section, $this->modelClass); 158 | } 159 | 160 | /** 161 | * Call the Key Manager to get the Cache tags. 162 | * 163 | * @param string|bool $id 164 | * @param array $array 165 | * 166 | * @return array 167 | */ 168 | public function getTags($id = false, array $array = []) 169 | { 170 | return $this->keyManager->getTags($id, $array, $this->section, $this->modelClass); 171 | } 172 | 173 | /** 174 | * Call the Key Manager method to the Custom Method Key. 175 | * 176 | * @param string|bool $customName 177 | * @param string|bool $id 178 | * @param array $array 179 | * 180 | * @return string 181 | */ 182 | public function getCustomMethodKey($customName = false, $id = false, array $array = []) 183 | { 184 | return $this->keyManager->getCustomMethodKey($customName, $id, $array, $this->section, $this->modelClass); 185 | } 186 | 187 | /** 188 | * Call the Key Manager method to get the Custom Method Tags. 189 | * 190 | * @param string|bool $customName 191 | * @param string|bool $id 192 | * @param array $array 193 | * 194 | * @return array 195 | */ 196 | public function getCustomMethodTags($customName = false, $id = false, array $array = []) 197 | { 198 | return $this->keyManager->getCustomMethodTags($customName, $id, $array, $this->section, $this->modelClass); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/MicheleAngioni/Support/Helpers.php: -------------------------------------------------------------------------------- 1 | $max) { 40 | return false; 41 | } 42 | } else { 43 | return false; 44 | } 45 | } 46 | 47 | return is_numeric($int) && (int)$int == $int; 48 | } 49 | 50 | /** 51 | * Return a random value out of an array 52 | * 53 | * @param array $array 54 | * @return mixed 55 | */ 56 | static public function randInArray(array $array) 57 | { 58 | return $array[mt_rand(0, count($array) - 1)]; 59 | } 60 | 61 | /** 62 | * Check date validity. Return true on success or false on failure. 63 | * 64 | * @param string $date 65 | * @param string $format = 'Y-m-d' 66 | * 67 | * @return bool 68 | */ 69 | static public function checkDate(string $date, string $format = 'Y-m-d'): bool 70 | { 71 | $d = DateTime::createFromFormat($format, $date); 72 | 73 | return $d && $d->format($format) == $date; 74 | } 75 | 76 | /** 77 | * Check datetime 'Y-m-d H:i:s' validity. Returns true if ok or false if it fails. 78 | * 79 | * @param string $datetime 80 | * @return bool 81 | */ 82 | static public function checkDatetime(string $datetime): bool 83 | { 84 | return self::checkDate($datetime, 'Y-m-d H:i:s'); 85 | } 86 | 87 | /** 88 | * Split two 'Y-m-d'-format dates into an array of dates. Returns null on failure. 89 | * $firstDate must be < than $secondDate 90 | * Third optional parameter indicates max days difference allowed (0 = no limits). 91 | * 92 | * @param string $firstDate 93 | * @param string $secondDate 94 | * @param int $maxDifference = 0 95 | * 96 | * @return array 97 | */ 98 | static public function splitDates(string $firstDate, string $secondDate, int $maxDifference = 0):? array 99 | { 100 | if (!self::checkDate($firstDate) || !self::checkDate($secondDate)) { 101 | return null; 102 | } 103 | 104 | if (!self::isInt($maxDifference, 0)) { 105 | return null; 106 | } 107 | 108 | $date1 = new DateTime($firstDate); 109 | $date2 = new DateTime($secondDate); 110 | $interval = $date1->diff($date2, false); 111 | 112 | if ((int)$interval->format('%R%a') < 0) { 113 | return null; 114 | } 115 | 116 | if ($maxDifference != 0) { 117 | if ((int)$interval->format('%R%a') > $maxDifference) { 118 | return null; 119 | } 120 | } 121 | 122 | list($year, $month, $day) = array_pad(explode("-", $firstDate), 3, 0); 123 | 124 | $i = 0; 125 | $newDate = $firstDate; 126 | $dates = []; 127 | 128 | while ($newDate <= $secondDate) { 129 | $dates[] = $newDate; 130 | $i++; 131 | $newDate = date("Y-m-d", mktime(0, 0, 0, $month, $day + $i, $year)); 132 | } 133 | 134 | return $dates; 135 | } 136 | 137 | /** 138 | * Return the number of days between the two input 'Y-m-d' or 'Y-m-d X' (X is some text) dates. 139 | * $date2 must be >= than $date1. 140 | * Returns null on failure. 141 | * 142 | * @param string $date1 143 | * @param string $date2 144 | * 145 | * @return int|null 146 | */ 147 | static public function daysBetweenDates(string $date1, string $date2):? int 148 | { 149 | // If input dates have datetime 'Y-m-d X' format, take only the date part 150 | list($d1) = array_pad(explode(' ', $date1), 1, 0); 151 | list($d2) = array_pad(explode(' ', $date2), 1, 0); 152 | 153 | if (!self::checkDate($d1) || !self::checkDate($d2)) { 154 | return null; 155 | } 156 | 157 | if (!($dates = self::splitDates($d1, $d2))) { 158 | return null; 159 | } 160 | 161 | return (count($dates) - 1); 162 | } 163 | 164 | 165 | // <<<--- PSEUDO-RANDOM NUMBERS METHODS --->>> 166 | 167 | /** 168 | * Return $quantity UNIQUE random value between $min and $max. 169 | * Return null on failure. 170 | * 171 | * @param int $min = 0 172 | * @param int $max 173 | * @param int $quantity = 1 174 | * @throws \Exception 175 | * 176 | * @return int[]|null 177 | */ 178 | static public function getUniqueRandomValues(int $min = 0, int $max, int $quantity = 1):? array 179 | { 180 | if ($min > $max || $quantity < 0) { 181 | return null; 182 | } 183 | 184 | $rand = []; 185 | 186 | while (count($rand) < $quantity) { 187 | $r = random_int($min, $max); 188 | 189 | if (!in_array($r, $rand)) { 190 | $rand[] = $r; 191 | } 192 | } 193 | 194 | return $rand; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/MicheleAngioni/Support/Repos/RepositoryInterface.php: -------------------------------------------------------------------------------- 1 | make($with); 20 | 21 | return $query->get(); 22 | } 23 | 24 | /** 25 | * Make a new instance of the entity to query on. 26 | * 27 | * @param array $with 28 | */ 29 | public function make(array $with = []) 30 | { 31 | return $this->model->with($with); 32 | } 33 | 34 | 35 | // <--- QUERYING METHODS ---> 36 | 37 | /** 38 | * Find a specific record. 39 | * Return null if not found. 40 | * 41 | * @param int $id 42 | * @param array $with 43 | * 44 | * @return mixed 45 | */ 46 | public function find($id, array $with = []) 47 | { 48 | $query = $this->make($with); 49 | 50 | return $query->find($id); 51 | } 52 | 53 | /** 54 | * Find a specific record. 55 | * Throws exception if not found. 56 | * 57 | * @param $id 58 | * @param array $with 59 | * 60 | * @throws \Illuminate\Database\Eloquent\ModelNotFoundException 61 | * 62 | * @return mixed 63 | */ 64 | public function findOrFail($id, array $with = []) 65 | { 66 | $query = $this->make($with); 67 | 68 | return $query->findOrFail($id); 69 | } 70 | 71 | /** 72 | * Return the first record of the table. 73 | * Return null if no record is found. 74 | * 75 | * @return mixed 76 | */ 77 | public function first() 78 | { 79 | $query = $this->make(); 80 | 81 | return $query->first(); 82 | } 83 | 84 | /** 85 | * Return the first record. 86 | * Throws exception if no record is found. 87 | * 88 | * @throws \Illuminate\Database\Eloquent\ModelNotFoundException 89 | * @return mixed 90 | */ 91 | public function firstOrFail() 92 | { 93 | $query = $this->make(); 94 | 95 | return $query->firstOrFail(); 96 | } 97 | 98 | /** 99 | * Return the first record querying input parameters. 100 | * Return null if no record is found. 101 | * 102 | * @param array $where 103 | * @param array $with 104 | * 105 | * @return mixed 106 | */ 107 | public function firstBy(array $where = [], array $with = []) 108 | { 109 | $query = $this->make($with); 110 | 111 | $query = $this->applyWhere($query, $where); 112 | 113 | return $query->first(); 114 | } 115 | 116 | /** 117 | * Return the first record querying input parameters. 118 | * Throws exception if no record is found. 119 | * 120 | * @param array $where 121 | * @param array $with 122 | * 123 | * @throws \Illuminate\Database\Eloquent\ModelNotFoundException 124 | * 125 | * @return mixed 126 | */ 127 | public function firstOrFailBy(array $where = [], array $with = []) 128 | { 129 | $query = $this->make($with); 130 | 131 | $query = $this->applyWhere($query, $where); 132 | 133 | return $query->firstOrFail(); 134 | } 135 | 136 | /** 137 | * Return records querying input parameters. 138 | * 139 | * @param array $where 140 | * @param array $with 141 | * 142 | * @return Collection 143 | */ 144 | public function getBy(array $where = [], array $with = []) 145 | { 146 | $query = $this->make($with); 147 | 148 | $query = $this->applyWhere($query, $where); 149 | 150 | return $query->get(); 151 | } 152 | 153 | /** 154 | * Return the first $limit records querying input parameters. 155 | * 156 | * @param int $limit 157 | * @param array $where 158 | * @param array $with 159 | * 160 | * @return Collection 161 | */ 162 | public function getByLimit(int $limit, array $where = [], array $with = []) 163 | { 164 | $query = $this->make($with); 165 | 166 | $query = $this->applyWhere($query, $where); 167 | 168 | return $query->take($limit)->get(); 169 | } 170 | 171 | /** 172 | * Return the first ordered $limit records querying input parameters. 173 | * $limit = 0 means no limits. 174 | * 175 | * @param string $orderBy 176 | * @param array $where 177 | * @param array $with 178 | * @param string $order 179 | * @param int $limit 180 | * 181 | * @return Collection 182 | */ 183 | public function getByOrder(string $orderBy, array $where = [], array $with = [], string $order = 'desc', int $limit = 0) 184 | { 185 | $query = $this->make($with); 186 | 187 | $query = $this->applyWhere($query, $where); 188 | 189 | $query = $query->orderBy($orderBy, $order); 190 | 191 | if ($limit) { 192 | $query = $query->take($limit); 193 | } 194 | 195 | return $query->get(); 196 | } 197 | 198 | /** 199 | * Return the first ordered $limit records querying input parameters. 200 | * $limit = 0 means no limits. 201 | * 202 | * @param string|int $whereInKey 203 | * @param array $whereIn 204 | * @param array $with 205 | * @param string|null $orderBy 206 | * @param string $order 207 | * @param int $limit 208 | * 209 | * @return Collection 210 | */ 211 | public function getIn( 212 | $whereInKey, 213 | array $whereIn = [], 214 | array $with = [], 215 | string $orderBy = null, 216 | string $order = 'desc', 217 | int $limit = 0 218 | ) { 219 | $query = $this->make($with); 220 | 221 | $query = $query->whereIn($whereInKey, $whereIn); 222 | 223 | if ($orderBy) { 224 | $query = $query->orderBy($orderBy, $order); 225 | } 226 | 227 | if ($limit) { 228 | $query = $query->take($limit); 229 | } 230 | 231 | return $query->get(); 232 | } 233 | 234 | /** 235 | * Return the first ordered $limit records querying input parameters. 236 | * $limit = 0 means no limits. 237 | * 238 | * @param string|int $whereNotInKey 239 | * @param array $whereNotIn 240 | * @param array $with 241 | * @param string|null $orderBy 242 | * @param string $order 243 | * @param int $limit 244 | * 245 | * @return Collection 246 | */ 247 | public function getNotIn( 248 | $whereNotInKey, 249 | array $whereNotIn = [], 250 | array $with = [], 251 | string $orderBy = null, 252 | string $order = 'desc', 253 | int $limit = 0 254 | ) { 255 | $query = $this->make($with); 256 | 257 | $query = $query->whereNotIn($whereNotInKey, $whereNotIn); 258 | 259 | if ($orderBy) { 260 | $query = $query->orderBy($orderBy, $order); 261 | } 262 | 263 | if ($limit) { 264 | $query = $query->take($limit); 265 | } 266 | 267 | return $query->get(); 268 | } 269 | 270 | /** 271 | * Return all results that have a required relationship. 272 | * 273 | * @param string $relation 274 | * @param array $where 275 | * @param array $with 276 | * @param int $hasAtLeast = 1 277 | * 278 | * @return Collection 279 | */ 280 | public function getHas(string $relation, array $where = [], array $with = [], int $hasAtLeast = 1) 281 | { 282 | $query = $this->make($with); 283 | 284 | $query = $this->applyWhere($query, $where); 285 | 286 | return $query->has($relation, '>=', $hasAtLeast)->get(); 287 | } 288 | 289 | /** 290 | * Return the first result that has a required relationship. 291 | * Return null if no record is found. 292 | * 293 | * @param string $relation 294 | * @param array $where 295 | * @param array $with 296 | * @param int $hasAtLeast = 1 297 | * 298 | * @return mixed 299 | */ 300 | public function hasFirst(string $relation, array $where = [], array $with = [], int $hasAtLeast = 1) 301 | { 302 | $query = $this->make($with); 303 | 304 | $query = $this->applyWhere($query, $where); 305 | 306 | return $query->has($relation, '>=', $hasAtLeast)->first(); 307 | } 308 | 309 | /** 310 | * Return the first result that have a required relationship. 311 | * Throws exception if no record is found. 312 | * 313 | * @param string $relation 314 | * @param array $where 315 | * @param array $with 316 | * @param int $hasAtLeast = 1 317 | * 318 | * @throws \Illuminate\Database\Eloquent\ModelNotFoundException 319 | * 320 | * @return Collection 321 | */ 322 | public function hasFirstOrFail(string $relation, array $where = [], array $with = [], int $hasAtLeast = 1) 323 | { 324 | $query = $this->make($with); 325 | 326 | $query = $this->applyWhere($query, $where); 327 | 328 | return $query->has($relation, '>=', $hasAtLeast)->firstOrFail(); 329 | } 330 | 331 | /** 332 | * Return all results that have a required relationship with input constraints. 333 | * 334 | * @param string $relation 335 | * @param array $where 336 | * @param array $whereHas 337 | * @param array $with 338 | * 339 | * @return Collection 340 | */ 341 | public function whereHas($relation, array $where = [], array $whereHas = [], array $with = []) 342 | { 343 | $query = $this->make($with); 344 | 345 | $query = $this->applyWhere($query, $where); 346 | 347 | $query = $query->whereHas($relation, function ($q) use ($whereHas) { 348 | $this->applyWhere($q, $whereHas); 349 | }); 350 | 351 | return $query->get(); 352 | } 353 | 354 | /** 355 | * Get ordered results by Page. 356 | * 357 | * @param int $page 358 | * @param int $limit 359 | * @param array $where 360 | * @param array $with 361 | * @param string|null $orderBy 362 | * @param string $order 363 | * 364 | * @return Collection 365 | */ 366 | public function getByPage( 367 | int $page = 1, 368 | int $limit = 10, 369 | array $where = [], 370 | array $with = [], 371 | string $orderBy = null, 372 | string $order = 'desc' 373 | ) { 374 | $query = $this->make($with); 375 | 376 | $query = $this->applyWhere($query, $where); 377 | 378 | if ($orderBy) { 379 | $query = $query->orderBy($orderBy, $order); 380 | } 381 | 382 | return $query->skip($limit * ($page - 1)) 383 | ->take($limit) 384 | ->get(); 385 | } 386 | 387 | 388 | // <--- CREATING / UPDATING / DELETING METHODS ---> 389 | 390 | /** 391 | * Create a collection of new records. 392 | * 393 | * @param array $collection 394 | * 395 | * @return mixed 396 | */ 397 | public function insert(array $collection) 398 | { 399 | foreach ($collection as $key => $inputs) { 400 | $collection[$key] = $this->purifyInputs($inputs); 401 | } 402 | 403 | return $this->model->insert($collection); 404 | } 405 | 406 | /** 407 | * Create a new record. 408 | * 409 | * @param array $inputs 410 | * 411 | * @return mixed 412 | */ 413 | public function create(array $inputs = []) 414 | { 415 | $inputs = $this->purifyInputs($inputs); 416 | 417 | return $this->model->create($inputs); 418 | } 419 | 420 | /** 421 | * Update all records. 422 | * 423 | * @param array $inputs 424 | * 425 | * @return mixed 426 | */ 427 | public function update(array $inputs) 428 | { 429 | $inputs = $this->purifyInputs($inputs); 430 | 431 | return $this->model->update($inputs); 432 | } 433 | 434 | /** 435 | * Update an existing record, retrieved by id. 436 | * 437 | * @param int $id 438 | * @param array $inputs 439 | * 440 | * @return mixed 441 | */ 442 | public function updateById($id, array $inputs) 443 | { 444 | $inputs = $this->purifyInputs($inputs); 445 | 446 | $model = $this->model->findOrFail($id); 447 | 448 | return $model->update($inputs); 449 | } 450 | 451 | /** 452 | * Update all records matching input parameters. 453 | * 454 | * @param array $where 455 | * @param array $inputs 456 | * 457 | * @return mixed 458 | */ 459 | public function updateBy(array $where, array $inputs) 460 | { 461 | $inputs = $this->purifyInputs($inputs); 462 | 463 | $query = $this->make(); 464 | 465 | $query = $this->applyWhere($query, $where); 466 | 467 | return $query->update($inputs); 468 | } 469 | 470 | /** 471 | * Update the record matching input parameters. 472 | * If no record is found, create a new one. 473 | * 474 | * @param array $where 475 | * @param array $inputs 476 | * 477 | * @return mixed 478 | */ 479 | public function updateOrCreateBy(array $where, array $inputs = []) 480 | { 481 | $inputs = $this->purifyInputs($inputs); 482 | 483 | $query = $this->make(); 484 | 485 | $query = $this->applyWhere($query, $where); 486 | 487 | $model = $query->first(); 488 | 489 | if ($model) { 490 | $model->update($inputs); 491 | 492 | return $model; 493 | } else { 494 | return $this->model->create($inputs); 495 | } 496 | } 497 | 498 | /** 499 | * Delete input record. 500 | * 501 | * @param int $id 502 | * 503 | * @return mixed 504 | */ 505 | public function destroy($id) 506 | { 507 | $model = $this->model->findOrFail($id); 508 | 509 | return $model->delete(); 510 | } 511 | 512 | /** 513 | * Retrieve and delete the first record matching input parameters. 514 | * Throws exception if no record is found. 515 | * 516 | * @param array $where 517 | * 518 | * @throws \Illuminate\Database\Eloquent\ModelNotFoundException 519 | * 520 | * @return mixed 521 | */ 522 | public function destroyFirstBy(array $where) 523 | { 524 | $model = $this->firstOrFailBy($where); 525 | 526 | return $model->delete(); 527 | } 528 | 529 | /** 530 | * Retrieve and delete the all records matching input parameters. 531 | * 532 | * @param array $where 533 | * 534 | * @return mixed 535 | */ 536 | public function destroyBy(array $where) 537 | { 538 | $query = $this->make(); 539 | 540 | $query = $this->applyWhere($query, $where); 541 | 542 | return $query->delete(); 543 | } 544 | 545 | /** 546 | * Truncate the table. 547 | * 548 | * @return mixed 549 | */ 550 | public function truncate() 551 | { 552 | return $this->model->truncate(); 553 | } 554 | 555 | 556 | // <--- COUNT METHODS ---> 557 | 558 | /** 559 | * Count the number of records. 560 | * 561 | * @return int 562 | */ 563 | public function count() 564 | { 565 | return $this->model->count(); 566 | } 567 | 568 | /** 569 | * Count the number of records matching input parameters. 570 | * 571 | * @param array $where 572 | * @return int 573 | */ 574 | public function countBy(array $where = []) 575 | { 576 | $query = $this->make(); 577 | 578 | $query = $this->applyWhere($query, $where); 579 | 580 | return $query->count(); 581 | } 582 | 583 | /** 584 | * Count all records that have a required relationship and matching input parameters.. 585 | * 586 | * @param string $relation 587 | * @param array $where 588 | * @param array $whereHas 589 | * 590 | * @return int 591 | */ 592 | public function countWhereHas($relation, array $where = [], array $whereHas = []) 593 | { 594 | $query = $this->make(); 595 | 596 | $query = $this->applyWhere($query, $where); 597 | 598 | $query = $query->whereHas($relation, function ($q) use ($whereHas) { 599 | $this->applyWhere($q, $whereHas); 600 | }); 601 | 602 | return $query->count(); 603 | } 604 | 605 | 606 | // <--- INTERNALLY USED METHODS ---> 607 | 608 | /** 609 | * Apply the where clauses to input query. 610 | * $where can have the format ['key' => 'value'] or ['key' => [, 'value']] 611 | * 612 | * @param Builder $query 613 | * @param array $where 614 | * 615 | * @return Builder 616 | */ 617 | protected function applyWhere(Builder $query, array $where) 618 | { 619 | foreach ($where as $key => $value) { 620 | if (is_null($value)) { 621 | $query = $query->whereNull($key); 622 | } else { 623 | if (is_array($value)) { 624 | $query = $query->where($key, $value[0], $value[1]); 625 | } else { 626 | $query = $query->where($key, '=', $value); 627 | } 628 | } 629 | } 630 | 631 | return $query; 632 | } 633 | 634 | /** 635 | * Remove keys from the $inputs array beginning with '_' . 636 | * 637 | * @param array $inputs 638 | * 639 | * @return array 640 | */ 641 | protected function purifyInputs(array $inputs) 642 | { 643 | foreach ($inputs as $key => $input) { 644 | if ($key[0] === '_') { 645 | unset($inputs[$key]); 646 | } 647 | } 648 | 649 | return $inputs; 650 | } 651 | } 652 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SUPPORT 2 | 3 | [![License](https://poser.pugx.org/michele-angioni/support/license.svg)](https://packagist.org/packages/michele-angioni/support) 4 | [![Latest Stable Version](https://poser.pugx.org/michele-angioni/support/v/stable)](https://packagist.org/packages/michele-angioni/support) 5 | [![Build Status](https://travis-ci.org/micheleangioni/support.svg)](https://travis-ci.org/micheleangioni/support) 6 | [![SensioLabsInsight](https://insight.sensiolabs.com/projects/8af14b44-cf82-4028-8a89-438fcb133ba6/small.png)](https://insight.sensiolabs.com/projects/8af14b44-cf82-4028-8a89-438fcb133ba6) 7 | 8 | ## Introduction 9 | 10 | Support consists of a series of useful classes to ease development and the use of best practices and design patterns with [Laravel 5](http://laravel.com). 11 | 12 | Part of this package has been highly inspired by the [culttt.com](http://culttt.com/) blog, which I highly recommend to both new and experienced developers since it focuses on a wide range of topics with always interesting point of views and discussions. I have personally learned much from it. 13 | 14 | Support requires PHP 7.1.3+ and several 5.8 Illuminate components in order to work. 15 | 16 | In order to use Support with Laravel or Lumen 5.4 - 5.7, [check version 3.x](https://github.com/micheleangioni/support/tree/3.x/). 17 | 18 | ## Installation 19 | 20 | Support can be installed through Composer, just include `"michele-angioni/support": "^4.0"` to your composer.json and run `composer update` or `composer install`. 21 | 22 | Then add the Support Service Provider in the Laravel `app.php` config file, under the providers array 23 | 24 | ```php 25 | MicheleAngioni\Support\SupportServiceProvider::class 26 | ``` 27 | 28 | and the Helpers facade in the aliases array 29 | 30 | ```php 31 | 'Helpers' => MicheleAngioni\Support\Facades\Helpers::class 32 | ``` 33 | 34 | #### Laravel 5.4 - 5.7 35 | 36 | If you are looking for the Laravel 5.3 - 5.7 compatible version, check the [3.x branch](https://github.com/micheleangioni/support/tree/3.x) and its documentation. 37 | 38 | #### Laravel 5.0 - 5.3 39 | 40 | If you are looking for the Laravel 5.0 - 5.3 compatible version, check the [2.x branch](https://github.com/micheleangioni/support/tree/2.x) and its documentation. 41 | 42 | #### Laravel 4 43 | 44 | If you are looking for the Laravel 4 version, check the [1.0 branch](https://github.com/micheleangioni/support/tree/1.0) and its documentation. 45 | 46 | #### Lumen 47 | 48 | At the moment, only partial and **unstable** support for Lumen is guaranteed. 49 | 50 | First of all load the Service Provider in your bootstrap file 51 | 52 | ```php 53 | $app->register('MicheleAngioni\Support\SupportServiceProvider'); 54 | ``` 55 | 56 | and set the needed config key 57 | 58 | ```php 59 | config(['ma_support.cache_time' => 10]); // Default number of minutes the repositories will be cached 60 | ``` 61 | 62 | ## Modules summary 63 | 64 | Support comes bundled of the following features: Repositories, Cache, Presenters, Semaphores, an Helpers class and new custom validators. 65 | In addition Support provides several new custom exceptions. 66 | 67 | ## Configuration 68 | 69 | Support does not need any configuration to work. However, you may publish the configuration file through the artisan command `php artisan vendor:publish` that will add the `ma_support.php` file in your config directory. 70 | You can then edit this file to customize Support behaviour. 71 | 72 | In order to access to the file keys in your code, you can use `config('ma_support.key')`, where key is one of the file keys. 73 | 74 | ## Repositories Usage 75 | 76 | The abstract class `AbstractEloquentRepository` consists of a model wrapper with numerous useful queries to be performed over the Laravel models. 77 | This way implementing the repository pattern becomes straightforward. 78 | 79 | As an example let's take a `Post` model. 80 | First of all we shall create a common Repository Interface for all models of our application that extends the package `RepositoryInterface` 81 | 82 | ```php 83 | model = $model; 112 | } 113 | } 114 | ``` 115 | 116 | Now we need to bind the implementation to the interface, which can be done by adding 117 | 118 | ```php 119 | $this->app->bind( 120 | PostRepositoryInterface::class, 121 | EloquentPostRepository::class 122 | ); 123 | ``` 124 | 125 | to an existing Laravel Service Provider. Or we can create a brand new one 126 | 127 | ```php 128 | app->bind( 138 | PostRepositoryInterface::class, 139 | EloquentPostRepository::class 140 | ); 141 | } 142 | } 143 | ``` 144 | 145 | and add it to the `config/app.php` file in the providers array 146 | 147 | ```php 148 | RepositoryServiceProvider::class, 149 | ``` 150 | 151 | Suppose that now we need the Post repo in our PostController. We simply inject our `PostRepositoryInterface` in the controller which gets resolved thanks to the Laravel IoC Container 152 | 153 | ```php 154 | postRepo = $postRepo; 166 | } 167 | 168 | public function show($idPost) 169 | { 170 | $post = $this->postRepo->find($idPost); 171 | 172 | // Use the retrieved post 173 | } 174 | } 175 | ``` 176 | 177 | The `AbstractEloquentRepository` empowers automatically our repositories of the following public methods: 178 | 179 | - all(array $with = []) 180 | - find($id, array $array) 181 | - findOrFail($id, array $with = []) 182 | - first() 183 | - firstOrFail() 184 | - firstBy(array $where = [], array $with = []) 185 | - firstOrFailBy(array $where = [], array $with = []) 186 | - getBy(array $where = [], array $with = []) 187 | - getByLimit($limit, array $where = [], array $with = []) 188 | - getByOrder($orderBy, array $where = [], array $with = [], $order = 'desc', $limit = 0) 189 | - getIn($whereInKey, array $whereIn = [], $with = [], $orderBy = NULL, $order = 'desc', $limit = 0) 190 | - getNotIn($whereNotInKey, array $whereNotIn = [], $with = [], $orderBy = NULL, $order = 'desc', $limit = 0) 191 | - getHas($relation, array $where = [], array $with = [], $hasAtLeast = 1) 192 | - hasFirst($relation, array $where = [], array $with = [], $hasAtLeast = 1) 193 | - hasFirstOrFail($relation, array $where = [], array $with = [], $hasAtLeast = 1) 194 | - whereHas($relation, array $where = [], array $whereHas = [], array $with = []) 195 | - getByPage($page = 1, $limit = 10, array $where = [], $with = [], $orderBy = NULL, $order = 'desc') 196 | - insert(array $collection) 197 | - create(array $inputs = []) 198 | - update(array $inputs) 199 | - updateById($id, array $inputs) 200 | - updateBy(array $where, array $inputs) 201 | - updateOrCreateBy(array $where, array $inputs = []) 202 | - destroy($id) 203 | - destroyFirstBy(array $where) 204 | - destroyBy(array $where) 205 | - truncate() 206 | - count() 207 | - countBy(array $where = []) 208 | - countWhereHas($relation, array $where = [], array $whereHas = []) 209 | 210 | The `$where` array can have both format `['key' => 'value']` and `['key' => [, 'value']]`, where `` can be `=`, `<` or `>`. 211 | 212 | The Repository module also supports xml repositories. Suppose we have a staff.xml file. We need to define a `StaffXMLRepositoryInterface` 213 | 214 | ```php 215 | app->bind( 255 | 'StaffXMLRepositoryInterface', 256 | 'SimpleXMLStaffRepository' 257 | ); 258 | } 259 | } 260 | ``` 261 | 262 | We can then inject the repo in the class we need or simply call it through the Laravel application instance / facade 263 | 264 | ```php 265 | $xmlStaffRepo = App::make('StaffXMLRepositoryInterface'); 266 | ``` 267 | 268 | ### !!Warning!! 269 | 270 | The `AbstractEloquentRepository` and `AbstractSimpleXMLRepository` classes do NOT provide any input validation! 271 | 272 | ## Cache Usage 273 | 274 | The Cache module can be used to give Cache capabilities to our repositories, through the use of the [decorator pattern](http://en.wikipedia.org/wiki/Decorator_pattern). 275 | We can then continue our previous example of a Post model and its repo. We define a `CachePostRepoDecorator` as follows 276 | 277 | ```php 278 | app->bind( 333 | 'PostRepositoryInterface', function($app) 334 | { 335 | $repo = $app->make('EloquentPostRepository'); 336 | 337 | return new CachePostRepoDecorator($repo, new LaravelCache($app['cache']), new KeyManager); 338 | } 339 | ); 340 | } 341 | } 342 | ``` 343 | 344 | Now you can use the Post repository as before, but when calling the all(), find() or findOrFail() methods the result will be cached (default time: 10 minutes It can be modified in the configuration file). 345 | 346 | ### Hint 347 | 348 | Want to manually delete a cache result? 349 | In your Post model define a flush() method as follows 350 | 351 | ```php 352 | public function flush() 353 | { 354 | Cache::tags(get_called_class().'id'.$this->{$this->primaryKey})->flush(); 355 | } 356 | ``` 357 | 358 | You can then call it when editing or deleting a Post model you that your clients don't get outdated results. 359 | 360 | The Cache module comes with xml handlers too. Let's take the staff.xml class we used before. All we need to provide cache is to define the caching xml repo as follows 361 | 362 | ```php 363 | app->bind( 406 | 'StaffXMLRepositoryInterface', function($app) 407 | { 408 | $repo = $app->make('SimpleXMLStaffRepository'); 409 | 410 | return new CacheSimpleXMLStaffRepoDecorator($repo, new LaravelCache($app['cache']) 411 | ); 412 | }); 413 | } 414 | } 415 | ``` 416 | 417 | ## Presenters Usage 418 | 419 | A Presenter is a particular kind of [decorator](http://en.wikipedia.org/wiki/Decorator_pattern), used to decorate an object before sending it to the view. 420 | The most common uses include date and numbers formatting, text validation and formatting, obscuration of sensible data. 421 | 422 | Support provides an easy way to decorate Eloquent models. Let's continue to use our `Post` model and suppose we want to escape its `text` attribute before passing the model to the view. 423 | 424 | First of all define the PostDecorator by extending `MicheleAngioni\Support\Presenters\AbstractPresenter` and implementing `MicheleAngioni\Support\Presenters\PresentableInterface`. 425 | The AbstractPresenter will allow to access al model's attributes through the use of PHP magic method __GET. 426 | It also implements ArrayAccess interface so that we can keep to access our attributes both as an object and as array. 427 | 428 | ```php 429 | object->text); 440 | } 441 | 442 | public function capitalText() 443 | { 444 | return e(strtoupper($this->object->text)); 445 | } 446 | } 447 | ``` 448 | 449 | Now we have to couple our presenter to the Post model with the help of the `MicheleAngioni\Support\Presenters\Presenter` class, for example directly in the PostController 450 | 451 | ```php 452 | postRepo = $postRepo; 465 | $this->presenter = $presenter; 466 | } 467 | 468 | public function index() 469 | { 470 | $posts = $this->postRepo->all(); 471 | 472 | // Pass the post collection to the presenter 473 | $posts = $this->presenter->collection($posts, new PostPresenter()); 474 | 475 | // Pass the post collection to the view 476 | return View::make('forum')->with('posts', $posts); 477 | } 478 | 479 | public function show($idPost) 480 | { 481 | $post = $this->postRepo->find($idPost); 482 | 483 | // Pass the post to the presenter 484 | $post = $this->presenter->model($post, new PostPresenter()); 485 | 486 | // Pass the post to the view 487 | return View::make('forum')->with('post', $post); 488 | } 489 | } 490 | ``` 491 | 492 | In the above Controller we have decorated a single model in the show method and an entire collection in the index method, thus passed them to the view. 493 | 494 | In the view we will have our decorated models, instances of `PostPresenter`, and the text attribute will be already escaped. 495 | Through the presenter we can also add brand new functionality to our models: in this example we added a capitalText attribute. 496 | 497 | ## Semaphores Usage 498 | 499 | The semaphores module consists of a single class, the `SemaphoresManager`. Its constructor needs a Cache Manager and a Key Manager. 500 | The Support package provides both of them, so we can bind them to the SemaphoresManager in a service provider 501 | 502 | ```php 503 | app->bind( 515 | 'MicheleAngioni\Support\Semaphores\SemaphoresManager', function($app) 516 | { 517 | return new SemaphoresManager(new LaravelCache($app['cache']), new KeyManager); 518 | }); 519 | } 520 | } 521 | ``` 522 | 523 | We can them simply inject the SemaphoresManager in a constructor to be resolver by the IoC Container and it is ready to use through the following methods: 524 | 525 | - `setLockingTime(int $minutes)` : set the semaphores locking time in minutes 526 | - `lockSemaphore($id, string $section)` : lock a semaphore with input id belonging to input section 527 | - `unlockSemaphore($id, string $section)` : unlock the semaphore with input id belonging to input section 528 | - `checkIfSemaphoreIsLocked($id, string $section)` : check if the semaphore with input id belonging to input section is actually locked 529 | - `getSemaphoreKey($id, string $section)` : return the cache key used by the semaphore with input id belonging to input section is actually locked 530 | 531 | ## Helpers Usage 532 | 533 | The helpers class provides several useful methods which simplify php development. Support has also an Helpers facade which can be registered in the `app.php` file under the aliases array as 534 | 535 | ```php 536 | 'Helpers' => MicheleAngioni\Support\Facades\Helpers::class 537 | ``` 538 | 539 | The available methods are: 540 | 541 | - `isInt($int, int $min = null, int $max = null)` : check if input $int is an integer. Examples: 542 | int(4), string '4', float(4), 0x7FFFFFFF will return true. 543 | int(4.1), string '1.2', string '0x8', float(1.2) will return false. 544 | min and max allowed values can be inserted. 545 | - `randInArray(array $array)` : return a random value out of an array 546 | - `checkDate(string $date, string $format = 'Y-m-d')` : check if input date is a valid date based of input format 547 | - `checkDatetime(string $datetime)` : check if input datetime is a valid 'Y-m-d H:m:s' datetime 548 | - `splitDates(string $firstDate, string $second_Date, int $maxDifference = 0)` : split two 'Y-m-d'-format dates into an array of dates 549 | - `daysBetweenDates(string $date1, string $date2)` : return the number of days between the two input 'Y-m-d' or 'Y-m-d X' (X is some text) dates 550 | - `getRandomValueUrandom(int $min = 0, int $max = 0x7FFFFFFF)` : return a random value between input $min and $max values by using the MCRYPT_DEV_URANDOM source 551 | - `getUniqueRandomValues(int $min = 0, int $max, int $quantity = 1)` : return $quantity UNIQUE random value between $min and $max 552 | 553 | ### Removed in 3.0 version 554 | 555 | - `divideCollectionIntoGroups()`: use Collection's [split method](https://laravel.com/docs/5.4/collections#method-split) instead 556 | - `compareDates()`: use [Carbon](http://carbon.nesbot.com/docs/) instead 557 | - `getTodayDay()`: use [Carbon](http://carbon.nesbot.com/docs/) instead 558 | - `getDate()`: use [Carbon](http://carbon.nesbot.com/docs/) instead 559 | - `getTime()`: use [Carbon](http://carbon.nesbot.com/docs/) instead 560 | - `getRandomValueUrandom()`: use PHP's [random_int](http://php.net/manual/en/function.random-int.php) instead 561 | 562 | ## Custom validators 563 | 564 | Just after registering the SupportServiceProvider, the following new custom validators will be available: 565 | 566 | - alpha_complete : The following characters are allowed: letters, numbers, spaces, slashes, pipes and several punctuation characters | = # _ ! . , : / ; ? & ( ) [ ] { } 567 | - alpha_space : The following characters are allowed: letters, numbers and spaces 568 | - alpha_underscore : The following characters are allowed: letters, numbers and underscores 569 | - alpha_names : The following characters are allowed: letters, menus, apostrophes, underscores and spaces 570 | - alphanumeric_names : The following characters are allowed: letters, numbers, menus, apostrophes, underscores and spaces 571 | - alphanumeric_dotted_names : The following characters are allowed: letters, numbers, menus, apostrophes, underscores, dots and spaces 572 | 573 | ## Custom Exceptions 574 | 575 | The following new custom exceptions will be available: 576 | 577 | - `DatabaseException` : thought to be used where a query error arises 578 | - `DbClientRequestException` : can be thrown when an entity required by a client is not available 579 | - `PermissionsException` : a general permission exception 580 | 581 | ## API Docs 582 | 583 | You can browse the Support [API Documentation](http://micheleangioni.github.io/support/master/index.html). 584 | 585 | ## Contribution guidelines 586 | 587 | Support follows PSR-1, PSR-2 and PSR-4 PHP coding standards, and semantic versioning. 588 | 589 | Pull requests are welcome. 590 | 591 | ## License 592 | 593 | Support is free software distributed under the terms of the MIT license. 594 | --------------------------------------------------------------------------------