Tobias Hahner: wie __destruct sinnvoll nutzen?

Beitrag lesen

Hallihallo!

Doku liest doch keiner. Da steht sowieso nie das drin, was man wissen will ...

Ist das wirklich Dein Ernst? Da hatte ich Dich vollkommen anders eingeschätzt. Ich habe bisher zumindest immer die Dokus überflogen, den Teil "Designrichtlinien" (oder ähnlich, heisst ja immer anders) habe ich sogar immer aufmerksam gelesen... Einmal hatte ich ein Projekt vor mir, das keine ordentliche Doku hatte, da wäre ich fast abgedreht...

Mein Destruktor schließt am Ende des Arbeitstages lediglich das Zeug vom Schreibtisch in den Schrank ein. Deiner aber nimmt vorher auch noch den Stapel der erzeugten Geschäftsbriefe, tütet sie in Umschläge, frankiert diese und legt sie in den Postausgang.

Nein, das tut er nicht :-) Ich stelle fest, dass ich anscheinend kein guter Erklärbär bin...

Bei dir muss man Feierabend machen, damit es die Post versendet. Das kann man auch dokumentieren, aber "Zum Post versenden machen Sie bitte Feierabend!" ist schon etwas seltsam.

Neinneinnein :-) (s.o.)

Da ich anscheinend arge Probleme habe, Programmtechnisches umgangssprachlich zu erklären, zeige ich Dir einfach mal den nicht ganz so stark vereinfachten Code. Vielleicht siehst Du dann, dass er im Endeffekt genau das tut, was Du , nur anders verpackt:

Die Klasse log: (bedenke bitte, sie hat noch die Versionsnummer 0.0.2, ist also durchaus verbesserungswürdig! Zum Beispiel sind konkurrierende Zugriffe auf logfiles noch nicht bedacht...


class log {
		// severity codes, used for writing the log file. No effect on recording, though
		const NONE = 0;
		const DEBUG = 1;
		const INFO = 2;
		const NOTICE = 4;
		const WARNING = 8;
		const ERROR = 16;
		const ALL = 255;

		/**
		 * @var resource the logfile
		 * @access protected
		 */
		protected $logfile = null;
		
		/**
		 * @var string Fully qualified name of the logfile.
		 * @access protected
		 */
		protected $logfilename = '';
		
		/**
		 * @var string the content to be written
		 * @access protected
		 */
		protected $logcontent = '';
		
		/**
		 * @var array
		 * @access protected
		 * the collected messages
		 */
		protected $messages;
		
		
		/**
		 * @var integer the contents to log (bitwise on or off)
		 */
		protected $loglevel = 0;
		
		/**
		 * @var bool
		 * flag to differ between paranoid (true) and lazy (false) mode
		 * should only be set for debugging, because its not threadsafe!
		 * True also uses more resources, as every message will be saved to the logfile immediately
		 */
		protected $paranoia;
		
		
		/**
		 * constructor
		 * sets the loglevel and, if not off, opens the logfile for write access
		 * @access public
		 * @param $logfile
		 * @param $loglevel
		 * @param $paranoia
		 */
		public function __construct($logfile, $loglevel, $paranoia=false) {
			$this->messages = array();
			if ( ($loglevel == 'NONE')||($loglevel === 0) ) {
				$this->loglevel = 0;
				$this->logfile = null;
				$this->logcontent = '';
				$this->paranoia = false;
				$this->logfilename = $logfile;
				return;
			}
			if (intval($loglevel) === $loglevel) {
				// ok, some nerd doesnt need words...
				$this->loglevel = $loglevel;
				if ($paranoia) {
					$this->logfile = @fopen($logfile, 'a+');
					if (FALSE === $this->logfile) $this->logfilename = '/var/www/files/logs/'.date("Y-m-d").'-generic.log';
					$this->logfile = fopen($this->logfilename, 'a+');
				}
				$this->logfilename = $logfile;
				$this->paranoia = $paranoia;
				$this->logcontent = date("Y-m-d H:i:s").' ---------- START LOGGING, INITIALIZED IN NERD MODE, LOGLEVEL='.$this->loglevel."\n";
				return;
			}
			$levs = array(
				'NONE'		=> 0,
				'DEBUG'		=> self::DEBUG,
				'INFO'		=> self::INFO,
				'NOTICE'	=> self::NOTICE,
				'WARNING'	=> self::WARNING,
				'ERROR'		=> self::ERROR,
				'ALL'		=> self::ALL
			);
			$this->loglevel = 0;
			$levels = explode('|', $loglevel);
			foreach ($levels as $part) {
				$part = trim($part);
				$cleanpart = str_replace('~', '', $part);
				$multiplikator = (FALSE === strpos($part, '~')) ? 1 : (-1);
				if (array_key_exists($cleanpart, $levs)) {
					$this->loglevel += $multiplikator*$levs[$cleanpart];
				}
			}
			if ($this->loglevel != 0) {
				$this->logfilename = $logfile;
				if ($paranoia) $this->logfile = @fopen($logfile, 'a+');
				if (FALSE === $this->logfile) $this->logfilename = '/var/www/files/logs/'.date("Y-m-d").'-generic.log';
				//$this->logfile = fopen($this->logfilename, 'a+');
				$this->paranoia = $paranoia;
				$this->logcontent = date("Y-m-d H:i:s").' ---------- START LOGGING, INITIALIZED IN NORMAL MODE, LOGLEVEL='.$this->loglevel.' ('.$loglevel.')'."\n";
			}
			return;
		}

		/**
		 * destructor
		 * in lazy mode, opens the logfile, writes the content
		 * in paranoid and lazy mode, closes the logfile
		 * @access public
		 * @return void
		 */
		public function __destruct() {
			if ($this->logfilename == '') return;
			if (!$this->paranoia) {
				$this->logfile = fopen($this->logfilename, 'a+');
				fwrite($this->logfile, $this->logcontent);
			}
			if (!is_null($this->logfile)) {
				fclose($this->logfile);
			}
		}

		/**
		 * savelog
		 * the main function of class log.
		 * receives the text and the loglevel. determines, whether the text has to be saved,
		 * and then eventually saves it.
		 * in lazy mode, the text will be in ram ($this->logcontent) until this object is destructed
		 * in paranoid mode, the text will immediately be stored in the logfile
		 * Regardless of paranoia and loglevel, all messages will be stored in ram for eventual later use
		 * @param string $text
		 * @param integer $level
		 * @return bool
		 * @version 0.0.1 initial implementation
		 * @version 0.0.2 added microtime recording
		 */
		public function savelog($text,$level) {
			// store the message anyway, no matter what the loglevel is:
			$this->messages[] = array('time'=>microtime(true),'level'=>$level,'message'=>$text);
			if ( ($level & $this->loglevel) == $level) {
				$this->logcontent .= date("Y-m-d H:i:s").' ';
				switch ($level) {
					case 1 :	$this->logcontent .= 'DEBUG     ';
								break;
					case 2 :	$this->logcontent .= 'INFO      ';
								break;
					case 4 :	$this->logcontent .= 'NOTICE    ';
								break;
					case 8 :	$this->logcontent .= 'WARNING   ';
								break;
					case 16:	$this->logcontent .= 'ERROR     ';
								break;
					default:	break;	
				}
				$this->logcontent .= $text."\n";
				if ($this->paranoia) {
					$success = fwrite($this->logfile, $this->logcontent);
					$this->logcontent = '';
					return $success;
				}
				return true;
			}
		}

		/**
		 * debug
		 * a wrapper method for convenient logging
		 * @access public
		 * @param string $text
		 * @return bool
		 */
		public function debug($text){
			return $this->savelog($text, self::DEBUG);
		}
		
		/**
		 * info
		 * a wrapper method for convenient logging
		 * @access public
		 * @param string $text
		 * @return bool
		 */
		public function info($text) {
			return $this->savelog($text, self::INFO);
		}
		
		/**
		 * notice
		 * a wrapper method for convenient logging
		 * @access public
		 * @param string $text
		 * @return bool
		 */
		public function notice($text) {
			return $this->savelog($text, self::NOTICE);
		}
		
		/**
		 * warning
		 * a wrapper method for convenient logging
		 * @access public
		 * @param string $text
		 * @return bool
		 */
		public function warning($text) {
			return $this->savelog($text, self::WARNING);
		}
		
		/**
		 * error
		 * a wrapper method for convenient logging
		 * @access public
		 * @param string $text
		 * @return bool
		 */
		public function error($text) {
			return $this->savelog($text, self::ERROR);
		}

Die Klasse logs (Das ist quasi der globale Scope, den Du angesprochen hattest, um von überall Zugriff auf alle log- Objekte zu haben)


	abstract class logs {
		protected static $logs = array();
		protected static $directory = null;
		protected static $time = '';
		protected static $sortcrit = 'time';
		protected static $filters = array();
		
		const DIR = '/var/www/files/logs/';
		
		/**
		 * init
		 * For internal use only
		 * Sets the log directory (creates if does not exist)
		 * @access protected
		 * @return void
		 * @since 0.0.1
		 */
		protected function init() {
			$date = date("Y-m-d");
			if (!is_dir(self::DIR.$date)) {
				mkdir(self::DIR.$date);
			}
			self::$time = date("H_i");
			self::$directory = self::DIR.$date.'/';
		}
		
		
		/**
		 * getInstance
		 * This is the function every other class should call for creating a log.
		 * Calls the log constructor and stores the log object in the static class var $logs
		 * @param string $name identifier for the log
		 * @param mixed $loglevel The loglevel. See log::__construct for details
		 * @return log the requested log object
		 */
		static public function getInstance($name,$loglevel,$paranoia=false) {
			if (is_null(self::$directory)) self::init();
			if (!array_key_exists($name, self::$logs)) {
				// needed for most identifiers as they must not contain characters confusing the file structure
				$filename = str_replace(array('\\','/',':'), '_', $name);
				$new = new log(self::$directory.self::$time.'-'.$filename.'.log', $loglevel,$paranoia);
				self::$logs[$name] = $new;
				self::$filters[$name] = 'ALL';		// required for getMessages()
			}
			return self::$logs[$name];
		}

		/**
		 * getMessages
		 * returns the messages of all log objects created by this class.
		 * The messages can be specified by the parameter.
		 * The messages are returned in the same format as if called log::getMessages
		 * The returned messages will be sorted by the given second parameter.
		 * If the given parameter is not 'time', 'time' will be used as second criteria for sorting
		 * @see log::getMessages
		 * @since 0.0.1
		 * @param mixed $loglevel
		 * @param string $sortBy The key the messages shall be sorted. Can be any array key used in log::getMessages(). SHOULD be 'time' or 'identifier'
		 * @param bool $useFilters If set to true, messages will be collected using individual loglevels.
		 * @return array the requested messages
		 * @uses log::getMessages
		 */
		static public function getMessages($loglevel,$sortBy='time',$useFilters=true) {
			$ret = array();
			// collect data
			foreach (self::$logs as $identifier => $logobj) {
				$level = $loglevel;
				if ($useFilters) $level = self::$filters[$identifier];
				$mess = $logobj->getMessages($level);
				foreach ($mess as $entry) {
					// will produce
					// $ret[$counter] = array('identifier'=>..., 'time'=>..., 'level'=>..., 'leveltext'=>... , 'message'=>...)
					$tmp = $entry;
					$tmp['identifier'] = $identifier;
					$ret[] = $tmp;
				}
			}
			// sort data
			self::$sortcrit = $sortBy;
			$sorted = uasort($ret, array('self','sort'));
			if ($sorted) return $ret;
			return $sorted;
		}

Und zu guter letzt noch ein Beispiel, wie es benutzt wird:


	class example {
		protected $log;
		protected $foo;
		
		public function __construct($bar) {
			$this->log = logs::getInstance('example', 'ALL|~DEBUG');
			$this->foo = $bar;
		}
		
		public function setFoo($baz) {
			$this->log->debug('setting foo to '.$baz);
			$this->foo = $baz;
		}
	}

Ich hoffe, durch den Code erkennt man a) dass wir anscheinend aneinander vorbeigeredet haben, denn b) der Destruktor nicht "die Briefe sortiert und eintütet" sondern lediglich "den Stapel vom    Schreibtisch in den Safe schliesst", und, zu guter letzt, dass c) Ich mir manchmal echt schwer tue beim Erklären. Worte sind noch weniger meine Stärke als    Quellcode...

Beste Grüsse,     Tobias Hahner