Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
68.09% covered (warning)
68.09%
32 / 47
44.44% covered (danger)
44.44%
4 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Lock
68.09% covered (warning)
68.09%
32 / 47
44.44% covered (danger)
44.44%
4 / 9
45.32
0.00% covered (danger)
0.00%
0 / 1
 __construct
72.22% covered (warning)
72.22%
13 / 18
0.00% covered (danger)
0.00%
0 / 1
6.77
 filepath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 acquired
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 haveYouKilledFrozenProcess
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 release
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 isToKill
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isKilledFrozenProcess
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 mkPath
62.50% covered (warning)
62.50%
5 / 8
0.00% covered (danger)
0.00%
0 / 1
15.27
 mkLock
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
2.09
1<?php
2namespace DiegoSorrentino\Library;
3
4/**
5 * Lock class for PHP scripts
6 * Create a lock file in "<basePath>/processName.lock", containing the script PID, 
7 * and the maxLife (in minutes) for the process. When script is called it search for the lock file and:
8 * - if not exists, it is created
9 * - if exists but is expired (creationTime filectime + $this->maxLifeInMin*60 > NOW), kill the previuos PID, destroy and recreate lock
10 * - if exists and is not expired, simply dont acquire the lock
11 * 
12 * @param $processName     string     set filename for lock file. Default value 'file.lock'. It will be <basePath>/processName.lock
13 * @param $basePath     string     (nullable) set basePath to store lock file. Default value './' (if not exists it will be created)
14 * @param $maxLifeInMin    int    (nullable) set maxLife (in minutes) for the process. Default value is 30 min
15 */
16
17class Lock{
18    private int $maxLifeInMin = 30;
19
20    private string $path;
21    private string $filepath;
22
23    private bool $acquired = false;
24    private bool $killedFrozenProcess = false;
25    
26    public function __construct( string $processName, ?string $basePath = './', ?int $maxLifeInMin = 30){
27        $this->path = $basePath;
28        $this->maxLifeInMin = $maxLifeInMin;
29
30        if( ! $this->mkPath($this->path) )
31            throw new \RuntimeException(sprintf("[%s] Unable to create or write in %s directory", __METHOD__, $this->path) );
32
33        $this->filepath = sprintf('%s/%s%s', 
34                    $this->path,
35                    $processName,
36                    preg_match('/.lock$/', $processName) ? '' : '.lock');
37        
38        if( ! file_exists($this->filepath)){ 
39            $this->acquired = $this->mkLock();
40            return;
41        }
42        //else lock already exists... but i must check if is started before than $maxLifeInMin
43        if ( ! $this->isToKill() ){
44            error_log("[INF] {$this->filepath} <= maxLifeInMin: i wait");
45            return; 
46        }
47
48        error_log("[ERR] {$this->filepath} > maxLifeInMin: process must be killed");
49        if ( $this->isKilledFrozenProcess() ){
50            $this->acquired = $this->mkLock();
51            $this->killFrozenProcess = true;
52        }
53        
54    }
55
56    /**
57     * Get filepath url
58     *
59     * @return string lock file path
60     */
61    public  function filepath():string{ return $this->filepath; }
62
63    /**
64     * Get lock acquire status
65     *
66     * @return bool True if is acquired, false otherwise
67     */
68    public function acquired():bool { return $this->acquired; }
69
70    /**
71     * Get status about previuos process killed
72     *
73     * @return bool True if is killed, false otherwise
74     */
75    public function haveYouKilledFrozenProcess():bool { return $this->killedFrozenProcess; }
76
77    /**
78     * Get status lock release
79     *
80     * @return bool True if is released, false otherwise
81     */
82    public  function release():bool{
83        $released = unlink($this->filepath);
84        
85        error_log($released 
86                ? "[INF] Lock released"
87                : "[ERR] Impossible release lock"
88        );
89        return $released;
90    }
91
92    /**
93     * Check if previous executed process is to kill
94     *
95     * @return bool True if is to kill, false otherwise
96     */
97    private function isToKill():bool{ return ( filectime($this->filepath) + $this->maxLifeInMin*60 < time() ); }
98
99    /**
100     * Kill frozen process
101     *
102     * @return bool True when process is killed, false otherwise
103     */
104    private function isKilledFrozenProcess():bool{
105        $pid = file_get_contents($this->filepath);
106        unlink($this->filepath);
107        posix_kill($pid, SIGKILL);
108        return true;
109    }
110
111    /**
112     * Create path dir for lock files
113     * Check if file exists AND is directory AND is writable, or try to create/set it permission
114     * 
115     * @param string $path Path to try to create 
116     *
117     * @return bool True if file exists AND is directory AND is writable, false otherwise
118     */
119    private function mkPath(string $path):bool{
120        if( file_exists($path) && is_dir($path) && is_writable($path) )
121            return true;
122 
123        if( file_exists($path) && is_dir($path) && ! is_writable($path) )
124            return chmod($path, 0755);
125        
126        if( file_exists($path) && ! is_dir($path) ){
127            if ( ! unlink($path) )
128                return false; //and below i try to mkdir
129        }
130
131        //file not exists
132        return mkdir($path, 0755, true);
133    }
134
135    /**
136     * Create lock file containing process PID 
137     * 
138     * @return bool True if file exists AND is directory AND is writable, false otherwise
139     */
140    private function mkLock():bool{
141        $handler = fopen($this->filepath, 'w');
142
143        if( false == $handler ){
144            error_log(sprintf("[%s] Unable to create lock file %s", __METHOD__, $this->filepath));
145            return false;
146        }
147
148        fwrite( $handler, getmypid() );
149        fclose($handler);
150        return true;
151    }
152
153
154
155}
156
157?>