diff --git a/composer.json b/composer.json index da4e10c..2af6378 100644 --- a/composer.json +++ b/composer.json @@ -13,9 +13,10 @@ "sort-packages": true }, "require": { - "php": ">=7.4", + "php": ">=8.0", "ext-apcu": "*", "ext-curl": "*", + "ext-iconv": "*", "ext-json": "*", "ext-openssl": "*", "ext-redis": "*", @@ -23,14 +24,15 @@ "ext-simplexml": "*", "ext-sockets": "*", "ext-zip": "*", + "bjeavons/zxcvbn-php": "^1.0", "cache/apc-adapter": "^1.0", "cache/apcu-adapter": "^1.0", "cache/array-adapter": "^1.0", "cache/chain-adapter": "^1.0", "cache/redis-adapter": "^1.0", + "cocur/slugify": "^4.0", "doctrine/annotations": "^1.10", "donatj/flags": "^1.4", - "duncan3dc/fork-helper": "^2.0", "friendsofphp/php-cs-fixer": "^2.0", "fzaninotto/faker": "^1.9", "gone.io/inflection": "^2.1", diff --git a/phinx.php b/phinx.php new file mode 100644 index 0000000..d50e220 --- /dev/null +++ b/phinx.php @@ -0,0 +1,37 @@ +get(ORM\Connection\Databases::class) + ->getDatabase('default') + ->getAdapter() + ->getDriver() + ->getConnection() + ->getResource() +; +$name = $pdo->query('SELECT DATABASE()')->fetchColumn(0); + +return [ + 'paths' => [ + 'migrations' => [ + 'db/migrations', + ], + 'seeds' => [ + 'db/seeds', + ], + ], + 'migration_base_class' => ORM\Migrations\AbstractMigration::class, + 'environments' => [ + 'default_environment' => 'default', + 'default_migration_table' => 'Migrations', + 'default' => [ + 'adapter' => 'mysql', + 'connection' => $pdo, + 'name' => $name, + ], + ], +]; diff --git a/src/App.php b/src/App.php index c87b6bb..270c510 100644 --- a/src/App.php +++ b/src/App.php @@ -15,6 +15,7 @@ use Cache\Adapter\Apcu\ApcuCachePool; use Cache\Adapter\Chain\CachePoolChain; use Cache\Adapter\PHPArray\ArrayCachePool; use Cache\Adapter\Redis\RedisCachePool; +use Cocur\Slugify\Slugify; use DebugBar\Bridge\MonologCollector; use DebugBar\DataCollector\ExceptionsCollector; use DebugBar\DataCollector\MemoryCollector; @@ -336,11 +337,16 @@ class App ); }); - $container->set(Logger::class, function (ConfigurationService $configurationService, EnvironmentService $environmentService) { + $container->set(Logger::class, function (ConfigurationService $configurationService, EnvironmentService $environmentService, Slugify $slugify) { $appName = $configurationService->get(ConfigurationService::KEY_APP_NAME); $logName = $environmentService->has('REQUEST_URI') ? sprintf('%s(%s)', $appName, $environmentService->get('REQUEST_URI')) : $appName; $monolog = new Logger($logName); - $monolog->pushHandler(new StreamHandler(sprintf('%s/%s.log', $this->getLogPath(), strtolower($appName)))); + $monolog->pushHandler(new StreamHandler(sprintf( + '%s/%s.%s.log', + $this->getLogPath(), + $slugify->slugify($appName), + $slugify->slugify(PHP_SAPI) + ))); $monolog->pushHandler(new ErrorLogHandler(), Logger::DEBUG); $monolog->pushProcessor(new PsrLogMessageProcessor()); diff --git a/src/Controllers/AbstractController.php b/src/Controllers/AbstractController.php index 4239f13..826875f 100644 --- a/src/Controllers/AbstractController.php +++ b/src/Controllers/AbstractController.php @@ -4,7 +4,6 @@ namespace Benzine\Controllers; use Benzine\Controllers\Filters\Filter; use Benzine\Exceptions\FilterDecodeException; -use Benzine\ORM\Abstracts\AbstractService; use League\Flysystem\Filesystem; use League\MimeTypeDetection\ExtensionMimeTypeDetector; use League\MimeTypeDetection\FinfoMimeTypeDetector; @@ -15,40 +14,10 @@ use Slim\Psr7\Response; abstract class AbstractController { - protected Logger $logger; - protected AbstractService $service; - protected bool $apiExplorerEnabled = true; - protected CacheProvider $cacheProvider; - - public function __construct(Logger $logger, CacheProvider $cacheProvider) - { - $this->logger = $logger; - //$this->logger->debug(sprintf('Entered Controller in %sms', number_format((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']) * 1000, 2))); - $this->cacheProvider = $cacheProvider; - } - - public function getService(): AbstractService - { - return $this->service; - } - - public function setService(AbstractService $service): self - { - $this->service = $service; - - return $this; - } - - public function isApiExplorerEnabled(): bool - { - return $this->apiExplorerEnabled; - } - - public function setApiExplorerEnabled(bool $apiExplorerEnabled): self - { - $this->apiExplorerEnabled = $apiExplorerEnabled; - - return $this; + public function __construct( + protected Logger $logger, + protected CacheProvider $cacheProvider + ) { } public function xmlResponse(\SimpleXMLElement $root, Request $request, Response $response): Response diff --git a/src/Controllers/AbstractHTMLController.php b/src/Controllers/AbstractHTMLController.php index 996a546..e4884dd 100644 --- a/src/Controllers/AbstractHTMLController.php +++ b/src/Controllers/AbstractHTMLController.php @@ -11,19 +11,15 @@ use Slim\Views\Twig; abstract class AbstractHTMLController extends AbstractController { - protected Twig $twig; - protected DebugBar $debugBar; protected string $pageNotFoundTemplate = '404.html.twig'; public function __construct( Logger $logger, CacheProvider $cacheProvider, - Twig $twig, - DebugBar $debugBar + protected Twig $twig, + protected DebugBar $debugBar ) { parent::__construct($logger, $cacheProvider); - $this->twig = $twig; - $this->debugBar = $debugBar; } protected function renderInlineCss(array $files) diff --git a/src/Controllers/Filters/Filter.php b/src/Controllers/Filters/Filter.php index 5dab8ea..b6a9d7d 100644 --- a/src/Controllers/Filters/Filter.php +++ b/src/Controllers/Filters/Filter.php @@ -46,18 +46,22 @@ class Filter $this->setLimit($value); break; + case 'offset': $this->setOffset($value); break; + case 'wheres': $this->setWheres($value); break; + case 'order': $this->parseOrder($value); break; + default: throw new FilterDecodeException("Failed to decode Filter, unknown key: {$key}"); } diff --git a/src/Models/Traits/AuthableTrait.php b/src/Models/Traits/AuthableTrait.php new file mode 100644 index 0000000..bb2edf5 --- /dev/null +++ b/src/Models/Traits/AuthableTrait.php @@ -0,0 +1,30 @@ +password = password_hash($password, PASSWORD_DEFAULT); + + // Handle updating the last password change time if enabled + if (method_exists($this, 'setPasswordLastUpdatedAt')) { + $this->setPasswordLastUpdatedAt(Carbon::now()); + } + + // Handle password scores if enabled + if (method_exists($this, 'setPasswordStrengthScore')) { + $this->setPasswordStrengthScore((new Zxcvbn())->passwordStrength($password)['score']); + } + + // Save entity. + $this->save(); + + return $this; + } +} diff --git a/src/Services/ConfigurationService.php b/src/Services/ConfigurationService.php index 173cb37..59a8901 100644 --- a/src/Services/ConfigurationService.php +++ b/src/Services/ConfigurationService.php @@ -3,6 +3,7 @@ namespace Benzine\Services; use Benzine\App; +use Benzine\Exceptions\BenzineConfigurationException; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Yaml\Yaml; @@ -23,6 +24,8 @@ class ConfigurationService protected string $appRoot; protected array $config; + protected array $configNotFoundInPaths = []; + public function __construct(App $app, EnvironmentService $environmentService) { $this->app = $app; @@ -106,11 +109,20 @@ class ConfigurationService } if (!(new Filesystem())->exists($path.'/.benzine.yml')) { + $this->configNotFoundInPaths[] = $path; $currentDirElem = explode(DIRECTORY_SEPARATOR, $path); array_pop($currentDirElem); $parentPath = implode(DIRECTORY_SEPARATOR, $currentDirElem); - return $this->findConfig($parentPath); + if (!in_array($parentPath, $this->configNotFoundInPaths, true)) { + return $this->findConfig($parentPath); + } + $this->configNotFoundInPaths = array_unique($this->configNotFoundInPaths); + + throw new BenzineConfigurationException(sprintf( + 'Cannot find .benzine.yml in any of the following locations: %s', + implode(', ', $this->configNotFoundInPaths) + )); } $this->parseFile($path.'/.benzine.yml'); diff --git a/src/Twig/Extensions/TransformExtension.php b/src/Twig/Extensions/TransformExtension.php index 16745a9..fe4b504 100644 --- a/src/Twig/Extensions/TransformExtension.php +++ b/src/Twig/Extensions/TransformExtension.php @@ -55,18 +55,23 @@ class TransformExtension extends AbstractExtension case 'camel': case 'camelcase': return new Format\CamelCase(); + case 'screaming': case 'screamingsnake': case 'screamingsnakecase': return new Format\ScreamingSnakeCase(); + case 'snake': case 'snakecase': return new Format\SnakeCase(); + case 'spinal': case 'spinalcase': return new Format\SpinalCase(); + case 'studly': return new Format\StudlyCaps(); + default: throw new TransformExtensionException("Unknown transformer: \"{$name}\"."); } diff --git a/src/Workers/WorkerWorkItem.php b/src/Workers/WorkerWorkItem.php index df0cee7..411335c 100644 --- a/src/Workers/WorkerWorkItem.php +++ b/src/Workers/WorkerWorkItem.php @@ -19,8 +19,10 @@ class WorkerWorkItem implements \Serializable $this->data[$field] = $arguments[0]; return $this; + case 'get': return $this->data[$field]; + default: throw new WorkerException("Method {$name} doesn't exist"); }