├── 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 | [](https://packagist.org/packages/michele-angioni/support)
4 | [](https://packagist.org/packages/michele-angioni/support)
5 | [](https://travis-ci.org/micheleangioni/support)
6 | [](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 |
--------------------------------------------------------------------------------