├── .github
└── FUNDING.yml
├── .gitignore
├── Config
└── laravel_auto_crud.php
├── LICENSE
├── README.md
├── composer.json
├── images
├── command.png
├── laravel-auto-crud.png
└── resources_views.png
├── phpunit.xml
├── src
├── Builders
│ ├── BaseBuilder.php
│ ├── ControllerBuilder.php
│ ├── DocumentationBuilders
│ │ ├── CURLBuilder.php
│ │ ├── PostmanBuilder.php
│ │ └── SwaggerAPIBuilder.php
│ ├── EnumBuilder.php
│ ├── RepositoryBuilder.php
│ ├── RequestBuilder.php
│ ├── ResourceBuilder.php
│ ├── RouteBuilder.php
│ ├── ServiceBuilder.php
│ ├── SpatieDataBuilder.php
│ └── ViewBuilder.php
├── Console
│ └── Commands
│ │ └── GenerateAutoCrudCommand.php
├── LaravelAutoCrudServiceProvider.php
├── Services
│ ├── CRUDGenerator.php
│ ├── DatabaseValidatorService.php
│ ├── DocumentationGenerator.php
│ ├── FileService.php
│ ├── HelperService.php
│ ├── ModelService.php
│ └── TableColumnsService.php
├── Stubs
│ ├── api.controller.stub
│ ├── api_repository.controller.stub
│ ├── api_repository_spatie_data.controller.stub
│ ├── api_spatie_data.controller.stub
│ ├── enum.stub
│ ├── repository.stub
│ ├── request.stub
│ ├── resource.stub
│ ├── service.stub
│ ├── spatie_data.stub
│ ├── web.controller.stub
│ ├── web_repository.controller.stub
│ ├── web_repository_spatie_data.controller.stub
│ └── web_spatie_data.controller.stub
├── Traits
│ └── TableColumnsTrait.php
└── Transformers
│ ├── EnumTransformer.php
│ └── SpatieDataTransformer.php
└── tests
├── Models
└── User.php
├── Pest.php
├── TestCase.php
└── Unit
├── DatabaseValidatorServiceTest.php
├── EnumTransformerTest.php
├── FileServiceTest.php
├── HelperServiceTest.php
├── LaravelAutoCrudServiceProviderTest.php
├── ModelServiceTest.php
├── SpatieDataTransformerTest.php
└── TableColumnsServiceTest.php
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [mrmarchone]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | polar: # Replace with a single Polar username
13 | buy_me_a_coffee: mrmarchone
14 | thanks_dev: # Replace with a single thanks.dev username
15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | /.idea
3 | composer.lock
4 | .phpunit.cache
5 |
--------------------------------------------------------------------------------
/Config/laravel_auto_crud.php:
--------------------------------------------------------------------------------
1 | 'app/Models/',
15 | ];
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Abdelrahman Muhammed
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Laravel Auto CRUD Generator
2 |
3 | 
4 |
5 | 
6 | 
7 | 
8 | 
9 |
10 | Laravel Auto CRUD Generator is a package that simplifies CRUD (Create, Read, Update, Delete) operations for your Laravel application. With a single command, you can generate all necessary files and logic for a selected model, reducing development time and effort.
11 |
12 | [Watch the Video on YouTube](https://www.youtube.com/watch?v=6IqRc3OgUIM)
13 |
14 | ## Features
15 | - Automatically detects models in the app/Models folder.
16 | - Provides an interactive CLI to select a model.
17 | - Generates controller, request validation, routes, views, and more.
18 | - Follows Laravel's best practices for clean and maintainable code.
19 |
20 | ## Installation
21 |
22 | You can install the package via Composer:
23 |
24 | ```bash
25 | composer require mrmarchone/laravel-auto-crud --dev
26 | ```
27 |
28 | ## Publish Configuration
29 |
30 | You can publish the configuration file via:
31 |
32 | ```bash
33 | php artisan vendor:publish --provider="Mrmarchone\LaravelAutoCrud\LaravelAutoCrudServiceProvider" --tag="auto-crud-config"
34 | ```
35 |
36 |
37 | ## Usage
38 |
39 | To generate CRUD operations for a model, use the following Artisan command:
40 |
41 | ```bash
42 | php artisan auto-crud:generate -h
43 | ```
44 |
45 | ```textmate
46 | Description:
47 | A command to create auto CRUD for your models.
48 |
49 | Usage:
50 | auto-crud:generate [options]
51 |
52 | Options:
53 | -A, --all Force generate all possible files without overwrite option.
54 | -M, --model[=MODEL] Select one or more of your models. (multiple values allowed)
55 | -T, --type[=TYPE] Select api, web or both. (multiple values allowed)
56 | -R, --repository Working with repository design pattern.
57 | -O, --overwrite Overwrite the files if already exists.
58 | -P, --pattern[=PATTERN] Supports Spatie-Data Pattern. [default: "normal"]
59 | -C, --curl Generate CURL Requests for API.
60 | -S, --swagger-api Generate Swagger API json for API.
61 | -h, --help Display help for the given command. When no command is given display help for the list command
62 | --silent Do not output any message
63 | -q, --quiet Only errors are displayed. All other output is suppressed
64 | -V, --version Display this application version
65 | --ansi|--no-ansi Force (or disable --no-ansi) ANSI output
66 | -n, --no-interaction Do not ask any interactive question
67 | --env[=ENV] The environment the command should run under
68 | -FA, --force-all Force generate all possible files with overwrite option.
69 | -MP, --model-path[=MODEL-PATH] Set models path.
70 | -PM, --postman Generate Postman Collection for API.
71 | -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
72 | ```
73 |
74 | > ⚠️ **Warning**
75 | > Take care when you create a models outside app directory, because command will generate the folder structure in all folders like Controllers, Resources, Requests, etc.
76 | > Example (Models Path) => AnotherModels/Models , this will generate files like this one app/Http/Controllers/AnotherModels/Models/ModelController.php
77 |
78 | ### Example:
79 |
80 | ```bash
81 | php artisan auto-crud:generate --model-path=app/AnotherModels --model=User --model=Manager --overwrite --type=api --repository --pattern=spatie-data --curl --postman
82 | ```
83 |
84 | 
85 |
86 | This will generate:
87 | - API Controller:
88 | ```php
89 | paginate(10));
105 | }
106 |
107 | public function store(UserRequest $request): UserResource|\Illuminate\Http\JsonResponse
108 | {
109 | try {
110 | $user = User::create($request->validated());
111 | return new UserResource($user);
112 | } catch (\Exception $exception) {
113 | report($exception);
114 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
115 | }
116 | }
117 |
118 | public function show(User $user): UserResource
119 | {
120 | return UserResource::make($user);
121 | }
122 |
123 | public function update(UserRequest $request, User $user): UserResource|\Illuminate\Http\JsonResponse
124 | {
125 | try {
126 | $user->update($request->validated());
127 | return new UserResource($user);
128 | } catch (\Exception $exception) {
129 | report($exception);
130 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
131 | }
132 | }
133 |
134 | public function destroy(User $user): \Illuminate\Http\JsonResponse
135 | {
136 | try {
137 | $user->delete();
138 | return response()->json(['message' => 'Deleted successfully'], Response::HTTP_NO_CONTENT);
139 | } catch (\Exception $exception) {
140 | report($exception);
141 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
142 | }
143 | }
144 | }
145 | ```
146 | - API Controller with Spatie Data (if applicable):
147 | ```php
148 | paginate(10));
162 | }
163 |
164 | public function store(UserData $data): UserData|\Illuminate\Http\JsonResponse
165 | {
166 | try {
167 | $user = User::create($data->all());
168 | return new UserData($user);
169 | } catch (\Exception $exception) {
170 | report($exception);
171 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
172 | }
173 | }
174 |
175 | public function show(User $user): UserData
176 | {
177 | return UserData::from($user);
178 | }
179 |
180 | public function update(UserData $data, User $user): UserData|\Illuminate\Http\JsonResponse
181 | {
182 | try {
183 | $user->update($data->all());
184 | return UserData::from($user);
185 | } catch (\Exception $exception) {
186 | report($exception);
187 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
188 | }
189 | }
190 |
191 | public function destroy(User $user): \Illuminate\Http\JsonResponse
192 | {
193 | try {
194 | $user->delete();
195 | return response()->json(['message' => 'Deleted successfully'], Response::HTTP_OK);
196 | } catch (\Exception $exception) {
197 | report($exception);
198 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
199 | }
200 | }
201 | }
202 | ```
203 |
204 | - API Controller with Service (if applicable):
205 | ```php
206 | userService = $userService;
233 | }
234 |
235 | public function index(): \Illuminate\Http\Resources\Json\AnonymousResourceCollection
236 | {
237 | return UserResource::collection($this->userService->getAll());
238 | }
239 |
240 | public function store(UserRequest $request): UserResource|\Illuminate\Http\JsonResponse
241 | {
242 | try {
243 | return new UserResource($this->userService->save($request->validated()));
244 | } catch (\Exception $exception) {
245 | report($exception);
246 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
247 | }
248 | }
249 |
250 | public function show(int $id): UserResource
251 | {
252 | return UserResource::make($this->userService->getById($id));
253 | }
254 |
255 | public function update(UserRequest $request, int $id): UserResource|\Illuminate\Http\JsonResponse
256 | {
257 | try {
258 | return new UserResource($this->userService->update($request->validated(), $id));
259 | } catch (\Exception $exception) {
260 | report($exception);
261 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
262 | }
263 | }
264 |
265 | public function destroy(int $id): \Illuminate\Http\JsonResponse
266 | {
267 | try {
268 | $this->userService->deleteById($id);
269 | return response()->json(['message' => 'Deleted successfully'], Response::HTTP_OK);
270 | } catch (\Exception $exception) {
271 | report($exception);
272 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
273 | }
274 | }
275 | }
276 | ```
277 |
278 | - Web Controller:
279 | ```php
280 | paginate(10);
293 | return view('users.index', compact('users'));
294 | }
295 |
296 | public function create(): \Illuminate\Contracts\View\View
297 | {
298 | return view('users.create');
299 | }
300 |
301 | public function store(UserRequest $request): \Illuminate\Http\RedirectResponse
302 | {
303 | User::create($request->validated());
304 | return redirect()->route('users.index')->with('success', 'Created successfully');
305 | }
306 |
307 | public function show(User $user): \Illuminate\Contracts\View\View
308 | {
309 | return view('users.show', compact('user'));
310 | }
311 |
312 | public function edit(User $user): \Illuminate\Contracts\View\View
313 | {
314 | return view('users.edit', compact('user'));
315 | }
316 |
317 | public function update(UserRequest $request, User $user): \Illuminate\Http\RedirectResponse
318 | {
319 | $user->update($request->validated());
320 | return redirect()->route('users.index')->with('success', 'Updated successfully');
321 | }
322 |
323 | public function destroy(User $user): \Illuminate\Http\RedirectResponse
324 | {
325 | $user->delete();
326 | return redirect()->route('users.index')->with('success', 'Deleted successfully');
327 | }
328 | }
329 | ```
330 | - Web Controller with Spatie Data (if applicable):
331 | ```php
332 | paginate(10));
345 | return view('users.index', compact('users'));
346 | }
347 |
348 | public function create(): \Illuminate\Contracts\View\View
349 | {
350 | return view('users.create');
351 | }
352 |
353 | public function store(UserData $data): \Illuminate\Http\RedirectResponse
354 | {
355 | User::create($data->all());
356 | return redirect()->route('users.index')->with('success', 'Created successfully');
357 | }
358 |
359 | public function show(User $user): \Illuminate\Contracts\View\View
360 | {
361 | return view('users.show', compact('user'));
362 | }
363 |
364 | public function edit(User $user): \Illuminate\Contracts\View\View
365 | {
366 | return view('users.edit', compact('user'));
367 | }
368 |
369 | public function update(UserData $data, User $user): \Illuminate\Http\RedirectResponse
370 | {
371 | $user->update($data->all());
372 | return redirect()->route('users.index')->with('success', 'Updated successfully');
373 | }
374 |
375 | public function destroy(User $user): \Illuminate\Http\RedirectResponse
376 | {
377 | $user->delete();
378 | return redirect()->route('users.index')->with('success', 'Deleted successfully');
379 | }
380 | }
381 | ```
382 |
383 | - Web Controller with Service (if applicable):
384 | ```php
385 | userService = $userService;
409 | }
410 |
411 | public function index(): \Illuminate\Contracts\View\View
412 | {
413 | $users = $this->userService->getAll();
414 | return view('users.index', compact('users'));
415 | }
416 |
417 | public function create(): \Illuminate\Contracts\View\View
418 | {
419 | return view('users.create');
420 | }
421 |
422 | public function store(UserRequest $request): \Illuminate\Http\RedirectResponse
423 | {
424 | $this->userService->save($request->validated());
425 | return redirect()->route('users.index')->with('success', 'Created successfully');
426 | }
427 |
428 | public function show(int $id): \Illuminate\Contracts\View\View
429 | {
430 | $user = $this->userService->getById($id);
431 | return view('users.show', compact('user'));
432 | }
433 |
434 | public function edit(int $id): \Illuminate\Contracts\View\View
435 | {
436 | $user = $this->userService->getById($id);
437 | return view('users.edit', compact('user'));
438 | }
439 |
440 | public function update(UserRequest $request, int $id): \Illuminate\Http\RedirectResponse
441 | {
442 | $this->userService->update($request->validated(), $id);
443 | return redirect()->route('users.index')->with('success', 'Updated successfully');
444 | }
445 |
446 | public function destroy(int $id): \Illuminate\Http\RedirectResponse
447 | {
448 | $this->userService->deleteById($id);
449 | return redirect()->route('users.index')->with('success', 'Deleted successfully');
450 | }
451 | }
452 | ```
453 |
454 | - Request:
455 | ```php
456 | 'required|string|max:255',
473 | 'email' => 'required|string|max:255|unique:users,email',
474 | 'email_verified_at' => 'nullable|date',
475 | 'password' => 'required|string|max:255',
476 | 'remember_token' => 'nullable|string|max:100',
477 | ];
478 | }
479 | }
480 | ```
481 |
482 | - Resource:
483 | ```php
484 | $this->id,
497 | 'name' => $this->name,
498 | 'email' => $this->email,
499 | 'email_verified_at' => $this->email_verified_at,
500 | 'password' => $this->password,
501 | 'remember_token' => $this->remember_token,
502 | 'created_at' => $this->created_at,
503 | 'updated_at' => $this->updated_at,
504 | ];
505 | }
506 | }
507 | ```
508 |
509 | - API Routes:
510 | ```php
511 |
532 |
Create users
533 |
573 |
574 | ```
575 | - Edit:
576 | ```bladehtml
577 |
578 |
Edit user
579 |
622 |
623 | ```
624 | - Index:
625 | ```bladehtml
626 |
627 |
users List
628 |
Create users
629 |
630 |
631 |
632 | name |
633 | email |
634 | email_verified_at |
635 | password |
636 | remember_token |
637 |
638 |
639 |
640 | @foreach ($users as $item)
641 |
642 | {{$item->name}} |
643 | {{$item->email}} |
644 | {{$item->email_verified_at}} |
645 | {{$item->password}} |
646 | {{$item->remember_token}} |
647 |
648 | Edit
649 |
656 | |
657 |
658 | @endforeach
659 |
660 |
661 |
662 | ```
663 | - View:
664 | ```bladehtml
665 |
666 |
user Details
667 |
name: {{ $user ->name }}
668 |
email: {{ $user ->email }}
669 |
email_verified_at: {{ $user ->email_verified_at }}
670 |
password: {{ $user ->password }}
671 |
remember_token: {{ $user ->remember_token }}
672 |
673 |
674 | ```
675 |
676 | - CURL (if applicable):
677 | - You will find it in the laravel-auto-crud folder under the name curl.txt.
678 | ```bash
679 | =====================User=====================
680 | curl --location 'http://127.0.0.1:8000/api/users' \
681 | --header 'Accept: application/json' \
682 | --header 'Content-Type: application/json' \
683 | --request POST \
684 | --data '{
685 | "name": "value",
686 | "email": "value",
687 | "email_verified_at": "value",
688 | "password": "value",
689 | "remember_token": "value"
690 | }'
691 |
692 | curl --location 'http://127.0.0.1:8000/api/users/:id' \
693 | --header 'Accept: application/json' \
694 | --header 'Content-Type: application/json' \
695 | --request PATCH \
696 | --data '{
697 | "name": "value",
698 | "email": "value",
699 | "email_verified_at": "value",
700 | "password": "value",
701 | "remember_token": "value"
702 | }'
703 |
704 | curl --location 'http://127.0.0.1:8000/api/users/:id' \
705 | --header 'Accept: application/json' \
706 | --header 'Content-Type: application/json' \
707 | --request DELETE
708 |
709 | curl --location 'http://127.0.0.1:8000/api/users' \
710 | --header 'Accept: application/json' \
711 | --header 'Content-Type: application/json' \
712 | --request GET
713 |
714 | curl --location 'http://127.0.0.1:8000/api/users/:id' \
715 | --header 'Accept: application/json' \
716 | --header 'Content-Type: application/json' \
717 | --request GET
718 |
719 | =====================User=====================
720 | ```
721 | - Postman Collection (if applicable):
722 | - You will find it in the laravel-auto-crud folder under the name postman.json.
723 | - Swagger API V3 Collection (if applicable):
724 | - You will find it in the laravel-auto-crud folder under the name swagger-api.json.
725 | - Repository (if applicable):
726 | ```php
727 | user = $user;
747 | }
748 |
749 | /**
750 | * Get all user.
751 | *
752 | * @return User $user
753 | */
754 | public function all()
755 | {
756 | return $this->user->get();
757 | }
758 |
759 | /**
760 | * Get user by id
761 | *
762 | * @param $id
763 | * @return mixed
764 | */
765 | public function getById(int $id)
766 | {
767 | return $this->user->find($id);
768 | }
769 |
770 | /**
771 | * Save User
772 | *
773 | * @param $data
774 | * @return User
775 | */
776 | public function save(array $data)
777 | {
778 | return User::create($data);
779 | }
780 |
781 | /**
782 | * Update User
783 | *
784 | * @param $data
785 | * @return User
786 | */
787 | public function update(array $data, int $id)
788 | {
789 | $user = $this->user->find($id);
790 | $user->update($data);
791 | return $user;
792 | }
793 |
794 | /**
795 | * Delete User
796 | *
797 | * @param $data
798 | * @return User
799 | */
800 | public function delete(int $id)
801 | {
802 | $user = $this->user->find($id);
803 | $user->delete();
804 | return $user;
805 | }
806 | }
807 | ```
808 |
809 | - Service (if applicable):
810 | ```php
811 | userRepository = $userRepository;
835 | }
836 |
837 | /**
838 | * Get all userRepository.
839 | *
840 | * @return String
841 | */
842 | public function getAll()
843 | {
844 | return $this->userRepository->all();
845 | }
846 |
847 | /**
848 | * Get userRepository by id.
849 | *
850 | * @param $id
851 | * @return String
852 | */
853 | public function getById(int $id)
854 | {
855 | return $this->userRepository->getById($id);
856 | }
857 |
858 | /**
859 | * Validate userRepository data.
860 | * Store to DB if there are no errors.
861 | *
862 | * @param array $data
863 | * @return String
864 | */
865 | public function save(array $data)
866 | {
867 | return $this->userRepository->save($data);
868 | }
869 |
870 | /**
871 | * Update userRepository data
872 | * Store to DB if there are no errors.
873 | *
874 | * @param array $data
875 | * @return String
876 | */
877 | public function update(array $data, int $id)
878 | {
879 | DB::beginTransaction();
880 | try {
881 | $userRepository = $this->userRepository->update($data, $id);
882 | DB::commit();
883 | return $userRepository;
884 | } catch (Exception $e) {
885 | DB::rollBack();
886 | report($e);
887 | throw new InvalidArgumentException('Unable to update post data');
888 | }
889 | }
890 |
891 | /**
892 | * Delete userRepository by id.
893 | *
894 | * @param $id
895 | * @return String
896 | */
897 | public function deleteById(int $id)
898 | {
899 | DB::beginTransaction();
900 | try {
901 | $userRepository = $this->userRepository->delete($id);
902 | DB::commit();
903 | return $userRepository;
904 | } catch (Exception $e) {
905 | DB::rollBack();
906 | report($e);
907 | throw new InvalidArgumentException('Unable to delete post data');
908 | }
909 | }
910 |
911 | }
912 | ```
913 | - Spatie Data (if applicable):
914 | ```php
915 | =8.1",
7 | "illuminate/support": "^10.0|^11.0|^12.0",
8 | "laravel/prompts": "*",
9 | "spatie/laravel-data": "*"
10 | },
11 | "license": "MIT",
12 | "autoload": {
13 | "psr-4": {
14 | "Mrmarchone\\LaravelAutoCrud\\": "src/"
15 | }
16 | },
17 | "autoload-dev": {
18 | "psr-4": {
19 | "Tests\\": "tests/"
20 | }
21 | },
22 | "authors": [
23 | {
24 | "name": "Abdelrahman Muhammed",
25 | "email": "mrmarchone@gmail.com"
26 | }
27 | ],
28 | "extra": {
29 | "laravel": {
30 | "providers": [
31 | "Mrmarchone\\LaravelAutoCrud\\LaravelAutoCrudServiceProvider"
32 | ]
33 | }
34 | },
35 | "minimum-stability": "dev",
36 | "prefer-stable": true,
37 | "require-dev": {
38 | "laravel/pint": "*",
39 | "pestphp/pest": "^3.7",
40 | "orchestra/testbench": "^9.9",
41 | "mockery/mockery": "*"
42 | },
43 | "config": {
44 | "allow-plugins": {
45 | "pestphp/pest-plugin": true
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/images/command.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrmarchone/laravel-auto-crud/a5fd7f0d1b950f5a7171dccb3e9955376786b8fe/images/command.png
--------------------------------------------------------------------------------
/images/laravel-auto-crud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrmarchone/laravel-auto-crud/a5fd7f0d1b950f5a7171dccb3e9955376786b8fe/images/laravel-auto-crud.png
--------------------------------------------------------------------------------
/images/resources_views.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrmarchone/laravel-auto-crud/a5fd7f0d1b950f5a7171dccb3e9955376786b8fe/images/resources_views.png
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
15 | ./tests/Unit
16 |
17 |
18 | ./tests/Feature
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | src/
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/Builders/BaseBuilder.php:
--------------------------------------------------------------------------------
1 | fileService = new FileService;
16 | }
17 |
18 | protected function getFullModelNamespace(array $modelData): string
19 | {
20 | return $modelData['namespace'] ? $modelData['namespace'].'\\'.$modelData['modelName'] : $modelData['modelName'];
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Builders/ControllerBuilder.php:
--------------------------------------------------------------------------------
1 | fileService->createFromStub($modelData, 'api.controller', 'Http/Controllers/API', 'Controller', $overwrite, function ($modelData) use ($resource, $request) {
15 | $model = $this->getFullModelNamespace($modelData);
16 | $resourceName = explode('\\', $resource);
17 | $requestName = explode('\\', $request);
18 |
19 | return [
20 | '{{ requestNamespace }}' => $request,
21 | '{{ resourceNamespace }}' => $resource,
22 | '{{ modelNamespace }}' => $model,
23 | '{{ resource }}' => end($resourceName),
24 | '{{ request }}' => end($requestName),
25 | '{{ model }}' => $modelData['modelName'],
26 | '{{ modelVariable }}' => lcfirst($modelData['modelName']),
27 | ];
28 | });
29 | }
30 |
31 | public function createAPISpatieData(array $modelData, string $spatieData, bool $overwrite = false): string
32 | {
33 | return $this->fileService->createFromStub($modelData, 'api_spatie_data.controller', 'Http/Controllers/API', 'Controller', $overwrite, function ($modelData) use ($spatieData) {
34 | $model = $this->getFullModelNamespace($modelData);
35 | $spatieDataName = explode('\\', $spatieData);
36 |
37 | return [
38 | '{{ spatieDataNamespace }}' => $spatieData,
39 | '{{ modelNamespace }}' => $model,
40 | '{{ spatieData }}' => end($spatieDataName),
41 | '{{ model }}' => $modelData['modelName'],
42 | '{{ modelVariable }}' => lcfirst($modelData['modelName']),
43 | ];
44 | });
45 | }
46 |
47 | public function createAPIRepository(array $modelData, string $resource, string $request, string $service, bool $overwrite = false): string
48 | {
49 | return $this->fileService->createFromStub($modelData, 'api_repository.controller', 'Http/Controllers/API', 'Controller', $overwrite, function ($modelData) use ($resource, $request, $service) {
50 | $resourceName = explode('\\', $resource);
51 | $requestName = explode('\\', $request);
52 | $serviceName = explode('\\', $service);
53 |
54 | return [
55 | '{{ requestNamespace }}' => $request,
56 | '{{ resourceNamespace }}' => $resource,
57 | '{{ resource }}' => end($resourceName),
58 | '{{ request }}' => end($requestName),
59 | '{{ serviceNamespace }}' => $service,
60 | '{{ service }}' => end($serviceName),
61 | '{{ serviceVariable }}' => lcfirst(end($serviceName)),
62 | ];
63 | });
64 | }
65 |
66 | public function createAPIRepositorySpatieData(array $modelData, string $spatieData, string $service, bool $overwrite = false): string
67 | {
68 | return $this->fileService->createFromStub($modelData, 'api_repository_spatie_data.controller', 'Http/Controllers/API', 'Controller', $overwrite, function ($modelData) use ($spatieData, $service) {
69 | $spatieDataName = explode('\\', $spatieData);
70 | $serviceName = explode('\\', $service);
71 |
72 | return [
73 | '{{ spatieDataNamespace }}' => $spatieData,
74 | '{{ spatieData }}' => end($spatieDataName),
75 | '{{ serviceNamespace }}' => $service,
76 | '{{ service }}' => end($serviceName),
77 | '{{ serviceVariable }}' => lcfirst(end($serviceName)),
78 | ];
79 | });
80 | }
81 |
82 | public function createWeb(array $modelData, string $request, bool $overwrite = false): string
83 | {
84 | return $this->fileService->createFromStub($modelData, 'web.controller', 'Http/Controllers', 'Controller', $overwrite, function ($modelData) use ($request) {
85 | $model = $this->getFullModelNamespace($modelData);
86 | $requestName = explode('\\', $request);
87 |
88 | return [
89 | '{{ requestNamespace }}' => $request,
90 | '{{ modelNamespace }}' => $model,
91 | '{{ request }}' => end($requestName),
92 | '{{ model }}' => $modelData['modelName'],
93 | '{{ modelVariable }}' => lcfirst($modelData['modelName']),
94 | '{{ viewPath }}' => HelperService::toSnakeCase(Str::plural($modelData['modelName'])),
95 | '{{ modelPlural }}' => HelperService::toSnakeCase(Str::plural($modelData['modelName'])),
96 | '{{ routeName }}' => HelperService::toSnakeCase(Str::plural($modelData['modelName'])),
97 | ];
98 | });
99 | }
100 |
101 | public function createWebRepository(array $modelData, string $request, string $service, bool $overwrite = false): string
102 | {
103 | return $this->fileService->createFromStub($modelData, 'web_repository.controller', 'Http/Controllers', 'Controller', $overwrite, function ($modelData) use ($service, $request) {
104 | $model = $this->getFullModelNamespace($modelData);
105 | $serviceName = explode('\\', $service);
106 | $requestName = explode('\\', $request);
107 |
108 | return [
109 | '{{ requestNamespace }}' => $request,
110 | '{{ request }}' => end($requestName),
111 | '{{ serviceNamespace }}' => $service,
112 | '{{ service }}' => end($serviceName),
113 | '{{ serviceVariable }}' => lcfirst(end($serviceName)),
114 | '{{ modelNamespace }}' => $model,
115 | '{{ model }}' => $modelData['modelName'],
116 | '{{ modelVariable }}' => lcfirst($modelData['modelName']),
117 | '{{ viewPath }}' => HelperService::toSnakeCase(Str::plural($modelData['modelName'])),
118 | '{{ modelPlural }}' => HelperService::toSnakeCase(Str::plural($modelData['modelName'])),
119 | '{{ routeName }}' => HelperService::toSnakeCase(Str::plural($modelData['modelName'])),
120 | ];
121 | });
122 | }
123 |
124 | public function createWebSpatieData(array $modelData, string $spatieData, bool $overwrite = false): string
125 | {
126 | return $this->fileService->createFromStub($modelData, 'web_spatie_data.controller', 'Http/Controllers', 'Controller', $overwrite, function ($modelData) use ($spatieData) {
127 | $model = $this->getFullModelNamespace($modelData);
128 | $spatieDataName = explode('\\', $spatieData);
129 |
130 | return [
131 | '{{ spatieDataNamespace }}' => $spatieData,
132 | '{{ modelNamespace }}' => $model,
133 | '{{ spatieData }}' => end($spatieDataName),
134 | '{{ model }}' => $modelData['modelName'],
135 | '{{ modelVariable }}' => lcfirst($modelData['modelName']),
136 | '{{ viewPath }}' => HelperService::toSnakeCase(Str::plural($modelData['modelName'])),
137 | '{{ modelPlural }}' => HelperService::toSnakeCase(Str::plural($modelData['modelName'])),
138 | '{{ routeName }}' => HelperService::toSnakeCase(Str::plural($modelData['modelName'])),
139 | ];
140 | });
141 | }
142 |
143 | public function createWebRepositorySpatieData(array $modelData, string $spatieData, string $service, bool $overwrite = false): string
144 | {
145 | return $this->fileService->createFromStub($modelData, 'web_repository_spatie_data.controller', 'Http/Controllers', 'Controller', $overwrite, function ($modelData) use ($service, $spatieData) {
146 | $model = $this->getFullModelNamespace($modelData);
147 | $serviceName = explode('\\', $service);
148 | $spatieDataName = explode('\\', $spatieData);
149 |
150 | return [
151 | '{{ spatieDataNamespace }}' => $spatieData,
152 | '{{ spatieData }}' => end($spatieDataName),
153 | '{{ serviceNamespace }}' => $service,
154 | '{{ service }}' => end($serviceName),
155 | '{{ serviceVariable }}' => lcfirst(end($serviceName)),
156 | '{{ modelNamespace }}' => $model,
157 | '{{ model }}' => $modelData['modelName'],
158 | '{{ modelVariable }}' => lcfirst($modelData['modelName']),
159 | '{{ viewPath }}' => HelperService::toSnakeCase(Str::plural($modelData['modelName'])),
160 | '{{ modelPlural }}' => HelperService::toSnakeCase(Str::plural($modelData['modelName'])),
161 | '{{ routeName }}' => HelperService::toSnakeCase(Str::plural($modelData['modelName'])),
162 | ];
163 | });
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/Builders/DocumentationBuilders/CURLBuilder.php:
--------------------------------------------------------------------------------
1 | modelService = new ModelService;
22 | $this->tableColumnsService = new TableColumnsService;
23 | }
24 |
25 | public function create(array $modelData, bool $overwrite = false): void
26 | {
27 | $laravelAutoCrudPath = base_path('laravel-auto-crud');
28 | if (! file_exists($laravelAutoCrudPath)) {
29 | mkdir($laravelAutoCrudPath, 0755, true);
30 | }
31 |
32 | $routeBase = sprintf(
33 | config('app.url').'/api/%s',
34 | HelperService::toSnakeCase(Str::plural($modelData['modelName']))
35 | );
36 | $endpoints = [
37 | ['POST', '', $this->getCurlData($modelData)],
38 | ['PATCH', '/:id', $this->getCurlData($modelData)],
39 | ['DELETE', '/:id'],
40 | ['GET', ''],
41 | ['GET', '/:id'],
42 | ];
43 | $overwrite = $overwrite ? 0 : FILE_APPEND;
44 | file_put_contents($laravelAutoCrudPath.'/curl.txt', "====================={$modelData['modelName']}====================="."\n", $overwrite);
45 | foreach ($endpoints as $endpoint) {
46 | [$method, $path] = $endpoint;
47 | $data = $endpoint[2] ?? [];
48 | $curlCommand = $this->generateCurlCommand($method, $routeBase.$path, $data);
49 | file_put_contents($laravelAutoCrudPath.'/curl.txt', $curlCommand."\n\n", $overwrite);
50 | }
51 | file_put_contents($laravelAutoCrudPath.'/curl.txt', "====================={$modelData['modelName']}====================="."\n", $overwrite);
52 |
53 | info("Updated: $laravelAutoCrudPath/curl.txt");
54 | }
55 |
56 | private function generateCurlCommand(string $method, string $url, array $data = []): string
57 | {
58 | $method = strtoupper($method);
59 | // Base cURL command
60 | $curlCommand = "curl --location '{$url}' \\\n";
61 | $curlCommand .= "--header 'Accept: application/json' \\\n";
62 | $curlCommand .= "--header 'Content-Type: application/json' \\\n";
63 | // Attach data for methods that require a request body
64 | if (in_array($method, ['POST', 'PATCH'])) {
65 | $jsonData = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
66 | $curlCommand .= "--request {$method} \\\n";
67 | $curlCommand .= "--data '".$jsonData."'";
68 | } else {
69 | $curlCommand .= "--request {$method}";
70 | }
71 |
72 | return $curlCommand;
73 | }
74 |
75 | private function getCurlData(array $modelData): array
76 | {
77 | $columns = $this->getAvailableColumns($modelData);
78 | $data = [];
79 |
80 | foreach ($columns as $column) {
81 | $columnName = $column['name'];
82 | $data[$columnName] = 'value';
83 | }
84 |
85 | return $data;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/Builders/DocumentationBuilders/PostmanBuilder.php:
--------------------------------------------------------------------------------
1 | modelService = new ModelService;
22 | $this->tableColumnsService = new TableColumnsService;
23 | }
24 |
25 | public function create(array $modelData, bool $overwrite = false): void
26 | {
27 | $laravelAutoCrudPath = base_path('laravel-auto-crud');
28 | if (! file_exists($laravelAutoCrudPath)) {
29 | mkdir($laravelAutoCrudPath, 0755, true);
30 | }
31 |
32 | $oldItems = [];
33 |
34 | if (! $overwrite) {
35 | if (file_exists($laravelAutoCrudPath.'/postman.json')) {
36 | $fileContents = file_get_contents($laravelAutoCrudPath.'/postman.json');
37 | $fileContents = json_decode($fileContents, true);
38 | $oldItems = $fileContents['item'] ?? [];
39 | }
40 | }
41 |
42 | $oldModels = array_values(array_column($oldItems, 'name'));
43 |
44 | if (count($oldModels)) {
45 | $oldModels = array_combine(range(1, count($oldModels)), $oldModels);
46 | }
47 |
48 | $model = HelperService::toSnakeCase(Str::plural($modelData['modelName']));
49 |
50 | $routeBase = sprintf(
51 | config('app.url').'/api/%s',
52 | $model
53 | );
54 |
55 | $parsedUrl = parse_url($routeBase);
56 |
57 | $items = [
58 | 'name' => ucfirst($model),
59 | ];
60 |
61 | if (! in_array($items['name'], $oldModels)) {
62 | $data = $this->getColumnsData($modelData);
63 |
64 | $endpoints = [
65 | ['POST', '', $data, 'Create '.$model],
66 | ['PATCH', '/:id', $data, 'Update '.$model],
67 | ['DELETE', '/:id', [], 'Delete '.$model],
68 | ['GET', '', [], 'Get '.$model],
69 | ['GET', '/:id', [], 'Get single '.$model],
70 | ];
71 |
72 | foreach ($endpoints as $endpoint) {
73 | [$method, $path] = $endpoint;
74 | $data = $endpoint[2] ?? [];
75 | $items['item'][] = [
76 | 'name' => $endpoint[3],
77 | 'request' => [
78 | 'method' => $method,
79 | 'header' => [
80 | [
81 | 'key' => 'Accept',
82 | 'value' => 'application/json',
83 | 'type' => 'text',
84 | ],
85 | [
86 | 'key' => 'Content-Type',
87 | 'value' => 'application/json',
88 | 'type' => 'text',
89 | ],
90 | ],
91 | 'body' => [
92 | 'mode' => 'raw',
93 | 'raw' => json_encode($data, JSON_PRETTY_PRINT),
94 | 'options' => [
95 | 'raw' => [
96 | 'language' => 'json',
97 | ],
98 | ],
99 | ],
100 | 'url' => [
101 | 'raw' => $routeBase.$path,
102 | 'protocol' => $parsedUrl['scheme'],
103 | 'host' => explode('.', $parsedUrl['host']),
104 | 'port' => $parsedUrl['port'] ?? 80,
105 | 'path' => array_merge(explode('/', substr($parsedUrl['path'], 1)), ! empty($path) ? [substr($path, 1)] : []),
106 | 'variable' => ! empty($path) ? [
107 | [
108 | 'key' => 'id',
109 | 'value' => '1',
110 | ],
111 | ] : [],
112 | ],
113 | ],
114 | 'response' => [
115 | ],
116 | ];
117 | }
118 |
119 | $oldItems[] = $items;
120 |
121 | $newData = json_encode($this->buildPostmanObject(count($oldItems) ? $oldItems : [$items]), JSON_PRETTY_PRINT);
122 | file_put_contents($laravelAutoCrudPath.'/postman.json', $newData);
123 | }
124 |
125 | info("Updated: $laravelAutoCrudPath/postman.json");
126 | }
127 |
128 | private function getColumnsData(array $modelData): array
129 | {
130 | $columns = $this->getAvailableColumns($modelData);
131 | $data = [];
132 |
133 | foreach ($columns as $column) {
134 | $columnName = $column['name'];
135 | $data[$columnName] = 'value';
136 | }
137 |
138 | return $data;
139 | }
140 |
141 | private function buildPostmanObject(array $data): array
142 | {
143 | $appName = config('app.name');
144 |
145 | return [
146 | 'info' => [
147 | 'name' => "Laravel Auto Crud ($appName)",
148 | 'schema' => 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json',
149 | ],
150 | 'item' => [
151 | ...$data,
152 | ],
153 | ];
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/Builders/DocumentationBuilders/SwaggerAPIBuilder.php:
--------------------------------------------------------------------------------
1 | modelService = new ModelService;
22 | $this->tableColumnsService = new TableColumnsService;
23 | }
24 |
25 | public function create(array $modelData, bool $overwrite = false): void
26 | {
27 | $laravelAutoCrudPath = base_path('laravel-auto-crud');
28 | if (! file_exists($laravelAutoCrudPath)) {
29 | mkdir($laravelAutoCrudPath, 0755, true);
30 | }
31 |
32 | $oldSchemas = $oldPaths = [];
33 |
34 | if (! $overwrite) {
35 | if (file_exists($laravelAutoCrudPath.'/swagger-api.json')) {
36 | $fileContents = file_get_contents($laravelAutoCrudPath.'/swagger-api.json');
37 | $fileContents = json_decode($fileContents, true);
38 | $oldPaths = $fileContents['paths'] ?? [];
39 | $oldSchemas = $fileContents['components']['schemas'] ?? [];
40 | }
41 | }
42 |
43 | $oldPathsKeys = array_keys($oldPaths);
44 | $oldSchemasKeys = array_keys($oldSchemas);
45 |
46 | $model = HelperService::toSnakeCase(Str::plural($modelData['modelName']));
47 |
48 | $routeBase = config('app.url').'/api';
49 |
50 | $items = $schemas = [];
51 |
52 | $data = $this->getColumnsData($modelData);
53 |
54 | $endpoints = [
55 | ['POST', '', $data, 'Create '.$model],
56 | ['PATCH', '/{id}', $data, 'Update '.$model],
57 | ['DELETE', '/{id}', [], 'Delete '.$model],
58 | ['GET', '', [], 'Get '.$model],
59 | ['GET', '/{id}', [], 'Get single '.$model],
60 | ];
61 |
62 | $ucFirstModel = ucfirst($model);
63 |
64 | foreach ($endpoints as $endpoint) {
65 | [$method, $parameter] = $endpoint;
66 | $path = '/'.$model.$parameter;
67 | if (in_array($path, $oldPathsKeys)) {
68 | continue;
69 | }
70 | $description = $endpoint[3] ?? '';
71 | $requestBody = $endpoint[2] ?? [];
72 | $items[$path][strtolower($method)] = [
73 | 'summary' => $description,
74 | 'operationId' => HelperService::toSnakeCase($description),
75 | 'tags' => [$ucFirstModel],
76 | 'parameters' => ! empty($parameter) ? [
77 | [
78 | 'name' => 'id',
79 | 'in' => 'path',
80 | 'required' => true,
81 | 'schema' => [
82 | 'type' => 'integer',
83 | ],
84 | ]] : [],
85 | 'responses' => [
86 | '200' => [
87 | 'description' => 'Successful response',
88 | 'content' => [
89 | 'application/json' => [
90 | 'schema' => [
91 | 'type' => 'array',
92 | 'items' => [
93 | '$ref' => '#/components/schemas/'.$ucFirstModel,
94 | ],
95 | ],
96 | ],
97 | ],
98 | ],
99 | ],
100 | ];
101 | if (count($requestBody)) {
102 | $items[$path][strtolower($method)]['requestBody'] = [
103 | 'required' => true,
104 | 'content' => [
105 | 'application/json' => [
106 | 'schema' => [
107 | '$ref' => '#/components/schemas/'.$ucFirstModel,
108 | ],
109 | ],
110 | ],
111 | ];
112 | }
113 | }
114 | if (! in_array($ucFirstModel, $oldSchemasKeys)) {
115 | $schemas[$ucFirstModel] = [
116 | 'type' => 'object',
117 | ];
118 |
119 | if (count($data)) {
120 | $schemas[$ucFirstModel]['properties'] = collect($data)->map(fn ($item) => collect($item)->filter(fn ($value, $key) => $key !== 'is_required'))->toArray();
121 | $schemas[$ucFirstModel]['required'] = collect($data)->where('is_required', true)->keys()->toArray();
122 | }
123 | }
124 |
125 | if (count($items)) {
126 | $oldPaths = array_merge($oldPaths, $items);
127 | }
128 |
129 | if (count($schemas)) {
130 | $oldSchemas = array_merge($oldSchemas, $schemas);
131 | }
132 |
133 | $newData = json_encode($this->buildSwaggerObject($routeBase, $oldPaths, $oldSchemas), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
134 |
135 | file_put_contents($laravelAutoCrudPath.'/swagger-api.json', $newData);
136 |
137 | info("Updated: $laravelAutoCrudPath/swagger-api.json");
138 | }
139 |
140 | private function getColumnsData(array $modelData): array
141 | {
142 | $columns = $this->getAvailableColumns($modelData);
143 | $data = [];
144 | $is_enum = false;
145 | foreach ($columns as $column) {
146 | $columnName = $column['name'];
147 | switch ($column['type']) {
148 | case 'varchar':
149 | case 'char':
150 | case 'text':
151 | case 'tinytext':
152 | case 'mediumtext':
153 | case 'longtext':
154 | $columnType = 'string';
155 | $value = 'Example Value';
156 | break;
157 |
158 | case 'int':
159 | case 'tinyint':
160 | case 'smallint':
161 | case 'mediumint':
162 | case 'bigint':
163 | $columnType = 'integer';
164 | $value = 1;
165 | break;
166 |
167 | case 'decimal':
168 | case 'numeric':
169 | case 'float':
170 | case 'double':
171 | $columnType = 'number';
172 | $value = 1.1;
173 | break;
174 |
175 | case 'boolean':
176 | case 'bit':
177 | case 'tinyint(1)':
178 | $columnType = 'boolean';
179 | $value = true;
180 | break;
181 |
182 | case 'date':
183 | case 'datetime':
184 | case 'timestamp':
185 | $columnType = 'string';
186 | $format = 'date-time';
187 | $value = '2025-01-01 00:00:00';
188 | break;
189 |
190 | case 'time':
191 | $columnType = 'string';
192 | $format = 'time';
193 | $value = '00:00:00';
194 | break;
195 |
196 | case 'json':
197 | case 'enum':
198 | case 'set':
199 | $columnType = 'string';
200 | $value = count($column['allowed_values']) ? $column['allowed_values'] : ['Value 1', 'Value 2'];
201 | $is_enum = true;
202 | $format = null;
203 | break;
204 |
205 | case 'blob':
206 | case 'binary':
207 | case 'varbinary':
208 | case 'tinyblob':
209 | case 'mediumblob':
210 | case 'longblob':
211 | $columnType = 'string';
212 | $format = 'binary';
213 | $value = 'Value';
214 | break;
215 |
216 | default:
217 | $columnType = 'string';
218 | $value = 'Value';
219 | }
220 |
221 | $enumData = $is_enum ? ['enum' => $value] : ['example' => $value];
222 |
223 | $data[$columnName] = ['type' => $columnType, ...$enumData, 'is_required' => ! $column['is_nullable']];
224 | if (isset($format)) {
225 | $data[$columnName]['format'] = $format;
226 | }
227 | }
228 |
229 | return $data;
230 | }
231 |
232 | private function buildSwaggerObject(string $url, array $data, array $schemas = []): array
233 | {
234 | $appName = config('app.name');
235 |
236 | return [
237 | 'openapi' => '3.0.0',
238 | 'info' => [
239 | 'title' => "Laravel Auto Crud ($appName)",
240 | 'version' => '1.0.0',
241 | 'description' => 'This is an automatic generated Swagger JSON file with full CRUD operations',
242 | ],
243 | 'servers' => [
244 | [
245 | 'url' => $url,
246 | 'description' => 'Server',
247 | ],
248 | ],
249 | 'paths' => $data,
250 | 'components' => [
251 | 'schemas' => $schemas,
252 | ],
253 | ];
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/src/Builders/EnumBuilder.php:
--------------------------------------------------------------------------------
1 | fileService->createFromStub($modelData, 'enum', 'Enums', 'Enum', $overwrite, function ($modelData) use ($values) {
14 | return [
15 | '{{ data }}' => EnumTransformer::convertDataToString($values),
16 | ];
17 | });
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Builders/RepositoryBuilder.php:
--------------------------------------------------------------------------------
1 | fileService->createFromStub($modelData, 'repository', 'Repositories', 'Repository', $overwrite, function ($modelData) {
12 | $model = $this->getFullModelNamespace($modelData);
13 |
14 | return [
15 | '{{ modelNamespace }}' => $model,
16 | '{{ model }}' => $modelData['modelName'],
17 | '{{ modelVariable }}' => lcfirst($modelData['modelName']),
18 | ];
19 | });
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Builders/RequestBuilder.php:
--------------------------------------------------------------------------------
1 | tableColumnsService = new TableColumnsService;
20 | $this->modelService = new ModelService;
21 | }
22 |
23 | public function create(array $modelData, bool $overwrite = false): string
24 | {
25 | return $this->fileService->createFromStub($modelData, 'request', 'Http/Requests', 'Request', $overwrite, function ($modelData) {
26 | return ['{{ data }}' => HelperService::formatArrayToPhpSyntax($this->getRequestData($modelData))];
27 | });
28 | }
29 |
30 | private function getRequestData(array $modelData): array
31 | {
32 | $columns = $this->getAvailableColumns($modelData);
33 |
34 | $validationRules = [];
35 |
36 | foreach ($columns as $column) {
37 | $rules = [];
38 | $columnType = $column['type'];
39 | $maxLength = $column['max_length'];
40 | $isUnique = $column['is_unique'];
41 | $allowedValues = $column['allowed_values'];
42 | // Handle column types
43 | switch ($columnType) {
44 | case 'string':
45 | case 'char':
46 | case 'varchar':
47 | $rules[] = 'string';
48 | if ($maxLength) {
49 | $rules[] = 'max:'.$maxLength;
50 | }
51 | break;
52 |
53 | case 'integer':
54 | case 'int':
55 | case 'bigint':
56 | case 'smallint':
57 | case 'tinyint':
58 | $rules[] = 'integer';
59 | if (str_contains($columnType, 'unsigned')) {
60 | $rules[] = 'min:0';
61 | }
62 | break;
63 |
64 | case 'boolean':
65 | $rules[] = 'boolean';
66 | break;
67 |
68 | case 'date':
69 | case 'datetime':
70 | case 'timestamp':
71 | $rules[] = 'date';
72 | break;
73 |
74 | case 'text':
75 | case 'longtext':
76 | case 'mediumtext':
77 | $rules[] = 'string';
78 | break;
79 |
80 | case 'decimal':
81 | case 'float':
82 | case 'double':
83 | $rules[] = 'numeric';
84 | break;
85 |
86 | case 'enum':
87 | if (! empty($allowedValues)) {
88 | $rules[] = 'in:'.implode(',', $allowedValues);
89 | }
90 | break;
91 |
92 | case 'json':
93 | $rules[] = 'json';
94 | break;
95 |
96 | case 'binary':
97 | case 'blob':
98 | $rules[] = 'string'; // Handle binary data as string for simplicity
99 | break;
100 |
101 | default:
102 | $rules[] = 'string'; // Default fallback
103 | break;
104 | }
105 |
106 | $columnName = $column['name'];
107 |
108 | // Handle unique columns
109 | if ($isUnique) {
110 | $rules[] = 'unique:'.$column['table'].','.$columnName;
111 | }
112 |
113 | // Add rules to the validation array
114 | $validationRules[$columnName] = implode('|', $rules);
115 | }
116 |
117 | return $validationRules;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/Builders/ResourceBuilder.php:
--------------------------------------------------------------------------------
1 | modelService = new ModelService;
20 | $this->tableColumnsService = new TableColumnsService;
21 | }
22 |
23 | public function create(array $modelData, bool $overwrite = false): string
24 | {
25 | return $this->fileService->createFromStub($modelData, 'resource', 'Http/Resources', 'Resource', $overwrite, function ($modelData) {
26 | return ['{{ data }}' => HelperService::formatArrayToPhpSyntax($this->getResourcesData($modelData), true)];
27 | });
28 | }
29 |
30 | private function getResourcesData(array $modelData): array
31 | {
32 | $columns = $this->getAvailableColumns($modelData);
33 |
34 | $data = [];
35 |
36 | foreach ($columns as $column) {
37 | $columnName = $column['name'];
38 | $data[$columnName] = '$this->'.$columnName;
39 | }
40 |
41 | return $data;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Builders/RouteBuilder.php:
--------------------------------------------------------------------------------
1 | createRoutes($routesPath, $routeCode);
20 | }
21 |
22 | if (in_array('web', $types)) {
23 | $routesPath = base_path('routes/web.php');
24 | $routeCode = "Route::resource('/{$modelName}', {$controller}::class);";
25 | $this->createRoutes($routesPath, $routeCode);
26 | }
27 | }
28 |
29 | private function createRoutes(string $routesPath, string $routeCode): void
30 | {
31 | if (! file_exists($routesPath)) {
32 | file_put_contents($routesPath, "fileService->createFromStub($modelData, 'service', 'Services', 'Service', $overwrite, function ($modelData) use ($repository) {
12 | $model = $this->getFullModelNamespace($modelData);
13 | $repositorySplitting = explode('\\', $repository);
14 | $repositoryNamespace = $repository;
15 | $repository = end($repositorySplitting);
16 | $repositoryVariable = lcfirst($repository);
17 |
18 | return [
19 | '{{ modelNamespace }}' => $model,
20 | '{{ model }}' => $modelData['modelName'],
21 | '{{ modelVariable }}' => lcfirst($modelData['modelName']),
22 | '{{ repository }}' => $repository,
23 | '{{ repositoryNamespace }}' => $repositoryNamespace,
24 | '{{ repositoryVariable }}' => $repositoryVariable,
25 | ];
26 | });
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Builders/SpatieDataBuilder.php:
--------------------------------------------------------------------------------
1 | enumBuilder = new EnumBuilder;
22 | $this->modelService = new ModelService;
23 | $this->tableColumnsService = new TableColumnsService;
24 | }
25 |
26 | public function create(array $modelData, bool $overwrite = false): string
27 | {
28 | return $this->fileService->createFromStub($modelData, 'spatie_data', 'Data', 'Data', $overwrite, function ($modelData) use ($overwrite) {
29 | $supportedData = $this->getHelperData($modelData, $overwrite);
30 |
31 | return [
32 | '{{ namespaces }}' => SpatieDataTransformer::convertNamespacesToString($supportedData['namespaces']),
33 | '{{ data }}' => SpatieDataTransformer::convertDataToString($supportedData['properties'] ?? []),
34 | ];
35 | });
36 | }
37 |
38 | private function getHelperData(array $modelData, $overwrite = false): array
39 | {
40 | $columns = $this->getAvailableColumns($modelData);
41 | $properties = [];
42 | $validationNamespaces = [];
43 | $validationNamespace = 'use Spatie\LaravelData\Attributes\Validation\{{ validationNamespace }};';
44 |
45 | foreach ($columns as $column) {
46 | $rules = [];
47 | $isNullable = $column['is_nullable'];
48 | $columnType = $column['type'];
49 | $maxLength = $column['max_length'];
50 | $isUnique = $column['is_unique'];
51 | $allowedValues = $column['allowed_values'];
52 | $validation = '#[{{ validation }}]';
53 | $property = 'public '.($isNullable ? '?' : '').'{{ type }} $'.$column['name'].';';
54 |
55 | // Handle column types
56 | switch ($columnType) {
57 | case 'string':
58 | case 'char':
59 | case 'varchar':
60 | case 'text':
61 | case 'longtext':
62 | case 'mediumtext':
63 | case 'binary':
64 | case 'blob':
65 | $property = str_replace('{{ type }}', 'string', $property);
66 | if ($maxLength) {
67 | $rules[] = 'Max('.$maxLength.')';
68 | $validationNamespaces[] = str_replace('{{ validationNamespace }}', 'Max', $validationNamespace);
69 | }
70 | break;
71 |
72 | case 'integer':
73 | case 'int':
74 | case 'bigint':
75 | case 'smallint':
76 | case 'tinyint':
77 | $property = str_replace('{{ type }}', 'int', $property);
78 | if (str_contains($columnType, 'unsigned')) {
79 | $rules[] = 'Min(0)';
80 | $validationNamespaces[] = str_replace('{{ validationNamespace }}', 'Min', $validationNamespace);
81 | }
82 | break;
83 |
84 | case 'boolean':
85 | $property = str_replace('{{ type }}', 'bool', $property);
86 | break;
87 |
88 | case 'date':
89 | case 'datetime':
90 | case 'timestamp':
91 | $property = str_replace('{{ type }}', 'Carbon', $property);
92 | $rules[] = 'Date';
93 | $validationNamespaces[] = str_replace('{{ validationNamespace }}', 'Date', $validationNamespace);
94 | $validationNamespaces[] = 'use Carbon\Carbon;';
95 | break;
96 |
97 | case 'decimal':
98 | case 'float':
99 | case 'double':
100 | $property = str_replace('{{ type }}', 'int', $property);
101 | $rules[] = 'Numeric';
102 | $validationNamespaces[] = str_replace('{{ validationNamespace }}', 'Numeric', $validationNamespace);
103 | break;
104 |
105 | case 'enum':
106 | if (! empty($allowedValues)) {
107 | $enum = $this->enumBuilder->create($modelData, $allowedValues, $overwrite);
108 | $enumClass = explode('\\', $enum);
109 | $enumClass = end($enumClass);
110 | $rules[] = "Enum($enumClass::class)";
111 | $property = str_replace('{{ type }}', $enumClass, $property);
112 | $validationNamespaces[] = str_replace('{{ validationNamespace }}', 'Enum', $validationNamespace);
113 | $validationNamespaces[] = 'use '.$enum.';';
114 | }
115 | break;
116 |
117 | case 'json':
118 | $property = str_replace('{{ type }}', 'array', $property);
119 | $rules[] = 'Json';
120 | $validationNamespaces[] = str_replace('{{ validationNamespace }}', 'Json', $validationNamespace);
121 | break;
122 |
123 | default:
124 | $property = str_replace('{{ type }}', 'string', $property);
125 | break;
126 | }
127 |
128 | // Handle unique columns
129 | if ($isUnique) {
130 | $table = $column['table'];
131 | $columnName = $column['name'];
132 | $rules[] = "Unique('$table', '$columnName')";
133 | $validationNamespaces[] = str_replace('{{ validationNamespace }}', 'Unique', $validationNamespace);
134 | }
135 |
136 | $properties['properties'][$property] = count($rules) ? str_replace('{{ validation }}', implode(', ', $rules), $validation) : '';
137 | }
138 | $properties['namespaces'] = array_unique($validationNamespaces);
139 |
140 | return $properties;
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/Builders/ViewBuilder.php:
--------------------------------------------------------------------------------
1 | modelService = new ModelService;
22 | $this->tableColumnsService = new TableColumnsService;
23 | }
24 |
25 | public function create(array $modelData, $overwrite = false): void
26 | {
27 | $data = $this->getAvailableColumns($modelData);
28 | $modelName = HelperService::toSnakeCase(Str::plural($modelData['modelName']));
29 | $viewPath = base_path("resources/views/{$modelName}");
30 |
31 | if (! is_dir($viewPath)) {
32 | mkdir($viewPath, 0755, true);
33 | }
34 |
35 | $modelName = $modelData['modelName'];
36 | $methods = [
37 | 'index' => $this->generateIndexPage($modelName, $data),
38 | 'show' => $this->generateViewPage($modelName, $data),
39 | 'create' => $this->generateCreatePage($modelName, $data),
40 | 'edit' => $this->generateEditPage($modelName, $data),
41 | ];
42 |
43 | foreach ($methods as $view => $data) {
44 | $filePath = "$viewPath/{$view}.blade.php";
45 | if (! file_exists($filePath)) {
46 | file_put_contents($filePath, $data);
47 | info('Created '.$filePath);
48 | } else {
49 | if ($overwrite) {
50 | file_put_contents($filePath, $data);
51 | info('Created '.$filePath);
52 | }
53 | }
54 | }
55 | }
56 |
57 | private function generateViewPage(string $modelName, array $data): string
58 | {
59 | $var = lcfirst($modelName);
60 | $html = '';
61 | foreach ($data as $item) {
62 | $html .= ''.$item['name'].': {{ $'.$var.' ->'.$item['name'].' }}
'."\n";
63 | }
64 |
65 | return <<
67 | $var Details
68 | $html
69 |
70 | BLADE;
71 | }
72 |
73 | private function generateIndexPage(string $modelName, array $data): string
74 | {
75 | $var = HelperService::toSnakeCase(Str::plural($modelName));
76 | $header = '';
77 | $body = '';
78 | foreach ($data as $item) {
79 | $header .= ''.$item['name'].' | ';
80 | $body .= '{{$item->'.$item['name'].'}} | '."\n";
81 | }
82 | $header .= '
';
83 |
84 | return <<
86 | $var List
87 | Create $var
88 |
89 |
90 | $header
91 |
92 |
93 | @foreach (\$$var as \$item)
94 |
95 | $body
96 | Edit
97 |
102 | |
103 |
104 | @endforeach
105 |
106 |
107 |
108 | BLADE;
109 | }
110 |
111 | private function generateCreatePage(string $modelName, array $data): string
112 | {
113 | $var = HelperService::toSnakeCase(Str::plural($modelName));
114 | $html = '';
115 | foreach ($data as $item) {
116 | $html .= '
117 |
118 |
119 | @error("'.$item['name'].'")
120 |
{{$message}}
121 | @enderror
122 |
'."\n";
123 | }
124 |
125 | return <<
127 | Create $var
128 |
133 |
134 | BLADE;
135 | }
136 |
137 | private function generateEditPage(string $modelName, array $data): string
138 | {
139 | $var = lcfirst($modelName);
140 | $plural = HelperService::toSnakeCase(Str::plural($modelName));
141 | $parameter = '$'.$var.'->id';
142 | $html = '';
143 | foreach ($data as $item) {
144 | $html .= '
145 |
146 |
147 | @error("'.$item['name'].'")
148 |
{{$message}}
149 | @enderror
150 |
'."\n";
151 | }
152 |
153 | return <<
155 | Edit $var
156 |
162 |
163 | BLADE;
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/Console/Commands/GenerateAutoCrudCommand.php:
--------------------------------------------------------------------------------
1 | option('model-path') ?? config('laravel_auto_crud.fallback_models_path', 'app/Models/');
45 |
46 | HelperService::displaySignature();
47 |
48 | if (! $this->everythingIsOk()) {
49 | return;
50 | }
51 |
52 | $models = [];
53 | if (count($this->option('model'))) {
54 | foreach ($this->option('model') as $model) {
55 | $modelExists = ModelService::isModelExists($model, $modelPath);
56 | if (! $modelExists) {
57 | alert('Model '.$model.' does not exist');
58 |
59 | continue;
60 | }
61 | $models[] = $modelExists;
62 | }
63 | } else {
64 | $models = ModelService::showModels($modelPath);
65 | }
66 |
67 | if (empty($models)) {
68 | alert("Can't find models, if the models folder outside app directory , make sure it's already loaded in psr-4.");
69 |
70 | return;
71 | }
72 | $this->parsingOptions();
73 | $this->generate($models);
74 | }
75 |
76 | private function generate(array $models): void
77 | {
78 | foreach ($models as $model) {
79 | $modelData = ModelService::resolveModelName($model);
80 | $table = ModelService::getFullModelNamespace($modelData, fn ($modelName) => new $modelName);
81 | if (! $this->databaseValidatorService->checkTableExists($table)) {
82 | $createFiles = confirm(
83 | label: 'Table '.$table.' not found, Do you want to create empty auto CRUD files?.'
84 | );
85 | if (! $createFiles) {
86 | alert('Auto CRUD files not generated for model '.$model.'.');
87 |
88 | continue;
89 | }
90 | }
91 | $this->CRUDGenerator->generate($modelData, $this->options());
92 | $this->documentationGenerator->generate($modelData, $this->options(), count($models) > 1);
93 | }
94 |
95 | }
96 |
97 | private function everythingIsOk(): bool
98 | {
99 | if (! $this->databaseValidatorService->checkDataBaseConnection()) {
100 | alert('DB Connection Error.');
101 |
102 | return false;
103 | }
104 |
105 | if ($this->option('type') && empty(array_intersect($this->option('type'), ['api', 'web']))) {
106 | alert('Make sure that the type is "api", "web" or "both".');
107 |
108 | return false;
109 | }
110 |
111 | if ($this->option('pattern') == 'spatie-data' && ! class_exists(\Spatie\LaravelData\Data::class)) {
112 | alert('Make sure that the "spatie-data" package is installed."');
113 |
114 | return false;
115 | }
116 |
117 | return true;
118 | }
119 |
120 | private function parsingOptions(): void
121 | {
122 | if ($this->option('all')) {
123 | $this->forceAllBooleanOptions();
124 | $this->input->setOption('overwrite', false);
125 | }
126 |
127 | if ($this->option('force-all')) {
128 | $this->forceAllBooleanOptions();
129 | $this->input->setOption('overwrite', true);
130 | }
131 |
132 | if (! $this->option('all') && ! $this->option('force-all')) {
133 | HelperService::askForType($this->input, $this->option('type'));
134 | }
135 | }
136 |
137 | private function forceAllBooleanOptions(): void
138 | {
139 | $this->input->setOption('repository', true);
140 | $this->input->setOption('curl', true);
141 | $this->input->setOption('postman', true);
142 | $this->input->setOption('swagger-api', true);
143 | $this->input->setOption('type', ['api', 'web']);
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/LaravelAutoCrudServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->singleton(TableColumnsService::class, function ($app) {
14 | return new TableColumnsService;
15 | });
16 | }
17 |
18 | public function boot(): void
19 | {
20 | $this->publishes([
21 | __DIR__.'/../Config/laravel_auto_crud.php' => config_path('laravel_auto_crud.php'),
22 | ], 'auto-crud-config');
23 |
24 | // Boot any package services here
25 | if ($this->app->runningInConsole()) {
26 | $this->commands([
27 | GenerateAutoCrudCommand::class,
28 | ]);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Services/CRUDGenerator.php:
--------------------------------------------------------------------------------
1 | controllerBuilder = new ControllerBuilder;
31 | $this->resourceBuilder = new ResourceBuilder;
32 | $this->requestBuilder = new RequestBuilder;
33 | $this->routeBuilder = new RouteBuilder;
34 | $this->viewBuilder = new ViewBuilder;
35 | $this->repositoryBuilder = new RepositoryBuilder;
36 | $this->serviceBuilder = new ServiceBuilder;
37 | $this->spatieDataBuilder = new SpatieDataBuilder;
38 | }
39 |
40 | public function generate($modelData, array $options): void
41 | {
42 | $checkForType = $options['type'];
43 |
44 | if ($options['pattern'] == 'spatie-data') {
45 | $spatieDataName = $this->spatieDataBuilder->create($modelData, $options['overwrite']);
46 | } else {
47 | $requestName = $this->requestBuilder->create($modelData, $options['overwrite']);
48 | }
49 |
50 | $repository = $service = null;
51 | if ($options['repository']) {
52 | $repository = $this->repositoryBuilder->create($modelData, $options['overwrite']);
53 | $service = $this->serviceBuilder->create($modelData, $repository, $options['overwrite']);
54 | }
55 |
56 | $data = [
57 | 'requestName' => $requestName ?? '',
58 | 'repository' => $repository ?? '',
59 | 'service' => $service ?? '',
60 | 'spatieData' => $spatieDataName ?? '',
61 | ];
62 |
63 | $controllerName = $this->generateController($checkForType, $modelData, $data, $options);
64 | $this->routeBuilder->create($modelData['modelName'], $controllerName, $checkForType);
65 |
66 | info('Auto CRUD files generated successfully for '.$modelData['modelName'].' Model');
67 | }
68 |
69 | private function generateController(array $types, array $modelData, array $data, array $options): string
70 | {
71 | $controllerName = null;
72 |
73 | if (in_array('api', $types)) {
74 | $controllerName = $this->generateAPIController($modelData, $data['requestName'], $data['repository'], $data['service'], $options, $data['spatieData']);
75 | }
76 |
77 | if (in_array('web', $types)) {
78 | $controllerName = $this->generateWebController($modelData, $data['requestName'], $data['repository'], $data['service'], $options, $data['spatieData']);
79 | }
80 |
81 | if (! $controllerName) {
82 | throw new InvalidArgumentException('Unsupported controller type');
83 | }
84 |
85 | return $controllerName;
86 | }
87 |
88 | private function generateAPIController(array $modelData, string $requestName, string $repository, string $service, array $options, ?string $spatieData = null): string
89 | {
90 | $controllerName = null;
91 |
92 | if ($options['pattern'] == 'spatie-data') {
93 | $controllerName = $repository
94 | ? $this->controllerBuilder->createAPIRepositorySpatieData($modelData, $spatieData, $service, $options['overwrite'])
95 | : $this->controllerBuilder->createAPISpatieData($modelData, $spatieData, $options['overwrite']);
96 | } elseif ($options['pattern'] == 'normal') {
97 | $resourceName = $this->resourceBuilder->create($modelData, $options['overwrite']);
98 | $controllerName = $repository
99 | ? $this->controllerBuilder->createAPIRepository($modelData, $resourceName, $requestName, $service, $options['overwrite'])
100 | : $this->controllerBuilder->createAPI($modelData, $resourceName, $requestName, $options['overwrite']);
101 | }
102 |
103 | if (! $controllerName) {
104 | throw new InvalidArgumentException('Unsupported controller type');
105 | }
106 |
107 | return $controllerName;
108 | }
109 |
110 | private function generateWebController(array $modelData, string $requestName, string $repository, string $service, array $options, string $spatieData = ''): string
111 | {
112 | $controllerName = null;
113 |
114 | if ($options['pattern'] == 'spatie-data') {
115 | $controllerName = $repository
116 | ? $this->controllerBuilder->createWebRepositorySpatieData($modelData, $spatieData, $service, $options['overwrite'])
117 | : $this->controllerBuilder->createWebSpatieData($modelData, $spatieData, $options['overwrite']);
118 | } elseif ($options['pattern'] == 'normal') {
119 | $controllerName = $repository
120 | ? $this->controllerBuilder->createWebRepository($modelData, $requestName, $service, $options['overwrite'])
121 | : $this->controllerBuilder->createWeb($modelData, $requestName, $options['overwrite']);
122 | }
123 |
124 | $this->viewBuilder->create($modelData, $options['overwrite']);
125 |
126 | if (! $controllerName) {
127 | throw new InvalidArgumentException('Unsupported controller type');
128 | }
129 |
130 | return $controllerName;
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/Services/DatabaseValidatorService.php:
--------------------------------------------------------------------------------
1 | getPdo();
16 |
17 | return true;
18 | } catch (\PDOException $e) {
19 | return false;
20 | }
21 | }
22 |
23 | public function checkTableExists(string $table): bool
24 | {
25 | return Schema::hasTable($table);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Services/DocumentationGenerator.php:
--------------------------------------------------------------------------------
1 | CURLBuilder = new CURLBuilder;
18 | $this->postmanBuilder = new PostmanBuilder;
19 | $this->swaggerAPIBuilder = new SwaggerAPIBuilder;
20 | }
21 |
22 | public function generate(array $modelData, array $options, bool $multipleModels = false): void
23 | {
24 | if (in_array('api', $options['type'])) {
25 | $overwrite = $options['overwrite'];
26 |
27 | if ($multipleModels && $options['overwrite']) {
28 | $overwrite = false;
29 | }
30 |
31 | $this->generatePostman($modelData, $options['postman'], $overwrite);
32 | $this->generateCURL($modelData, $options['curl'], $overwrite);
33 | $this->generateSwaggerAPI($modelData, $options['swagger-api'], $overwrite);
34 | }
35 | }
36 |
37 | private function generatePostman(array $modelData, bool $generate, bool $overwrite): void
38 | {
39 | if ($generate) {
40 | $this->postmanBuilder->create($modelData, $overwrite);
41 | }
42 | }
43 |
44 | private function generateCURL(array $modelData, bool $generate, bool $overwrite): void
45 | {
46 | if ($generate) {
47 | $this->CURLBuilder->create($modelData, $overwrite);
48 | }
49 | }
50 |
51 | private function generateSwaggerAPI(array $modelData, bool $generate, bool $overwrite): void
52 | {
53 | if ($generate) {
54 | $this->swaggerAPIBuilder->create($modelData, $overwrite);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Services/FileService.php:
--------------------------------------------------------------------------------
1 | generateFilePath($modelData, $basePath, $suffix);
22 |
23 | if (! $overwrite) {
24 | if (file_exists($filePath)) {
25 | $overwrite = confirm(
26 | label: ucfirst($suffix).' file already exists, do you want to overwrite it? '.$filePath
27 | );
28 | if (! $overwrite) {
29 | return $namespace.'\\'.$modelData['modelName'].$suffix;
30 | }
31 | }
32 | }
33 |
34 | File::ensureDirectoryExists(dirname($filePath), 0777, true);
35 |
36 | $data = $dataCallback ? $dataCallback($modelData) : [];
37 | $content = $this->generateContent($stubPath, $modelData, $namespace, $suffix, $data);
38 |
39 | File::put($filePath, $content);
40 |
41 | info("Created: $filePath");
42 |
43 | return $namespace.'\\'.$modelData['modelName'].$suffix;
44 | }
45 |
46 | private function generateFilePath(array $modelData, string $basePath, string $suffix): string
47 | {
48 | if ($modelData['folders']) {
49 | return app_path("{$basePath}/{$modelData['folders']}/{$modelData['modelName']}{$suffix}.php");
50 | }
51 |
52 | return app_path("{$basePath}/{$modelData['modelName']}{$suffix}.php");
53 | }
54 |
55 | private function generateContent(string $stubPath, array $modelData, string $namespace, string $suffix, array $data = []): string
56 | {
57 | $replacements = [
58 | '{{ class }}' => $modelData['modelName'].$suffix,
59 | '{{ namespace }}' => $namespace,
60 | ...$data,
61 | ];
62 |
63 | return str_replace(array_keys($replacements), array_values($replacements), file_get_contents($stubPath));
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Services/HelperService.php:
--------------------------------------------------------------------------------
1 | $value) {
17 | if ($removeValuesQuotes) {
18 | $formattedArray .= $indent."'$key' => $value,\n";
19 | } else {
20 | $formattedArray .= $indent."'$key' => '$value',\n";
21 | }
22 | }
23 |
24 | $formattedArray .= str_repeat(' ', $indentation - 4).']';
25 |
26 | return $formattedArray;
27 | }
28 |
29 | public static function toSnakeCase(string $text): string
30 | {
31 | // Convert camelCase or PascalCase (ModelName -> model_name)
32 | $text = preg_replace('/([a-z])([A-Z])/', '$1_$2', $text);
33 |
34 | // Replace any non-alphanumeric characters (spaces, dashes, etc.) with underscores
35 | $text = preg_replace('/[^a-zA-Z0-9]+/', '_', $text);
36 |
37 | // Convert to lowercase
38 | return strtolower(trim($text, '_'));
39 | }
40 |
41 | public static function displaySignature(): void
42 | {
43 | $asciiArt = << 0 ? $types : multiselect(
61 | label: 'Do you want to create an "api", "web" or "both" controller?',
62 | options: ['api', 'web'],
63 | default: ['api'],
64 | required: true,
65 | validate: function ($value) {
66 | if (empty(array_intersect($value, ['api', 'web']))) {
67 | return 'Please enter a valid type api or web';
68 | }
69 |
70 | return null;
71 | },
72 | hint: 'Select api, web or both',
73 | );
74 |
75 | $types = array_map('strtolower', $newTypes);
76 |
77 | $inputType->setOption('type', $types);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Services/ModelService.php:
--------------------------------------------------------------------------------
1 | filter(function ($fullNamespace) use ($modelName) {
19 | if (! $fullNamespace) {
20 | return false;
21 | }
22 |
23 | // Check if the class name matches (without namespace)
24 | $classParts = explode('\\', $fullNamespace);
25 | $actualClassName = end($classParts);
26 |
27 | return $actualClassName === $modelName;
28 | })
29 | ->first();
30 | }
31 |
32 | public static function showModels(string $modelsPath): ?array
33 | {
34 | $models = self::getAllModels($modelsPath)
35 | ->filter(function ($fullNamespace) {
36 | if (! $fullNamespace) {
37 | return false;
38 | }
39 |
40 | // Ensure the class exists and is an instance of Model
41 | if (! class_exists($fullNamespace)) {
42 | return false;
43 | }
44 |
45 | return is_subclass_of($fullNamespace, Model::class);
46 | })
47 | ->values() // Reset array keys
48 | ->toArray();
49 |
50 | $models = array_values($models);
51 |
52 | return count($models) ? multiselect(label: 'Select your model, use your space-bar to select.', options: $models) : null;
53 | }
54 |
55 | public static function resolveModelName($modelName): array
56 | {
57 | $parts = explode('\\', $modelName);
58 |
59 | return [
60 | 'modelName' => array_pop($parts),
61 | 'folders' => implode('/', $parts) !== 'App/Models' ? implode('/', $parts) : null,
62 | 'namespace' => str_replace('/', '\\', implode('/', $parts)) ?: null,
63 | ];
64 | }
65 |
66 | public static function getFullModelNamespace(array $modelData, ?callable $modelFactory = null): string
67 | {
68 | if (isset($modelData['namespace']) && $modelData['namespace']) {
69 | $modelName = $modelData['namespace'].'\\'.$modelData['modelName'];
70 | } else {
71 | $modelName = $modelData['modelName'];
72 | }
73 |
74 | // استخدم الـ Factory إذا تم تمريره، وإلا أنشئ الكائن بالطريقة العادية
75 | $model = $modelFactory ? $modelFactory($modelName) : new $modelName;
76 |
77 | if (is_subclass_of($model, Model::class)) {
78 | return $model->getTable();
79 | }
80 |
81 | throw new InvalidArgumentException("Model {$modelName} does not exist");
82 | }
83 |
84 | public static function handleModelsPath(string $modelsPath): string
85 | {
86 | return str_ends_with($modelsPath, '/') ? $modelsPath : $modelsPath.DIRECTORY_SEPARATOR;
87 | }
88 |
89 | private static function getAllModels(string $modelsPath): \Illuminate\Support\Collection
90 | {
91 | $modelsPath = static::handleModelsPath($modelsPath);
92 |
93 | return collect(File::allFiles(static::getModelNameFromPath($modelsPath)))->map(function ($file) {
94 | $content = static::getClassContent($file->getRealPath());
95 | $namespace = '';
96 | if (preg_match('/namespace\s+([^;]+);/', $content, $matches)) {
97 | $namespace = trim($matches[1]);
98 | }
99 | $className = pathinfo($file->getFilename(), PATHINFO_FILENAME);
100 |
101 | return $namespace ? $namespace.'\\'.$className : null;
102 | });
103 | }
104 |
105 | private static function getModelNameFromPath(string $modelsPath): string
106 | {
107 | return base_path($modelsPath);
108 | }
109 |
110 | private static function getClassContent($file): false|string
111 | {
112 | return File::get($file);
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/Services/TableColumnsService.php:
--------------------------------------------------------------------------------
1 | getDriverName();
17 |
18 | $output = [];
19 |
20 | foreach ($columns as $column) {
21 | $columnDetails = $this->getColumnDetails($driver, $table, $column);
22 |
23 | if (count($columnDetails)) {
24 | // Exclude primary keys and timestamps
25 | if ($columnDetails['is_primary_key'] || in_array($column, $excludeColumns)) {
26 | continue;
27 | }
28 |
29 | $output[] = $columnDetails;
30 | }
31 | }
32 |
33 | return $output;
34 | }
35 |
36 | private function getColumnDetails(string $driver, string $table, string $column): array
37 | {
38 | return match ($driver) {
39 | 'mysql' => $this->getMySQLColumnDetails($table, $column),
40 | 'pgsql' => $this->getPostgresColumnDetails($table, $column),
41 | 'sqlite' => $this->getSQLiteColumnDetails($table, $column),
42 | 'sqlsrv' => $this->getSQLServerColumnDetails($table, $column),
43 | default => [],
44 | };
45 | }
46 |
47 | private function getMysqlColumnDetails(string $table, string $column): array
48 | {
49 | $columnDetails = DB::select("SHOW COLUMNS FROM `$table` WHERE Field = ?", [$column]);
50 |
51 | if (empty($columnDetails)) {
52 | return [];
53 | }
54 |
55 | $columnInfo = $columnDetails[0];
56 | $isPrimaryKey = ($columnInfo->Key === 'PRI');
57 | $isUnique = ($columnInfo->Key === 'UNI');
58 | $isNullable = ($columnInfo->Null === 'YES');
59 | $maxLength = isset($columnInfo->Type) && preg_match('/\((\d+)\)/', $columnInfo->Type, $matches) ? $matches[1] : null;
60 |
61 | if (str_starts_with($columnInfo->Type, 'enum')) {
62 | preg_match("/^enum\((.+)\)$/", $columnInfo->Type, $matches);
63 | $allowedValues = isset($matches[1]) ? str_getcsv(str_replace("'", '', $matches[1])) : [];
64 | }
65 |
66 | return [
67 | 'is_primary_key' => $isPrimaryKey,
68 | 'is_unique' => $isUnique,
69 | 'is_nullable' => $isNullable,
70 | 'type' => Schema::getColumnType($table, $column),
71 | 'max_length' => $maxLength,
72 | 'allowed_values' => $allowedValues ?? [],
73 | 'name' => $column,
74 | 'table' => $table,
75 | ];
76 | }
77 |
78 | private function getPostgresColumnDetails(string $table, string $column): array
79 | {
80 | $columnDetails = DB::select("
81 | SELECT
82 | column_name,
83 | column_default,
84 | is_nullable,
85 | data_type,
86 | character_maximum_length,
87 | udt_name,
88 | (SELECT COUNT(*) > 0 FROM information_schema.table_constraints tc
89 | JOIN information_schema.constraint_column_usage ccu
90 | ON tc.constraint_name = ccu.constraint_name
91 | WHERE tc.table_name = ? AND ccu.column_name = ? AND tc.constraint_type = 'PRIMARY KEY') AS is_primary,
92 | (SELECT COUNT(*) > 0 FROM information_schema.table_constraints tc
93 | JOIN information_schema.constraint_column_usage ccu
94 | ON tc.constraint_name = ccu.constraint_name
95 | WHERE tc.table_name = ? AND ccu.column_name = ? AND tc.constraint_type = 'UNIQUE') AS is_unique
96 | FROM information_schema.columns
97 | WHERE table_name = ? AND column_name = ?",
98 | [$table, $column, $table, $column, $table, $column]
99 | );
100 |
101 | if (empty($columnDetails)) {
102 | return [];
103 | }
104 |
105 | $columnInfo = $columnDetails[0];
106 | $isPrimaryKey = $columnInfo->is_primary;
107 | $isUnique = $columnInfo->is_unique;
108 | $isNullable = ($columnInfo->is_nullable === 'YES');
109 | $maxLength = $columnInfo->character_maximum_length ?? null;
110 |
111 | if (str_starts_with($columnInfo->udt_name, '_')) {
112 | preg_match('/^_(.+)$/', $columnInfo->udt_name, $matches);
113 | $allowedValues = isset($matches[1]) ? str_getcsv(str_replace("'", '', $matches[1])) : [];
114 | }
115 |
116 | return [
117 | 'is_primary_key' => $isPrimaryKey,
118 | 'is_unique' => $isUnique,
119 | 'is_nullable' => $isNullable,
120 | 'type' => Schema::getColumnType($table, $column),
121 | 'max_length' => $maxLength,
122 | 'allowed_values' => $allowedValues ?? [],
123 | 'name' => $column,
124 | 'table' => $table,
125 | ];
126 | }
127 |
128 | private function getSqliteColumnDetails(string $table, string $column): array
129 | {
130 | $columnDetails = DB::select("PRAGMA table_info($table)");
131 | foreach ($columnDetails as $col) {
132 | if ($col->name === $column) {
133 | return [
134 | 'is_primary_key' => $col->pk == 1,
135 | 'is_unique' => false,
136 | 'is_nullable' => $col->notnull == 0,
137 | 'type' => Schema::getColumnType($table, $column),
138 | 'max_length' => null,
139 | 'allowed_values' => [],
140 | 'name' => $column,
141 | 'table' => $table,
142 | ];
143 | }
144 | }
145 |
146 | return [];
147 | }
148 |
149 | private function getSQLServerColumnDetails(string $table, string $column): array
150 | {
151 | $columnDetails = DB::select(
152 | "SELECT COLUMN_NAME, COLUMNPROPERTY(object_id(?), COLUMN_NAME, 'IsIdentity') AS is_identity, IS_NULLABLE, DATA_TYPE
153 | FROM INFORMATION_SCHEMA.COLUMNS
154 | WHERE TABLE_NAME = ? AND COLUMN_NAME = ?",
155 | [$table, $table, $column]
156 | );
157 |
158 | if (empty($columnDetails)) {
159 | return [];
160 | }
161 |
162 | $columnInfo = $columnDetails[0];
163 |
164 | return [
165 | 'is_primary_key' => $columnInfo->is_identity,
166 | 'is_unique' => false,
167 | 'is_nullable' => $columnInfo->IS_NULLABLE === 'YES',
168 | 'type' => Schema::getColumnType($table, $column),
169 | 'max_length' => null,
170 | 'allowed_values' => [],
171 | 'name' => $column,
172 | 'table' => $table,
173 | ];
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/src/Stubs/api.controller.stub:
--------------------------------------------------------------------------------
1 | paginate(10));
17 | }
18 |
19 | public function store({{ request }} $request): {{ resource }}|\Illuminate\Http\JsonResponse
20 | {
21 | try {
22 | ${{ modelVariable }} = {{ model }}::create($request->validated());
23 | return new {{ resource }}(${{ modelVariable }});
24 | } catch (\Exception $exception) {
25 | report($exception);
26 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
27 | }
28 | }
29 |
30 | public function show({{ model }} ${{ modelVariable }}): {{ resource }}
31 | {
32 | return {{ resource }}::make(${{ modelVariable }});
33 | }
34 |
35 | public function update({{ request }} $request, {{ model }} ${{ modelVariable }}): {{ resource }}|\Illuminate\Http\JsonResponse
36 | {
37 | try {
38 | ${{ modelVariable }}->update($request->validated());
39 | return new {{ resource }}(${{ modelVariable }});
40 | } catch (\Exception $exception) {
41 | report($exception);
42 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
43 | }
44 | }
45 |
46 | public function destroy({{ model }} ${{ modelVariable }}): \Illuminate\Http\JsonResponse
47 | {
48 | try {
49 | ${{ modelVariable }}->delete();
50 | return response()->json(['message' => 'Deleted successfully'], Response::HTTP_OK);
51 | } catch (\Exception $exception) {
52 | report($exception);
53 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Stubs/api_repository.controller.stub:
--------------------------------------------------------------------------------
1 | {{ serviceVariable }} = ${{ serviceVariable }};
28 | }
29 |
30 | public function index(): \Illuminate\Http\Resources\Json\AnonymousResourceCollection
31 | {
32 | return {{ resource }}::collection($this->{{ serviceVariable }}->getAll());
33 | }
34 |
35 | public function store({{ request }} $request): {{ resource }}|\Illuminate\Http\JsonResponse
36 | {
37 | try {
38 | return new {{ resource }}($this->{{ serviceVariable }}->save($request->validated()));
39 | } catch (\Exception $exception) {
40 | report($exception);
41 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
42 | }
43 | }
44 |
45 | public function show(int $id): {{ resource }}
46 | {
47 | return {{ resource }}::make($this->{{ serviceVariable }}->getById($id));
48 | }
49 |
50 | public function update({{ request }} $request, int $id): {{ resource }}|\Illuminate\Http\JsonResponse
51 | {
52 | try {
53 | return new {{ resource }}($this->{{ serviceVariable }}->update($request->validated(), $id));
54 | } catch (\Exception $exception) {
55 | report($exception);
56 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
57 | }
58 | }
59 |
60 | public function destroy(int $id): \Illuminate\Http\JsonResponse
61 | {
62 | try {
63 | $this->{{ serviceVariable }}->deleteById($id);
64 | return response()->json(['message' => 'Deleted successfully'], Response::HTTP_OK);
65 | } catch (\Exception $exception) {
66 | report($exception);
67 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Stubs/api_repository_spatie_data.controller.stub:
--------------------------------------------------------------------------------
1 | {{ serviceVariable }} = ${{ serviceVariable }};
27 | }
28 |
29 | public function index(): array|\Illuminate\Contracts\Pagination\CursorPaginator|\Illuminate\Contracts\Pagination\Paginator|\Illuminate\Pagination\AbstractCursorPaginator|\Illuminate\Pagination\AbstractPaginator|\Illuminate\Support\Collection|\Illuminate\Support\Enumerable|\Illuminate\Support\LazyCollection|\Spatie\LaravelData\CursorPaginatedDataCollection|\Spatie\LaravelData\DataCollection|\Spatie\LaravelData\PaginatedDataCollection
30 | {
31 | return {{ spatieData }}::collect($this->{{ serviceVariable }}->getAll());
32 | }
33 |
34 | public function store({{ spatieData }} $data): {{ spatieData }}|\Illuminate\Http\JsonResponse
35 | {
36 | try {
37 | return {{ spatieData }}::from($this->{{ serviceVariable }}->save($data->all()));
38 | } catch (\Exception $exception) {
39 | report($exception);
40 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
41 | }
42 | }
43 |
44 | public function show(int $id): {{ spatieData }}
45 | {
46 | return {{ spatieData }}::from($this->{{ serviceVariable }}->getById($id));
47 | }
48 |
49 | public function update({{ spatieData }} $data, int $id): {{ spatieData }}|\Illuminate\Http\JsonResponse
50 | {
51 | try {
52 | return {{ spatieData }}::from($this->{{ serviceVariable }}->update($data->all(), $id));
53 | } catch (\Exception $exception) {
54 | report($exception);
55 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
56 | }
57 | }
58 |
59 | public function destroy(int $id): \Illuminate\Http\JsonResponse
60 | {
61 | try {
62 | $this->{{ serviceVariable }}->deleteById($id);
63 | return response()->json(['message' => 'Deleted successfully'], Response::HTTP_OK);
64 | } catch (\Exception $exception) {
65 | report($exception);
66 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Stubs/api_spatie_data.controller.stub:
--------------------------------------------------------------------------------
1 | paginate(10));
15 | }
16 |
17 | public function store({{ spatieData }} $data): {{ spatieData }}|\Illuminate\Http\JsonResponse
18 | {
19 | try {
20 | ${{ modelVariable }} = {{ model }}::create($data->all());
21 | return new {{ spatieData }}(${{ modelVariable }});
22 | } catch (\Exception $exception) {
23 | report($exception);
24 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
25 | }
26 | }
27 |
28 | public function show({{ model }} ${{ modelVariable }}): {{ spatieData }}
29 | {
30 | return {{ spatieData }}::from(${{ modelVariable }});
31 | }
32 |
33 | public function update({{ spatieData }} $data, {{ model }} ${{ modelVariable }}): {{ spatieData }}|\Illuminate\Http\JsonResponse
34 | {
35 | try {
36 | ${{ modelVariable }}->update($data->all());
37 | return {{ spatieData }}::from(${{ modelVariable }});
38 | } catch (\Exception $exception) {
39 | report($exception);
40 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
41 | }
42 | }
43 |
44 | public function destroy({{ model }} ${{ modelVariable }}): \Illuminate\Http\JsonResponse
45 | {
46 | try {
47 | ${{ modelVariable }}->delete();
48 | return response()->json(['message' => 'Deleted successfully'], Response::HTTP_OK);
49 | } catch (\Exception $exception) {
50 | report($exception);
51 | return response()->json(['error' => 'There is an error.'], Response::HTTP_INTERNAL_SERVER_ERROR);
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Stubs/enum.stub:
--------------------------------------------------------------------------------
1 | {{ modelVariable }} = ${{ modelVariable }};
21 | }
22 |
23 | /**
24 | * Get all {{ modelVariable }}.
25 | *
26 | * @return {{ model }} ${{ modelVariable }}
27 | */
28 | public function all()
29 | {
30 | return $this->{{ modelVariable }}->get();
31 | }
32 |
33 | /**
34 | * Get {{ modelVariable }} by id
35 | *
36 | * @param $id
37 | * @return mixed
38 | */
39 | public function getById(int $id)
40 | {
41 | return $this->{{ modelVariable }}->find($id);
42 | }
43 |
44 | /**
45 | * Save {{ model }}
46 | *
47 | * @param $data
48 | * @return {{ model }}
49 | */
50 | public function save(array $data)
51 | {
52 | return {{ model }}::create($data);
53 | }
54 |
55 | /**
56 | * Update {{ model }}
57 | *
58 | * @param $data
59 | * @return {{ model }}
60 | */
61 | public function update(array $data, int $id)
62 | {
63 | ${{ modelVariable }} = $this->{{ modelVariable }}->find($id);
64 | ${{ modelVariable }}->update($data);
65 | return ${{ modelVariable }};
66 | }
67 |
68 | /**
69 | * Delete {{ model }}
70 | *
71 | * @param $data
72 | * @return {{ model }}
73 | */
74 | public function delete(int $id)
75 | {
76 | ${{ modelVariable }} = $this->{{ modelVariable }}->find($id);
77 | ${{ modelVariable }}->delete();
78 | return ${{ modelVariable }};
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Stubs/request.stub:
--------------------------------------------------------------------------------
1 | {{ repositoryVariable }} = ${{ repositoryVariable }};
25 | }
26 |
27 | /**
28 | * Get all {{ repositoryVariable }}.
29 | *
30 | * @return String
31 | */
32 | public function getAll()
33 | {
34 | return $this->{{ repositoryVariable }}->all();
35 | }
36 |
37 | /**
38 | * Get {{ repositoryVariable }} by id.
39 | *
40 | * @param $id
41 | * @return String
42 | */
43 | public function getById(int $id)
44 | {
45 | return $this->{{ repositoryVariable }}->getById($id);
46 | }
47 |
48 | /**
49 | * Validate {{ repositoryVariable }} data.
50 | * Store to DB if there are no errors.
51 | *
52 | * @param array $data
53 | * @return String
54 | */
55 | public function save(array $data)
56 | {
57 | return $this->{{ repositoryVariable }}->save($data);
58 | }
59 |
60 | /**
61 | * Update {{ repositoryVariable }} data
62 | * Store to DB if there are no errors.
63 | *
64 | * @param array $data
65 | * @return String
66 | */
67 | public function update(array $data, int $id)
68 | {
69 | DB::beginTransaction();
70 | try {
71 | ${{ repositoryVariable }} = $this->{{ repositoryVariable }}->update($data, $id);
72 | DB::commit();
73 | return ${{ repositoryVariable }};
74 | } catch (Exception $e) {
75 | DB::rollBack();
76 | report($e);
77 | throw new InvalidArgumentException('Unable to update post data');
78 | }
79 | }
80 |
81 | /**
82 | * Delete {{ repositoryVariable }} by id.
83 | *
84 | * @param $id
85 | * @return String
86 | */
87 | public function deleteById(int $id)
88 | {
89 | DB::beginTransaction();
90 | try {
91 | ${{ repositoryVariable }} = $this->{{ repositoryVariable }}->delete($id);
92 | DB::commit();
93 | return ${{ repositoryVariable }};
94 | } catch (Exception $e) {
95 | DB::rollBack();
96 | report($e);
97 | throw new InvalidArgumentException('Unable to delete post data');
98 | }
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/src/Stubs/spatie_data.stub:
--------------------------------------------------------------------------------
1 | paginate(10);
14 | return view('{{ viewPath }}.index', compact('{{ modelPlural }}'));
15 | }
16 |
17 | public function create(): \Illuminate\Contracts\View\View
18 | {
19 | return view('{{ viewPath }}.create');
20 | }
21 |
22 | public function store({{ request }} $request): \Illuminate\Http\RedirectResponse
23 | {
24 | {{ model }}::create($request->validated());
25 | return redirect()->route('{{ routeName }}.index')->with('success', 'Created successfully');
26 | }
27 |
28 | public function show({{ model }} ${{ modelVariable }}): \Illuminate\Contracts\View\View
29 | {
30 | return view('{{ viewPath }}.show', compact('{{ modelVariable }}'));
31 | }
32 |
33 | public function edit({{ model }} ${{ modelVariable }}): \Illuminate\Contracts\View\View
34 | {
35 | return view('{{ viewPath }}.edit', compact('{{ modelVariable }}'));
36 | }
37 |
38 | public function update({{ request }} $request, {{ model }} ${{ modelVariable }}): \Illuminate\Http\RedirectResponse
39 | {
40 | ${{ modelVariable }}->update($request->validated());
41 | return redirect()->route('{{ routeName }}.index')->with('success', 'Updated successfully');
42 | }
43 |
44 | public function destroy({{ model }} ${{ modelVariable }}): \Illuminate\Http\RedirectResponse
45 | {
46 | ${{ modelVariable }}->delete();
47 | return redirect()->route('{{ routeName }}.index')->with('success', 'Deleted successfully');
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Stubs/web_repository.controller.stub:
--------------------------------------------------------------------------------
1 | {{ serviceVariable }} = ${{ serviceVariable }};
25 | }
26 |
27 | public function index(): \Illuminate\Contracts\View\View
28 | {
29 | ${{ modelPlural }} = $this->{{ serviceVariable }}->getAll();
30 | return view('{{ viewPath }}.index', compact('{{ modelPlural }}'));
31 | }
32 |
33 | public function create(): \Illuminate\Contracts\View\View
34 | {
35 | return view('{{ viewPath }}.create');
36 | }
37 |
38 | public function store({{ request }} $request): \Illuminate\Http\RedirectResponse
39 | {
40 | $this->{{ serviceVariable }}->save($request->validated());
41 | return redirect()->route('{{ routeName }}.index')->with('success', 'Created successfully');
42 | }
43 |
44 | public function show(int $id): \Illuminate\Contracts\View\View
45 | {
46 | ${{ modelVariable }} = $this->{{ serviceVariable }}->getById($id);
47 | return view('{{ viewPath }}.show', compact('{{ modelVariable }}'));
48 | }
49 |
50 | public function edit(int $id): \Illuminate\Contracts\View\View
51 | {
52 | ${{ modelVariable }} = $this->{{ serviceVariable }}->getById($id);
53 | return view('{{ viewPath }}.edit', compact('{{ modelVariable }}'));
54 | }
55 |
56 | public function update({{ request }} $request, int $id): \Illuminate\Http\RedirectResponse
57 | {
58 | $this->{{ serviceVariable }}->update($request->validated(), $id);
59 | return redirect()->route('{{ routeName }}.index')->with('success', 'Updated successfully');
60 | }
61 |
62 | public function destroy(int $id): \Illuminate\Http\RedirectResponse
63 | {
64 | $this->{{ serviceVariable }}->deleteById($id);
65 | return redirect()->route('{{ routeName }}.index')->with('success', 'Deleted successfully');
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Stubs/web_repository_spatie_data.controller.stub:
--------------------------------------------------------------------------------
1 | {{ serviceVariable }} = ${{ serviceVariable }};
25 | }
26 |
27 | public function index(): \Illuminate\Contracts\View\View
28 | {
29 | ${{ modelPlural }} = {{ spatieData }}::collect($this->{{ serviceVariable }}->getAll());
30 | return view('{{ viewPath }}.index', compact('{{ modelPlural }}'));
31 | }
32 |
33 | public function create(): \Illuminate\Contracts\View\View
34 | {
35 | return view('{{ viewPath }}.create');
36 | }
37 |
38 | public function store({{ spatieData }} $data): \Illuminate\Http\RedirectResponse
39 | {
40 | $this->{{ serviceVariable }}->save($data->all());
41 | return redirect()->route('{{ routeName }}.index')->with('success', 'Created successfully');
42 | }
43 |
44 | public function show(int $id): \Illuminate\Contracts\View\View
45 | {
46 | ${{ modelVariable }} = $this->{{ serviceVariable }}->getById($id);
47 | return view('{{ viewPath }}.show', compact('{{ modelVariable }}'));
48 | }
49 |
50 | public function edit(int $id): \Illuminate\Contracts\View\View
51 | {
52 | ${{ modelVariable }} = $this->{{ serviceVariable }}->getById($id);
53 | return view('{{ viewPath }}.edit', compact('{{ modelVariable }}'));
54 | }
55 |
56 | public function update({{ spatieData }} $data, int $id): \Illuminate\Http\RedirectResponse
57 | {
58 | $this->{{ serviceVariable }}->update($data->all(), $id);
59 | return redirect()->route('{{ routeName }}.index')->with('success', 'Updated successfully');
60 | }
61 |
62 | public function destroy(int $id): \Illuminate\Http\RedirectResponse
63 | {
64 | $this->{{ serviceVariable }}->deleteById($id);
65 | return redirect()->route('{{ routeName }}.index')->with('success', 'Deleted successfully');
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Stubs/web_spatie_data.controller.stub:
--------------------------------------------------------------------------------
1 | paginate(10));
14 | return view('{{ viewPath }}.index', compact('{{ modelPlural }}'));
15 | }
16 |
17 | public function create(): \Illuminate\Contracts\View\View
18 | {
19 | return view('{{ viewPath }}.create');
20 | }
21 |
22 | public function store({{ spatieData }} $data): \Illuminate\Http\RedirectResponse
23 | {
24 | {{ model }}::create($data->all());
25 | return redirect()->route('{{ routeName }}.index')->with('success', 'Created successfully');
26 | }
27 |
28 | public function show({{ model }} ${{ modelVariable }}): \Illuminate\Contracts\View\View
29 | {
30 | return view('{{ viewPath }}.show', compact('{{ modelVariable }}'));
31 | }
32 |
33 | public function edit({{ model }} ${{ modelVariable }}): \Illuminate\Contracts\View\View
34 | {
35 | return view('{{ viewPath }}.edit', compact('{{ modelVariable }}'));
36 | }
37 |
38 | public function update({{ spatieData }} $data, {{ model }} ${{ modelVariable }}): \Illuminate\Http\RedirectResponse
39 | {
40 | ${{ modelVariable }}->update($data->all());
41 | return redirect()->route('{{ routeName }}.index')->with('success', 'Updated successfully');
42 | }
43 |
44 | public function destroy({{ model }} ${{ modelVariable }}): \Illuminate\Http\RedirectResponse
45 | {
46 | ${{ modelVariable }}->delete();
47 | return redirect()->route('{{ routeName }}.index')->with('success', 'Deleted successfully');
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Traits/TableColumnsTrait.php:
--------------------------------------------------------------------------------
1 | modelService->getFullModelNamespace($modelData);
17 |
18 | return $this->tableColumnsService->getAvailableColumns($table);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Transformers/EnumTransformer.php:
--------------------------------------------------------------------------------
1 | $value) {
12 | $string .= $indent.$value."\n".$indent.$key."\n";
13 | }
14 |
15 | return $string;
16 | }
17 |
18 | public static function convertNamespacesToString(array $data): string
19 | {
20 | $string = '';
21 | foreach ($data as $value) {
22 | $string .= $value."\n";
23 | }
24 |
25 | return $string;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Models/User.php:
--------------------------------------------------------------------------------
1 | extend(Tests\TestCase::class)->in(__DIR__);
15 |
16 | use Tests\TestCase;
17 |
18 | // uses(TestCase::class)->in(__DIR__);
19 | /*
20 | |--------------------------------------------------------------------------
21 | | Expectations
22 | |--------------------------------------------------------------------------
23 | |
24 | | When you're writing tests, you often need to check that values meet certain conditions. The
25 | | "expect()" function gives you access to a set of "expectations" methods that you can use
26 | | to assert different things. Of course, you may extend the Expectation API at any time.
27 | |
28 | */
29 |
30 | // expect()->extend('toBeOne', function () {
31 | // return $this->toBe(1);
32 | // });
33 |
34 | /*
35 | |--------------------------------------------------------------------------
36 | | Functions
37 | |--------------------------------------------------------------------------
38 | |
39 | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your
40 | | project that you don't want to repeat in every file. Here you can also expose helpers as
41 | | global functions to help you to reduce the number of lines of code in your test files.
42 | |
43 | */
44 |
45 | function something()
46 | {
47 | // ..
48 | }
49 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | service = new DatabaseValidatorService;
9 | });
10 |
11 | test('checkDataBaseConnection returns true when connection is successful', function () {
12 | DB::shouldReceive('connection->getPdo')->once()->andReturn(true);
13 |
14 | expect($this->service->checkDataBaseConnection())->toBeTrue();
15 | });
16 |
17 | test('checkDataBaseConnection returns false when connection fails', function () {
18 | DB::shouldReceive('connection->getPdo')->once()->andThrow(new \PDOException);
19 |
20 | expect($this->service->checkDataBaseConnection())->toBeFalse();
21 | });
22 |
23 | test('checkTableExists returns true when table exists', function () {
24 | Schema::shouldReceive('hasTable')->with('users')->once()->andReturn(true);
25 |
26 | expect($this->service->checkTableExists('users'))->toBeTrue();
27 | });
28 |
29 | test('checkTableExists returns false when table does not exist', function () {
30 | Schema::shouldReceive('hasTable')->with('non_existent_table')->once()->andReturn(false);
31 |
32 | expect($this->service->checkTableExists('non_existent_table'))->toBeFalse();
33 | });
34 |
--------------------------------------------------------------------------------
/tests/Unit/EnumTransformerTest.php:
--------------------------------------------------------------------------------
1 | toBe("{$indent}case name = 'name';"."\n");
9 | });
10 |
--------------------------------------------------------------------------------
/tests/Unit/FileServiceTest.php:
--------------------------------------------------------------------------------
1 | app->setBasePath(__DIR__.'/../');
8 | File::partialMock();
9 | });
10 |
11 | it('can create from stub file', function () {
12 | $modelData = [
13 | 'modelName' => 'User',
14 | 'folders' => null,
15 | 'namespace' => null,
16 | ];
17 | $service = Mockery::mock(FileService::class)->makePartial();
18 |
19 | $service->shouldReceive('createFromStub')
20 | ->withArgs([$modelData, 'enum', 'Enums', 'Enum'])
21 | ->andReturn('App\\Enums\\UserEnum');
22 |
23 | $results = $service->createFromStub($modelData, 'enum', 'Enums', 'Enum'); // Call on mock
24 |
25 | expect($results)->toBe('App\\Enums\\UserEnum');
26 |
27 | });
28 |
--------------------------------------------------------------------------------
/tests/Unit/HelperServiceTest.php:
--------------------------------------------------------------------------------
1 | toBe($expectedOutput);
29 | });
30 |
31 | it('format array to php syntax', function () {
32 | $expected = HelperService::formatArrayToPhpSyntax(['testing' => 'me']);
33 | $indent = 12;
34 | $indentation = str_repeat(' ', $indent);
35 | $anotherIndent = str_repeat(' ', $indent - 4);
36 | expect($expected)->toBe("[\n{$indentation}'testing' => 'me',\n{$anotherIndent}]");
37 | });
38 |
39 | it('format array to php syntax with remove value quotes', function () {
40 | $expected = HelperService::formatArrayToPhpSyntax(['testing' => 'me'], true);
41 | $indent = 12;
42 | $indentation = str_repeat(' ', $indent);
43 | $anotherIndent = str_repeat(' ', $indent - 4);
44 | expect($expected)->toBe("[\n{$indentation}'testing' => me,\n{$anotherIndent}]");
45 | });
46 |
47 | it('convert to snake case', function () {
48 | $expected = HelperService::toSnakeCase('hello world');
49 | expect($expected)->toBe('hello_world');
50 | });
51 |
--------------------------------------------------------------------------------
/tests/Unit/LaravelAutoCrudServiceProviderTest.php:
--------------------------------------------------------------------------------
1 | app->register(LaravelAutoCrudServiceProvider::class);
10 | });
11 |
12 | it('registers the GenerateAutoCrudCommand command', function () {
13 | expect(Artisan::all())->toHaveKey('auto-crud:generate');
14 | expect(Artisan::all()['auto-crud:generate'])->toBeInstanceOf(GenerateAutoCrudCommand::class);
15 | });
16 |
17 | it('publishes the config file', function () {
18 | $this->artisan('vendor:publish', ['--tag' => 'auto-crud-config']);
19 | expect(file_exists(config_path('laravel_auto_crud.php')))->toBeTrue();
20 | });
21 |
22 | it('binds TableColumnsService as a singleton', function () {
23 | // Resolve the service from the container
24 | $instance1 = app(TableColumnsService::class);
25 | $instance2 = app(TableColumnsService::class);
26 |
27 | // Ensure it's an instance of TableColumnsService
28 | expect($instance1)->toBeInstanceOf(TableColumnsService::class);
29 |
30 | // Ensure it returns the same instance (singleton behavior)
31 | expect($instance1)->toBe($instance2);
32 | });
33 |
--------------------------------------------------------------------------------
/tests/Unit/ModelServiceTest.php:
--------------------------------------------------------------------------------
1 | modelsPath = 'app/Models';
9 | });
10 |
11 | test('isModelExists returns model namespace if it exists', function () {
12 | $mockFile = Mockery::mock();
13 | $mockFile->shouldReceive('getRealPath')->andReturn('app/Models/User.php');
14 | $mockFile->shouldReceive('getFilename')->andReturn('User.php');
15 |
16 | File::shouldReceive('allFiles')->once()->andReturn([$mockFile]);
17 |
18 | File::shouldReceive('get')->with('app/Models/User.php')->andReturn('modelsPath))->toBe('App\\Models\\User');
21 | });
22 |
23 | test('isModelExists returns null if model does not exist', function () {
24 | File::shouldReceive('allFiles')->once()->andReturn([]);
25 |
26 | expect(ModelService::isModelExists('NonExistent', $this->modelsPath))->toBeNull();
27 | });
28 |
29 | test('resolveModelName correctly extracts model details', function () {
30 | $result = ModelService::resolveModelName('App\\Models\\User');
31 |
32 | expect($result)->toMatchArray([
33 | 'modelName' => 'User',
34 | 'folders' => null,
35 | 'namespace' => 'App\\Models',
36 | ]);
37 | });
38 |
39 | test('handleModelsPath ensures trailing slash', function () {
40 | expect(ModelService::handleModelsPath('app/Models'))->toBe('app/Models/');
41 | expect(ModelService::handleModelsPath('app/Models/'))->toBe('app/Models/');
42 | });
43 |
44 | it('returns table name when model has namespace and is valid', function () {
45 | // Create an anonymous class extending Model
46 | $mockModel = new class extends Model
47 | {
48 | public function getTable()
49 | {
50 | return 'mock_table';
51 | }
52 | };
53 |
54 | // Mock the class creation
55 | $modelData = [
56 | 'namespace' => 'App\\Models',
57 | 'modelName' => 'TestModel',
58 | ];
59 |
60 | // Mock the class existence
61 | class_alias(get_class($mockModel), 'App\\Models\\TestModel');
62 |
63 | $result = ModelService::getFullModelNamespace($modelData);
64 |
65 | expect($result)->toBe('mock_table');
66 | });
67 |
68 | it('returns table name when model has no namespace and is valid', function () {
69 | $mockModel = new class extends Model
70 | {
71 | public function getTable()
72 | {
73 | return 'mock_table';
74 | }
75 | };
76 |
77 | $modelData = [
78 | 'namespace' => '',
79 | 'modelName' => 'TestModelNoNamespace',
80 | ];
81 |
82 | class_alias(get_class($mockModel), 'TestModelNoNamespace');
83 |
84 | $result = ModelService::getFullModelNamespace($modelData);
85 |
86 | expect($result)->toBe('mock_table');
87 | });
88 |
--------------------------------------------------------------------------------
/tests/Unit/SpatieDataTransformerTest.php:
--------------------------------------------------------------------------------
1 | '#[Max(254)]']);
8 | expect($service)->toBe("$indent#[Max(254)]\n{$indent}name\n");
9 | });
10 |
11 | it('can convert to array', function () {
12 | $service = SpatieDataTransformer::convertNamespacesToString(['userNamespace', 'AnotherNamespace']);
13 | expect($service)->toBe("userNamespace\nAnotherNamespace\n");
14 | });
15 |
--------------------------------------------------------------------------------
/tests/Unit/TableColumnsServiceTest.php:
--------------------------------------------------------------------------------
1 | once()
11 | ->with('users')
12 | ->andReturn(['id', 'name', 'email', 'status', 'password', 'created_at', 'updated_at']);
13 | });
14 |
15 | it('returns available columns excluding primary keys and specified columns with mysql', function () {
16 | // Mock DB facade to return MySQL driver
17 | DB::shouldReceive('connection')
18 | ->andReturnSelf()
19 | ->shouldReceive('getDriverName')
20 | ->andReturn('mysql');
21 |
22 | DB::shouldReceive('select')
23 | ->andReturnUsing(function ($query, $bindings) {
24 | $column = $bindings[0];
25 |
26 | $mockColumns = [
27 | 'id' => (object) ['Key' => 'PRI', 'Null' => 'NO', 'Type' => 'int(11)'],
28 | 'name' => (object) ['Key' => '', 'Null' => 'NO', 'Type' => 'varchar(255)'],
29 | 'email' => (object) ['Key' => '', 'Null' => 'NO', 'Type' => 'varchar(255)'],
30 | 'status' => (object) ['Key' => '', 'Null' => 'YES', 'Type' => "enum('active', 'inactive', 'pending')"],
31 | 'password' => (object) ['Key' => '', 'Null' => 'NO', 'Type' => 'varchar(255)'],
32 | 'created_at' => (object) ['Key' => '', 'Null' => 'YES', 'Type' => 'timestamp'],
33 | 'updated_at' => (object) ['Key' => '', 'Null' => 'YES', 'Type' => 'timestamp'],
34 | ];
35 |
36 | return isset($mockColumns[$column]) ? [$mockColumns[$column]] : [];
37 | });
38 |
39 | Schema::shouldReceive('getColumnType')
40 | ->andReturn('varchar');
41 |
42 | // Mock the service and override getColumnDetails
43 | $service = new TableColumnsService;
44 |
45 | // Act: Call getAvailableColumns
46 | $result = $service->getAvailableColumns('users');
47 |
48 | // Assert: Should return only non-primary, non-excluded columns
49 | expect($result)->toBe([
50 | [
51 | 'is_primary_key' => false,
52 | 'is_unique' => false,
53 | 'is_nullable' => false,
54 | 'type' => 'varchar',
55 | 'max_length' => '255',
56 | 'allowed_values' => [],
57 | 'name' => 'name',
58 | 'table' => 'users',
59 | ],
60 | [
61 | 'is_primary_key' => false,
62 | 'is_unique' => false,
63 | 'is_nullable' => false,
64 | 'type' => 'varchar',
65 | 'max_length' => '255',
66 | 'allowed_values' => [],
67 | 'name' => 'email',
68 | 'table' => 'users',
69 | ],
70 | [
71 | 'is_primary_key' => false,
72 | 'is_unique' => false,
73 | 'is_nullable' => true,
74 | 'type' => 'varchar',
75 | 'max_length' => null,
76 | 'allowed_values' => [
77 | 'active',
78 | ' inactive',
79 | ' pending',
80 | ],
81 | 'name' => 'status',
82 | 'table' => 'users',
83 | ],
84 | [
85 | 'is_primary_key' => false,
86 | 'is_unique' => false,
87 | 'is_nullable' => false,
88 | 'type' => 'varchar',
89 | 'max_length' => '255',
90 | 'allowed_values' => [],
91 | 'name' => 'password',
92 | 'table' => 'users',
93 | ],
94 | ]);
95 | });
96 |
97 | it('returns empty columns excluding primary keys and specified columns with mysql', function () {
98 | // Mock DB facade to return MySQL driver
99 | DB::shouldReceive('connection')
100 | ->andReturnSelf()
101 | ->shouldReceive('getDriverName')
102 | ->andReturn('mysql');
103 |
104 | DB::shouldReceive('select')
105 | ->andReturnUsing(function ($query, $bindings) {
106 | $column = $bindings[0];
107 | $mockColumns = [];
108 |
109 | return isset($mockColumns[$column]) ? [$mockColumns[$column]] : [];
110 | });
111 |
112 | Schema::shouldReceive('getColumnType')
113 | ->andReturn('varchar');
114 |
115 | // Mock the service and override getColumnDetails
116 | $service = new TableColumnsService;
117 |
118 | // Act: Call getAvailableColumns
119 | $result = $service->getAvailableColumns('users');
120 |
121 | // Assert: Should return only non-primary, non-excluded columns
122 | expect($result)->toBe([]);
123 | });
124 |
125 | it('returns available columns excluding primary keys and specified columns with postgres', function () {
126 | // Mock DB facade to return MySQL driver
127 | DB::shouldReceive('connection')
128 | ->andReturnSelf()
129 | ->shouldReceive('getDriverName')
130 | ->andReturn('pgsql');
131 |
132 | DB::shouldReceive('select')
133 | ->andReturnUsing(function ($query, $bindings) {
134 | $column = $bindings[1]; // PostgreSQL query uses second binding for column name
135 |
136 | $mockColumns = [
137 | 'id' => (object) [
138 | 'column_name' => 'id',
139 | 'is_nullable' => 'NO',
140 | 'data_type' => 'integer',
141 | 'character_maximum_length' => null,
142 | 'udt_name' => 'int4',
143 | 'is_primary' => true,
144 | 'is_unique' => false,
145 | ],
146 | 'name' => (object) [
147 | 'column_name' => 'name',
148 | 'is_nullable' => 'NO',
149 | 'data_type' => 'character varying',
150 | 'character_maximum_length' => 255,
151 | 'udt_name' => 'varchar',
152 | 'is_primary' => false,
153 | 'is_unique' => false,
154 | ],
155 | 'email' => (object) [
156 | 'column_name' => 'email',
157 | 'is_nullable' => 'NO',
158 | 'data_type' => 'character varying',
159 | 'character_maximum_length' => 255,
160 | 'udt_name' => 'varchar',
161 | 'is_primary' => false,
162 | 'is_unique' => true,
163 | ],
164 | 'status' => (object) [
165 | 'column_name' => 'status',
166 | 'is_nullable' => 'NO',
167 | 'data_type' => 'USER-DEFINED', // Enum types in PostgreSQL are user-defined
168 | 'character_maximum_length' => null, // Enum types don't have a character length
169 | 'udt_name' => '_my_enum_type', // Replace with your actual enum type name
170 | 'is_primary' => false,
171 | 'is_unique' => false,
172 | ],
173 | 'password' => (object) [
174 | 'column_name' => 'password',
175 | 'is_nullable' => 'NO',
176 | 'data_type' => 'character varying',
177 | 'character_maximum_length' => 255,
178 | 'udt_name' => 'varchar',
179 | 'is_primary' => false,
180 | 'is_unique' => false,
181 | ],
182 | 'created_at' => (object) [
183 | 'column_name' => 'created_at',
184 | 'is_nullable' => 'YES',
185 | 'data_type' => 'timestamp without time zone',
186 | 'character_maximum_length' => null,
187 | 'udt_name' => 'timestamp',
188 | 'is_primary' => false,
189 | 'is_unique' => false,
190 | ],
191 | 'updated_at' => (object) [
192 | 'column_name' => 'updated_at',
193 | 'is_nullable' => 'YES',
194 | 'data_type' => 'timestamp without time zone',
195 | 'character_maximum_length' => null,
196 | 'udt_name' => 'timestamp',
197 | 'is_primary' => false,
198 | 'is_unique' => false,
199 | ],
200 | ];
201 |
202 | return isset($mockColumns[$column]) ? [$mockColumns[$column]] : [];
203 | });
204 |
205 | Schema::shouldReceive('getColumnType')
206 | ->andReturn('varchar');
207 |
208 | // Mock the service and override getColumnDetails
209 | $service = new TableColumnsService;
210 |
211 | // Act: Call getAvailableColumns
212 | $result = $service->getAvailableColumns('users');
213 |
214 | // Assert: Should return only non-primary, non-excluded columns
215 | expect($result)->toBe([
216 | [
217 | 'is_primary_key' => false,
218 | 'is_unique' => false,
219 | 'is_nullable' => false,
220 | 'type' => 'varchar',
221 | 'max_length' => 255,
222 | 'allowed_values' => [],
223 | 'name' => 'name',
224 | 'table' => 'users',
225 | ],
226 | [
227 | 'is_primary_key' => false,
228 | 'is_unique' => true,
229 | 'is_nullable' => false,
230 | 'type' => 'varchar',
231 | 'max_length' => 255,
232 | 'allowed_values' => [],
233 | 'name' => 'email',
234 | 'table' => 'users',
235 | ],
236 | [
237 | 'is_primary_key' => false,
238 | 'is_unique' => false,
239 | 'is_nullable' => false,
240 | 'type' => 'varchar',
241 | 'max_length' => null,
242 | 'allowed_values' => [
243 | 'my_enum_type',
244 | ],
245 | 'name' => 'status',
246 | 'table' => 'users',
247 | ],
248 | [
249 | 'is_primary_key' => false,
250 | 'is_unique' => false,
251 | 'is_nullable' => false,
252 | 'type' => 'varchar',
253 | 'max_length' => 255,
254 | 'allowed_values' => [],
255 | 'name' => 'password',
256 | 'table' => 'users',
257 | ],
258 | ]);
259 | });
260 |
261 | it('returns empty columns excluding primary keys and specified columns with postgres', function () {
262 | // Mock DB facade to return MySQL driver
263 | DB::shouldReceive('connection')
264 | ->andReturnSelf()
265 | ->shouldReceive('getDriverName')
266 | ->andReturn('pgsql');
267 |
268 | DB::shouldReceive('select')
269 | ->andReturnUsing(function ($query, $bindings) {
270 | $column = $bindings[1]; // PostgreSQL query uses second binding for column name
271 |
272 | $mockColumns = [];
273 |
274 | return isset($mockColumns[$column]) ? [$mockColumns[$column]] : [];
275 | });
276 |
277 | Schema::shouldReceive('getColumnType')
278 | ->andReturn('varchar');
279 |
280 | // Mock the service and override getColumnDetails
281 | $service = new TableColumnsService;
282 |
283 | // Act: Call getAvailableColumns
284 | $result = $service->getAvailableColumns('users');
285 |
286 | // Assert: Should return only non-primary, non-excluded columns
287 | expect($result)->toBe([]);
288 | });
289 |
290 | it('returns available columns excluding primary keys and specified columns with sqlite', function () {
291 | // Mock DB facade to return MySQL driver
292 | DB::shouldReceive('connection')
293 | ->andReturnSelf()
294 | ->shouldReceive('getDriverName')
295 | ->andReturn('sqlite');
296 |
297 | DB::shouldReceive('select')
298 | ->andReturnUsing(function ($query) {
299 | $mockColumns = [
300 | (object) ['name' => 'id', 'type' => 'INTEGER', 'notnull' => 1, 'pk' => 1],
301 | (object) ['name' => 'name', 'type' => 'TEXT', 'notnull' => 1, 'pk' => 0],
302 | (object) ['name' => 'email', 'type' => 'TEXT', 'notnull' => 1, 'pk' => 0],
303 | (object) ['name' => 'password', 'type' => 'TEXT', 'notnull' => 1, 'pk' => 0],
304 | (object) ['name' => 'created_at', 'type' => 'DATETIME', 'notnull' => 0, 'pk' => 0],
305 | (object) ['name' => 'updated_at', 'type' => 'DATETIME', 'notnull' => 0, 'pk' => 0],
306 | ];
307 |
308 | return $mockColumns;
309 | });
310 |
311 | Schema::shouldReceive('getColumnType')
312 | ->andReturn('varchar');
313 |
314 | // Mock the service and override getColumnDetails
315 | $service = new TableColumnsService;
316 |
317 | // Act: Call getAvailableColumns
318 | $result = $service->getAvailableColumns('users');
319 |
320 | // Assert: Should return only non-primary, non-excluded columns
321 | expect($result)->toBe([
322 | [
323 | 'is_primary_key' => false,
324 | 'is_unique' => false,
325 | 'is_nullable' => false,
326 | 'type' => 'varchar',
327 | 'max_length' => null,
328 | 'allowed_values' => [],
329 | 'name' => 'name',
330 | 'table' => 'users',
331 | ],
332 | [
333 | 'is_primary_key' => false,
334 | 'is_unique' => false,
335 | 'is_nullable' => false,
336 | 'type' => 'varchar',
337 | 'max_length' => null,
338 | 'allowed_values' => [],
339 | 'name' => 'email',
340 | 'table' => 'users',
341 | ],
342 | [
343 | 'is_primary_key' => false,
344 | 'is_unique' => false,
345 | 'is_nullable' => false,
346 | 'type' => 'varchar',
347 | 'max_length' => null,
348 | 'allowed_values' => [],
349 | 'name' => 'password',
350 | 'table' => 'users',
351 | ],
352 | ]);
353 | });
354 |
355 | it('returns empty columns excluding primary keys and specified columns with sqlite', function () {
356 | // Mock DB facade to return MySQL driver
357 | DB::shouldReceive('connection')
358 | ->andReturnSelf()
359 | ->shouldReceive('getDriverName')
360 | ->andReturn('sqlite');
361 |
362 | DB::shouldReceive('select')
363 | ->andReturnUsing(function ($query) {
364 | $mockColumns = [];
365 |
366 | return $mockColumns;
367 | });
368 |
369 | Schema::shouldReceive('getColumnType')
370 | ->andReturn('varchar');
371 |
372 | // Mock the service and override getColumnDetails
373 | $service = new TableColumnsService;
374 |
375 | // Act: Call getAvailableColumns
376 | $result = $service->getAvailableColumns('users');
377 |
378 | // Assert: Should return only non-primary, non-excluded columns
379 | expect($result)->toBe([]);
380 | });
381 |
382 | it('returns available columns excluding primary keys and specified columns with sql server', function () {
383 | // Mock DB facade to return MySQL driver
384 | DB::shouldReceive('connection')
385 | ->andReturnSelf()
386 | ->shouldReceive('getDriverName')
387 | ->andReturn('sqlsrv');
388 |
389 | DB::shouldReceive('select')
390 | ->andReturnUsing(function ($query, $bindings) {
391 | $column = $bindings[2]; // The column name is typically the third binding
392 |
393 | $mockColumns = [
394 | 'id' => (object) [
395 | 'COLUMN_NAME' => 'id',
396 | 'DATA_TYPE' => 'int',
397 | 'IS_NULLABLE' => 'NO',
398 | 'is_identity' => 1, // Primary key (Identity column)
399 | ],
400 | 'name' => (object) [
401 | 'COLUMN_NAME' => 'name',
402 | 'DATA_TYPE' => 'varchar',
403 | 'IS_NULLABLE' => 'NO',
404 | 'is_identity' => 0,
405 | ],
406 | 'email' => (object) [
407 | 'COLUMN_NAME' => 'email',
408 | 'DATA_TYPE' => 'varchar',
409 | 'IS_NULLABLE' => 'NO',
410 | 'is_identity' => 0,
411 | ],
412 | 'password' => (object) [
413 | 'COLUMN_NAME' => 'password',
414 | 'DATA_TYPE' => 'varchar',
415 | 'IS_NULLABLE' => 'NO',
416 | 'is_identity' => 0,
417 | ],
418 | 'created_at' => (object) [
419 | 'COLUMN_NAME' => 'created_at',
420 | 'DATA_TYPE' => 'datetime',
421 | 'IS_NULLABLE' => 'YES',
422 | 'is_identity' => 0,
423 | ],
424 | 'updated_at' => (object) [
425 | 'COLUMN_NAME' => 'updated_at',
426 | 'DATA_TYPE' => 'datetime',
427 | 'IS_NULLABLE' => 'YES',
428 | 'is_identity' => 0,
429 | ],
430 | ];
431 |
432 | return isset($mockColumns[$column]) ? [$mockColumns[$column]] : [];
433 | });
434 |
435 | Schema::shouldReceive('getColumnType')
436 | ->andReturn('varchar');
437 |
438 | // Mock the service and override getColumnDetails
439 | $service = new TableColumnsService;
440 |
441 | // Act: Call getAvailableColumns
442 | $result = $service->getAvailableColumns('users');
443 |
444 | // Assert: Should return only non-primary, non-excluded columns
445 | expect($result)->toBe([
446 | [
447 | 'is_primary_key' => 0,
448 | 'is_unique' => false,
449 | 'is_nullable' => false,
450 | 'type' => 'varchar',
451 | 'max_length' => null,
452 | 'allowed_values' => [],
453 | 'name' => 'name',
454 | 'table' => 'users',
455 | ],
456 | [
457 | 'is_primary_key' => 0,
458 | 'is_unique' => false,
459 | 'is_nullable' => false,
460 | 'type' => 'varchar',
461 | 'max_length' => null,
462 | 'allowed_values' => [],
463 | 'name' => 'email',
464 | 'table' => 'users',
465 | ],
466 | [
467 | 'is_primary_key' => 0,
468 | 'is_unique' => false,
469 | 'is_nullable' => false,
470 | 'type' => 'varchar',
471 | 'max_length' => null,
472 | 'allowed_values' => [],
473 | 'name' => 'password',
474 | 'table' => 'users',
475 | ],
476 | ]);
477 | });
478 |
479 | it('returns empty columns excluding primary keys and specified columns with sql server', function () {
480 | // Mock DB facade to return MySQL driver
481 | DB::shouldReceive('connection')
482 | ->andReturnSelf()
483 | ->shouldReceive('getDriverName')
484 | ->andReturn('sqlsrv');
485 |
486 | DB::shouldReceive('select')
487 | ->andReturnUsing(function ($query, $bindings) {
488 | $column = $bindings[2]; // The column name is typically the third binding
489 |
490 | $mockColumns = [];
491 |
492 | return isset($mockColumns[$column]) ? [$mockColumns[$column]] : [];
493 | });
494 |
495 | Schema::shouldReceive('getColumnType')
496 | ->andReturn('varchar');
497 |
498 | // Mock the service and override getColumnDetails
499 | $service = new TableColumnsService;
500 |
501 | // Act: Call getAvailableColumns
502 | $result = $service->getAvailableColumns('users');
503 |
504 | // Assert: Should return only non-primary, non-excluded columns
505 | expect($result)->toBe([]);
506 | });
507 |
--------------------------------------------------------------------------------