diff --git a/Account/Account.php b/Account/Account.php index 32c6174af..efed51bb9 100644 --- a/Account/Account.php +++ b/Account/Account.php @@ -21,7 +21,10 @@ use phpOMS\Localization\NullLocalization; use phpOMS\Validation\Network\Email; /** - * Account manager class. + * Account class. + * + * The account class is the base model for accounts. This model contains the most common account + * information. This model is not comparable to a profile which contains much more information. * * @category Framework * @package phpOMS\Account @@ -109,7 +112,7 @@ class Account implements ArrayableInterface, \JsonSerializable /** * Permissions. * - * @var array + * @var PermissionAbstract[] * @since 1.0.0 */ protected $permissions = []; @@ -125,7 +128,7 @@ class Account implements ArrayableInterface, \JsonSerializable /** * Password. * - * @var Password + * @var string * @since 1.0.0 */ protected $password = ''; @@ -156,6 +159,8 @@ class Account implements ArrayableInterface, \JsonSerializable /** * Constructor. + * + * The constructor automatically sets the created date as well as the last activity to now. * * @param int $id Account id * @@ -183,6 +188,8 @@ class Account implements ArrayableInterface, \JsonSerializable /** * Get localization. + * + * Every account can have a different localization which can be accessed here. * * @return Localization * @@ -193,6 +200,35 @@ class Account implements ArrayableInterface, \JsonSerializable return $this->l11n; } + /** + * Get groups. + * + * Every account can belong to multiple groups. + * These groups usually are used for permissions and categorize accounts. + * + * @return array Returns array of all groups + * + * @since 1.0.0 + */ + public function getGroups() : array + { + return $this->groups; + } + + /** + * Add group. + * + * @param mixed $group Group to add + * + * @return void + * + * @since 1.0.0 + */ + public function addGroup($group) /* : void */ + { + $this->groups[] = $group; + } + /** * Set localization. * @@ -207,6 +243,102 @@ class Account implements ArrayableInterface, \JsonSerializable $this->l11n = $l11n; } + /** + * Set permissions. + * + * The method accepts an array of permissions. All existing permissions are replaced. + * + * @param PermissionAbstract[] $permissions + * + * @return void + * + * @since 1.0.0 + */ + public function setPermissions(array $permissions) /* : void */ + { + $this->permissions = $permissions; + } + + /** + * Add permissions. + * + * Adds permissions to the account + * + * @param PermissionAbstract[] $permissions Array of permissions to add to the account + * + * @return void + * + * @since 1.0.0 + */ + public function addPermissions(array $permissions) /* : void */ + { + $this->permissions = array_merge($this->permissions, $permissions); + } + + /** + * Add permission. + * + * Adds a single permission to the account + * + * @param PermissionAbstract $permission Permission to add to the account + * + * @return void + * + * @since 1.0.0 + */ + public function addPermission(PermissionAbstract $permission) /* : void */ + { + $this->permissions[] = $permission; + } + + /** + * Get permissions. + * + * @return array + * + * @since 1.0.0 + */ + public function getPermissions() : array + { + return $this->permissions; + } + + /** + * Has permissions. + * + * Checks if the account has a permission defined + * + * @param int $permission Permission to check + * @param int $unit Unit Unit to check (null if all are acceptable) + * @param string $app App App to check (null if all are acceptable) + * @param int $module Module Module to check (null if all are acceptable) + * @param int $type Type (e.g. customer) (null if all are acceptable) + * @param int $element (e.g. customer id) (null if all are acceptable) + * @param int $component (e.g. address) (null if all are acceptable) + * + * @return bool Returns true if the account has the permission, false otherwise + * + * @since 1.0.0 + */ + public function hasPermission(int $permission, int $unit = null, string $app = null, int $module = null, int $type = null, $element = null, $component = null) : bool + { + $app = isset($app) ? strtolower($app) : $app; + + foreach($this->permissions as $p) { + if(($p->getUnit() === $unit || $p->getUnit() === null || !isset($unit)) + && ($p->getApp() === $app || $p->getApp() === null || !isset($app)) + && ($p->getModule() === $module || $p->getModule() === null || !isset($module)) + && ($p->getType() === $type || $p->getType() === null || !isset($type)) + && ($p->getElement() === $element || $p->getElement() === null || !isset($element)) + && ($p->getComponent() === $component || $p->getComponent() === null || !isset($component)) + && ($p->getPermission() | $permission) === $p->getPermission()) { + return true; + } + } + + return false; + } + /** * Get name. * @@ -231,6 +363,20 @@ class Account implements ArrayableInterface, \JsonSerializable return $this->name1; } + /** + * Set name1. + * + * @param string $name Name + * + * @return void + * + * @since 1.0.0 + */ + public function setName1(string $name) /* : void */ + { + $this->name1 = $name; + } + /** * Get name2. * @@ -243,6 +389,20 @@ class Account implements ArrayableInterface, \JsonSerializable return $this->name2; } + /** + * Set name2. + * + * @param string $name Name + * + * @return void + * + * @since 1.0.0 + */ + public function setName2(string $name) /* : void */ + { + $this->name2 = $name; + } + /** * Get name3. * @@ -255,6 +415,20 @@ class Account implements ArrayableInterface, \JsonSerializable return $this->name3; } + /** + * Set name3. + * + * @param string $name Name + * + * @return void + * + * @since 1.0.0 + */ + public function setName3(string $name) /* : void */ + { + $this->name3 = $name; + } + /** * Get email. * @@ -267,6 +441,26 @@ class Account implements ArrayableInterface, \JsonSerializable return $this->email; } + /** + * Set email. + * + * @param string $email Email + * + * @return void + * + * @throws \InvalidArgumentException Exception is thrown if the provided string is not a valid email + * + * @since 1.0.0 + */ + public function setEmail(string $email) /* : void */ + { + if (!Email::isValid($email)) { + throw new \InvalidArgumentException(); + } + + $this->email = mb_strtolower($email); + } + /** * Get status. * @@ -281,6 +475,24 @@ class Account implements ArrayableInterface, \JsonSerializable return $this->status; } + /** + * Get status. + * + * @param int $status Status + * + * @return void + * + * @since 1.0.0 + */ + public function setStatus(int $status) /* : void */ + { + if (!AccountStatus::isValidValue($status)) { + throw new \InvalidArgumentException(); + } + + $this->status = $status; + } + /** * Get type. * @@ -295,6 +507,24 @@ class Account implements ArrayableInterface, \JsonSerializable return $this->type; } + /** + * Get type. + * + * @param int $type Type + * + * @return void + * + * @since 1.0.0 + */ + public function setType(int $type) /* : void */ + { + if (!AccountType::isValidValue($type)) { + throw new \InvalidArgumentException(); + } + + $this->type = $type; + } + /** * Get last activity. * @@ -347,102 +577,6 @@ class Account implements ArrayableInterface, \JsonSerializable $this->login = $name; } - /** - * Set name1. - * - * @param string $name Name - * - * @return void - * - * @since 1.0.0 - */ - public function setName1(string $name) /* : void */ - { - $this->name1 = $name; - } - - /** - * Set name2. - * - * @param string $name Name - * - * @return void - * - * @since 1.0.0 - */ - public function setName2(string $name) /* : void */ - { - $this->name2 = $name; - } - - /** - * Set name3. - * - * @param string $name Name - * - * @return void - * - * @since 1.0.0 - */ - public function setName3(string $name) /* : void */ - { - $this->name3 = $name; - } - - /** - * Set email. - * - * @param string $email Email - * - * @return void - * - * @since 1.0.0 - */ - public function setEmail(string $email) /* : void */ - { - if (!Email::isValid($email)) { - throw new \InvalidArgumentException(); - } - - $this->email = mb_strtolower($email); - } - - /** - * Get status. - * - * @param int $status Status - * - * @return void - * - * @since 1.0.0 - */ - public function setStatus(int $status) /* : void */ - { - if (!AccountStatus::isValidValue($status)) { - throw new \InvalidArgumentException(); - } - - $this->status = $status; - } - - /** - * Get type. - * - * @param int $type Type - * - * @return void - * - * @since 1.0.0 - */ - public function setType(int $type) /* : void */ - { - if (!AccountType::isValidValue($type)) { - throw new \InvalidArgumentException(); - } - - $this->type = $type; - } - /** * Update last activity. * @@ -455,6 +589,18 @@ class Account implements ArrayableInterface, \JsonSerializable $this->lastActive = new \DateTime('NOW'); } + /** + * Get string representation. + * + * @return string + * + * @since 1.0.0 + */ + public function __toString() + { + return json_encode($this->toArray()); + } + /** * {@inheritdoc} */ @@ -476,18 +622,6 @@ class Account implements ArrayableInterface, \JsonSerializable ]; } - /** - * Get string representation. - * - * @return string - * - * @since 1.0.0 - */ - public function __toString() - { - return json_encode($this->toArray()); - } - /** * Json serialize. * diff --git a/Account/AccountManager.php b/Account/AccountManager.php index c8bfed69b..8457c5e02 100644 --- a/Account/AccountManager.php +++ b/Account/AccountManager.php @@ -21,6 +21,8 @@ use phpOMS\DataStorage\Session\SessionInterface; /** * Account manager class. + * + * The account manager is used to manage multiple accounts. * * @category Framework * @package phpOMS\Account @@ -47,14 +49,6 @@ class AccountManager implements \Countable */ private $session = null; - /** - * Database connection instance. - * - * @var ConnectionAbstract - * @since 1.0.0 - */ - private $connection = null; - /** * Authenticator. * @@ -66,16 +60,14 @@ class AccountManager implements \Countable /** * Constructor. * - * @param ConnectionAbstract $connection Database connection * @param SessionInterface $session Session * * @since 1.0.0 */ - public function __construct(ConnectionAbstract $connection, SessionInterface $session) + public function __construct(SessionInterface $session) { - $this->connection = $connection; $this->session = $session; - $this->auth = new Auth($this->connection, $this->session); + $this->auth = new Auth($this->session); } /** @@ -102,6 +94,18 @@ class AccountManager implements \Countable return $this->accounts[$id] ?? new NullAccount(); } + /** + * Returns the authentication manager + * + * @return Auth + * + * @since 1.0.0 + */ + public function getAuth() : Auth + { + return $this->auth; + } + /** * Add account. * diff --git a/Account/AccountStatus.php b/Account/AccountStatus.php index c7aa95269..e8d8efd75 100644 --- a/Account/AccountStatus.php +++ b/Account/AccountStatus.php @@ -28,8 +28,8 @@ use phpOMS\Stdlib\Base\Enum; */ abstract class AccountStatus extends Enum { - /* public */ const ACTIVE = 1; - /* public */ const INACTIVE = 2; - /* public */ const TIMEOUT = 3; - /* public */ const BANNED = 4; + /* public */ const ACTIVE = 1; + /* public */ const INACTIVE = 2; + /* public */ const TIMEOUT = 3; + /* public */ const BANNED = 4; } diff --git a/Account/AccountType.php b/Account/AccountType.php index 92369d6c9..93a963465 100644 --- a/Account/AccountType.php +++ b/Account/AccountType.php @@ -21,13 +21,13 @@ use phpOMS\Stdlib\Base\Enum; * Account type enum. * * @category Framework - * @package phpOMS\DataStorage\Database + * @package phpOMS\Account * @license OMS License 1.0 * @link http://orange-management.com * @since 1.0.0 */ abstract class AccountType extends Enum { - /* public */ const USER = 0; - /* public */ const GROUP = 1; + /* public */ const USER = 0; + /* public */ const GROUP = 1; } diff --git a/Account/Group.php b/Account/Group.php index fde961c6a..46f58c16d 100644 --- a/Account/Group.php +++ b/Account/Group.php @@ -191,18 +191,6 @@ class Group implements ArrayableInterface, \JsonSerializable return json_encode($this->toArray()); } - /** - * Json serialize. - * - * @return string - * - * @since 1.0.0 - */ - public function jsonSerialize() - { - return $this->toArray(); - } - /** * {@inheritdoc} */ @@ -216,4 +204,16 @@ class Group implements ArrayableInterface, \JsonSerializable 'members' => $this->members, ]; } + + /** + * Json serialize. + * + * @return string + * + * @since 1.0.0 + */ + public function jsonSerialize() + { + return $this->toArray(); + } } diff --git a/Account/GroupStatus.php b/Account/GroupStatus.php index 14916b4ef..0af5c9655 100644 --- a/Account/GroupStatus.php +++ b/Account/GroupStatus.php @@ -20,17 +20,15 @@ use phpOMS\Stdlib\Base\Enum; /** * Accept status enum. * - * @category Calendar - * @package Modules + * @category Framework + * @package phpOMS\Account * @license OMS License 1.0 * @link http://orange-management.com * @since 1.0.0 */ abstract class GroupStatus extends Enum { - /* public */ const ACTIVE = 1; - - /* public */ const INACTIVE = 2; - - /* public */ const HIDDEN = 4; + /* public */ const ACTIVE = 1; + /* public */ const INACTIVE = 2; + /* public */ const HIDDEN = 4; } diff --git a/Account/PermissionAbstract.php b/Account/PermissionAbstract.php new file mode 100644 index 000000000..13a7312f6 --- /dev/null +++ b/Account/PermissionAbstract.php @@ -0,0 +1,351 @@ +id; + } + + /** + * Get unit id. + * + * @return int + * + * @since 1.0.0 + */ + public function getUnit() /* : ?int */ + { + return $this->unit; + } + + /** + * Set unit id. + * + * @param int $unit Unit + * + * @return void + * + * @since 1.0.0 + */ + public function setUnit(int $unit = null) /* : void */ + { + $this->unit = $unit; + } + + /** + * Get app name. + * + * @return string + * + * @since 1.0.0 + */ + public function getApp() /* : ?string */ + { + return $this->app; + } + + /** + * Set app name. + * + * @param string $app App name + * + * @return void + * + * @since 1.0.0 + */ + public function setApp(string $app = null) /* : void */ + { + $this->app = $app; + } + + /** + * Get module id. + * + * @return int + * + * @since 1.0.0 + */ + public function getModule() /* : ?int */ + { + return $this->module; + } + + /** + * Set module id. + * + * @param int $module Module + * + * @return void + * + * @since 1.0.0 + */ + public function setModule(int $module = null) /* : void */ + { + $this->module = $module; + } + + /** + * Get providing module id. + * + * @return int + * + * @since 1.0.0 + */ + public function getFrom() /* : ?int */ + { + return $this->from; + } + + /** + * Set providing module id. + * + * @param int $from Providing module + * + * @return void + * + * @since 1.0.0 + */ + public function setFrom(int $from = null) /* : void */ + { + $this->from = $from; + } + + /** + * Get type. + * + * @return int + * + * @since 1.0.0 + */ + public function getType() /* : ?int */ + { + return $this->type; + } + + /** + * Set type. + * + * @param int $type Type + * + * @return void + * + * @since 1.0.0 + */ + public function setType(int $type = null) /* : void */ + { + $this->type = $type; + } + + /** + * Get element id. + * + * @return int + * + * @since 1.0.0 + */ + public function getElement() /* : ?int */ + { + return $this->element; + } + + /** + * Set element id. + * + * @param int $element Element id + * + * @return void + * + * @since 1.0.0 + */ + public function setElement(int $element = null) /* : void */ + { + $this->element = $element; + } + + /** + * Get component id. + * + * @return int + * + * @since 1.0.0 + */ + public function getComponent() /* : ?int */ + { + return $this->component; + } + + /** + * Set component id. + * + * @param int $component Component + * + * @return void + * + * @since 1.0.0 + */ + public function setComponent(int $component = null) /* : void */ + { + $this->component = $component; + } + + /** + * Get permission + * + * @return int + * + * @since 1.0.0 + */ + public function getPermission() : int + { + return $this->permission; + } + + /** + * Set permission. + * + * @param int $permission Permission + * + * @return void + * + * @since 1.0.0 + */ + public function setPermission(int $permission = 0) /* : void */ + { + $this->permission = $permission; + } + + /** + * Add permission. + * + * @param int $permission Permission + * + * @return void + * + * @since 1.0.0 + */ + public function addPermission(int $permission = 0) /* : void */ + { + $this->permission |= $permission; + } + + /** + * Has permission. + * + * @param int $permission Permission + * + * @return void + * + * @since 1.0.0 + */ + public function hasPermission(int $permission) : bool + { + return ($this->permission | $permission) === $this->permission; + } +} diff --git a/Account/PermissionType.php b/Account/PermissionType.php new file mode 100644 index 000000000..29d6059a1 --- /dev/null +++ b/Account/PermissionType.php @@ -0,0 +1,37 @@ +$name) || $name === 'config') { + if(!empty($this->$name)) { return; } @@ -173,10 +165,6 @@ class ApplicationAbstract */ public function __get($name) { - if($name === 'config') { - return []; - } - return $this->$name; } } diff --git a/Asset/AssetType.php b/Asset/AssetType.php index 7f710555f..95fd69919 100644 --- a/Asset/AssetType.php +++ b/Asset/AssetType.php @@ -28,7 +28,7 @@ use phpOMS\Stdlib\Base\Enum; */ abstract class AssetType extends Enum { - /* public */ const CSS = 0; - /* public */ const JS = 1; - /* public */ const JSLATE = 2; + /* public */ const CSS = 0; + /* public */ const JS = 1; + /* public */ const JSLATE = 2; } diff --git a/Auth/Auth.php b/Auth/Auth.php index 38a80b112..0f1a06395 100644 --- a/Auth/Auth.php +++ b/Auth/Auth.php @@ -16,7 +16,6 @@ declare(strict_types=1); namespace phpOMS\Auth; use phpOMS\DataStorage\Database\Connection\ConnectionAbstract; -use phpOMS\DataStorage\Database\DatabaseType; use phpOMS\DataStorage\Session\SessionInterface; /** @@ -40,26 +39,16 @@ class Auth */ private $session = null; - /** - * Database connection instance. - * - * @var ConnectionAbstract - * @since 1.0.0 - */ - private $connection = null; - /** * Constructor. * - * @param ConnectionAbstract $connection Database connection * @param SessionInterface $session Session * * @since 1.0.0 */ - public function __construct(ConnectionAbstract $connection, SessionInterface $session) + public function __construct(SessionInterface $session) { - $this->connection = $connection; - $this->session = $session; + $this->session = $session; } /** @@ -73,11 +62,7 @@ class Auth { $uid = $this->session->get('UID'); - if (empty($uid)) { - return 0; - } - - return $uid; + return empty($uid) ? 0 : $uid; } /** diff --git a/Auth/LoginReturnType.php b/Auth/LoginReturnType.php index 8ce0352ae..b18aa8db0 100644 --- a/Auth/LoginReturnType.php +++ b/Auth/LoginReturnType.php @@ -30,14 +30,14 @@ use phpOMS\Stdlib\Base\Enum; */ abstract class LoginReturnType extends Enum { - /* public */ const OK = 0; /* Everything is ok and the user got authed */ - /* public */ const FAILURE = -1; /* Authentication resulted in a unexpected failure */ - /* public */ const WRONG_PASSWORD = -2; /* Authentication with wrong password */ - /* public */ const WRONG_USERNAME = -3; /* Authentication with unknown user */ - /* public */ const WRONG_PERMISSION = -4; /* User doesn't have permission to authenticate */ - /* public */ const NOT_ACTIVATED = -5; /* The user is not activated yet */ + /* public */ const OK = 0; /* Everything is ok and the user got authed */ + /* public */ const FAILURE = -1; /* Authentication resulted in a unexpected failure */ + /* public */ const WRONG_PASSWORD = -2; /* Authentication with wrong password */ + /* public */ const WRONG_USERNAME = -3; /* Authentication with unknown user */ + /* public */ const WRONG_PERMISSION = -4; /* User doesn't have permission to authenticate */ + /* public */ const NOT_ACTIVATED = -5; /* The user is not activated yet */ /* public */ const WRONG_INPUT_EXCEEDED = -6; /* Too many wrong logins recently */ - /* public */ const TIMEOUTED = -7; /* User received a timeout and can not log in until a certain date */ - /* public */ const BANNED = -8; /* User is banned */ - /* public */ const INACTIVE = -9; /* User is inactive */ + /* public */ const TIMEOUTED = -7; /* User received a timeout and can not log in until a certain date */ + /* public */ const BANNED = -8; /* User is banned */ + /* public */ const INACTIVE = -9; /* User is inactive */ } diff --git a/Business/Finance/Depreciation.php b/Business/Finance/Depreciation.php index cc0fd25b3..b6f89b96e 100644 --- a/Business/Finance/Depreciation.php +++ b/Business/Finance/Depreciation.php @@ -17,5 +17,51 @@ namespace phpOMS\Business\Finance; class Depreciation { + public static function getLinearDepreciationRate(float $start, int $duration) : float + { + return $start / $duration; + } + public static function getLinearDepreciationResidualInT(float $start, int $duration, int $t) : float + { + return $start - self::getLinearDepreciationRate($start, $duration) * $t; + } + + public static function getArithmeticProgressivDepreciationRate(float $start, int $duration) : float + { + return $start / ($duration * ($duration+1) / 2); + } + + public static function getArithmeticProgressivDepreciationInT(float $start, int $duration, int $t) : float + { + return $t * self::getArithmeticProgressivDepreciationRate($start, $duration); + } + + public static function getArithmeticProgressivDepreciationResidualInT(float $start, int $duration, int $t) : float + { + return $start - self::getArithmeticProgressivDepreciationRate($start, $duration) * $t * ($t + 1) / 2; + } + + public static function getGeometicProgressivDepreciationRate(float $start, float $residual, int $duration) : float + { + return (1-pow($residual / $start, 1 / $duration)); + } + + public static function getGeometicDegressivDepreciationInT(float $start, float $residual, int $duration, int $t) : float + { + return $start * (1 - self::getGeometicDegressivDepreciationRate($start, $residual, $duration)) ** $t; + } + + public static function getGeometicDegressivDepreciationResidualInT(float $start, float $residual, int $duration, int $t) : float + { + } + + public static function getGeometicProgressivDepreciationInT(float $start, float $residual, int $duration, int $t) : float + { + return $start * (1 - self::getGeometicProgressivDepreciationRate($start, $residual, $duration)) ** ($duration - $t + 1); + } + + public static function getGeometicProgressivDepreciationResidualInT(float $start, float $residual, int $duration, int $t) : float + { + } } \ No newline at end of file diff --git a/Business/Finance/FinanceFormulas.php b/Business/Finance/FinanceFormulas.php index dfca67533..c71c110ac 100644 --- a/Business/Finance/FinanceFormulas.php +++ b/Business/Finance/FinanceFormulas.php @@ -1197,14 +1197,14 @@ class FinanceFormulas /** * Rate of Inflation * - * @param float $oldCPI Consumer price index old * @param float $newCPI Consumer price index new + * @param float $oldCPI Consumer price index old * * @return float * * @since 1.0.0 */ - public static function getRateOfOnflation(float $oldCPI, float $newCPI) : float + public static function getRateOfOnflation(float $newCPI, float $oldCPI) : float { return $newCPI / $oldCPI - 1; } @@ -1315,6 +1315,54 @@ class FinanceFormulas return $P * $r * $t; } + /** + * Simple Interest Rate + * + * @param float $I Interest + * @param float $P Principal + * @param int $t Time + * + * @return float + * + * @since 1.0.0 + */ + public static function getSimpleInterestRate(float $I, float $P, int $t) : float + { + return $I / ($P * $t); + } + + /** + * Simple Interest Principal + * + * @param float $I Interest + * @param float $r Rate + * @param int $t Time + * + * @return float + * + * @since 1.0.0 + */ + public static function getSimpleInterestPrincipal(float $I, float $r, int $t) : float + { + return $I / ($r * $t); + } + + /** + * Simple Interest Principal + * + * @param float $I Interest + * @param float $P Principal + * @param float $r Rate + * + * @return int + * + * @since 1.0.0 + */ + public static function getSimpleInterestTime(float $I, float $P, float $r) : int + { + return (int) round($I / ($P * $r)); + } + /** * Relative market share by share * diff --git a/Business/Marketing/Metrics.php b/Business/Marketing/Metrics.php index 41f7872ae..7a0124756 100644 --- a/Business/Marketing/Metrics.php +++ b/Business/Marketing/Metrics.php @@ -16,7 +16,9 @@ declare(strict_types=1); namespace phpOMS\Business\Marketing; /** - * Net Promoter Score + * Marketing Metrics + * + * This class provided basic marketing metric calculations * * @category Framework * @package phpOMS\Business diff --git a/Business/Marketing/NetPromoterScore.php b/Business/Marketing/NetPromoterScore.php index 81a857fd3..0c22eee8d 100644 --- a/Business/Marketing/NetPromoterScore.php +++ b/Business/Marketing/NetPromoterScore.php @@ -17,6 +17,9 @@ namespace phpOMS\Business\Marketing; /** * Net Promoter Score + * + * The net promoter score is a basic evaluation of the happiness of customers. + * Instead of customers the NPS can also be transferred to non-customers. * * @category Framework * @package phpOMS\Business @@ -65,8 +68,8 @@ class NetPromoterScore { */ public function getScore() : int { - $promoters = 0; - $passives = 0; + $promoters = 0; + $passives = 0; $detractors = 0; foreach($this->scores as $score) { diff --git a/Business/Programming/Metrics.php b/Business/Programming/Metrics.php index fd461d7b4..04b1056ad 100644 --- a/Business/Programming/Metrics.php +++ b/Business/Programming/Metrics.php @@ -12,9 +12,13 @@ * @link http://orange-management.com */ declare(strict_types=1); + namespace phpOMS\Business\Programming; + /** * Programming metrics + * + * This class provides basic programming metric calculations. * * @category Framework * @package phpOMS\Business @@ -40,4 +44,22 @@ class Metrics { { return (int) sqrt($a*$a+$b*$b+$c*$c); } + + /** + * Calculate the C.R.A.P score + * + * @latex r = \sqrt{a^{2} + b^{2} + c^{2}} + * + * @param int $a Assignments + * @param int $b Branches + * @param int $c Conditionals + * + * @return int + * + * @since 1.0.0 + */ + public static function CRAP(int $complexity, float $coverage) : int + { + return (int) ($complexity ** 2 * (1 - $coverage) ** 3 + $complexity); + } } \ No newline at end of file diff --git a/Business/Sales/MarketShareEstimation.php b/Business/Sales/MarketShareEstimation.php index 3b870bd16..b2b03079a 100644 --- a/Business/Sales/MarketShareEstimation.php +++ b/Business/Sales/MarketShareEstimation.php @@ -12,9 +12,14 @@ * @link http://orange-management.com */ declare(strict_types=1); + namespace phpOMS\Business\Sales; + /** * Market share calculations (Zipf function) + * + * This class can be used to calculate the market share based on a rank or vice versa + * the rank based on a marketshare in a Zipf distributed market. * * @category Framework * @package phpOMS\Business @@ -24,15 +29,15 @@ namespace phpOMS\Business\Sales; */ class MarketShareEstimation { /** - * Calculate rank (r) based on marketshare (m) + * Calculate rank (r) based on market share (m) * * @latex r = \sqrt[s]{\frac{1}{m \times \sum_{n=1}^N{\frac{1}{n^{s}}}}} * - * @param int $participants (p) - * @param float $marketShare (m) - * @param float $modifier (s) + * @param int $participants The amount of existing participants in the market or compentitors (N) + * @param float $marketShare The absolute own market share (m) + * @param float $modifier Distribution modifier (s) * - * @return float + * @return int Returns the rank * * @since 1.0.0 */ @@ -47,15 +52,15 @@ class MarketShareEstimation { } /** - * Calculate marketshare (m) based on rank (r) + * Calculate market share (m) based on rank (r) * * @latex m = \frac{\frac{1}{r^{s}}}{\sum_{n=1}^N{\frac{1}{n^{s}}}} * - * @param int $participants (p) - * @param int $rank (r) - * @param float $modifier (s) + * @param int $participants The amount of existing participants in the market or compentitors (N) + * @param int $rank The absolute own rank in the market (r) + * @param float $modifier Distribution modifier (s) * - * @return float + * @return float Returns the Market share * * @since 1.0.0 */ diff --git a/DataStorage/Cache/CachePool.php b/DataStorage/Cache/CachePool.php index 8f5de89d0..eef56b141 100644 --- a/DataStorage/Cache/CachePool.php +++ b/DataStorage/Cache/CachePool.php @@ -104,12 +104,16 @@ class CachePool implements OptionsInterface * * @since 1.0.0 */ - public function get(string $key) /* : ?CacheInterface */ + public function get(string $key = '') /* : ?CacheInterface */ { - if (!isset($this->pool[$key])) { + if((!empty($key) && !isset($this->pool[$key])) || empty($this->pool)) { return null; } + if(empty($key)) { + return reset($this->pool); + } + return $this->pool[$key]; } diff --git a/DataStorage/Cache/FileCache.php b/DataStorage/Cache/FileCache.php index ef9906889..236e882ea 100644 --- a/DataStorage/Cache/FileCache.php +++ b/DataStorage/Cache/FileCache.php @@ -83,7 +83,7 @@ class FileCache implements CacheInterface public function __construct(string $path) { if (!Directory::exists(File::parent($path))) { - Directory::create($path); + Directory::create($path, 0664, true); } $this->cachePath = realpath($path); diff --git a/DataStorage/Cookie/CookieJar.php b/DataStorage/Cookie/CookieJar.php index 4ee0e663b..2124a8d4f 100644 --- a/DataStorage/Cookie/CookieJar.php +++ b/DataStorage/Cookie/CookieJar.php @@ -127,9 +127,13 @@ class CookieJar throw new LockException('CookieJar'); } - setcookie($id, '', time() - 3600); + if(!headers_sent()) { + setcookie($id, '', time() - 3600); - return true; + return true; + } + + return false; } return false; diff --git a/DataStorage/Database/Connection/ConnectionAbstract.php b/DataStorage/Database/Connection/ConnectionAbstract.php index 1a97a9a1d..20f4379a7 100644 --- a/DataStorage/Database/Connection/ConnectionAbstract.php +++ b/DataStorage/Database/Connection/ConnectionAbstract.php @@ -110,6 +110,42 @@ abstract class ConnectionAbstract implements ConnectionInterface return $this->status; } + /** + * Get database name. + * + * @return string + * + * @since 1.0.0 + */ + public function getDatabase() : string + { + return $this->dbdata['database'] ?? ''; + } + + /** + * Get database host. + * + * @return string + * + * @since 1.0.0 + */ + public function getHost() : string + { + return $this->dbdata['host'] ?? ''; + } + + /** + * Get database port. + * + * @return int + * + * @since 1.0.0 + */ + public function getPort() : int + { + return (int) $this->dbdata['port'] ?? 0; + } + /** * Get table prefix. * diff --git a/DataStorage/Database/Connection/ConnectionFactory.php b/DataStorage/Database/Connection/ConnectionFactory.php index ffe361716..9448235bd 100644 --- a/DataStorage/Database/Connection/ConnectionFactory.php +++ b/DataStorage/Database/Connection/ConnectionFactory.php @@ -33,6 +33,7 @@ class ConnectionFactory * Constructor. * * @since 1.0.0 + * @codeCoverageIgnore */ private function __construct() { diff --git a/DataStorage/Database/DataMapperAbstract.php b/DataStorage/Database/DataMapperAbstract.php index d117baba4..8c7e7abd6 100644 --- a/DataStorage/Database/DataMapperAbstract.php +++ b/DataStorage/Database/DataMapperAbstract.php @@ -174,6 +174,7 @@ class DataMapperAbstract implements DataMapperInterface * Constructor. * * @since 1.0.0 + * @codeCoverageIgnore */ private function __construct() { @@ -185,6 +186,7 @@ class DataMapperAbstract implements DataMapperInterface * @return void * * @since 1.0.0 + * @codeCoverageIgnore */ private function __clone() { @@ -268,7 +270,7 @@ class DataMapperAbstract implements DataMapperInterface self::$fields = $objects; - return __CLASS__; + //return __CLASS__; } /** @@ -363,6 +365,31 @@ class DataMapperAbstract implements DataMapperInterface return $objId; } + /** + * Create object in db. + * + * @param array $obj Object reference (gets filled with insert id) + * @param int $relations Create all relations as well + * + * @return mixed + * + * @since 1.0.0 + */ + public static function createArray(array &$obj, int $relations = RelationType::ALL) + { + self::extend(__CLASS__); + + $objId = self::createModelArray($obj); + settype($objId, static::$columns[static::$primaryField]['type']); + $obj[static::$columns[static::$primaryField]['internal']] = $objId; + + if ($relations === RelationType::ALL) { + self::createHasManyArray($obj, $objId); + } + + return $objId; + } + /** * Create base model. * @@ -422,6 +449,52 @@ class DataMapperAbstract implements DataMapperInterface return self::$db->con->lastInsertId(); } + /** + * Create base model. + * + * @param array $obj Model to create + * + * @return mixed + * + * @since 1.0.0 + */ + private static function createModelArray($obj) + { + $query = new Builder(self::$db); + $query->prefix(self::$db->getPrefix())->into(static::$table); + + foreach ($obj as $propertyName => &$property) { + if (isset(static::$hasMany[$propertyName]) || isset(static::$hasOne[$propertyName])) { + continue; + } + + foreach (static::$columns as $key => $column) { + if (isset(static::$ownsOne[$propertyName]) && $column['internal'] === $propertyName) { + $id = self::createOwnsOneArray($propertyName, $property); + $value = self::parseValue($column['type'], $id); + + $query->insert($column['name'])->value($value, $column['type']); + break; + } elseif (isset(static::$belongsTo[$propertyName]) && $column['internal'] === $propertyName) { + $id = self::createBelongsToArray($propertyName, $property); + $value = self::parseValue($column['type'], $id); + + $query->insert($column['name'])->value($value, $column['type']); + break; + } elseif ($column['internal'] === $propertyName && $column['type'] !== static::$primaryField) { + $value = self::parseValue($column['type'], $property); + + $query->insert($column['name'])->value($value, $column['type']); + break; + } + } + } + + self::$db->con->prepare($query->toSql())->execute(); + + return self::$db->con->lastInsertId(); + } + /** * Get id of object * @@ -434,7 +507,7 @@ class DataMapperAbstract implements DataMapperInterface */ private static function getObjectId($obj, \ReflectionClass $reflectionClass = null) { - $reflectionClass = $reflectionClass ?? new \ReflectionClass(get_class($obj)); + $reflectionClass = $reflectionClass ?? new \ReflectionClass(get_class($obj)); $reflectionProperty = $reflectionClass->getProperty(static::$columns[static::$primaryField]['internal']); if (!($isPublic = $reflectionProperty->isPublic())) { @@ -509,7 +582,7 @@ class DataMapperAbstract implements DataMapperInterface throw new InvalidMapperException(); } - /** @var DataMapperAbstract $mapper */ + /** @var string $mapper */ $mapper = static::$hasMany[$propertyName]['mapper']; $objsIds = []; $relReflectionClass = null; @@ -559,6 +632,62 @@ class DataMapperAbstract implements DataMapperInterface } } + /** + * Create has many + * + * @param array $obj Object to create + * @param mixed $objId Id to set + * + * @return void + * + * @throws InvalidMapperException + * + * @since 1.0.0 + */ + private static function createHasManyArray(array &$obj, $objId) /* : void */ + { + foreach (static::$hasMany as $propertyName => $rel) { + $values = $obj[$propertyName]; + + if (!isset(static::$hasMany[$propertyName]['mapper'])) { + throw new InvalidMapperException(); + } + + /** @var string $mapper */ + $mapper = static::$hasMany[$propertyName]['mapper']; + $objsIds = []; + + foreach ($values as $key => &$value) { + if (!is_object($value)) { + // Is scalar => already in database + $objsIds[$key] = $value; + + continue; + } + + $primaryKey = $obj[static::$columns[static::$primaryField]['internal']]; + + // already in db + if (!empty($primaryKey)) { + $objsIds[$key] = $value; + + continue; + } + + // Setting relation value (id) for relation (since the relation is not stored in an extra relation table) + /** @var string $table */ + /** @var array $columns */ + if (static::$hasMany[$propertyName]['table'] === static::$hasMany[$propertyName]['mapper']::$table) { + $value[$mapper::$columns[static::$hasMany[$propertyName]['dst']]['internal']] = $objId; + } + + $objsIds[$key] = $mapper::createArray($value); + } + + self::createRelationTable($propertyName, $objsIds, $objId); + } + } + private static function createHasOne(\ReflectionClass $reflectionClass, $obj) { throw new \Exception(); @@ -583,7 +712,7 @@ class DataMapperAbstract implements DataMapperInterface private static function createOwnsOne(string $propertyName, $obj) { if (is_object($obj)) { - $mapper = static::$ownsOne[$propertyName]['mapper']; + $mapper = static::$ownsOne[$propertyName]['mapper']; $primaryKey = $mapper::getObjectId($obj); if (empty($primaryKey)) { @@ -596,6 +725,34 @@ class DataMapperAbstract implements DataMapperInterface return $obj; } + /** + * Create owns one + * + * The reference is stored in the main model + * + * @param string $propertyName Property name to initialize + * @param array $obj Object to create + * + * @return mixed + * + * @since 1.0.0 + */ + private static function createOwnsOneArray(string $propertyName, array &$obj) + { + if (is_array($obj)) { + $mapper = static::$ownsOne[$propertyName]['mapper']; + $primaryKey = $obj[static::$columns[static::$primaryField]['internal']]; + + if (empty($primaryKey)) { + return $mapper::createArray($obj); + } + + return $primaryKey; + } + + return $obj; + } + /** * Create owns one * @@ -611,8 +768,8 @@ class DataMapperAbstract implements DataMapperInterface private static function createBelongsTo(string $propertyName, $obj) { if (is_object($obj)) { - /** @var DataMapperAbstract $mapper */ - $mapper = static::$belongsTo[$propertyName]['mapper']; + /** @var string $mapper */ + $mapper = static::$belongsTo[$propertyName]['mapper']; $primaryKey = $mapper::getObjectId($obj); if (empty($primaryKey)) { @@ -625,6 +782,35 @@ class DataMapperAbstract implements DataMapperInterface return $obj; } + /** + * Create owns one + * + * The reference is stored in the main model + * + * @param string $propertyName Property name to initialize + * @param array $obj Object to create + * + * @return mixed + * + * @since 1.0.0 + */ + private static function createBelongsToArray(string $propertyName, array $obj) + { + if (is_array($obj)) { + /** @var string $mapper */ + $mapper = static::$belongsTo[$propertyName]['mapper']; + $primaryKey = $obj[static::$columns[static::$primaryField]['internal']]; + + if (empty($primaryKey)) { + return $mapper::createArray($obj); + } + + return $primaryKey; + } + + return $obj; + } + /** * Create relation table entry * @@ -671,11 +857,12 @@ class DataMapperAbstract implements DataMapperInterface */ private static function parseValue(string $type, $value) { + // todo: checking for string === string and is_* is slow. maybe only check type or only string if (is_null($value)) { return null; - } elseif ($type === 'DateTime') { + } elseif ($type === 'DateTime' || $value instanceof \DateTime) { return $value->format('Y-m-d H:i:s'); - } elseif ($type === 'Json' || $type === 'jsonSerializable') { + } elseif ($type === 'Json' || $type === 'jsonSerializable' || is_array($value)) { return json_encode($value); } elseif ($type === 'Serializable') { return $value->serialize(); @@ -683,13 +870,13 @@ class DataMapperAbstract implements DataMapperInterface return json_encode($value->jsonSerialize()); } elseif (is_object($value) && method_exists($value, 'getId')) { return $value->getId(); - } elseif ($type === 'int') { + } elseif ($type === 'int' || is_int($value)) { return (int) $value; - } elseif ($type === 'string') { + } elseif ($type === 'string' || is_string($value)) { return (string) $value; - } elseif ($type === 'float') { + } elseif ($type === 'float' || is_float($value)) { return (float) $value; - } elseif ($type === 'bool') { + } elseif ($type === 'bool' || is_bool($value)) { return (bool) $value; } @@ -711,6 +898,8 @@ class DataMapperAbstract implements DataMapperInterface */ private static function updateHasMany(\ReflectionClass $reflectionClass, $obj, $objId) /* : void */ { + $objsIds = []; + foreach (static::$hasMany as $propertyName => $rel) { $property = $reflectionClass->getProperty($propertyName); @@ -728,15 +917,15 @@ class DataMapperAbstract implements DataMapperInterface throw new InvalidMapperException(); } - /** @var DataMapperAbstract $mapper */ + /** @var string $mapper */ $mapper = static::$hasMany[$propertyName]['mapper']; - $objsIds = []; $relReflectionClass = null; + $objsIds[$propertyName] = []; foreach ($values as $key => &$value) { if (!is_object($value)) { // Is scalar => already in database - $objsIds[$key] = $value; + $objsIds[$propertyName][$key] = $value; continue; } @@ -749,7 +938,9 @@ class DataMapperAbstract implements DataMapperInterface // already in db if (!empty($primaryKey)) { - $objsIds[$key] = $value; + $mapper::update($value); + + $objsIds[$propertyName][$key] = $value; continue; } @@ -771,11 +962,11 @@ class DataMapperAbstract implements DataMapperInterface } } - $objsIds[$key] = $mapper::create($value); + $objsIds[$propertyName][$key] = $mapper::create($value); } - - self::updateRelationTable($propertyName, $objsIds, $objId); } + + self::updateRelationTable($objsIds, $objId); } /** @@ -793,29 +984,20 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - private static function updateRelationTable(string $propertyName, array $objsIds, $objId) + private static function updateRelationTable(array $objsIds, $objId) { - /** @var string $table */ - if ( - !empty($objsIds) - && static::$hasMany[$propertyName]['table'] !== static::$table - && static::$hasMany[$propertyName]['table'] !== static::$hasMany[$propertyName]['mapper']::$table - ) { - $many = self::getHasManyRaw($objId); + $many = self::getHasManyRaw($objId); - foreach(static::$hasMany as $member => $value) { - // todo: definately an error here. needs testing - throw new \Exception(); - $removes = array_diff_key($many[$member], $objsIds[$member]); - $adds = array_diff_key($objsIds[$member], $many[$member]); + foreach (static::$hasMany as $propertyName => $rel) { + $removes = array_diff($many[$propertyName], array_keys($objsIds[$propertyName] ?? [])); + $adds = array_diff(array_keys($objsIds[$propertyName] ?? []), $many[$propertyName]); - if(!empty($removes)) { - self::deleteRelationTable($propertyName, $removes, $objId); - } + if(!empty($removes)) { + self::deleteRelationTable($propertyName, $removes, $objId); + } - if(!empty($adds)) { - self::createRelationTable($propertyName, $adds, $objId); - } + if(!empty($adds)) { + self::createRelationTable($propertyName, $adds, $objId); } } } @@ -842,11 +1024,10 @@ class DataMapperAbstract implements DataMapperInterface foreach ($objsIds as $key => $src) { $relQuery = new Builder(self::$db); $relQuery->prefix(self::$db->getPrefix()) - ->into(static::$hasMany[$propertyName]['table']) - ->delete(); - - $relQuery->where(static::$hasMany[$propertyName]['src'], '=', $src) - ->where(static::$hasMany[$propertyName]['dst'], '=', $objId, 'and'); + ->delete() + ->from(static::$hasMany[$propertyName]['table']) + ->where(static::$hasMany[$propertyName]['table'] . '.' . static::$hasMany[$propertyName]['src'], '=', $src) + ->where(static::$hasMany[$propertyName]['table'] . '.' . static::$hasMany[$propertyName]['dst'], '=', $objId, 'and'); self::$db->con->prepare($relQuery->toSql())->execute(); } @@ -868,7 +1049,7 @@ class DataMapperAbstract implements DataMapperInterface private static function updateOwnsOne(string $propertyName, $obj) { if (is_object($obj)) { - /** @var DataMapperAbstract $mapper */ + /** @var string $mapper */ $mapper = static::$ownsOne[$propertyName]['mapper']; // todo: delete owned one object is not recommended since it can be owned by by something else? or does owns one mean that nothing else can have a relation to this one? @@ -894,7 +1075,7 @@ class DataMapperAbstract implements DataMapperInterface private static function updateBelongsTo(string $propertyName, $obj) { if (is_object($obj)) { - /** @var DataMapperAbstract $mapper */ + /** @var string $mapper */ $mapper = static::$belongsTo[$propertyName]['mapper']; return $mapper::update($obj); @@ -918,8 +1099,8 @@ class DataMapperAbstract implements DataMapperInterface { $query = new Builder(self::$db); $query->prefix(self::$db->getPrefix()) - ->into(static::$table) - ->where(static::$primaryField, '=', $objId); + ->update(static::$table) + ->where(static::$table . '.' . static::$primaryField, '=', $objId); $properties = $reflectionClass->getProperties(); @@ -938,23 +1119,23 @@ class DataMapperAbstract implements DataMapperInterface foreach (static::$columns as $key => $column) { if (isset(static::$ownsOne[$propertyName]) && $column['internal'] === $propertyName) { - $id = self::updateOwnsOne($propertyName, $property->getValue($obj)); + $id = self::updateOwnsOne($propertyName, $property->getValue($obj)); $value = self::parseValue($column['type'], $id); // todo: should not be done if the id didn't change. but for now don't know if id changed - $query->update($column['name'])->value($value, $column['type']); + $query->set([static::$table . '.' . $column['name'] => $value], $column['type']); break; } elseif (isset(static::$belongsTo[$propertyName]) && $column['internal'] === $propertyName) { $id = self::updateBelongsTo($propertyName, $property->getValue($obj)); $value = self::parseValue($column['type'], $id); // todo: should not be done if the id didn't change. but for now don't know if id changed - $query->update($column['name'])->value($value, $column['type']); + $query->set([static::$table . '.' . $column['name'] => $value], $column['type']); break; } elseif ($column['internal'] === $propertyName && $column['type'] !== static::$primaryField) { $value = self::parseValue($column['type'], $property->getValue($obj)); - $query->update($column['name'])->value($value, $column['type']); + $query->set([static::$table . '.' . $column['name'] => $value], $column['type']); break; } } @@ -980,9 +1161,10 @@ class DataMapperAbstract implements DataMapperInterface public static function update($obj, int $relations = RelationType::ALL) : int { self::extend(__CLASS__); + $reflectionClass = new \ReflectionClass(get_class($obj)); - $objId = self::getObjectId($obj, $reflectionClass); - $update = true; + $objId = self::getObjectId($obj, $reflectionClass); + $update = true; if(empty($objId)) { $update = false; @@ -1033,7 +1215,7 @@ class DataMapperAbstract implements DataMapperInterface throw new InvalidMapperException(); } - /** @var DataMapperAbstract $mapper */ + /** @var string $mapper */ $mapper = static::$hasMany[$propertyName]['mapper']; $objsIds = []; $relReflectionClass = null; @@ -1086,7 +1268,7 @@ class DataMapperAbstract implements DataMapperInterface private static function deleteOwnsOne(string $propertyName, $obj) { if (is_object($obj)) { - /** @var DataMapperAbstract $mapper */ + /** @var string $mapper */ $mapper = static::$ownsOne[$propertyName]['mapper']; // todo: delete owned one object is not recommended since it can be owned by by something else? or does owns one mean that nothing else can have a relation to this one? @@ -1111,7 +1293,7 @@ class DataMapperAbstract implements DataMapperInterface private static function deleteBelongsTo(string $propertyName, $obj) { if (is_object($obj)) { - /** @var DataMapperAbstract $mapper */ + /** @var string $mapper */ $mapper = static::$belongsTo[$propertyName]['mapper']; return $mapper::delete($obj); @@ -1137,8 +1319,8 @@ class DataMapperAbstract implements DataMapperInterface $query = new Builder(self::$db); $query->prefix(self::$db->getPrefix()) ->delete() - ->into(static::$table) - ->where(static::$primaryField, '=', $objId); + ->from(static::$table) + ->where(static::$table . '.' . static::$primaryField, '=', $objId); $properties = $reflectionClass->getProperties(); @@ -1189,8 +1371,9 @@ class DataMapperAbstract implements DataMapperInterface public static function delete($obj, int $relations = RelationType::REFERENCE) { self::extend(__CLASS__); + $reflectionClass = new \ReflectionClass(get_class($obj)); - $objId = self::getObjectId($obj, $reflectionClass); + $objId = self::getObjectId($obj, $reflectionClass); if(empty($objId)) { return null; @@ -1229,6 +1412,30 @@ class DataMapperAbstract implements DataMapperInterface return $row; } + /** + * Populate data. + * + * @param array $result Result set + * + * @return array + * + * @since 1.0.0 + */ + public static function populateIterableArray(array $result) : array + { + $row = []; + + foreach ($result as $element) { + if (isset($element[static::$primaryField])) { + $row[$element[static::$primaryField]] = self::populateAbstractArray($element); + } else { + $row[] = self::populateAbstractArray($element); + } + } + + return $row; + } + /** * Populate data. * @@ -1244,7 +1451,7 @@ class DataMapperAbstract implements DataMapperInterface $class = static::class; $class = str_replace('Mapper', '', $class); - if (count($result) === 0) { + if (empty($result)) { $parts = explode('\\', $class); $name = $parts[$c = (count($parts) - 1)]; $parts[$c] = 'Null' . $name; @@ -1264,18 +1471,18 @@ class DataMapperAbstract implements DataMapperInterface * @param array[] $result Result set * @param mixed $obj Object to add the relations to * - * @return mixed + * @return void * * @since 1.0.0 */ - public static function populateManyToMany(array $result, &$obj) + public static function populateManyToMany(array $result, &$obj) /* : void */ { // todo: maybe pass reflectionClass as optional parameter for performance increase $reflectionClass = new \ReflectionClass(get_class($obj)); foreach ($result as $member => $values) { if (!empty($values) && $reflectionClass->hasProperty($member)) { - /** @var DataMapperAbstract $mapper */ + /** @var string $mapper */ $mapper = static::$hasMany[$member]['mapper']; $reflectionProperty = $reflectionClass->getProperty($member); @@ -1298,18 +1505,46 @@ class DataMapperAbstract implements DataMapperInterface } } + /** + * Populate data. + * + * @param array[] $result Result set + * @param array $obj Object to add the relations to + * + * @return void + * + * @since 1.0.0 + */ + public static function populateManyToManyArray(array $result, array &$obj) /* : void */ + { + foreach ($result as $member => $values) { + if (!empty($values)) { + /** @var string $mapper */ + $mapper = static::$hasMany[$member]['mapper']; + $values = array_diff($values, array_keys(self::$initObjects[$mapper] ?? [])); + + if(empty($values)) { + continue; + } + + $objects = $mapper::getArray($values); + $obj[$member] = $objects; + } + } + } + /** * Populate data. * * @param mixed $obj Object to add the relations to * - * @return mixed + * @return void * * @todo accept reflection class as parameter * * @since 1.0.0 */ - public static function populateHasOne(&$obj) + public static function populateHasOne(&$obj) /* : void */ { $reflectionClass = new \ReflectionClass(get_class($obj)); @@ -1322,10 +1557,10 @@ class DataMapperAbstract implements DataMapperInterface $reflectionProperty->setAccessible(true); } - /** @var DataMapperAbstract $mapper */ + /** @var string $mapper */ $mapper = static::$hasOne[$member]['mapper']; - if(self::isInitialized($mapper, $reflectionProperty->getValue($obj))) { + if(self::isInitialized($mapper, ($id = $reflectionProperty->getValue($obj)))) { $value = self::$initObjects[$mapper][$id]; } else { $value = $mapper::get($reflectionProperty->getValue($obj)); @@ -1343,15 +1578,42 @@ class DataMapperAbstract implements DataMapperInterface /** * Populate data. * - * @param mixed $obj Object to add the relations to + * @param array $obj Object to add the relations to * - * @return mixed + * @return void * * @todo accept reflection class as parameter * * @since 1.0.0 */ - public static function populateOwnsOne(&$obj) + public static function populateHasOneArray(array &$obj) /* : void */ + { + foreach (static::$hasOne as $member => $one) { + /** @var string $mapper */ + $mapper = static::$hasOne[$member]['mapper']; + + if(self::isInitialized($mapper, $obj['member'])) { + $value = self::$initObjects[$mapper][$obj['member']]; + } else { + $value = $mapper::getArray($obj[$member]); + } + + $obj[$member] = $value; + } + } + + /** + * Populate data. + * + * @param mixed $obj Object to add the relations to + * + * @return void + * + * @todo accept reflection class as parameter + * + * @since 1.0.0 + */ + public static function populateOwnsOne(&$obj) /* : void */ { $reflectionClass = new \ReflectionClass(get_class($obj)); @@ -1364,10 +1626,10 @@ class DataMapperAbstract implements DataMapperInterface $reflectionProperty->setAccessible(true); } - /** @var DataMapperAbstract $mapper */ + /** @var string $mapper */ $mapper = static::$ownsOne[$member]['mapper']; - if(self::isInitialized($mapper, $reflectionProperty->getValue($obj))) { + if(self::isInitialized($mapper, ($id = $reflectionProperty->getValue($obj)))) { $value = self::$initObjects[$mapper][$id]; } else { $value = $mapper::get($reflectionProperty->getValue($obj)); @@ -1385,15 +1647,42 @@ class DataMapperAbstract implements DataMapperInterface /** * Populate data. * - * @param mixed $obj Object to add the relations to + * @param array $obj Object to add the relations to * - * @return mixed + * @return void * * @todo accept reflection class as parameter * * @since 1.0.0 */ - public static function populateBelongsTo(&$obj) + public static function populateOwnsOneArray(array &$obj) /* : void */ + { + foreach (static::$ownsOne as $member => $one) { + /** @var string $mapper */ + $mapper = static::$ownsOne[$member]['mapper']; + + if(self::isInitialized($mapper, $obj[$member])) { + $value = self::$initObjects[$mapper][$obj[$member]]; + } else { + $value = $mapper::getArray($obj[$member]); + } + + $obj[$member] = $value; + } + } + + /** + * Populate data. + * + * @param mixed $obj Object to add the relations to + * + * @return void + * + * @todo accept reflection class as parameter + * + * @since 1.0.0 + */ + public static function populateBelongsTo(&$obj) /* : void */ { $reflectionClass = new \ReflectionClass(get_class($obj)); @@ -1406,15 +1695,15 @@ class DataMapperAbstract implements DataMapperInterface $reflectionProperty->setAccessible(true); } - /** @var DataMapperAbstract $mapper */ + /** @var string $mapper */ $mapper = static::$belongsTo[$member]['mapper']; - if(self::isInitialized($mapper, $reflectionProperty->getValue($obj))) { + if(self::isInitialized($mapper, ($id = $reflectionProperty->getValue($obj)))) { $value = self::$initObjects[$mapper][$id]; } else { $value = $mapper::get($reflectionProperty->getValue($obj)); } - + $reflectionProperty->setValue($obj, $value); if (!$accessible) { @@ -1424,6 +1713,33 @@ class DataMapperAbstract implements DataMapperInterface } } + /** + * Populate data. + * + * @param array $obj Object to add the relations to + * + * @return void + * + * @todo accept reflection class as parameter + * + * @since 1.0.0 + */ + public static function populateBelongsToArray(array &$obj) /* : void */ + { + foreach (static::$belongsTo as $member => $one) { + /** @var string $mapper */ + $mapper = static::$belongsTo[$member]['mapper']; + + if(self::isInitialized($mapper, $obj[$member])) { + $value = self::$initObjects[$mapper][$obj[$member]]; + } else { + $value = $mapper::get($obj[$member]); + } + + $obj[$member] = $value; + } + } + /** * Populate data. * @@ -1441,7 +1757,7 @@ class DataMapperAbstract implements DataMapperInterface $reflectionClass = new \ReflectionClass(get_class($obj)); foreach ($result as $column => $value) { - if (isset(static::$columns[$column]['internal']) && $reflectionClass->hasProperty(static::$columns[$column]['internal'])) { + if (isset(static::$columns[$column]['internal']) /* && $reflectionClass->hasProperty(static::$columns[$column]['internal']) */) { $reflectionProperty = $reflectionClass->getProperty(static::$columns[$column]['internal']); if (!($accessible = $reflectionProperty->isPublic())) { @@ -1449,7 +1765,10 @@ class DataMapperAbstract implements DataMapperInterface } if (in_array(static::$columns[$column]['type'], ['string', 'int', 'float', 'bool'])) { - settype($value, static::$columns[$column]['type']); + if($value !== null || $reflectionProperty->getValue($obj) !== null) { + settype($value, static::$columns[$column]['type']); + } + $reflectionProperty->setValue($obj, $value); } elseif (static::$columns[$column]['type'] === 'DateTime') { $reflectionProperty->setValue($obj, new \DateTime($value ?? '')); @@ -1471,6 +1790,41 @@ class DataMapperAbstract implements DataMapperInterface return $obj; } + /** + * Populate data. + * + * @param array $result Query result set + * + * @return array + * + * @throws \UnexpectedValueException + * + * @since 1.0.0 + */ + public static function populateAbstractArray(array $result) : array + { + $obj = []; + + foreach ($result as $column => $value) { + if (isset(static::$columns[$column]['internal'])) { + if (in_array(static::$columns[$column]['type'], ['string', 'int', 'float', 'bool'])) { + settype($value, static::$columns[$column]['type']); + $obj[static::$columns[$column]['internal']] = $value; + } elseif (static::$columns[$column]['type'] === 'DateTime') { + $obj[static::$columns[$column]['internal']] = new \DateTime($value ?? ''); + } elseif (static::$columns[$column]['type'] === 'Json') { + $obj[static::$columns[$column]['internal']] = json_decode($value, true); + } elseif (static::$columns[$column]['type'] === 'Serializable') { + $obj[static::$columns[$column]['internal']] = $value; + } else { + throw new \UnexpectedValueException('Value "' . static::$columns[$column]['type'] . '" is not supported.'); + } + } + } + + return $obj; + } + /** * Get object. * @@ -1518,6 +1872,147 @@ class DataMapperAbstract implements DataMapperInterface self::fillRelations($obj, $relations); self::clear(); + $countResulsts = count($obj); + + if($countResulsts === 0) { + return self::getNullModelObj(); + } elseif($countResulsts === 1) { + return reset($obj); + } + + return $obj; + } + + private static function getNullModelObj() + { + $class = static::class; + $class = str_replace('Mapper', '', $class); + $parts = explode('\\', $class); + $name = $parts[$c = (count($parts) - 1)]; + $parts[$c] = 'Null' . $name; + $class = implode('\\', $parts); + + return new $class(); + } + + /** + * Get object. + * + * @param mixed $primaryKey Key + * @param int $relations Load relations + * + * @return mixed + * + * @since 1.0.0 + */ + public static function getArray($primaryKey, int $relations = RelationType::ALL) : array + { + if(!isset(self::$parentMapper)) { + self::setUpParentMapper(); + } + + self::extend(__CLASS__); + + $primaryKey = (array) $primaryKey; + $obj = []; + + foreach ($primaryKey as $key => $value) { + if(self::isInitialized(static::class, $value)) { + continue; + } + + $obj[$value] = self::populateAbstractArray(self::getRaw($value)); + + self::addInitialized(static::class, $value); + } + + self::fillRelationsArray($obj, $relations); + self::clear(); + + return count($obj) === 1 ? reset($obj) : $obj; + } + + /** + * Get object. + * + * @param mixed $refKey Key + * @param string $ref The field that defines the for + * @param int $relations Load relations + * @param mixed $fill Object to fill + * + * @return mixed + * + * @since 1.0.0 + */ + public static function getFor($refKey, string $ref, int $relations = RelationType::ALL, $fill = null) + { + if(!isset(self::$parentMapper)) { + self::setUpParentMapper(); + } + + self::extend(__CLASS__); + + $refKey = (array) $refKey; + $obj = []; + + foreach ($refKey as $key => $value) { + $toLoad = []; + + if(isset(static::$hasMany[$ref]) && static::$hasMany[$ref]['src'] !== null) { + $toLoad = self::getHasManyPrimaryKeys($value, $ref); + } else { + $toLoad = self::getPrimaryKeysBy($value, self::getColumnByMember($ref)); + } + + $obj[$value] = self::get($toLoad, $relations, $fill); + } + + $countResulsts = count($obj); + + if($countResulsts === 0) { + return self::getNullModelObj(); + } elseif($countResulsts === 1) { + return reset($obj); + } + + return $obj; + } + + /** + * Get object. + * + * @param mixed $refKey Key + * @param string $ref The field that defines the for + * @param int $relations Load relations + * @param mixed $fill Object to fill + * + * @return mixed + * + * @since 1.0.0 + */ + public static function getForArray($refKey, string $ref, int $relations = RelationType::ALL, $fill = null) + { + if(!isset(self::$parentMapper)) { + self::setUpParentMapper(); + } + + self::extend(__CLASS__); + + $refKey = (array) $refKey; + $obj = []; + + foreach ($refKey as $key => $value) { + $toLoad = []; + + if(isset(static::$hasMany[$ref]) && static::$hasMany[$ref]['src'] !== null) { + $toLoad = self::getHasManyPrimaryKeys($value, $ref); + } else { + $toLoad = self::getPrimaryKeysBy($value, self::getColumnByMember($ref)); + } + + $obj[$value] = self::get($toLoad, $relations, $fill); + } + return count($obj) === 1 ? reset($obj) : $obj; } @@ -1544,6 +2039,29 @@ class DataMapperAbstract implements DataMapperInterface return $obj; } + /** + * Get object. + * + * @param int $relations Load relations + * @param string $lang Language + * + * @return array + * + * @since 1.0.0 + */ + public static function getAllArray(int $relations = RelationType::ALL, string $lang = '') : array + { + if(!isset(self::$parentMapper)) { + self::setUpParentMapper(); + } + + $obj = self::populateIterableArray(self::getAllRaw($lang)); + self::fillRelationsArray($obj, $relations); + self::clear(); + + return $obj; + } + /** * Find data. * @@ -1618,7 +2136,7 @@ class DataMapperAbstract implements DataMapperInterface */ public static function getAllByQuery(Builder $query, int $relations = RelationType::ALL) : array { - $sth = self::$db->con->prepare($query->toSql()); + $sth = self::$db->con->prepare($query->toSql()); $sth->execute(); $results = $sth->fetchAll(\PDO::FETCH_ASSOC); @@ -1667,9 +2185,9 @@ class DataMapperAbstract implements DataMapperInterface */ public static function fillRelations(array &$obj, int $relations = RelationType::ALL) /* : void */ { - $hasMany = !empty(static::$hasMany); - $hasOne = !empty(static::$hasOne); - $ownsOne = !empty(static::$ownsOne); + $hasMany = !empty(static::$hasMany); + $hasOne = !empty(static::$hasOne); + $ownsOne = !empty(static::$ownsOne); $belongsTo = !empty(static::$belongsTo); if ($relations !== RelationType::NONE && ($hasMany || $hasOne || $ownsOne || $belongsTo)) { @@ -1694,6 +2212,45 @@ class DataMapperAbstract implements DataMapperInterface } } + /** + * Fill object with relations + * + * @param mixed $obj Objects to fill + * @param int $relations Relations type + * + * @return void + * + * @since 1.0.0 + */ + public static function fillRelationsArray(array &$obj, int $relations = RelationType::ALL) /* : void */ + { + $hasMany = !empty(static::$hasMany); + $hasOne = !empty(static::$hasOne); + $ownsOne = !empty(static::$ownsOne); + $belongsTo = !empty(static::$belongsTo); + + if ($relations !== RelationType::NONE && ($hasMany || $hasOne || $ownsOne || $belongsTo)) { + foreach ($obj as $key => $value) { + /* loading relations from relations table and populating them and then adding them to the object */ + if ($hasMany) { + self::populateManyToManyArray(self::getHasManyRaw($key, $relations), $obj[$key]); + } + + if ($hasOne) { + self::populateHasOneArray($obj[$key]); + } + + if ($ownsOne) { + self::populateOwnsOneArray($obj[$key]); + } + + if ($belongsTo) { + self::populateBelongsToArray($obj[$key]); + } + } + } + } + /** * Get object. * @@ -1716,6 +2273,58 @@ class DataMapperAbstract implements DataMapperInterface return is_bool($results) ? [] : $results; } + /** + * Get object. + * + * @param mixed $refKey Key + * @param string $ref Ref + * + * @return mixed + * + * @since 1.0.0 + */ + public static function getPrimaryKeysBy($refKey, string $ref) : array + { + $query = new Builder(self::$db); + $query->prefix(self::$db->getPrefix()) + ->select(static::$table . '.' . static::$primaryField) + ->from(static::$table) + ->where(static::$table . '.' . $ref, '=', $refKey); + + $sth = self::$db->con->prepare($query->toSql()); + $sth->execute(); + + $results = array_column($sth->fetchAll(\PDO::FETCH_NUM) ?? [], 0); + + return $results; + } + + /** + * Get object. + * + * @param mixed $refKey Key + * @param string $ref Ref + * + * @return mixed + * + * @since 1.0.0 + */ + public static function getHasManyPrimaryKeys($refKey, string $ref) : array + { + $query = new Builder(self::$db); + $query->prefix(self::$db->getPrefix()) + ->select(static::$hasMany[$ref]['table'] . '.' . static::$hasMany[$ref]['dst']) + ->from(static::$hasMany[$ref]['table']) + ->where(static::$hasMany[$ref]['table'] . '.' . static::$hasMany[$ref]['src'], '=', $refKey); + + $sth = self::$db->con->prepare($query->toSql()); + $sth->execute(); + + $results = array_column($sth->fetchAll(\PDO::FETCH_NUM) ?? [], 0); + + return $results; + } + /** * Get all in raw output. * @@ -1733,7 +2342,7 @@ class DataMapperAbstract implements DataMapperInterface $query->where(static::$table . '.' . static::$language_field, '=', $lang, 'AND'); } - $sth = self::$db->con->prepare($query->toSql()); + $sth = self::$db->con->prepare($query->toSql()); $sth->execute(); $results = $sth->fetchAll(\PDO::FETCH_ASSOC); @@ -1767,7 +2376,6 @@ class DataMapperAbstract implements DataMapperInterface ->from($value['table']) ->where($value['table'] . '.' . $value['dst'], '=', $primaryKey); } elseif ($relations === RelationType::NEWEST) { - /* SELECT c.*, p1.* FROM customer c @@ -1865,13 +2473,7 @@ class DataMapperAbstract implements DataMapperInterface $result = static::getAllByQuery($query); } } else { - $class = static::class; - $class = str_replace('Mapper', '', $class); - $parts = explode('\\', $class); - $name = $parts[$c = (count($parts) - 1)]; - $parts[$c] = 'Null' . $name; - $class = implode('\\', $parts); - $result = new $class(); + return self::getNullModelObj(); } return $result; @@ -1948,4 +2550,26 @@ class DataMapperAbstract implements DataMapperInterface return count($results) === 0; } + + /** + * Find database column name by member name + * + * @param string $name member name + * + * @return string + * + * @throws \Exception Throws this exception if the member couldn't be found + * + * @since 1.0.0 + */ + private static function getColumnByMember(string $name) : string + { + foreach(static::$columns as $cName => $column) { + if($column['internal'] === $name) { + return $cName; + } + } + + throw new \Exception('Invalid member name'); + } } diff --git a/DataStorage/Database/DataMapperBaseAbstract.php b/DataStorage/Database/DataMapperBaseAbstract.php new file mode 100644 index 000000000..5974cd0a4 --- /dev/null +++ b/DataStorage/Database/DataMapperBaseAbstract.php @@ -0,0 +1,437 @@ + [], + 'createdAt' => [], + 'columns' => [], + 'hasMany' => [], + 'hasOne' => [], + 'ownsOne' => [], + 'table' => [], + ]; + + /** + * Constructor. + * + * @since 1.0.0 + * @codeCoverageIgnore + */ + private function __construct() + { + } + + /** + * Clone. + * + * @return void + * + * @since 1.0.0 + * @codeCoverageIgnore + */ + private function __clone() + { + } + + /** + * Set database connection. + * + * @param ConnectionAbstract $con Database connection + * + * @return void + * + * @since 1.0.0 + */ + public static function setConnection(ConnectionAbstract $con) /* : void */ + { + self::$db = $con; + } + + /** + * Get primary field. + * + * @return string + * + * @since 1.0.0 + */ + public static function getPrimaryField() : string + { + return static::$primaryField; + } + + /** + * Get main table. + * + * @return string + * + * @since 1.0.0 + */ + public static function getTable() : string + { + return static::$table; + } + + /** + * Collect values from extension. + * + * @param mixed $class Current extended mapper + * + * @return void + * + * @since 1.0.0 + */ + private static function extend($class) /* : void */ + { + /* todo: have to implement this in the queries, so far not used */ + self::$collection['primaryField'][] = $class::$primaryField; + self::$collection['createdAt'][] = $class::$createdAt; + self::$collection['columns'][] = $class::$columns; + self::$collection['hasMany'][] = $class::$hasMany; + self::$collection['hasOne'][] = $class::$hasOne; + self::$collection['ownsOne'][] = $class::$ownsOne; + self::$collection['table'][] = $class::$table; + + if (($parent = get_parent_class($class)) !== false && !$class::$overwrite) { + self::extend($parent); + } + } + + /** + * Resets all loaded mapper variables. + * + * This is used after one action is performed otherwise other models would use wrong settings. + * + * @return void + * + * @since 1.0.0 + */ + public static function clear() /* : void */ + { + self::$overwrite = true; + self::$primaryField = ''; + self::$createdAt = ''; + self::$columns = []; + self::$hasMany = []; + self::$hasOne = []; + self::$ownsOne = []; + self::$table = ''; + self::$fields = []; + self::$collection = [ + 'primaryField' => [], + 'createdAt' => [], + 'columns' => [], + 'hasOne' => [], + 'ownsMany' => [], + 'ownsOne' => [], + 'table' => [], + ]; + + // clear parent and objects + if(static::class === self::$parentMapper) { + self::$initObjects = []; + self::$parentMapper = null; + } + } + + /** + * Get created at column + * + * @return string + * + * @since 1.0.0 + */ + public static function getCreatedAt() : string + { + return static::$createdAt; + } + + /** + * Get id of object + * + * @param Object $obj Model to create + * @param \ReflectionClass $reflectionClass Reflection class + * + * @return mixed + * + * @since 1.0.0 + */ + private static function getObjectId($obj, \ReflectionClass $reflectionClass = null) + { + $reflectionClass = $reflectionClass ?? new \ReflectionClass(get_class($obj)); + $reflectionProperty = $reflectionClass->getProperty(static::$columns[static::$primaryField]['internal']); + + if (!($isPublic = $reflectionProperty->isPublic())) { + $reflectionProperty->setAccessible(true); + } + + $objectId = $reflectionProperty->getValue($obj); + + if (!$isPublic) { + $reflectionProperty->setAccessible(false); + } + + return $objectId; + } + + /** + * Set id to model + * + * @param \ReflectionClass $reflectionClass Reflection class + * @param Object $obj Object to create + * @param mixed $objId Id to set + * + * @return void + * + * @since 1.0.0 + */ + private static function setObjectId(\ReflectionClass $reflectionClass, $obj, $objId) /* : void */ + { + $reflectionProperty = $reflectionClass->getProperty(static::$columns[static::$primaryField]['internal']); + + if (!($isPublic = $reflectionProperty->isPublic())) { + $reflectionProperty->setAccessible(true); + } + + settype($objId, static::$columns[static::$primaryField]['type']); + $reflectionProperty->setValue($obj, $objId); + + if (!$isPublic) { + $reflectionProperty->setAccessible(false); + } + } + + /** + * Parse value + * + * @param string $type Value type + * @param mixed $value Value to parse + * + * @return mixed + * + * @since 1.0.0 + */ + private static function parseValue(string $type, $value) + { + if (is_null($value)) { + return null; + } elseif ($type === 'DateTime') { + return $value->format('Y-m-d H:i:s'); + } elseif ($type === 'Json' || $type === 'jsonSerializable') { + return json_encode($value); + } elseif ($type === 'Serializable') { + return $value->serialize(); + } elseif ($value instanceof \JsonSerializable) { + return json_encode($value->jsonSerialize()); + } elseif (is_object($value) && method_exists($value, 'getId')) { + return $value->getId(); + } elseif ($type === 'int') { + return (int) $value; + } elseif ($type === 'string') { + return (string) $value; + } elseif ($type === 'float') { + return (float) $value; + } elseif ($type === 'bool') { + return (bool) $value; + } + + return $value; + } + + /** + * Get mapper specific builder + * + * @param Builder $query Query to fill + * + * @return Builder + * + * @since 1.0.0 + */ + public static function getQuery(Builder $query = null) : Builder + { + $query = $query ?? new Builder(self::$db); + $query->prefix(self::$db->getPrefix()) + ->select('*') + ->from(static::$table); + + return $query; + } + + /** + * Define the highest mapper of this request + * + * @return void + * + * @since 1.0.0 + */ + private static function setUpParentMapper() /* : void */ + { + self::$parentMapper = static::class; + } + + private static function getColumnByMember(string $name) : string + { + foreach(static::$columns as $cName => $column) { + if($column['internal'] === $name) { + return $cName; + } + } + + throw \Exception(); + } +} diff --git a/DataStorage/Database/DatabaseExceptionFactory.php b/DataStorage/Database/DatabaseExceptionFactory.php index 7a88508d4..99565eb00 100644 --- a/DataStorage/Database/DatabaseExceptionFactory.php +++ b/DataStorage/Database/DatabaseExceptionFactory.php @@ -15,6 +15,8 @@ declare(strict_types=1); namespace phpOMS\DataStorage\Database; +use phpOMS\DataStorage\Database\Schema\Exception\TableException; + /** * Database exception factory. * diff --git a/DataStorage/Database/DatabasePool.php b/DataStorage/Database/DatabasePool.php index aaec74103..5f4751454 100644 --- a/DataStorage/Database/DatabasePool.php +++ b/DataStorage/Database/DatabasePool.php @@ -77,10 +77,14 @@ class DatabasePool * * @since 1.0.0 */ - public function get(string $key = 'core') /* : ?ConnectionAbstract */ + public function get(string $key = '') /* : ?ConnectionAbstract */ { - if (!isset($this->pool[$key])) { - return null; /* todo: return nullconnection */ + if((!empty($key) && !isset($this->pool[$key])) || empty($this->pool)) { + return null; + } + + if(empty($key)) { + return reset($this->pool); } return $this->pool[$key]; diff --git a/DataStorage/Database/DatabaseStatus.php b/DataStorage/Database/DatabaseStatus.php index db73f88cb..1a1877a05 100644 --- a/DataStorage/Database/DatabaseStatus.php +++ b/DataStorage/Database/DatabaseStatus.php @@ -30,10 +30,10 @@ use phpOMS\Stdlib\Base\Enum; */ abstract class DatabaseStatus extends Enum { - /* public */ const OK = 0; /* Database connection successful */ + /* public */ const OK = 0; /* Database connection successful */ /* public */ const MISSING_DATABASE = 1; /* Couldn't find database */ - /* public */ const MISSING_TABLE = 2; /* One of the core tables couldn't be found */ - /* public */ const FAILURE = 3; /* Unknown failure */ - /* public */ const READONLY = 4; /* Database connection is in readonly (but ok) */ - /* public */ const CLOSED = 5; /* Database connection closed */ + /* public */ const MISSING_TABLE = 2; /* One of the core tables couldn't be found */ + /* public */ const FAILURE = 3; /* Unknown failure */ + /* public */ const READONLY = 4; /* Database connection is in readonly (but ok) */ + /* public */ const CLOSED = 5; /* Database connection closed */ } diff --git a/DataStorage/Database/DatabaseType.php b/DataStorage/Database/DatabaseType.php index f0ac90eb4..2de4a65c0 100644 --- a/DataStorage/Database/DatabaseType.php +++ b/DataStorage/Database/DatabaseType.php @@ -30,9 +30,9 @@ use phpOMS\Stdlib\Base\Enum; */ abstract class DatabaseType extends Enum { - /* public */ const MYSQL = 'mysql'; /* MySQL */ - /* public */ const SQLITE = 'sqlite'; /* SQLITE */ - /* public */ const PGSQL = 2; /* PostgreSQL */ - /* public */ const ORACLE = 3; /* Oracle */ - /* public */ const SQLSRV = 'mssql'; /* Microsoft SQL Server */ + /* public */ const MYSQL = 'mysql'; /* MySQL */ + /* public */ const SQLITE = 'sqlite'; /* SQLITE */ + /* public */ const PGSQL = 2; /* PostgreSQL */ + /* public */ const ORACLE = 3; /* Oracle */ + /* public */ const SQLSRV = 'mssql'; /* Microsoft SQL Server */ } diff --git a/DataStorage/Database/Exception/InvalidConnectionConfigException.php b/DataStorage/Database/Exception/InvalidConnectionConfigException.php index c56ddff3e..5b1a3cef7 100644 --- a/DataStorage/Database/Exception/InvalidConnectionConfigException.php +++ b/DataStorage/Database/Exception/InvalidConnectionConfigException.php @@ -24,7 +24,7 @@ namespace phpOMS\DataStorage\Database\Exception; * @link http://orange-management.com * @since 1.0.0 */ -class InvalidConnectionConfigException extends \RuntimeException +class InvalidConnectionConfigException extends \InvalidArgumentException { /** * Constructor. diff --git a/DataStorage/Database/GrammarAbstract.php b/DataStorage/Database/GrammarAbstract.php index 99e0c5e47..bd5bcc1c3 100644 --- a/DataStorage/Database/GrammarAbstract.php +++ b/DataStorage/Database/GrammarAbstract.php @@ -15,8 +15,6 @@ declare(strict_types=1); namespace phpOMS\DataStorage\Database; -use phpOMS\Utils\StringUtils; - /** * Grammar. * @@ -79,7 +77,7 @@ abstract class GrammarAbstract /** * Special keywords. * - * @var string + * @var array * @since 1.0.0 */ protected $specialKeywords = [ @@ -170,6 +168,41 @@ abstract class GrammarAbstract { $expression = ''; + foreach ($elements as $key => $element) { + if (is_string($element) && $element !== '*') { + if(strpos($element, '.') === false) { + $prefix = ''; + } + + $expression .= $this->compileSystem($element, $prefix) . ', '; + } elseif (is_string($element) && $element === '*') { + $expression .= '*, '; + } elseif ($element instanceof \Closure) { + $expression .= $element() . ', '; + } elseif ($element instanceof BuilderAbstract) { + $expression .= $element->toSql() . ', '; + } else { + throw new \InvalidArgumentException(); + } + } + + return rtrim($expression, ', '); + } + + /** + * Expressionize elements. + * + * @param array $elements Elements + * @param string $prefix Prefix for table + * + * @return string + * + * @since 1.0.0 + */ + protected function expressionizeTable(array $elements, string $prefix = '') : string + { + $expression = ''; + foreach ($elements as $key => $element) { if (is_string($element) && $element !== '*') { $expression .= $this->compileSystem($element, $prefix) . ', '; @@ -205,7 +238,7 @@ abstract class GrammarAbstract $identifier = $this->systemIdentifier; foreach($this->specialKeywords as $keyword) { - if(StringUtils::startsWith($system, $keyword)) { + if($keyword === '' || strrpos($system, $keyword, -strlen($system)) !== false) { $prefix = ''; $identifier = ''; } diff --git a/DataStorage/Database/Query/Builder.php b/DataStorage/Database/Query/Builder.php index 9628f1736..996b6a15b 100644 --- a/DataStorage/Database/Query/Builder.php +++ b/DataStorage/Database/Query/Builder.php @@ -45,6 +45,22 @@ class Builder extends BuilderAbstract */ public $selects = []; + /** + * Columns. + * + * @var array + * @since 1.0.0 + */ + public $updates = []; + + /** + * Stupid work around because value needs to be not null for it to work in Grammar. + * + * @var array + * @since 1.0.0 + */ + public $deletes = [1]; + /** * Into. * @@ -69,6 +85,14 @@ class Builder extends BuilderAbstract */ public $values = []; + /** + * Into columns. + * + * @var array + * @since 1.0.0 + */ + public $sets = []; + /** * Distinct. * @@ -165,12 +189,6 @@ class Builder extends BuilderAbstract */ public $raw = ''; - protected $unionLimit = null; - - protected $unionOffset = null; - - protected $unionOrders = []; - /** * Comparison OPERATORS. * @@ -312,7 +330,7 @@ class Builder extends BuilderAbstract */ public function newQuery() : Builder { - return new static($this->connection, $this->grammar); + return new static($this->connection, $this->isReadOnly); } /** @@ -352,7 +370,7 @@ class Builder extends BuilderAbstract } $this->type = QueryType::RAW; - $this->raw = $raw; + $this->raw = rtrim($raw, ';'); return $this; } @@ -521,14 +539,9 @@ class Builder extends BuilderAbstract * * @since 1.0.0 */ - public function andWhere(Where $where) : Builder + public function andWhere($where, $operator = null, $values = null) : Builder { - $this->wheres[][] = [ - 'column' => $where, - 'boolean' => 'and', - ]; - - return $this; + return $this->where($where, $operator, $values, 'and'); } /** @@ -540,14 +553,9 @@ class Builder extends BuilderAbstract * * @since 1.0.0 */ - public function orWhere(Where $where) : Builder + public function orWhere($where, $operator = null, $values = null) : Builder { - $this->wheres[][] = [ - 'column' => $where, - 'boolean' => 'or', - ]; - - return $this; + return $this->where($where, $operator, $values, 'or'); } /** @@ -669,10 +677,14 @@ class Builder extends BuilderAbstract public function orderBy($columns, $order = 'DESC') : Builder { if (is_string($columns) || $columns instanceof \Closure) { - $this->orders[] = ['column' => $columns, 'order' => $order]; + if(!isset($this->orders[$order])) { + $this->orders[$order] = []; + } + + $this->orders[$order][] = $columns; } elseif (is_array($columns)) { foreach ($columns as $key => $column) { - $this->orders[] = ['column' => $column, 'order' => $order[$key]]; + $this->orders[is_string($order) ? $order : $order[$key]][] = $column; } } else { throw new \InvalidArgumentException(); @@ -781,15 +793,6 @@ class Builder extends BuilderAbstract return $this->select('COUNT(' . $table . ')'); } - /** - * Check if exists. - * - * @since 1.0.0 - */ - public function exists() - { - } - /** * Select minimum. * @@ -902,6 +905,39 @@ class Builder extends BuilderAbstract return $this; } + /** + * Values to insert. + * + * @param array $sets Values + * + * @return Builder + * + * @since 1.0.0 + */ + public function sets(...$sets) : Builder + { + $this->sets[] = $sets; + + return $this; + } + + /** + * Values to insert. + * + * @param mixed $set Values + * @param string $type Data type to insert + * + * @return Builder + * + * @since 1.0.0 + */ + public function set($set, string $type = 'string') : Builder + { + $this->sets[key($set)] = current($set); + + return $this; + } + /** * Update columns. * @@ -911,7 +947,7 @@ class Builder extends BuilderAbstract * * @since 1.0.0 */ - public function update(...$columns) : Builder + public function update(...$tables) : Builder { if($this->isReadOnly) { throw new \Exception(); @@ -919,13 +955,28 @@ class Builder extends BuilderAbstract $this->type = QueryType::UPDATE; - foreach ($columns as $key => $column) { - $this->inserts[] = $column; + foreach ($tables as $key => $table) { + if (is_string($table) || $table instanceof \Closure) { + $this->updates[] = $table; + } else { + throw new \InvalidArgumentException(); + } } return $this; } + public function delete() : Builder + { + if($this->isReadOnly) { + throw new \Exception(); + } + + $this->type = QueryType::DELETE; + + return $this; + } + /** * Increment value. * diff --git a/DataStorage/Database/Query/Grammar/Grammar.php b/DataStorage/Database/Query/Grammar/Grammar.php index a67b302b1..3ba97ff75 100644 --- a/DataStorage/Database/Query/Grammar/Grammar.php +++ b/DataStorage/Database/Query/Grammar/Grammar.php @@ -78,6 +78,18 @@ class Grammar extends GrammarAbstract 'wheres', ]; + /** + * Update components. + * + * @var string[] + * @since 1.0.0 + */ + protected $deleteComponents = [ + 'deletes', + 'from', + 'wheres', + ]; + /** * Random components. * @@ -111,10 +123,10 @@ class Grammar extends GrammarAbstract $components = $this->insertComponents; break; case QueryType::UPDATE: - $components = []; + $components = $this->updateComponents; break; case QueryType::DELETE: - $components = []; + $components = $this->deleteComponents; break; case QueryType::RANDOM: $components = $this->selectComponents; @@ -157,6 +169,42 @@ class Grammar extends GrammarAbstract return ($query->distinct ? 'SELECT DISTINCT ' : 'SELECT ') . $expression; } + /** + * Compile select. + * + * @param Builder $query Builder + * @param array $columns Columns + * + * @return string + * + * @since 1.0.0 + */ + protected function compileUpdates(Builder $query, array $table) : string + { + $expression = $this->expressionizeTable($table, $query->getPrefix()); + + if ($expression === '') { + return ''; + } + + return 'UPDATE ' . $expression; + } + + /** + * Compile select. + * + * @param Builder $query Builder + * @param array $columns Columns + * + * @return string + * + * @since 1.0.0 + */ + protected function compileDeletes(Builder $query, array $columns) : string + { + return 'DELETE'; + } + /** * Compile from. * @@ -169,7 +217,7 @@ class Grammar extends GrammarAbstract */ protected function compileFrom(Builder $query, array $table) : string { - $expression = $this->expressionizeTableColumn($table, $query->getPrefix()); + $expression = $this->expressionizeTable($table, $query->getPrefix()); if ($expression === '') { return ''; @@ -200,7 +248,7 @@ class Grammar extends GrammarAbstract } } - if ($expression == '') { + if ($expression === '') { return ''; } @@ -240,6 +288,9 @@ class Grammar extends GrammarAbstract if (isset($element['value'])) { $expression .= ' ' . strtoupper($element['operator']) . ' ' . $this->compileValue($element['value'], $query->getPrefix()); + } else { + $operator = strtoupper($element['operator']) === '=' ? 'IS' : 'IS NOT'; + $expression .= ' ' . $operator . ' ' . $this->compileValue($element['value'], $query->getPrefix()); } return $expression; @@ -282,15 +333,17 @@ class Grammar extends GrammarAbstract return '(' . rtrim($values, ', ') . ')'; } elseif ($value instanceof \DateTime) { - return $this->valueQuotes . $value->format('Y-m-d h:m:s') . $this->valueQuotes; + return $this->valueQuotes . $value->format('Y-m-d H:i:s') . $this->valueQuotes; } elseif (is_null($value)) { return 'NULL'; } elseif (is_bool($value)) { return (string) ((int) $value); + } elseif (is_float($value)) { + return (string) $value; } elseif ($value instanceof Column) { return $this->compileSystem($value->getColumn(), $prefix); } else { - throw new \InvalidArgumentException(); + throw new \InvalidArgumentException(gettype($value)); } } @@ -366,11 +419,16 @@ class Grammar extends GrammarAbstract { $expression = ''; - foreach ($orders as $order) { - $expression .= $this->compileSystem($order['column'], $query->getPrefix()) . ' ' . $order['order'] . ', '; + foreach ($orders as $key => $order) { + foreach($order as $column) { + $expression .= $this->compileSystem($column, $query->getPrefix()) . ', '; + } + + $expression = rtrim($expression, ', '); + $expression .= ' ' . $key . ', '; } - if ($expression == '') { + if ($expression === '') { return ''; } @@ -422,7 +480,7 @@ class Grammar extends GrammarAbstract $cols .= $this->compileSystem($column) . ', '; } - if ($cols == '') { + if ($cols === '') { return ''; } @@ -447,10 +505,38 @@ class Grammar extends GrammarAbstract $vals .= $this->compileValue($value) . ', '; } - if ($vals == '') { + if ($vals === '') { return ''; } return 'VALUES ' . rtrim($vals, ', '); } + + /** + * Compile insert values. + * + * @param Builder $query Builder + * @param array $values Values + * + * @return string + * + * @since 1.0.0 + */ + protected function compileSets(Builder $query, array $values) : string + { + $vals = ''; + + foreach ($values as $column => $value) { + // todo change expressionizeTableColumn to accept single column and create additionl for Columns + $expression = $this->expressionizeTableColumn([$column], $query->getPrefix()); + + $vals .= $expression . ' = ' . $this->compileValue($value) . ', '; + } + + if ($vals === '') { + return ''; + } + + return 'SET ' . rtrim($vals, ', '); + } } diff --git a/DataStorage/Database/RelationType.php b/DataStorage/Database/RelationType.php index 206e5d019..2ce47c83c 100644 --- a/DataStorage/Database/RelationType.php +++ b/DataStorage/Database/RelationType.php @@ -30,11 +30,11 @@ use phpOMS\Stdlib\Base\Enum; */ abstract class RelationType extends Enum { - /* public */ const NONE = 0; - /* public */ const NEWEST = 1; - /* public */ const BELONGS_TO = 2; - /* public */ const OWNS_ONE = 4; - /* public */ const HAS_MANY = 8; - /* public */ const ALL = 16; - /* public */ const REFERENCE = 32; + /* public */ const NONE = 1; + /* public */ const NEWEST = 2; + /* public */ const BELONGS_TO = 4; + /* public */ const OWNS_ONE = 8; + /* public */ const HAS_MANY = 16; + /* public */ const ALL = 32; + /* public */ const REFERENCE = 64; } diff --git a/DataStorage/Session/HttpSession.php b/DataStorage/Session/HttpSession.php index 05a33847c..8101b7553 100644 --- a/DataStorage/Session/HttpSession.php +++ b/DataStorage/Session/HttpSession.php @@ -86,8 +86,10 @@ class HttpSession implements SessionInterface $this->inactivityInterval = $inactivityInterval; - session_set_cookie_params($liftetime, '/', '', false, true); - session_start(); + if(session_status() !== PHP_SESSION_ACTIVE && !headers_sent()) { + session_set_cookie_params($liftetime, '/', '', false, true); + session_start(); + } if($this->inactivityInterval > 0 && ($this->inactivityInterval + ($_SESSION['lastActivity'] ?? 0) < time())) { $this->destroy(); diff --git a/Dispatcher/Dispatcher.php b/Dispatcher/Dispatcher.php index 1c8cb680a..ba8786ae8 100644 --- a/Dispatcher/Dispatcher.php +++ b/Dispatcher/Dispatcher.php @@ -135,9 +135,7 @@ class Dispatcher { $views = []; foreach ($controller as $controllerSingle) { - foreach ($controllerSingle as $c) { - $views += $this->dispatch($c, ...$data); - } + $views += $this->dispatch($controllerSingle, ...$data); } return $views; diff --git a/Html/TagType.php b/Html/TagType.php deleted file mode 100644 index 9c246f465..000000000 --- a/Html/TagType.php +++ /dev/null @@ -1,40 +0,0 @@ - */ - /* public */ const BUTTON = 1; /*