15. ASP.NET Core 5 – CRUD Using Blazor And Entity Framework Core

Ankit Sharma, 2020.05.28 Updated to .NET Core 3.2 Preview-1
Tutorial: https://www.c-sharpcorner.com/article/asp-net-core-crud-using-blazor-and-entity-framework-core/

Github: https://github.com/AnkitSharma-007/ASPCore.BlazorCrud

Deploy on IIS : https://www.c-sharpcorner.com/article/deploying-a-blazor-application-on-iis/

 

Employee CRUD module (app.)

(Record Management System)

Open MS SQL Server Management studio (tool simmilar to Oracle SQLDeveloper or PHPMyadmin) and create tblEmployee table (catalog) in eg WizLib DB. Connect params are :
Server type:     DB engine
Server name :  localhost\SQLEXPRESS
Auth : Win auth
User name :  SSPC2/ss

Create table tblEmployee( 
  EmployeeId int IDENTITY(1,1) NOT NULL, 
  Name varchar(20) NOT NULL, 
  City varchar(20) NOT NULL, 
  Department varchar(20) NOT NULL, 
  Gender varchar(6) NOT NULL 
)

STEP 1. same as in Calculator example (L:\3_sw_video\net_core\BlazorDemoCalc_AnkitSharma202005.html).

Be aware: Sharma’s tutorials are older than his Github code (.sln) which is in this article !

3 project files (in corresponding 3 dirs) created inside solution dir BlazorCrudEMP : see J:\netcore\source\repos\BlazorCrudEMP\BlazorCrud.sln

  1. BlazorCrud.Client – It has the client-side code and contains the pages that will be rendered on the browser.
  2. BlazorCrud.Server – It has the server-side codes such as DB related operations and web API.
  3. BlazorCrud.Shared – It contains the shared code that can be accessed by both client and server.

Execute the program – click F5 or on IIS while on client proj. – displays navig.menu links Home and Fetch Employees.
No more M, V and C dirs !

Original Counter and Fetch Data pages created automaticaly we deleted.

 

STEP 2. Adding Model to App

  1. Right-click on BlazorCrud.Shared project and then select Add >> New Folder and name the folder as Models. We will be adding our model class in this folder only.
  2. Right-click on Models folder and select Add >> Class. Name your class Employee. This class will contain our Employee model properties.
  3. Open Employee.cs
    J:\netcore\source\repos\BlazorCrudEMP\BlazorCrud\Shared\Models\Employee.cs and put the following code in it :
// J:\netcore\source\repos\BlazorCrudEMP\BlazorCrud\Shared\Models\Employee.cs
using System.ComponentModel.DataAnnotations;

namespace BlazorCrud.Shared.Models
{
    public class Employee
    {
        public int EmployeeId { get; set; }
        [Required]
        public string Name { get; set; }
        [Required]
        public string Gender { get; set; }
        [Required]
        public string Department { get; set; }
        [Required]
        public string City { get; set; }
    }
}

 

STEP 3. Creating DAL for App

DAL is Data Access Layer, here connect string and adapter CRUD functions for calling CRUD fns of lower level.

Right click on Models folder in BlazorCrud.Shared project  and select Add >> Class. Name your class EmployeeContext. This is our Entity Framework DB context class to interact with database. Open EmployeeContext.cs and put the following code into it.

using BlazorCrud.Shared.Models;
// J:\netcore\source\repos\BlazorCrudEMP\BlazorCrud\Shared\Models\EmployeeContext.cs
using Microsoft.EntityFrameworkCore;

namespace BlazorCrud.Server.DataAccess
{
    public class EmployeeContext : DbContext
    {
        public virtual DbSet<Employee> tblEmployee { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                //"ora7": "User Id=hr;Password=hr; Data Source=sspc2:1521/XE;"
                //optionsBuilder.UseSqlServer(@"Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=myTestDB;Data Source=ANKIT-LENOVO\SQLEXPRESS;");
                optionsBuilder.UseSqlServer(@"Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=WizLib;Data Source=localhost\SQLEXPRESS;");
            }
        }
    }
}

Do not forget to put your own connection string.

Right click on BlazorCrud.Server project and then select Add >> New Folder and name the folder DataAccess. We will be adding our classes to handle database related operations inside this folder only. Add class to DataAccess folder and name it as EmployeeDataAccessLayer.

// J:\netcore\source\repos\BlazorCrudEMP\BlazorCrud\Server\DataAccess\EmployeeDataAccessLayer.cs
// This class will handle our CRUD related DB operations
using BlazorCrud.Server.Interfaces;
using BlazorCrud.Shared.Models;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace BlazorCrud.Server.DataAccess
{
    public class EmployeeDataAccessLayer: IEmployee
    {
        EmployeeContext db = new EmployeeContext();

        //To Get all employees details   
        public IEnumerable<Employee> GetAllEmployees()
        {
            try
            {
                return db.tblEmployee.ToList();
            }
            catch
            {
                throw;
            }
        }

        //To Add new employee record     
        public void AddEmployee(Employee employee)
        {
            try
            {
                db.tblEmployee.Add(employee);
                db.SaveChanges();
            }
            catch
            {
                throw;
            }
        }

        //To Update the records of a particluar employee    
        public void UpdateEmployee(Employee employee)
        {
            try
            {
                db.Entry(employee).State = EntityState.Modified;
                db.SaveChanges();
            }
            catch
            {
                throw;
            }
        }

        //Get the details of a particular employee    
        public Employee GetEmployeeData(int id)
        {
            try
            {
                Employee employee = db.tblEmployee.Find(id);
                return employee;
            }
            catch
            {
                throw;
            }
        }

        //To Delete the record of a particular employee    
        public void DeleteEmployee(int id)
        {
            try
            {
                Employee emp = db.tblEmployee.Find(id);
                db.tblEmployee.Remove(emp);
                db.SaveChanges();
            }
            catch
            {
                throw;
            }
        }
    }
}

And hence our data access layer is complete.

 

 

Now, we will proceed to create our web API Controller.

STEP 4. Adding web API Controller to App

Right click on BlazorCrud.Server/Controllers folder and select Add >> New Item. An “Add New Item” dialog box will open. Select ASP.NET from the left panel, then select “API Controller Class” from templates panel and put the name as EmployeeController. Press OK.

EmployeeController class calls methods of EmployeeDataAccessLayer class and pass on data to client side.

// J:\netcore\source\repos\BlazorCrudEMP\BlazorCrud\Server\Controllers\EmployeeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BlazorCrud.Server.Interfaces;
using BlazorCrud.Shared.Models;
using Microsoft.AspNetCore.Mvc;

// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860

namespace BlazorCrud.Server.Controllers
{
    [Route("api/[controller]")]
    public class EmployeeController : Controller
    {
        private readonly IEmployee objemployee;

        public EmployeeController(IEmployee _objemployee)
        {
            objemployee = _objemployee;
        }

        [HttpGet]
        [Route("Index")]
        public IEnumerable<Employee> Index()
        {
            return objemployee.GetAllEmployees();
        }

        [HttpPost]
        [Route("Create")]
        public void Create([FromBody] Employee employee)
        {
            if (ModelState.IsValid)
                objemployee.AddEmployee(employee);
        }

        [HttpGet]
        [Route("Details/{id}")]
        public Employee Details(int id)
        {

            return objemployee.GetEmployeeData(id);
        }

        [HttpPut]
        [Route("Edit")]
        public void Edit([FromBody]Employee employee)
        {
            if (ModelState.IsValid)
                objemployee.UpdateEmployee(employee);
        }

        [HttpDelete]
        [Route("Delete/{id}")]
        public void Delete(int id)
        {
            objemployee.DeleteEmployee(id);
        }
    }
}

We are done with our backend logic. So, we will now proceed to code our client side.

 

STEP 5. Adding Razor Views to App

Right click on BlazorCrud.Client/Pages folder and then select Add >> New Item. An “Add New Item” dialog box will open, select Web from the left panel, then select “Razor View” from templates panel and name it FetchEmployee.cshtml.

This will add a FetchEmployee.cshtml page to our BlazorCrud.Client/Pages folder. Similarly add 3 more pages AddEmployee.cshtml, EditEmployee.cshtml and DeleteEmployee.cshtml.

Let’s add codes to these pages

5.1 V FetchEmployee.razor (was .cshtml)

This page will be displaying all the employee records present in the database. Additionally, we will also provide action methods Edit and Delete on each record. Open FetchEmployee.cshtml and put the following code in it.

@* J:\netcore\source\repos\BlazorCrudEMP\BlazorCrud\Client\Pages\FetchEmployee.razor *@
@page "/fetchemployee"
@using BlazorCrud.Shared.Models
@inject HttpClient Http


<h1>Employee Data</h1>
<p>This component demonstrates fetching Employee data from the server.</p>

<p>
    <a href="/addemployee">Create New</a>
</p>

@if (empList == null)
{
<p><em>Loading...</em></p> }
            else
            {
<table class='table'>
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Gender</th>
            <th>Department</th>
            <th>City</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var emp in empList)
        {
<tr>
    <td>@emp.EmployeeId</td>
    <td>@emp.Name</td>
    <td>@emp.Gender</td>
    <td>@emp.Department</td>
    <td>@emp.City</td>
    <td>
        <a href='/editemployee/@emp.EmployeeId'>Edit</a>  |
        <a href='/delete/@emp.EmployeeId'>Delete</a>
    </td>
</tr>}
    </tbody>
</table>}


@code { Employee[] empList;

  protected override async Task OnInitializedAsync()
  {
     empList = await Http.GetJsonAsync<Employee[]>("/api/Employee/Index");
  }
}

Let’s understand this code

On the top, we have included Blazor.Shared.Models namespace so that we can use our Employee model class in this page.

We are defining the route of this page using @page directive. So, in this application, if we append “/fetchemployee” to base URL then we will be redirected to this page.

We are also injecting HttpClient service to enable web API call.

Then we have defined the HTML part to display all the employees record in a tabular manner. We have also added two action links for Edit and Delete which will navigate to EditEmployee.cshtml and DeleteEmployee.cshtml pages.

At the bottom of the page, we have a @code (eas functions) section which contains our business logic. We have created an array variable empList of type Employee and populating it inside OnInitAsync method by calling our web API. This will bind to our HTML table on the page load.

 

5.2 V AddEmployee.razor

This page is used to create a new employee record.

@* J:\netcore\source\repos\BlazorCrudEMP\BlazorCrud\Client\Pages\AddEmployee.razor *@
@page "/addemployee"
@using BlazorCrud.Shared.Models
@inject HttpClient Http
@inject NavigationManager urlNavigationManager

<h1>Create Employee</h1>
<hr />

<EditForm Model="@emp" OnValidSubmit="CreateEmployee">
    <DataAnnotationsValidator />
    <div class="form-group row">
        <label class="control-label col-md-12">Name</label>
        <div class="col-md-4">
            <input class="form-control" @bind="emp.Name" />
        </div>
        <ValidationMessage For="@(() => emp.Name)" />
    </div>
    <div class="form-group row">
        <label class="control-label col-md-12">Gender</label>
        <div class="col-md-4">
            <select class="form-control" @bind="emp.Gender">
                <option value="">-- Select Gender --</option>
                <option value="Male">Male</option>
                <option value="Female">Female</option>
            </select>
        </div>
        <ValidationMessage For="@(() => emp.Gender)" />
    </div>
    <div class="form-group row">
        <label class="control-label col-md-12">Department</label>
        <div class="col-md-4">
            <input class="form-control" @bind="emp.Department" />
        </div>
        <ValidationMessage For="@(() => emp.Department)" />
    </div>
    <div class="form-group row">
        <label class="control-label col-md-12">City</label>
        <div class="col-md-4">
            <input class="form-control" @bind="emp.City" />
        </div>
        <ValidationMessage For="@(() => emp.City)" />
    </div>
    <div class="form-group">
        <button type="submit" class="btn btn-primary">Save</button>
        <button class="btn btn-light" @onclick="Cancel">Cancel</button>
    </div>
</EditForm>

@code { Employee emp = new Employee();

   protected async Task CreateEmployee()
   {
      await Http.SendJsonAsync(HttpMethod.Post, "/api/Employee/Create", emp);
      urlNavigationManager.NavigateTo("/fetchemployee");
   }

   void Cancel()
   {
      urlNavigationManager.NavigateTo("/fetchemployee");
   }
 }

In this page the route is “/addemployee”.

We are also injecting urlNavigationManager (was “Microsoft.AspNetCore.Blazor.Services.IUriHelper”) service to enable URL redirection. The HTML part will generate a form to get inputs from the user. The attribute “bind” is used to bind the value entered in the textbox to the properties of Employee object.

In the @code (was functions) section we have defined two methods :

  1.  CreateEmployee will be invoked on clicking “Submit” button and send a POST request to our API along with the Employee object emp.
  2. Cancel method will be invoked on clicking cancel button and redirect the user back to FetchEmployee page.

5.3 V EditEmployee.razor

@* J:\netcore\source\repos\BlazorCrudEMP\BlazorCrud\Client\Pages\EditEmployee.razor *@
@page "/editemployee/{empID:int}"
@using BlazorCrud.Shared.Models
@inject HttpClient Http
@inject NavigationManager urlNavigationManager

<h2>Edit Employee</h2>
<hr />

<EditForm Model="@emp" OnValidSubmit="UpdateEmployee">
    <DataAnnotationsValidator />
    <div class="form-group row">
        <label class="control-label col-md-12">Name</label>
        <div class="col-md-4">
            <input class="form-control" @bind="emp.Name" />
        </div>
        <ValidationMessage For="@(() => emp.Name)" />
    </div>
    <div class="form-group row">
        <label class="control-label col-md-12">Gender</label>
        <div class="col-md-4">
            <select asp-for="Gender" class="form-control" @bind="emp.Gender">
                <option value="">-- Select Gender --</option>
                <option value="Male">Male</option>
                <option value="Female">Female</option>
            </select>
        </div>
        <span><ValidationMessage For="@(() => emp.Gender)" /></span>
    </div>
    <div class="form-group row">
        <label class="control-label col-md-12">Department</label>
        <div class="col-md-4">
            <input class="form-control" @bind="emp.Department" />
        </div>
        <span><ValidationMessage For="@(() => emp.Department)" /></span>
    </div>
    <div class="form-group row">
        <label class="control-label col-md-12">City</label>
        <div class="col-md-4">
            <input class="form-control" @bind="emp.City" />
        </div>
        <span><ValidationMessage For="@(() => emp.City)" /></span>
    </div>
    <div class="form-group">
        <button type="submit" class="btn btn-primary">Save</button>
        <button class="btn btn-light" @onclick="Cancel">Cancel</button>
    </div>
</EditForm>

@code { [Parameter]
    public int empID { get; set; }

    Employee emp = new Employee();

    protected override async Task OnInitializedAsync()
    {
        emp = await Http.GetJsonAsync<Employee>("/api/Employee/Details/" + empID);
    }

    protected async Task UpdateEmployee()
    {
        await Http.SendJsonAsync(HttpMethod.Put, "api/Employee/Edit", emp);
        urlNavigationManager.NavigateTo("/fetchemployee");
    }

    void Cancel()
    {
        urlNavigationManager.NavigateTo("/fetchemployee");
    }
}

In this page we have defined the route as “/editemployee/{empID}”. empID is an URL parameter of type string declared in @code section. We will use the [Parameter] attribute to mark the variable as a parameter. To navigate to this page, we need to pass the employee id in the URL which will be captured in empID variable. If we do not mark the variable with the [Parameter] attribute, we will get an error “Object of type ‘BlazorCrud.Client.Pages.EditEmployee’ has a property matching the name ’empID’, but it does not have [ParameterAttribute] applied.”. This will not allow empID to bind to the employee id value passed in the parameter.

The HTML part is similar to that of AddEmployee.cshtml page. The attribute “bind” is used for two-way binding; i.e., binding edited textbox values to employee object properties and vice versa.

Inside the @code section we are fetching the employee records in OnInitAsync method based on the employeeID passed in the parameter. This will bind to the fields in the form on page load itself.

UpdateEmployee method will send a PUT request to our API along with the Employee object emp. The Cancel method will be invoked on clicking cancel button and redirect the user back to FetchEmployee page.

5.4 V DeleteEmployee.razor

@* J:\netcore\source\repos\BlazorCrudEMP\BlazorCrud\Client\Pages\DeleteEmployee.razor  *@
@page "/delete/{empID:int}"
@using BlazorCrud.Shared.Models
@inject HttpClient Http
@inject NavigationManager urlNavigationManager

<h2>Delete Employee</h2>
<h3>Are you sure you want to delete employee with id : @empID</h3>
<br />

<div class="col-md-4">
    <table class="table">
        <tr>
            <td>Name</td>
            <td>@emp.Name</td>
        </tr>
        <tr>
            <td>Gender</td>
            <td>@emp.Gender</td>
        </tr>
        <tr>
            <td>Department</td>
            <td>@emp.Department</td>
        </tr>
        <tr>
            <td>City</td>
            <td>@emp.City</td>
        </tr>
    </table>
    <div class="form-group">
        <button class="btn btn-danger" @onclick="(async () => await Delete())">Delete</button>
        <button class="btn btn-light" @onclick="Cancel">Cancel</button>
    </div>
</div>

@code { [Parameter]
    public int empID { get; set; }

    Employee emp = new Employee();

    protected override async Task OnInitializedAsync()
    {
        emp = await Http.GetJsonAsync<Employee>("/api/Employee/Details/" + empID);
    }

    protected async Task Delete()
    {
        await Http.DeleteAsync("api/Employee/Delete/" + Convert.ToInt32(empID));
        urlNavigationManager.NavigateTo("/fetchemployee");
    }

    void Cancel()
    {
        urlNavigationManager.NavigateTo("/fetchemployee");
    }
}

The route for this page is also parametrized since we are fetching the record of the employee on page load.

The HTML part will display the employee data and ask the user for a confirmation to delete the employee record.

Inside the @code section we are fetching the employee records in OnInitAsync method based on the employeeID passed in the parameter. This will display the employee records as the page loads.

The Delete method will be invoked on clicking “Delete” button, which will send a delete request to our API along with the employee ID of the employee to be deleted. On successful deletion the user will be navigated back to FetchEmployee page.

Navigation menu for our module (app.)

Open BlazorCrud.Client/Shared/ NavMenu.razor file and put the following code in it.

@* J:\netcore\source\repos\BlazorCrudEMP\BlazorCrud\Client\Shared\NavMenu.razor *@
<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">BlazorCrud</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">

        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>

        <li class="nav-item px-3">
            <NavLink class="nav-link" href="fetchemployee">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch employee
            </NavLink>
        </li>

    </ul>
</div>

@code { private bool collapseNavMenu = true;

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

 

URL R : https://localhost:5001/fetchemployee – URL has “/fetchemployee” appended to it as we have defined it using @page directive.

URL C : https://localhost:5001/addemployee

URL U and D : https://localhost:5001/editemployee/2 – simmilar to releteemployee/2

 

 

WPF :

https://www.youtube.com/watch?v=LUKp76CNmJY         https://goo.gl/Axoeja

https://www.youtube.com/watch?v=NiGsgctfiYM           https://goo.gl/fAUCRh

 

13. PHP 7.4.2, Bootstrap 4.3.1 : Laravel 6.12 CRUD tutorial

2020.02.17 Tested on newest Windows 10 64 bit, XAMPP – works !

http://localhost:8083/laravel6/public/shows/create
http://localhost:8083/laravel6/public/shows/ – here are del and edit buttons
http://localhost:8083/laravel6/public/shows/1/edit

https://appdividend.com/2019/09/12/laravel-6-crud-example-laravel-6-tutorial-for-beginners/ 2019.12.10 by Krunal

https://github.com/KrunalLathiya/Laravel6CRUDExample

http://dev1:8083/fwphp/glomodul/blog/?i/read_post/id/43#crequest is URL to show this article in my Blog (Msg module).

 

1. MySQL DB   2. M   3. C requeest   4. Bootstrap 4

5. CreRequest   6. validateSaveResponse

7. RvDisplayresponse   8. CUD   9. D

Install Laravel 6.12

1. MySQL DB   2. M   3. C requeest   4. Bootstrap 4

5. CreRequest   6. validateSaveResponse

7. RvDisplayresponse   8. CUD   9. D

Install Laravel 6.12

For some reason WordPress editor changes pasted text to uppercase !! eg :
composer create-project –prefer-dist laravel/laravel laravel6

~ 130 MB – one of reasons why I do not like such developing SW (included is Oracle Forms). If error – how debug 130 MB ?
B12phpfw has 20-30 kB code to debug (few hundert lines) using own debugging and Xdebug which both work excellent.
If there were somebody who guaranties support (for many years) then huge developing SW would be better. Stories about security… are not true – we only need good help about implement security and some features (which is difficult to find).

cd J:\xampp\htdocs\laravel6\
install the frontend dependencies :

npm install

Step 1. Configure MySQL DB

Values inside .env file are loaded inside the files from the config directory.

J:\xampp\htdocs\laravel6\.env file

Where J:\xampp\htdocs\laravel6\ is root of our laravel project.

If you make changes to .env file then don’t forget to restart server ( if you are using laravel dev server). If you are using virtual host and changes don’t seem to take effect then run :

cd J:\xampp\htdocs\laravel6\

php artisan config:clear

(This command will clear the configuration cache) in your terminal.

Laravel always ships with the migration files, so you can generate DB tables so :

php artisan migrate

migrations table created successfully. Laravel comes with three migrations which are used for authentication. Their file names are prepended with a timestamp :

Migrating: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_000000_create_users_table (0.48 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated: 2014_10_12_100000_create_password_resets_table (0.44 seconds)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated: 2019_08_19_000000_create_failed_jobs_table (0.27 seconds)

select * from information_schema.tables where table_schema = ‘z_laravel6’ and table_name = ‘migrations’ and table_type = ‘BASE TABLE’

Step 2. M – create model and migration files

Database migration is used to save details about DB table, so you don’t have to manually generate all of the tables by going to the database interface or phpmyadmin or …
We can develop migrations using artisan with ‘make: migration’ command.
Type the following command to create a model and migration files.

cd J:\xampp\htdocs\laravel6\

php artisan make:model Show -m

It will create migration files inside database/migrations directory J:\xampp\htdocs\laravel6\database\migrations\ :

  1. J:\xampp\htdocs\laravel6\database\migrations\Show.php file – model name has to be singular
  2. [timestamp]_create_shows_table.php migration file – migration name should be plural to automatically find table name.

//J:\xampp\htdocs\laravel6\database\migrations\2020_02_17_074520_create_shows_table.php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateShowsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     *
     * Create table in DB using the following command.
     *    php artisan migrate
     * execute up() function ee run all migrations and create defined tbls (incremental changes).
     *
     * To reverse the migrations :
     *    php artisan migrate:rollback which will execute the down() function.
     *
     */
    public function up()
    {
        Schema::create('shows', function (Blueprint $table) {
           $table->bigIncrements('id');        //**php artisan make:model Show -m created**
              $table->string('show_name');   //**I added**
              $table->string('genre');
              $table->float('imdb_rating');
              $table->string('lead_actor');
           $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('shows');
    }
}

If you want to create the migration but have a different table name in mind, then you can explicitly define a table name with ‘create‘ flag.

  1. up() function is used for creating/updating tables, columns, and indexes Inside up() function, We have the Schema: create(‘table_name,’ callback) method which will be used for creating the new table.
    1. Inside a callback, we have $table->bigIncrements(‘id’) which will create auto_increment integer column with the primary key and argument ‘id’ is the name of a column.
    2. Second is $table->timestamps() which will create the two timestamp columns created_at and updated_at.
  2. down() function is used for reversing an operation done by up() method
       $table->bigIncrements('id');        //**php artisan make:model Show -m created**       
          $table->string('show_name');   //**I added 4 columns**        
          $table->string('genre');      
          $table->float('imdb_rating');          
          $table->string('lead_actor');            
       $table->timestamps();  //**php artisan make:model Show -m created**          

Create table in DB using the following command :

php artisan migrate

which will execute up() function ee run all migrations and create defined tbls (incremental changes).

To reverse the migrations :

php artisan migrate:rollback

which will execute down() function.

Now, add the fillable property inside file :

J:\xampp\htdocs\laravel6\app\Show.php

// J:\xampp\htdocs\laravel6\app\Show.php
namespace App;
use Illuminate\Database\Eloquent\Model;

/*
* We can specify all the properties to modify the behavior of the model.
* We can write a $table property which is used to determine a name of the table that this model will interact with in the future operations.
* By default, It will define a table name as the plural of the model name, e.g., shows table for Show model and users table for User model.
* When you don't need to use timestamps on your table, then you will also have to specify the $timestamps property and set it to false value in your Model because Laravel expects your table to have the created_at and updated_at timestamp columns.
*/
class Show extends Model
{
   protected $fillable = [
      'show_name', 'genre', 'imdb_rating', 'lead_actor'
   ];
}

Step 3. C – Create routes and controller

First, create the ShowController using the following command.

php artisan make:controller ShowController –resource

Note that we have also added the — resource flag which will define six methods inside the ShowController namely:

  1. Index (used for displaying a list of Shows)
  2. Create (will show the view with a form for creating a Show)
  3. Store (used for creating a Show inside the database. Note: create method submits to store method)
  4. Show (will display a specified Show)
  5. Edit (will show the form for editing a Show. Form will be filled with the existing Show data)
  6. Update (Used for updating a Show inside the database. Note: edit submits to update method)
  7. Destroy (used for deleting a specified Show)

J:\xampp\htdocs\laravel6\routes\web.php

add Route::resource… line of code :

// ShowController.php
Route::get('/', function () {
    return view('welcome');
});

Route::resource('shows', 'ShowController');

We can pass dynamic parameters with {} brackets, and you might have noticed that show, update, and destroy has the same url but different methods, so its legit.

Just like resource flag, laravel has a method called resource() that will generate all the above routes. You can also use that method instead of specifying them individually like above.

Actually, by adding Route::resource… line, we have registered multiple routes for our application. We can check it so :

php artisan route:list

Domain Method URI Name Action Middleware
GET/HEAD / Closure web
GET/HEAD api/user Closure api,auth:api
GET/HEAD shows shows.index App\Http\Controllers\ShowController@index web
POST shows shows.store App\Http\Controllers\ShowController@store web
GET/HEAD shows/create shows.create App\Http\Controllers\ShowController@create web
GET/HEAD shows/{show} shows.show App\Http\Controllers\ShowController@show web
PUT/PATCH shows/{show} shows.update App\Http\Controllers\ShowController@update web
DELETE shows/{show} shows.destroy App\Http\Controllers\ShowController@destroy web
GET/HEAD shows/{show}/edit shows.edit App\Http\Controllers\ShowController@edit web

Step 4. Configure Bootstrap 4

Right now (2019.12.10), there are some issues, or somehow I do not see any code inside the public >> css >> app.css file. I have already compiled the CSS and JS file by the

npm run dev

command, but still, the app.css file is empty.

One possible solution is to copy code of previous version’s Laravel’s app.css file and paste it here :

J:\xampp\htdocs\laravel6\public\css\ap.css

Link of the previous css file is :
https://raw.githubusercontent.com/KrunalLathiya/Laravel58CRUD/master/public/css/app.css

Second possible solution is this. This new scaffolding is only available in Laravel 6 and not in the earlier versions like Laravel 5.8 or 5.7.

While Laravel 6 does not dictate which JavaScript or CSS pre-processors you use, it does provide the essential starting point using Bootstrap and Vue that will be helpful for many projects.

By default, the Laravel uses the NPM to install both of these frontend packages.

Bootstrap and Vue scaffolding provided by Laravel is located in the laravel/ui Composer package, which you can install using Composer so :

composer require laravel/ui –dev

Once laravel/ui package has been installed, and you may install the frontend scaffolding using the ui Artisan command:

// Generate basic scaffolding…
php artisan ui vue
php artisan ui react

// Generate login / registration scaffolding…
php artisan ui vue –auth
php artisan ui react –auth

Step 5. Create request (form to insert show tbl row)

Create the views.

http://localhost:8083/laravel6/public/shows/create

Inside the J:\xampp\htdocs\laravel6\resources\views folder where is only welcome.blade.php , create view files :

  1. C create.blade.php
  2. U edit.blade.php
  3. R index.blade.php Inside the views folder, we also need to create the layout file. So create a file inside the views folder called

    layout.blade.php :

    our main template file, and all the other view files will extend this layout.blade.php file.

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Laravel 6 CRUD Example</title>
    <link href="{{ asset('css/app.css') }}" rel="stylesheet" type="text/css" />
    </head>
    <body>
    <div class="container">
    @yield('content')
    </div>
    <script src="{{ asset('js/app.js') }}" type="text/js"></script>
    </body>
    </html>

    Here, we have already included the Bootstrap 4 by adding the app.css.

create.blade.php :

@extends('layout')

@section('content')
<style>
  .uper {
    margin-top: 40px;
  }
</style>
<div class="card uper">
  <div class="card-header">
    Add Shows
  </div>
  <div class="card-body">
    @if ($errors->any())
      <div class="alert alert-danger">
        <ul>
            @foreach ($errors->all() as $error)
              <li>{{ $error }}</li>
            @endforeach
        </ul>
      </div><br />
    @endif
      <form method="post" action="{{ route('shows.store') }}">
          <div class="form-group">
              @csrf
              <label for="name">Show Name:</label>
              <input type="text" class="form-control" name="show_name"/>
          </div>
          <div class="form-group">
              <label for="price">Show Genre :</label>
              <input type="text" class="form-control" name="genre"/>
          </div>
          <div class="form-group">
              <label for="price">Show IMDB Rating :</label>
              <input type="text" class="form-control" name="imdb_rating"/>
          </div>
          <div class="form-group">
              <label for="quantity">Show Lead Actor :</label>
              <input type="text" class="form-control" name="lead_actor"/>
          </div>
          <button type="submit" class="btn btn-primary">Create Show</button>
      </form>
  </div>
</div>
@endsection

Open ShowController.php file, and on the create() function, we need to return the view, and that is a create.blade.php file.

// ShowController.php

public function create()
{
   return view('create'); // create.blade.php file
}

Go to a http://localhost:8000/books/create or http://laravel6.test/shows/create

http://localhost:8083/laravel6/public/shows/create

You will see form “Create show”.
Add Shows Show Name: Show Genre : Show IMDB Rating : Show Lead Actor : Create show button

Step 6. Add Laravel Validation rules and save data

First step inside the ShowController.php is that import namespace of Show model inside the ShowController.php file.


// ShowController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Show; // import namespace of Show model 

Write following code inside ShowController.php file’s store() function.

public function store(Request $request)
{
        $validatedData = $request->validate([
            // check for all four fields of the form
            // If incoming data fail any of the rules, then it will directly go to the form with the error messages
            // $request object parameter will be used to access form data
            'show_name' => 'required|max:255',
            'genre' => 'required|max:255',
            'imdb_rating' => 'required|numeric',
            'lead_actor' => 'required|max:255',
        ]);

        $show = Show::create($validatedData);

        return redirect('/shows')->with('success', 'Show is successfully saved');
 }

The first thing you want to do is validate a form of data.
We can use a $request->validate() function for validation, which will receive the array of validation rules.
Validation rules is an associative array.
Key will be the field_name and value with being the validation rules.

The second parameter is the optional array for custom validation messages.
Rules are separated with pipe sign “|.” We are using the most basic validation rules https://laravel.com/docs/5.8/validation#available-validation-rules .

First is “required,” which means the field_name should not be empty. (“nullable” rule is vice versa), “string” means it should be the string value, “min” is the limit of minimum characters for a string in an input field and “max” is the maximum characters. “unique:table, column” with see if the same value does not exists in the database (comes handy for storing emails or any other unique data).

If the validation fails, then it will redirect us back. After the validation, we are creating a new book and save that book in the database.
We need to loop through that error messages inside the create.blade.php file which we have already done it.

If you leave all the form fields empty, then you will find an error message at page top.

Now, if you fill the form fields correctly, then it will create a new row in the database.

Step 7. V – display response

Display the data.

http://localhost:8083/laravel6/public/shows/

Write the ShowController’s index function to return an index view with data fetched from a database. Write the following code inside the index() function.

// ShowController.php

public function index()
{
     $shows = Show::all();

     return view('index', compact('shows'));
}

Create a file inside the views folder :

index.blade.php

@extends('layout')

@section('content')
<style>
  .uper {
    margin-top: 40px;
  }
</style>
<div class="uper">
  @if(session()->get('success'))
    <div class="alert alert-success">
      {{ session()->get('success') }}  
    </div><br />
  @endif
  <table class="table table-striped">
    <thead>
        <tr>
          <td>ID</td>
          <td>Show Name</td>
          <td>Show Genre</td>
          <td>Show IMDB Rating</td>
          <td>Lead Actor</td>
          <td colspan="2">Action</td>
        </tr>
    </thead>
    <tbody>
        @foreach($shows as $show)
        <tr>
            <td>{{$show->id}}</td>
            <td>{{$show->show_name}}</td>
            <td>{{$show->genre}}</td>
            <td>{{number_format($show->imdb_rating,2)}}</td>
            <td>{{$show->lead_actor}}</td>
            <td><a href="{{ route('shows.edit', $show->id)}}" class="btn btn-primary">Edit</a></td>
            <td>
                <form action="{{ route('shows.destroy', $show->id)}}" method="post">
                  @csrf
                  @method('DELETE')
                  <button class="btn btn-danger" type="submit">Delete</button>
                </form>
            </td>
        </tr>
        @endforeach
    </tbody>
  </table>
<div>
@endsection

We have used the PHP number_format() function to print the IMDB Rating float value.

Here, we have looped through the show’s array and display the data in the table format.

Also, we have added two buttons for edit and delete operation.

Step 8. CUD

Create Edit and Update Operation.
Add following code inside ShowController.php edit function :

// ShowController.php

public function edit($id)
{
     $show = Show::findOrFail($id);

     return view('edit', compact('show'));
}

Create new file inside the views folder

edit.blade.php

@extends('layout')

@section('content')
<style>
  .uper {
    margin-top: 40px;
  }
</style>
<div class="card uper">
  <div class="card-header">
    Update Shows
  </div>
  <div class="card-body">
    @if ($errors->any())
      <div class="alert alert-danger">
        <ul>
            @foreach ($errors->all() as $error)
              <li>{{ $error }}</li>
            @endforeach
        </ul>
      </div><br />
    @endif
    <form method="post" action="{{ route('shows.update', $show->id) }}">
          <div class="form-group">
              @csrf
              @method('PATCH')
              <label for="name">Show Name:</label>
              <input type="text" class="form-control" name="show_name" value="{{ $show->show_name }}"/>
          </div>
          <div class="form-group">
              <label for="price">Show Genre :</label>
              <input type="text" class="form-control" name="genre" value="{{ $show->genre }}"/>
          </div>
          <div class="form-group">
              <label for="price">Show IMDB Rating :</label>
              <input type="text" class="form-control" name="imdb_rating" value="{{ number_format($show->imdb_rating, 2) }}"/>
          </div>
          <div class="form-group">
              <label for="quantity">Show Lead Actor :</label>
              <input type="text" class="form-control" name="lead_actor" value="{{ $show->lead_actor }}"/>
          </div>
          <button type="submit" class="btn btn-primary">Update Show</button>
      </form>
  </div>
</div>
@endsection

In this file, you can show the values of the particular row using its unique id inside the form fields.

So, when you hit this URL: http://localhost:8000/shows/1/edit or http://laravel6.test/shows/1/edit, you will see row edit form.

Create Edit and Update Operation

Add following code inside ShowController update() function.

// ShowController.php

public function update(Request $request, $id)
{
        $validatedData = $request->validate([
            'show_name' => 'required|max:255',
            'genre' => 'required|max:255',
            'imdb_rating' => 'required|numeric',
            'lead_actor' => 'required|max:255',
        ]);
        Show::whereId($id)->update($validatedData);

        return redirect('/shows')->with('success', 'Show is successfully updated');
}

So now, you can edit and update all the data into the database successfully.

Step 9. D – create delete row functionality

Write following code inside ShowController destroy function to to remove show row :

// ShowController.php
public function destroy($id)
{
        $show = Show::findOrFail($id);
        $show->delete();

        return redirect('/shows')->with('success', 'Show is successfully deleted');
}

We have completed a Laravel 6 CRUD operations tutorial with the example from scratch.

If you are interested in FrontEnd framework like Vue.js with Laravel or Angular with Laravel (Code is on Github as for this tutorial) :

  1. Vue Laravel CRUD example https://appdividend.com/2018/11/17/vue-laravel-crud-example-tutorial-from-scratch/
  2. Angular Laravel Tutorial Example https://appdividend.com/2017/09/22/laravel-5-5-angular-4-tutorial-example-scratch/

https://appdividend.com/2019/09/13/how-to-import-and-export-data-in-csv-excel-in-laravel-6/
https://appdividend.com/2019/09/13/laravel-6-generate-pdf-from-view-example-tutorial-from-scratch/
https://appdividend.com/2019/09/04/how-to-upgrade-laravel-valet-and-update-to-laravel-6/
https://appdividend.com/2019/04/08/laravel-collections-search-method-tutorial-with-example/
https://appdividend.com/2019/04/03/laravel-collections-filter-method-tutorial-with-example/
https://appdividend.com/2018/08/15/laravel-file-upload-example/
https://appdividend.com/2018/02/09/laravel-multiple-files-upload-tutorial-example/
https://appdividend.com/2018/02/08/laravel-ajax-validation-tutorial-scratch/
https://appdividend.com/2018/02/07/laravel-ajax-tutorial-example/


Laravel 6 continues the improvements made in Laravel 5.8 by introducing the following features.

  1. Semantic versioning – serverless deployment platform for Laravel, powered by AWS
  2. Compatibility with Laravel Vapor
  3. Improved authorization responses
  4. Job middleware
  5. Lazy collections
  6. Sub-query improvements
  7. The extraction of frontend scaffolding to the laravel/ui Composer package

https://appdividend.com/2018/10/31/how-to-use-php-in-visual-studio-code/ – For PHP Developers.

See https://github.com/slavkoss/fwphp/ (J:\awww\www\readme.md).

B12phpfw does not use jQuery and AJAX, see examples in https://github.com/slavkoss/fwphp/tree/master/fwphp/glomodul/z_examples/AJAX

12. PHP menu & CRUD code skeleton (B12phpfw) comparison

Featured

Revised November 2021 October 2020, December 2019. Original post was published in February 2019.
Download https://github.com/slavkoss/fwphp/ . My articles before this are not needed to learn B12phpfw.

November 2021 version 8.0.0 (requires PHP ver. >= 7). Download  contained many learning examples, but no more because three modules – packages :  Mnu (menu code skeleton), Msg (Blog – compound module) and Mkd  contain enough (not to simple)  PHP code for learning, create large sites CRUD pages and own WYSIWYG web editor. To simple learning code (frequent in learning sources books and internet) is dangerous, leads to wrong coding, which is one of reasons why I have 8 versions. Version 8 is in my opinion smallest code to learn serious web programing.

What’s new in version 8 :

  1. Autoload.php is refactored, much simpler, see old z_NOT_USED_TO_COMPLICATED_Autoload_1stVersion.php
    This is good case showing that first code versions are to complicated code – very frequent case – which is one of reasons why I have 8 versions.
  2. Shares are not any more in \zinc folder but in in vendor folder eg \vendor\b12phpfw\Autoload.php (below web server doc root eg J:\awww\www\). Site roots are on same place in folders hierarchy eg \fwphp (may be named site_x). Also group of modules eg \fwphp\glomodul. Also modules eg \fwphp\glomodul\blog – dir like oracle Forms form module.
  3. PHP 8 (ver. 7 still works) and Bootsrap 5
<?php
http://dev1:8083/fwphp/www/
// J:\awww\www\fwphp\www\index.php
namespace B12phpfw\site_home\www ;

use B12phpfw\core\b12phpfw\Autoload ;

//1. settings - properties - assign global variables to use them in any code part
$module_path = str_replace('\\','/', __DIR__) .'/' ;
$site_path = dirname($module_path) .'/' ; //to app dir eg "glomodul" dir and app
//to web server doc root or our doc root by ISP $module_towsroot = eg '../../../'
$wsroot_path = dirname(dirname($module_path)) .'/' ;
//or $wsroot_path = str_replace('\\','/', realpath('../../')) .'/' ;
$shares_path = $wsroot_path.'vendor/b12phpfw/' ; //includes, globals, commons, reusables

$pp1 = (object)
[ 'dbg'=>'1', 'stack_trace'=>[str_replace('\\','/', __FILE__ ).', lin='.__LINE__]
, 'module_version'=>'8.0.0.0 Mnu' //, 'vendor_namesp_prefix'=>'B12phpfw'
// $_SESSION["TrackingURL"]=$_SERVER["PHP_SELF"];
// 1p. (Upper) Dirs of clsScriptsToAutoload. With 2p(ath). makes clsScriptToAutoloadPath
// 2p. Dir name of clsScriptToAutoload is last in namespace and use (not full path !).
, 'wsroot_path' => $wsroot_path // to awww/www (or any names)
, 'shares_path' => $shares_path // to b12phpfw, b12phpfw is required dir name
, 'site_path' => $site_path // to fwphp (or any names)
, 'module_path' => $module_path // to fwphp/www (or any names)
//
, 'glomodul_path' => $site_path .'glomodul/'
, 'examples_path' => $site_path .'glomodul/z_examples/'
] ;

//2. global cls loads classes scripts automatically
require($pp1->shares_path . 'Autoload.php');
new Autoload($pp1);

//3. process request from ibrowser & send response to ibrowser :
//Home_ ctr "inherits" index.php ee inherits $p p 1
$module = new Home_ctr($pp1) ; //also instatiates higher cls : Config_ allsites

exit(0);
<?php
// J:\awww\www\vendor\b12phpfw\Autoload.php
namespace B12phpfw\core\b12phpfw ; //Dir name is last in namespace and use 
//use B12phpfw\dbadapter\post\Tbl_crud ; // as Tbl_crud_post ;

class Autoload
{
protected $pp1 ; //M O D U L E PROPERTIES PALLETE like in Oracle Forms

public function __construct(object &$pp1) {
$pp1->stack_trace[]=str_replace('\\','/', __FILE__ ).', lin='.__LINE__ .' ('. __METHOD__ .')';
$this->pp1 = $pp1 ;
spl_autoload_register(array($this, 'autoloader'));
return null ;
}

function autoloader(string $nscls) 
{
//$nscls is namespaced called cls name eg B12phpfw\module\book\Home_ctr
$DS = DIRECTORY_SEPARATOR ;
$nscls_linfmt = str_replace('\\',$DS, $nscls) ; //ON LINUX
$clsname = basename($nscls_linfmt) ; //eg Home_ctr, Config_ allsites, Db_allsites
$module_dir = basename(dirname($nscls_linfmt)) ; //eg Home_ctr, Config_ allsites, Db_allsites
//echo '<pre>$nscls='; print($nscls) ; echo ' $module_dir='; print($module_dir) ; echo '</pre>';

switch ($module_dir) {
case 'b12phpfw': 
$clsscript_path=dirname($this->pp1->shares_path).'/'.$module_dir.'/'.$clsname .'.php' ; 
break;
default:
$clsscript_path=dirname($this->pp1->module_path).'/'.$module_dir.'/'.$clsname .'.php' ; break;
break; 
}
//echo '<p>$clsscript_path='; print($clsscript_path) ; echo ' SAYS: '. __METHOD__ .'</p>';

require $clsscript_path;


}
}

2020.09.30 DONE version 7.0.0 (declare(strict_types=1);).

DBI is improved with trait Db_allsites instead class Db_allsites. Each DB (persistent storage) adapter-class named Tbl_crud :

  1. use B12phpfw\core\zinc\Db_allsites
  2. implements Interf_Tbl_crud

This means that :

  1. eg blog module in blog folder, works much easier with more model classes ee Tbl_crud -s, ee with own Tbl_crud and with other tables Tbl_crud.
  2. eg sole module controller class Home_ctr extends class Config_allsites, no more extends also two DB CRUD classes which is unnatural (but seems easy because logically : all is in Home_ctr).

B12phpfw core (CRUD) code – How to get ONLY banana ?

It is not easy to see need to why convert code from procedural MVC to OOP MVC with namespaces and autoloading. For navigation (url-s, links) code procedural and OOP is same – OOP does not help. Procedural MVC user module code is more clear and readable. So why is OOP better ?

Some say: “is OOP mistake ?” – eg lack of reusability in OOP – to get banana (some method or attribute) you get also gorilla holding banana and whole gorilla`s jungle (all higher classes with complicated dependencies). It is why B12phpfw code skeleton is for CRUD modules (is not required for Mnu and Mkd modules but I put both on B12phpfw code skeleton).

Interfaces help to get ONLY banana, but coding is complicated – I could find only strong-talk-weak-work code examples about advanced use of interfaces.

  1. My not complicated interface Interf_Tbl_crud I made to standardize coding Tbl_crud model adapter classes and Db_allsites shared model class. Each simple (ee one table) module like “invoice items” module has own dir and own Tbl_crud class leading to more than 100 Tbl_crud.php model adapter classes scripts in big application (eg material and financial book keeping).
    J:\awww\www\vendor\b12phpfw\Interf_Tbl_crud.php (... hits)
    Line 20: static public function get_cursor( //instead rr
    Line 28: static public function cc(object $pp1, array $other=[]): object ; //create row
    Line 31: static public function rrnext(object $cursor ): object ; //read next from $c=get_cursor
    Line 34: static public function rrcnt( //string $sellst, //read count rows in table
    ...
  2. With DBI trait Db_allsites I eliminated two higher DB classes. So if banana (some method or attribute) is get_cursor (read) from more tables it is not in two higher DB classes which seems simplest solution but caused complicated coding in version 6. Eg invoice module works with two (or three – bill) tables : invoice and invoice_items.get_cursor banana is not in jungle (two higher DB classes) any more, gorilla and jungle is only one abstract class Config_allsites which is de facto assigning $pp1 = properties (Oracle Forms form property palete made global).
    1. Banana $pp1 = properties palette may cause difficulties in aggregate (compound, composed, multiplex) modules like Blog, Invoice… but $pp1 is inevitably (imminence, necessity) gorilla-jungle and can not be further simplified. I worked 20 years with $pp1 and globals jungle (Oracle Forms 6i) which i not so well accomplished as in B12phpfw .

    See https://phpthewrongway.com/, or Joe Armstrong why OOP sucks http://harmful.cat-v.org/software/OO_programming/why_oo_sucks.

    Similar “simplest solution” three dirs M,V,C is (I think) bad, I have opinion -:) for 3dirs lovers who put in foreground coding technic (M,V,C code separation) instead pages (functionality, business logic , eg invoice page).

During winter 2019/2020 year (much to late because I tested lot what others did) I made Version 6. of menu and CRUD PHP code skeleton (own framework named “B12phpfw”) – core code is about 50 kB.
I also made posts module “Msg” with posts edited with WYSIWYG Markdown SimpleMDE editor (or HTML WYSIWYG Sumernote editor or … any editor you wish). Msg module is based on B12phpfw, also very small code.

B12phpfw code skeleton and Msg application on B12phpfw skeleton is minimal PHP code to learn (medium/advanced knowlege) PHP !

Why ?

  1. I do not like proposed solutions in best php frameworks (Laravel, Simfony, Yii…) and learning sources (internet, books).
  2. I think that eg module invoice php code should be in own folder like Oracle Forms form invoice.fmb instead all forms/reports in 3 folders: M, V, C).
  3. I think that should be simple/fast/professional : shares (globals), routing, dispaching, classes loading , web rich text editing – it is why I spend many hours coding my B12phpfw (huge time wasting which should do software authors, not sw users-programers like me).

Compared B12phpfw Msg (blog) module and TraversyMVC blog module and Mini3 module URLs Youtube songs adresses

Why I do not like proposed solutions in existing PHP frameworks and what I did to (I hope) improve fw code. Red colored features and three asterisks *** are main reasons for B12phpfw, but I improved also other features.

TraversyMVC (has video) and Mini3 are simplified, with some (many?) differences Laravel, Simfony, Yii… B12phpfw is much more different – see red colored features.

Feature B12phpfw TraversyMVC and Mini3
1. ***Modules in own folder like Oracle Forms .fmb has – it is main reason for B12phpfw ! has not like Mini3 – all forms/reports in 3 folders: M, V, C
2. Name spaced classes (functional name spacing) has has not – no functional, no positional (paths) name spacing, Mini3 MVC PHP fw which is in my opinion better than TraversyMVC : https://github.com/panique/mini3 has name spaced classes
3. Number of folders (my opinion) optimal to many like Mini3
4. Minimal PHP code to learn (medium) PHP (my opinion) optimal (but we could add additional code) good but not enough like Mini3 eg see WYSIWYG, globals…
5. Functional methods, attr. etc naming (my opinion) good could be better
6. ***Global classes, methods etc (my opinion) good bad like Mini3
7. (Posts edited with any) WYSIWYG editor has has not like Mini3
8. ***Home_ctr or Home_mdl CRUD layer methods do not know for underlaying Db_allsites layer PDO methods, MySql, Oracle… has much improved has not like Mini3
9. OOP has has like Mini3
10. namespaces (own PSR-4 (or Composer’s) autoloading classes scripts) improved has not, Mini3 has
11. ***All scripts are included (ee no http jumps except some jumps to other module) has has not, Mini3 has, B12phpfw took it from Mini3
12. jQuery only for Bootstrap 5 yes yes, Mini3 has own CSS
13. no AJAX, no JSON yes yes, Mini3 has basic jQuery AJAX explained
14. server side validation has has like Mini3
15. authentification (log in / out) has has, Mini3 has not
16. authorization (only logged in users may execute some code ee CRUD code…) has has, Mini3 has not
17. ***Own debugging very simple and useful : msg in pre tag or popup JS msg). xdebug also helps. has has not like Mini3

10. Real life application “Messages” PHP PDO, AJAX+jQuery CRUD&filter (thema, blog, forum, CMS, builetinboard, Skype replacement)

Revised 2020.03.26

HOME  7. Jan. 2018 I uploaded code on Github https://github.com/slavkoss/fwphp – much different then in this article title – see below “All we need…”. This old B12phpfw version 3. is more complicated but if you like it could be simplified. I think version 6 on Github is best, simplest.

I think it is my last word about routing which I do not like in my previous articles here. Basic ideas are same in all my articles (code skeleton like Oracle Forms) Previous articles were tests of different technologies which made my conclusion :

All we need for MVC OOP PHP framework is (beside love) : to be mazohist what I try to minimize here, which is reason for this blog :

  1. routing / dispatching (last code as in Mini3 PHP framework https://github.com/panique/mini3)  based on URL query array
  2. PHP PDO CRUD
  3. CSS ~ bootstrap or own ~200 lines
  4. for blogging rich text web editor like : markdown SimpleMDE or html Summernote
  5. no or minimal Javascript, no AJAX, no JS frameworks : React, jQuery, AngularJS…  and simmilar overprogrammed libraries

I would use PHP and JS frameworks if they had better help, but they all seem (partially) like cat in bag, vaporware. It is pity so much work and at end we have application, not developing SW. Only author and his friends understand it.

Same is fate of Oracle Forms, new versions, after 6i, with 3-4 servers. It is application, not developing SW, it is why nobody starts new projects with Oracle Forms and Oracle advises Java developer also written in C called Java and better for evil empires. There is no sin to make new SW – old should be refactored – recoding all the time – see in Google search “extreme programming”.

Kae Verens published 2010 year nice book -CMS Design Using PHP and jQuery-. It has same problem as 90% WEB learning resources : smarty, and rich text ckeditor do not work and I did not found easy way to use newer version (Kae did not explain such problem). -Path science- is very poor – based on -all is in site-s document root dir. What if we do not have more virtual hosts (sites). It is very strange that this basics (and some other) are unfinished because this book is based on 10 years real CMS development.

 

Old versions

15.october 2017 I uploaded complete test site (with php.ini and Apache config files) in 1_tests.zip, it is old code not of interest, except some code snippets.

14.august 2016   I uploaded 3sites .rar – see HOME page for downloads, it is old code not of interest, except some code snippets. **4.august 2016** -Messages- real life application added **nicEdit** rich text editor (after testing ~10 RTE).

9. Main development, test and production menu (& 3 sites) – PHP 7 RC5, Oracle 11g on Windows 10 all 64 bit

HOME – Download Win10 64 bit AMP config and three sites php scripts.

14.8.2016 I changed a lot, see my article 10. This article 9 can be used only for explanations but everything is in code in 1_3sites_mnu_crud_ver1.rar.

22.11.2015 code refactored – i uploaded sites code version 3, (SINGLETON UTILS-HELPERS CLASS, config_site.php…).

Scripts config_site.php, utls.php, utls_glbsetpg.php and lsweb.php explain most important changes. I hope this is final code skeleton, ver4 will be bug fixes and cosmetic updates. (In ver3 I tested only most important lsweb.php but testing others is good exercise – and it shows how difficult this job is).

Statements (functions) flow article (not yet uploaded) should link together all I wanted to say.

Every (vitual) host – I have them 3: dev, test, production – should have in its web site doc root script which tells web server where are resources which are outside doc tree (global fns, settings, css, imgs)

(Problem with resources outside web doc tree is that we need file_get_contents() fn which is disabled by some inet providers. But if you want own SOAP server eg for direct b2b copying einvoice xml – then every your user should have own web site (bad news for some not needed service brokers – posrednici).)

Most important are:
1. “path science” – unexplained enough in learning materials I met (for SPA script very different than for URL called script).
2. Statements (functions) flow (to understand SW & for debugging) – I shall soon write article 10 or 11 (?) about this (SOAP examples for copying xml across inet will also be soon).

I can not understand SPA & frameworks without above two explained. To me seems that framework authors intentionally do not explain important things about their scripts coding (there is to much commercial interests in free SW) – so we have 100 frameworks instead 5-6.

I am shure that Utils class should be, settings should be in global_config.php both outside web doc tree. Local settings in doc tree should owerwrite global.

Eg my : J:\awww\apl\dev1\config_site.php contais:

 < ?php
 // development site (virtual host on home PC) :
 // J:\awww\apl\dev1\config_site.php
//$_SERVER['DOCUMENT_ROOT'] = J:\awww\apl\dev1\
 // yii2 advanced site (virtual host on home PC) :
 // J:\awww\apl\dev1\zfw\yii205\frontend\web\config_site.php
 // J:\awww\apl\dev1\zfw\yii205\aplmy\backend\web\config_site.php
 // realpath
 use utlmoj\utlmoj as utl;
 $ds = DIRECTORY_SEPARATOR;
 /**
 * CONVENTION: sitedocroot/../zinc = eg J:/awww/apl/zinc
 * site does not know where (outside site doc tree) are global resources
 * (util scripts, css, img for all sites), so we must assign
 * 1. GLOBAL RESOURCES (INCLUDES) FOLDER :
 */
 $gloresdir = realpath($_SERVER['DOCUMENT_ROOT'].'/../zinc'); // 1.
 require_once($gloresdir.'/utls.php'); // 2. util (helper) scripts
 $utl=utl::uget(); // 3. get or create helper fns object (singleton)
 require_once($gloresdir.'/utls_glbsetpg.php'); // 4. global page properties
 /**
 * IN APPL (IN SPA) IF IT NEEDS C R U D :
 * 2. g l o b a l C R U D :
 * $dbi = 'sqlite'; $dsn = 'cars_makes_names_savings.sqlite';
 * require_once($gloresdir.$ds.'db_conn.php');
 * db_conn.php does:
 * 1. require_once($gloresdir.'/klase/dbi.php');
 * 2. require_once($gloresdir.'/tbl/zodiac_mdl.php);
 * CONVENTION for M D L of concrete tbl :
 * require_once($gloresdir.'/tbl/'
 * .str_replace('.php','_mdl.php', basename($curpgpath)));
 * 3. template crud script :
 * require_once($gloresdir.$ds.'crud.php');
 */
 // ******************************************
 //exit();
 ?>

I am not shure if Utils class should have static or non static or both methods & properties (because you need many years PHP programming experience to be shure). Properties of this two working ways are not clear to me and unexplained enough in my learning sources.

Pitty that somebody – PHP expert – which I am not – does not explain this somewhere.

2.Sept.2015 – site_ver2.rar – improved code for all scripts, eg for lsweb.php and added some dir icons and better presented/explained site dir structure (see awww_DIR_NOT_VISIBLE_TO_ME.txt).

This article is enough for (advanced) beginning PHP programming Oracle and SQLite CRUD. 
Articles 1 to 8 are supplementary info.

This article unites my posts 3. Zwamp menu and 5. CRUD simple table (example 1) with refactored code for 5. CRUD and my (I hope) final site directories structure.

Most important examples in this article (others are in site_verx.rar):

Example Šifrarnik – is not finished, but shows much. Model is table (id, few_columns)

Example web server directories listing – dir items listing can be extended with row filters, sorts, downloads… but so as it is is very useful for web development.
Model (input data) is DirectoryIterator().
I find lsweb.php very useful for web development:
J:\awww\apl\dev1\zinc\utl\ls.php          http://dev1:8083/zinc/utl/ls.php  (or with ?dir=J:\awww\apl\dev1)
J:\awww\apl\dev1\zinc\utl\lsweb.php

Code for article 3. Zwamp menu is also contained in  site_verx.rar – it is to complicated for real life sites (except if you like something like)  but is excellent for learning PHP.

 

Oracle example 1

Model (input data) is simple table (šifrarnik) (id, few_columns), but more than 50% programming techniques can be learned on this and next example.


J:\awww\apl\dev1\zinstalac\ddl\DDL_selfjoin.sql:
-- winkey+X -> Comm.prompt admin
C:
cd C:\oraclexe\app\oracle\product\11.2.0\server\bin
sqlplus hr/hr
sho user
CREATE TABLE ZODIAC (
  ID          NUMBER(10) NOT NULL,
  SIGN        VARCHAR2(11),
  SYMBOL      VARCHAR2(13),
  PLANET      VARCHAR2(7),
  ELEMENT     VARCHAR2(5),
  START_MONTH INTEGER,
  START_DAY   INTEGER,
  END_MONTH   INTEGER,
  END_DAY     INTEGER,
  PRIMARY KEY(ID)
);

CREATE SEQUENCE ZODIAC_SEQ;
CREATE or replace TRIGGER ZODIAC_PREINS_TRIG 
BEFORE INSERT ON ZODIAC 
FOR EACH ROW
BEGIN
  -- PRIOR TO 11G :
  SELECT ZODIAC_SEQ.NEXTVAL INTO :NEW.ID FROM DUAL;
  -- 11G :
  --:NEW.MSG_ID := ZODIAC_SEQ.NEXTVAL;
END;
/
sho err

DML :
INSERT INTO zodiac VALUES (1,'Aries','Ram','Mars','fire',3,21,4,19);
INSERT INTO zodiac VALUES (2,'Taurus','Bull','Venus','earth',4,20,5,20);
INSERT INTO zodiac VALUES (3,'Gemini','Twins','Mercury','air',5,21,6,21);
INSERT INTO zodiac VALUES (4,'Cancer','Crab','Moon','water',6,22,7,22);
INSERT INTO zodiac VALUES (5,'Leo','Lion','Sun','fire',7,23,8,22);
INSERT INTO zodiac VALUES (6,'Virgo','Virgin','Mercury','earth',8,23,9,22);
INSERT INTO zodiac VALUES (7,'Libra','Scales','Venus','air',9,23,10,23);
INSERT INTO zodiac VALUES (8,'Scorpio','Scorpion','Mars','water',10,24,11,21);
INSERT INTO zodiac VALUES (9,'Sagittarius','Archer','Jupiter','fire',11,22,12,21);
INSERT INTO zodiac VALUES (10,'Capricorn','Goat','Saturn','earth',12,22,1,19);
INSERT INTO zodiac VALUES (11,'Aquarius','Water Carrier','Uranus','air',1,20,2,18);
INSERT INTO zodiac VALUES (12,'Pisces','Fishes','Neptune','water',2,19,3,20);

Scripts directory structure (SPA, MVC domain style)

SPA means scripts are included, only exceptionally URL called. So scripts see all $ variables as own.

In SPA is not posible using relative page adresses because path relative to included script is not relative to includer (SPA) script  – adresses syntax explained here must be used !! To learn dirs science is one of most difficult PHP beginning parts, and allmost NOT EVEN MENTIONED in PHP learning materials.

MVC domain style means Scripts directory structure is “one form (application) one dir (& its subdeirs if needed)”. MVC is basically old structured programming:

  1. initialize (bootstrap, config),
  2. manage (ctr),
  3. input (model),
  4. output (view).

It is interesting that no one MVC promotor mentions this simple truth. Names are most important, but working means not (only) inventing new names but understand+explain.

DDLs are in J:\awww\apl\dev1\zinstalac\ddl\,  eg above  DDL_selfjoin.sql contain also selfjoin DDL & for SQLite.

Global config scripts (.php, .css, .js…most important of all types !) are in J:\awww\apl\dev1\zinc\,  eg  utls.php.

Below zinc are dirs: utl (helpers), slike (img), js…

Three sites (Apache virtual hosts on home PC):

Development URL is http://dev1:8083/ is Windows dir J:\awww\apl\dev1.

Production URL is http://pro1:8083/  is Windows dir J:\awww\apl\pro1.

Simmilar are both test site adresses.

 

After huderts Oracle Forms 6i created from scratch (from existing form ctrl+c,v) in more then 15 years it seems to me, whatever they say :), for PHP & Oracle should be :

CONVENTIONS FOR BETTER SCRIPTS VISIBILITY

  1. No camelcase becouse name higher_lower is (to me) better visible then camelcase higherLower
  2. Scripts sufixes :
    1. controller scripts have no sufix
    2. other scripts :
      1. model scripts: _mdl, _val (validation scripts)
      2. view scripts: _frm, _tbl, _rep
  3. ctr / bootstrap scripts are NOT named index.php, but are named simmilar to DB table name which they (CRUD) manage.Beside script visibility, this enables us to have more/all small tables crud (forms) scripts in one dir.To many dirs is not practical and old principle “one form (application) one dir (& its subdeirs if needed)” is newest fashion (2015 year).
  4. GLOBAL configuration scripts are in $CNFGD=J:\awww\apl\dev1\zinc, eg $CNFGD.$DS.utls.phputls.php (with help of config not static class & namespace) defines PHP $  (adress) variables for all included scripts (SPA !!) (no constants except pi and simmilar). Conf. vars must be defined as $ vars in utls.php and not as concatenation of dirs in all index.php scripts, becouse :
    IF DIR TREE CHANGES -> WE CHANGE ONLY ONE PLACE: utls.php
  5. Some strange names are better for search or for name conflicts (eg zinc instead of includes, chcons instead configclass, this example zodiac…)

 

Example 2: Web server directories listing programs

Model (input data) is DirectoryIterator() dir items listing.

I find them very useful for web development:
J:\awww\apl\dev1\zinc\utl\ls.php          http://dev1:8083/zinc/utl/ls.php  (or with ?dir=J:\awww\apl\dev1)
J:\awww\apl\dev1\zinc\utl\lsweb.php

 Conclusion

I did not see advantages in my testings compared with programming techniques I explained in this article (and in other before):

  1. AngularJS 1.4.3 CRUD with Oracle 11g
  2. Yii 2.0.6 with PHP 7 beta 3
  3. FatFree PHP fw

But frameworks above:

  1. no Oracle DBI example for normal people given
  2. It seems to me they are unfinished – always in development (uncompatible new versions), always new fws appear.
  3. slower ( min ~ 150 kB code with uncertain future included),
  4. more complicated – another (more) programming languages
  5. They do not even mention reporting (php reports from Eustáquio Rangel or similar) or tab key -> enter key which I made with few lines Javascript code (see site_ver1.rar J:\awww\apl\dev1\zinc\key_pressed.js).
  6. I doubt if they are more productive – learn time for programming techniques I explained in this article is ~ as for them  (lot of time 🙂 )
  7. to many incompatibilities uncertain future (yii2, ng 2),

Until authors of fw sw convince us on CRUD examples I give in this article + master-detail example I can not believe their to simple / not clear CRUD examples.


~~~utls.php $test=1 ~~~end script J:\awww\apl\dev1\index.php   source   phpinfo~~~

 

6. CRUD selfjoin table forum – message board PDO SQLite

HOME   . Downloads

Excellent code to learn object oriented programming, model-view-controller code separation (structured programming), single page application (4 utility-helper functions + 5 CRUD functions), all one selfjoin table CRUD code in one script, require scripts, not URL them, most code outside Apache doc root, Javascript msg & yes-no dialogs and key TAB->ENTER.
(see Home -> site_ver2.rar).

6.Dec.2015 code refactored and parialy improved. Later (next year) I shall finish all I planed and upload also  site_ver2.rar where is code outside appl. root. Below is newest code, other is in
Home -> site_ver2.rar

2.Sept.2015 Download – see site_ver2.rar from my article 9. Code below is enough to understand – it is not much changed.

1. Download sqlitestudio and install DDL at end tema.php

2.     1_sync_tema_sifrarnik_JtoH.bat

rem SINCHRONIZATION: 2 click this .bat script (or Git)
rem J:\awww\apl\dev1\01apl\04tema\1_sync_tema_sifrarnik_JtoH.bat
rem
rem robocopy <Original Folder> <Destination Folder> /e /purge
rem **** OUTPUTS EG : *****
rem Total Copied Skipped Mismatch FAILED Extras
rem Dirs : 20 0 20 0 0 0
rem Files : 94 6 88 0 0 0
rem Bytes : 453.9 k 36.6 k 417.2 k 0 0 0
rem Times : 0:00:00 0:00:00 0:00:00 0:00:00
rem
rem Speed : 708811 Bytes/sec. 40.558 MegaBytes/min.
rem Ended : 6. prosinca 2015. 20:33:19

rem 1. resources outside appl. tree - utils, settings, css, img
rem 1.1 
robocopy J:\awww\apl\dev1\config_site.php H:\awww\apl\dev1\config_site.php /e /purge
rem 1.2
robocopy J:\awww\apl\zinc H:\awww\apl\zinc /e /purge

rem 2. inside appl. tree
robocopy J:\awww\apl\dev1\01apl\04tema H:\awww\apl\dev1\01apl\04tema /e /purge

pause

3. tema.php Code – all in one script ~350 lines :

<?php
/**
* Themes (threads) (msg-reply, task, menu) 
* Teme (niti) (poruka-odgovor, zadaća, izbornik)
* PHP, Javascript, PDO SQLite3, selfjoin, 2015.12.7
*
* LICENCE: Free code example - if you use it, do not remove this:
* Slavko Srakočić, Croatia, Zagreb
* see my blog http://phporacle.altervista.org
*
*
* 1. ADRESSES :
* J:\awww\apl\dev1\01apl\04tema\tema.php
* http://dev1:8083/01apl/04tema/tema.php
*
* 2. robocopy (or Git) SYNCHRONIZATION: 2 click this .bat script (or git)
* J:\awww\apl\dev1\01apl\04tema\1_sync_tema_sifrarnik_JtoH.bat
*
* 3. DDL: see CREATE TABLE message at this script end
*
* 4. Home page looks like :
* Open new thema (nit, thread) Help = Klik Ord.Nr
* |...| = |bytes| idniti, rbruniti, nivo, id, idviši |
* 1. MSGs self join sqlite3 THIS PG by ss @ 2015-03-25 01:41 |DELETE| 1900 |UPDATE |1,0,0 ,1,0|
*     1. funkcije by ss @ 2015-03-25 01:42 |DEL| 1654 |UPD|1,1,1 ,2,1|
*          2. Re: funkcije by ss @ 2015-03-26 14:31 |DEL| 14 |UPD|1,2,2 ,18,2|
*     3. funkcija save() by ss @ 2015-03-25 01:52 |DEL| 1296 |UPD|1,3,2 ,3,2|
* ...
* 2. Z-WAMP menu by aa @ 2015-12-05 15:58 |DELE| 14 |UPD |3,0,0 ,25,0|
*
*
* 5. PHP CREATED JAVASCRIPT MSG & YES-NO DIALOGS :
* $this->utl->phpjsmsg(7 parameters), phpjsmsgyn() 
* 6. F U N C T I O N S IN THIS SCRIPT
*LINE FUNCTION WHAT (HOW) IS DOING 
*89: public __construct() { // INITIALIZE (B O O T S T R A P I N G)
*151: public ctr() { // M A N A G E (R O U T I N G)
*192: protected crerec() { // crud1. FRONTEND A D D SELFJOIN ROW
*257: protected tbl() { // crud2. D I S P L A Y HIERARCHY
*363: protected frm_rpt() { // crud3. R O W REPORT (m s g b o d y...)
*417: protected frm_post() // crud4. ENTER USER DATA & PROCESS IT
*491: protected delrec() { // crud5 BACKEND D E L E T E R O W
*522: protected valid() {
*538: protected logAndDie(Exception $e) {
*
*/
use utlmoj\utlmoj as utl;

ini_set('max_execution_time', 0);
$start = microtime(true);

$selfj = new SelfJoin(); // should be named: MessageBoard, MainMenu...
$selfj->ctr(); //ROUTING = starts scripts according url params cmd, ... 

$end = microtime(true);

echo '<br />'.'Vrijeme izvođenja: '.($end - $start) . ' sekundi';

class SelfJoin { // should be named: MessageBoard, MainMenu...
 
 // 1. C R U D (M O D E L) PROPERTIES :
 protected $inTransaction = false;
 
 protected $db;
 protected $table;
 protected $idname;
 protected $idval;
 // c r u d actions (CURSORS = named sql set process comands):
 // D = cursor for deleting
 protected $cdel_row_byid ; 
 // R e a d cursor
 protected $cr_max_rbrdet_inm ;
 protected $cr_byid_nivo_orddet_inm ;
 protected $cr_byid ; // R E A D r o w B Y I D (M O D E L c R u d)
 protected $cr_byid_colname ; 
 // U p d a t e cursor
 protected $cu_incr_rbrdet_inm ;
 // C r e a t e cursor
 protected $cc_mast_or_det ;
 
 // 2. U T L S F U N C T I O N S (H E L P E R S) :
 protected $utl; // OUTSIDE WEBSERVERDOCROOT
 // a d r e s s properties :
 protected $curpgurl;
 protected $curpgpath;

 // 3. V I E W PROPERTIES :
 protected $form_errors = array();
 
 
 public function __construct() { // I N I T I A L I Z E (B O O T S T R A P I N G)
 set_exception_handler(array($this,'logAndDie'));
 
 // C R U D (m o d e l) properties :
 $this->table = 'message';
 $this->idname = 'id';
 $this->idval_url = isset($_REQUEST[$this->idname]) ?
 intval($_REQUEST[$this->idname]) : '';
 
 // C R U D actions (cursors, named sql set process comands):
$this->cdel_row_byid = // cd_ means cursor for deleting
"DELETE FROM $this->table WHERE $this->idname=?"; //$values[]=$parent_id;

$this->cr_max_rbrdet_inm;
"SELECT MAX(thread_pos) FROM $this->table WHERE thread_id = ? AND parent_id = ?"; 

$this->cr_byid_nivo_orddet_inm= // cr_ means cursor for reading
"SELECT thread_id,level,thread_pos FROM $this->table WHERE $this->idname=?";

$this->cu_incr_rbrdet_inm=
"UPDATE $this->table SET thread_pos = thread_pos + 1 WHERE thread_id = ? AND thread_pos >= ?";

$this->cc_mast_or_det="INSERT INTO $this->table "
. "($this->idname, thread_id,parent_id, thread_pos, posted_on, level, author, subject, body) "
.'VALUES (?,?,?,?,?,?,?,?,?)';

$this->cr_byid=
"SELECT author,subject,body,posted_on FROM $this->table WHERE $this->idname = ?";

$this->cr_byid_colname=
"SELECT subject FROM $this->table WHERE $this->idname = ?";

 
 // 1. g l o b a l u t l s, s e t t i n g s :
 $curpgpath =__FILE__; $mastpgpath=$curpgpath; //dirname(__DIR__) ;
 $this->curpgpath = $curpgpath;
 require_once($_SERVER['DOCUMENT_ROOT'].'/config_site.php');
 $this->curpgurl = $curpgurl ; 
 //htmlentities($_SERVER['PHP_SELF'],ENT_QUOTES);
 //$_SERVER['DOCUMENT_ROOT'] = eg J:\awww\apl\dev1\
 
 $this->utl = $utl;
 
?><SCRIPT LANGUAGE="JavaScript"><!-- Begin 
<?php echo file_get_contents($jsd.$ds.'key_pressed.js');?>//End --></SCRIPT><?php if ('') $utl->phpjsmsg('aaaaaaaaaa key_ pressed.js IS INCLUD E D');
 
 // 2. g l o b a l c r u d :
 $dbi = 'sqlite3'; $sqlitetbl = 'tema.sqlite3' ; //$dsn='default';
 require_once($gloresdir.$ds.'db_conn.php'); //requires klase/dbi.php
 $this->db = $db;
// ******************************************
 $title = '<h1>Teme (threads) (poruka-odgovor, zadaća, izbornik) selfjoin PHP PDO SQLite3</h1>' ;
 $title2 = 'Teme selfjoin'; // ibrowser tab txt
 $basecss=$cssd.$ds.'style00.css'; //'default' or $cssd.$ds.'style00.css';
 include ($gloresdir.'/hdr.php');
 //include ($cnfgd.$ds.'hdr.php');

 } // e n d _ _ c o n s t r u c t
 
 
 
 
 public function ctr() { // M A N A G E (R O U T I N G)
 // The value of $_REQUEST['cmd'] tells us what to do
 $cmd = isset($_REQUEST['cmd']) ? $_REQUEST['cmd'] : 'tbl';
 // frontend - user actions (backend in sqlitestudio) :
 switch ($cmd) {
 case 'delrec':
 $this->utl->phpjsmsgyn('Obrisati redak ?'
 , '?cmd=delrecyes&'.$this->idname.'='.$this->idval_url);
 break;
 case 'delrecyes': 
 //case 'delrec': 
 if ($this->idval_url) $this->delrec($this->idval_url);
 else $this->utl->phpjsmsg('Nije zadana šifra retka za brisanje !');
 $this->tbl();
 break;
 case 'frm_rpt': // read an self join r o w
 $this->frm_rpt();
 break;
 case 'frm_post': // display form to post self join r o w
 $this->frm_post();
 break;
 case 'crerec': // insert - i n s e r t posted self join r o w
 if ($this->valid()) { // if m s g e is v a l i d,
 $this->crerec(); // then i n s e r t it
 $this->tbl(); // and display self join list
 } else {
 $this->frm_post(); // otherwise, redisplay the posting form
 }
 break;
 case 'tbl': // show self join r o w s list
 default:
 $this->tbl();
 break;
 }
 }





 // F R O N T E N D - user actions :
 protected function crerec() { // crud1. FRONTEND A D D SELFJOIN ROW
 $parent_id = isset($_REQUEST['parent_id']) ?
 intval($_REQUEST['parent_id']) : 0;
 // Make sure m s g e doesn't change while we're working with it.
 $this->db->beginTransaction();
 $this->inTransaction = true;
 // is this m s g e a reply?
 if ($parent_id) {
 // get the thread, level, and thread_pos of the parent m s g e
 //"SELECT thread_id,level,thread_pos FROM $this->table WHERE $this->idname=?";
 $st = $this->db->prepare($this->cr_byid_nivo_orddet_inm);
 $st->execute(array($parent_id));
 $parent = $st->fetch();
 // a reply's level is one greater than its parent's
 $level = $parent['level'] + 1;
 // what's biggest thread_pos in this thread among m s g e s with the same parent? 
 //"SELECT MAX(thread_pos) FROM $this->table WHERE thread_id = ? AND parent_id = ?";
 $st = $this->db->prepare($this->cr_max_rbrdet_inm);
 $st->execute(array($parent['thread_id'], $parent_id));
 $thread_pos = $st->fetchColumn(0);
 // are there existing replies to this parent?
 if ($thread_pos) {
 // this thread_pos goes after the biggest existing one
 $thread_pos++;
 } else {
 // this is the first reply, so put it right after the parent
 $thread_pos = $parent['thread_pos'] + 1;
 }
 // increment thread_pos of all m s g e s in the thread that come after this one 
 //"UPDATE $this->table SET thread_pos = thread_pos + 1 WHERE thread_id = ? AND thread_pos >= ?";
 $st = $this->db->prepare($this->cu_incr_rbrdet_inm);
 $st->execute(array($parent['thread_id'], $thread_pos));
 // the new m s g e should be i n s. with the parent's thread_id
 $thread_id = $parent['thread_id'];
 } else {
 // m s g e is not reply, so it's the s t a r t of new t h r e a d
 $thread_id = $this->db->query(
 "SELECT MAX(thread_id) + 1 
 FROM $this->table")->fetchColumn(0);
 // If there are no rows yet, make sure we s t a r t at 1 for thread_id
 if (! $thread_id) {
 $thread_id = 1;
 }
 $level = 0;
 $thread_pos = 0;
 }
 // i n s e r t m s g e into DB. Using prepare() and execute() makes sure that all fields are properly quoted 
 //"INSERT INTO $this->table "
 //. "($this->idname, thread_id,parent_id, thread_pos, posted_on, level, author, subject, body) "
 //.'VALUES (?,?,?,?,?,?,?,?,?)';
 $st = $this->db->prepare($this->cc_mast_or_det);
 $st->execute(array(
null, $thread_id, $parent_id, $thread_pos, date('c'), $level
,$_REQUEST['author'], $_REQUEST['subject'],$_REQUEST['body']));
 // Commit all the operations
 $this->db->commit();
 $this->inTransaction = false;
 }
 
 
 
 
 
 
 
 protected function tbl() // crud2. D I S P L A Y H I E R A R C H Y
 { //print '<h2><a href="http://dev:8083/test/books/a01cookbook/tema.php">Teme (poruka-odgovor)</a></h2>';
 // provide a way to p o s t non-reply m s g e
 // IZBORNIK t b l - i c e :
 
 // t b l h d r r o w (action menu) :
 $lnk_addmaster = '<strong>'."<a class='btn' href='".$this->curpgurl
 ."?cmd=frm_post'>Otvoriti novu temu (nit, thread)</a>"
 .'</strong> ';
 print $lnk_addmaster;
 if ('1') print '&nbsp;&nbsp;&nbsp; Help = Klik Rbr.
 &nbsp;&nbsp;&nbsp;
 |...| = | bytes|idniti, rbruniti, nivo, id, idviši| '
 .'<hr/>';

 //order m s g s by their thread (thread_id) and their position within thread (thread_pos)
 $st = $this->db->query(
 "SELECT $this->idname, subject,author"
 .",LENGTH(body) body_length,posted_on,level,thread_id,thread_pos
 ,parent_id,url"
 ." FROM $this->table ORDER BY thread_id,thread_pos");
 
 
 while ($row = $st->fetch()) {
 
 $when = date('Y-m-d H:i', strtotime($row['posted_on']));
 
 // indent m s g e s with level > 0
 print '<font style="font-family: Courier;">'
 .str_repeat('&nbsp;',2 * $row['level']) .'</font>';

 // print info about m s g with
 // - link to open page eg http://dev1:8083/01apl/04tema/tema.php
 // - link to read it
 print
 // ----------- 1. thread_id (NIT) = ORD.NO :
 ( ($row['level'] == 0) // branch root
 ?
 //'<strong>'
 // Help (msg content report) = Klik Rbr :
 "<a href='" . $this->curpgurl
 . "?cmd=frm_rpt&amp;$this->idname={$row[$this->idname]}"
 ."'>"
 . '<span class="btnsmall">'
 .'<font style="color: black; font-family: Courier;">' 
 // background-color: red; 
 .str_repeat('&nbsp;', (6 - strlen($row['thread_id'])) )
 .$row['thread_id'] //.'</strong>'
 .'</font>'
 . '</span>'
 .'</a>'
 .'. ' 
 :
 // ----------- 2. thread_pos = ORD.NO IN THREAD - Help (msg content report) = Klik Rbr u niti :
 "<a href='" . $this->curpgurl
 . "?cmd=frm_rpt&amp;$this->idname={$row[$this->idname]}"
 ."'>"
 //.'&nbsp;&nbsp;'.$row['thread_pos'].'. ' .'</strong>'
 . '<span>'
 .'<font style="color: black; background-color: white; font-family: Courier;">' 
 .str_repeat('&nbsp;', (6 - strlen($row['thread_pos'])) )
 .$row['thread_pos'] //.'</strong>'
 .'</font>'
 . '</span>'
 .'</a>'
 .'. ' 
 
 
 )
 // Klik msg to open page :
 // eg http://dev1:8083/index.php?cmd=lsweb&dir=J:\awww\apl\dev1\test\01info
 ."<a href='" . htmlentities($row['url'],ENT_QUOTES)
 ."'>"
 . htmlentities($row['subject'],ENT_QUOTES)
 .'</strong>'
 . '</a> '
 . ' by '. htmlentities($row['author'],ENT_QUOTES) . ' @ '
 . htmlentities($when,ENT_QUOTES)
 ;
 // L I N K c m d = d e l r e c
 printf('<a href="%s?cmd=delrec&'.$this->idname.'=%s'
 //.'&'.'rrgo'.'=%s'
 .'">'.'%s</a>'
 , $this->curpgurl
 , $row[$this->idname]
 //, $pgrr1
 , '<font style="color: red;">'.' |'.'</font>'
 .'BRIŠI' // d e l e t e
 .'<font style="color: red;">'.'|'.'</font>'
 );
 print " {$row['body_length']} " // bytes|
 . " |PROMJ " // u p d a t e
 ;
if ('1') print "|{$row['thread_id']},{$row['thread_pos']},{$row['level']}
,{$row[$this->idname]},{$row['parent_id']}|";
print '<br/>';

 } // e n d p r i n t r o w s
 
 print '<hr/>'.$lnk_addmaster;
 } // e n d f n t b l ( )

 
 
 
 
 protected function frm_rpt() { // crud3. R O W REPORT (m s g b o d y...)
 // make sure the m s g e i d we are passed is an integer and really represents a m s g e 
 if (! isset($_REQUEST[$this->idname])) {
 throw new Exception('Nije formirana šifra poruke');
 }
 $id = intval($_REQUEST[$this->idname]);
 //"SELECT author,subject,body,posted_on FROM $this->table WHERE $this->idname = ?";
 $st = $this->db->prepare($this->cr_byid);
 $st->execute(array($id));
 $msg = $st->fetch();
 if (! $msg) {
 throw new Exception('Loša šifra poruke');
 }
 /* don't display user-entered HTML, but display newlines as
 HTML line breaks */
 $body = str_replace('{{/strong}}','</strong>',
 str_replace('{{strong}}','<strong>',
 nl2br(htmlentities($msg['body']))
 ));
 //
 while (false !== strpos($body, '{{url}}')) {
 $beg = strpos($body, '{{url}}');
 $end = strpos($body, '{{/url}}');
 $url = substr($body, $beg+7, $end - $beg -7);
 $url2 = '<a href="'.$url.'">'.$url.'</a>'; // </strong>
 $body = str_replace('{{url}}'.$url.'{{/url}}',$url2,$body);
 //$body .= '<br />'.$url2;
 }
 //
 // display m s g e with links to reply and return to the m s g e list
 $subject = htmlentities($msg['subject']);
 $author = htmlentities($msg['author']);
 // ---------------------------------------
 // h d r m e n u f r m _ v i e w - a
 // --------------------------------------
 print<<<_HTML_
 <h2>$subject</h2>
 <h3>
 by $author &nbsp; &nbsp;
 <a href="$this->curpgurl?cmd=frm_post&parent_id=$id">Odgovor</a>
 &nbsp; &nbsp; <a href="$this->curpgurl?cmd=tbl">Stablo poruka</a>
 </h3>
 <hr/>

 <p>$body</p>

_HTML_;
 } // tbl()

 
 
 
 
 
 protected function frm_post() // crud4. ENTER USER DATA & PROCESS IT
 {
 $safe = array();
 foreach (array('author','subject','body') as $field) {

 // escape input values :
 if (isset($_POST[$field])) {
 $safe[$field] = htmlentities($_POST[$field]);
 } else { $safe[$field] = ''; }

 // make err m s g s display in red :
 if (isset($this->form_errors[$field])) {
 $this->form_errors[$field] = '<span style="color: red">' .
 $this->form_errors[$field] . '</span><br/>';
 } else { $this->form_errors[$field] = ''; }
 } // e n d f o r e a c h


 // is this m s g e reply ?
 if (isset($_REQUEST['parent_id']) &&
 $parent_id = intval($_REQUEST['parent_id'])) {
 // send parent_id along when form is submitted
 $parent_field = sprintf(
 '<input type="hidden" 
 name="parent_id" 
 value="$this->idname" />'
 , $parent_id);
 // if no subject's been passed in, use parent's subject
 if (! strlen($safe['subject'])) {
 //"SELECT subject FROM $this->table WHERE $this->idname = ?";
 $st = $this->db->prepare($this->cr_byid_colname);
 $st->execute(array($parent_id));
 $parent_subject = $st->fetchColumn(0);
 /* prefix 'Re: ' to parent subject if it exists and
 doesn't already have 'Re:' */
 $safe['subject'] = htmlentities($parent_subject);
 if ( $parent_subject
 && (! preg_match('/^re:/i',$parent_subject)))
 { $safe['subject'] = "Re: {$safe['subject']}"; }
 }
 } else { $parent_field = ''; }


 // display posting form, with errors and default values
 print<<<_HTML_
 <form method="post" action="$this->curpgurl">
 <table>
 <tr>
 <td>Your Name:</td>
 <td>{$this->form_errors['author']}
 <input type="text" name="author" value="{$safe['author']}" />
 </td>
 <tr>
 <td>Subject:</td>
 <td>{$this->form_errors['subject']}
 <input type="text" name="subject" value="{$safe['subject']}" />
 </td>
 <tr>
 <td>Poruka:</td>
 <td>{$this->form_errors['body']}
 <textarea rows="4" cols="30" wrap="physical"
 name="body">{$safe['body']}</textarea>
 </td>
 <tr><td colspan="2"><input type="submit" value="Pošaljite poruku" /></td></tr>
 </table>
 $parent_field
 <input type="hidden" name="cmd" value="crerec" />
 </form>
_HTML_;
}



 // B A C K E N D - administrator actions :
 protected function delrec($parent_id) { // crud5 BACKEND D ELETE R OW
 //$parent_id = isset($_REQUEST[$this->idname]) ? intval($_REQUEST[$this->idname]) : 0;
// $this->idval_url
//basename(__FILE__).' SAYS'.'<br>'.'$id'.'=='.$parent_id.'=='
if ('') $this->utl->phpjsmsg('***** '.__FUNCTION__.'() SAYS: ' 
 .'<br>'.'$dml=***'.$this->cdel_row_byid.'***<br>'.'?=$parent_id=***'
 .$parent_id.'***');
 // is this m s g e a reply?
 if ($parent_id) {
 // Make sure m s g e doesn't change while we're working with it.
 $this->db->beginTransaction();
 $this->inTransaction = true;
 //"DELETE FROM $this->table WHERE $this->idname=?"; //$values[]=$parent_id;
 $st = $this->db->prepare($this->cdel_row_byid);
 //or $this->db->get_con()->prepare ?
 $st->execute(array($parent_id));
 // fetchAll() is needed only for s e l e c t 
 //Commit all the operations
 $this->db->commit();
 $this->inTransaction = false;
 } 
 }

 
 
 
 



 // 5. makes sure something is entered in each field :
 protected function valid() {
 $this->form_errors = array();
 // R E Q U I R E D U S E R D A T A :
 if (! (isset($_POST['author']) && strlen(trim($_POST['author'])))) {
 $this->form_errors['author'] = 'Upišite vaše ime (autor).';
 }
 if (! (isset($_POST['subject']) && strlen(trim($_POST['subject'])))) {
 $this->form_errors['subject'] = 'Upišite naslov poruke.';
 }
 if (! (isset($_POST['body']) && strlen(trim($_POST['body'])))) {
 $this->form_errors['body'] = 'Upišite tekst poruke.';
 }
 return (count($this->form_errors) == 0);
 }

 // 6.
 protected function logAndDie(Exception $e) {
 print 'ERROR: ' . htmlentities($e->getMessage());
 if ($this->db && $this->db->inTransaction()) {
 $this->db->rollback();
 }
 exit();
 }
 
 
 
 
 
} // e n d c l a s s



// <strong><a href="http://...">http://...</a></strong>
// if (false !== strpos($string, $substring)) { /* found it! */ }
// if (strpos($haystack, $needle) !== false) echo 'match!';
// $withoutCommas = is_numeric(str_replace(',', '', $number));

/*
ALTER TABLE message RENAME TO sqlitestudio_temp_table;

CREATE TABLE message (
 id INTEGER PRIMARY KEY AUTOINCREMENT,
 subject CHAR (255),
 url VARCHAR (500),
 thread_id [INT UNSIGNED] NOT NULL,
 thread_pos [INT UNSIGNED] NOT NULL,
 level [INT UNSIGNED] NOT NULL,
 parent_id [INT UNSIGNED] NOT NULL,
 author CHAR (255),
 body MEDIUMTEXT,
 posted_on DATETIME NOT NULL
);

INSERT INTO message (
id
,subject
,url
,thread_id
,thread_pos
,level
,parent_id
,author
,body
,posted_on
)
SELECT
id
,subject
,url
,thread_id
,thread_pos
,level
,parent_id
,author
,body
,posted_on
FROM sqlitestudio_temp_table;

DROP TABLE sqlitestudio_temp_table;




 TO INCLUDE RESULTS OF EXECUTING FN OR EXPRESSION WITHIN A STRING
 You can put vars, obj.prop, array el. (if subscript is unquoted) directly in double-quoted strings:

 eg print "You owe $amounts[payment] immediately.";
 eg print "My circle's diameter is $circle->diameter inches.";

 Curly braces around more complicated expressions to interpolate them into a string:

 print "I have {$children} children.";
 print "You owe {$amounts['payment']} immediately.";
 print "My circle's diameter is {$circle->getDiameter()} inches.";

 Direct interpolation or string concat. also works with heredocs:
 print <<< END
 Right now, the time is
 END
 . strftime('%c') . <<< END
 but tomorrow it will be
END
 . strftime('%c',time() + 86400);

 
 {{url}} http://dev1:8083/my_dev/pdo/tema/tema.php {{/url}}
 J:\awww\apl\dev1\my_dev\pdo\tema\tema.php
 J:\dev_web\htdocs\test\books\a01cookbook\tema.php


Promjene podataka programom J:\aplp\aplp\sqlitestudio\SQLiteStudio.exe

SELFJOIN TABLE :
 1. C INSERT frm data,
 2. R DISPLAY tbl, row,
 3. V VALIDATE, E set_exception_handler
Not neccessarily here:
U UPDATE and D DELETE WITH J:\aplp\aplp\sqlitestudio\SQLiteStudio.exe

*/