PHP & Web Development Blogs

Search Results For: lets
Showing 1 to 5 of 10 blog articles.
24789 views · 4 years ago
PHP CHAT WITH SOCKETS

Hey Friends,

I am sharing a very interesting blog on how to create a chat system in php without using ajax. As we all know ajax based chat system in php is not a good solution
because itincreases the server load and redundant xhr calls on our server.

Instead, I am going to use sockets for incoming messages from and send messages to another user. So lets try them out using the following steps:


Step 1: Cross check in php.ini that sockets extension is enabled


;extension=sockets
extension=sockets


Step 2: Create server.php file


This file will handle the incoming and outgoing messages on sockets, Add following variables in top of the file:

$host = 'localhost';
$port = '9000';
$null = NULL; 


Step 3: After it add helper methods


The following code for handshake with new incoming connections and encrypt and decrypt messages incoming and outgoing over sockets:

function send_message($msg)
{
global $clients;
foreach($clients as $changed_socket)
{
@socket_write($changed_socket,$msg,strlen($msg));
}
return true;
}
function unmask($text) {
$length = ord($text[1]) & 127;
if($length == 126) {
$masks = substr($text, 4, 4);
$data = substr($text, 8);
}
elseif($length == 127) {
$masks = substr($text, 10, 4);
$data = substr($text, 14);
}
else {
$masks = substr($text, 2, 4);
$data = substr($text, 6);
}
$text = "";
for ($i = 0; $i < strlen($data); ++$i) {
$text .= $data[$i] ^ $masks[$i%4];
}
return $text;
}
function mask($text)
{
$b1 = 0x80 | (0x1 & 0x0f);
$length = strlen($text);

if($length <= 125)
$header = pack('CC', $b1, $length);
elseif($length > 125 && $length < 65536)
$header = pack('CCn', $b1, 126, $length);
elseif($length >= 65536)
$header = pack('CCNN', $b1, 127, $length);
return $header.$text;
}
function perform_handshaking($receved_header,$client_conn, $host, $port)
{
$headers = array();
$lines = preg_split("/

/", $receved_header);
foreach($lines as $line)
{
$line = chop($line);
if(preg_match('/\A(\S+): (.*)\z/', $line, $matches))
{
$headers[$matches[1]] = $matches[2];
}
}
$secKey = $headers['Sec-WebSocket-Key'];
$secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
$upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake

" .
"Upgrade: websocket

" .
"Connection: Upgrade

" .
"WebSocket-Origin: $host

" .
"WebSocket-Location: ws://$host:$port/php-ws/chat-daemon.php

".
"Sec-WebSocket-Accept:$secAccept



";
socket_write($client_conn,$upgrade,strlen($upgrade));
}


Step 4: Now add following code to create bind and listen tcp/ip sockets:


$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($socket, 0, $port);
socket_listen($socket);
$clients = array($socket);


Ok now a endless loop that will use for handeling incominga nd send messages:

while (true) {
$changed = $clients;
socket_select($changed, $null, $null, 0, 10);

if (in_array($socket, $changed)) {
$socket_new = socket_accept($socket); $clients[] = $socket_new;
$header = socket_read($socket_new, 1024); perform_handshaking($header, $socket_new, $host, $port);
socket_getpeername($socket_new, $ip); $response = mask(json_encode(array('type'=>'system', 'message'=>$ip.' connected'))); send_message($response);
$found_socket = array_search($socket, $changed);
unset($changed[$found_socket]);
}

foreach ($changed as $changed_socket) {

while(socket_recv($changed_socket, $buf, 1024, 0) >= 1)
{
$received_text = unmask($buf); $tst_msg = json_decode($received_text, true); $user_name = $tst_msg['name']; $user_message = $tst_msg['message']; $user_color = $tst_msg['color'];
$response_text = mask(json_encode(array('type'=>'usermsg', 'name'=>$user_name, 'message'=>$user_message, 'color'=>$user_color)));
send_message($response_text); break 2; }

$buf = @socket_read($changed_socket, 1024, PHP_NORMAL_READ);
if ($buf === false) { $found_socket = array_search($changed_socket, $clients);
socket_getpeername($changed_socket, $ip);
unset($clients[$found_socket]);

$response = mask(json_encode(array('type'=>'system', 'message'=>$ip.' disconnected')));
send_message($response);
}
}
}
socket_close($socket);


So you are ready with server side socket program, Now its time to move on front side where we will implement w3c provided client side Web Socket Apis,

Step 5: create a file named index.php for frontend usage with following initial code


$host = 'localhost';
$port = '9000';
$subfolder = "php_ws/";
$colors = array('#007AFF','#FF7000','#FF7000','#15E25F','#CFC700','#CFC700','#CF1100','#CF00BE','#F00');
$color_pick = array_rand($colors);
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="chat-wrapper">
<div id="message-box"></div>
<div class="user-panel">
<input type="text" name="name" id="name" placeholder="Your Name" maxlength="15" />
<input type="text" name="message" id="message" placeholder="Type your message here..." maxlength="100" />
<button id="send-message">Send</button>
</div>
</div>
</body>
</html>


Now add some basic styling in the head section using following code:

<style type="text/css">
.chat-wrapper {
font: bold 11px/normal 'lucida grande', tahoma, verdana, arial, sans-serif;
background: #00a6bb;
padding: 20px;
margin: 20px auto;
box-shadow: 2px 2px 2px 0px #00000017;
max-width:700px;
min-width:500px;
}
#message-box {
width: 97%;
display: inline-block;
height: 300px;
background: #fff;
box-shadow: inset 0px 0px 2px #00000017;
overflow: auto;
padding: 10px;
}
.user-panel{
margin-top: 10px;
}
input[type=text]{
border: none;
padding: 5px 5px;
box-shadow: 2px 2px 2px #0000001c;
}
input[type=text]#name{
width:20%;
}
input[type=text]#message{
width:60%;
}
button#send-message {
border: none;
padding: 5px 15px;
background: #11e0fb;
box-shadow: 2px 2px 2px #0000001c;
}
</style>


Ok Style is all set now need to add a jquery script and create web socket object and handle all events on it as following code need to add before closing of bosy tag:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script language="javascript" type="text/javascript">
var msgBox = $('#message-box');
var wsUri = "ws://".$host.":".$port."/php-ws/server.php";
websocket = new WebSocket(wsUri);

websocket.onopen = function(ev) { msgBox.append('<div class="system_msg" style="color:#bbbbbb">Welcome to my "Chat box"!</div>'); }
websocket.onmessage = function(ev) {
var response = JSON.parse(ev.data);
var res_type = response.type; var user_message = response.message; var user_name = response.name; var user_color = response.color; switch(res_type){
case 'usermsg':
msgBox.append('<div><span class="user_name" style="color:' + user_color + '">' + user_name + '</span> : <span class="user_message">' + user_message + '</span></div>');
break;
case 'system':
msgBox.append('<div style="color:#bbbbbb">' + user_message + '</div>');
break;
}
msgBox[0].scrollTop = msgBox[0].scrollHeight; };

websocket.onerror = function(ev){ msgBox.append('<div class="system_error">Error Occurred - ' + ev.data + '</div>'); };
websocket.onclose = function(ev){ msgBox.append('<div class="system_msg">Connection Closed</div>'); };
$('#send-message').click(function(){
send_message();
});

$( "#message" ).on( "keydown", function( event ) {
if(event.which==13){
send_message();
}
});

function send_message(){
var message_input = $('#message'); var name_input = $('#name');
if(message_input.val() == ""){ alert("Enter your Name please!");
return;
}
if(message_input.val() == ""){ alert("Enter Some message Please!");
return;
}
var msg = {
message: message_input.val(),
name: name_input.val(),
color : '<?php echo $colors[$color_pick]; ?>'
};
websocket.send(JSON.stringify(msg));
message_input.val(''); }
</script>


Ok All set, Now need to run the server.php file using following php-cli utility,make sure you have php cli utility installed in your system:

php -q c:\xampp\htdocs\php-ws\server.php


Now you may access the front index.php file via the browser url like following and see a chatbox and connection status, you may use the same url or different browser to check the chat system is working or not.
12812 views · 5 years ago
Creating a PHP Daemon Service

What is a Daemon?

The term daemon was coined by the programmers of Project MAC at MIT. It is inspired on Maxwell's demon in charge of sorting molecules in the background. The UNIX systems adopted this terminology for daemon programs.

It also refers to a character from Greek mythology that performs the tasks for which the gods do not want to take. As stated in the "Reference System Administrator UNIX", in ancient Greece, the concept of "personal daemon" was, in part, comparable to the modern concept of "guardian angel." BSD family of operating systems use the image as a demon's logo.

Daemons are usually started at machine boot time. In the technical sense, a demon is considered a process that does not have a controlling terminal, and accordingly there is no user interface. Most often, the ancestor process of the deamon is init - process root on UNIX, although many daemons run from special rcd scripts started from a terminal console.

Richard Stevenson describes the following steps for writing daemons:
    . Resetting the file mode creation mask to 0 function umask(), to mask some bits of access rights from the starting process.
    . Cause fork() and finish the parent process. This is done so that if the process was launched as a group, the shell believes that the group finished at the same time, the child inherits the process group ID of the parent and gets its own process ID. This ensures that it will not become process group leader.
    . Create a new session by calling setsid(). The process becomes a leader of the new session, the leader of a new group of processes and loses the control of the terminal.
    . Make the root directory of the current working directory as the current directory will be mounted.
    . Close all file descriptors.
    . Make redirect descriptors 0,1 and 2 (STDIN, STDOUT and STDERR) to /dev/null or files /var/log/project_name.out because some standard library functions use these descriptors.
    . Record the pid (process ID number) in the pid-file: /var/run/projectname.pid.
    . Correctly process the signals and SigTerm SigHup: end with the destruction of all child processes and pid - files and / or re-configuration.

How to Create Daemons in PHP

To create demons in PHP you need to use the extensions pcntl and posix. To implement the fast communication withing daemon scripts it is recommended to use the extension libevent for asynchronous I/O.

Lets take a closer look at the code to start a daemon:
umask(0);
$pid = pcntl_fork(); 
if ($pid < 0) {
print('fork failed');
exit 1;
}


After a fork, the execution of the program works as if there are two branches of the code, one for the parent process and the second for the child process. What distinguishes these two processes is the result value returned the fork() function call. The parent process ID receives the newly created process number and the child process receives a 0.
if ($pid > 0) { echo "daemon process started
";
exit; }

$sid = posix_setsid(); if ($sid < 0) {
exit 2;
}

chdir('/'); file_put_contents($pidFilename, getmypid() );
run_process();


The implementation of step 5 "to close all file descriptors" can be done in two ways. Well, closing all file descriptors is difficult to implement in PHP. You just need to open any file descriptors before fork(). Second, you can override the standard output to an error log file using init_set() or use buffering using ob_start() to a variable and store it in log file:
ob_start();
var_dump($some_object);
$content = ob_get_clean();
fwrite($fd_log, $content); 


Typically, ob_start() is the start of the daemon life cycle and ob_get_clean() and fwrite() calls are the end. However, you can directly override STDIN, STDOUT and STDERR:
ini_set('error_log', $logDir.'/error.log');
fclose(STDIN); 
fclose(STDOUT);
fclose(STDERR);
$STDIN = fopen('/dev/null', 'r');
$STDOUT = fopen($logDir.'/application.log', 'ab');
$STDERR = fopen($logDir.'/application.error.log', 'ab');


Now, our process is disconnected from the terminal and the standard output is redirected to a log file.

Handling Signals

Signal processing is carried out with the handlers that you can use either via the library pcntl (pcntl_signal_dispatch()), or by using libevent. In the first case, you must define a signal handler:
function sig_handler($signo)
{
global $fd_log;
switch ($signo) {
case SIGTERM:
fclose($fd_log); unlink($pidfile); exit;
break;
case SIGHUP:
init_data(); break;
default:
}
}

pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGHUP, "sig_handler");


Note that signals are only processed when the process is in an active mode. Signals received when the process is waiting for input or in sleep mode will not be processed. Use the wait function pcntl_signal_dispatch(). We can ignore the signal using flag SIG_IGN: pcntl_signal(SIGHUP, SIG_IGN); Or, if necessary, restore the signal handler using the flag SIG_DFL, which was previously installed by default: pcntl_signal(SIGHUP, SIG_DFL);

Asynchronous I/O with Libevent

In the case you use blocking input / output signal processing is not applied. It is recommended to use the library libevent which provides non-blocking as input / output, processing signals, and timers. Libevent library provides a simple mechanism to start the callback functions for events on file descriptor: Write, Read, Timeout, Signal.

Initially, you have to declare one or more events with an handler (callback function) and attach them to the basic context of the events:
$base = event_base_new();
$event = event_new();
$errno = 0;
$errstr = '';
$socket = stream_socket_server("tcp://$IP:$port", $errno, $errstr);
stream_set_blocking($socket, 0); event_set($event, $socket, EV_READ | EV_PERSIST, 'onAccept', $base);


Function handlers 'onRead', 'onWrite', 'onError' must implement the processing logic. Data is written into the buffer, which is obtained in the non-blocking mode:
function onRead($buffer, $id)
{
while($read = event_buffer_read($buffer, 256)) {
var_dump($read);
}
}


The main event loop runs with the function event_base_loop($base);. With a few lines of code, you can exit the handler only by calling: event_base_loobreak(); or after the specified time (timeout) event_loop_exit();.

Error handling deals with failure Events:
function onError($buffer, $error, $id)
{
global $id, $buffers, $ctx_connections;
event_buffer_disable($buffers[$id], EV_READ | EV_WRITE);
event_buffer_free($buffers[$id]);
fclose($ctx_connections[$id]);
unset($buffers[$id], $ctx_connections[$id]);
}


It should be noted the following subtlety: Working with timers is only possible through the file descriptor. The example of official the documentation does not work. Here is an example of processing that runs at regular intervals.
$event2 = event_new();
$tmpfile = tmpfile();
event_set($event2, $tmpfile, 0, 'onTimer', $interval);
$res = event_base_set($event2, $base);
event_add($event2, 1000000 * $interval);


With this code we can have a working timer finishes only once. If we need a "permanent" Timer, using the function onTimer we need create a new event each time, and reassign it to process through a "period of time":
function onTimer($tmpfile, $flag, $interval)
{
$global $base, $event2;

if ($event2) {
event_delete($event2);
event_free($event2);
}

call_user_function(‘process_data’,$args);

$event2 = event_new();
event_set($event2, $tmpfile, 0, 'onTimer', $interval);
$res = event_base_set($event2, $base);
event_add($event2, 1000000 * $interval);
}


At the end of the daemon we must release all previously allocated resources:
event_delete($event);
event_free($event);
event_base_free($base);

event_base_set($event, $base);
event_add($event);


Also it should be noted that for the signal processing handler is set the flag EV_SIGNAL: event_set($event, SIGHUP, EV_SIGNAL, 'onSignal', $base);

If needed constant signal processing, it is necessary to set a flag EV_PERSIST. Here follows a handler for the event onAccept, which occurs when a new connection is a accepted on a file descriptor:
function onAccept($socket, $flag, $base) {
global $id, $buffers, $ctx_connections;
$id++;
$connection = stream_socket_accept($socket);
stream_set_blocking($connection, 0);
$buffer = event_buffer_new($connection, 'onRead', NULL, 'onError', $id);
event_buffer_base_set($buffer, $base);
event_buffer_timeout_set($buffer, 30, 30);
event_buffer_watermark_set($buffer, EV_READ, 0, 0xffffff); event_buffer_priority_set($buffer, 10); event_buffer_enable($buffer, EV_READ | EV_PERSIST); $ctx_connections[$id] = $connection;
$buffers[$id] = $buffer;
}


Monitoring a Daemon

It is good practice to develop the application so that it was possible to monitor the daemon process. Key indicators for monitoring are the number of items processed / requests in the time interval, the speed of processing with queries, the average time to process a single request or downtime.

With the help of these metrics can be understood workload of our demon, and if it does not cope with the load it gets, you can run another process in parallel, or for running multiple child processes.

To determine these variables need to check these features at regular intervals, such as once per second. For example downtime is calculated as the difference between the measurement interval and total time daemon.

Typically downtime is determined as a percentage of a measurement interval. For example, if in one second were executed 10 cycles with a total processing time of 50ms, the time will be 950ms or 95%.

Query performance wile be 10rps (request per second). Average processing time of one request: the ratio of the total time spent on processing requests to the number of requests processed, will be 5ms.

These characteristics, as well as additional features such as memory stack size queue, number of transactions, the average time to access the database, and so on.

An external monitor can be obtain data through a TCP connection or unix socket, usually in the format of Nagios or zabbix, depending on the monitoring system. To do this, the demon should use an additional system port.

As mentioned above, if one worker process can not handle the load, usually we run in parallel multiple processes. Starting a parallel process should be done by the parent master process that uses fork() to launch a series of child processes.

Why not run processes using exec() or system()? Because, as a rule, you must have direct control over the master and child processes. In this case, we can handle it via interaction signals. If you use the exec command or system, then launch the initial interpreter, and it has already started processes that are not direct descendants of the parent process.

Also, there is a misconception that you can make a demon process through command nohup. Yes, it is possible to issue a command: nohup php mydaemon.php -master >> /var/log/daemon.log 2 >> /var/log/daemon.error.log &

But, in this case, would be difficult to perform log rotation, as nohup "captures" file descriptors for STDOUT / STDERR and release them only at the end of the command, which may overload of the process or the entire server. Overload demon process may affect the integrity of data processing and possibly cause partial loss of some data.

Starting a Daemon

Starting the daemon must happen either automatically at boot time, or with the help of a "boot script."

All startup scripts are usually in the directory /etc/rc.d. The startup script in the directory service is made /etc/init.d/ . Run command start service myapp or start group /etc/init.d/myapp depending on the type of OS.

Here is a sample script text:
#! /bin/sh
#
$appdir = /usr/share/myapp/app.php
$parms = --master –proc=8 --daemon
export $appdir
export $parms
if [ ! -x appdir ]; then
exit 1
fi

if [ -x /etc/rc.d/init.d/functions ]; then
. /etc/rc.d/init.d/functions
fi

RETVAL=0

start () {
echo "Starting app"
daemon /usr/bin/php $appdir $parms
RETVAL=$?
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/mydaemon
echo
return $RETVAL
}

stop () {
echo -n "Stopping $prog: "
killproc /usr/bin/fetchmail
RETVAL=$?
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/mydaemon
echo
return $RETVAL
}

case in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
status)
status /usr/bin/mydaemon
;;
*)
echo "Usage:
php
if (is_file('app.phar')) {
unlink('app.phar');
}
$phar = new Phar('app.phar', 0, 'app.phar');
$phar->compressFiles(Phar::GZ);
$phar->setSignatureAlgorithm (Phar::SHA1);
$files = array();
$files['bootstrap.php'] = './bootstrap.php';
$rd = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('.'));
foreach($rd as $file){
if ($file->getFilename() != '..' && $file->getFilename() != '.' && $file->getFilename() != __FILE__) {
if ( $file->getPath() != './log'&& $file->getPath() != './script'&& $file->getPath() != '.')
$files[substr($file->getPath().DIRECTORY_SEPARATOR.$file->getFilename(),2)] =
$file->getPath().DIRECTORY_SEPARATOR.$file->getFilename();
}
}
if (isset($opt['version'])) {
$version = $opt['version'];
$file = "buildFromIterator(new ArrayIterator($files));
$phar->setStub($phar->createDefaultStub('bootstrap.php'));
$phar = null;
}
 {start|stop|restart|status}"
;;

RETVAL=$?
exit $RETVAL


Distributing Your PHP Daemon

To distribute a daemon it is better to pack it in a single phar archive module. The assembled module should include all the necessary PHP and .ini files.

Below is a sample build script:
#php app.phar
myDaemon version 0.1 Debug
usage:
--daemon – run as daemon
--debug – run in debug mode
--settings – print settings
--nofork – not run child processes
--check – check dependency modules
--master – run as master
--proc=[8] – run child processes


Additionally, it may be advisable to make a PEAR package as a standard unix-console utility that when run with no arguments prints its own usage instruction:
[NMD%%CODE%%]


Conclusion

Creating daemons in PHP it is not hard but to make them run correctly it is important to follow the steps described in this article.

Post a comment here if you have questions or comments on how to create daemon services in PHP.
16172 views · 5 years ago
PHP IPC with Daemon Service using Message Queues, Shared Memory and Semaphores

Introduction

In a previous article we learned about Creating a PHP Daemon Service. Now we are going to learn how to use methods to perform IPC - Inter-Process Communication - to communicate with daemon processes.

Message Queues

In the world of UNIX, there is an incredible variety of ways to send a message or a command to a daemon script and vice versa. But first I want to talk only about message queues - "System V IPC Messages Queues".

A long time ago I learned that a queue can be either in the System V IPC implementation, or in the POSIX implementation. I want to comment only about the System V implementation, as I know it better.

Lets get started. At the "normal" operating system level, queues are stored in memory. Queue data structures are available to all system programs. Just as in the file system, it is possible to configure queues access rights and message size. Usually a queue message size is small, less than 8 KB.

This introductory part is over. Lets move on to the practice with same example scripts.queue-send.php
$key = ftok(__FILE__, 'A'); 
$queue = msg_get_queue($key);

msg_send($queue, 1, 'message, type 1');
msg_send($queue, 2, 'message, type 2');
msg_send($queue, 3, 'message, type 3');
msg_send($queue, 1, 'message, type 1');

echo "send 4 messages
";

queue-receive.php
$key = ftok('queue-send.php', 'A');
$queue = msg_get_queue($key);

for ($i = 1; $i <= 3; $i++) {
echo "type: {$i}
";

while ( msg_receive($queue, $i, $msgtype, 4096, $message, false, MSG_IPC_NOWAIT) ) {
echo "type: {$i}, msgtype: {$msgtype}, message: {$message}
";
}
}


Lets run on the first stage of the file queue-send.php, and then queue-receive.php.
u% php queue-send.php
send 4 messages
u% php queue-receive.php
type: 1
type: 1, msgtype: 1, message: s:15:"message, type 1";
type: 1, msgtype: 1, message: s:15:"message, type 1";
type: 2
type: 2, msgtype: 2, message: s:15:"message, type 2";
type: 3
type: 3, msgtype: 3, message: s:15:"message, type 3";


You may notice that the messages have been grouped. The first group gathered 2 messages of the first type, and then the remaining messages.

If we would have indicated to receive messages of type 0, you would get all messages, regardless of the type.
while (msg_receive($queue, $i, $msgtype, 4096, $message, false, MSG_IPC_NOWAIT)) {


Here it is worth noting another feature of the queues: if we do not use the constant MSG_IPC_NOWAIT in the script and run the script queue-receive.php from a terminal, and then run periodically the file queue-send.php, we see how a daemon can effectively use this to wait jobs.queue-receive-wait.php
$key = ftok('queue-send.php', 'A');
$queue = msg_get_queue($key);

while ( msg_receive($queue, 0, $msgtype, 4096, $message) ) {
echo "msgtype: {$msgtype}, message: {$message}
";
}


Actually that is the most interesting information of all I have said. There are also functions to get statistics, disposal and checking for the existence of queues.

Lets now try to write a daemon listening to a queue:queue-daemon.php
$pid = pcntl_fork();
$key = ftok('queue-send.php', 'A');
$queue = msg_get_queue($key);

if ($pid == -1) {
exit;
} elseif ($pid) {
exit;
} else {
while ( msg_receive($queue, 0, $msgtype, 4096, $message) ) {
echo "msgtype: {$msgtype}, message: {$message}
";
}
}

posix_setsid();


Shared Memory

We have learned to work with queues, with which you can send small system messages. But then we may certainly be faced with the task of transmitting large amounts of data. My favorite type of system, System V, has solved the problem of rapid transmission and preservation of large data in memory using a mechanism calledShared Memory.

In short, the data in the Shared Memory lives until the system is rebooted. Since the data is in memory, it works much faster than if it was stored in a database somewhere in a file, or, God forgive me on a network share.

Lets try to write a simple example of data storage.shared-memory-write-base.php
$id = ftok(__FILE__, 'A');


$shmId = shm_attach($id);

$var = 1;

if (shm_has_var($shmId, $var)) {
$data = (array) shm_get_var($shmId, $var);
} else {
$data = array();
}

$data[time()] = file_get_contents(__FILE__);

shm_put_var($shmId, $var, $data);


Run this script several times to save the value in memory. Now lets write a script only to read from the memory.shared-memory-read-base.php
$id = ftok(__DIR__ . '/shared-memory-write-base.php', 'A');
$shmId = shm_attach($id);
$var = 1;

if (shm_has_var($shmId, $var)) {
$data = (array) shm_get_var($shmId, $var);
} else {
$data = array();
}

foreach ($data as $key => $value) {
$path = "/tmp/$key.php";
file_put_contents($path, $value);

echo $path . PHP_EOL;
}


Semaphores

So, in general terms, it should be clear for you by now how to work with shared memory. The only problems left to figure out are about a couple of nuances, such as: "What to do if two processes want to record one block of memory?" Or "How to store binary files of any size?".

To prevent simultaneous accesses we will use semaphores. Semaphores allow us to flag that we want to have exclusive access to some resource, like for instance a shared memory block. While that happens other processes will wait for their turn on semaphore.

In this code it explained clearly:shared-memory-semaphors.php

$id = ftok(__FILE__, 'A');

$semId = sem_get($id);

sem_acquire($semId);

$data = file_get_contents(__DIR__.'/06050396.JPG', FILE_BINARY);

$shmId = shm_attach($id, strlen($data)+4096);
$var = 1;

if (shm_has_var($shmId, $var)) {
$data = shm_get_var($shmId, $var);

$filename = '/tmp/' . time();
file_put_contents($filename, $data, FILE_BINARY);

shm_remove($shmId);
} else {
shm_put_var($shmId, $var, $data);
}

sem_release($semId);


Now you can use the md5sum command line utility to compare two files, the original and the saved file. Or, you can open the file in image editor or whatever prefer to compare the images.

With this we are done with shared memory and semaphores. As your homework I want to ask you to write code that a demon will use semaphores to access shared memory.

Conclusion

Exchanging data between the daemons is very simple. This article described two options for data exchange: message queues and shared memory.

Post a comment here if you have questions or comments about how to exchange data with daemon services in PHP.
13223 views · 5 years ago
Working With Thin Controller And Fat Model Concept In Laravel

Models and controllers are one of the most essential programming handlers in the Laravel MVC framework, and both are used vastly for different functional operations. Models in Laravel are created inside the app folder and are mostly used to interact with the database using Eloquent ORM, while the controllers are located inside the directory App/Http/Controllers.

As a programmer, you should have the knowledge how to keep the balance in between the programming usage of Models and controllers. As which one should be more utilized for allowing functional tasks in applications deployed on any PHP MySQL hosting.


What is the Concept of Thin Controller and FAT Models


The concept of the thin controller and fat model is that we do less work in our controllers and more work in our models. Like we use our controllers to validate our data and then pass it to the models. While in models, we define our actual functional logic and main coding operations of the desired application. This code structuring process is also a very basic concept of MVC and also the differentiating factor from the conventional complex programming which we mistakenly ignore sometimes.


Why FAT Controllers Are Bad For Handling Code


Controllers are always meant to be defined short and concise, and it should only be used for receiving requests and return responses to it. Anything else further should be programmed in Models, which is actually made for main functional operations.

Placing functional logic in controllers can be bad for many reasons for your applications deployed on anyhosting for PHP. As it not only makes code structure long but also makes it complex sometimes. Further placing code in Controllers is also not recommended because if same functionality is needed somewhere else in route, then pulling out the whole code from their becomes difficult and so its reusability in the application.

Though Laravel is an MVC framework while developing on laravel, we sometimes ignore this and write mostly all our code including the extending of App\Model and all our functional logic in controller route methods. What we can do here is we can create a sub model of our parent model. For example, our parent model is User then we can create another sub model of username in CustomerModel if you are using the same User model for all types of users. In this model, we will write all the logic related to user type Customer.

So now let's take an example of my existing blog creating comment system with laravel and vuejs. In that article, you can see I have made so much mess in my controller methods. Mostly, I have written all my comments logic in my methods, so to shorten that let's clean them in this article. Inside app folder, I will create a new file with name CommentModel.php. Inside this file, I will write my whole logic for comment functions. This is my basic file:


<?php
namespace App;

use App\Comment;
use App\CommentVote;
use App\CommentSpam;
use App\User;
use Auth;

class CommentModel
{


}

?>



Right now it contains no function but has the reference of all my models which I required for this model. Let's first add a function namedgetallcomments passing$pageId as a parameter inside it. The function will get all the comments for the given page:


public function getAllComments($pageId)
{
$comments = Comment::where('page_id',$pageId)->get();

$commentsData = [];


foreach ($comments as $key) {
$user = User::find($key->users_id);
$name = $user->name;
$replies = $this->replies($key->id);
$photo = $user->first()->photo_url;
$reply = 0;
$vote = 0;
$voteStatus = 0;
$spam = 0;
if(Auth::user()){
$voteByUser = CommentVote::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();
$spamComment = CommentSpam::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();


if($voteByUser){
$vote = 1;
$voteStatus = $voteByUser->vote;
}

if($spamComment){
$spam = 1;
}
}


if(sizeof($replies) > 0){
$reply = 1;
}

if(!$spam){
array_push($commentsData,[
"name" => $name,
"photo_url" => (string)$photo,
"commentid" => $key->id,
"comment" => $key->comment,
"votes" => $key->votes,
"reply" => $reply,
"votedByUser" =>$vote,
"vote" =>$voteStatus,
"spam" => $spam,
"replies" => $replies,
"date" => $key->created_at->toDateTimeString()
]);
}


}
$collection = collect($commentsData);
return $collection->sortBy('votes');
}



Now I will create another function namedreplies which takes$commentId as a parameter. The function is more or less programmed in the same manner as the upper function get all comments.


protected function replies($commentId)
{
$comments = Comment::where('reply_id',$commentId)->get();
$replies = [];



foreach ($comments as $key) {
$user = User::find($key->users_id);
$name = $user->name;
$photo = $user->first()->photo_url;

$vote = 0;
$voteStatus = 0;
$spam = 0;


if(Auth::user()){
$voteByUser = CommentVote::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();
$spamComment = CommentSpam::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();

if($voteByUser){
$vote = 1;
$voteStatus = $voteByUser->vote;
}

if($spamComment){
$spam = 1;
}
}
if(!$spam){


array_push($replies,[
"name" => $name,
"photo_url" => $photo,
"commentid" => $key->id,
"comment" => $key->comment,
"votes" => $key->votes,
"votedByUser" => $vote,
"vote" => $voteStatus,
"spam" => $spam,
"date" => $key->created_at->toDateTimeString()
]);
}




}


$collection = collect($replies);
return $collection->sortBy('votes');
}



Now lets create a functioncreate comment which passes$array as a parameter in it:


public function createComment($arary)
{
$comment = Comment::create($array);


if($comment)
return [ "status" => "true","commentId" => $comment->id ];
else
return [ "status" => "false" ];
}



Similarly, Now I will create all the function for comment in myCommentModel, so that all the functions gets accumulated in one model.


<?php
namespace App;

use App\Comment;
use App\CommentSpam;
use App\CommentVote;
use App\User;
use Auth;

class CommentModel
{
public function getAllComments($pageId)
{
$comments = Comment::where('page_id', $pageId)->get();

$commentsData = [];

foreach ($comments as $key) {
$user = User::find($key->users_id);
$name = $user->name;
$replies = $this->replies($key->id);
$photo = $user->first()->photo_url;
$reply = 0;
$vote = 0;
$voteStatus = 0;
$spam = 0;
if (Auth::user()) {
$voteByUser = CommentVote::where('comment_id', $key->id)->where('user_id', Auth::user()->id)->first();
$spamComment = CommentSpam::where('comment_id', $key->id)->where('user_id', Auth::user()->id)->first();

if ($voteByUser) {
$vote = 1;
$voteStatus = $voteByUser->vote;
}

if ($spamComment) {
$spam = 1;
}
}

if (sizeof($replies) > 0) {
$reply = 1;
}

if (!$spam) {
array_push($commentsData, [
"name" => $name,
"photo_url" => (string) $photo,
"commentid" => $key->id,
"comment" => $key->comment,
"votes" => $key->votes,
"reply" => $reply,
"votedByUser" => $vote,
"vote" => $voteStatus,
"spam" => $spam,
"replies" => $replies,
"date" => $key->created_at->toDateTimeString(),
]);
}

}
$collection = collect($commentsData);
return $collection->sortBy('votes');
}

protected function replies($commentId)
{
$comments = Comment::where('reply_id', $commentId)->get();
$replies = [];

foreach ($comments as $key) {
$user = User::find($key->users_id);
$name = $user->name;
$photo = $user->first()->photo_url;

$vote = 0;
$voteStatus = 0;
$spam = 0;

if (Auth::user()) {
$voteByUser = CommentVote::where('comment_id', $key->id)->where('user_id', Auth::user()->id)->first();
$spamComment = CommentSpam::where('comment_id', $key->id)->where('user_id', Auth::user()->id)->first();

if ($voteByUser) {
$vote = 1;
$voteStatus = $voteByUser->vote;
}

if ($spamComment) {
$spam = 1;
}
}
if (!$spam) {

array_push($replies, [
"name" => $name,
"photo_url" => $photo,
"commentid" => $key->id,
"comment" => $key->comment,
"votes" => $key->votes,
"votedByUser" => $vote,
"vote" => $voteStatus,
"spam" => $spam,
"date" => $key->created_at->toDateTimeString(),
]);
}

}

$collection = collect($replies);
return $collection->sortBy('votes');
}

public function createComment($arary)
{
$comment = Comment::create($array);

if ($comment) {
return ["status" => "true", "commentId" => $comment->id];
} else {
return ["status" => "false"];
}

}

public function voteComment($commentId, $array)
{
$comments = Comment::find($commentId);
$data = [
"comment_id" => $commentId,
'vote' => $array->vote,
'user_id' => $array->users_id,
];

if ($array->vote == "up") {
$comment = $comments->first();
$vote = $comment->votes;
$vote++;
$comments->votes = $vote;
$comments->save();
}

if ($array->vote == "down") {
$comment = $comments->first();
$vote = $comment->votes;
$vote--;
$comments->votes = $vote;
$comments->save();
}

if (CommentVote::create($data)) {
return true;
}

}

public function spamComment($commentId, $array)
{
$comments = Comment::find($commentId);

$comment = $comments->first();
$spam = $comment->spam;
$spam++;
$comments->spam = $spam;
$comments->save();

$data = [
"comment_id" => $commentId,
'user_id' => $array->users_id,
];

if (CommentSpam::create($data)) {
return true;
}

}
}
?>



Now we have all our required methods inCommentModel. So now let's clean upCommentController which is currently bit complex and lengthy in code structure. As right nowCommentController look like this:


<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests;
use App\Comment;
use App\CommentVote;
use App\CommentSpam;
use App\User;
use Auth;

class CommentController extends Controller
{



public function index($pageId)
{
$comments = Comment::where('page_id',$pageId)->get();

$commentsData = [];




foreach ($comments as $key) {
$user = User::find($key->users_id);
$name = $user->name;
$replies = $this->replies($key->id);
$photo = $user->first()->photo_url;
$reply = 0;
$vote = 0;
$voteStatus = 0;
$spam = 0;
if(Auth::user()){
$voteByUser = CommentVote::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();
$spamComment = CommentSpam::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();


if($voteByUser){
$vote = 1;
$voteStatus = $voteByUser->vote;
}

if($spamComment){
$spam = 1;
}
}


if(sizeof($replies) > 0){
$reply = 1;
}

if(!$spam){
array_push($commentsData,[
"name" => $name,
"photo_url" => (string)$photo,
"commentid" => $key->id,
"comment" => $key->comment,
"votes" => $key->votes,
"reply" => $reply,
"votedByUser" =>$vote,
"vote" =>$voteStatus,
"spam" => $spam,
"replies" => $replies,
"date" => $key->created_at->toDateTimeString()
]);
}


}
$collection = collect($commentsData);
return $collection->sortBy('votes');
}

protected function replies($commentId)
{
$comments = Comment::where('reply_id',$commentId)->get();
$replies = [];



foreach ($comments as $key) {
$user = User::find($key->users_id);
$name = $user->name;
$photo = $user->first()->photo_url;

$vote = 0;
$voteStatus = 0;
$spam = 0;


if(Auth::user()){
$voteByUser = CommentVote::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();
$spamComment = CommentSpam::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();

if($voteByUser){
$vote = 1;
$voteStatus = $voteByUser->vote;
}

if($spamComment){
$spam = 1;
}
}
if(!$spam){


array_push($replies,[
"name" => $name,
"photo_url" => $photo,
"commentid" => $key->id,
"comment" => $key->comment,
"votes" => $key->votes,
"votedByUser" => $vote,
"vote" => $voteStatus,
"spam" => $spam,
"date" => $key->created_at->toDateTimeString()
]);
}




}


$collection = collect($replies);
return $collection->sortBy('votes');
}


public function store(Request $request)
{
$this->validate($request, [
'comment' => 'required',
'reply_id' => 'filled',
'page_id' => 'filled',
'users_id' => 'required',
]);
$comment = Comment::create($request->all());
if($comment)
return [ "status" => "true","commentId" => $comment->id ];
}


public function update(Request $request, $commentId,$type)
{
if($type == "vote"){


$this->validate($request, [
'vote' => 'required',
'users_id' => 'required',
]);

$comments = Comment::find($commentId);
$data = [
"comment_id" => $commentId,
'vote' => $request->vote,
'user_id' => $request->users_id,
];

if($request->vote == "up"){
$comment = $comments->first();
$vote = $comment->votes;
$vote++;
$comments->votes = $vote;
$comments->save();
}

if($request->vote == "down"){
$comment = $comments->first();
$vote = $comment->votes;
$vote--;
$comments->votes = $vote;
$comments->save();
}

if(CommentVote::create($data))
return "true";
}

if($type == "spam"){


$this->validate($request, [
'users_id' => 'required',
]);

$comments = Comment::find($commentId);


$comment = $comments->first();
$spam = $comment->spam;
$spam++;
$comments->spam = $spam;
$comments->save();

$data = [
"comment_id" => $commentId,
'user_id' => $request->users_id,
];

if(CommentSpam::create($data))
return "true";
}
}


public function destroy($id)
{
}
}?>



After cleaning up the controller it will look much simpler and easy to understand like this:


<?php

namespace App\Http\Controllers;

use App\CommentModel;
use Illuminate\Http\Request;

class CommentController extends Controller
{

private $commentModel = null;
private function __construct()
{
$this->commentModel = new CommentModel();
}


public function index($pageId)
{
return $this->commentModel->getAllComments($pageId);
}


public function store(Request $request)
{
$this->validate($request, [
'comment' => 'required',
'reply_id' => 'filled',
'page_id' => 'filled',
'users_id' => 'required',
]);
return $this->commentModel->createComment($request->all());
}


public function update(Request $request, $commentId, $type)
{
if ($type == "vote") {

$this->validate($request, [
'vote' => 'required',
'users_id' => 'required',
]);

return $this->commentModel->voteComment($commentId, $request->all());
}

if ($type == "spam") {

$this->validate($request, [
'users_id' => 'required',
]);

return $this->commentModel->spamComment($commentId, $request->all());
}
}

}
?>




Wrap Up!


So Isn't it looking much cleaner and simpler to understand now? This is what actually a thin controller and fat model looks like. We have all our logic related to Comment system programmed in ourCommentModel and our controller is now just used to transfer data from the user to our model and returning the response which is coming from our model.

So this is how the structuring of the thin controller and fat model is made. Give your thoughts in the comments below.
11131 views · 4 years ago


It took me quite some time to settle on my first blog post in this series and I found myself thinking about the most requested functionality in my career – The good ‘ol Custom CMS – typically geared towards clients that want a straight forward, secure solution that can be expanded upon in a modular format and that’s their IP.

This will be our starting point. A blank slate to build something epic with clean code and even cleaner design. And in the spirit of building from scratch, I will refrain from using classes or a framework. The main reasoning behind this is to truly get everyone acquainted with and excited about PHP development.

Join me as I transform rudimentary code into something extraordinary that can be morphed into just about any Content, PHP, and MySQL driven project. So without further ado, let’s jump into it!

The bare necessities


If you’re just getting started with development, there’s a nifty bite sized server called UniformServer that will be your best friend throughout your coding career. PHPMyAdmin (an awesome visual db management tool) comes built in so if you’re looking for a work right out of the box solution, this is it.

Alternatively, you can opt for XAMPP or use an alternative server of your choice.

Now here’s where the exciting stuff begins, mapping things out.


I don’t see this done/encouraged often enough. Feel free to grab a piece of paper to logically map out your steps or produce a rough draft of where you’d like this project to go.

In this tutorial, I would like to achieve the following:



DB, DB, Set up your DB.


This requires a bit of planning but let’s start of with the basic structure we need to see this through.

We are going to need a user table and a content table and are a few ways to tackle this.

If you’re using the PHPMyAdmin tool you can create your database, add user permissions (Click on Permissions after creating your database), and create a table with ease.



If you’re like me and prefer to look at good ‘ol SQL then writing an SQL statement is the preferred approach.


CREATE TABLE <code>mydbname</code>.<code>content</code> ( <code>ID</code> INT(11) NOT NULL AUTO_INCREMENT , <code>title</code> VARCHAR(100) NOT NULL , <code>content</code> LONGTEXT NOT NULL , <code>author</code> VARCHAR(50) NOT NULL , PRIMARY KEY (<code>ID</code>)) ENGINE = MyISAM COMMENT = 'content table';


Understanding the SQL statement

In a nutshell we are creating a table with important fields. Namely:

####

ID | Title | Content | Author

#######

The ID field is our unique identifier.Now we can move on to the file structure.

Everything has a place in the file structure game


You can use a structure that speaks to your coding style / memory.

I tend to use the following:



Choose a name for your CMS, which should be placed at the webroot of your localhost/server.

Replicate the folder structure as per the above example.

Next, we’re going to create a basic connection file.


You can create a conn.php file in your root/includes folder.

The connection file will provide crucial information to connect to the database.

Type the following into your conn.php file, remember to include your own database credentials.


<?php

$letsconnect = new mysqli("localhost","dbuser","dbpass","dbname");

?>


Let’s go to the homepage (index.php)


Create a file called index.php at the root of your CMS folder.

I will be adding comments in my code to help you understand what each line does.

Comments are a useful tool for developers to add important notes private to their code.

We need to pull information from the database so it’s imperative that we include our connection file.


<?php

include('includes/conn.php');

if ($letsconnect -> connect_errno) { echo "Error " . $letsconnect -> connect_error;

}else{

$getmydata=$letsconnect -> query("SELECT * FROM content");

foreach($getmydata as $mydata){ echo "Title: "; echo $mydata['title']; echo "<br/>"; echo "Content: "; echo $mydata['content']; echo "<br/>"; echo "Author: "; echo $mydata['author']; echo "<br/>"; echo "<br/>";

}

}

$letsconnect -> close();

?>


Let’s get a (very) basic backend up and running


Create a file called index.php in your backend folder.

We need to create a basic form to capture our data.

Let’s code some HTML!


<html>

<head><title>Backend - Capture Content</title></head>

<body>

<form action="<?php $_SERVER[‘PHP_SELF’];?>" method="post">

<input type="text" name="title" placeholder="Content Title here" required/>

<textarea name="content">Content Here</textarea>

<input type="text" name="author" placeholder="Author" required/>

<input type="submit" value="Save My Data" name="savedata"/>

</form>

</body>

</html>


Next, we need to process the form data.


Type the following just above the
<form> 
tag.


<?php

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

include('../includes/conn.php');

if ($letsconnect->connect_error) {

die("Your Connection failed: " . $letsconnect->connect_error);

}else{

$sql = "INSERT INTO content(title,content,author)VALUES ('".$_POST["title"]."', '".$_POST["content"]."', '".$_POST["author"]."')";

if (mysqli_query($letsconnect, $sql)) {

echo "Your data was saved successfully!";

} else { echo "Error: " . $sql . "" . mysqli_error($letsconnect);

} $letsconnect->close();

}

}

?>


Note, this is a basic MySQL query to insert data. However, before using this in production it's important to add proper escaping and security to prevent SQL injections. This will be covered in the next article.


Congrats you made it to the end of tutorial 1!


Test out your creation, modify your content, and play around.

Go to your sitename/index.php to see your frontend after capturing data via sitename/backend/index.php

Next Up:


codewithme Now With Security, Functionality, and Aesthetics in mind.


Conclusion


Coding doesn’t have to be daunting and it’s my aim to divide a complex system into bitesized tutorials so you can truly use the knowledge you’ve acquired in your own projects.

SPONSORS