DC Blog Blog RSS Feed https://dcblog.dev composer install killed If you eve run composer install and terminal hands and then shows killed it means the server does not have enough memory to install the packages, this is common if you do not have a composer.lock file in the repository.

IF you do not have a composer.lock file in your respositoy then should:

ensure compoer.lock is not in a .gitignore file Run composer install in a local environment (either your local physical machine, or a development virtual machine)

With the updated composer.lock file commit the file and push it to your repository.

Then on the server git pull and then you should be able to do a composer install using the same versions as you've done locally.

]]>
Fri, 22 May 2020 07:38:26 GMT https://dcblog.dev/composer-install-killed https://dcblog.dev/composer-install-killed
Reversing and Re-ordering ol items in HTML I've just learned you can alter HTML ol items. You can reserver the order and change the starting number.

Thanks to @IMAC2 for this tip.

To reverse the order add <ol reversed>

To change the starting number add a start <ol start='15'> which tells HTML to start the number at 15.

]]>
Wed, 20 May 2020 08:46:31 GMT https://dcblog.dev/reversing-and-re-ordering-ol-items-in-html https://dcblog.dev/reversing-and-re-ordering-ol-items-in-html
MySQL 8 The server requested authentication method unknown to the client On a new installation of MySQL 8 you may come across the error below when running application.

The server requested authentication method unknown to the client

The simple fix is to go login to the MySQL shell:

mysql -uroot

then run:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '';

This updates the root user to be identified with no password (or the one you've specified).

]]>
Wed, 13 May 2020 23:50:02 GMT https://dcblog.dev/mysql-8-the-server-requested-authentication-method-unknown-to-the-client https://dcblog.dev/mysql-8-the-server-requested-authentication-method-unknown-to-the-client
Software/tools for self-publishing books I've recently published 2 books with Packt publishing which was an interesting experience but now I'm looking to start self-publishing.

My question is what software would you recommend/use for authoring books? I used Microsoft Word with Packt. Now I'm looking the following software looks promising:

  • Ulysses App
  • Bear App
  • Google Docs
  • Scrivener

I'm thinking something web-based may be best then I can write on any device.

Each of these has epub export formats not sure whether I should care about the export options before I have even started writing?

Ideally, I'd like a tool that can create a table of contents that links to the chapters, can export to epub, and can help in making a manuscript file.

Would love to hear what other people have used.

]]>
Thu, 07 May 2020 12:39:34 GMT https://dcblog.dev/softwaretools-for-self-publishing-books https://dcblog.dev/softwaretools-for-self-publishing-books
jQuery Fullcalender with PHP and MySQL

I'm a big fan of fullcalendar.io the latest version is quite a bit different from earlier versions, in this tutorial I'll cover how to integrate fullcalendar into a database with MySQL and load the events from PHP.

 

I've downloaded files from https://fullcalendar.io/docs/getting-started also I've added the following:

Bootstrap v4.4.1
jQuery v3.2.1
jQuery UI v1.10.3
Datepicker v1.6.3
Colorpicker

I've placed all the downloaded packages and the onces listed above in a folder called packages, Also I've got a folder called api where all the database files will live.

I'm going to use a PDO Wrapper for the database interactions to install it create a file called composer.json and add:

{
    "require": {
        "daveismyname/pdo-wrapper": "^1.1"
    }
}

Now run `composer install` in a terminal to install the wrapper. A vendor folder will be created.

Now create a config.php file and add:

<?php
require('vendor/autoload.php');

use Daveismyname\PdoWrapper\Database;

$host = "localhost";
$database = "calendar";
$username = "root";
$password = "";

$db = Database::get($username, $password, $database, $host);
$dir = "./";

First, we include the autoloader so any files from composer can be loaded.

Make an instance of the Database wrapper and set the database credentials.

Finally set the folder path $dir = "./" points to the current folder, you can make this absolute if you prefer.

Create a table called events with the following structure:

CREATE TABLE `events` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` text,
  `start_event` datetime NOT NULL,
  `end_event` datetime NOT NULL,
  `color` varchar(191) DEFAULT NULL,
  `text_color` varchar(191) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

You can import the db.sql file provided on my GitHub repo or run this in your MySQL program of choice.

Create a file called index.php this is where the calender will be used.

Include the config and the packages:

<?php require('config.php');?>
<!DOCTYPE html>
<html>
<head>
    <title>Calandar</title>
    
    <link href='<?=$dir;?>packages/core/main.css' rel='stylesheet' />
    <link href='<?=$dir;?>packages/daygrid/main.css' rel='stylesheet' />
    <link href='<?=$dir;?>packages/timegrid/main.css' rel='stylesheet' />
    <link href='<?=$dir;?>packages/list/main.css' rel='stylesheet' />
    <link href='<?=$dir;?>packages/bootstrap/css/bootstrap.css' rel='stylesheet' />
    <link href="<?=$dir;?>packages/jqueryui/custom-theme/jquery-ui-1.10.4.custom.min.css" rel="stylesheet">
    <link href='<?=$dir;?>packages/datepicker/datepicker.css' rel='stylesheet' />
    <link href='<?=$dir;?>packages/colorpicker/bootstrap-colorpicker.min.css' rel='stylesheet' />
    <link href='<?=$dir;?>style.css' rel='stylesheet' />

    <script src='<?=$dir;?>packages/core/main.js'></script>
    <script src='<?=$dir;?>packages/daygrid/main.js'></script>
    <script src='<?=$dir;?>packages/timegrid/main.js'></script>
    <script src='<?=$dir;?>packages/list/main.js'></script>
    <script src='<?=$dir;?>packages/interaction/main.js'></script>
    <script src='<?=$dir;?>packages/jquery/jquery.js'></script>
    <script src='<?=$dir;?>packages/jqueryui/jqueryui.min.js'></script>
    <script src='<?=$dir;?>packages/bootstrap/js/bootstrap.js'></script>
    <script src='<?=$dir;?>packages/datepicker/datepicker.js'></script>
    <script src='<?=$dir;?>packages/colorpicker/bootstrap-colorpicker.min.js'></script>
    <script src='<?=$dir;?>calendar.js'></script>
</head>
<body>

The calendar.js is where all the calendar functions will go.

Now add the calender markup:

<div class="container">

    <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addeventmodal">
      Add Event
    </button>

    <div id="calendar"></div>
</div>

</body>
</html>

By default the calender will attached to an element by its id.

Also since we're using bootstrap we can make use of its modals to load popup windows for adding and editing events.

the markup for these:

<div class="modal fade" id="addeventmodal" tabindex="-1" role="dialog">
    <div class="modal-dialog">
        <div class="modal-content">

            <div class="modal-header">
                <h5 class="modal-title">Add Event</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>

            <div class="modal-body">

                <div class="container-fluid">

                    <form id="createEvent" class="form-horizontal">

                    <div class="row">

                        <div class="col-md-6">

                            <div id="title-group" class="form-group">
                                <label class="control-label" for="title">Title</label>
                                <input type="text" class="form-control" name="title">
                                <!-- errors will go here -->
                            </div>

                            <div id="startdate-group" class="form-group">
                                <label class="control-label" for="startDate">Start Date</label>
                                <input type="text" class="form-control datetimepicker" id="startDate" name="startDate">
                                <!-- errors will go here -->
                            </div>

                            <div id="enddate-group" class="form-group">
                                <label class="control-label" for="endDate">End Date</label>
                                <input type="text" class="form-control datetimepicker" id="endDate" name="endDate">
                                <!-- errors will go here -->
                            </div>

                        </div>

                        <div class="col-md-6">

                            <div id="color-group" class="form-group">
                                <label class="control-label" for="color">Colour</label>
                                <input type="text" class="form-control colorpicker" name="color" value="#6453e9">
                                <!-- errors will go here -->
                            </div>

                            <div id="textcolor-group" class="form-group">
                                <label class="control-label" for="textcolor">Text Colour</label>
                                <input type="text" class="form-control colorpicker" name="text_color" value="#ffffff">
                                <!-- errors will go here -->
                            </div>

                        </div>

                    </div>

                    

                </div>

            </div>

            <div class="modal-footer">
              <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
              <button type="submit" class="btn btn-primary">Save changes</button>
            </div>

            </form>

        </div><!-- /.modal-content -->
    </div><!-- /.modal-dialog -->
</div><!-- /.modal -->

<div class="modal fade" id="editeventmodal" tabindex="-1" role="dialog">
    <div class="modal-dialog">
        <div class="modal-content">

            <div class="modal-header">
                <h5 class="modal-title">Update Event</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>

            <div class="modal-body">

                <div class="container-fluid">

                    <form id="editEvent" class="form-horizontal">
                    <input type="hidden" id="editEventId" name="editEventId" value="">

                    <div class="row">

                        <div class="col-md-6">

                            <div id="edit-title-group" class="form-group">
                                <label class="control-label" for="editEventTitle">Title</label>
                                <input type="text" class="form-control" id="editEventTitle" name="editEventTitle">
                                <!-- errors will go here -->
                            </div>

                            <div id="edit-startdate-group" class="form-group">
                                <label class="control-label" for="editStartDate">Start Date</label>
                                <input type="text" class="form-control datetimepicker" id="editStartDate" name="editStartDate">
                                <!-- errors will go here -->
                            </div>

                            <div id="edit-enddate-group" class="form-group">
                                <label class="control-label" for="editEndDate">End Date</label>
                                <input type="text" class="form-control datetimepicker" id="editEndDate" name="editEndDate">
                                <!-- errors will go here -->
                            </div>

                        </div>

                        <div class="col-md-6">

                            <div id="edit-color-group" class="form-group">
                                <label class="control-label" for="editColor">Colour</label>
                                <input type="text" class="form-control colorpicker" id="editColor" name="editColor" value="#6453e9">
                                <!-- errors will go here -->
                            </div>

                            <div id="edit-textcolor-group" class="form-group">
                                <label class="control-label" for="editTextColor">Text Colour</label>
                                <input type="text" class="form-control colorpicker" id="editTextColor" name="editTextColor" value="#ffffff">
                                <!-- errors will go here -->
                            </div>

                        </div>

                    </div>

                </div>

            </div>

            <div class="modal-footer">
              <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
              <button type="submit" class="btn btn-primary">Save changes</button>
              <button type="button" class="btn btn-danger" id="deleteEvent" data-id>Delete</button>
            </div>

            </form>

        </div><!-- /.modal-content -->
    </div><!-- /.modal-dialog -->
</div><!-- /.modal -->

These both have forms to add and edit the event.

Now lets move on to the JS create a file called calendar.js

add an event listener so the code will only run once the page is fully loaded:

document.addEventListener('DOMContentLoaded', function() {

    var url ='/';

Now we will setup a class for the datepicker to attach to

$('body').on('click', '.datetimepicker', function() {
    $(this).not('.hasDateTimePicker').datetimepicker({
        controlType: 'select',
        changeMonth: true,
        changeYear: true,
        dateFormat: "dd-mm-yy",
        timeFormat: 'HH:mm:ss',
        yearRange: "1900:+10",
        showOn:'focus',
        firstDay: 1
    }).focus();
});

And the color picker

$(".colorpicker").colorpicker();

Now setup a variable for the calendar to attach to a div with an id of calendar

var calendarEl = document.getElementById('calendar');

Now for the FullCalendar 

var calendar = new FullCalendar.Calendar(calendarEl, {

Inside FullCalendar place the calendar options:

plugins: ['interaction', 'dayGrid', 'timeGrid', 'list'],
header: {
    left: 'prev,next today',
    center: 'title',
    right: 'dayGridMonth,timeGridWeek,timeGridDay,listMonth'
},
navLinks: true, // can click day/week names to navigate views
businessHours: true, // display business hours
editable: true,

to load the events we need to call events:

events: url+'api/load.php',

This sends a request to load.php which will return a JSON array of all the events, I'll cover the JS functions first then go over the PHP code.

Now can call 3 additional options, eventDrop, eventResize and eventClick

eventDrop is triggered when an event is dragged and dropped to another date or time.

We collect the start and end dates and then send an ajax request to update.php and pass the id of the event and the start and end time.

eventDrop: function(arg) {
    var start = arg.event.start.toDateString()+' '+arg.event.start.getHours()+':'+arg.event.start.getMinutes()+':'+arg.event.start.getSeconds();
    if (arg.event.end == null) {
        end = start;
    } else {
        var end = arg.event.end.toDateString()+' '+arg.event.end.getHours()+':'+arg.event.end.getMinutes()+':'+arg.event.end.getSeconds();
    }

    $.ajax({
      url:url+"api/update.php",
      type:"POST",
      data:{id:arg.event.id, start:start, end:end},
    });
}

eventReize is very similar, this is triggered when an event is manually resized from a week or day view.

eventResize: function(arg) {
    var start = arg.event.start.toDateString()+' '+arg.event.start.getHours()+':'+arg.event.start.getMinutes()+':'+arg.event.start.getSeconds();
    var end = arg.event.end.toDateString()+' '+arg.event.end.getHours()+':'+arg.event.end.getMinutes()+':'+arg.event.end.getSeconds();

    $.ajax({
      url:url+"api/update.php",
      type:"POST",
      data:{id:arg.event.id, start:start, end:end},
    });
}

eventClick is triggered when an event is clicked on, at this point we want to collect the id of the event and then do an ajax call to get all the event details, add them to a model and show the modal. Also check if a delete button has been pressed on the modal and if so send an ajax event to delete the entry.

eventClick: function(arg) {
    var id = arg.event.id;
    
    $('#editEventId').val(id);
    $('#deleteEvent').attr('data-id', id); 

    $.ajax({
      url:url+"api/getevent.php",
      type:"POST",
      dataType: 'json',
      data:{id:id},
      success: function(data) {
            $('#editEventTitle').val(data.title);
            $('#editStartDate').val(data.start);
            $('#editEndDate').val(data.end);
            $('#editColor').val(data.color);
            $('#editTextColor').val(data.textColor);
            $('#editeventmodal').modal();
        }
    });

    $('body').on('click', '#deleteEvent', function() {
        if(confirm("Are you sure you want to remove it?")) {
            $.ajax({
                url:url+"api/delete.php",
                type:"POST",
                data:{id:arg.event.id},
            }); 

            //close model
            $('#editeventmodal').modal('hide');

            //refresh calendar
            calendar.refetchEvents();         
        }
    });
    
    calendar.refetchEvents();
}

On the model for adding an event we need to send the form data to PHP and process the response, we do this by sending an ajax call:

$('#createEvent').submit(function(event) {

    // stop the form refreshing the page
    event.preventDefault();

    $('.form-group').removeClass('has-error'); // remove the error class
    $('.help-block').remove(); // remove the error text

    // process the form
    $.ajax({
        type        : "POST",
        url         : url+'api/insert.php',
        data        : $(this).serialize(),
        dataType    : 'json',
        encode      : true
    }).done(function(data) {

        // insert worked
        if (data.success) {
            
            //remove any form data
            $('#createEvent').trigger("reset");

            //close model
            $('#addeventmodal').modal('hide');

            //refresh calendar
            calendar.refetchEvents();

        } else {

            //if error exists update html
            if (data.errors.date) {
                $('#date-group').addClass('has-error');
                $('#date-group').append('<div class="help-block">' + data.errors.date + '</div>');
            }

            if (data.errors.title) {
                $('#title-group').addClass('has-error');
                $('#title-group').append('<div class="help-block">' + data.errors.title + '</div>');
            }

        }

    });
});

The process for editing an event is similar:

$('#editEvent').submit(function(event) {

    // stop the form refreshing the page
    event.preventDefault();

    $('.form-group').removeClass('has-error'); // remove the error class
    $('.help-block').remove(); // remove the error text

    //form data
    var id = $('#editEventId').val();
    var title = $('#editEventTitle').val();
    var start = $('#editStartDate').val();
    var end = $('#editEndDate').val();
    var color = $('#editColor').val();
    var textColor = $('#editTextColor').val();

    // process the form
    $.ajax({
        type        : "POST",
        url         : url+'api/update.php',
        data        : {
            id:id, 
            title:title, 
            start:start,
            end:end,
            color:color,
            text_color:textColor
        },
        dataType    : 'json',
        encode      : true
    }).done(function(data) {

        // insert worked
        if (data.success) {
            
            //remove any form data
            $('#editEvent').trigger("reset");

            //close model
            $('#editeventmodal').modal('hide');

            //refresh calendar
            calendar.refetchEvents();

        } else {

            //if error exists update html
            if (data.errors.date) {
                $('#date-group').addClass('has-error');
                $('#date-group').append('<div class="help-block">' + data.errors.date + '</div>');
            }

            if (data.errors.title) {
                $('#title-group').addClass('has-error');
                $('#title-group').append('<div class="help-block">' + data.errors.title + '</div>');
            }

        }

    });
});

At this point we have the calender and all the ajax calls next we need PHP to process the requests.

Create a folder called api and the following files:

delete.php
getevent.php
insert.php
load.php
update.php

delete.php

Check if an id exists in the POST request and if do then attempt to delete the event where the id posted matched an id from the events table.

include("../config.php");

if (isset($_POST["id"])) {
    $db->delete('events', ['id' => $_POST['id']]);
}

getevent

Check if an id exists in the POST request then load the event that matched the id and then create a data array matching the structure the calendar needs and finally return the array as JSON.

<?php
include("../config.php");

if (isset($_POST['id'])) {
    $row = $db->find("* FROM events where id=?", [$_POST['id']]);
    $data = [
        'id'        => $row->id,
        'title'     => $row->title,
        'start'     => date('d-m-Y H:i:s', strtotime($row->start_event)),
        'end'       => date('d-m-Y H:i:s', strtotime($row->end_event)),
        'color'     => $row->color,
        'textColor' => $row->text_color
    ];

    echo json_encode($data);
}

insert.php

here we validate the data meets our requirements by ensuring the title, start and end are not empty then format the data before inserting the data. We always return JSON data to the user can be show validation errors or to close the model on success.

<?php
include("../config.php");

if (isset($_POST['title'])) {

    //collect data
    $error      = null;
    $title      = $_POST['title'];
    $start      = $_POST['startDate'];
    $end        = $_POST['startDate'];
    $color      = $_POST['color'];
    $text_color = $_POST['text_color'];

    //validation
    if ($title == '') {
        $error['title'] = 'Title is required';
    }

    if ($start == '') {
        $error['start'] = 'Start date is required';
    }

    if ($end == '') {
        $error['end'] = 'End date is required';
    }

    //if there are no errors, carry on
    if (! isset($error)) {

        //format date
        $start = date('Y-m-d H:i:s', strtotime($start));
        $end = date('Y-m-d H:i:s', strtotime($end));
        
        $data['success'] = true;
        $data['message'] = 'Success!';

        //store
        $insert = [
            'title'       => $title,
            'start_event' => $start,
            'end_event'   => $end,
            'color'       => $color,
            'text_color'  => $text_color
        ];
        $db->insert('events', $insert);
      
    } else {

        $data['success'] = false;
        $data['errors'] = $error;
    }

    echo json_encode($data);
}

load.php

this file loads all the events for the calendar to read

<?php
include("../config.php");
$data = [];

$result = $db->select("* FROM events ORDER BY id");
foreach($result as $row) {
    $data[] = [
        'id'              => $row->id,
        'title'           => $row->title,
        'start'           => $row->start_event,
        'end'             => $row->end_event,
        'backgroundColor' => $row->color,
        'textColor'       => $row->text_color
    ];
}

echo json_encode($data);

update.php

this again will validate all required fields have been entered and then perform an update 

<?php
include("../config.php");

if (isset($_POST['id'])) {

    //collect data
    $error      = null;
    $id         = $_POST['id'];
    $start      = $_POST['start'];
    $end        = $_POST['end'];

    //optional fields
    $title      = isset($_POST['title']) ? $_POST['title']: '';
    $color      = isset($_POST['color']) ? $_POST['color']: '';
    $text_color = isset($_POST['text_color']) ? $_POST['text_color']: '';

    //validation
    if ($start == '') {
        $error['start'] = 'Start date is required';
    }

    if ($end == '') {
        $error['end'] = 'End date is required';
    }

    //if there are no errors, carry on
    if (! isset($error)) {

        //reformat date
        $start = date('Y-m-d H:i:s', strtotime($start));
        $end = date('Y-m-d H:i:s', strtotime($end));
        
        $data['success'] = true;
        $data['message'] = 'Success!';

        //set core update array
        $update = [
            'start_event' => date('Y-m-d H:i:s', strtotime($_POST['start'])),
            'end_event' => date('Y-m-d H:i:s', strtotime($_POST['end']))
        ];

        //check for additional fields, and add to $update array if they exist
        if ($title !='') {
            $update['title'] = $title;
        }

        if ($color !='') {
            $update['color'] = $color;
        }

        if ($text_color !='') {
            $update['text_color'] = $text_color;
        }

        //set the where condition ie where id = 2
        $where = ['id' => $_POST['id']];

        //update database
        $db->update('events', $update, $where);
      
    } else {

        $data['success'] = false;
        $data['errors'] = $error;
    }

    echo json_encode($data);
}

At this point, events can be added, edit, delete and so dragged to other days. 

I'd recommend looking at the complete source code available on:

]]>
Tue, 14 Apr 2020 10:54:30 GMT https://dcblog.dev/jquery-fullcalender-with-php-and-mysql https://dcblog.dev/jquery-fullcalender-with-php-and-mysql
MySQL categories and subcategories Working with categories is a common task, also is the need for using subcategories, in this tutorial I will explain how to design a database schema to support both categories and subcategories from a single table. Let's start with the schema:

CREATE TABLE `categories` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `parent_id` int(11) NOT NULL DEFAULT '0',
  `category` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

In order to store categories and subcategories together, we need a way to determine what categories are the subcategories. This can be accomplished by the parent_id column, all root categories will have a parent_id of 0. The subcategories will have a parent_id that matches the id of the parent category.

Let's add some data to make this clearer.

INSERT INTO `categories` (`id`, `parent_id`, `category`) VALUES
(1, 0, 'General'),
(2, 0, 'PHP'),
(3, 0, 'HTML'),
(4, 3, 'Tables'),
(5, 2, 'Functions'),
(6, 2, 'Variables'),
(7, 3, 'Forms');

Here we have 3 categories (General, PHP & HTML) and 4 subcategories.

an easier way to read this:

From this image, we can see that Tables is a subcategory of HTML we can tell as it's parent_id is 3 and HTML has an id of 3.

Now we can see how categories and subcategories are stored let's put this into practice.

I'll use PDO for these examples:

Connect to database:

$host = "localhost";
$database = "categories";
$username = "root";
$password = "";

$db = new PDO("mysql:host=$host;dbname=$database", $username, $password);

//turn on exceptions
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

//set default fetch mode
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);

Now select all root categories:

$categories = $db->query('SELECT id, parent_id, category FROM categories WHERE parent_id = 0 ORDER BY category');

Now loop over the categories and print the title, next perform another query to get to the subcategories based on the parent_id and loop over them.

<ul>
<?php
foreach($categories->fetchAll() as $row) {

    echo "<li>$row->category</li>";

    //get child categories
    $children = $db->prepare('SELECT id, parent_id, category FROM categories WHERE parent_id = ? ORDER BY category');
    $children->execute([$row->id]);

    //determine if there are child items
    $hasChildren = $children->rowCount() > 0 ? true : false;

    if ($hasChildren) {
        echo "<ul>";
    }

    foreach($children->fetchAll() as $child) {
        echo "<li>$child->category</li>";
    }

    if ($hasChildren) {
        echo "</ul>";
    }
     
}
?>
</ul>

One issue this setup is if there are another level of subcategories they would not be displayed unless another loop is added.

Let's tackle this, select all categories regardless of the parent. Add all the results as an array and pass them to a function called generateTree.

$categories = $db->query('SELECT id, parent_id, category FROM categories ORDER BY category');
$rows = $categories->fetchAll(PDO::FETCH_ASSOC);
echo generateTree($rows);

This function will loop itself and show the title, and will recall the function for the depth needed until all loops have finished.

function generateTree($data, $parent = 0, $depth=0)
{
    $tree = "<ul>\n";
    for ($i=0, $ni=count($data); $i < $ni; $i++) {
        if ($data[$i]['parent_id'] == $parent) {    
            
            $tree .= "<li>\n";
            $tree .= $data[$i]['category'];
            $tree .= generateTree($data, $data[$i]['id'], $depth+1);
            $tree .= "</li>\n";
        }
    }
    $tree .= "</ul>\n";
    return $tree;
}

Which outputs the following based on this data:

INSERT INTO `categories` (`id`, `parent_id`, `category`) VALUES
(1, 0, 'General'),
(2, 0, 'PHP'),
(3, 0, 'HTML'),
(4, 3, 'Tables'),
(5, 2, 'Functions'),
(6, 2, 'Variables'),
(7, 3, 'Forms'),
(8, 5, 'sub 1'),
(9, 8, 'sub 2');
  • General
  • HTML
    • Forms
    • Tables
  • PHP
    • Functions
    • Variables
  • General
  • HTML
    • Forms
    • Tables
  • PHP
    • Functions
      • sub 1
        • sub 2
    • Variables
]]>
Sun, 12 Apr 2020 21:34:33 GMT https://dcblog.dev/mysql-categories-and-subcategories https://dcblog.dev/mysql-categories-and-subcategories
Writing to an existing PDF with FPDI FPDI allows existing PDF's to be used as a template for a new PDF. Whilst this does not give the ability to edit a PDF it can import one and add to it.

The FPDI extends FPDF so a copy of the latest version will be needed to use, place it in the same directory or update paths to it as needed.

It's recommended to use composer to install the library, you can do it like this:

In a composer.json file add the dependencies:

{
    "require": {
        "setasign/fpdf": "1.8.*",
        "setasign/fpdi": "^2.0"
    }
}

Then do a composer install in a terminal this will generate a vendor folder where the libraries are installed into.

Here's an example with comments.

<?php
require_once('vendor/autoload.php');

use setasign\Fpdi\Fpdi;

// initiate FPDI
$pdf = new Fpdi();

// add a page
$pdf->AddPage();

// set the source file
$pdf->setSourceFile("demo.pdf");

// import page 1
$tplId = $pdf->importPage(1);

// use the imported page and place it at point 10,10 with a width of 100 mm
$pdf->useTemplate($tplId);

// The new content
$fontSize = '15';
$fontColor = `255,0,0`;
$left = 16;
$top = 40;
$text = 'Sample Text over overlay';

//set the font, colour and text to the page.
$pdf->SetFont("helvetica", "B", 15);
$pdf->SetTextColor($fontColor);
$pdf->Text($left,$top,$text);

//see the results
$pdf->Output();  

There are no built in options to get the page numbers so if you want to add them you need to know how many pages the PDF has then loop over them like this:

//optionally add additional pages, start the $i after the current page ie start from 2
for ($i=2; $i < 6; $i++) {
    $pdf->AddPage();
    $tplId = $pdf->importPage($i);
    $pdf->useTemplate($tplId);
}

 

]]>
Fri, 10 Apr 2020 19:00:35 GMT https://dcblog.dev/writing-to-an-existing-pdf-with-fpdi https://dcblog.dev/writing-to-an-existing-pdf-with-fpdi
Easily assign variable when using explode in PHP I use explode all the time, for splitting strings into parts. I typically write my explodes like this:

//set the string
$name = 'Joe Bloggs';

//explode where there is a space
$parts = explode(' ', $name);

//add the parts to variables
$firstName = $parts[0];
$lastName = $parts[1];

But I've recently found out you can assign variables during the explode. Really handy!

This is how do you do it. Using an array to specify the name variables then assign them a value from explode:

//set the string
$name = 'Joe Bloggs';

//explode where there is a space and assign the indexes to named variables
[$firstName, $lastName] = explode(' ', $name);

A must cleaner way in my opinion.

]]>
Fri, 10 Apr 2020 16:04:34 GMT https://dcblog.dev/easily-assign-variable-when-using-explode-in-php https://dcblog.dev/easily-assign-variable-when-using-explode-in-php
Latitude and Longitude with Google Maps Google Maps allows using its service to get map coordinates:

For instance, In your HTML, two fields to store the latitude and longitude 

<input id="latitude" type="text" name="latitude" value="">
<input id="longitude" type="text" name="longitude" value="">

When a postcode is entered use Google Maps to update the latitude and longitude:

<input id="postcode" type="text" name="postcode" value="">

The JS code, note the <?=$apiKey;?> should contain your Google Maps API key.

<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=<?=$apiKey;?>"></script>
<script type="text/javascript">
    $(document).on('change', '#postcode', function (e) {
        var geocoder = new google.maps.Geocoder();
        var address = this.value;
        geocoder.geocode({ 'address': address }, function (results, status) {
            if (status == google.maps.GeocoderStatus.OK) {
                var latitude = results[0].geometry.location.lat();
                var longitude = results[0].geometry.location.lng();

                //set the value of input elements with an id of latitude and longitude
                document.getElementById("latitude").value = latitude;
                document.getElementById("longitude").value = longitude;
            } 
        });
    });
</script>

 

]]>
Thu, 12 Mar 2020 18:33:42 GMT https://dcblog.dev/latitude-and-longitude-with-google-maps https://dcblog.dev/latitude-and-longitude-with-google-maps
Laravel Merge PDFs

Ever needed to merge multiple PDF's together? It's a common need. In this tutorial, I'll cover how to do merge multiple pdf's together in Laravel.

First, we need a package called lara-pdf-merger Install it with composer:

composer require daltcore/lara-pdf-merger

Next, create import the namespace:

use LynX39\LaraPdfMerger\Facades\PdfMerger;

Create a new instance:

$pdfMerger = PDFMerger::init();

Add PDF to be merged:

$pdfMerger->addPDF(base_path('Modules/Quotes/pages/1.pdf'), 'all');

Do this for each PDF to be merged.

$pdfMerger->addPDF(base_path('Modules/Quotes/pages/1.pdf'), 'all');
$pdfMerger->addPDF(base_path('Modules/Quotes/pages/2.pdf'), 'all');
$pdfMerger->addPDF(base_path('Modules/Quotes/pages/3.pdf'), 'all');
$pdfMerger->addPDF(base_path('Modules/Quotes/pages/4.pdf'), 'all');

Next, call a merge method to perform the merge.

$pdfMerger->merge();

Next, save the PDF to disk

$pdfMerger->save(public_path('quotes/001.pdf'), "file");

Putting this all together:

$pdfMerger = PDFMerger::init(); //Initialize the merger
$pdfMerger->addPDF(base_path('Modules/Quotes/pages/1.pdf'), 'all');
$pdfMerger->addPDF(base_path('Modules/Quotes/pages/2.pdf'), 'all');
$pdfMerger->addPDF(base_path('Modules/Quotes/pages/3.pdf'), 'all');
$pdfMerger->addPDF(base_path('Modules/Quotes/pages/4.pdf'), 'all');
$pdfMerger->merge();
$pdfMerger->save(public_path('quotes/001.pdf'), "file");

Generate a PDF and merge with existing

You may want to generate new PDF and merge that in with existing PDFs.

Let's install Laravel DOMPDF to make a new PDF. Install with composer:

composer require barryvdh/laravel-dompdf

Import PDF:

use PDF;

Create a new PDF, load a view file (quotes/pdf.blade.php) for the contents of the PDF and save to a quotes folder and use the filename quote-001.pdf

$filename = "quote-001.pdf";
$pdf = PDF::loadView('quotes.pdf', ['quote' => $data]);
$pdf->save('quotes/'.$filename);

Now, this can be merged using the same technique above.

$filename = "quote-001.pdf";
$pdf = PDF::loadView('quotes/pdf', ['quote' => $record]);
$pdf->save('quotes/'.$filename);

$pdfMerger = PDFMerger::init(); //Initialize the merger
$pdfMerger->addPDF(base_path('Modules/Quotes/pages/1.pdf'), 'all');
$pdfMerger->addPDF(base_path('Modules/Quotes/pages/2.pdf'), 'all');
$pdfMerger->addPDF(base_path('Modules/Quotes/pages/3.pdf'), 'all');
$pdfMerger->addPDF(base_path('Modules/Quotes/pages/4.pdf'), 'all');
$pdfMerger->addPDF(public_path('quotes/'.$filename), 'all');
$pdfMerger->addPDF(base_path('Modules/Quotes/pages/5.pdf'), 'all');
$pdfMerger->merge();
$pdfMerger->save(public_path('quotes/quote-001.pdf'), "file");

 

]]>
Mon, 02 Mar 2020 07:14:02 GMT https://dcblog.dev/laravel-merge-pdfs https://dcblog.dev/laravel-merge-pdfs
Laravel API change unauthenticated message

When making API calls to Laravel when a user who is not authenticated makes a call a 401 status code is returned and the following response:

{"message":"Unauthenticated."}

Which I find a little strange it's an error so message should say error in my opinion.

This can easily be changed by adding a customer unauthenticated method to app/Exceptions/Handler.php:

protected function unauthenticated($request, AuthenticationException $exception) 
{
    if ($request->expectsJson()) {
        return response()->json(['error' => 'Unauthenticated.'], 401);
    }

    return redirect()->guest('login');
}

Now the response will say error instead of message.

]]>
Sat, 29 Feb 2020 21:24:27 GMT https://dcblog.dev/laravel-api-change-unauthenticated-message https://dcblog.dev/laravel-api-change-unauthenticated-message
Laravel returns 302 for unauthenticated calls

I've started to secure API calls with Laravel Airlock, when calling a route that's been secured with the middleware auth:airlock unauthenticated calls we're returning a status code of 302 means the route has been found. When 401 unauthenticated should have been returned.

Turns out it was not a Laravel Airlock issue but how Laravel handles ajax calls in order for an API call to be treated as an API call it requires a header called Accept and a value of application/json. With this heading in place, 401 will be returned for unauthenticated calls.

]]>
Sat, 29 Feb 2020 21:15:09 GMT https://dcblog.dev/laravel-returns-302-for-unauthenticated-calls https://dcblog.dev/laravel-returns-302-for-unauthenticated-calls
Upgrade Laravel Valet to PHP 7.4

First you should upgrade your machine to PHP 7.4, it you haven't checkout this great guide from Brent 

the short version is to do these steps using homebrew:

Update Brew

brew update

Next upgrade php

brew upgrade php

At this point your machine should be using PHP 7.4 but Laravel Valet may still be using PHP 7.3.

Upgrade Valet:

Update composer

composer global update

Next update Valet

valet install

Now to tell valet to use the latest version of PHP

valet use php

this tells valet to unlink php 7.3 or whichever version you have and use the latest version i.e 7.4

Or you can specify the version to use:

valet use php@7.4

 

]]>
Tue, 11 Feb 2020 07:23:33 GMT https://dcblog.dev/upgrade-laravel-valet-to-php-74 https://dcblog.dev/upgrade-laravel-valet-to-php-74
How to integrate PayPal into PHP

In this post, I’ll explain how to use PayPal for one-off payment and how to use the Instant Payment Notification (IPN) to process payments on your website.

When testing you don’t want to actually spend any money, this is where sandbox accounts come in. Sandbox accounts lets you simulate transactions without actually spending money everything is simulated. The sandbox accounts lets you run through the entire transaction when it comes time to change to real accounts its a case of changing the account and URLs used, a quick change to make.

Sandbox  Accounts

Sign in to your PayPal account into PayPal Developer [Sandbox accounts - PayPal Developer](https://developer.paypal.com/developer/accounts/) 

Under Sandbox in the left sidebar click on Accounts, here you will see your automatically generated sandbox accounts if you don’t have any click create an account.

The default accounts are 1 personal and 1 business. These are perfect for testing transactions with PayPal.

You use the personal account to test payments to the business account. To get the password for each click on the ellipse on the far right and click on view/edit account.

PayPal Form

To send payment data to a PayPal checkout page you can use a form, there’s a lot of details on PayPal’s docs [how do-i-add-paypal-checkout-to-my-custom-shopping-cart](https://www.paypal.com/uk/smarthelp/article/how-do-i-add-paypal-checkout-to-my-custom-shopping-cart-ts1200)

The form posts to of https://www.sandbox.paypal.com/cgi-bin/webscr for sandbox accounts or for normal payments use https://www.paypal.com/cgi-bin/webscr

If you are sending multiple items then use the cart options:

<input type="hidden" name="cmd" value="_cart" />
<input type="hidden" name="upload" value="1" />

For single items only then use 

<input type=“hidden” name=“cmd” value="_xclick">

Having said this I default to using cart it can be used to single and multiple items so keep your code consistent.

To link the payment your PayPal business account enter the account email address to:

<input type="hidden" name="business" value="email@business.example.com" />

Core form data:

<form action="https://www.sandbox.paypal.com/cgi-bin/webscr" method="post" />
<input type="hidden" name="cmd" value="_cart" />
<input type="hidden" name="upload" value="1" />
<input type="hidden" name="business" value="email@business.example.com" />

Additional options:

Set the currency:

<input type="hidden" name="currency_code" value="GBP" />
<input type="hidden" name="lc" value="UK" />
<input type="hidden" name="rm" value="2" />

Set a return url, this will be where the user is directed to after completing a payment.

The cancel url will be where the user is directed after canceling a payment.

The notify url is where Instant Payment Notifications (IPN) are sent to after payment

<input type="hidden" name="return" value="https://domain.com/thankyou" />
<input type="hidden" name="cancel_return" value="https://domain.com/cancel" />
<input type="hidden" name="notify_url" value="https://domain.com/ipn" />
<input type="hidden" name="charset" value="utf-8" />

Adding items

Each item will have a number on the end for example imte_name_1 is the first item another item would have item_name_2 and so on:

<input type="hidden" name="item_name_1" value="PC" />
<input type="hidden" name="item_number_1" value="0045" />
<input type="hidden" name="amount_1" value="500.00" />
<input type="hidden" name="quantity_1" value="1" />

For the pay button you can use PayPal’s image:

<input style="margin-top:10px;" type="image" src="http://www.paypal.com/en_US/i/btn/x-click-but01.gif" name="submit" alt="Make payments with PayPal - it's fast, free and secure!">
</form>

Putting it all together:

<form action="https://www.sandbox.paypal.com/cgi-bin/webscr" method="post" />
<input type="hidden" name="cmd" value="_cart" />
<input type="hidden" name="upload" value="1" />
<input type="hidden" name="business" value="email@business.example.com" />
<input type="hidden" name="currency_code" value="GBP" />
<input type="hidden" name="lc" value="UK" />
<input type="hidden" name="rm" value="2" />
<input type="hidden" name="return" value="https://domain.com/thankyou" />
<input type="hidden" name="cancel_return" value="https://domain.com/cancel" />
<input type="hidden" name="notify_url" value="https://domain.com/ipn" />
<input type="hidden" name="charset" value="utf-8" />
<input type="hidden" name="item_name_1" value="PC" />
<input type="hidden" name="item_number_1 value="0045" />
<input type="hidden" name="amount_1" value="500.00" />
<input type="hidden" name="quantity_1" value="1" />
<input style="margin-top:10px;" type="image" src="http://www.paypal.com/en_US/i/btn/x-click-but01.gif" name="submit" alt="Make payments with PayPal - it's fast, free and secure!">
</form>

Whilst this works it does open it up to abuse, anyone can inspect the form and change any of the fields such as the price before it goes to PayPal! 

What I prefer to do it send the data directly from a PHP page, this way it cannot be modified before being sent to PayPal.

Post data to PayPal

The same form data is still used with this approach only it’s not laid out in a form but instead in an array:

$fields = [
    'cmd'           => '_cart',
    'upload'        => '1',
    'business'      => 'email@business.example.com',
    'currency_code' => 'GBP',
    'lc'            => 'UK',
    'rm'            => '2',
    'return'        => 'https://domain.com/thankyou',
    'cancel_return' => 'https://domain.com/cancel',
    'notify_url'    => 'https://domain.com/ipn'
];

//add items
$fields["item_name_1"]   = 'PC';
$fields["item_number_1"] = '0045';
$fields["amount_1"]      = '500.00';
$fields["quantity_1"]    = 1;

$fields["item_name_2"]   = 'Office Chair';
$fields["item_number_2"] = '0098';
$fields["amount_2"]      = '60.00';
$fields["quantity_2"]    = 1;

// Prepare query string
$query_string = http_build_query($fields);

header('Location: https://www.sandbox.paypal.com/cgi-bin/webscr?' . $query_string);
exit();

This will send the array data to the URL as a POST request.

To send a custom item such as an user_id you can use a custom field:

$fields['custom'] = $user_id;

Or part of the initial fields array:

$fields = [
    'cmd'           => '_cart',
    'upload'        => '1',
    'business'      => 'email@business.example.com',
    'currency_code' => 'GBP',
    'lc'            => 'UK',
    'rm'            => '2',
    'return'        => 'https://domain.com/thankyou',
    'cancel_return' => 'https://domain.com/cancel',
    'notify_url'    => 'https://domain.com/ipn',
    'custom'        => $user_id
];

Remember to change the sandbox URL and business field when switching to a live account.

For Laravel users, the IPN will be POSTED to your notify_url endpoint. It will not have a CSRF token to make sure you exclude the call from CSRF token checks by going to app/Http/Middleware/VerifyCsrfToken.php and adding the endpoint to the $except array:

class VerifyCsrfToken extends Middleware
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        'ipn'
    ];
}

 

Handling IPN

Once payment has been made an IPN is sent to the specified notify_url.

First, specify weather to use sandbox with a Y for yes or anything else for No.

Collect the POST data

// Read POST data
$raw_post_data = file_get_contents('php://input');
$raw_post_array = explode('&', $raw_post_data);
$myPost = array();
foreach ($raw_post_array as $keyval) {
    $keyval = explode ('=', $keyval);
    if (count($keyval) == 2){
        $myPost[$keyval[0]] = urldecode($keyval[1]);
    }
}

Read the post from PayPal and add CMD

$req = 'cmd=_notify-validate';
foreach ($myPost as $key => $value) {
    $value = urlencode($value);
    $req .= "&$key=$value";
}

Set the paypal URL

if (USE_SANDBOX == 'Y') {
    $paypal_url = "https://ipnpb.sandbox.paypal.com/cgi-bin/webscr";
} else {
    $paypal_url = "https://ipnpb.paypal.com/cgi-bin/webscr";
}

Next to confirm the payment a request is sent back to PayPal

$ch = curl_init($paypal_url);
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLINFO_HEADER_OUT, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));
$res = curl_exec($ch);

If there is an error connecting throw an exception

if (curl_errno($ch) != 0) {
    
    throw new \Exception("Can't connect to PayPal to validate IPN message: ".curl_error($ch)); 
    
    return true;
}

A successful payment will have VERIFIED in the PayPal payload so check for that:

if (preg_match("/VERIFIED/", $res)) {
//payment complete, process order & send an email etc
}

Putting it all together:

define("USE_SANDBOX", 'Y');

// Read POST data
$raw_post_data = file_get_contents('php://input');
$raw_post_array = explode('&', $raw_post_data);
$myPost = array();
foreach ($raw_post_array as $keyval) {
    $keyval = explode ('=', $keyval);
    if (count($keyval) == 2){
        $myPost[$keyval[0]] = urldecode($keyval[1]);
    }
}
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-validate';
foreach ($myPost as $key => $value) {
    $value = urlencode($value);
    $req .= "&$key=$value";
}
if (USE_SANDBOX == 'Y') {
    $paypal_url = "https://ipnpb.sandbox.paypal.com/cgi-bin/webscr";
} else {
    $paypal_url = "https://ipnpb.paypal.com/cgi-bin/webscr";
}
$ch = curl_init($paypal_url);
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLINFO_HEADER_OUT, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));
$res = curl_exec($ch);

if (curl_errno($ch) != 0) {
    
    throw new \Exception("Can't connect to PayPal to validate IPN message: ".curl_error($ch)); 
    
    return true;
} else {

    if (preg_match("/VERIFIED/", $res)) {
        //payment complete, you should send en email here.
    } else {
        //IPN not verified, you should send en email here.
    }
}
curl_close($ch);

 

]]>
Wed, 15 Jan 2020 08:01:27 GMT https://dcblog.dev/how-to-integrate-paypal-into-php https://dcblog.dev/how-to-integrate-paypal-into-php
Send emails with Office 365 SMTP

Office 365 is great for handling emails but it can also be used as an SMTP relay for your website. I've tried dedicated SMTP services with mixed results some work better than others. What I like about using Office 365 is it's easy to set up and very reliable. The added benefit of Office 365 is your sent emails saved in your sent folder! very handy.

Here are the SMTP credentials you need out of the box:

Host: smtp.office365.com
username: email_address
password: email_password
port: 587
encryption: tls

This will work without any setup, but if you have 2FA set up this will not work, instead, you need to create an app password. 

App passwords are passwords to be used for third party applications, this way you never expose your account password so it's a recommended approach even if you don't use 2FA but seriously use 2FA!

To set up app passwords login to your account click Manage security and privacy.

Next click on create and manage app passwords.

All your app passwords will be listed on this page, to add a new one click create.

Enter a name for the app password such as your website name.

Note down the generated app password it will not be displayed again. (the below is just an example)

Now with the pass password, you're ready to start sending emails with SMTP.

To use Office 365 with Laravel setup the .env file as follows

MAIL_DRIVER=smtp
MAIL_HOST=smtp.office365.com
MAIL_PORT=587
MAIL_USERNAME=email
MAIL_PASSWORD=app_password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=email
MAIL_FROM_NAME="your app name"

 

]]>
Mon, 13 Jan 2020 07:22:05 GMT https://dcblog.dev/send-emails-with-office-365-smtp https://dcblog.dev/send-emails-with-office-365-smtp
V1 PDO Wrapper released

I've updated and pushed a new release to my PDO Wrapper package.

This PDO wrapper, is a collection of crud methods for working with a database this includes selecting, inserting, updating and deleting records.

Install

Using composer include the repository by typing the following into a terminal

composer require daveismyname/pdo-wrapper

Set the db credentials. Finally create an instance of the classes by calling it's get method.

This wrapper makes use of a single database connection further connections attempts will reuse the already open connections, if not already connected.

use Daveismyname\PdoWrapper\Database;

// make a connection to mysql here
$db = Database::get($username, $password, $database, $host = 'localhost', $type = 'mysql');

Usage examples

Select:

$db->select("column FROM table");

To select data based on user data instead of passing the data to the query directly use a prepared statement, this is safer and stops any attempt at sql injections.

$db->select("username FROM members WHERE memberID = :id and email = :email", array(':id' => 1, ':email' => 'someone@domain.com'));

The above query will return the username from the members table where the memberID and email match. The memberID and email is passed seperartly in an array.

Instead of passing in an id and email to the query directly a placeholder is used :id and :email then an array is passed the keys in the array matches the placeholder and is bound, so the database will get both the query and the bound data.

Data returned from the query will be returns as an object this can be changed by passing a third param to the select containing PDO::FETCH_ASSOC.

To use the object loop through it, a typical example:

$rows = $db->select("firstName, lastName FROM members ORDER BY firstName, lastName");
foreach ($rows as $row) {
    echo "<p>$row->firstName $row->lastName</p>";
}

Select Single Record:

Using find() will return only a single result. Like select it accepts params being passed in an array as a second argument.

$db->find("column FROM table where id=:id", [':id' => 23]);

Raw

A raw query is a query that is not ran through a prepared statement and will execute the query passed directly. Useful when creating a table.

$db->raw("CREATE TABLE IF NOT EXISTS members (
  memberID INT(11) NOT NULL AUTO_INCREMENT,
  firstName VARCHAR(255) NOT NULL,
  lastnName VARCHAR(255) NOT NULL,
  email VARCHAR(255) NOT NULL,
  PRIMARY KEY (memberID))"
);

Insert

Data is inserted by calling the insert method it expects the table name followed by an array of key and values to insert in to the database.

$data = array(
    'firstName' => 'Joe',
    'lastnName' => 'Smith',
    'email' => 'someone@domain.com'
);
$db->insert('members', $data);

The insert automatically returns the last inserted id by returning 'lastInsertId' to collect the id:

$id = $db->insert('members', $data);

Updating

To update an existing record the update method is called. This method expects the table, array of data to update and a second array containing the where condition.

$data = array(
    'firstName' => 'Joe',
    'lastnName' => 'Smith',
    'email' => 'someone@domain.com'
);
$where = array('memberID' => 2);
$db->update('members', $data, $where);

Or:

$update = array( 
	'data'=>array(
	    'firstName' => 'Joe',
	    'lastnName' => 'Smith',
	    'email' => 'someone@domain.com'
		),
	'where'=> array('memberID' => 2)
	);

$db->update('members', $update['data'], $update['where']);

Delete

To delete records call the delete method. This method expects the table name and an array of the where condition.

$where = array('memberID' => 2);
$db->delete('members', $where);

This will delete a single record to set the limit pass a third parameters containing the number to limit to, or to remove the limit pass null as a third param.

$db->delete('members', $where, 10);  //delete 10 records matcing the where
$db->delete('members', $where, null); //delete all records matching the where

Delete multiple IN

To delete multiple records where ids are in a specific column, this uses WHERE id IN (4,5,6)

$db->deleteByIds('users', 'id', '4,5,6');

Truncate

To empty a table of all contents call the truncate method. Passing only the table name.

$db->truncate('members');

Count

To count records call the count method. This method expects the table name and column name (optional).

$db->count('members');

If table has no column id

$db->count('members', 'member_id');

See https://github.com/daveismyname/pdo-wrapper

]]>
Fri, 10 Jan 2020 00:13:04 GMT https://dcblog.dev/v1-pdo-wrapper-released https://dcblog.dev/v1-pdo-wrapper-released
Test composer packages locally

When developing a new composer package you will want to test it locally before uploading it to Packagist.

First create a composer.json file for your package, here's an example the important part is the autoload psr-4 which sets the namespace path and secondingly the relative source path.

{
    "name": "name of the package",
    "description": "short description",
    "autoload": {
        "psr-4": {
            "Daveismyname\\PdoWrapper\\": "/src"
        }
    },
    "minimum-stability": "dev"
}

The above example will autoload from a src folder, for this example I have a Database class stored in a namespace of Daveismyname\PdoWrapper this matches the path in composer.json

<?php
namespace Daveismyname\PdoWrapper;

use PDO;

class Database extends PDO
{

}

Now to use this the namespace would be imported and then the class can be called.

require('vendor/autoload.php');

use Daveismyname\PdoWrapper\Database;

$db = new Database();

So far this is the starting process for building a package but to actually test it another project is required.

Create a folder to run this from ideally in the parent folder so both the project folder and the package folder are on the same level.

Next create a composer.json file, add the vendor/package name to require and autoload it, to make it work add a repositories array and pass in the local path to the package. This lets composer load the package from the local file system instead of from Packagist.

{
    "name": "dc/demo",
    "description": "",
    "require": {
        "daveismyname/pdo-wrapper": "@dev"
    },
      "autoload": {
        "psr-4": {
          "Daveismyname\\PdoWrapper\\": "src/"
        }
    },
    "repositories": [
        {
          "type": "path",
          "url": "../pdo-wrapper-master"
        }
    ]
}

Run composer install

Now the class can be used like any other for example:

require('vendor/autoload.php');

use Daveismyname\PdoWrapper\Database;

$db = Database();

 

]]>
Thu, 09 Jan 2020 23:33:14 GMT https://dcblog.dev/test-composer-packages-locally https://dcblog.dev/test-composer-packages-locally
Convert translate message calls to a single json language file

Laravel supports using the __() call to setup locale for your pages ie in a blade file you may have a title called Dashboard. To make it translatable you would use:

{{ __('Dashboard') }}

This will default to the given text if the config/app.php file's locale is set to en, if its set to another language Laravel will look for a the lang.json file in the resourses folder, if one is not found then the given message will be used. 

You have to manually create the language files yourself which can be time consuming.

This package: https://github.com/vemcogroup/laravel-translation provides the ability to automate the lang file process!

Install it with composer:

composer require vemcogroup/laravel-translation

Publish the config:

php artisan vendor:publish --provider="Vemcogroup\Translation\TranslationServiceProvider"

This generated a config file to config/translation I'll stick with the defaults for this post.

Now all you need to do is run:

php artisan translation:scan

This will scan your .php and .vue files (you can add others in the config) looking for matching functions the defaut is to look for __() functions. All matches are added to resources/lang/en.json

This is very useful as can write your text in english using the __() syntax and generate a single language file for any translations needed, perfect!

]]>
Thu, 09 Jan 2020 07:37:07 GMT https://dcblog.dev/convert-translate-message-calls-to-a-single-json-language-file https://dcblog.dev/convert-translate-message-calls-to-a-single-json-language-file
MySQL 8.0+ error: The server requested authentication method unknown to the client

Upgraded MySQL to v8 and starting getting connection errors due to how MySQL 8 defaults to auth_socket connections. Applications that use passwords will fail and generate the error:

PHP with MySQL 8.0+ error: The server requested authentication method unknown to the client

The solution is to change the default authentication method to mysql_native_password.

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password
BY 'password';  

Replace password with your password, if you don't use a password then enter it as ''

Thanks to this stackoverflow post

]]>
Thu, 05 Dec 2019 09:16:13 GMT https://dcblog.dev/mysql-80-error-the-server-requested-authentication-method-unknown-to-the-client https://dcblog.dev/mysql-80-error-the-server-requested-authentication-method-unknown-to-the-client
Switching between PHP versions with Homebrew

With Homebrew it's possible to have multiple versions of PHP installed at once, to switch which version is active unlink the current version and link the desired version. 

Switch from 7.4 to 7.3

brew unlink php@7.4 && brew link --force --overwrite php@7.3

Switch from 7.3 to 7.4

brew unlink php@7.3 && brew link --force --overwrite php@7.4

Since you may want to do this frequently it's more convenient to create a function to do this.

Create a function to your bash/zshrc profile

switchphp() {
    brew unlink php && brew link --force --overwrite php@$1
}

This will unlink PHP and relink to the specified version.

Usage:

switch to PHP 7.4 

switchphp 7.4

switch to PHP 7.3

switchphp 7.3

 

]]>
Sun, 01 Dec 2019 14:40:58 GMT https://dcblog.dev/switching-between-php-versions-with-homebrew https://dcblog.dev/switching-between-php-versions-with-homebrew