CrestApps / laravel-code-generator

An efficient Laravel code generator, saving time by automating the creation of resources such as views, controllers, routes, migrations, languages, and form-requests. Highly flexible and customizable, it includes a cross-browser compatible template and client-side validation for application modernization.
https://laravel-code-generator.crestapps.com
MIT License
740 stars 158 forks source link

Suggestion: allow all singular #26

Closed eduardoarandah closed 7 years ago

eduardoarandah commented 7 years ago

In latin languages (spanish portuguese italian french romanian) compound words go inverse than english. Example:

in english you say: "User types"

in spanish it is "Tipos de Usuario" maybe valid "Tipos de Usuarios" maybe valid "Tipo de Usuarios"

this becomes a mess!

Imagine the possible foreign keys

User->tipos_de_usuario_id
User->tipos_de_usuarios_id
User->tipo_de_usuarios_id
User->tipo_de_usuarios_id

Lots of programmers I know, including me, prefer to use all singular, for models, controllers, tables, views, routes, etc

It would be amazing to have that option!

MikeAlhayek commented 7 years ago

I am sorry I am not following you here with your foreign key naming/comment.

Are you requesting an option that would allow you to have singular naming convention for the controller, table and views' folder? Although this would not be a problem to add it to the config, I like to understand how does it help you with the language variation you stated above? If it is just a preference I completely understand.

eduardoarandah commented 7 years ago

Sorry I wasn't clear on the suggestion. A custom pluralize argument would be of great help.

In my case I don't use plurals (because of the irregularities in spanish)

I would love to use something like:

create:resources User --plural=user

"plural" argument overriding str_plural

MikeAlhayek commented 7 years ago

You can still provide any name convention you like to override the default behavior. please visit the documentation for more info about the available options https://crestapps.com/laravel-code-generator/docs/2.1#how-to-create-resources Keep in mind that the default behavior is intended to save you time and give you very short commands (for English users that is :) )

Try calling the following command to override the naming

php artisan create:resources User --controller-name=UserController --table-name=user  --views-directory=user --language-file-name=user

However, I think it is a good idea to allow the user to pass --with-singular-naming to the create:resources command to auto convert everything for you without having to explicitly name everything yourself like the the command above suggests. Also I think it is a good idea to add a configuration option to allow you to set this as global setting for all your commands to use singular naming convention so you don't have to pass --with-singular-naming for each command.

Let me know if the above command, gives you the desired result. Meanwhile, I'll add the above features to the new release when time permits.

Again, thanks a lot for your feedback and your contribution. Suggestions are a great way to improve this package.

eduardoarandah commented 7 years ago

Thanks for that!

I'm using your package right now for a real app that has to be completed in record time.

My setup:

Laravel Backpack for the backend because it just works, just configure some crudControllers and the backend is ready in a couple of hours

Crestapps for the front-end Template used: Default-Collective because gives me less code less code=less problems

What I found in the way:

I used https://github.com/InfyOmLabs/laravel-generator but produced too much code

I used https://github.com/appzcoder/crud-generator and is great and simple, but had to copy the command line every time

I loved CrestApps/laravel-code-generator because it saves the model in json and I don't have to use the brain to remember!! great! It also validates inside the controller, so I don't have to generate custom requests for validation (like infyom does)

The hardest part in CrestApps/laravel-code-generator is namespacing and creating sub-folders that match those namespaces. I've solved it by using a bash template

Example: If I'm working in a user profile everything must be prefixed with /home

Bash file is this: This is it:


# vars
prefix=home
entity=profile
layout=layouts.home

# Plurals and Uppercase
fieldsfile=${entity}s.json
model="$(tr '[:lower:]' '[:upper:]' <<< ${entity:0:1})${entity:1}"
namespace="$(tr '[:lower:]' '[:upper:]' <<< ${prefix:0:1})${prefix:1}"

# fields file from BD
pa create:fields-file $entity --table-name=$entity

# Controller
pa create:controller $model --fields-file=$fieldsfile --controller-directory=$namespace --routes-prefix=$prefix --views-directory=$entity

# Views
pa create:views $model --fields-file=$fieldsfile --routes-prefix=$prefix --layout-name=$layout --views-directory=$entity

# Regenerar Form
php artisan create:form-view $model

# Routes
php artisan create:routes $model --routes-prefix=$prefix
MikeAlhayek commented 7 years ago

@eduardoarandah Thank again for your feedback :)

First of all, I added an option to allow you to create singular named resources from the config. Also, I added an option to allow you to turn off the code-first approach so no migrations are generated by default.

Try using the last commit in branch v2.2dev https://github.com/CrestApps/laravel-code-generator/commits/v2.2dev and let me know how it goes :)

Can you please explain the pain point you found in the way with this package to help me improve it?

Cruddiness must be the LAW (read this https://github.com/adamwathan/laracon2017)

What do you wish we do better? Hint: you may want to try generating the resources with a form-request that will lean up your controllers (i.e. php artisan create:resources Home --with-form-request)

prefixes and namespaces are VERY important to keep organized

What do you wish we do better here? We already keep everything grouped up in folder to keep your code clean and easy to maintain. Please clearly share your idea to help me understand... A comparison would be great so I can understand your request clearly.

I never auto-generate model/migration because it's just easier by hand

Take advantage of the new use_code_first_approach option in the config file. Just set it to false and you won't have to worry about auto-migrations being created.

I always use generator for controller/views

Please explain what is the issue... And what do you like to be different?

The most used: regenerate only the form fields and validations

Please explain what is the issue.... And what do you like to be different?

I don't use plurals because I don't like to use my brain

Like mentioned above, download the latest commit in v2.2dev and take advantage of the option that allows you to convert the plural resource naming to singular :)

/*
|--------------------------------------------------------------------------
| Plural vs singular naming conventions.
|--------------------------------------------------------------------------
*/
'plural_names_for' => [
     'controller-name' => false,
     'request-form-name' => false,
     'language-file-name' => true,
     'resource-file-name' => true,
     'table-name' => true,
],

Lastly, why would you need to create a bash file instead of just using the create:resources command? All the create:resources does is create all the commands at once.

Thank you

eduardoarandah commented 7 years ago

why would you need to create a bash file instead of just using the create:resources command? All the create:resources does is create all the commands at once

What I tried to explain is that your tool is great at the scaffolding phase and beyond

Let's say you have to create a new screen to modify only two fields

well, just create:resources again (in a sub-folder) and delete all fields except those two

MikeAlhayek commented 7 years ago

I am sorry I am still confused. You should use create:resources Command always (when creating and again when modifying) as long as your change is only made to the resources file, the command should recreate everything on its own without the need of any bash file.

MikeAlhayek commented 7 years ago

@eduardoarandah did you get a chance to try out the v2.2ddev? Did that help you over coming all the challenges you were facing? If needed, here is the documentation for the new version which should be coming up soon. http://crestapps.dev/laravel-code-generator/docs/2.2

Please let me know

eduardoarandah commented 7 years ago

Not yet! Too busy right now

By the way, the new laravel 5.5 has some new features that may simplify validation in the controller. Have you seen it ?

Also improves the way the Request grabs null values and non present values

eduardoarandah commented 7 years ago

Hey @CrestApps !

I tried it and works perfect

changed this config and generated everything in under a minute, good job!

 /*
    |--------------------------------------------------------------------------
    | Plural vs singular naming conventions.
    |--------------------------------------------------------------------------
     */
    'plural_names_for' => [
        'controller-name' => false,
        'request-form-name' => false,
        'language-file-name' => false,
        'resource-file-name' => false,
        'table-name' => false,
    ],
MikeAlhayek commented 7 years ago

That's great! Thank you for confirming. Additionally, I changed the default on the controller and the form request name to a singular. Also, not sure what feature are you referring too in 5.5 that would make validation easier.

eduardoarandah commented 7 years ago

in 5.5 release notes, says request now has validate method

https://laravel.com/docs/5.5/releases search for text "Request Validation"

But now that I think about it, it wouldn't be a good idea, as it would make it incompatible with previous versions.

MikeAlhayek commented 7 years ago

I see what your referring too now. As you stated, that will cause issues with versions >= 5.5. But, I will look into making it happen.

However, with the latest commit, you can define custom rules and use them here is more info if you're interested https://crestapps.com/laravel-code-generator/docs/2.2#field-validation

eduardoarandah commented 7 years ago

Great!

A couple of things:

In controllers you have a function "data" that converts empty strings to null.

That might be not necessary thanks to this middleware

ConvertEmptyStringsToNull

https://github.com/laravel/laravel/blob/master/app/Http/Kernel.php

And other thing:

What if a malicious user sends a field that must not be present?

You expect to receive in the request name, last name

But receive name, lastname, money, is_admin

Does the controller filter expected fields? Haven't made tests but maybe we need to add something like

$request->only(name,lastname)

MikeAlhayek commented 7 years ago

Yes I am aware of ConvertEmptyStringsToNull. I believe that is part of the 5.4 release and won't work for older versions.

Regarding the security concern you brought up, I believe that the fillable fields will protect you as the non-fillable fields will be ignored. I guess, it may not be a bad idea to change $request->all() to $request->only(['field1','field2']).

eduardoarandah commented 7 years ago

Would be great!

Because $fillable is a all-or-none solution.

In practice, you want some fields available in certain screens protected by some permission/role

MikeAlhayek commented 7 years ago

@eduardoarandah I know your going to LOVE this one! For anyone that is using Laravel 5.5+, the generated will have simplified code generated! The code generator will take advantage of the new changes "Request Validation" changes that were introduced with Laravel 5.5. Meanwhile, anyone that is using Laravel 5.1 - 5.4 will still have the same code that we previously generated so it is still a backward compatible.

Event more, anyone that has Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class middlewear registered in Laravel 5.4+ will not see the following code generated any more since the middlewear will do just that :)

$data['...'] = !empty($request->input('...')) ? $request->input('...') : null;

In other words, the Code-Generator will make smart decisions based on the current Laravel version and the loaded resources to determine the best code to generate!

Finally, when generating instead of using $request->all(), we now generate $request->only([]) as you suggested above :)

If you want to test drive the new changes, make sure you publish the vendor resources as some template and config changes took place.

If your already using dev-v2.2dev version, you'll have to follow these steps to get the latest code

1) Open composer.json file and update crestapps/laravel-code-generator version to 2.1.* 2) Execute the following two commands from the command line composer clearcache then composer update 3) Now, you are back to version 2.1.5. Now, to get the latest code from the package, Open composer.json file one more time and update “crestapps/laravel-code-generator version to dev-v2.2dev 4) From the command-line, execute the following command composer update which should give you the latest code. 5) publish the latest changed using this command php artisan vendor:publish --provider="CrestApps\CodeGenerator\CodeGeneratorServiceProvider" --tag=default

Note, if you currently using 2.1.* version, you can just ignore steps 1 and 2. Just follow steps 3 and 4.

Let me know your thoughts on this :)

Here is how the controller looks (Generated Using Laravel 5.4 where ConvertEmptyStringsToNull middlewear is NOT registered)

<?php

namespace App\Http\Controllers;

use App\Models\Test;
use Illuminate\Http\Request;
use App\Models\Manufacturer;
use App\Http\Controllers\Controller;

class TestsController extends Controller
{

    /**
     * Display a listing of the tests.
     *
     * @return Illuminate\View\View
     */
    public function index()
    {
        $tests = Test::with('manufacturer')->paginate(25);

        return view('tests.index', compact('tests'));
    }

    /**
     * Show the form for creating a new test.
     *
     * @return Illuminate\View\View
     */
    public function create()
    {
        $manufacturers = Manufacturer::pluck('name','id')->all();

        return view('tests.create', compact('manufacturers'));
    }

    /**
     * Store a new test in the storage.
     *
     * @param Illuminate\Http\Request$request
     *
     * @return Illuminate\Http\RedirectResponse | Illuminate\Routing\Redirector
     */
    public function store(Request $request)
    {
        try {
            $this->affirm($request);
            $data = $this->getData($request);

            Test::create($data);

            return redirect()->route('tests.test.index')
                             ->with('success_message', trans('tests.model_was_added'));

        } catch (Exception $exception) {

            return back()->withInput()
                         ->withErrors(['unexpected_error' => trans('tests.unexpected_error')]);
        }
    }

    /**
     * Display the specified test.
     *
     * @param int $id
     *
     * @return Illuminate\View\View
     */
    public function show($id)
    {
        $test = Test::with('manufacturer')->findOrFail($id);

        return view('tests.show', compact('test'));
    }

    /**
     * Show the form for editing the specified test.
     *
     * @param int $id
     *
     * @return Illuminate\View\View
     */
    public function edit($id)
    {
        $test = Test::findOrFail($id);
        $manufacturers = Manufacturer::pluck('name','id')->all();

        return view('tests.edit', compact('test','manufacturers'));
    }

    /**
     * Update the specified test in the storage.
     *
     * @param  int $id
     * @param Illuminate\Http\Request$request
     *
     * @return Illuminate\Http\RedirectResponse | Illuminate\Routing\Redirector
     */
    public function update($id, Request $request)
    {
        try {
            $this->affirm($request);
            $data = $this->getData($request);

            $test = Test::findOrFail($id);
            $test->update($data);

            return redirect()->route('tests.test.index')
                             ->with('success_message', trans('tests.model_was_updated'));

        } catch (Exception $exception) {

            return back()->withInput()
                         ->withErrors(['unexpected_error' => trans('tests.unexpected_error')]);
        }        
    }

    /**
     * Remove the specified test from the storage.
     *
     * @param  int $id
     *
     * @return Illuminate\Http\RedirectResponse | Illuminate\Routing\Redirector
     */
    public function destroy($id)
    {
        try {
            $test = Test::findOrFail($id);
            $test->delete();

            return redirect()->route('tests.test.index')
                             ->with('success_message', trans('tests.model_was_deleted'));

        } catch (Exception $exception) {

            return back()->withInput()
                         ->withErrors(['unexpected_error' => trans('tests.unexpected_error')]);
        }
    }

    /**
     * Validate the given request with the defined rules.
     *
     * @param  Illuminate\Http\Request  $request
     *
     * @return boolean
     */
    protected function affirm(Request $request)
    {
        return $this->validate($request, [
            'name' => 'nullable|string|min:0|max:255',
            'third' => 'nullable|string|min:0|max:255',
            'type' => 'nullable',
            'manufacturer_id' => 'nullable',

        ]);
    }

    /**
     * Get the request's data from the request.
     *
     * @param Illuminate\Http\Request\Request $request 
     * @return array
     */
    protected function getData(Request $request)
    {
        $data = $request->only(['name','third','type','manufacturer_id']);

        $data['created_at'] = !empty($request->input('created_at')) ? $request->input('created_at') : null;
        $data['updated_at'] = !empty($request->input('updated_at')) ? $request->input('updated_at') : null;
        $data['name'] = !empty($request->input('name')) ? $request->input('name') : null;
        $data['third'] = !empty($request->input('third')) ? $request->input('third') : null;
        $data['type'] = !empty($request->input('type')) ? $request->input('type') : null;
        $data['manufacturer_id'] = !empty($request->input('manufacturer_id')) ? $request->input('manufacturer_id') : null;
        return $data;
    }

}

Here is how the controller looks (Generated Using Laravel 5.5 where ConvertEmptyStringsToNull middlewear is registered)

<?php

namespace App\Http\Controllers;

use App\Models\Test;
use Illuminate\Http\Request;
use App\Models\Manufacturer;
use App\Http\Controllers\Controller;

class TestsController extends Controller
{

    /**
     * Display a listing of the tests.
     *
     * @return Illuminate\View\View
     */
    public function index()
    {
        $tests = Test::with('manufacturer')->paginate(25);

        return view('test.index', compact('tests'));
    }

    /**
     * Show the form for creating a new test.
     *
     * @return Illuminate\View\View
     */
    public function create()
    {
        $manufacturers = Manufacturer::pluck('name','id')->all();

        return view('test.create', compact('manufacturers'));
    }

    /**
     * Store a new test in the storage.
     *
     * @param Illuminate\Http\Request$request
     *
     * @return Illuminate\Http\RedirectResponse | Illuminate\Routing\Redirector
     */
    public function store(Request $request)
    {
        try {
            $data = $this->getData($request);

            Test::create($data);

            return redirect()->route('test.test.index')
                             ->with('success_message', trans('tests.model_was_added'));

        } catch (Exception $exception) {

            return back()->withInput()
                         ->withErrors(['unexpected_error' => trans('tests.unexpected_error')]);
        }
    }

    /**
     * Display the specified test.
     *
     * @param int $id
     *
     * @return Illuminate\View\View
     */
    public function show($id)
    {
        $test = Test::with('manufacturer')->findOrFail($id);

        return view('test.show', compact('test'));
    }

    /**
     * Show the form for editing the specified test.
     *
     * @param int $id
     *
     * @return Illuminate\View\View
     */
    public function edit($id)
    {
        $test = Test::findOrFail($id);
        $manufacturers = Manufacturer::pluck('name','id')->all();

        return view('test.edit', compact('test','manufacturers'));
    }

    /**
     * Update the specified test in the storage.
     *
     * @param  int $id
     * @param Illuminate\Http\Request$request
     *
     * @return Illuminate\Http\RedirectResponse | Illuminate\Routing\Redirector
     */
    public function update($id, Request $request)
    {
        try {
            $data = $this->getData($request);

            $test = Test::findOrFail($id);
            $test->update($data);

            return redirect()->route('test.test.index')
                             ->with('success_message', trans('tests.model_was_updated'));

        } catch (Exception $exception) {

            return back()->withInput()
                         ->withErrors(['unexpected_error' => trans('tests.unexpected_error')]);
        }        
    }

    /**
     * Remove the specified test from the storage.
     *
     * @param  int $id
     *
     * @return Illuminate\Http\RedirectResponse | Illuminate\Routing\Redirector
     */
    public function destroy($id)
    {
        try {
            $test = Test::findOrFail($id);
            $test->delete();

            return redirect()->route('test.test.index')
                             ->with('success_message', trans('tests.model_was_deleted'));

        } catch (Exception $exception) {

            return back()->withInput()
                         ->withErrors(['unexpected_error' => trans('tests.unexpected_error')]);
        }
    }

    /**
     * Get the request's data from the request.
     *
     * @param Illuminate\Http\Request\Request $request 
     * @return array
     */
    protected function getData(Request $request)
    {
        $data = $request->validate([
            'name' => 'nullable|string|min:0|max:255',
            'third' => 'nullable|string|min:0|max:255',
            'type' => 'nullable',
            'manufacturer_id' => 'nullable',
        ]);

        return $data;
    }

}
eduardoarandah commented 7 years ago

That is great!!

I've used all available generators and yours is clearly the best/easiest!

I'll surely test it man

MikeAlhayek commented 7 years ago

Best/easiest differently won't settle for less! Hopefully with evenyones input, it will be the most popular code generator package out there to benefit all.

Please let me know your input once you test it

eduardoarandah commented 7 years ago

That's the spirit!

In that case I'll give you my feedback in case it helps you.

I've used a lot of platforms/tools Asp, asp.net C#, desktop apps in C#, joomla, drupal, wordpress themes, wordpress plugins, Yii version 1, yii 2, vue and now laravel

The greatest strength in laravel is that it forces you to use advanced tools (composer, git, npm, bash) and advanced programming patterns (single reponsability principle, separation of concerns)

BUT This strength is also its weakness.

You end up with too much code. Too many places for bugs.

Want to change "name" field in your "user" model for a "last name" "first name" ?

you probably have to dig into 60+ files

Want to edit an entity?

you need: route, migration, model, repository, cache logic, a permission, controller, request, views: index, show, edit, delete, form, table

This is where laravel is the weakest. Without a generator, this becomes a pain. too much code.

When coding in Yii2, I used Gii, a generator included in the framework look: http://www.yiiframework.com/doc-2.0/guide-start-gii.html

You may get some amazing ideas from Yii generated code, like

What else may be useful?

Let the community create generators for you.

In my case, I've made templates to use crestapps with AdminLte in laravel-backpack, and will be glad to share.

I'll also would love to create generators for things like: dropzone field select2 vuejs table datatable file upload

I don't know what's the best tool to share knowledge, I've seen this one but never used it https://readme.readme.io

Hope this helps you in some way,

brofist, from Mexico

MikeAlhayek commented 7 years ago

I hope you seeing that Laravel-Code-Generator, is strengthening that weakness :) With Laravel-Code-Generator, everything gets store in the resource-file (JSON based) file, so if you want to change the field last_name to full_name you can easily do that in the JSON file without touching any code! When you run php artisan create:resources command, the field will change automatically throughout your code.

I am hoping the community can help by creating different templates for Laravel-code-generator. Unfortunately, I don't have the time to support multiple templates. you can have a datatable based template, a template to submit requests using AJAX....

Lets continue the chat offline. Please find my email in the composer.json file of the packages and send me an email.

Thank you