1<?php 2 3namespace Rubix\ML\Persisters; 4 5use Rubix\ML\Encoding; 6use Rubix\ML\Persistable; 7use Rubix\ML\Other\Helpers\Params; 8use Rubix\ML\Persisters\Serializers\Native; 9use Rubix\ML\Persisters\Serializers\Serializer; 10use Rubix\ML\Exceptions\RuntimeException; 11use League\Flysystem\Filesystem; 12use League\Flysystem\FilesystemOperator; 13use League\Flysystem\FilesystemException; 14 15/** 16 * Flysystem 17 * 18 * Flysystem is a filesystem library providing a unified storage interface and abstraction layer. 19 * It enables access to many different storage backends such as Local, Amazon S3, FTP, and more. 20 * 21 * > **Note:** The Flysystem persister is designed to work with Flysystem version 2.0. 22 * 23 * @see https://flysystem.thephpleague.com 24 * 25 * @category Machine Learning 26 * @package Rubix/ML 27 * @author Chris Simpson 28 */ 29class Flysystem implements Persister 30{ 31 /** 32 * The extension to give files created as part of a persistable's save history. 33 * 34 * @var string 35 */ 36 public const HISTORY_EXT = 'old'; 37 38 /** 39 * The path to the model file on the filesystem. 40 * 41 * @var string 42 */ 43 protected $path; 44 45 /** 46 * The filesystem implementation providing access to your backend storage. 47 * 48 * @var \League\Flysystem\FilesystemOperator 49 */ 50 protected $filesystem; 51 52 /** 53 * Should we keep a history of past saves? 54 * 55 * @var bool 56 */ 57 protected $history; 58 59 /** 60 * The serializer used to convert to and from serial format. 61 * 62 * @var \Rubix\ML\Persisters\Serializers\Serializer 63 */ 64 protected $serializer; 65 66 /** 67 * @param string $path 68 * @param \League\Flysystem\FilesystemOperator $filesystem 69 * @param bool $history 70 * @param \Rubix\ML\Persisters\Serializers\Serializer|null $serializer 71 */ 72 public function __construct( 73 string $path, 74 FilesystemOperator $filesystem, 75 bool $history = false, 76 ?Serializer $serializer = null 77 ) { 78 $this->path = $path; 79 $this->filesystem = $filesystem; 80 $this->history = $history; 81 $this->serializer = $serializer ?? new Native(); 82 } 83 84 /** 85 * Save the persistable object. 86 * 87 * @param \Rubix\ML\Persistable $persistable 88 * @throws \RuntimeException 89 */ 90 public function save(Persistable $persistable) : void 91 { 92 if ($this->history and $this->filesystem->fileExists($this->path)) { 93 $timestamp = time(); 94 95 $filename = "{$this->path}-$timestamp." . self::HISTORY_EXT; 96 97 $num = 0; 98 99 while ($this->filesystem->fileExists($filename)) { 100 $filename = "{$this->path}-$timestamp-" . ++$num . '.' . self::HISTORY_EXT; 101 } 102 103 try { 104 $this->filesystem->move($this->path, $filename); 105 } catch (FilesystemException $e) { 106 throw new RuntimeException("Failed to create history file '$filename'."); 107 } 108 } 109 110 $encoding = $this->serializer->serialize($persistable); 111 112 try { 113 $this->filesystem->write($this->path, $encoding); 114 } catch (FilesystemException $e) { 115 throw new RuntimeException('Could not write to filesystem.'); 116 } 117 } 118 119 /** 120 * Load the last model that was saved. 121 * 122 * @throws \RuntimeException 123 * @return \Rubix\ML\Persistable 124 */ 125 public function load() : Persistable 126 { 127 if (!$this->filesystem->fileExists($this->path)) { 128 throw new RuntimeException("File does not exist at {$this->path}."); 129 } 130 131 try { 132 $data = $this->filesystem->read($this->path); 133 } catch (FilesystemException $e) { 134 throw new RuntimeException("Error reading data from {$this->path}."); 135 } 136 137 $encoding = new Encoding($data); 138 139 if ($encoding->bytes() === 0) { 140 throw new RuntimeException("File at {$this->path} does not contain any data."); 141 } 142 143 return $this->serializer->unserialize($encoding); 144 } 145 146 /** 147 * Return the string representation of the object. 148 * 149 * @return string 150 */ 151 public function __toString() : string 152 { 153 return "Flysystem (path: {$this->path}, filesystem: " 154 . Params::toString($this->filesystem) . ', history: ' 155 . Params::toString($this->history) . ', serializer: ' 156 . "{$this->serializer})"; 157 } 158} 159