My Projects
My current project
Since 2015, I have been intensively dealing with Laravel and other modern web technologies. (see also my learning ). As a result, I have started to transfer many of my previous ideas and concepts from previous projects to Laravel.
My goal the design of a universal backend (admin area) that is suitable for any project in the frontend. (E-commerce, e-learning, etc.) Such backends are also called ‘boilerplate’. I have a clear picture of what such a boilerplate has to implement and have already completed a large part. I continue to work on this project with lots of fun and energy every day.
The boilerplate contains a multitude of user interfaces that are ajax-based linked to the database. More below …
The current version of my boilerplate can be found in my git repository . Because of the missing database, the version is not executable, but meaningful in terms of my code. The live version can be found here. I am happy to go through the highlights in the application together with you, preferably by remote connection (TeamViewer).
For more detailed information, see my blog posts . There I also added some videos and code snippets to demonstrate the different user interfaces. (Coming soon)
Previous projects
2011-2015: Development of a shop system with the possibility to customize the frontend. Here are two examples:
dos Santos, a shop selling licenses for Kaspersky and G-Data branded virus scanners.
gay mega store – a shop for the sale of videos, books and other articles of the gay scene.
Both shops, though worlds apart, use the same backend. Special features of the backend:
- Fully multilanguage, all texts can be translated automatically.
- Import of any WOW sliders for the header.
- Wizards for designing the blocks and boxes (widgets) on the home page.
- Bonus card and alternatively promo codes. Up to 10 promo codes at the same time, limited to product groups or manufacturer brands. In addition, individual credits to customers redeemed with a code (such as promo codes).
- Video manager to manage and display any video, including video gallery. (YouTube, Vimeo or local videos on own server)
- Various wizards for configuration and design.
- Page editor for any individual pages. Multilingual.
- Saferpay SIXT and PayPal as payment interfaces .
- Cool search function on the start page.
- Product tags for new, re-deliverable, hot etc.
- Extensive backend with many more features.
- Edit mode for the frontend. Only visible to privileged administrators.
- And much more. Not the lastest tech but full of good ideas.
Further projects: CNC Dynamix Musikhaus Grimm
My idea of a universal backend – my boilerplate
A boilerplate is a reusable framework that is already equipped with some functionality. In this sense, the pure Laravel framework is already a boilerplate.
My idea of a universal boilerplate or backend, however, goes far beyond that. Whether e-commerce or other, all applications are divided into frontend and backend. Experience shows that in the backend again and again the same basic requirements are to be met.
Here I’ll demonstrate some solutions I’ve already done in my version of a universal Laravel boilerplate. Many more are in preparation. Everything is ‘work in progress’ and is constantly being optimized.
CRUD – create read update delete
A CRUD-generator will unquestionably save the developer many hours. The approach with php artisan make: migration I rarely use.
In practice, there are already tables of the database filled with data. Any necessary changes to the structure will be done first with phpMySQL. Then I start a single command like:
php artisan infyom : scaffold Languages - fromTable - tableName = languages - save - paginate = 20
For the Languages table, it creates a model, controller, repository, and routes, and in the views / backend / languages folder it creates the following blade files: create, edit, fields, index, show, show_fields, and table . Even API-Controllers can be created.
This is based on infyom’s CRUD genrator, whose stubs I have extended with many features and adapted to Bootstrap4. My additional features to this CRUD-Genrator (from top to bottom):
- Header of the page. Explains purpose of the page. (editable / translatable)
- ‘create new’, ‘edit’ and ‘view’ open in a popup.
- Help (explanation below) (editable / translatable).
- Configuration Wizard for this page or table in the Help. (explanation below) (editable / translatable).
- Dev Notes . Notes as pop-up for ToDos and changes. Usefull and visible only for the developer.
- Search for. Search for a search term in all columns. It marks finds with yellow background.
- Export tables. In the format .xls, .xlsx, .csv, PDF
- Filter the table. Creates a dummy function in the controller. Allows manual creation of any individual filter.
- Hide columns. Allows manual hiding / showing of any columns of the table.
- Records per page dynamically creates the options to select. (based on total records)
- Sort Order UI . Here with immediate reload of page on change. This UI will be created for each field in a table named sort_order. (Initially commented out in the source code!)
- Switches . Here without page reload but with immediate storage including confirmation and caching.
- Action Buttons: View and Edit as popups. Delete with Sweet-Alert2 security prompt .
- Show debug area for the Dev. Usefull and visible only to the developer.
- The display of all these elements is optional . See Configuration Wizard for this page / table.
When changing the structure in the Languages table , I either manually adjust the model or delete and recreate the whole scaffolding. The deletion happens easily with:
php artisan infyom : rollback Languages scaffold
User Interfaces – UIs
Here’s a simple example: Switches (logical on-off switch) as shown above in the Languages table .
The switch is eg implemented for the above mentioned Languages table (in table.blade.php inside the @foreach ($dataset as $dataset) :
{!! get_checkbox_any_table ( $ this_table_name , 'status' , $ dataset -> id , $ id_field = 'id' , $ with_comment = false , $ hint_key = $ this_table_name . '_status_checkbox' , $ label_text = '' , $ with_panel = false , $ ax_response = true , $ input_style = '' , $ label_style = 'font-weight: normal' , $ with_tooltip = false , $ tt_class = '' , $ tt_width = '' , $ with_page_reload = false , $ this_value = $ dataset -> status , $ from_inside_loop = true , $ as_switch = true , // else as checkbox $ switch_size = 'no' // xs, sm, no, lg ); !!}
or can be implemented elsewhere for a key / value pair in the table diverses like so:
echo get_checkbox_any_table ( $ table = 'diverses' , $ field = ' div_res' , $ id = 'is_dev' , $ id_field = 'div_what' , $ with_comment = false , $ tt_hint_key = 'is_dev' , $ label_text = 'use any indiv Text here or leave blank ... ' , $ with_panel = false , $ ax_response = true , $ input_style = ' ' , $ label_style = 'margin-right: 12px; font-weight: normal;' , $ with_tooltip = true , $ tt_class = 'tip' , // tip or tip_lu for appearance right or left from icon $ tt_width = '400px' , // size of popup $ with_page_reload = true , $ this_value = '' , // !!! only if $ from_inside_loop = true fill with {$ model-> fieldname} $ from_inside_loop = false , // auto lookup for current value if set to false $ as_switch = true , // display as checkbox or switch? $ switch_size = 'no' // xs, sm, no, lg );
The switch is ajax-based and generates a confirmation or a page reload, depending on the parameters. It can be extended by a tooltip . The tooltip contains help text about the current switch in all enabled languages. The text display is based on the current session language. Tooltips also provide a link to edit all texts including all languages, but visible only for privileged users (DEV, Admin and maybe a Translator).
A tooltip on mouseover displays text in the current session-language. Here shown with Edit-Link that opens the general editor and translator in a popup:
Tooltips can also be inserted independently at any position to provide informations, exactly where the user needs the help. It takes only a uniqe key in diverses to create a tooltip.
echo function tooltip ( $ t_key = 'table_display_cols_hint' , $ class = 'tip' , $ style = '' , $ icon = '' // force an icon other than default icon )
Each table has an (optional) Help Button . The Help pops up in the page top like so:
Please note that all help texts are provided with an Edit-Link. Clicking on ‘Edit’ opens the general editor / translator for long texts (over 255 characters) or for short texts (up to 255 characters):
This means that the help-text incl. all translations for this table are edited directly from here. Pictures and links can be inserted. With one click, every text can be automatically translated into the other languages. If necessary, can be modified manually. Several different attempts of automatic translation with different source text are possible. (German to French, or English to French, French to Russian, English to all other languages except German etc.)
The fourth tab on the far right, titled ‘Configuration’, opens the configuration wizard for this table:
The left box contains configuration switches for the current table. The middle box is currently not used and will probably disappear again. The right box is visible only to the DEV (developer) and / or privileged admins.
Please note that all these features were created automatically with only one single command:
php artisan infyom : scaffold Languages - fromTable - tableName = languages - save - paginate = 20
So I easily can create any other tables, such as the following:
100% multilingualism
In order to achieve a 100% multi-linguality for backend and frontend, the lang-folders offered by Laravel are not really useful. I’m currently exploring a Laravel package that exports the contents of all lang-folders to a Google spreadsheet. The languages for export are selectable. In the spreadsheet a translator could manually edit the translations. A view in this spreadsheet shows that the translations are very incomplete.
After manual editing, the spreadsheet can be imported back, updating the arrays in the lang-folder accordingly. Open Spreadsheet …
I have extended the package so that when exporting and importing the spreadsheet the current translations are also written to a table named language_lines. Screenshot of this table:
The blue buttons on the left open the general editor and translator for short texts as popup like so:
The general editors / translators (for short and long texts) only show the languages that are currently activated in the Languages table. You may activate as many languages as you like. The order of the languages results from the sort_order in the Languages table, which is also arbitrary.
Of course, the general editors / translators themselves, with all text elements, are multilingual and editable!
The WYSIWYG editor for long text is ckEditor . This tool comes multilingual anyway. When loading, the session language is automatically injected to the ckEditor.
Please also note that the ckEditor is always available to the user in two versions (‘Basic’ and ‘with all functions’ – switchable directly in the general editor / translator for long texts).
I have extended the ckEditor with the Laravel File Manager . So images and documents can be inserted directly into texts from the server without having to upload them every time.
Key / Value Management
I use a key / values table with built-in help texts. The table is called diverses. (Will soon be renamed and restructured as part of a refactoring.)
These key / values form the basis for almost all user interfaces . Here’s an example of a simple logical switch for displaying the left sidebar in the backend with key dashboard_settings_sidebar_minimized :
Here in full display ( parameter $with_panel = true ):
The same in french:
If the automatic translation is not correct, you can edit the text manually at any time by clicking on ‘Modifier‘! Even the word modifier itself can be changed in the table diverses. The display of the key on the bottom right is only visible to the developer or privileged user.
Here comes the reduced display form ( parameter $with_panel = false ):
The help text will be displayed on mouseover on the blue tooltip.
Here is an even smaller presentation, can be integrated anywhere, here without tooltip:
All representations are displayed with the same function, with only the parameters varying:
$ what = 'dashboard_settings_sidebar_minimized' ; echo get_checkbox_div ( $ what , // the key in table 'diverses' in field' div_what ' $ label_text = ' ' , // label is only shown if $with_panel == false $ label_style = ' font-weight: bold; margin- right: 6px; ' , $ with_panel = true , // large dispaly - in a box with help text $ data_on = ' On ' , $ data_off = ' Off ' , $ wrapper_style = ' padding: 2px 9px 0 9px; margin: 0 0 4px 0 ; $ ax_response = false , // if true: displays a tick for confirmation $ ax_response_with_page_reload = false , // if true: page will be reloaded on change $ with_tooltip = true , // defaults to false if $ with_panel == true $ tt_class = 'tip' , // tip (apperance right of icon) or tip_lu (left) $ tt_width = '450px' // width of tooltip popup )
All 3 representations use the same key / value pair:
( key = dashboard_settings_sidebar_minimized – value = 1 or 0 )
All texts short or long including all translations are available in the same table-record. There are 34 languages in Languages , the table diverses has correspondingly many columns. The number of languages can even be expanded as required.
The Google Translation API supports a lot more than 34 languages . Note that for some languages, Google’s ‘Neural translation Technology’ is already used, based on AI and delivering significantly better results. Tip: Always translate first to English and then from English to the other (still empty) languages. My translation UI automatically does this (optional) when the switch ‘translate_to_english_first’ is activated.
This simple conversion of key / values into ‘multilingual’ wizards allows the combination of configuration wizards to configuration pages like this:
Of course, key / values are not limited to logical switches. Other UIs on the same principle:
Selects:
echo get_select_by_t_key ( $ t_key = 'categories_images_width' , $ t_key_arr = '' , $ pref = '' , $ suff = 'px' , $ arr_from = '10' , $ arr_to = '800' , $ style = 'font-size : 1.2em; ' , $ arr_step = ' 1 ' , $ with_tooltip = false , $ tt_class = ' tip ' , $ tt_width = '300px' );
or this Select:
echo get_select_by_t_key ( $ t_key = 'cache_minutes_selection' , $ t_key_arr = 'no caching, short caching, normal caching, extreme caching' , $ pref = '' , $ suff = '' , $ arr_from = null , $ arr_to = null , $ style = 'font-size: 1.2em ; , $ arr_step = null , $ with_tooltip = false , $ tt_class = ' tip ' , $ tt_width = '300px' );
Color Picker:
or in a compact form:
get_colorpicker_by_t_key ( $ t_key , $ wrapper_style = 'margin: 0 0 4px 0;' , $ with_panel = false , // makes it compact $ with_tooltip = true , // becomes autom. false if $with_panel = true $ tt_class = 'tip' , / / tip or tip_lu $ tt_width = '450px' )
The colorpicker is based on http://bgrins.github.io/spectrum/
Real 100% multilingualism
As described above, I have already put the Laravel translations from the lang folders into the table language_lines, and the key-based translations are the table diverses. The next logical step is to put both into a single table . This allows to use the UIs for editing and automatic translation as well as to add any new languages.
Saying this I also do not want to sacrify the possibility to export an import into a spreadsheet, so that a professional human translator can get involved.
In table language_lines the key is the column full_key in table diverses the key is div_what. Both have a uniqe index. The consolidation (coming soon) will be an improvement.
But there are still the text-parts that I insert in the normal, daily development. For example in links, selects, headers etc. I usually just insert the text in german first, and later I’ll take care of the translation.
Laravel offers this function to translate texts from the lang folders:
echo __('messages.welcome');
Since I want to forego the content of the lang folder in the future and instead retrieve translations directly from the table in the database here is my function when inserting a new text.Suppose the following case:
$r = '<a title="hier klicken für weitere Aktionen im Popup" ';
… I just manually alter to:
$r = '<a title="' . get_tr("hier klicken für weitere Aktionen im Popup") . '"';
The function get_tr()
- automatically creates the key from the text ( $content ) with str_slug ()
- checks if the key already exists in the table with create_dv ()
- translates the text to English and from English into all other languages with translate_to_all_other_langs ()
- reads the translation from the table based on the session_lang_code () with get_dv () and returns it.
function get_tr($content, $from_lang_code = null, $overwrite = false, $re_translate = false) { $content = trim($content); if (empty($from_lang_code)) $from_lang_code = session_lang_code(); $key = substr($content, 0, 120); $key = trim($key); $key = str_slug($key, '.'); //create key if not exists - with content into session_lang_code() create_dv($key, $content, true, $field = 'div_res_' . session_lang_code()); //if exists ?? if ($overwrite) { set_dv($key, $content, 'div_res_' . session_lang_code()); //gets cached } if ($re_translate) { translate_to_all_other_langs($key, $content, session_lang_code(), $method = 'all'); } else { translate_to_all_other_langs($key, $content, session_lang_code(), $method = 'all_empty'); } $translation = get_dv($key, 'div_res_' . $from_lang_code); if (get_dv('first_letter_in_translation_always_display_uppercase')) { return ucfirst($translation); } return $translation; }
All database accesses are being cached ! So really fast. More in Performance / Caching …
The table diverses will then look like this (see top record):
The integrated search function helps to search for texts and translations. The ability to show or hide any columns in the table makes handling easier. So do filters like, ‘only show languages that are enabled in languages‘.
The two buttons ‘kurz‘ and ‘lang‘ (short and long) again open the respective general editor and translator as popup as described above. Thus, all translations are manually editable from here too.
Additional features in this table are:
- Find records where any translation into one of the active languages is missing.
- Translate missing translations automatic in the background. (Optional)
- Select a language-column and run a find/replace on the complete column. For example replace all ‘enregistrement‘ with ‘record‘ in column french.
- Display all translations with first letter uppercase (ucfirst())
Performance / Caching
The above mentioned functions would be quite database-intensive, so I made the following for caching. The usual caching methods are based on caching entire records of the table. Here my approach is to cache every single field in the record of a table .
I use Redis on my local web server and file on my hosting (HostEurope VPS) because Redis is not supported here. Thanks Laravel, the configuration can be easily changed.
The central function is this:
function cache_it($c_key, $value, $minutes = CACHE_MINUTES) { //$c_key = $table.'.'.$field.'.'.$id_field.'.'.$id; Cache::forget($c_key); Cache::put($c_key, $value, $minutes); return true; }
A database query looks like this:
function get_dv($what, $field = 'div_res') { $what = trim($what); //caching in lookup() !! $res = lookup('diverses', $field, $what, $id_field ='div_what' ); return $res; }
Whereby all select-queries always use the function lookup():
function lookup($table, $field, $id, $id_field ='id' ) { if(is_null($id) or $id=='') return ''; $c_key = $table.'.'.$field.'.'.$id_field.'.'.$id; $res = Cache::remember($c_key, CACHE_MINUTES, function () use ($table, $field, $id, $id_field) { return DB::select("select $field from $table where $id_field = ?", [$id]); }); if(is_string($res)) return $res; if(! is_null($res) and count($res)>0 ) { $arr = (array)$res; return $arr[0]->$field; }else{ return ''; } }
Each key in the cache follows the pattern:
$c_key = $table.'.'.$field.'.'.$id_field.'.'.$id;
The $id_field is almost always id, except for the diverses table. Using $id_field makes this suitable for every table in the database.
When writing to the database, it looks like this:
function set_dv($what, $value, $field = 'div_res') { $what = trim($what); App\Models\Diverses::where('div_what', '=', $what)->update([$field => $value], ['updated_at' => NOW()]); $c_key = 'diverses'.'.'.$field.'.'.'div_what'.'.'.$what; cache_it($c_key,$value); //forgets old value and caches new value }
It’s easy to customize for any other tables.
Checking if a key already exists in the table:
function create_dv($what, $value = '',$first=false, $field = 'div_res') { $what = trim($what); $c_key = 'diverses'.'.'.$field.'.'.'div_what'.'.'.$what.'.count'; $count = 0; $count = Cache::remember($c_key, CACHE_MINUTES, function () use ($what,$field) { return App\Models\Diverses::where(['div_what' => $what])->count(); }); if ($count == 0) { App\Models\Diverses::create(['div_what' => $what, $field => $value]); $c_key = 'diverses'.'.'.$field.'.'.'div_what'.'.'.$what.'.count'; cache_it($c_key,1); //here always value = 1 because it was just created //if value=='' or $first indicates the very first creation of key - don't cache it yet if($value<>'' and ! $first) { $c_key = 'diverses' . '.' . $field . '.' . 'div_what' . '.' . $what; cache_it($c_key, $value); } } }
And finally, deleting an entire record in a table (should be added to an event-observer in the model):
function forget_all_cached_fields_for($table, $id) { //before actually delete a record in table: //if we delete a record in a table we want also all cached items for this record being removed if ($table == 'diverses' and is_numeric($id)) { //in table diverses we use div_what rather than id for the unique identifyer $id_field = 'div_what'; $div_what = get_div_what_by_id($id); $id = $div_what; } else { $id_field = 'id'; } // get all fields from table structure $arr = get_columns_from_table_as_array($table); $c_key = ''; foreach ($arr as $field) { $c_key = $table . '.' . $field . '.' . $id_field . '.' . $id; Cache::forget($c_key); $c_key = $c_key . '.count'; // important for create_dv()/check if exists - count must be null again Cache::forget($c_key); } return true; }
-more detailed examples follow in my blog posts
Continue to: Goals