diff --git a/src/main/php/web/Application.class.php b/src/main/php/web/Application.class.php index ed5a92dc..3b0c2b05 100755 --- a/src/main/php/web/Application.class.php +++ b/src/main/php/web/Application.class.php @@ -6,7 +6,7 @@ * @test xp://web.unittest.ApplicationTest */ abstract class Application implements \lang\Value { - private $routing; + private $routing= null; protected $environment; /** diff --git a/src/main/php/web/Environment.class.php b/src/main/php/web/Environment.class.php index 540b1aa1..0ea41cd2 100755 --- a/src/main/php/web/Environment.class.php +++ b/src/main/php/web/Environment.class.php @@ -15,7 +15,7 @@ * @test xp://web.unittest.EnvironmentTest */ class Environment { - private $profile, $webroot, $docroot, $arguments; + private $profile, $webroot, $docroot, $arguments, $logging; private $sources= []; /** @@ -26,8 +26,9 @@ class Environment { * @param string|io.Path $docroot * @param (string|util.PropertySource)[] $config * @param string[] $arguments + * @param string|string[]|web.Logging $logging Defaults to logging to console */ - public function __construct($profile, $webroot, $docroot, $config, $arguments= []) { + public function __construct($profile, $webroot, $docroot, $config, $arguments= [], $logging= '-') { $this->profile= $profile; $this->webroot= $webroot instanceof Path ? $webroot : new Path($webroot); $this->docroot= $docroot instanceof Path ? $docroot : new Path($docroot); @@ -40,6 +41,7 @@ public function __construct($profile, $webroot, $docroot, $config, $arguments= [ $this->sources[]= new ResourcePropertySource($source); } } + $this->logging= $logging instanceof Logging ? $logging : Logging::of($logging); $this->arguments= $arguments; } @@ -52,6 +54,9 @@ public function webroot() { return $this->webroot; } /** @return io.Path */ public function docroot() { return $this->docroot; } + /** @return web.Logging */ + public function logging() { return $this->logging; } + /** * Gets properties * diff --git a/src/main/php/web/Logging.class.php b/src/main/php/web/Logging.class.php new file mode 100755 index 00000000..d26015a9 --- /dev/null +++ b/src/main/php/web/Logging.class.php @@ -0,0 +1,81 @@ +sink= $sink; + } + + /** @return ?web.log.Sink */ + public function sink() { return $this->sink; } + + /** + * Create an instance from a given command line argument + * + * @param string $arg + * @return self + */ + public static function of($arg) { + return new self(Sink::of($arg)); + } + + /** + * Pipe to a given sink + * + * @param var $sink + * @return self + */ + public function pipe($sink) { + if (null === $sink || $sink instanceof Sink) { + $this->sink= $sink; + } else { + $this->sink= Sink::of($sink); + } + return $this; + } + + /** + * Tee to a given sink + * + * @param var $sink + * @return self + */ + public function tee($sink) { + if (null === $this->sink) { + $this->pipe($sink); + } else { + $this->sink= new ToAllOf($this->sink, $sink); + } + return $this; + } + + /** + * Writes a log entry + * + * @param web.Request $response + * @param web.Response $response + * @param ?web.Error $error Optional error + * @return void + */ + public function log($request, $response, $error= null) { + $this->sink && $this->sink->log($request, $response, $error); + } + + /** + * Returns logging target + * + * @return string + */ + public function target() { + return $this->sink ? $this->sink->target() : '(no logging)'; + } +} \ No newline at end of file diff --git a/src/main/php/web/logging/Sink.class.php b/src/main/php/web/logging/Sink.class.php new file mode 100755 index 00000000..847985c8 --- /dev/null +++ b/src/main/php/web/logging/Sink.class.php @@ -0,0 +1,50 @@ +sinks= array_merge($this->sinks, $arg->sinks); + } else if ($arg instanceof parent) { + $this->sinks[]= $arg; + } else { + $this->sinks[]= parent::of($arg); + } + } + } + + /** @return web.log.Sink[] */ + public function sinks() { return $this->sinks; } + + /** @return string */ + public function target() { + $s= ''; + foreach ($this->sinks as $sink) { + $s.= ' & '.$sink->target(); + } + return '('.substr($s, 3).')'; + } + + /** + * Writes a log entry + * + * @param web.Request $response + * @param web.Response $response + * @param ?web.Error $error Optional error + * @return void + */ + public function log($request, $response, $error) { + foreach ($this->sinks as $sink) { + $sink->log($request, $response, $error); + } + } +} \ No newline at end of file diff --git a/src/main/php/web/logging/ToCategory.class.php b/src/main/php/web/logging/ToCategory.class.php new file mode 100755 index 00000000..d29ff140 --- /dev/null +++ b/src/main/php/web/logging/ToCategory.class.php @@ -0,0 +1,32 @@ +cat= $cat; + } + + /** @return string */ + public function target() { return nameof($this).'('.$this->cat->toString().')'; } + + /** + * Writes a log entry + * + * @param web.Request $response + * @param web.Response $response + * @param ?web.Error $error Optional error + * @return void + */ + public function log($request, $response, $error) { + $query= $request->uri()->query(); + $uri= $request->uri()->path().($query ? '?'.$query : ''); + + if ($message) { + $this->cat->warn($response->status(), $request->method(), $uri, $error); + } else { + $this->cat->info($response->status(), $request->method(), $uri); + } + } +} \ No newline at end of file diff --git a/src/main/php/web/logging/ToConsole.class.php b/src/main/php/web/logging/ToConsole.class.php new file mode 100755 index 00000000..6f2ea205 --- /dev/null +++ b/src/main/php/web/logging/ToConsole.class.php @@ -0,0 +1,28 @@ +uri()->query(); + Console::writeLinef( + " \e[33m[%s %d %.3fkB]\e[0m %d %s %s %s", + date('Y-m-d H:i:s'), + getmypid(), + memory_get_usage() / 1024, + $response->status(), + $request->method(), + $request->uri()->path().($query ? '?'.$query : ''), + $message + ); + } +} \ No newline at end of file diff --git a/src/main/php/web/logging/ToFile.class.php b/src/main/php/web/logging/ToFile.class.php new file mode 100755 index 00000000..51dff083 --- /dev/null +++ b/src/main/php/web/logging/ToFile.class.php @@ -0,0 +1,49 @@ +file= $file instanceof File ? $file->getURI() : $file; + if (false === file_put_contents($this->file, '', FILE_APPEND | LOCK_EX)) { + $e= new IllegalArgumentException('Cannot write to '.$this->file); + \xp::gc(__FILE__); + throw $e; + } + } + + /** @return string */ + public function target() { return nameof($this).'('.$this->file.')'; } + + /** + * Writes a log entry + * + * @param web.Request $response + * @param web.Response $response + * @param ?web.Error $error Optional error + * @return void + */ + public function log($request, $response, $error) { + $query= $request->uri()->query(); + $line= sprintf( + "[%s %d %.3fkB] %d %s %s %s\n", + date('Y-m-d H:i:s'), + getmypid(), + memory_get_usage() / 1024, + $response->status(), + $request->method(), + $request->uri()->path().($query ? '?'.$query : ''), + $message + ); + file_put_contents($this->file, $line, FILE_APPEND | LOCK_EX); + } +} \ No newline at end of file diff --git a/src/main/php/web/logging/ToFunction.class.php b/src/main/php/web/logging/ToFunction.class.php new file mode 100755 index 00000000..d92afd07 --- /dev/null +++ b/src/main/php/web/logging/ToFunction.class.php @@ -0,0 +1,22 @@ +function= cast($function, 'function(web.Request, web.Response, ?web.Error): void'); + } + + /** + * Writes a log entry + * + * @param web.Request $response + * @param web.Response $response + * @param ?web.Error $error Optional error + * @return void + */ + public function log($request, $response, $error) { + $this->function->__invoke($request, $response, $error); + } +} \ No newline at end of file diff --git a/src/main/php/xp/web/Runner.class.php b/src/main/php/xp/web/Runner.class.php index 8c359988..b7eaef27 100755 --- a/src/main/php/xp/web/Runner.class.php +++ b/src/main/php/xp/web/Runner.class.php @@ -31,6 +31,9 @@ * The address the server listens to can be supplied via *-a {host}[:{port}]*. * The profile can be changed via *-p {profile}* (and can be anything!). One * or more configuration sources may be passed via *-c {file.ini|dir}*. + * + * The webserver log is sent to standard output by default. It can be redirected + * to a file via *-l /path/to/logfile.log*. */ class Runner { private static $modes= [ @@ -86,6 +89,7 @@ public static function main($args) { $arguments= []; $config= []; $source= '.'; + $log= []; for ($i= 0; $i < sizeof($args); $i++) { if ('-r' === $args[$i]) { @@ -96,6 +100,8 @@ public static function main($args) { $profile= $args[++$i]; } else if ('-c' === $args[$i]) { $config[]= $args[++$i]; + } else if ('-l' === $args[$i]) { + $log[]= $args[++$i]; } else if ('-m' === $args[$i]) { $arguments= explode(',', $args[++$i]); $mode= array_shift($arguments); @@ -107,8 +113,15 @@ public static function main($args) { } } - $server= self::server($mode, $address, $arguments); - $server->serve($source, $profile, $webroot, $webroot->resolve($docroot), $config, array_slice($args, $i + 1)); + self::server($mode, $address, $arguments)->serve( + $source, + $profile, + $webroot, + $webroot->resolve($docroot), + $config, + array_slice($args, $i + 1), + $log ?: '-' + ); return 0; } } \ No newline at end of file diff --git a/src/main/php/xp/web/WebRunner.class.php b/src/main/php/xp/web/WebRunner.class.php index 6a5b8934..b66c8586 100755 --- a/src/main/php/xp/web/WebRunner.class.php +++ b/src/main/php/xp/web/WebRunner.class.php @@ -13,38 +13,16 @@ */ class WebRunner { - /** - * Logs a request - * - * @param web.Request $response - * @param web.Response $response - * @param string $message - * @return void - */ - private static function log($request, $response, $message= null) { - $query= $request->uri()->query(); - fprintf(STDOUT, - " \e[33m[%s %d %.3fkB]\e[0m %d %s %s %s\n", - date('Y-m-d H:i:s'), - getmypid(), - memory_get_usage() / 1024, - $response->status(), - $request->method(), - $request->uri()->path().($query ? '?'.$query : ''), - $message - ); - } - /** * Sends an error * * @param web.Request $response * @param web.Response $response - * @param web.Error $error - * @param string $profile + * @param web.Environment $env + * @param ?web.Error $error * @return void */ - private static function error($request, $response, $error, $profile) { + private static function error($request, $response, $env, $error) { if ($response->flushed()) { error_log($error->toString(), 4); // 4 = SAPI error logger } else { @@ -52,7 +30,7 @@ private static function error($request, $response, $error, $profile) { $message= Status::message($error->status()); $response->answer($error->status(), $message); - foreach (['web/error-'.$profile.'.html', 'web/error.html'] as $variant) { + foreach (['web/error-'.$env->profile().'.html', 'web/error.html'] as $variant) { if (!$loader->providesResource($variant)) continue; $response->send(sprintf( $loader->getResource($variant), @@ -64,12 +42,19 @@ private static function error($request, $response, $error, $profile) { break; } } - self::log($request, $response, $error->toString()); + $env->logging()->log($request, $response, $error); } /** @param string[] $args */ public static function main($args) { - $env= new Environment($args[2], $args[0], $args[1], explode('PATH_SEPARATOR', getenv('WEB_CONFIG')), explode('|', getenv('WEB_ARGS'))); + $env= new Environment( + $args[2], + $args[0], + $args[1], + explode('PATH_SEPARATOR', getenv('WEB_CONFIG')), + explode('|', getenv('WEB_ARGS')), + getenv('WEB_LOG') + ); $sapi= new SAPI(); $request= new Request($sapi); @@ -80,13 +65,13 @@ public static function main($args) { try { $application= (new Source(getenv('WEB_SOURCE'), $env))->application(); $application->service($request, $response); - self::log($request, $response); + $env->logging()->log($request, $response); } catch (Error $e) { - self::error($request, $response, $e, $args[2]); + self::error($request, $response, $env, $e); } catch (\Throwable $e) { // PHP7 - self::error($request, $response, new InternalServerError($e), $args[2]); + self::error($request, $response, $env, new InternalServerError($e)); } catch (\Exception $e) { // PHP5 - self::error($request, $response, new InternalServerError($e), $args[2]); + self::error($request, $response, $env, new InternalServerError($e)); } finally { $response->flushed() || $response->flush(); } diff --git a/src/main/php/xp/web/srv/Develop.class.php b/src/main/php/xp/web/srv/Develop.class.php index 14a10d92..78a6c2d1 100755 --- a/src/main/php/xp/web/srv/Develop.class.php +++ b/src/main/php/xp/web/srv/Develop.class.php @@ -9,6 +9,7 @@ use lang\archive\ArchiveClassLoader; use peer\Socket; use util\cmd\Console; +use web\Logging; class Develop implements Server { private $host, $port; @@ -33,8 +34,9 @@ public function __construct($host, $port) { * @param io.Path $docroot * @param string[] $config * @param string[] $args + * @param string[] $logging */ - public function serve($source, $profile, $webroot, $docroot, $config, $args) { + public function serve($source, $profile, $webroot, $docroot, $config, $args, $logging) { // PHP doesn't start with a nonexistant document root if (!$docroot->exists()) { @@ -66,9 +68,10 @@ public function serve($source, $profile, $webroot, $docroot, $config, $args) { putenv('WEB_CONFIG='.implode('PATH_SEPARATOR', $config)); putenv('WEB_ROOT='.$webroot); putenv('WEB_ARGS='.implode('|', $args)); + putenv('WEB_LOG='.$logging); Console::writeLine("\e[33m@", nameof($this), "(HTTP @ `php ", implode(' ', $arguments), "`)\e[0m"); - Console::writeLine("\e[1mServing ", $source, $config, "\e[0m"); + Console::writeLine("\e[1mServing ", $source, $config, "\e[0m > ", Logging::of($logging)->target()); Console::writeLine("\e[36m", str_repeat('═', 72), "\e[0m"); Console::writeLine(); diff --git a/src/main/php/xp/web/srv/HttpProtocol.class.php b/src/main/php/xp/web/srv/HttpProtocol.class.php index 63bfc1f0..c8285fc3 100755 --- a/src/main/php/xp/web/srv/HttpProtocol.class.php +++ b/src/main/php/xp/web/srv/HttpProtocol.class.php @@ -15,6 +15,7 @@ * @test xp://web.unittest.HttpProtocolTest */ class HttpProtocol implements ServerProtocol { + private $application, $logging; public $server= null; private $close= false; @@ -22,7 +23,7 @@ class HttpProtocol implements ServerProtocol { * Creates a new protocol instance * * @param web.Application $application - * @param function(web.Request, web.Response, ?lang.Throwable): void $logging + * @param web.Logging $logging */ public function __construct($application, $logging) { $this->application= $application; @@ -57,7 +58,7 @@ private function sendError($request, $response, $error) { break; } } - $this->logging->__invoke($request, $response, $error->toString()); + $this->logging->log($request, $response, $error); } /** @@ -125,7 +126,7 @@ public function handleData($socket) { try { $this->application->service($request, $response); - $this->logging->__invoke($request, $response); + $this->logging->log($request, $response); } catch (Error $e) { $this->sendError($request, $response, $e); } catch (\Throwable $e) { // PHP7 diff --git a/src/main/php/xp/web/srv/Server.class.php b/src/main/php/xp/web/srv/Server.class.php index 22c4abb3..f5647f78 100755 --- a/src/main/php/xp/web/srv/Server.class.php +++ b/src/main/php/xp/web/srv/Server.class.php @@ -11,6 +11,7 @@ interface Server { * @param io.Path $docroot * @param string[] $config * @param string[] $args + * @param string[] $logging */ - public function serve($source, $profile, $webroot, $docroot, $config, $args); + public function serve($source, $profile, $webroot, $docroot, $config, $args, $logging); } \ No newline at end of file diff --git a/src/main/php/xp/web/srv/Standalone.class.php b/src/main/php/xp/web/srv/Standalone.class.php index 3aafe698..c884e711 100755 --- a/src/main/php/xp/web/srv/Standalone.class.php +++ b/src/main/php/xp/web/srv/Standalone.class.php @@ -22,27 +22,17 @@ public function __construct($server, $url) { * @param io.Path $docroot * @param string[] $config * @param string[] $args + * @param string[] $logging */ - public function serve($source, $profile, $webroot, $docroot, $config, $args) { - $application= (new Source($source, new Environment($profile, $webroot, $docroot, $config, $args)))->application($args); - - $this->server->setProtocol(new HttpProtocol($application, function($request, $response, $message= null) { - $query= $request->uri()->query(); - Console::writeLinef( - " \e[33m[%s %d %.3fkB]\e[0m %d %s %s %s", - date('Y-m-d H:i:s'), - getmypid(), - memory_get_usage() / 1024, - $response->status(), - $request->method(), - $request->uri()->path().($query ? '?'.$query : ''), - $message - ); - })); + public function serve($source, $profile, $webroot, $docroot, $config, $args, $logging) { + $environment= new Environment($profile, $webroot, $docroot, $config, $args, $logging); + $application= (new Source($source, $environment))->application($args); + + $this->server->setProtocol(new HttpProtocol($application, $environment->logging())); $this->server->init(); Console::writeLine("\e[33m@", nameof($this), '(HTTP @ ', $this->server->socket->toString(), ")\e[0m"); - Console::writeLine("\e[1mServing ", $application, $config, "\e[0m"); + Console::writeLine("\e[1mServing ", $application, $config, "\e[0m > ", $environment->logging()->target()); Console::writeLine("\e[36m", str_repeat('═', 72), "\e[0m"); Console::writeLine(); diff --git a/src/test/php/web/unittest/HttpProtocolTest.class.php b/src/test/php/web/unittest/HttpProtocolTest.class.php index 111cda01..cc416edc 100755 --- a/src/test/php/web/unittest/HttpProtocolTest.class.php +++ b/src/test/php/web/unittest/HttpProtocolTest.class.php @@ -1,16 +1,17 @@ log= function($req, $res, $error= null) { }; + $this->log= new Logging(null); } private function application($handler) { diff --git a/src/test/php/web/unittest/LoggingTest.class.php b/src/test/php/web/unittest/LoggingTest.class.php new file mode 100755 index 00000000..f5bf2b8c --- /dev/null +++ b/src/test/php/web/unittest/LoggingTest.class.php @@ -0,0 +1,85 @@ +assertEquals($sink->target(), (new Logging($sink))->target()); + } + + #[@test] + public function no_logging_target() { + $this->assertEquals('(no logging)', (new Logging(null))->target()); + } + + #[@test, @values([ + # ['GET /', null], + # ['GET / Test', new Error(404, 'Test')], + #])] + public function log($expected, $error) { + $req= new Request(new TestInput('GET', '/')); + $res= new Response(new TestOutput()); + + $logged= []; + $log= new Logging(new ToFunction(function($req, $res, $error) use(&$logged) { + $logged[]= $req->method().' '.$req->uri()->path().($error ? ' '.$error->getMessage() : ''); + })); + $log->log($req, $res, $error); + + $this->assertEquals([$expected], $logged); + } + + #[@test] + public function pipe() { + $a= new ToFunction(function($req, $res, $error) { /* a */ }); + $b= new ToFunction(function($req, $res, $error) { /* b */ }); + $this->assertEquals($b, (new Logging($a))->pipe($b)->sink()); + } + + #[@test] + public function tee() { + $a= new ToFunction(function($req, $res, $error) { /* a */ }); + $b= new ToFunction(function($req, $res, $error) { /* b */ }); + $this->assertEquals(new ToAllOf($a, $b), (new Logging($a))->tee($b)->sink()); + } + + #[@test] + public function tee_multiple() { + $a= new ToFunction(function($req, $res, $error) { /* a */ }); + $b= new ToFunction(function($req, $res, $error) { /* b */ }); + $c= new ToFunction(function($req, $res, $error) { /* c */ }); + $this->assertEquals(new ToAllOf($a, $b, $c), (new Logging($a))->tee($b)->tee($c)->sink()); + } + + #[@test] + public function pipe_on_no_logging() { + $sink= new ToFunction(function($req, $res, $error) { }); + $this->assertEquals($sink, (new Logging(null))->pipe($sink)->sink()); + } + + #[@test] + public function tee_on_no_logging() { + $sink= new ToFunction(function($req, $res, $error) { }); + $this->assertEquals($sink, (new Logging(null))->tee($sink)->sink()); + } +} \ No newline at end of file diff --git a/src/test/php/web/unittest/logging/SinkTest.class.php b/src/test/php/web/unittest/logging/SinkTest.class.php new file mode 100755 index 00000000..0cb59be3 --- /dev/null +++ b/src/test/php/web/unittest/logging/SinkTest.class.php @@ -0,0 +1,62 @@ +assertNull(Sink::of($arg)); + } + + #[@test] + public function logging_to_console() { + $this->assertInstanceOf(ToConsole::class, Sink::of('-')); + } + + #[@test] + public function logging_to_function() { + $this->assertInstanceOf(ToFunction::class, Sink::of(function($req, $res, $error) { })); + } + + #[@test] + public function logging_to_file() { + $t= new TempFile('log'); + try { + $this->assertInstanceOf(ToFile::class, Sink::of($t)); + } finally { + $t->unlink(); + } + } + + #[@test] + public function logging_to_file_by_name() { + $t= new TempFile('log'); + try { + $this->assertInstanceOf(ToFile::class, Sink::of($t->getURI())); + } finally { + $t->unlink(); + } + } + + #[@test] + public function logging_to_all_of() { + $t= new TempFile('log'); + try { + $this->assertInstanceOf(ToAllOf::class, Sink::of(['-', $t])); + } finally { + $t->unlink(); + } + } + + #[@test] + public function logging_to_all_of_flattened_when_only_one_argument_passed() { + $this->assertInstanceOf(ToConsole::class, Sink::of(['-'])); + } +} \ No newline at end of file diff --git a/src/test/php/web/unittest/logging/ToAllOfTest.class.php b/src/test/php/web/unittest/logging/ToAllOfTest.class.php new file mode 100755 index 00000000..f876d0a5 --- /dev/null +++ b/src/test/php/web/unittest/logging/ToAllOfTest.class.php @@ -0,0 +1,77 @@ +assertEquals([$a, $b], (new ToAllOf($a, $b))->sinks()); + } + + #[@test] + public function sinks_are_merged_when_passed_ToAllOf_instance() { + $a= new ToConsole(); + $b= new ToFunction(function($req, $res, $error) { }); + $this->assertEquals([$a, $b], (new ToAllOf(new ToAllOf($a, $b)))->sinks()); + } + + #[@test] + public function sinks_are_empty_when_created_without_arg() { + $this->assertEquals([], (new ToAllOf())->sinks()); + } + + #[@test] + public function targets() { + $a= new ToConsole(); + $b= new ToFunction(function($req, $res, $error) { }); + $this->assertEquals('(web.logging.ToConsole & web.logging.ToFunction)', (new ToAllOf($a, $b))->target()); + } + + #[@test, @values([ + # [['a' => ['GET /'], 'b' => ['GET /']], null], + # [['a' => ['GET / Test'], 'b' => ['GET / Test']], new Error(404, 'Test')], + #])] + public function logs_to_all($expected, $error) { + $req= new Request(new TestInput('GET', '/')); + $res= new Response(new TestOutput()); + + $logged= ['a' => [], 'b' => []]; + $sink= new ToAllOf( + new ToFunction(function($req, $res, $error) use(&$logged) { + $logged['a'][]= $req->method().' '.$req->uri()->path().($error ? ' '.$error->getMessage() : ''); + }), + new ToFunction(function($req, $res, $error) use(&$logged) { + $logged['b'][]= $req->method().' '.$req->uri()->path().($error ? ' '.$error->getMessage() : ''); + }) + ); + $sink->log($req, $res, $error); + + $this->assertEquals($expected, $logged); + } +} \ No newline at end of file diff --git a/src/test/php/web/unittest/logging/ToFileTest.class.php b/src/test/php/web/unittest/logging/ToFileTest.class.php new file mode 100755 index 00000000..3947965f --- /dev/null +++ b/src/test/php/web/unittest/logging/ToFileTest.class.php @@ -0,0 +1,40 @@ +temp= new TempFile('sink'); + } + + /** @return void */ + public function tearDown() { + if ($this->temp->exists()) { + $this->temp->setPermissions(0600); + $this->temp->unlink(); + } + } + + #[@test] + public function can_create() { + new ToFile($this->temp); + } + + #[@test] + public function file_created_during_constructor_call() { + new ToFile($this->temp); + $this->assertTrue($this->temp->exists()); + } + + #[@test, @expect(IllegalArgumentException::class)] + public function raises_error_if_file_cannot_be_written_to() { + $this->temp->setPermissions(0000); + new ToFile($this->temp); + } +} \ No newline at end of file