├── src
├── Housekeeper
│ ├── Exceptions
│ │ └── RepositoryException.php
│ ├── Abilities
│ │ ├── Cache
│ │ │ ├── Contracts
│ │ │ │ └── CacheAdapter.php
│ │ │ ├── Statically.php
│ │ │ ├── Statically
│ │ │ │ ├── Injections
│ │ │ │ │ ├── AbstractBase.php
│ │ │ │ │ ├── GetCachedIfExistsBefore.php
│ │ │ │ │ └── CacheResultOrClearCacheAfter.php
│ │ │ │ └── CacheAdapter.php
│ │ │ └── Foundation
│ │ │ │ └── Base.php
│ │ ├── Adjustable
│ │ │ ├── Contracts
│ │ │ │ └── Criteria.php
│ │ │ └── Injections
│ │ │ │ └── ApplyCriteriasBefore.php
│ │ ├── Metadata.php
│ │ ├── Vintage.php
│ │ ├── Metadata
│ │ │ └── Injections
│ │ │ │ └── ToMetadataAfter.php
│ │ ├── CacheStatically.php
│ │ ├── Adjustable.php
│ │ ├── Guardable.php
│ │ ├── SoftDeletes.php
│ │ └── Eloquently.php
│ ├── Contracts
│ │ ├── Injection
│ │ │ ├── Basic.php
│ │ │ ├── Reset.php
│ │ │ ├── After.php
│ │ │ └── Before.php
│ │ ├── Flow
│ │ │ ├── Basic.php
│ │ │ ├── Reset.php
│ │ │ ├── After.php
│ │ │ └── Before.php
│ │ ├── Plan.php
│ │ ├── Action.php
│ │ └── Repository.php
│ ├── Console
│ │ └── Generators
│ │ │ ├── stubs
│ │ │ └── repository.stub
│ │ │ └── MakeRepositoryCommand.php
│ ├── Support
│ │ ├── SmartHasher.php
│ │ └── InjectionContainer.php
│ ├── Providers
│ │ └── HousekeeperServiceProvider.php
│ ├── Flows
│ │ ├── Reset.php
│ │ ├── After.php
│ │ └── Before.php
│ ├── Action.php
│ ├── Plan.php
│ └── Repository.php
└── config
│ └── housekeeper.php
├── composer.json
├── phpunit.xml
├── tests
├── Housekeeper
│ ├── Abilities
│ │ └── AdjustableTest.php
│ ├── Support
│ │ └── InjectionContainerTest.php
│ └── RepositoryTest.php
└── bootstrap.php
├── .gitignore
├── LICENSE
└── README.md
/src/Housekeeper/Exceptions/RepositoryException.php:
--------------------------------------------------------------------------------
1 |
9 | * @package Housekeeper\Exceptions
10 | */
11 | class RepositoryException extends \Exception
12 | {
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/Housekeeper/Abilities/Cache/Contracts/CacheAdapter.php:
--------------------------------------------------------------------------------
1 |
9 | * @package Housekeeper\Contracts\Injection
10 | */
11 | interface Basic
12 | {
13 | /**
14 | * Lower is higher.
15 | *
16 | * @return integer
17 | */
18 | public function priority();
19 | }
20 |
--------------------------------------------------------------------------------
/src/Housekeeper/Contracts/Injection/Reset.php:
--------------------------------------------------------------------------------
1 |
11 | * @package Housekeeper\Contracts
12 | */
13 | interface Reset extends Basic
14 | {
15 | /**
16 | * @param \Housekeeper\Contracts\Flow\Reset $resetFlow
17 | * @return void
18 | */
19 | public function handle(ResetFlow $resetFlow);
20 | }
21 |
--------------------------------------------------------------------------------
/src/Housekeeper/Contracts/Injection/After.php:
--------------------------------------------------------------------------------
1 |
11 | * @package Housekeeper\Contracts\Injection
12 | */
13 | interface After extends Basic
14 | {
15 | /**
16 | * @param \Housekeeper\Contracts\Flow\After $afterFlow
17 | * @return void
18 | */
19 | public function handle(AfterFlow $afterFlow);
20 | }
21 |
--------------------------------------------------------------------------------
/src/Housekeeper/Contracts/Injection/Before.php:
--------------------------------------------------------------------------------
1 |
11 | * @package Housekeeper\Contracts\Injection
12 | */
13 | interface Before extends Basic
14 | {
15 | /**
16 | * @param \Housekeeper\Contracts\Flow\Before $beforeFlow
17 | * @return void
18 | */
19 | public function handle(BeforeFlow $beforeFlow);
20 | }
21 |
--------------------------------------------------------------------------------
/src/Housekeeper/Console/Generators/stubs/repository.stub:
--------------------------------------------------------------------------------
1 |
11 | * @package Housekeeper\Ablities\Adjustable\Contracts
12 | */
13 | interface Criteria
14 | {
15 | /**
16 | * Apply criteria to repository
17 | *
18 | * @param RepositoryContract $repository
19 | */
20 | public function apply(RepositoryContract $repository);
21 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Contracts/Flow/Basic.php:
--------------------------------------------------------------------------------
1 |
11 | * @package Housekeeper\Contracts\Flow
12 | */
13 | interface Basic
14 | {
15 | /**
16 | * @return \Housekeeper\Contracts\Repository
17 | */
18 | public function getRepository();
19 |
20 | /**
21 | * @return \Housekeeper\Action
22 | */
23 | public function getAction();
24 |
25 | /**
26 | * @return int
27 | */
28 | public function getIndex();
29 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Contracts/Flow/Reset.php:
--------------------------------------------------------------------------------
1 |
12 | * @package Housekeeper\Contracts\Flow
13 | */
14 | interface Reset extends Basic
15 | {
16 | /**
17 | * @param \Housekeeper\Contracts\Repository $repository
18 | * @param \Housekeeper\Contracts\Action $action
19 | * @param int $index
20 | */
21 | public function __construct(Repository $repository, Action $action, $index);
22 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Abilities/Metadata.php:
--------------------------------------------------------------------------------
1 |
14 | * @package Housekeeper\Traits\Repository
15 | */
16 | trait Metadata
17 | {
18 | /**
19 | * Binding injection.
20 | */
21 | public function bootMetadata()
22 | {
23 | $this->injectIntoAfter(new ToMetadataAfter());
24 | }
25 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Abilities/Vintage.php:
--------------------------------------------------------------------------------
1 |
14 | * @package Housekeeper\Traits\Repository
15 | */
16 | trait Vintage
17 | {
18 | /**
19 | * @param array $wheres
20 | * @return $this
21 | */
22 | public function applyWhere(array $wheres)
23 | {
24 | return $this->whereAre($wheres);
25 | }
26 |
27 | /**
28 | * @param $column
29 | * @param string $direction
30 | * @return mixed
31 | */
32 | public function applyOrder($column, $direction = 'asc')
33 | {
34 | return $this->applyOrderBy($column, $direction);
35 | }
36 | }
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aaronjan/housekeeper",
3 | "description": "Powerful, simple Repository-Pattern implementation for Laravel (>=5.1), and it come with tests.",
4 | "keywords": [
5 | "laravel",
6 | "repository",
7 | "housekeeper"
8 | ],
9 | "license": "Apache-2.0",
10 | "type": "library",
11 | "authors": [
12 | {
13 | "name": "Aaron Jan",
14 | "homepage": "https://github.com/AaronJan"
15 | }
16 | ],
17 | "require": {
18 | "php": ">=5.5.0"
19 | },
20 | "require-dev": {
21 | "phpunit/phpunit": "~4.0",
22 | "mockery/mockery": "^0.9.4"
23 | },
24 | "autoload": {
25 | "classmap": [
26 | ],
27 | "psr-4": {
28 | "Housekeeper\\": "src/Housekeeper/"
29 | }
30 | },
31 | "autoload-dev": {
32 | "classmap": [
33 | ]
34 | },
35 | "minimum-stability": "stable"
36 | }
37 |
--------------------------------------------------------------------------------
/src/Housekeeper/Contracts/Flow/After.php:
--------------------------------------------------------------------------------
1 |
12 | * @package Housekeeper\Contracts\Flow
13 | */
14 | interface After extends Basic
15 | {
16 | /**
17 | * @param \Housekeeper\Contracts\Repository $repository
18 | * @param \Housekeeper\Contracts\Action $action
19 | * @param int $index
20 | * @param mixed $returnValue
21 | */
22 | public function __construct(Repository $repository, Action $action, $index, $returnValue);
23 |
24 | /**
25 | * @return mixed
26 | */
27 | public function getReturnValue();
28 |
29 | /**
30 | * @param mixed $value
31 | * @return void
32 | */
33 | public function setReturnValue($value);
34 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Contracts/Flow/Before.php:
--------------------------------------------------------------------------------
1 |
12 | * @package Housekeeper\Contracts\Flow
13 | */
14 | interface Before extends Basic
15 | {
16 | /**
17 | * @param \Housekeeper\Contracts\Repository $repository
18 | * @param \Housekeeper\Contracts\Action $action
19 | * @param int $index
20 | */
21 | public function __construct(Repository $repository, Action $action, $index);
22 |
23 | /**
24 | * @return bool
25 | */
26 | public function hasReturnValue();
27 |
28 | /**
29 | * @return mixed
30 | */
31 | public function getReturnValue();
32 |
33 | /**
34 | * @param mixed $value
35 | * @return void
36 | */
37 | public function setReturnValue($value);
38 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Support/SmartHasher.php:
--------------------------------------------------------------------------------
1 |
9 | * @package Housekeeper\Support
10 | */
11 | class SmartHasher
12 | {
13 | /**
14 | * Hash anything (except PDO connection stuff, for now).
15 | *
16 | * @param mixed $object
17 | * @return string
18 | */
19 | static public function hash($object)
20 | {
21 | $object = is_array($object) ? $object : [$object];
22 |
23 | array_walk_recursive($object, function ($item) {
24 | if ($item instanceof \Closure) {
25 | $reflection = new \ReflectionFunction($item);
26 |
27 | // Unique and fast.
28 | $item = serialize($reflection->getClosureScopeClass()) .
29 | $reflection->getNumberOfParameters() .
30 | $reflection->getNamespaceName() .
31 | $reflection->getStartLine() .
32 | $reflection->getEndLine();
33 | }
34 | });
35 |
36 | return md5(serialize($object));
37 | }
38 | }
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 | ./tests/Housekeeper/
17 |
18 |
19 |
20 |
21 |
22 | ./vendor/
23 |
24 |
25 | ./src/Housekeeper/
26 |
27 |
28 |
29 |
30 |
31 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/Housekeeper/Abilities/Metadata/Injections/ToMetadataAfter.php:
--------------------------------------------------------------------------------
1 |
16 | * @package Housekeeper\Injections\Cacheable
17 | */
18 | class ToMetadataAfter implements BasicInjectionContract,
19 | AfterInjectionContract
20 | {
21 | const PRIORITY = 10;
22 |
23 |
24 | /**
25 | * @return int
26 | */
27 | public function priority()
28 | {
29 | return static::PRIORITY;
30 | }
31 |
32 | /**
33 | * @param \Housekeeper\Contracts\Flow\After $afterFlow
34 | */
35 | public function handle(AfterFlowContract $afterFlow)
36 | {
37 | $returnResult = $afterFlow->getReturnValue();
38 |
39 | if ($returnResult instanceof Arrayable) {
40 | $afterFlow->setReturnValue($returnResult->toArray());
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/Housekeeper/Abilities/AdjustableTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(1, 1);
46 | }
47 |
48 |
49 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Providers/HousekeeperServiceProvider.php:
--------------------------------------------------------------------------------
1 |
11 | * @package Housekeeper\Providers
12 | */
13 | class HousekeeperServiceProvider extends ServiceProvider
14 | {
15 | /**
16 | * Indicates if loading of the provider is deferred.
17 | *
18 | * @var bool
19 | */
20 | protected $defer = false;
21 |
22 | /**
23 | *
24 | * @return void
25 | */
26 | public function boot()
27 | {
28 | $this->publishes([
29 | __DIR__ . '/../../config/housekeeper.php' => config_path('housekeeper.php')
30 | ], 'config');
31 |
32 | $this->mergeConfigFrom(
33 | __DIR__ . '/../../config/housekeeper.php', 'housekeeper'
34 | );
35 | }
36 |
37 | /**
38 | * Register the service provider.
39 | *
40 | * @return void
41 | */
42 | public function register()
43 | {
44 | $this->commands(\Housekeeper\Console\Generators\MakeRepositoryCommand::class);
45 | }
46 |
47 | /**
48 | * Get the services provided by the provider.
49 | *
50 | * @return array
51 | */
52 | public function provides()
53 | {
54 | return [];
55 | }
56 |
57 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Abilities/Cache/Statically.php:
--------------------------------------------------------------------------------
1 | getRedis();
32 | $configs = $this->getCacheConfigs([
33 | 'prefix' => 'housekeeper_',
34 | ]);
35 |
36 | /**
37 | * @var $this \Housekeeper\Contracts\Repository|$this
38 | */
39 | $this->cacheAdapter = new CacheAdapter($this, $redis, $configs);
40 |
41 | $this->inject(new GetCachedIfExistsBefore($this->cacheAdapter), false);
42 | $this->inject(new CacheResultOrClearCacheAfter($this->cacheAdapter));
43 | }
44 |
45 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Abilities/CacheStatically.php:
--------------------------------------------------------------------------------
1 | getRedis();
29 | $configs = $this->getCacheConfigs([
30 | 'prefix' => 'housekeeper_',
31 | ]);
32 |
33 | /**
34 | * @var $this CacheStatically|\Housekeeper\Contracts\Repository
35 | */
36 | $this->cacheAdapter = new CacheAdapter($this, $redis, $configs);
37 |
38 | $this->injectIntoBefore(new GetCachedIfExistsBefore($this->cacheAdapter), false);
39 | $this->injectIntoAfter(new CacheResultOrClearCacheAfter($this->cacheAdapter));
40 | }
41 |
42 | }
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | getProperty($property);
24 | $reflectionProperty->setAccessible(true);
25 | $reflectionProperty->setValue($object, $value);
26 | }
27 |
28 | /**
29 | * @param $object
30 | * @param $property
31 | * @return mixed
32 | */
33 | function getUnaccessibleObjectPropertyValue($object, $property)
34 | {
35 | $relection = new \ReflectionClass($object);
36 |
37 | $reflectionProperty = $relection->getProperty($property);
38 | $reflectionProperty->setAccessible(true);
39 |
40 | return $reflectionProperty->getValue($object);
41 | }
42 |
43 | /**
44 | * @param $object
45 | * @param $methodName
46 | * @return ReflectionMethod
47 | */
48 | function getUnaccessibleObjectMethod($object, $methodName)
49 | {
50 | $relection = new \ReflectionClass($object);
51 | $method = $relection->getMethod($methodName);
52 | $method->setAccessible(true);
53 |
54 | return $method;
55 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Contracts/Plan.php:
--------------------------------------------------------------------------------
1 |
13 | * @package Housekeeper\Flow
14 | */
15 | class Reset implements ResetContract
16 | {
17 | /**
18 | * @var \Housekeeper\Contracts\Action
19 | */
20 | protected $action;
21 |
22 | /**
23 | * @var \Housekeeper\Contracts\Repository
24 | */
25 | protected $repository;
26 |
27 | /**
28 | * @var int
29 | */
30 | protected $index;
31 |
32 |
33 | /**
34 | * @param \Housekeeper\Contracts\Repository $repository
35 | * @param \Housekeeper\Contracts\Action $action
36 | * @param int $index
37 | */
38 | public function __construct(Repository $repository, Action $action, $index)
39 | {
40 | $this->repository = $repository;
41 | $this->action = $action;
42 | $this->index = $index;
43 | }
44 |
45 | /**
46 | * @return \Housekeeper\Contracts\Action
47 | */
48 | public function getAction()
49 | {
50 | return $this->action;
51 | }
52 |
53 | /**
54 | * @return \Housekeeper\Contracts\Repository
55 | */
56 | public function getRepository()
57 | {
58 | return $this->repository;
59 | }
60 |
61 | /**
62 | * @return int
63 | */
64 | public function getIndex()
65 | {
66 | return $this->index;
67 | }
68 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Abilities/Cache/Statically/Injections/AbstractBase.php:
--------------------------------------------------------------------------------
1 |
13 | * @package Housekeeper\Abilities\Cache\Statically\Injections
14 | */
15 | abstract class AbstractBase
16 | {
17 | /**
18 | * @var \Housekeeper\Abilities\Cache\Contracts\CacheAdapter
19 | */
20 | protected $cacheAdapter;
21 |
22 |
23 | /**
24 | * AbstractBase constructor.
25 | *
26 | * @param \Housekeeper\Abilities\Cache\Statically\CacheAdapter $hashCacheAdapter
27 | */
28 | public function __construct(CacheAdapter $hashCacheAdapter)
29 | {
30 | $this->cacheAdapter = $hashCacheAdapter;
31 | }
32 |
33 | /**
34 | * @param \Housekeeper\Contracts\Action $action
35 | * @return mixed|null
36 | */
37 | protected function getCacheForAction(ActionContract $action)
38 | {
39 | return $this->cacheAdapter->getCacheForAction($action);
40 | }
41 |
42 | /**
43 | * @param \Housekeeper\Contracts\Action $action
44 | * @param $value
45 | */
46 | protected function setCacheForAction(ActionContract $action, $value)
47 | {
48 | $this->cacheAdapter->setCacheForAction($action, $value);
49 | }
50 |
51 | /**
52 | *
53 | */
54 | protected function clearCache()
55 | {
56 | $this->cacheAdapter->flush();
57 | }
58 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Abilities/Adjustable/Injections/ApplyCriteriasBefore.php:
--------------------------------------------------------------------------------
1 |
13 | * @package Housekeeper\Abilities\Adjustable\Injections
14 | */
15 | class ApplyCriteriasBefore implements BasicInjectionContract,
16 | BeforeInjectionContract
17 | {
18 | /**
19 | * @var int
20 | */
21 | const PRIORITY = 10;
22 |
23 | /**
24 | * @return int
25 | */
26 | public function priority()
27 | {
28 | return static::PRIORITY;
29 | }
30 |
31 | /**
32 | * @param \Housekeeper\Contracts\Flow\Before $beforeFlow
33 | */
34 | public function handle(BeforeFlowContract $beforeFlow)
35 | {
36 | // Only affecting the first wrapped method
37 | if ($beforeFlow->getIndex() != 1) {
38 | return;
39 | }
40 |
41 | /**
42 | * @var \Housekeeper\Contracts\Repository|\Housekeeper\Abilities\Adjustable $repository
43 | */
44 | $repository = $beforeFlow->getRepository();
45 |
46 | $criterias = $repository->getCriterias();
47 |
48 | array_walk($criterias, function ($criteria) use ($repository) {
49 | /**
50 | * @var \Housekeeper\Abilities\Adjustable\Contracts\Criteria $criteria
51 | */
52 | $repository->applyCriteria($criteria);
53 | });
54 | }
55 |
56 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Contracts/Action.php:
--------------------------------------------------------------------------------
1 |
9 | * @package Housekeeper\Contracts
10 | */
11 | interface Action
12 | {
13 | /**
14 | * @var int
15 | */
16 | const UNKNOW = - 1;
17 |
18 | /**
19 | * @var int
20 | */
21 | const CREATE = 1;
22 |
23 | /**
24 | * @var int
25 | */
26 | const UPDATE = 2;
27 |
28 | /**
29 | * @var int
30 | */
31 | const READ = 3;
32 |
33 | /**
34 | * @var int
35 | */
36 | const DELETE = 4;
37 |
38 | /**
39 | * @var int
40 | */
41 | const CREATE_OR_UPDATE = 5;
42 |
43 | /**
44 | * @var int
45 | */
46 | const INTERNAL = 6;
47 |
48 | /**
49 | * @var int
50 | */
51 | const IGNORED = 7;
52 |
53 | /**
54 | * @param string $methodName
55 | */
56 | public function setMethodName($methodName);
57 |
58 | /**
59 | * @return null|string
60 | */
61 | public function getMethodName();
62 |
63 | /**
64 | * @param array $arguments
65 | */
66 | public function setArguments(array $arguments);
67 |
68 | /**
69 | * @return null|array
70 | */
71 | public function getArguments();
72 |
73 | /**
74 | * @return int
75 | */
76 | public function getType();
77 |
78 | /**
79 | * @param int $type
80 | * @return bool
81 | */
82 | public function isType($type);
83 |
84 | /**
85 | * @param $discription
86 | */
87 | public function describeAs($discription);
88 |
89 | /**
90 | * @param $discription
91 | * @return bool
92 | */
93 | public function isDescribedAs($discription);
94 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Abilities/Cache/Statically/Injections/GetCachedIfExistsBefore.php:
--------------------------------------------------------------------------------
1 |
18 | * @package Housekeeper\Injections\Cacheable
19 | */
20 | class GetCachedIfExistsBefore extends AbstractBase implements BasicInjectionContract,
21 | BeforeInjectionContract
22 | {
23 | const PRIORITY = 50;
24 |
25 |
26 | /**
27 | * @return int
28 | */
29 | public function priority()
30 | {
31 | return static::PRIORITY;
32 | }
33 |
34 | /**
35 | * @param \Housekeeper\Contracts\Flow\Before $beforeFlow
36 | */
37 | public function handle(BeforeFlowContract $beforeFlow)
38 | {
39 | /**
40 | * Skip cache logic if cache has been disabled in the repository.
41 | *
42 | * @var $repository Repository|Statically
43 | */
44 | $repository = $beforeFlow->getRepository();
45 | if ($repository->isCacheEnabled() === false) {
46 | return;
47 | }
48 |
49 | /**
50 | * Only get cache when Action is typed as "READ".
51 | */
52 | if ($beforeFlow->getAction()->isType(Action::READ)) {
53 | $cached = $this->getCacheForAction($beforeFlow->getAction());
54 |
55 | if ( ! is_null($cached)) {
56 | $beforeFlow->setReturnValue($cached);
57 | }
58 | }
59 | }
60 |
61 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Abilities/Adjustable.php:
--------------------------------------------------------------------------------
1 |
14 | * @package Housekeeper\Traits\Repository
15 | */
16 | trait Adjustable
17 | {
18 | /**
19 | * @var \Housekeeper\Abilities\Adjustable\Contracts\Criteria[]
20 | */
21 | protected $criterias = [];
22 |
23 |
24 | /**
25 | * Bind injection.
26 | */
27 | public function bootAdjustable()
28 | {
29 | $this->injectIntoBefore(new ApplyCriteriasBefore());
30 | }
31 |
32 | /**
33 | * ave this criteria, it would be auto-applied before every method calling.
34 | *
35 | * @param \Housekeeper\Abilities\Adjustable\Contracts\Criteria $criteria
36 | * @return $this
37 | */
38 | public function rememberCriteria(Criteria $criteria)
39 | {
40 | $this->criterias[] = $criteria;
41 |
42 | return $this;
43 | }
44 |
45 | /**
46 | * Remove all criterias that this repository remembered.
47 | *
48 | * @return $this
49 | */
50 | public function forgetCriterias()
51 | {
52 | $this->criterias = [];
53 |
54 | return $this;
55 | }
56 |
57 | /**
58 | * Get all criterias that this repository remembered.
59 | *
60 | * @return Criteria[]
61 | */
62 | public function getCriterias()
63 | {
64 | return $this->criterias;
65 | }
66 |
67 | /**
68 | * @param \Housekeeper\Abilities\Adjustable\Contracts\Criteria $criteria
69 | * @return $this
70 | */
71 | public function applyCriteria(Criteria $criteria)
72 | {
73 | /**
74 | * @var \Housekeeper\Contracts\Repository $this
75 | */
76 | $criteria->apply($this);
77 |
78 | return $this;
79 | }
80 |
81 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Flows/After.php:
--------------------------------------------------------------------------------
1 |
13 | * @package Housekeeper\Flow
14 | */
15 | class After implements AfterContract
16 | {
17 | /**
18 | * @var Action
19 | */
20 | protected $action;
21 |
22 | /**
23 | * @var Repository
24 | */
25 | protected $repository;
26 |
27 | /**
28 | * @var int
29 | */
30 | protected $index;
31 |
32 | /**
33 | * @var mixed
34 | */
35 | protected $returnValue;
36 |
37 |
38 | /**
39 | * @param \Housekeeper\Contracts\Repository $repository
40 | * @param \Housekeeper\Contracts\Action $action
41 | * @param int $index
42 | * @param mixed $returnValue
43 | */
44 | public function __construct(Repository $repository, Action $action, $index, $returnValue)
45 | {
46 | $this->repository = $repository;
47 | $this->action = $action;
48 | $this->index = $index;
49 | $this->returnValue = $returnValue;
50 | }
51 |
52 | /**
53 | * @return \Housekeeper\Contracts\Action
54 | */
55 | public function getAction()
56 | {
57 | return $this->action;
58 | }
59 |
60 | /**
61 | * @return \Housekeeper\Contracts\Repository
62 | */
63 | public function getRepository()
64 | {
65 | return $this->repository;
66 | }
67 |
68 | /**
69 | * @return int
70 | */
71 | public function getIndex()
72 | {
73 | return $this->index;
74 | }
75 |
76 | /**
77 | * @return mixed
78 | */
79 | public function getReturnValue()
80 | {
81 | return $this->returnValue;
82 | }
83 |
84 | /**
85 | * @param mixed $value
86 | */
87 | public function setReturnValue($value)
88 | {
89 | $this->returnValue = $value;
90 | }
91 | }
--------------------------------------------------------------------------------
/src/config/housekeeper.php:
--------------------------------------------------------------------------------
1 |
6 | * @package Housekeeper
7 | * @version 2.*
8 | * @license Apache 2.0
9 | * @copyright (c) 2015, AaronJan
10 | * @link https://github.com/AaronJan/Housekeeper
11 | */
12 |
13 | return [
14 |
15 | /*
16 | |--------------------------------------------------------------------------
17 | | Repository Directory
18 | |--------------------------------------------------------------------------
19 | |
20 | | Where to put repository class.
21 | |
22 | */
23 |
24 | 'directory' => '/Repositories/',
25 |
26 | /*
27 | |--------------------------------------------------------------------------
28 | | Paginate Configures
29 | |--------------------------------------------------------------------------
30 | */
31 |
32 | 'paginate' => [
33 |
34 | /*
35 | |--------------------------------------------------------------------------
36 | | Page Size
37 | |--------------------------------------------------------------------------
38 | |
39 | | How many entries per page for "paginate" method.
40 | |
41 | */
42 |
43 | 'per_page' => 15,
44 |
45 | ],
46 |
47 | 'abilities' => [
48 |
49 | /*
50 | |--------------------------------------------------------------------------
51 | | Cacheable
52 | |--------------------------------------------------------------------------
53 | |
54 | |
55 | |
56 | */
57 |
58 | 'cache' => [
59 |
60 | /*
61 | |--------------------------------------------------------------------------
62 | | Cache Key Prefix
63 | |--------------------------------------------------------------------------
64 | |
65 | |
66 | |
67 | */
68 |
69 | 'prefix' => 'housekeeper_',
70 |
71 | ],
72 |
73 | ],
74 |
75 | ];
76 |
--------------------------------------------------------------------------------
/src/Housekeeper/Flows/Before.php:
--------------------------------------------------------------------------------
1 |
13 | * @package Housekeeper\Flow
14 | */
15 | class Before implements BeforeContract
16 | {
17 | /**
18 | * @var Action
19 | */
20 | protected $action;
21 |
22 | /**
23 | * @var Repository
24 | */
25 | protected $repository;
26 |
27 | /**
28 | * @var int
29 | */
30 | protected $index;
31 |
32 | /**
33 | * @var mixed
34 | */
35 | protected $returnValue;
36 |
37 |
38 | /**
39 | * @param \Housekeeper\Contracts\Repository $repository
40 | * @param \Housekeeper\Contracts\Action $action
41 | * @param int $index
42 | */
43 | public function __construct(Repository $repository, Action $action, $index)
44 | {
45 | $this->repository = $repository;
46 | $this->action = $action;
47 | $this->index = $index;
48 | }
49 |
50 | /**
51 | * @return \Housekeeper\Contracts\Action
52 | */
53 | public function getAction()
54 | {
55 | return $this->action;
56 | }
57 |
58 | /**
59 | * @return \Housekeeper\Contracts\Repository
60 | */
61 | public function getRepository()
62 | {
63 | return $this->repository;
64 | }
65 |
66 | /**
67 | * @return int
68 | */
69 | public function getIndex()
70 | {
71 | return $this->index;
72 | }
73 |
74 | /**
75 | * @return bool
76 | */
77 | public function hasReturnValue()
78 | {
79 | return ! is_null($this->returnValue);
80 | }
81 |
82 | /**
83 | * @return mixed
84 | */
85 | public function getReturnValue()
86 | {
87 | return $this->returnValue;
88 | }
89 |
90 | /**
91 | * @param mixed $value
92 | */
93 | public function setReturnValue($value)
94 | {
95 | $this->returnValue = $value;
96 | }
97 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Abilities/Cache/Foundation/Base.php:
--------------------------------------------------------------------------------
1 | cacheEnabled = false;
38 |
39 | return $this;
40 | }
41 |
42 | /**
43 | *
44 | */
45 | public function enableCache()
46 | {
47 | $this->cacheEnabled = true;
48 |
49 | return $this;
50 | }
51 |
52 | /**
53 | * @return bool
54 | */
55 | public function isCacheEnabled()
56 | {
57 | return $this->cacheEnabled;
58 | }
59 |
60 | /**
61 | *
62 | */
63 | public function clearCache()
64 | {
65 | $this->cacheAdapter->flush();
66 |
67 | return $this;
68 | }
69 |
70 | /**
71 | * @return \Illuminate\Contracts\Redis\Database|\Illuminate\Redis\Database
72 | */
73 | protected function getRedis()
74 | {
75 | if (! $this->cacheRedis) {
76 | try {
77 | $this->cacheRedis = $this->getApp()->make('redis');
78 | } catch (\Exception $e) {
79 | throw new \RuntimeException('Cacheable trait of Housekeeper requires Redis support.');
80 | }
81 | }
82 |
83 | return $this->cacheRedis;
84 | }
85 |
86 | /**
87 | * @param array $default
88 | * @return mixed
89 | */
90 | protected function getCacheConfigs(array $default = [])
91 | {
92 | return $this->getConfig('housekeeper.abilities.cache', $default);
93 | }
94 |
95 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Abilities/Cache/Statically/Injections/CacheResultOrClearCacheAfter.php:
--------------------------------------------------------------------------------
1 |
18 | * @package Housekeeper\Injections\Cacheable
19 | */
20 | class CacheResultOrClearCacheAfter extends AbstractBase implements BasicInjectionContract,
21 | AfterInjectionContract
22 | {
23 | const PRIORITY = 50;
24 |
25 |
26 | /**
27 | * @return int
28 | */
29 | public function priority()
30 | {
31 | return static::PRIORITY;
32 | }
33 |
34 | /**
35 | * @param \Housekeeper\Contracts\Flow\After $afterFlow
36 | */
37 | public function handle(AfterFlowContract $afterFlow)
38 | {
39 | /**
40 | * Skip cache logic if cache has been disabled in the repository.
41 | *
42 | * @var $repository Repository|Statically
43 | */
44 | $repository = $afterFlow->getRepository();
45 | $cacheEnabled = $repository->isCacheEnabled();
46 |
47 | /**
48 | * Cache result only when action is "Read" type,
49 | * clear cache when action is "Create" or "Update".
50 | */
51 | switch ($afterFlow->getAction()->getType()) {
52 | case Action::CREATE:
53 | $this->clearCache();
54 |
55 | break;
56 | case Action::UPDATE:
57 | $this->clearCache();
58 |
59 | break;
60 | case Action::READ:
61 | $cacheEnabled && $this->remember($afterFlow);
62 |
63 | break;
64 | case Action::DELETE:
65 | $this->clearCache();
66 |
67 | break;
68 | case Action::CREATE_OR_UPDATE:
69 | $this->clearCache();
70 |
71 | break;
72 | default:
73 | break;
74 | }
75 | }
76 |
77 | /**
78 | * @param \Housekeeper\Contracts\Flow\After $afterFlow
79 | */
80 | protected function remember(AfterFlowContract $afterFlow)
81 | {
82 | $this->setCacheForAction(
83 | $afterFlow->getAction(),
84 | $afterFlow->getReturnValue()
85 | );
86 | }
87 |
88 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Action.php:
--------------------------------------------------------------------------------
1 |
11 | * @package Housekeeper
12 | */
13 | class Action implements ActionContract
14 | {
15 | /**
16 | * @var integer
17 | */
18 | protected $type;
19 |
20 | /**
21 | * @var null|array
22 | */
23 | protected $arguments;
24 |
25 | /**
26 | * @var null|string
27 | */
28 | protected $methodName;
29 |
30 | /**
31 | * @var array
32 | */
33 | protected $discriptions;
34 |
35 | /**
36 | * Action constructor.
37 | *
38 | * @param int $type
39 | * @param array $discriptions
40 | * @param null $methodName
41 | * @param array|null $arguments
42 | */
43 | public function __construct($type = ActionContract::UNKNOW,
44 | array $discriptions = [],
45 | $methodName = null,
46 | array $arguments = null)
47 | {
48 | $this->type = is_null($type) ? static::UNKNOW : $type;
49 | $this->discriptions = $discriptions;
50 |
51 | if ($methodName) {
52 | $this->setMethodName($methodName);
53 | }
54 |
55 | if (! is_null($arguments)) {
56 | $this->setArguments($arguments);
57 | }
58 | }
59 |
60 | /**
61 | * @param string $methodName
62 | */
63 | public function setMethodName($methodName)
64 | {
65 | $this->methodName = $methodName;
66 | }
67 |
68 | /**
69 | * @return null|string
70 | */
71 | public function getMethodName()
72 | {
73 | return $this->methodName;
74 | }
75 |
76 | /**
77 | * @param array $arguments
78 | */
79 | public function setArguments(array $arguments)
80 | {
81 | $this->arguments = $arguments;
82 | }
83 |
84 | /**
85 | * @return array|null
86 | */
87 | public function getArguments()
88 | {
89 | return $this->arguments;
90 | }
91 |
92 | /**
93 | * @return int
94 | */
95 | public function getType()
96 | {
97 | return $this->type;
98 | }
99 |
100 | /**
101 | * @param int $type
102 | * @return bool
103 | */
104 | public function isType($type)
105 | {
106 | return $this->type === $type;
107 | }
108 |
109 | /**
110 | * @param $discription
111 | */
112 | public function describeAs($discription)
113 | {
114 | $this->discriptions[] = $discription;
115 | }
116 |
117 | /**
118 | * @param $discription
119 | * @return bool
120 | */
121 | public function isDescribedAs($discription)
122 | {
123 | return in_array($discription, $this->discriptions);
124 | }
125 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Abilities/Guardable.php:
--------------------------------------------------------------------------------
1 |
11 | * @package Housekeeper\Abilities
12 | */
13 | trait Guardable
14 | {
15 | /**
16 | * @var bool
17 | */
18 | protected $guarded = false;
19 |
20 | /**
21 | * @return $this
22 | */
23 | public function guardUp()
24 | {
25 | $this->guarded = true;
26 |
27 | return $this;
28 | }
29 |
30 | /**
31 | * @return $this
32 | */
33 | public function guardDown()
34 | {
35 | $this->guarded = false;
36 |
37 | return $this;
38 | }
39 |
40 | /**
41 | * @return bool
42 | */
43 | public function isGuarded()
44 | {
45 | return $this->guarded;
46 | }
47 |
48 | /**
49 | * @param array $attributes
50 | * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|\Illuminate\Database\Query\Builder|static
51 | */
52 | protected function _create(array $attributes)
53 | {
54 | $model = $this->newModelInstance();
55 |
56 | if ($this->isGuarded()) {
57 | $model = $model->fill($attributes);
58 | } else {
59 | $model = $model->forceFill($attributes);
60 | }
61 |
62 | $model->save();
63 |
64 | return $model;
65 | }
66 |
67 | /**
68 | * @param $id
69 | * @param array $attributes
70 | * @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model
71 | */
72 | protected function _update($id, array $attributes)
73 | {
74 | if ($id instanceof Model) {
75 | $id = $id->getKey();
76 | }
77 |
78 | $model = $this->getModel()->findOrFail($id);
79 |
80 | if ($this->isGuarded()) {
81 | $model->fill($attributes);
82 | } else {
83 | $model->forceFill($attributes);
84 | }
85 |
86 | $model->save();
87 |
88 | return $model;
89 | }
90 |
91 | /**
92 | * @see \Housekeeper\Repository::getModel
93 | *
94 | * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
95 | */
96 | abstract protected function getModel();
97 |
98 | /**
99 | * @see \Housekeeper\Repository::newModelInstance
100 | *
101 | * Make a new Model instance.
102 | *
103 | * @return Model
104 | */
105 | abstract protected function newModelInstance();
106 |
107 | /**
108 | * @see \Housekeeper\Repository::injectIntoBefore
109 | *
110 | * @param \Housekeeper\Contracts\Injection\Before $injection
111 | * @param bool $sort
112 | * @return void
113 | */
114 | abstract protected function injectIntoBefore(\Housekeeper\Contracts\Injection\Before $injection, $sort = true);
115 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Contracts/Repository.php:
--------------------------------------------------------------------------------
1 |
9 | * @package Housekeeper\Contracts
10 | */
11 | interface Repository
12 | {
13 | /**
14 | * @return \Housekeeper\Contracts\Plan
15 | */
16 | public function getCurrentPlan();
17 |
18 | /**
19 | * This is more semantic than `applyWheres`.
20 | *
21 | * @param array $wheres
22 | * @return $this
23 | */
24 | public function whereAre(array $wheres);
25 |
26 | /**
27 | * @param array $wheres
28 | * @return mixed
29 | */
30 | public function applyWheres(array $wheres);
31 |
32 | /**
33 | * @param $column
34 | * @param string $direction
35 | * @return mixed
36 | */
37 | public function orderBy($column, $direction = 'asc');
38 |
39 | /**
40 | * @param $column
41 | * @param string $direction
42 | * @return mixed
43 | */
44 | public function applyOrderBy($column, $direction = 'asc');
45 |
46 | /**
47 | * @return mixed
48 | */
49 | public function with();
50 |
51 | /**
52 | * @param $value
53 | * @return $this
54 | */
55 | public function offset($value);
56 |
57 | /**
58 | * @param $value
59 | * @return $this
60 | */
61 | public function limit($value);
62 |
63 | /**
64 | * @param $id
65 | * @param null $column
66 | * @return mixed
67 | */
68 | public function exists($id, $column = null);
69 |
70 | /**
71 | * @param $id
72 | * @param array $columns
73 | * @return mixed
74 | */
75 | public function find($id, $columns = ['*']);
76 |
77 | /**
78 | * @param $ids
79 | * @param array $columns
80 | * @return mixed
81 | */
82 | public function findMany($ids, $columns = ['*']);
83 |
84 | /**
85 | * @param array $attributes
86 | * @return mixed
87 | */
88 | public function create(array $attributes);
89 |
90 | /**
91 | * @param $id
92 | * @return mixed
93 | */
94 | public function delete($id);
95 |
96 | /**
97 | * @param $id
98 | * @param array $attributes
99 | * @return mixed
100 | */
101 | public function update($id, array $attributes);
102 |
103 | /**
104 | * @param array $columns
105 | * @return mixed
106 | */
107 | public function all($columns = ['*']);
108 |
109 | /**
110 | * @param null $limit
111 | * @param array $columns
112 | * @param string $pageName
113 | * @param null $page
114 | * @return mixed
115 | */
116 | public function paginate($limit = null, $columns = ['*'], $pageName = 'page', $page = null);
117 |
118 | /**
119 | * @param $field
120 | * @param null $value
121 | * @param array $columns
122 | * @return mixed
123 | */
124 | public function getByField($field, $value = null, $columns = ['*']);
125 |
126 | /**
127 | * @param $field
128 | * @param null $value
129 | * @param array $columns
130 | * @return mixed
131 | */
132 | public function findByField($field, $value = null, $columns = ['*']);
133 | }
134 |
--------------------------------------------------------------------------------
/src/Housekeeper/Abilities/SoftDeletes.php:
--------------------------------------------------------------------------------
1 |
15 | * @package Housekeeper\Traits\Repository
16 | */
17 | trait SoftDeletes
18 | {
19 | /**
20 | * Include soft deletes for following use, will triggering a Reset flow.
21 | *
22 | * @return $this
23 | */
24 | public function startWithTrashed()
25 | {
26 | $this->reset(new Action(__METHOD__, [], Action::INTERNAL));
27 |
28 | $this->getCurrentPlan()->startWithTrashed();
29 |
30 | return $this;
31 | }
32 |
33 | /**
34 | * Only include soft deletes for following use, will triggering a Reset
35 | * flow.
36 | *
37 | * @return $this
38 | */
39 | public function startWithTrashedOnly()
40 | {
41 | $this->reset(new Action(__METHOD__, [], Action::INTERNAL));
42 |
43 | $this->getCurrentPlan()->startWithTrashedOnly();
44 |
45 | return $this;
46 | }
47 |
48 | /**
49 | * @param $id
50 | * @return bool|null
51 | * @throws ModelNotFoundException
52 | */
53 | public function forceDelete($id)
54 | {
55 | return $this->simpleWrap(Action::DELETE, [$this, '_forceDelete']);
56 | }
57 |
58 | /**
59 | * @param $id
60 | * @return bool|null
61 | * @throws ModelNotFoundException
62 | */
63 | protected function _forceDelete($id)
64 | {
65 | $model = $this->getModel()->findOrFail($id);
66 |
67 | $deleted = $model->forceDelete();
68 |
69 | return $deleted;
70 | }
71 |
72 | /**
73 | * Restore model which is soft-deleted.
74 | *
75 | * @return bool|null
76 | */
77 | public function restore($id)
78 | {
79 | return $this->simpleWrap(Action::UPDATE, [$this, '_restore']);
80 | }
81 |
82 | /**
83 | * @return bool|null
84 | */
85 | protected function _restore($id)
86 | {
87 | /**
88 | * @var $model Model|\Illuminate\Database\Eloquent\SoftDeletes
89 | */
90 | $model = $this->startWithTrashed()->find($id, [$this->getKeyName()]);
91 |
92 | $model->restore();
93 |
94 | return $model;
95 | }
96 |
97 | /**
98 | * @return string
99 | */
100 | abstract protected function getKeyName();
101 |
102 | /**
103 | * @param $id
104 | * @param array $columns
105 | * @return mixed
106 | * @throws \Exception
107 | */
108 | abstract public function find($id, $columns = ['*']);
109 |
110 | /**
111 | * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder
112 | */
113 | abstract protected function getModel();
114 |
115 | /**
116 | * @param $actionType
117 | * @param callable|null $function
118 | * @return mixed
119 | * @throws \Exception
120 | */
121 | abstract protected function simpleWrap($actionType, callable $function = null);
122 |
123 | /**
124 | * @param \Housekeeper\Contracts\Action $action
125 | * @return $this
126 | */
127 | abstract protected function reset(ActionContract $action);
128 |
129 | /**
130 | * @return \Housekeeper\Plan
131 | */
132 | abstract protected function getCurrentPlan();
133 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | tests/log/
2 | .settings/
3 | composer.lock
4 |
5 |
6 | # Created by .ignore support plugin (hsz.mobi)
7 | ### Composer template
8 | composer.phar
9 | vendor/
10 |
11 | # Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file
12 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
13 | # composer.lock
14 |
15 |
16 | ### JetBrains template
17 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion
18 |
19 | *.iml
20 |
21 | ## Directory-based project format:
22 | .idea/
23 | # if you remove the above rule, at least ignore the following:
24 |
25 | # User-specific stuff:
26 | # .idea/workspace.xml
27 | # .idea/tasks.xml
28 | # .idea/dictionaries
29 |
30 | # Sensitive or high-churn files:
31 | # .idea/dataSources.ids
32 | # .idea/dataSources.xml
33 | # .idea/sqlDataSources.xml
34 | # .idea/dynamic.xml
35 | # .idea/uiDesigner.xml
36 |
37 | # Gradle:
38 | # .idea/gradle.xml
39 | # .idea/libraries
40 |
41 | # Mongo Explorer plugin:
42 | # .idea/mongoSettings.xml
43 |
44 | ## File-based project format:
45 | *.ipr
46 | *.iws
47 |
48 | ## Plugin-specific files:
49 |
50 | # IntelliJ
51 | /out/
52 |
53 | # mpeltonen/sbt-idea plugin
54 | .idea_modules/
55 |
56 | # JIRA plugin
57 | atlassian-ide-plugin.xml
58 |
59 | # Crashlytics plugin (for Android Studio and IntelliJ)
60 | com_crashlytics_export_strings.xml
61 | crashlytics.properties
62 | crashlytics-build.properties
63 |
64 |
65 | ### OSX template
66 | .DS_Store
67 | .AppleDouble
68 | .LSOverride
69 |
70 | # Icon must end with two \r
71 | Icon
72 |
73 | # Thumbnails
74 | ._*
75 |
76 | # Files that might appear in the root of a volume
77 | .DocumentRevisions-V100
78 | .fseventsd
79 | .Spotlight-V100
80 | .TemporaryItems
81 | .Trashes
82 | .VolumeIcon.icns
83 |
84 | # Directories potentially created on remote AFP share
85 | .AppleDB
86 | .AppleDesktop
87 | Network Trash Folder
88 | Temporary Items
89 | .apdisk
90 |
91 |
92 | ### Windows template
93 | # Windows image file caches
94 | Thumbs.db
95 | ehthumbs.db
96 |
97 | # Folder config file
98 | Desktop.ini
99 |
100 | # Recycle Bin used on file shares
101 | $RECYCLE.BIN/
102 |
103 | # Windows Installer files
104 | *.cab
105 | *.msi
106 | *.msm
107 | *.msp
108 |
109 | # Windows shortcuts
110 | *.lnk
111 |
112 |
113 |
114 | ### JetBrains template
115 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion
116 |
117 | *.iml
118 |
119 | ## Directory-based project format:
120 | .idea/
121 | # if you remove the above rule, at least ignore the following:
122 |
123 | # User-specific stuff:
124 | # .idea/workspace.xml
125 | # .idea/tasks.xml
126 | # .idea/dictionaries
127 |
128 | # Sensitive or high-churn files:
129 | # .idea/dataSources.ids
130 | # .idea/dataSources.xml
131 | # .idea/sqlDataSources.xml
132 | # .idea/dynamic.xml
133 | # .idea/uiDesigner.xml
134 |
135 | # Gradle:
136 | # .idea/gradle.xml
137 | # .idea/libraries
138 |
139 | # Mongo Explorer plugin:
140 | # .idea/mongoSettings.xml
141 |
142 | ## File-based project format:
143 | *.ipr
144 | *.iws
145 |
146 | ## Plugin-specific files:
147 |
148 | # IntelliJ
149 | /out/
150 |
151 | # mpeltonen/sbt-idea plugin
152 | .idea_modules/
153 |
154 | # JIRA plugin
155 | atlassian-ide-plugin.xml
156 |
157 | # Crashlytics plugin (for Android Studio and IntelliJ)
158 | com_crashlytics_export_strings.xml
159 | crashlytics.properties
160 | crashlytics-build.properties
161 |
162 |
163 |
164 | ### Composer template
165 | composer.phar
166 | vendor/
167 |
168 | # Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file
169 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
170 | # composer.lock
171 |
172 |
173 |
--------------------------------------------------------------------------------
/src/Housekeeper/Abilities/Cache/Statically/CacheAdapter.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
51 | $this->redis = $redis;
52 |
53 | $this->prefix = $configs['prefix'];
54 | }
55 |
56 | /**
57 | * @return \Illuminate\Contracts\Redis\Database|\Illuminate\Redis\Database
58 | */
59 | protected function getRedis()
60 | {
61 | return $this->redis;
62 | }
63 |
64 | /**
65 | * @return \Housekeeper\Contracts\Repository
66 | */
67 | protected function getRepository()
68 | {
69 | return $this->repository;
70 | }
71 |
72 | /**
73 | * @param \Housekeeper\Contracts\Action $action
74 | * @param $value
75 | */
76 | public function setCacheForAction(ActionContract $action, $value)
77 | {
78 | $this->getRedis()->hset(
79 | $this->cacheKey(),
80 | $this->getCacheFieldForAction($action),
81 | serialize($value)
82 | );
83 | }
84 |
85 | /**
86 | * @param \Housekeeper\Contracts\Action $action
87 | * @return mixed|null
88 | */
89 | public function getCacheForAction(ActionContract $action)
90 | {
91 | $cachedValue = $this->getRedis()->hget(
92 | $this->cacheKey(),
93 | $this->getCacheFieldForAction($action)
94 | );
95 |
96 | return is_null($cachedValue) ? null : unserialize($cachedValue);
97 | }
98 |
99 | /**
100 | *
101 | */
102 | public function flush()
103 | {
104 | $this->getRedis()->del($this->cacheKey());
105 | }
106 |
107 | /**
108 | * @param \Housekeeper\Contracts\Action $action
109 | * @return string
110 | */
111 | protected function getCacheFieldForAction(ActionContract $action)
112 | {
113 | $id = md5(
114 | SmartHasher::hash($this->getRepository()->getCurrentPlan()) .
115 | $action->getMethodName() .
116 | SmartHasher::hash($action->getArguments())
117 | );
118 |
119 | return $id;
120 | }
121 |
122 | /**
123 | * @return string
124 | */
125 | protected function cacheKey()
126 | {
127 | if ( ! $this->cacheKey) {
128 | $this->cacheKey = $this->prefix .
129 | str_replace(
130 | '\\', '#',
131 | get_class($this->getRepository())
132 | );
133 | }
134 |
135 | return $this->cacheKey;
136 | }
137 | }
--------------------------------------------------------------------------------
/tests/Housekeeper/Support/InjectionContainerTest.php:
--------------------------------------------------------------------------------
1 |
22 | * @package Housekeeper\Eloquent
23 | */
24 | class InjectionContainerTest extends \PHPUnit_Framework_TestCase
25 | {
26 | /**
27 | *
28 | */
29 | protected function setUp()
30 | {
31 |
32 | }
33 |
34 | /**
35 | *
36 | */
37 | public function tearDown()
38 | {
39 | m::close();
40 | }
41 |
42 | //
43 | // /**
44 | // * @runInSeparateProcess
45 | // * @covers Housekeeper\Repository::sortAllInjections
46 | // * @covers Housekeeper\Repository::sortInjection
47 | // */
48 | // public function testSortAllInjections()
49 | // {
50 | // // mock Injections for Repository to sort
51 | // $mockInjections = $this->makeMockInjections();
52 | //
53 | // $mockRepository = $this->makeMockRepository(MockSetupRepository::class, false);
54 | //
55 | // // Mock `sortInjection` method (same as the original method) because it's private
56 | // $methodSortInjection = getUnaccessibleObjectMethod($mockRepository, 'sortInjection');
57 | // $mockRepository->shouldReceive('sortInjection')
58 | // ->andReturnUsing(function ($a, $b) use ($methodSortInjection, $mockRepository) {
59 | // return $methodSortInjection->invoke($mockRepository, $a, $b);
60 | // });
61 | //
62 | // // Replace `injections` property with mock `injections`
63 | // $mockRepositoryReflection = new \ReflectionClass($mockRepository);
64 | // $propertyInjectionsReflection = $mockRepositoryReflection->getProperty('injections');
65 | // $propertyInjectionsReflection->setAccessible(true);
66 | // $propertyInjectionsReflection->setValue($mockRepository, $mockInjections);
67 | //
68 | // // Execute `sortAllInjections` method
69 | // $methodSortAllInjections = getUnaccessibleObjectMethod($mockRepository, 'sortAllInjections');
70 | // $methodSortAllInjections->invoke($mockRepository);
71 | //
72 | // // Verify results
73 | // $sortedInjections = $propertyInjectionsReflection->getValue($mockRepository);
74 | // foreach ($sortedInjections as $group) {
75 | // $lastPriority = - 1;
76 | //
77 | // foreach ($group as $injection) {
78 | // // ascending order
79 | // $this->assertGreaterThanOrEqual($lastPriority, $injection->priority());
80 | // }
81 | // }
82 | // }
83 | //
84 | // /**
85 | // * @return array
86 | // */
87 | // protected function makeMockInjections()
88 | // {
89 | // $mockInjections = [
90 | // 'reset' => [],
91 | // 'before' => [],
92 | // 'after' => [],
93 | // ];
94 | // $priorities = [
95 | // 3, 3, 4, 5, 1,
96 | // 2, 1, 5, 4, 6,
97 | // 100, 1, 99, 2, 66,
98 | // ];
99 | // foreach (['reset', 'before', 'after'] as $group) {
100 | // for ($i = 0; $i < 5; $i ++) {
101 | // $priority = array_pop($priorities);
102 | //
103 | // $mockInjection = m::mock(MockBasicInjection::class);
104 | // $mockInjection->shouldReceive('priority')
105 | // ->andReturnUsing(function () use ($priority) {
106 | // return $priority;
107 | // });
108 | //
109 | // $mockInjections[$group][] = $mockInjection;
110 | // }
111 | // }
112 | //
113 | // return $mockInjections;
114 | // }
115 |
116 |
117 | }
118 |
119 | // ============================================================================
120 |
--------------------------------------------------------------------------------
/src/Housekeeper/Plan.php:
--------------------------------------------------------------------------------
1 |
11 | * @package Housekeeper
12 | */
13 | class Plan implements Contracts\Plan
14 | {
15 | /**
16 | * @var \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder
17 | */
18 | protected $model;
19 |
20 | /**
21 | * @var array
22 | */
23 | protected $conditions = [];
24 |
25 |
26 | /**
27 | * Plan constructor.
28 | *
29 | * @param \Illuminate\Database\Eloquent\Model $model
30 | */
31 | public function __construct(Model $model)
32 | {
33 | $this->model = $model;
34 | }
35 |
36 | /**
37 | * Every single action that would afftected returns, should add a condition.
38 | *
39 | * @param string $type
40 | * @param mixed $condition
41 | */
42 | protected function addCondition($type, $condition)
43 | {
44 | $this->conditions[] = [
45 | $type => $condition,
46 | ];
47 | }
48 |
49 | /**
50 | * @return array
51 | */
52 | public function getConditions()
53 | {
54 | return $this->conditions;
55 | }
56 |
57 | /**
58 | * @return bool
59 | */
60 | public function isEmpty()
61 | {
62 | return empty($this->conditions);
63 | }
64 |
65 | /**
66 | * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder
67 | */
68 | public function getModel()
69 | {
70 | return $this->model;
71 | }
72 |
73 | /**
74 | * @param string $column
75 | * @param string $direction
76 | * @return $this
77 | */
78 | public function applyOrderBy($column, $direction = 'asc')
79 | {
80 | /**
81 | * Save to conditons.
82 | */
83 | $this->addCondition('order by', [$column, $direction]);
84 |
85 | $this->model = $this->model->orderBy($column, $direction);
86 |
87 | return $this;
88 | }
89 |
90 | /**
91 | * @param $value
92 | * @return $this
93 | */
94 | public function applyOffset($value)
95 | {
96 | /**
97 | * Save to conditons.
98 | */
99 | $this->addCondition('offset', $value);
100 |
101 | $this->model = $this->model->offset($value);
102 |
103 | return $this;
104 | }
105 |
106 | /**
107 | * @param $value
108 | * @return $this
109 | */
110 | public function applyLimit($value)
111 | {
112 | /**
113 | * Save to conditons.
114 | */
115 | $this->addCondition('limit', $value);
116 |
117 | $this->model = $this->model->limit($value);
118 |
119 | return $this;
120 | }
121 |
122 | /**
123 | * @param array $wheres
124 | * @return $this
125 | */
126 | public function applyWheres(array $wheres)
127 | {
128 | /**
129 | * Save to conditons.
130 | */
131 | $this->addCondition('wheres', $wheres);
132 |
133 | foreach ($wheres as $key => $where) {
134 | $parameters = ($where instanceof \Closure ? [$where] : $where);
135 | $this->model = call_user_func_array([$this->model, 'where'], $parameters);
136 | }
137 |
138 | return $this;
139 | }
140 |
141 | /**
142 | * @param mixed $relations
143 | * @return $this
144 | */
145 | public function applyWith()
146 | {
147 | $arguments = func_get_args();
148 |
149 | /**
150 | * Save to conditons.
151 | */
152 | $this->addCondition('with', $arguments);
153 |
154 | $this->model = call_user_func_array(array($this->model, 'with'), $arguments);
155 |
156 | return $this;
157 | }
158 |
159 | /**
160 | * Reset and includes soft deletes for following queries.
161 | *
162 | * @return $this
163 | */
164 | public function startWithTrashed()
165 | {
166 | /**
167 | * Save to conditons.
168 | */
169 | $this->addCondition('withTrashed', 'withTrashed');
170 |
171 | $this->model = $this->model->withTrashed();
172 |
173 | return $this;
174 | }
175 |
176 | /**
177 | * Reset and only includes soft deletes for following queries.
178 | *
179 | * @return $this
180 | */
181 | public function startWithTrashedOnly()
182 | {
183 | /**
184 | * Save to conditons.
185 | */
186 | $this->addCondition('onlyTrashed', 'onlyTrashed');
187 |
188 | $this->model = $this->model->onlyTrashed();
189 |
190 | return $this;
191 | }
192 |
193 | /**
194 | * @return array
195 | */
196 | public function __sleep()
197 | {
198 | return [
199 | 'conditions',
200 | ];
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/src/Housekeeper/Support/InjectionContainer.php:
--------------------------------------------------------------------------------
1 |
18 | * @package Housekeeper\Support
19 | */
20 | class InjectionContainer
21 | {
22 | /**
23 | * @var string
24 | */
25 | const GROUP_BEFORE = 'before';
26 |
27 | /**
28 | * @var string
29 | */
30 | const GROUP_AFTER = 'after';
31 |
32 | /**
33 | * @var string
34 | */
35 | const GROUP_RESET = 'reset';
36 |
37 | /**
38 | * All injections.
39 | *
40 | * @var array
41 | */
42 | protected $groupedInjections = [
43 | self::GROUP_BEFORE => [],
44 | self::GROUP_AFTER => [],
45 | self::GROUP_RESET => [],
46 | ];
47 |
48 | /**
49 | * @param $group
50 | * @param $injection
51 | * @param bool $sort
52 | */
53 | protected function addInjection($group, $injection, $sort = true)
54 | {
55 | $this->groupedInjections[$group][] = $injection;
56 |
57 | // If need to sort all injections after inject.
58 | if ($sort) {
59 | $this->sortInjections($group);
60 | }
61 | }
62 |
63 | /**
64 | * @param \Housekeeper\Contracts\Injection\Before $injection
65 | * @param bool $sort
66 | */
67 | public function addBeforeInjection(BeforeInjectionContract $injection, $sort = true)
68 | {
69 | $this->addInjection(static::GROUP_BEFORE, $injection, $sort);
70 | }
71 |
72 | /**
73 | * @param \Housekeeper\Contracts\Injection\After $injection
74 | * @param bool $sort
75 | */
76 | public function addAfterInjection(AfterInjectionContract $injection, $sort = true)
77 | {
78 | $this->addInjection(static::GROUP_AFTER, $injection, $sort);
79 | }
80 |
81 | /**
82 | * @param \Housekeeper\Contracts\Injection\Reset $injection
83 | * @param bool $sort
84 | */
85 | public function addResetInjection(ResetInjectionContract $injection, $sort = true)
86 | {
87 | $this->addInjection(static::GROUP_RESET, $injection, $sort);
88 | }
89 |
90 | /**
91 | * @param $group
92 | * @param $flow
93 | */
94 | protected function handleFlow($group, $flow)
95 | {
96 | foreach ($this->groupedInjections[$group] as $injection) {
97 | /**
98 | * @var $injection \Housekeeper\Contracts\Injection\Before|\Housekeeper\Contracts\Injection\After|\Housekeeper\Contracts\Injection\Reset
99 | */
100 | $injection->handle($flow);
101 | }
102 | }
103 |
104 | /**
105 | * @param \Housekeeper\Contracts\Flow\Before $flow
106 | */
107 | public function handleBeforeFlow(BeforeFlowContract $flow)
108 | {
109 | $this->handleFlow(static::GROUP_BEFORE, $flow);
110 | }
111 |
112 | /**
113 | * @param \Housekeeper\Contracts\Flow\After $flow
114 | */
115 | public function handleAfterFlow(AfterFlowContract $flow)
116 | {
117 | $this->handleFlow(static::GROUP_AFTER, $flow);
118 | }
119 |
120 | /**
121 | * @param \Housekeeper\Contracts\Flow\Reset $flow
122 | */
123 | public function handleResetFlow(ResetFlowContract $flow)
124 | {
125 | $this->handleFlow(static::GROUP_RESET, $flow);
126 | }
127 |
128 | /**
129 | * Sort by priority ASC.
130 | *
131 | * @param string|null $group
132 | */
133 | public function sortInjections($group = null)
134 | {
135 | if ($group) {
136 | usort($this->groupedInjections[$group], [$this, 'sortInjection']);
137 | } else {
138 | foreach ($this->groupedInjections as $injections) {
139 | usort($injections, [$this, 'sortInjection']);
140 | }
141 | }
142 | }
143 |
144 | /**
145 | * Custom function for "usort" used by "sortAllInjections".
146 | *
147 | * @param \Housekeeper\Contracts\Injection\Basic $a
148 | * @param \Housekeeper\Contracts\Injection\Basic $b
149 | * @return int
150 | */
151 | static protected function sortInjection(BasicInjectionContract $a, BasicInjectionContract $b)
152 | {
153 | if ($a->priority() == $b->priority()) {
154 | return 0;
155 | }
156 |
157 | return ($a->priority() < $b->priority()) ? - 1 : 1;
158 | }
159 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Abilities/Eloquently.php:
--------------------------------------------------------------------------------
1 |
15 | * @package Housekeeper\Traits\Repository
16 | */
17 | trait Eloquently
18 | {
19 | /**
20 | * @param $column
21 | * @param null $operator
22 | * @param null $value
23 | * @param string $boolean
24 | * @return $this
25 | */
26 | public function where($column, $operator = null, $value = null, $boolean = 'and')
27 | {
28 | $this->whereAre([
29 | [$column, $operator, $value, $boolean],
30 | ]);
31 |
32 | return $this;
33 | }
34 |
35 | /**
36 | * @param $column
37 | * @param null $operator
38 | * @param null $value
39 | * @return $this
40 | */
41 | public function orWhere($column, $operator = null, $value = null)
42 | {
43 | $this->whereAre([
44 | [$column, $operator, $value, 'or'],
45 | ]);
46 |
47 | return $this;
48 | }
49 |
50 | /**
51 | * @param $relation
52 | * @param string $operator
53 | * @param int $count
54 | * @param string $boolean
55 | * @param \Closure|null $callback
56 | * @return $this
57 | */
58 | public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', \Closure $callback = null)
59 | {
60 | $this->whereAre([
61 | function ($query) use (&$relation, &$operator, &$count, &$boolean, &$callback) {
62 | /**
63 | * @var $query \Illuminate\Database\Eloquent\Builder
64 | */
65 | $query->has($relation, $operator, $count, $boolean, $callback);
66 | },
67 | ]);
68 |
69 | return $this;
70 | }
71 |
72 | /**
73 | * @param $relation
74 | * @param \Closure $callback
75 | * @param string $operator
76 | * @param int $count
77 | * @return $this
78 | */
79 | public function whereHas($relation, \Closure $callback, $operator = '>=', $count = 1)
80 | {
81 | return $this->has($relation, $operator, $count, 'and', $callback);
82 | }
83 |
84 | /**
85 | * @param $relation
86 | * @param \Closure|null $callback
87 | * @return \Housekeeper\Abilities\Eloquently
88 | */
89 | public function whereDoesntHave($relation, \Closure $callback = null)
90 | {
91 | return $this->has($relation, '<', 1, 'and', $callback);
92 | }
93 |
94 | /**
95 | * @param $relation
96 | * @param \Closure $callback
97 | * @param string $operator
98 | * @param int $count
99 | * @return \Housekeeper\Abilities\Eloquently
100 | */
101 | public function orWhereHas($relation, \Closure $callback, $operator = '>=', $count = 1)
102 | {
103 | return $this->has($relation, $operator, $count, 'or', $callback);
104 | }
105 |
106 | /**
107 | * @param $column
108 | * @param $values
109 | * @param string $boolean
110 | * @param bool $not
111 | * @return \Housekeeper\Abilities\Eloquently
112 | */
113 | public function whereIn($column, $values, $boolean = 'and', $not = false)
114 | {
115 | return $this->whereAre([
116 | function ($query) use ($column, $values, $boolean, $not) {
117 | /**
118 | * @var $query Builder
119 | */
120 | $query->whereIn($column, $values, $boolean, $not);
121 | },
122 | ]);
123 | }
124 |
125 | /**
126 | * @param $column
127 | * @param $values
128 | * @param string $boolean
129 | * @return \Housekeeper\Abilities\Eloquently
130 | */
131 | public function whereNotIn($column, $values, $boolean = 'and')
132 | {
133 | return $this->whereIn($column, $values, $boolean, true);
134 | }
135 |
136 | /**
137 | * @param $column
138 | * @param $values
139 | * @return \Housekeeper\Abilities\Eloquently
140 | */
141 | public function orWhereNotIn($column, $values)
142 | {
143 | return $this->whereNotIn($column, $values, 'or');
144 | }
145 |
146 | /**
147 | * @param $column
148 | * @param string $boolean
149 | * @param bool $not
150 | * @return \Housekeeper\Abilities\Eloquently
151 | */
152 | public function whereNull($column, $boolean = 'and', $not = false)
153 | {
154 | $type = $not ? 'NotNull' : 'Null';
155 |
156 | return $this->whereAre([
157 | compact('type', 'column', 'boolean'),
158 | ]);
159 | }
160 |
161 | /**
162 | * @param $column
163 | * @return \Housekeeper\Abilities\Eloquently
164 | */
165 | public function orWhereNull($column)
166 | {
167 | return $this->whereNull($column, 'or');
168 | }
169 |
170 | /**
171 | * @param $column
172 | * @param string $boolean
173 | * @return \Housekeeper\Abilities\Eloquently
174 | */
175 | public function whereNotNull($column, $boolean = 'and')
176 | {
177 | return $this->whereNull($column, $boolean, true);
178 | }
179 |
180 | /**
181 | * @param $column
182 | * @return \Housekeeper\Abilities\Eloquently
183 | */
184 | public function orWhereNotNull($column)
185 | {
186 | return $this->whereNotNull($column, 'or');
187 | }
188 | }
--------------------------------------------------------------------------------
/src/Housekeeper/Console/Generators/MakeRepositoryCommand.php:
--------------------------------------------------------------------------------
1 |
14 | * @package Housekeeper\Console\Generators
15 | */
16 | class MakeRepositoryCommand extends GeneratorCommand
17 | {
18 | /**
19 | * The console command name.
20 | *
21 | * @var string
22 | */
23 | protected $signature = 'housekeeper:make' .
24 | ' {name : The name of the repository}' .
25 | ' {--a|adjustable : Allow you to reuse queries}' .
26 | ' {--cache= : Chose from 2 strategies to caching result: statically}' .
27 | ' {--cs : Short for "--cache=statically"}' .
28 | ' {--create= : Create a new model file for the repository.}' .
29 | ' {--e|eloquently : With frequently-used Eloquent-Style query methods}' .
30 | ' {--g|guardable : Enable mass-assignment protection in model}' .
31 | ' {--metadata : Convert all result that implemented `Arrayable` to array automatically}' .
32 | ' {--model= : Specify the model used by the repository (Root Namespace "\App").}' .
33 | ' {--sd : Allow you to interact with the `SoftDeletes` trait of Eloquent.}' .
34 | ' {--vintage : With backward compatible APIs for Housekeeper `0.9.x`}';
35 |
36 | /**
37 | * The console command description.
38 | *
39 | * @var string
40 | */
41 | protected $description = 'Make a Housekeeper repository class file.';
42 |
43 | /**
44 | * @var string
45 | */
46 | protected $type = 'Repository';
47 |
48 | /**
49 | * @var array
50 | */
51 | protected $cacheAbilities = ['statically'];
52 |
53 |
54 | /**
55 | * Execute the console command.
56 | *
57 | * @return void
58 | */
59 | public function handle()
60 | {
61 | $name = $this->parseName($this->getNameInput());
62 |
63 | $path = $this->getPath($name);
64 |
65 | if ($this->alreadyExists($this->getNameInput())) {
66 | $this->error($this->type . " \"{$name}\" already exists!");
67 |
68 | return;
69 | }
70 |
71 | if (! $this->checkOptions()) {
72 | return;
73 | }
74 |
75 | $this->makeDirectory($path);
76 |
77 | $this->files->put($path, $this->buildClass($name));
78 |
79 | $this->info($this->type . " \"{$name}\" created successfully.");
80 |
81 | // If also needs a new model file
82 | if ($model = $this->option('create')) {
83 | $this->call('make:model', ['name' => $model]);
84 | }
85 | }
86 |
87 | /**
88 | * @return bool
89 | */
90 | protected function checkOptions()
91 | {
92 | $cache = $this->option('cache');
93 |
94 | if ($cache && ! in_array($cache, $this->cacheAbilities)) {
95 | $this->error("The \"cache\" option must be one of these: \"" . implode(',', $this->cacheAbilities) . "\".");
96 |
97 | return false;
98 | }
99 |
100 | return true;
101 | }
102 |
103 | /**
104 | * Build the class with the given name.
105 | *
106 | * @param string $name
107 | * @return string
108 | */
109 | protected function buildClass($name)
110 | {
111 | $stub = $this->files->get($this->getStub());
112 |
113 | return $this
114 | ->replaceNamespace($stub, $name)
115 | ->replaceModel($stub)
116 | ->replaceAbility($stub)
117 | ->replaceClass($stub, $name);
118 | }
119 |
120 | /**
121 | * @param $stub
122 | * @return $this
123 | */
124 | protected function replaceModel(&$stub)
125 | {
126 | $model = '';
127 |
128 | if ($model = ($this->option('create') ?: $this->option('model'))) {
129 | $rootNamespace = $this->getLaravel()->getNamespace();
130 | $modelNamespace = (Str::startsWith($model, $rootNamespace) ? '\\' : '\\App\\');
131 | $model = "return {$modelNamespace}{$model}::class;";
132 | } else {
133 | $model = '//';
134 | }
135 |
136 | $stub = str_replace('DummyModel', $model, $stub);
137 |
138 | return $this;
139 | }
140 |
141 | /**
142 | * @param $stub
143 | * @return $this
144 | */
145 | protected function replaceAbility(&$stub)
146 | {
147 | $use = '';
148 | $trait = '';
149 |
150 | $traits = [];
151 |
152 | if ($this->option('adjustable')) {
153 | $use .= "use Housekeeper\\Abilities\\Adjustable;\n";
154 | $traits[] = "Adjustable";
155 | }
156 |
157 | if ($cache = $this->option('cache')) {
158 | $cache = ucfirst($cache);
159 |
160 | $use .= "use Housekeeper\\Abilities\\Cache{$cache};\n";
161 | $traits[] = "Cache$cache";
162 | } elseif ($this->option('cs')) {
163 | $use .= "use Housekeeper\\Abilities\\CacheStatically;\n";
164 | $traits[] = 'CacheStatically';
165 | }
166 |
167 | if ($this->option('guardable')) {
168 | $use .= "use Housekeeper\\Abilities\\Guardable;\n";
169 | $traits[] = "Guardable";
170 | }
171 |
172 | if ($this->option('metadata')) {
173 | $use .= "use Housekeeper\\Abilities\\Metadata;\n";
174 | $traits[] = "Metadata";
175 | }
176 |
177 | if ($this->option('eloquently')) {
178 | $use .= "use Housekeeper\\Abilities\\Eloquently;\n";
179 | $traits[] = "Eloquently";
180 | }
181 |
182 | if ($this->option('vintage')) {
183 | $use .= "use Housekeeper\\Abilities\\Vintage;\n";
184 | $traits[] = "Vintage";
185 | }
186 |
187 | if ($this->option('sd')) {
188 | $use .= "use Housekeeper\\Abilities\\SoftDeletes;\n";
189 | $traits[] = "SoftDeletes";
190 | }
191 |
192 | if (! empty($traits)) {
193 | $trait = "\n use " . implode(',', $traits) . ";\n";
194 | }
195 |
196 | $stub = str_replace('DummyUse', $use, $stub);
197 | $stub = str_replace('DummyTrait', $trait, $stub);
198 |
199 | return $this;
200 | }
201 |
202 | /**
203 | * Get the stub file for the generator.
204 | *
205 | * @return string
206 | */
207 | protected function getStub()
208 | {
209 | return __DIR__ . '/stubs/repository.stub';
210 | }
211 |
212 | /**
213 | * Get the default namespace for the class.
214 | *
215 | * @param string $rootNamespace
216 | * @return string
217 | */
218 | protected function getDefaultNamespace($rootNamespace)
219 | {
220 | $config = $this->getLaravel()->make('config');
221 |
222 | $namespace = '\\' .
223 | trim(
224 | str_replace(
225 | '/', '\\',
226 | $config['housekeeper']['directory']
227 | ),
228 | '\\'
229 | );
230 |
231 | return $rootNamespace . $namespace;
232 | }
233 |
234 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/src/Housekeeper/Repository.php:
--------------------------------------------------------------------------------
1 |
27 | * @package Housekeeper
28 | * @version 2.3.1
29 | */
30 | abstract class Repository implements RepositoryContract
31 | {
32 | /**
33 | * The name of `Boot Method`.
34 | */
35 | const BOOT_METHOD = 'boot';
36 |
37 | /**
38 | * @var \Illuminate\Contracts\Foundation\Application
39 | */
40 | protected $app;
41 |
42 | /**
43 | * @var string
44 | */
45 | protected $fullModelClassName;
46 |
47 | /**
48 | * @var int
49 | */
50 | protected $planStep;
51 |
52 | /**
53 | * @var \Housekeeper\Plan[]
54 | */
55 | protected $plans;
56 |
57 | /**
58 | * @var \Housekeeper\Plan
59 | */
60 | protected $defaultPlan;
61 |
62 | /**
63 | * @var InjectionContainer
64 | */
65 | protected $injectionContainer;
66 |
67 | /**
68 | * Page size for pagination.
69 | *
70 | * @var int
71 | */
72 | protected $perPage;
73 |
74 |
75 | /**
76 | * Specify the full class name of model for this repository.
77 | *
78 | * @return string
79 | */
80 | abstract protected function model();
81 |
82 | /**
83 | * Developer could write a `Boot Method` instead `__construct` in child
84 | * class to make program easier.
85 | *
86 | * @param \Illuminate\Contracts\Foundation\Application $app
87 | */
88 | public function __construct(Application $app)
89 | {
90 | $this->setApp($app);
91 |
92 | $this->initialize();
93 |
94 | $this->callBootable();
95 |
96 | // All official traits for Repository are injecting Injection without
97 | // sorting for better performance, so when injecting finished, then sort
98 | // them at once.
99 | $this->sortAllInjections();
100 |
101 | // Call the `Boot Method` fo the child with Dependency Injection process
102 | // if that method exists.
103 | // This provide an easy way to add custom logic that will be executed
104 | // when repository been created.
105 | $this->callBoot();
106 |
107 | // Reset to prepare everything that would be used.
108 | $this->reset(new Action(Action::INTERNAL, [], __METHOD__));
109 | }
110 |
111 | /**
112 | *
113 | */
114 | protected function callBoot()
115 | {
116 | if (method_exists($this, static::BOOT_METHOD)) {
117 | $this->getApp()->call([$this, static::BOOT_METHOD]);
118 | }
119 | }
120 |
121 | /**
122 | * @param \Illuminate\Contracts\Foundation\Application $app
123 | */
124 | protected function setApp(Application $app)
125 | {
126 | $this->app = $app;
127 | }
128 |
129 | /**
130 | * @return \Illuminate\Contracts\Foundation\Application
131 | */
132 | protected function getApp()
133 | {
134 | return $this->app;
135 | }
136 |
137 | /**
138 | * @return string
139 | */
140 | protected function getKeyName()
141 | {
142 | return $this->newModelInstance()->getKeyName();
143 | }
144 |
145 | /**
146 | * Make a new Model instance.
147 | *
148 | * @return Model
149 | */
150 | protected function newModelInstance()
151 | {
152 | if (is_null($this->fullModelClassName)) {
153 | $this->fullModelClassName = $this->model();
154 | }
155 |
156 | return new $this->fullModelClassName;
157 | }
158 |
159 | /**
160 | * Read configure from configure file, if it's not exists, "default" will be
161 | * returned.
162 | *
163 | * @param $key
164 | * @param null $default
165 | * @return mixed
166 | */
167 | protected function getConfig($key, $default = null)
168 | {
169 | $config = $this->getApp()->make('config');
170 |
171 | return $config->get($key, $default);
172 | }
173 |
174 | /**
175 | * Validate the model that provided by `model` method, and load configures.
176 | */
177 | protected function initialize()
178 | {
179 | $this->injectionContainer = new InjectionContainer();
180 |
181 | $model = $this->newModelInstance();
182 |
183 | // The model instance must be an instance of `Model` class from
184 | // `Laravel`, otherwise just throw an exception.
185 | if (! $model instanceof Model) {
186 | throw new RepositoryException(
187 | "Class \"" . get_class($model) . "\" must be an instance of " . Model::class
188 | );
189 | }
190 |
191 | // Load configures from `housekeeper.php` or just use default settings.
192 | $this->perPage = $this->getConfig('housekeeper.paginate.per_page', 15);
193 | }
194 |
195 | /**
196 | * @return \Housekeeper\Support\InjectionContainer
197 | */
198 | protected function getInjectionContainer()
199 | {
200 | return $this->injectionContainer;
201 | }
202 |
203 | /**
204 | * Call all methods that name start with "boot" (must followed by an
205 | * upper-case latter) with DI process.
206 | * This allow us to encapsulate injecting logics in trait.
207 | */
208 | protected function callBootable()
209 | {
210 | $reflection = new \ReflectionClass($this);
211 |
212 | foreach ($reflection->getMethods() as $method) {
213 | $methodName = $method->getName();
214 |
215 | // Method name has to start with "boot" and followed by an
216 | // upper-case latter.
217 | if (preg_match('/^boot[A-Z]/', $methodName)) {
218 | $this->getApp()->call([$this, $methodName]);
219 | }
220 | }
221 | }
222 |
223 | /**
224 | * Reset the Plan object for the next use.
225 | */
226 | protected function resetPlan()
227 | {
228 | $this->defaultPlan = new Plan($this->newModelInstance());
229 | $this->plans = [];
230 | $this->planStep = null;
231 | }
232 |
233 | /**
234 | * @return \Housekeeper\Plan
235 | */
236 | public function getCurrentPlan()
237 | {
238 | return $this->defaultPlan ?: $this->plans[$this->planStep];
239 | }
240 |
241 | /**
242 | * @param $offset
243 | */
244 | protected function dropPlan($offset)
245 | {
246 | unset($this->plans[$offset]);
247 | }
248 |
249 | /**
250 | * @return int
251 | */
252 | protected function newPlan()
253 | {
254 | if ($this->defaultPlan) {
255 | $offset = $this->planStep = 0;
256 |
257 | $this->plans[$offset] = $this->defaultPlan;
258 | $this->defaultPlan = null;
259 | } else {
260 | $offset = ++ $this->planStep;
261 | $this->plans[$offset] = new Plan($this->newModelInstance());
262 | }
263 |
264 | return $offset;
265 | }
266 |
267 | /**
268 | * @return int
269 | */
270 | private function getCurrentWrappedMethodIndex()
271 | {
272 | return count($this->plans);
273 | }
274 |
275 | /**
276 | * Get model instance from Plan.
277 | *
278 | * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
279 | */
280 | protected function getModel()
281 | {
282 | return $this->getCurrentPlan()->getModel();
283 | }
284 |
285 | /**
286 | * @param \Housekeeper\Contracts\Action $action
287 | * @return $this
288 | */
289 | protected function reset(ActionContract $action)
290 | {
291 | /**
292 | * Conditions are use for identify each method call.
293 | */
294 | $this->resetPlan();
295 |
296 | /**
297 | * Make a Reset Flow object.
298 | */
299 | $flow = new ResetFlow($this, $action, $this->getCurrentWrappedMethodIndex());
300 |
301 | /**
302 | * Execute all `Reset` Injections.
303 | */
304 | $this->getInjectionContainer()->handleResetFlow($flow);
305 |
306 | return $this;
307 | }
308 |
309 | /**
310 | * @param \Housekeeper\Action $action
311 | * @return \Housekeeper\Flows\Before
312 | */
313 | protected function before(Action $action)
314 | {
315 | /**
316 | * Make a Before Flow ojbect.
317 | */
318 | $flow = new BeforeFlow($this, $action, $this->getCurrentWrappedMethodIndex());
319 |
320 | /**
321 | * Execute all `Before` Injections.
322 | */
323 | $this->getInjectionContainer()->handleBeforeFlow($flow);
324 |
325 | return $flow;
326 | }
327 |
328 | /**
329 | * @param \Housekeeper\Action $action
330 | * @param $returnValue
331 | * @return \Housekeeper\Flows\After
332 | */
333 | protected function after(Action $action, $returnValue)
334 | {
335 | // Make a After Flow ojbect.
336 | $flow = new AfterFlow($this, $action, $this->getCurrentWrappedMethodIndex(), $returnValue);
337 |
338 | // Execute all `After` Injections.
339 | $this->getInjectionContainer()->handleAfterFlow($flow);
340 |
341 | return $flow;
342 | }
343 |
344 | /**
345 | * @param callable $function
346 | * @param array $args
347 | * @param int|ActionContract $actionType
348 | * @return mixed
349 | * @throws \Exception
350 | */
351 | protected function wrap(callable $function,
352 | array $args,
353 | $actionType = ActionContract::UNKNOW)
354 | {
355 | // Prepare a Plan object for this wrapped function and returns the
356 | // offset. This will allow you to call another wrapped internal function
357 | // that even had queries like "$this->getModel()->where('name', 'kid')"
358 | // without any affection to each other.
359 | $planOffset = $this->newPlan();
360 |
361 | // Action indecated this method calling.
362 | $action = ($actionType instanceof ActionContract) ?
363 | $actionType :
364 | new Action(
365 | $actionType,
366 | [],
367 | $this->getMethodNameOfCallable($function),
368 | $args
369 | );
370 |
371 | // First it's the Before Flow, if there has any returned in this Flow,
372 | // then use it as the final returns, jump to the Reset Flow and return
373 | // the result.
374 | $beforeFlow = $this->before($action);
375 |
376 | if ($beforeFlow->hasReturnValue()) {
377 | $this->reset($action);
378 |
379 | return $beforeFlow->getReturnValue();
380 | }
381 |
382 | // Next, execute the wrapped function and goes to the After Flow.
383 | try {
384 | $result = call_user_func_array($function, $action->getArguments());
385 |
386 | // After wrapped function executed, it's the After Flow. In this
387 | // Flow, injection may alter returns, thus the final returns are
388 | // come from the After Flow.
389 | $afterFlow = $this->after($action, $result);
390 |
391 | // Release memory of the Plan, since it will not be used anymore.
392 | $this->dropPlan($planOffset);
393 |
394 | return $afterFlow->getReturnValue();
395 | } catch (\Exception $e) {
396 | // Bubble up the exception.
397 | throw $e;
398 | } finally {
399 | // No matter what happens, must go to the Reset Flow.
400 | $this->reset($action);
401 | }
402 | }
403 |
404 | /**
405 | * @param callable $function
406 | * @return string
407 | */
408 | private function getMethodNameOfCallable(callable $function)
409 | {
410 | return ($function instanceof \Closure) ?
411 | '\\Closure' :
412 | $function[1];
413 | }
414 |
415 | /**
416 | * @param int|ActionContract $actionType
417 | * @param callable|null $function
418 | * @return mixed
419 | * @throws \Exception
420 | */
421 | protected function simpleWrap($actionType, callable $function = null)
422 | {
423 | $caller = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2)[1];
424 |
425 | return $this->wrap(
426 | ($function ?: $this->traceToRealMethod($caller['function'])),
427 | $caller['args'],
428 | $actionType
429 | );
430 | }
431 |
432 | /**
433 | * Get the real method that will be executed.
434 | *
435 | * By convention, the real method should be named start with an underscore
436 | * follow by the cover method's name, for instance: cover method named
437 | * "getUserName", so the real method should be "_getUserName".
438 | *
439 | * @param null|string $coverMethodName
440 | * @return Callable|array
441 | */
442 | protected function traceToRealMethod($coverMethodName = null)
443 | {
444 | if (! $coverMethodName) {
445 | $coverMethodName = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function'];
446 | }
447 |
448 | return [$this, "_{$coverMethodName}"];
449 | }
450 |
451 | /**
452 | * @param \Housekeeper\Contracts\Injection\Before $injection
453 | * @param bool $sort
454 | */
455 | protected function injectIntoBefore(BeforeInjectionContract $injection, $sort = true)
456 | {
457 | $this->getInjectionContainer()->addBeforeInjection($injection, $sort);
458 | }
459 |
460 | /**
461 | * @param \Housekeeper\Contracts\Injection\After $injection
462 | * @param bool $sort
463 | */
464 | protected function injectIntoAfter(AfterInjectionContract $injection, $sort = true)
465 | {
466 | $this->getInjectionContainer()->addAfterInjection($injection, $sort);
467 | }
468 |
469 | /**
470 | * @param \Housekeeper\Contracts\Injection\Reset $injection
471 | * @param bool $sort
472 | */
473 | protected function injectIntoReset(ResetInjectionContract $injection, $sort = true)
474 | {
475 | $this->getInjectionContainer()->addResetInjection($injection, $sort);
476 | }
477 |
478 | /**
479 | * Sort all event handlers by priority ASC.
480 | */
481 | protected function sortAllInjections()
482 | {
483 | $this->getInjectionContainer()->sortInjections();
484 | }
485 |
486 | /**
487 | * This is more semantic than `applyWheres`.
488 | *
489 | * @param array $wheres
490 | * @return $this
491 | */
492 | public function whereAre(array $wheres)
493 | {
494 | $this->getCurrentPlan()->applyWheres($wheres);
495 |
496 | return $this;
497 | }
498 |
499 | /**
500 | * @param array $wheres
501 | * @return $this
502 | */
503 | public function applyWheres(array $wheres)
504 | {
505 | return $this->whereAre($wheres);
506 | }
507 |
508 | /**
509 | * @param $column
510 | * @param string $direction
511 | * @return $this
512 | */
513 | public function orderBy($column, $direction = 'asc')
514 | {
515 | $this->getCurrentPlan()->applyOrderBy($column, $direction);
516 |
517 | return $this;
518 | }
519 |
520 | /**
521 | * Same as the `orderBy`.
522 | *
523 | * @param $column
524 | * @param string $direction
525 | * @return $this
526 | */
527 | public function applyOrderBy($column, $direction = 'asc')
528 | {
529 | return $this->orderBy($column, $direction);
530 | }
531 |
532 | /**
533 | * Set the relationships that should be eager loaded, just like Eloquent.
534 | *
535 | * @return $this
536 | */
537 | public function with()
538 | {
539 | call_user_func_array([$this->getCurrentPlan(), 'applyWith'], func_get_args());
540 |
541 | return $this;
542 | }
543 |
544 | /**
545 | * @param $value
546 | * @return $this
547 | */
548 | public function offset($value)
549 | {
550 | $this->getCurrentPlan()->applyOffset($value);
551 |
552 | return $this;
553 | }
554 |
555 | /**
556 | * @param $value
557 | * @return $this
558 | */
559 | public function limit($value)
560 | {
561 | $this->getCurrentPlan()->applyLimit($value);
562 |
563 | return $this;
564 | }
565 |
566 | /**
567 | * @param $id
568 | * @param null $primaryKeyName
569 | * @return mixed
570 | * @throws \Exception
571 | */
572 | public function exists($id, $primaryKeyName = null)
573 | {
574 | return $this->simpleWrap(Action::READ, [$this, '_exists']);
575 | }
576 |
577 | /**
578 | * @param $id
579 | * @param null $primaryKeyName
580 | * @return bool
581 | */
582 | protected function _exists($id, $primaryKeyName = null)
583 | {
584 | /**
585 | * Fix auto-completion.
586 | *
587 | * @var \Illuminate\Database\Query\Builder $model
588 | */
589 | $model = $this->getModel();
590 |
591 | $primaryKeyName = ($primaryKeyName ?: $model->getKeyName());
592 |
593 | return $model->where($primaryKeyName, $id)->exists();
594 | }
595 |
596 | /**
597 | * @param string $columns
598 | * @return int
599 | */
600 | public function count($columns = '*')
601 | {
602 | return $this->simpleWrap(Action::READ, [$this, '_count']);
603 | }
604 |
605 | /**
606 | * @param string $columns
607 | * @return int
608 | */
609 | protected function _count($columns = '*')
610 | {
611 | return $this->getModel()->count($columns);
612 | }
613 |
614 | /**
615 | * @param $id
616 | * @param array $columns
617 | * @return Model
618 | * @throws \Exception
619 | */
620 | public function find($id, $columns = ['*'])
621 | {
622 | return $this->simpleWrap(Action::READ, [$this, '_find']);
623 | }
624 |
625 | /**
626 | * Find data by id
627 | *
628 | * @param $id
629 | * @param array $columns
630 | * @return Model
631 | */
632 | protected function _find($id, $columns = ['*'])
633 | {
634 | return $this->getModel()->findOrFail($id, $columns);
635 | }
636 |
637 | /**
638 | * Same as the "findMany" method of Eloquent.
639 | *
640 | * @param array $ids
641 | * @param array $columns
642 | * @return EloquentCollection
643 | */
644 | public function findMany($ids, $columns = ['*'])
645 | {
646 | return $this->simpleWrap(Action::READ, [$this, '_findMany']);
647 | }
648 |
649 | /**
650 | * @param array $ids
651 | * @param array $columns
652 | * @return EloquentCollection
653 | */
654 | protected function _findMany($ids, $columns = ['*'])
655 | {
656 | return $this->getModel()->findMany($ids, $columns);
657 | }
658 |
659 | /**
660 | * Save a new model and return the instance, like the same method of
661 | * Eloquent.
662 | *
663 | * @param array $attributes
664 | * @return Model
665 | * @throws \Exception
666 | */
667 | public function create(array $attributes)
668 | {
669 | return $this->simpleWrap(Action::CREATE, [$this, '_create']);
670 | }
671 |
672 | /**
673 | * Save a new entity in repository
674 | *
675 | * @param array $attributes
676 | * @return Model
677 | */
678 | protected function _create(array $attributes)
679 | {
680 | $model = $this->newModelInstance()->forceFill($attributes);
681 |
682 | $model->save();
683 |
684 | return $model;
685 | }
686 |
687 | /**
688 | * @param $id
689 | * @return mixed
690 | * @throws ModelNotFoundException
691 | */
692 | public function delete($id)
693 | {
694 | return $this->simpleWrap(Action::DELETE, [$this, '_delete']);
695 | }
696 |
697 | /**
698 | * @param $id
699 | * @return bool|null
700 | * @throws ModelNotFoundException
701 | */
702 | protected function _delete($id)
703 | {
704 | $model = $this->getModel()->findOrFail($id);
705 |
706 | $deleted = $model->delete();
707 |
708 | return $deleted;
709 | }
710 |
711 | /**
712 | * Update a record in the database, could use a model as $id.
713 | *
714 | * @param mixed $id
715 | * @param array $attributes
716 | * @return Model
717 | */
718 | public function update($id, array $attributes)
719 | {
720 | return $this->simpleWrap(Action::UPDATE, [$this, '_update']);
721 | }
722 |
723 | /**
724 | * @param mixed $id
725 | * @param array $attributes
726 | * @return Model
727 | */
728 | protected function _update($id, array $attributes)
729 | {
730 | if ($id instanceof Model) {
731 | $id = $id->getKey();
732 | }
733 |
734 | $model = $this->getModel()
735 | ->findOrFail($id)
736 | ->forceFill($attributes);
737 |
738 | $model->save();
739 |
740 | return $model;
741 | }
742 |
743 | /**
744 | * Retrieve first data of repository
745 | *
746 | * @param array $columns
747 | * @return Model
748 | */
749 | public function first($columns = ['*'])
750 | {
751 | return $this->simpleWrap(Action::READ, [$this, '_first']);
752 | }
753 |
754 | /**
755 | * @param array $columns
756 | * @return Model
757 | */
758 | protected function _first($columns = ['*'])
759 | {
760 | return $this->getModel()->first($columns);
761 | }
762 |
763 | /**
764 | * Retrieve all data of repository
765 | *
766 | * @param array $columns
767 | * @return EloquentCollection
768 | */
769 | public function all($columns = ['*'])
770 | {
771 | return $this->simpleWrap(Action::READ, [$this, '_all']);
772 | }
773 |
774 | /**
775 | * @param array $columns
776 | * @return EloquentCollection
777 | */
778 | protected function _all($columns = ['*'])
779 | {
780 | return $this->getModel()->get($columns);
781 | }
782 |
783 | /**
784 | * Same as the "paginate" method of Eloquent.
785 | *
786 | * @param int|null $limit
787 | * @param array $columns
788 | * @return LengthAwarePaginator
789 | */
790 | public function paginate($limit = null, $columns = ['*'], $pageName = 'page', $page = null)
791 | {
792 | return $this->simpleWrap(Action::READ, [$this, '_paginate']);
793 | }
794 |
795 | /**
796 | * @param int|null $limit
797 | * @param array $columns
798 | * @return LengthAwarePaginator
799 | */
800 | protected function _paginate($limit = null, $columns = ['*'], $pageName = 'page', $page = null)
801 | {
802 | $limit = $limit ?: $this->perPage;
803 |
804 | return $this->getModel()->paginate($limit, $columns, $pageName, $page);
805 | }
806 |
807 | /**
808 | * Get models by a simple equality query.
809 | *
810 | * @param $field
811 | * @param null $value
812 | * @param array $columns
813 | * @return array|EloquentCollection
814 | */
815 | public function getByField($field, $value = null, $columns = ['*'])
816 | {
817 | return $this->simpleWrap(Action::READ, [$this, '_getByField']);
818 | }
819 |
820 | /**
821 | * @param $field
822 | * @param null $value
823 | * @param array $columns
824 | * @return array|EloquentCollection
825 | */
826 | protected function _getByField($field, $value = null, $columns = ['*'])
827 | {
828 | return $this->getModel()->where($field, '=', $value)->get($columns);
829 | }
830 |
831 | /**
832 | * @deprecated This is not a frequently used method.
833 | *
834 | * Get one model by a simple equality query.
835 | *
836 | * @param $field
837 | * @param null $value
838 | * @param array $columns
839 | * @return Model
840 | * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
841 | */
842 | public function findByField($field, $value = null, $columns = ['*'])
843 | {
844 | return $this->simpleWrap(Action::READ, [$this, '_findByField']);
845 | }
846 |
847 | /**
848 | * @param $field
849 | * @param null $value
850 | * @param array $columns
851 | * @return Model
852 | * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
853 | */
854 | protected function _findByField($field, $value = null, $columns = ['*'])
855 | {
856 | return $this->getModel()->where($field, '=', $value)->firstOrFail($columns);
857 | }
858 | }
859 |
--------------------------------------------------------------------------------
/tests/Housekeeper/RepositoryTest.php:
--------------------------------------------------------------------------------
1 |
22 | * @package Housekeeper\Eloquent
23 | */
24 | class RepositoryTest extends \PHPUnit_Framework_TestCase
25 | {
26 | /**
27 | *
28 | */
29 | protected function setUp()
30 | {
31 |
32 | }
33 |
34 | /**
35 | *
36 | */
37 | public function tearDown()
38 | {
39 | m::close();
40 | }
41 |
42 | /**
43 | * @runInSeparateProcess
44 | * @covers Housekeeper\Repository::setApp
45 | * @covers Housekeeper\Repository::getApp
46 | */
47 | public function testSetAppAndGetApp()
48 | {
49 | $mockRepository = $this->makeMockRepository(MockSetupRepository::class, true);
50 |
51 | $mockApp = $this->makeMockApplication();
52 |
53 | $methodSetApp = getUnaccessibleObjectMethod($mockRepository, 'setApp');
54 | $methodGetApp = getUnaccessibleObjectMethod($mockRepository, 'getApp');
55 |
56 | $appFromRepository = $methodGetApp->invoke($mockRepository);
57 | $this->assertInstanceOf('Illuminate\Contracts\Foundation\Application', $appFromRepository);
58 |
59 | $methodSetApp->invoke($mockRepository, $mockApp);
60 | $appFromRepositoryLater = $methodGetApp->invoke($mockRepository);
61 | $this->assertEquals($mockApp, $appFromRepositoryLater);
62 | }
63 |
64 | /**
65 | * @runInSeparateProcess
66 | * @covers Housekeeper\Repository::getConfig
67 | */
68 | public function testGetConfig()
69 | {
70 | $keyShouldBePassed = 'aaron';
71 | $defaultShouldBePassed = 'jan';
72 | $keyPassed = false;
73 | $defaultPassed = false;
74 |
75 | $mockRepository = $this->makeMockRepository(MockSetupRepository::class, false);
76 |
77 | $mockConfig = m::mock('Illuminate\Config\Repository');
78 | $mockConfig->shouldReceive('get')
79 | ->andReturnUsing(function ($key, $default) use ($keyShouldBePassed, $defaultShouldBePassed, &$keyPassed, &$defaultPassed) {
80 | if ($key === $keyShouldBePassed) {
81 | $keyPassed = true;
82 | }
83 |
84 | if ($default === $defaultShouldBePassed) {
85 | $defaultPassed = true;
86 | }
87 | });
88 |
89 | $mockApplication = m::mock('Illuminate\Contracts\Foundation\Application');
90 | $mockApplication->shouldReceive('make')
91 | ->with('config')
92 | ->andReturn($mockConfig);
93 |
94 | // Set Application instance manually
95 | $methodSetApp = getUnaccessibleObjectMethod($mockRepository, 'setApp');
96 | $methodSetApp->invoke($mockRepository, $mockApplication);
97 |
98 | $methodGetConfig = getUnaccessibleObjectMethod($mockRepository, 'getConfig');
99 | $methodGetConfig->invoke($mockRepository, $keyShouldBePassed, $defaultShouldBePassed);
100 |
101 | $this->assertTrue($keyPassed);
102 | $this->assertTrue($defaultPassed);
103 | }
104 |
105 | /**
106 | * @covers Housekeeper\Repository::newModelInstance
107 | */
108 | public function testNewModelInstance()
109 | {
110 | $methodModelCalled = false;
111 |
112 | $mockRepository = m::mock(MockSetupRepository::class);
113 | $mockRepository->makePartial()
114 | ->shouldAllowMockingProtectedMethods();
115 |
116 | $mockRepository->shouldReceive('model')
117 | ->once()
118 | ->andReturnUsing(function () use (&$methodModelCalled) {
119 | $methodModelCalled = true;
120 |
121 | return MockModel::class;
122 | });
123 |
124 | $methodNewModelInstance = getUnaccessibleObjectMethod($mockRepository, 'newModelInstance');
125 | $model = $methodNewModelInstance->invoke($mockRepository);
126 |
127 | $this->assertInstanceOf(MockModel::class, $model);
128 | $this->assertTrue($methodModelCalled);
129 |
130 | $model = $methodNewModelInstance->invoke($mockRepository);
131 |
132 | $this->assertInstanceOf(MockModel::class, $model);
133 | }
134 |
135 | /**
136 | * @covers Housekeeper\Repository::initialize
137 | */
138 | public function testInitializeInNormal()
139 | {
140 | $methodNewModelInstanceCalled = false;
141 |
142 | $fakeConfigs = [
143 | 'housekeeper.paginate.per_page' => 10,
144 | ];
145 |
146 | $mockRepository = $this->makeMockRepository(MockSetupRepository::class, false);
147 |
148 | $mockRepository->shouldReceive('newModelInstance')
149 | ->andReturnUsing(function () use (&$methodNewModelInstanceCalled) {
150 | $methodNewModelInstanceCalled = true;
151 |
152 | return m::mock('Illuminate\Database\Eloquent\Model');
153 | });
154 |
155 | $mockRepository->shouldReceive('getConfig')
156 | ->andReturnUsing(function ($key, $default) use ($fakeConfigs) {
157 | return $fakeConfigs[$key];
158 | });
159 |
160 | $methodInitialize = getUnaccessibleObjectMethod($mockRepository, 'initialize');
161 | $methodInitialize->invoke($mockRepository);
162 |
163 | $attributePerPage = getUnaccessibleObjectPropertyValue($mockRepository, 'perPage');
164 | $this->assertEquals($fakeConfigs['housekeeper.paginate.per_page'], $attributePerPage);
165 | }
166 |
167 | /**
168 | * @covers Housekeeper\Repository::initialize
169 | * @expectedException \Housekeeper\Exceptions\RepositoryException
170 | */
171 | public function testInitializeExpectException()
172 | {
173 | $methodNewModelInstanceCalled = false;
174 |
175 | $fakeConfigs = [
176 | 'housekeeper.paginate.per_page' => 10,
177 | ];
178 |
179 | $mockRepository = m::mock(MockSetupRepository::class);
180 | $mockRepository->makePartial()
181 | ->shouldAllowMockingProtectedMethods();
182 |
183 | $mockRepository->shouldReceive('newModelInstance')
184 | ->andReturnUsing(function () use (&$methodNewModelInstanceCalled) {
185 | $methodNewModelInstanceCalled = true;
186 |
187 | return new \stdClass();
188 | });
189 |
190 | $mockRepository->shouldReceive('getConfig')
191 | ->andReturnUsing(function ($key, $default) use ($fakeConfigs) {
192 | return $fakeConfigs[$key];
193 | });
194 |
195 | $methodInitialize = getUnaccessibleObjectMethod($mockRepository, 'initialize');
196 | $methodInitialize->invoke($mockRepository);
197 | }
198 |
199 | /**
200 | * @runInSeparateProcess
201 | * @covers Housekeeper\Repository::callBootable
202 | */
203 | public function testCallBootable()
204 | {
205 | $methodBootTestOneCalled = false;
206 | $methodBootTestTwoCalled = false;
207 |
208 | $mockApplication = m::mock('Illuminate\Contracts\Foundation\Application');
209 | $mockRepository = $this->makeMockRepository(MockSetupRepository::class, false);
210 |
211 | $mockApplication->shouldReceive('call')
212 | ->andReturnUsing(function ($callable) use (&$methodBootTestOneCalled, &$methodBootTestTwoCalled) {
213 | list($object, $methodName) = $callable;
214 |
215 | if ($methodName == 'bootTestOne') {
216 | $methodBootTestOneCalled = true;
217 | } elseif ($methodName == 'bootTestTwo') {
218 | $methodBootTestTwoCalled = true;
219 | }
220 | });
221 | $methodSetApp = getUnaccessibleObjectMethod($mockRepository, 'setApp');
222 | $methodSetApp->invoke($mockRepository, $mockApplication);
223 |
224 | $methodCallBootable = getUnaccessibleObjectMethod($mockRepository, 'callBootable');
225 | $methodCallBootable->invoke($mockRepository);
226 |
227 | $this->assertTrue($methodBootTestOneCalled);
228 | $this->assertTrue($methodBootTestTwoCalled);
229 | }
230 |
231 | /**
232 | * @runInSeparateProcess
233 | * @covers Housekeeper\Repository::callBoot
234 | */
235 | public function testCallBoot()
236 | {
237 | $mockRepository = $this->makeMockRepository(MockSetupRepository::class, false);
238 |
239 | $mockApplication = $this->makeMockApplication();
240 | $mockApplication->shouldReceive('call');
241 |
242 | $mockRepository->shouldReceive('getApp')->andReturn($mockApplication);
243 |
244 | $methodCallBoot = getUnaccessibleObjectMethod($mockRepository, 'callBoot');
245 | $methodCallBoot->invoke($mockRepository);
246 |
247 | $mockApplication->shouldHaveReceived('call')->withAnyArgs()->once();
248 | }
249 |
250 | /**
251 | * @runInSeparateProcess
252 | * @depends testSetAppAndGetApp
253 | * @depends testCallBoot
254 | * @depends testCallBootable
255 | * @depends testInitializeInNormal
256 | * @depends testInitializeExpectException
257 | * @covers Housekeeper\Repository::__construct
258 | */
259 | public function testConstruct()
260 | {
261 | $methodsWillBeCalledByOrder = [
262 | 'setApp',
263 | 'initialize',
264 | 'callBootable',
265 | 'sortAllInjections',
266 | 'callBoot',
267 | 'reset',
268 | ];
269 | $methodsCalledByOrder = [];
270 |
271 | $mockApplication = $this->makeMockApplication();
272 | $mockRepository = $this->makeMockRepository(MockSetupRepository::class, false);
273 |
274 | // `__construct` method will call these methods
275 | $mockRepository->shouldReceive('setApp')
276 | ->andReturnUsing(function ($app) use (&$mockApplication, &$methodsCalledByOrder) {
277 | $method = array_push($methodsCalledByOrder, 'setApp');
278 |
279 | $this->assertEquals($mockApplication, $app);
280 | });
281 | $mockRepository->shouldReceive('initialize')
282 | ->andReturnUsing(function () use (&$methodsCalledByOrder) {
283 | array_push($methodsCalledByOrder, 'initialize');
284 | });
285 | $mockRepository->shouldReceive('callBootable')
286 | ->andReturnUsing(function () use (&$methodsCalledByOrder) {
287 | array_push($methodsCalledByOrder, 'callBootable');
288 | });
289 | $mockRepository->shouldReceive('sortAllInjections')
290 | ->andReturnUsing(function () use (&$methodsCalledByOrder) {
291 | array_push($methodsCalledByOrder, 'sortAllInjections');
292 | });
293 | $mockRepository->shouldReceive('callBoot')
294 | ->andReturnUsing(function () use (&$methodsCalledByOrder) {
295 | array_push($methodsCalledByOrder, 'callBoot');
296 | });
297 | $mockRepository->shouldReceive('reset')
298 | ->andReturnUsing(function ($action) use (&$methodsCalledByOrder) {
299 | array_push($methodsCalledByOrder, 'reset');
300 |
301 | $this->assertInstanceOf(Action::class, $action);
302 |
303 | $this->assertEquals(Action::INTERNAL, $action->getType());
304 | });
305 |
306 | $mockRepository->__construct($mockApplication);
307 |
308 | $this->assertEquals($methodsWillBeCalledByOrder, $methodsCalledByOrder);
309 | }
310 |
311 | /**
312 | * @covers Housekeeper\Repository::getKeyName
313 | */
314 | public function testGetKeyName()
315 | {
316 | /**
317 | * @var $mockModel m\MockInterface
318 | */
319 | $mockRepository = m::mock(MockSetupRepository::class);
320 | $mockRepository->makePartial()->shouldAllowMockingProtectedMethods();
321 |
322 | $mockModel = $this->makeMockModel();
323 | $mockModel->shouldReceive('getKeyName')->withNoArgs();
324 |
325 | $mockRepository->shouldReceive('newModelInstance')
326 | ->andReturnUsing(function () use (&$mockModel) {
327 | return $mockModel;
328 | });
329 |
330 | $methodGetKeyName = getUnaccessibleObjectMethod($mockRepository, 'getKeyName');
331 | $methodGetKeyName->invoke($mockRepository);
332 |
333 | $mockModel->shouldHaveReceived('getKeyName', [])->once();
334 | }
335 |
336 | /**
337 | * @runInSeparateProcess
338 | * @covers Housekeeper\Repository::resetPlan
339 | */
340 | public function testResetPlan()
341 | {
342 | $mockRepository = $this->makeMockRepository(MockSetupRepository::class, false);
343 |
344 | // `resetPlan` method will change these properties, these should be `null` now
345 | $propertyDefaultPlan = getUnaccessibleObjectPropertyValue($mockRepository, 'defaultPlan');
346 | $propertyPlans = getUnaccessibleObjectPropertyValue($mockRepository, 'plans');
347 | $propertyPlanStep = getUnaccessibleObjectPropertyValue($mockRepository, 'planStep');
348 | $this->assertNull($propertyDefaultPlan);
349 | $this->assertNull($propertyPlans);
350 | $this->assertNull($propertyPlanStep);
351 |
352 | $methodResetPlan = getUnaccessibleObjectMethod($mockRepository, 'resetPlan');
353 | $methodResetPlan->invoke($mockRepository);
354 |
355 | // properties should be changed
356 | $propertyDefaultPlan = getUnaccessibleObjectPropertyValue($mockRepository, 'defaultPlan');
357 | $this->assertInstanceOf(\Housekeeper\Plan::class, $propertyDefaultPlan);
358 |
359 | $propertyPlans = getUnaccessibleObjectPropertyValue($mockRepository, 'plans');
360 | $this->assertInternalType('array', $propertyPlans);
361 | $this->assertEmpty($propertyPlans);
362 |
363 | $propertyPlanStep = getUnaccessibleObjectPropertyValue($mockRepository, 'planStep');
364 | $this->assertNull($propertyPlanStep);
365 | }
366 |
367 | /**
368 | * @covers Housekeeper\Repository::reset
369 | */
370 | public function testReset()
371 | {
372 |
373 | }
374 |
375 | /**
376 | * @covers Housekeeper\Repository::getCurrentPlan
377 | */
378 | public function testGetCurrentPlanWhenDefault()
379 | {
380 | $randomNumber = mt_rand(1, 1000);
381 |
382 | $mockRepository = $this->makeMockRepository(MockSetupRepository::class, false);
383 |
384 | setUnaccessibleObjectPropertyValue($mockRepository, 'defaultPlan', $randomNumber);
385 |
386 | $actualValue = $mockRepository->getCurrentPlan();
387 |
388 | $this->assertEquals($randomNumber, $actualValue);
389 | }
390 |
391 | /**
392 | * @covers Housekeeper\Repository::getCurrentPlan
393 | */
394 | public function testGetCurrentPlanWhenNotDefault()
395 | {
396 | $randomNumber = mt_rand(1, 1000);
397 |
398 | $mockRepository = $this->makeMockRepository(MockSetupRepository::class, false);
399 |
400 | setUnaccessibleObjectPropertyValue($mockRepository, 'plans', [$randomNumber]);
401 | setUnaccessibleObjectPropertyValue($mockRepository, 'planStep', 0);
402 |
403 | $actualValue = $mockRepository->getCurrentPlan();
404 |
405 | $this->assertEquals($randomNumber, $actualValue);
406 | }
407 |
408 | /**
409 | * @covers Housekeeper\Repository::dropPlan
410 | */
411 | public function testDropPlan()
412 | {
413 | $mockRepository = $this->makeMockRepository(MockSetupRepository::class, false);
414 |
415 | setUnaccessibleObjectPropertyValue($mockRepository, 'plans', [1]);
416 | $propertyPlans = getUnaccessibleObjectPropertyValue($mockRepository, 'plans');
417 | $this->assertEquals([1], $propertyPlans);
418 |
419 | // Execute `dropPlan` to remove the value setted before
420 | $methodDropPlan = getUnaccessibleObjectMethod($mockRepository, 'dropPlan');
421 | $methodDropPlan->invoke($mockRepository, 0);
422 |
423 | $propertyPlans = getUnaccessibleObjectPropertyValue($mockRepository, 'plans');
424 | $this->assertEquals([], $propertyPlans);
425 | }
426 |
427 | /**
428 | * @covers Housekeeper\Repository::newPlan
429 | */
430 | public function testNewPlanWhenHaveDefault()
431 | {
432 | $expectDefaultPlan = mt_rand(1, 1000);
433 |
434 | $mockRepository = $this->makeMockRepository(MockSetupRepository::class, false);
435 |
436 | setUnaccessibleObjectPropertyValue($mockRepository, 'defaultPlan', $expectDefaultPlan);
437 | setUnaccessibleObjectPropertyValue($mockRepository, 'plans', []);
438 |
439 | $methodNewPlan = getUnaccessibleObjectMethod($mockRepository, 'newPlan');
440 | $offset = $methodNewPlan->invoke($mockRepository);
441 |
442 | $propertyPlanStep = getUnaccessibleObjectPropertyValue($mockRepository, 'planStep');
443 | $propertyDefaultPlan = getUnaccessibleObjectPropertyValue($mockRepository, 'defaultPlan');
444 | $propertyPlans = getUnaccessibleObjectPropertyValue($mockRepository, 'plans');
445 |
446 | $this->assertEquals(0, $offset);
447 | $this->assertEquals(0, $propertyPlanStep);
448 | $this->assertNull($propertyDefaultPlan);
449 | $this->assertEquals([$expectDefaultPlan], $propertyPlans);
450 | }
451 |
452 | /**
453 | * @runInSeparateProcess
454 | * @covers Housekeeper\Repository::newPlan
455 | */
456 | public function testNewPlanWhenDoNotHaveDefault()
457 | {
458 | $mockRepository = $this->makeMockRepository(MockSetupRepository::class, false);
459 |
460 | setUnaccessibleObjectPropertyValue($mockRepository, 'defaultPlan', null);
461 | setUnaccessibleObjectPropertyValue($mockRepository, 'plans', [1]);
462 | setUnaccessibleObjectPropertyValue($mockRepository, 'planStep', 0);
463 |
464 | $methodNewPlan = getUnaccessibleObjectMethod($mockRepository, 'newPlan');
465 | $offset = $methodNewPlan->invoke($mockRepository);
466 |
467 | $propertyPlanStep = getUnaccessibleObjectPropertyValue($mockRepository, 'planStep');
468 | $propertyPlans = getUnaccessibleObjectPropertyValue($mockRepository, 'plans');
469 |
470 | $this->assertEquals(1, $offset);
471 | $this->assertEquals(1, $propertyPlanStep);
472 | $this->assertCount(2, $propertyPlans);
473 |
474 | $plan = $propertyPlans[1];
475 | $this->assertInstanceOf(\Housekeeper\Plan::class, $plan);
476 | }
477 |
478 | /**
479 | * @covers Housekeeper\Repository::getModel
480 | */
481 | public function testGetModel()
482 | {
483 | $mockPlan = m::mock();
484 | $mockPlan->shouldReceive('getModel')
485 | ->andReturn('');
486 |
487 | $mockRepository = $this->makeMockRepository(MockSetupRepository::class, false);
488 |
489 | $mockRepository->shouldReceive('getCurrentPlan')
490 | ->andReturn($mockPlan);
491 |
492 | $methodGetModel = getUnaccessibleObjectMethod($mockRepository, 'getModel');
493 | $methodGetModel->invoke($mockRepository);
494 |
495 | $mockPlan->shouldHaveReceived('getModel')->once();
496 | }
497 |
498 | /**
499 | * @covers Housekeeper\Repository::before
500 | */
501 | public function testBefore()
502 | {
503 |
504 | }
505 |
506 | /**
507 | * @covers Housekeeper\Repository::after
508 | */
509 | public function testAfter()
510 | {
511 |
512 | }
513 |
514 | /**
515 | * @covers Housekeeper\Repository::wrap
516 | */
517 | public function testWrap()
518 | {
519 |
520 | }
521 |
522 | /**
523 | * @covers Housekeeper\Repository::getMethodNameofCallable
524 | */
525 | public function testGetMethodNameOfCallable()
526 | {
527 |
528 | }
529 |
530 | /**
531 | * @covers Housekeeper\Repository::simpleWrap
532 | */
533 | public function testSimpleWrap()
534 | {
535 |
536 | }
537 |
538 | /**
539 | * @covers Housekeeper\Repository::traceToRealMethod
540 | */
541 | public function testTraceToRealMethod()
542 | {
543 |
544 | }
545 |
546 | /**
547 | * @covers Housekeeper\Repository::whereAre
548 | * @covers Housekeeper\Repository::applyWheres
549 | */
550 | public function testWhereAre()
551 | {
552 |
553 | }
554 |
555 | /**
556 | * @covers Housekeeper\Repository::orderBy
557 | * @covers Housekeeper\Repository::applyOrderBy
558 | */
559 | public function testOrderBy()
560 | {
561 |
562 | }
563 |
564 | /**
565 | * @covers Housekeeper\Repository::with
566 | */
567 | public function testWith()
568 | {
569 |
570 | }
571 |
572 | /**
573 | * @covers Housekeeper\Repository::offset
574 | */
575 | public function testOffset()
576 | {
577 |
578 | }
579 |
580 | /**
581 | * @covers Housekeeper\Repository::limit
582 | */
583 | public function testLimit()
584 | {
585 |
586 | }
587 |
588 | /**
589 | * @covers Housekeeper\Repository::exists
590 | */
591 | public function testExists()
592 | {
593 |
594 | }
595 |
596 | /**
597 | * @covers Housekeeper\Repository::find
598 | * @covers Housekeeper\Repository::_find
599 | */
600 | public function testFind()
601 | {
602 |
603 | }
604 |
605 | /**
606 | * @covers Housekeeper\Repository::findMany
607 | * @covers Housekeeper\Repository::_findMany
608 | */
609 | public function testFindMany()
610 | {
611 |
612 | }
613 |
614 | /**
615 | * @covers Housekeeper\Repository::create
616 | * @covers Housekeeper\Repository::_create
617 | */
618 | public function testCreate()
619 | {
620 |
621 | }
622 |
623 | /**
624 | * @covers Housekeeper\Repository::delete
625 | * @covers Housekeeper\Repository::_delete
626 | */
627 | public function testDelete()
628 | {
629 |
630 | }
631 |
632 | /**
633 | * @covers Housekeeper\Repository::update
634 | * @covers Housekeeper\Repository::_update
635 | */
636 | public function testUpdate()
637 | {
638 |
639 | }
640 |
641 | /**
642 | * @covers Housekeeper\Repository::all
643 | * @covers Housekeeper\Repository::_all
644 | */
645 | public function testAll()
646 | {
647 |
648 | }
649 |
650 | /**
651 | * @covers Housekeeper\Repository::paginate
652 | * @covers Housekeeper\Repository::_paginate
653 | */
654 | public function testPaginate()
655 | {
656 |
657 | }
658 |
659 | /**
660 | * @covers Housekeeper\Repository::getByField
661 | * @covers Housekeeper\Repository::_getByField
662 | */
663 | public function testGetByField()
664 | {
665 |
666 | }
667 |
668 | /**
669 | * @covers Housekeeper\Repository::findByField
670 | * @covers Housekeeper\_findByField::_firstOrFailByField
671 | */
672 | public function testFindByField()
673 | {
674 |
675 | }
676 |
677 | /**
678 | * Internal method calling should creating different layer to isolate
679 | * outcome from each other.
680 | */
681 | public function testIsolationWhenCallingInternalMethod()
682 | {
683 |
684 | }
685 |
686 |
687 | // ========================================================================
688 |
689 | /**
690 | * @param string $class
691 | * @param bool $concrete
692 | * @return Repository|m\MockInterface
693 | */
694 | protected function makeMockRepository($class = 'Housekeeper\Repository',
695 | $concrete = true)
696 | {
697 | /**
698 | * Setup some hints for variables.
699 | *
700 | * @var \Housekeeper\Repository|\Mockery\MockInterface $mockRepository
701 | */
702 |
703 | /**
704 | * Mock Repository
705 | */
706 | $mockRepository = m::mock($class);
707 | $mockRepository->makePartial()
708 | ->shouldAllowMockingProtectedMethods();
709 |
710 | /**
711 | * Override "newModelInstance" method, returns a mock model.
712 | */
713 | $mockRepository->shouldReceive('newModelInstance')
714 | ->andReturnUsing(function () {
715 | return $this->makeMockModel();
716 | });
717 |
718 | /**
719 | * Once we mocked "makeModel" method, we can safely Re-concreting
720 | * Repository object.
721 | */
722 | if ($concrete) {
723 | $mockRepository->__construct($this->makeMockApplication());
724 | }
725 |
726 | return $mockRepository;
727 | }
728 |
729 | /**
730 | * @return m\MockInterface|\Illuminate\Contracts\Foundation\Application
731 | */
732 | protected function makeMockApplication()
733 | {
734 | /**
735 | * Mock "Config" instance.
736 | */
737 | $mockConfig = m::mock('Illuminate\Config\Repository');
738 |
739 | $mockConfig->shouldReceive('get')
740 | ->andReturnNull();
741 |
742 | /**
743 | * Mock "Application".
744 | */
745 | $mockApplication = m::mock('Illuminate\Contracts\Foundation\Application');
746 |
747 | $mockApplication->shouldReceive('config')
748 | ->andReturn([]);
749 |
750 | $mockApplication->shouldReceive('make')
751 | ->with('config')
752 | ->andReturn($mockConfig);
753 |
754 | $mockApplication->shouldReceive('make')
755 | ->with()
756 | ->andReturn([]);
757 |
758 | $mockApplication->shouldReceive('call')
759 | ->andReturn([]);
760 |
761 | return $mockApplication;
762 | }
763 |
764 | /**
765 | * @param string $class
766 | * @return m\MockInterface
767 | */
768 | protected function makeMockModel($class = 'Illuminate\Database\Eloquent\Model')
769 | {
770 | $mock = m::mock($class);
771 |
772 | $mock->shouldReceive('get')
773 | ->andReturn(m::mock('Illuminate\Database\Eloquent\Collection'));
774 |
775 | return $mock;
776 | }
777 |
778 | /**
779 | * @return m\MockInterface
780 | */
781 | protected function makeMockAction()
782 | {
783 | $mock = m::mock('Housekeeper\Action');
784 |
785 | $mock->shouldReceive('getArguments')
786 | ->andReturn([]);
787 |
788 | $mock->shouldReceive('getMethodName')
789 | ->andReturn('fake');
790 |
791 | return $mock;
792 | }
793 |
794 | }
795 |
796 | // ============================================================================
797 |
798 | /**
799 | * Class MockBasicInjection
800 | *
801 | * @package Housekeeper
802 | */
803 | class MockBasicInjection implements BasicInjectionContract
804 |
805 | {
806 | /**
807 | * @return int
808 | */
809 | public function priority()
810 | {
811 | return 1;
812 | }
813 | }
814 |
815 | /**
816 | * Class MockSetupRepository
817 | *
818 | * @package Housekeeper
819 | */
820 | class MockSetupRepository extends Repository
821 | {
822 | /**
823 | *
824 | */
825 | protected function model()
826 | {
827 |
828 | }
829 |
830 | public function boot()
831 | {
832 |
833 | }
834 |
835 | /**
836 | *
837 | */
838 | public function bootTestOne()
839 | {
840 | $mockInjection = new MockBasicInjection();
841 |
842 | $this->inject($mockInjection);
843 | }
844 |
845 | /**
846 | *
847 | */
848 | public function bootTestTwo()
849 | {
850 |
851 | }
852 |
853 | /**
854 | * @param \Housekeeper\Contracts\Injection\Basic $a
855 | * @param \Housekeeper\Contracts\Injection\Basic $b
856 | * @return int
857 | */
858 | protected static function sortInjection(\Housekeeper\Contracts\Injection\Basic $a, \Housekeeper\Contracts\Injection\Basic $b)
859 | {
860 | return 0;
861 | }
862 | }
863 |
864 | /**
865 | * Class MockModel
866 | *
867 | * @package Housekeeper
868 | */
869 | class MockModel
870 | {
871 |
872 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [](https://packagist.org/packages/aaronjan/housekeeper)
6 | [](https://packagist.org/packages/aaronjan/housekeeper)
7 |
8 |
9 | # Housekeeper - Laravel
10 |
11 | After nearly six months developing, testing and polishing, the first stable version of `Housekeeper 2` is finally released!
12 |
13 | `Housekeeper` aims to be the coolest, handiest `Repository Pattern` implementation, any useful suggestion and PR are welcomed.
14 |
15 | Increasing unit test code coverage is a work in progress (lots of works), but there is a set of integration tests running locally that covered most code.
16 |
17 |
18 | ## Introduction
19 |
20 | `Housekeeper` is a flexable and powerful `Repository Pattern` implemention for `Laravel`. In addition to the basic `Repository Pattern` and elegant syntax, `Housekeeper` has features like `Injection system`, Auto-Booting Method that will let you creating endless possibilities. The goal of `Housekeeper` is free you from the redundant and boring [`DAL`](https://en.wikipedia.org/wiki/Data_access_layer) stuff, coding more intuitively.
21 |
22 |
23 | ## Sections
24 |
25 | - [Repository Pattern and Housekeeper](#repository-pattern-and-housekeeper)
26 | - [Installation](#installation)
27 | - [TL;DR (Quick start)](#tldr)
28 | - [Features](#features)
29 | - [API](#api)
30 | - [Abilities](#abilities)
31 | - [Adjustable](#adjustable)
32 | - [Eloquently](#eloquently)
33 | - [CacheStatically](#cachestatically)
34 | - [Guardable](#guardable)
35 | - [SoftDeletes](#softdeletes)
36 | - [Issue](#issue)
37 | - [Lisence](#lisence)
38 | - [Credits](#credits)
39 |
40 | ## Repository Pattern and Housekeeper
41 |
42 | The `Repository Pattern` is a software design pattern. In a nutshell, it means to encapsulate your data interaction code as methods that belong to different classes (Base on data domain), we call this type of class as `Repository`. When your business logic layer needs to accessing data such as an article entry in the database, it should ask to the `Article Repository` instead of writing inline query that deal with database directly.
43 |
44 | OK, but ... I already got Eloquent, why not just using that?
45 |
46 | Of course you can! But there're people who's not a fan of the Active Record, it just doesn't feel right for them, for these people, the Repository Pattern makes more sense. Besides, you can write method that is more expressive on your repository class, like **getActivatedUsers()**, and you can write tests for them very easily.
47 |
48 | More importantly, `Housekeeper` is a better version of `Repository Pattern` (In some ways), you could read more about it below.
49 |
50 | Housekeeper loves Eloquent. Most query APIs are the same as the Eloquent's, so you can use them without the needing to learn anything, and the returns are like Eloquent's too.
51 |
52 |
53 | ## Installation
54 |
55 | ### Requirement
56 |
57 | `PHP` `>= 5.5` and `Laravel` `>= 5.1`
58 |
59 | ### Install Via Composer
60 |
61 | ```
62 | $ composer require aaronjan/housekeeper ~2.3
63 | ```
64 |
65 | or add these to your `composer.json` file:
66 |
67 | ```
68 | "require": {
69 | "aaronjan/housekeeper": "~2.3"
70 | }
71 | ```
72 |
73 | then execute console command:
74 |
75 | ```
76 | $ composer install
77 | ```
78 |
79 | After `Composer` finish running, add the `HousekeeperServiceProvider` to the providers in `config/app.php`:
80 |
81 | ```php
82 | [
87 | // ...
88 |
89 | // Add this:
90 | \Housekeeper\Providers\HousekeeperServiceProvider::class,
91 |
92 | // ...
93 | ],
94 |
95 | // ...
96 |
97 | ```
98 |
99 | Make a configuration file for `Housekeeper` could allow you to tweak things:
100 |
101 | ```
102 | $ artisan vendor:publish --provider=Housekeeper\\Providers\\HousekeeperServiceProvider --tag=config
103 | ```
104 |
105 | It's done! Now you can make a repository:
106 |
107 | ```
108 | $ artisan housekeeper:make UserRepository
109 | ```
110 |
111 |
112 | ## TL;DR
113 |
114 | If you have outstanding insight, this section will tell you how to use `Housekeeper` in the simplest words.
115 |
116 | * Do not write `class constructor`, use `boot` method instead, supports `Type-Hinting`.
117 |
118 | * Any public method that name starts with **boot** and followed by an upper-case letter (for instance, **bootForInject**), then this method will be called during class initializing, also support `Type-Hinting`.
119 |
120 | * If you want to do something before/after some methods belong across multiple repository, encapsulate these logics as `Injections` then inject them into your repositorys.
121 |
122 | * By using the two features above, you can write `Trait` to inject `Injection`, and **use** it in your repository.
123 |
124 | Take a example:
125 |
126 | ```php
127 | injectIntoBefore(new LogTimeBefore());
157 | }
158 | }
159 |
160 | ```
161 |
162 | ```php
163 | simpleWrap(Action::READ, [$this, '_getByName']);
207 | }
208 |
209 | protected function _getByName($name)
210 | {
211 | return $this->getModel() // this function give you an Eloquent / Builder instance
212 | ->where('name', '=', $name)
213 | ->get();
214 | }
215 | }
216 |
217 | ```
218 |
219 | That's it, take a look at the `Abilities`' code for more usages, Have fun!
220 |
221 |
222 | ## Features
223 |
224 | ### Extend-less & Flows
225 |
226 | The `Repository Pattern` is usually used with the `Decorator Pattern`. For instance, you have a repository class to interacting with data source directly, later you decide to add cache logic on top of that, so instead of changing the repository class its self, you could create a new class that extending it, and the whole thing may looks like this:
227 |
228 | ```php
229 | simpleWrap(Action::READ, [$this, '_getByName']);
275 | }
276 |
277 | protected function _getByName($name)
278 | {
279 | return $this->getModel() // this function give you an Eloquent / Builder instance
280 | ->where('name', '=', $name)
281 | ->get();
282 | }
283 | }
284 | ```
285 |
286 | Why there are two methods that had similar names? Well, the `getByName` method is basically a configuration and an API hint for the core method `_getByName`, it wrapped the core method by calling the `simpleWrap` with an `Callable` which is `[$this, '_getByName']`, It says what this method does is `reading` data (`Action::READ`), the whole reading logic is in the `_getByName` method.
287 |
288 | You don't have to worry about method arguments, `Housekeeper` will takes care of that. In fact, you don't even need to write `[$this, '_getByName']`, since it's a convention in `Housekeeper` (An underscore before your method name):
289 |
290 | ```php
291 | simpleWrap(Action::READ);
296 | }
297 |
298 | ```
299 |
300 | Let's back to the `cache logic` topic. In `Housekeeper`, if you wrapped your method like above, than to adding cache process, all you need to do is writing a single line of code like this:
301 |
302 | ```php
303 | getAction();
341 |
342 | // Or get the `Repository`
343 | $repository = $beforeFlow->getRepository();
344 |
345 | // And you can set the returns (Only in `Before Flow`)
346 | $beforeFlow->setReturnValue(1);
347 | }
348 | }
349 | ```
350 |
351 | The `handle` method in `Injection` takes a `Flow` object, depends on what `Flow` you injected into, the methods of the `Flow` object could be different, for instance, `Before Flow` provides `setReturnValue` method, you could call it by pass a value to it, then `Housekeeper` will use this value as the return and skip the actual method.
352 |
353 | You can inject `Injection` by using the these methods: `injectIntoBefore`, `injectIntoAfter` and `injectIntoReset`.
354 |
355 | ```php
356 | injectIntoBefore(new MyBeforeInjection());
364 | }
365 |
366 | // ...
367 | }
368 | ```
369 |
370 |
371 | Here is flowchart of the `Before Flow` execution:
372 |
373 | 
374 |
375 | `Housekeeper` also will calling every method in the `Repository` class that name start with `boot` (before calling the `boot` method) when `Repository` instance been creating, some of the out-of-the-box `Abilities` in `Housekeeper` are took advantage of this, like in `Adjustable` trait:
376 |
377 | ```php
378 | injectIntoBefore(new ApplyCriteriasBefore());
387 | }
388 |
389 | // ...
390 | }
391 | ```
392 |
393 |
394 | ### Wrapping layer
395 |
396 | Let's assume someone wrote code like these:
397 |
398 | ```php
399 | simpleWrap(Action::READ);
408 | }
409 |
410 | protected function _getArticlesByAuthorId($authorId)
411 | {
412 | return $this
413 | ->applyWheres([
414 | ['author_id', '=', $authorId],
415 | ])
416 | ->get();
417 | }
418 |
419 | public function getArticlesBySameAuthor($articleId)
420 | {
421 | return $this->simpleWrap(Action::READ);
422 | }
423 |
424 | protected function _getArticlesBySameAuthor($articleId)
425 | {
426 | $article = $this->getModel()->find($articleId, ['id', 'author_id']);
427 |
428 | return $this->getArticlesByAuthorId($article->author_id);
429 | }
430 |
431 |
432 | // ...
433 | }
434 | ```
435 |
436 | ```php
437 | applyWheres([
445 | ['language', '=', 'chinese'],
446 | ])
447 | ->getArticlesBySameAuthor($articleId);
448 |
449 | return view('article.recommend-for-article', compact('articles'));
450 | }
451 |
452 | // ...
453 | }
454 | ```
455 |
456 | In this example, the `applyWheres` method has been used twice, one is in the `Controller`, the other is in the `Repository`, could the first one affecting the `_getArticlesByAuthorId` method? No. It will only affecting the `_getArticlesBySameAuthor` method, and be more precisely, it's affecting this line:
457 |
458 | ```php
459 | $article = $this->getModel()->find($articleId, ['id', 'author_id']);
460 | ```
461 |
462 | Every wrapped method in `Housekeeper` has their own `Scope`, means they have their own `Eloquent Model` (Or `Builder`), thus they will not taking any affect to each other. If you calling `applyWheres` or `ApplyOrderBy` outside the repository, they would only affecting the first wrapped method you called.
463 |
464 |
465 | ### Another Choice For Wrapping
466 |
467 | Having two methods could be annoying, you can write an `Anonymous Function`, before the `simpleWrap` takes a `Callable`:
468 |
469 | ```php
470 | simpleWrap(Action::READ, function (name) {
475 | return $this->getModel()
476 | ->where('name', '=', $name)
477 | ->get();
478 | });
479 | }
480 | ```
481 |
482 |
483 | ## API
484 |
485 | -----------------------------
486 |
487 | #### whereAre(array $wheres)
488 |
489 | Add an array of where clauses to the query.
490 |
491 | __Arguments__
492 |
493 | * `$wheres` - An array of `where` conditions.
494 |
495 | __Example__
496 |
497 | ```php
498 | whereAre([
502 | ['age', '>', 40],
503 | ['area', 'west']
504 | ])
505 | ->all();
506 | ```
507 |
508 | ```php
509 | whereAre([
513 | ['area', 'east'],
514 | function ($query) {
515 | $query->whereHas('posts', function ($hasQuery) {
516 | $hasQuery->where('type', 1);
517 | });
518 |
519 | $query->whereNull('has_membership');
520 | },
521 | ])
522 | ->paginate(12);
523 |
524 | ```
525 |
526 |
527 | -----------------------------
528 |
529 | #### applyWheres(array $wheres)
530 |
531 | Alias for the `whereAre` method.
532 |
533 | __Arguments__
534 |
535 | * `$wheres` - An array of `where` conditions.
536 |
537 |
538 | -----------------------------
539 |
540 | #### orderBy($column, $direction = 'asc')
541 |
542 | Add an "order by" clause to the query.
543 |
544 | __Arguments__
545 |
546 | * `$column`
547 | * `$direction`
548 |
549 | __Example__
550 |
551 | ```php
552 | orderBy('age', 'desc')
556 | ->all();
557 | ```
558 |
559 |
560 | -----------------------------
561 |
562 | #### applyOrderBy($column, $direction = 'asc')
563 |
564 | Alias for the `orderBy` method.
565 |
566 | __Arguments__
567 |
568 | * `$column`
569 | * `$direction`
570 |
571 |
572 | -----------------------------
573 |
574 | #### offset($value)
575 |
576 | Set the "offset" value of the query.
577 |
578 | __Arguments__
579 |
580 | * `$value` - The specified offset of the first row to return.
581 |
582 | __Example__
583 |
584 | ```php
585 | limit(10)
589 | ->all();
590 | ```
591 |
592 |
593 | -----------------------------
594 |
595 | #### limit($value)
596 |
597 | Set the "limit" value of the query.
598 |
599 | __Arguments__
600 |
601 | * `$value` - The maximum number of rows to return.
602 |
603 | __Example__
604 |
605 | ```php
606 | limit(10)
610 | ->all();
611 | ```
612 |
613 |
614 | -----------------------------
615 |
616 | #### exists($id, $column = null)
617 |
618 | Determine if the record exists using its primary key.
619 |
620 | __Arguments__
621 |
622 | * `$id` - The primary key of the record.
623 | * `$column` - You could also specify a column other than primary key, and change the value of `$id` correspondingly.
624 |
625 | __Examples__
626 |
627 | ```php
628 | exists(3);
631 |
632 | ```
633 |
634 | ```php
635 | exists('name', 'John');
638 |
639 | ```
640 |
641 | You could use this method with custom query conditions too:
642 |
643 | ```php
644 | whereAre(['gender' => 'female'])->exists(1);
647 |
648 | ```
649 |
650 |
651 | -----------------------------
652 |
653 | #### count($columns = '*')
654 |
655 | Retrieve the "count" result of the query.
656 |
657 | __Arguments__
658 |
659 | * `$columns`
660 |
661 |
662 | -----------------------------
663 |
664 | #### find($id, $columns = array('*'))
665 |
666 | Find a model by its primary key.
667 |
668 | __Arguments__
669 |
670 | * `$id`
671 | * `$columns` - Specify columns that you want to retrieve.
672 |
673 | __Examples__
674 |
675 | ```php
676 | find(1, ['id', 'name', 'gender', 'age']);
679 |
680 | ```
681 |
682 |
683 | -----------------------------
684 |
685 | #### findMany($ids, $columns = array('*'))
686 |
687 | Find a collection of models by their primary key.
688 |
689 | __Arguments__
690 |
691 | * `$ids`
692 | * `$columns` - Specify columns that you want to retrieve.
693 |
694 |
695 | -----------------------------
696 |
697 | #### update($id, array $attributes)
698 |
699 | Update a record in the database.
700 |
701 | __Arguments__
702 |
703 | * `$id`
704 | * `$attributes`
705 |
706 | __Examples__
707 |
708 | ```php
709 | update(24, [
712 | 'name' => 'Kobe Bryant'
713 | ]);
714 |
715 | ```
716 |
717 |
718 | -----------------------------
719 |
720 | #### create(array $attributes)
721 |
722 | Create a model with `$attributes`.
723 |
724 | __Arguments__
725 |
726 | * `$attributes`
727 |
728 |
729 | -----------------------------
730 |
731 | #### delete($id)
732 |
733 | Delete a record from the database by its primary key.
734 |
735 | __Arguments__
736 |
737 | * `$id`
738 |
739 |
740 | -----------------------------
741 |
742 | #### first($columns = ['*'])
743 |
744 | Execute the query and retrieve the first result.
745 |
746 | __Arguments__
747 |
748 | * `$columns`
749 |
750 |
751 | -----------------------------
752 |
753 | #### all($columns = ['*'])
754 |
755 | Execute the query as a "select" statement.
756 |
757 | __Arguments__
758 |
759 | * `$columns`
760 |
761 |
762 | -----------------------------
763 |
764 | #### paginate($limit = null, $columns = ['*'], $pageName = 'page', $page = null)
765 |
766 | Paginate the given query.
767 |
768 | __Arguments__
769 |
770 | * `$limit`
771 | * `$columns`
772 | * `$pageName`
773 | * `$page`
774 |
775 |
776 | -----------------------------
777 |
778 | #### getByField($field, $value = null, $columns = ['*'])
779 |
780 | Retrieve models by a simple equality query.
781 |
782 | __Arguments__
783 |
784 | * `$field`
785 | * `$value`
786 | * `$columns`
787 |
788 |
789 | -----------------------------
790 |
791 | #### with($relations)
792 |
793 | Set the relationships that should be eager loaded, like `Eloquent`.
794 |
795 | __Examples__
796 |
797 | ```php
798 | with('posts')->paginate(10);
801 |
802 | ```
803 |
804 |
805 | -----------------------------
806 |
807 | ## Adjustable
808 |
809 | For more complex queries, you could put them in a `Criteria` class that is more semantic and reuse them anywhere you want, for that, using the `Adjustable` ability.
810 |
811 | ### Examples
812 |
813 | ```php
814 | whereAre([
823 | ['paid', '=', 1],
824 | ['logged_recently', '=', 1],
825 | ]);
826 | }
827 | }
828 |
829 | ```
830 |
831 | Then in your **controller**:
832 |
833 | ```php
834 | applyCriteria($activeUserCriteria)->all();
840 |
841 | // Or you can remember this Criteria:
842 | $userRepository->rememberCriteria($activeUserCriteria);
843 |
844 | $activeUsers = $userRepository->all();
845 |
846 | $femaleActiveUsers = $userRepository->where('gender', '=', 'female')->all();
847 |
848 | ```
849 |
850 |
851 | ### API
852 |
853 | -----------------------------
854 |
855 | #### applyCriteria(\Housekeeper\Abilities\Adjustable\Contracts\Criteria $criteria)
856 |
857 | Apply this `Criteria` only once.
858 |
859 | __Arguments__
860 |
861 | * `$criteria` - Criteria object.
862 |
863 |
864 | -----------------------------
865 |
866 | #### rememberCriteria(\Housekeeper\Abilities\Adjustable\Contracts\Criteria $criteria)
867 |
868 | Remember this `Criteria`, it will be applied when every wrapped method been called (Only the first one, iternal method calling will be ignored).
869 |
870 | __Arguments__
871 |
872 | * `$criteria` - Criteria object.
873 |
874 |
875 | -----------------------------
876 |
877 | #### forgetCriterias()
878 |
879 | Remove all remembered `Criterias` (Not applied).
880 |
881 |
882 | -----------------------------
883 |
884 | #### getCriterias()
885 |
886 | Get all remembered `Criterias`.
887 |
888 |
889 | -----------------------------
890 |
891 | ## Eloquently
892 |
893 | This `Abilitiy` provides lots of `Eloquent` style query APIs that you are very familiar with.
894 |
895 |
896 | ### API
897 |
898 | -----------------------------
899 |
900 | #### where($column, $operator = null, $value = null, $boolean = 'and')
901 |
902 |
903 | -----------------------------
904 |
905 | #### orWhere($column, $operator = null, $value = null)
906 |
907 |
908 | -----------------------------
909 |
910 | #### has($relation, $operator = '>=', $count = 1, $boolean = 'and', \Closure $callback = null)
911 |
912 |
913 | -----------------------------
914 |
915 | #### whereHas($relation, Closure $callback, $operator = '>=', $count = 1)
916 |
917 |
918 | -----------------------------
919 |
920 | #### whereDoesntHave($relation, Closure $callback = null)
921 |
922 |
923 | -----------------------------
924 |
925 | #### orWhereHas($relation, Closure $callback, $operator = '>=', $count = 1)
926 |
927 |
928 | -----------------------------
929 |
930 | #### whereIn($column, $values, $boolean = 'and', $not = false)
931 |
932 |
933 | -----------------------------
934 |
935 | #### whereNotIn($column, $values, $boolean = 'and')
936 |
937 |
938 | -----------------------------
939 |
940 | #### orWhereNotIn($column, $values)
941 |
942 |
943 | -----------------------------
944 |
945 | #### whereNull($column, $boolean = 'and', $not = false)
946 |
947 |
948 | -----------------------------
949 |
950 | #### orWhereNull($column)
951 |
952 |
953 | -----------------------------
954 |
955 | #### whereNotNull($column, $boolean = 'and')
956 |
957 |
958 | -----------------------------
959 |
960 | #### orWhereNotNull($column)
961 |
962 |
963 | -----------------------------
964 |
965 | ## CacheStatically
966 |
967 | This `Ability` implemented a very simple cache system: Caching all method returns, and delete them all when creating/updating/deleting, you can clear cache manually too.
968 |
969 | Once you use this `Ability`, everything is automatic. `all()`, `find()`, `paginate()` and others will go through the cache logic, if any cached return be found, then no database query will be executed. Different method has different cache key, even applying query will change the cache key.
970 |
971 | This `Ability` may not be much practical in large project, but it shows the flexibility of `Housekeeper`, and other cache system is in the roadmap.
972 |
973 |
974 | ### Examples
975 |
976 | ```php
977 | enableCache()->all();
981 |
982 | // This also will be cached!
983 | $userRepository->where('age', '<', '30')->orderBy('age', 'desc')->all();
984 |
985 | ```
986 |
987 | Wrapped methods has their own cache:
988 |
989 | ```php
990 | simpleWrap(Housekeeper\Action::READ);
999 | }
1000 |
1001 | protected function _getOnlyActive()
1002 | {
1003 | // Every wrapped method has it's own scope, they don't interfere with each other
1004 | return $this->whereAre([
1005 | ['paid', '=', 1],
1006 | ['logged_recently', '=', 1],
1007 | ])
1008 | ->all(); // Cached too
1009 | }
1010 | }
1011 |
1012 | ```
1013 |
1014 | ### API
1015 |
1016 | -----------------------------
1017 |
1018 | #### enableCache()
1019 |
1020 | Enable cache system.
1021 |
1022 |
1023 | -----------------------------
1024 |
1025 | #### disableCache()
1026 |
1027 | Disable cache system.
1028 |
1029 |
1030 | -----------------------------
1031 |
1032 | #### isCacheEnabled()
1033 |
1034 | Indicate whether cache system is enabled or not.
1035 |
1036 |
1037 | -----------------------------
1038 |
1039 | #### clearCache()
1040 |
1041 | Delete all caches of this repository.
1042 |
1043 |
1044 | -----------------------------
1045 |
1046 | ## Guardable
1047 |
1048 | `Housekeeper` ignored `Mass Assignment Protection` by default, use this `Ability` if you need it.
1049 |
1050 | `Guardable` disabled `Mass Assignment Protection` by default too, you have to turn it on manually.
1051 |
1052 |
1053 | ### Examples
1054 |
1055 | ```php
1056 | guardUp()->create($request->all());
1060 |
1061 | // But we can trust our internal process
1062 | $userRepository->guardDown()->create($attributes);
1063 |
1064 | ```
1065 |
1066 |
1067 | ### API
1068 |
1069 | -----------------------------
1070 |
1071 | #### guardUp()
1072 |
1073 | Enable `Mass Assignment Protection`。
1074 |
1075 |
1076 | -----------------------------
1077 |
1078 | #### guardDown()
1079 |
1080 | Disable `Mass Assignment Protection`。
1081 |
1082 |
1083 | -----------------------------
1084 |
1085 | #### isGuarded()
1086 |
1087 | Whether or not the `Mass Assignment Protection` is enabled.
1088 |
1089 |
1090 | -----------------------------
1091 |
1092 | ## SoftDeletes
1093 |
1094 | To utilize the `SoftDeletes` trait of the `Eloquent`, you should use this `Ability` in your repository.
1095 |
1096 |
1097 | ### API
1098 |
1099 | -----------------------------
1100 |
1101 | #### startWithTrashed()
1102 |
1103 | Include soft deletes.
1104 |
1105 |
1106 | -----------------------------
1107 |
1108 | #### startWithTrashedOnly()
1109 |
1110 | Include soft deletes only.
1111 |
1112 |
1113 | -----------------------------
1114 |
1115 | #### forceDelete($id)
1116 |
1117 | Hard delete a record by primary key.
1118 |
1119 | __Arguments__
1120 |
1121 | * `$id`
1122 |
1123 |
1124 | -----------------------------
1125 |
1126 | #### restore($id)
1127 |
1128 | Restore a soft-deleted record by primary key.
1129 |
1130 | __Arguments__
1131 |
1132 | * `$id`
1133 |
1134 |
1135 | -----------------------------
1136 |
1137 |
1138 | ## Console Commands
1139 |
1140 | Create a new repository:
1141 |
1142 | ```
1143 | php artisan housekeeper:make MyRepository
1144 | ```
1145 |
1146 | Create a new repsitory and a new model:
1147 |
1148 | ```
1149 | php artisan housekeeper:make MyRepository --create=Models\\Student
1150 | ```
1151 |
1152 | Create a new repository with some `Abilities`:
1153 |
1154 | ```
1155 | php artisan housekeeper:make MyRepository --cache=statically --eloquently --adjustable --sd
1156 | ```
1157 |
1158 |
1159 | ## Issue
1160 |
1161 | If you have any question about `Housekeeper`, feel free to create an issue, I'll reply you ASAP.
1162 |
1163 | Any useful pull request are welcomed too.
1164 |
1165 |
1166 | ## Lisence
1167 |
1168 | Licensed under the [APACHE LISENCE 2.0](http://www.apache.org/licenses/LICENSE-2.0)
1169 |
1170 |
1171 | ## Credits
1172 |
1173 | Thanks to [prettus/l5-repository](https://github.com/prettus/l5-repository) for inspiring.
1174 |
1175 | Thanks to [sunkey](https://github.com/sunkeyfong) for the awesome LOGOs!
1176 |
1177 | Thanks to [@DarKDinDoN](https://github.com/DarKDinDoN), [@Bruce Peng](https://github.com/ipengxh), [@FelipeUmpierre](https://github.com/FelipeUmpierre), [@rsdev000](https://github.com/rsdev000) for your contributions!
1178 |
1179 | Thanks to [Laravel](https://github.com/laravel/framework) for making our life easier!
1180 |
--------------------------------------------------------------------------------