From 3eecdc303dc053b026b2e91bfc5527c09f8e6b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Tue, 19 Oct 2021 13:14:43 +0200 Subject: [PATCH] Fix entity usage (#78) --- .github/workflows/test-unit.yml | 29 +++++++++++--------------- README.md | 17 ++++++++-------- behat.yml.dist | 9 ++++++--- composer.json | 10 ++++----- demos/_demo-data/create-db.php | 32 ++++++++++++++++++----------- phpstan.neon.dist | 12 +++-------- src/Form/Control/Generic.php | 2 +- src/RoleAdmin.php | 7 ++++--- src/UserAdmin.php | 36 ++++++++++++++++----------------- tests/Generic.php | 6 +++--- 10 files changed, 79 insertions(+), 81 deletions(-) diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index be2abb19..078eb6c7 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -140,7 +140,7 @@ jobs: php -r '(new PDO("mysql:host=mysql", "root", "atk4_pass_root"))->exec("ALTER USER '"'"'atk4_test_user'"'"'@'"'"'%'"'"' WITH MAX_USER_CONNECTIONS 5");' php -r '(new PDO("mysql:host=mariadb", "root", "atk4_pass_root"))->exec("ALTER USER '"'"'atk4_test_user'"'"'@'"'"'%'"'"' WITH MAX_USER_CONNECTIONS 5");' php -r '(new PDO("pgsql:host=postgres;dbname=atk4_test", "atk4_test_user", "atk4_pass"))->exec("ALTER ROLE atk4_test_user CONNECTION LIMIT 1");' - if [ -n "$LOG_COVERAGE" ]; then mkdir coverage && cp tools/CoverageUtil.php demos; fi + # if [ -n "$LOG_COVERAGE" ]; then mkdir coverage && cp tools/CoverageUtil.php demos; fi sed -E "s/\(('sqlite:.+)\);/(\$_ENV['DB_DSN'] ?? \\1, \$_ENV['DB_USER'] ?? null, \$_ENV['DB_PASSWORD'] ?? null);/g" -i demos/db.default.php - name: "Run tests: SQLite" @@ -216,7 +216,7 @@ jobs: name: Behat runs-on: ubuntu-latest container: - image: ghcr.io/mvorisek/image-php:${{ matrix.php }}-node + image: ghcr.io/mvorisek/image-php:${{ matrix.php }}-selenium strategy: fail-fast: false matrix: @@ -252,12 +252,6 @@ jobs: image: ghcr.io/mvorisek/docker-oracle-xe-11g env: ORACLE_ALLOW_REMOTE: true - selenium-chrome: - image: selenium/standalone-chrome:latest - options: --health-cmd "/opt/bin/check-grid.sh" - selenium-firefox: - image: selenium/standalone-firefox:latest - options: --health-cmd "/opt/bin/check-grid.sh" steps: - name: Checkout uses: actions/checkout@v2 @@ -310,19 +304,20 @@ jobs: php -r '(new PDO("mysql:host=mysql", "root", "atk4_pass_root"))->exec("ALTER USER '"'"'atk4_test_user'"'"'@'"'"'%'"'"' WITH MAX_USER_CONNECTIONS 5");' php -r '(new PDO("mysql:host=mariadb", "root", "atk4_pass_root"))->exec("ALTER USER '"'"'atk4_test_user'"'"'@'"'"'%'"'"' WITH MAX_USER_CONNECTIONS 5");' php -r '(new PDO("pgsql:host=postgres;dbname=atk4_test", "atk4_test_user", "atk4_pass"))->exec("ALTER ROLE atk4_test_user CONNECTION LIMIT 1");' - if [ -n "$LOG_COVERAGE" ]; then mkdir coverage && cp tools/CoverageUtil.php demos; fi + # if [ -n "$LOG_COVERAGE" ]; then mkdir coverage && cp tools/CoverageUtil.php demos; fi sed -E "s/\(('sqlite:.+)\);/(\$_ENV['DB_DSN'] ?? \\1, \$_ENV['DB_USER'] ?? null, \$_ENV['DB_PASSWORD'] ?? null);/g" -i demos/db.default.php - sed -i "s~'https://raw.githack.com/atk4/ui/develop/public.*~'/public',~" vendor/atk4/ui/src/App.php - php -S 172.18.0.2:8888 > /dev/null 2>&1 & - sleep 0.2 + sed -i "s~'https://raw.githack.com/atk4/ui/develop/public.*~'/vendor/atk4/ui/public',~" vendor/atk4/ui/src/App.php + ci_wait_until () { timeout 30 sh -c "until { $1 2> /dev/null; }; do sleep 0.02; done" || timeout 15 sh -c "$1" || { echo "health timeout: $1"; exit 1; }; } + php -S 127.0.0.1:8888 > /dev/null 2>&1 & + ci_wait_until 'nc -w 1 127.0.0.1 8888' + if [ -f /etc/alpine-release ]; then addgroup browser && adduser browser -G browser -D -s /bin/sh; else adduser browser --gecos "" --disabled-login -shell /bin/sh > /dev/null; fi + { Xvfb -ac :99 -screen 0 1920x1200x24 2> /dev/null & } && export DISPLAY=:99 + ci_wait_until '[ -e /tmp/.X11-unix/X99 ]' + su browser -c 'java -Dwebdriver.chrome.whitelistedIps=127.0.0.1 -jar /opt/selenium-server-standalone.jar -role standalone -host 127.0.0.1 -port 4444 -sessionTimeout 15 -browserTimeout 12 > /dev/null 2>&1 &' + ci_wait_until 'nc -w 1 127.0.0.1 4444' if [ "${{ matrix.type }}" == "Firefox" ]; then sed -i "s~chrome~firefox~" behat.yml.dist; fi if [ "${{ matrix.type }}" == "Chrome Slow" ]; then echo 'sleep(1);' >> demos/init-app.php; fi - # remove once https://github.com/minkphp/Mink/pull/801 - # and https://github.com/minkphp/MinkSelenium2Driver/pull/322 are released - sed -i 's/usleep(100000)/usleep(5000)/' vendor/behat/mink/src/Element/Element.php - sed -i 's/usleep(100000)/usleep(5000)/' vendor/behat/mink-selenium2-driver/src/Selenium2Driver.php - - name: "Run tests: SQLite" run: | php demos/_demo-data/create-db.php diff --git a/README.md b/README.md index 05a00624..9dd85c3d 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ $app->auth = new \Atk4\Login\Auth(); $app->auth->setModel(new \Atk4\Login\Model\User($app->db)); // The rest of YOUR UI code will now be protected -$app->add([\Atk4\Ui\Crud:class])->setModel(new Client($app->db)); +\Atk4\Ui\Crud::addTo($app)->setModel(new Client($app->db)); ``` (If you do not have User model yet, you can extend or use \Atk4\Login\Model\User). @@ -81,14 +81,14 @@ $app->auth->setModel(new User($app->db)); // Now manually use login logic if (!$app->auth->user->loaded()) { - $app->add([new \Atk4\Login\LoginForm(), 'auth'=>$app->auth]); + \Atk4\Login\LoginForm::addTo($app, ['auth' => $app->auth]); } ``` #### Adding sign-up form ``` php -$app->add([\Atk4\Login\RegisterForm::class]) +\Atk4\Login\RegisterForm::addTo($app) ->setModel(new \Atk4\Login\Model\User($app->db)); ``` @@ -104,8 +104,7 @@ Displays email and 2 password fields (for confirmation). If filled successfully ![Login](./docs/login-form.png) ``` php -$app->add([ - \Atk4\Login\LoginForm::class, +\Atk4\Login\LoginForm::addTo($app, [ 'auth'=>$app->auth, //'successLink'=>['dashboard'], //'forgotLink'=>['forgot'], @@ -143,14 +142,14 @@ You may also access user data like this: `$app->auth->model['name']`; Things to This form would allow user to change user data (including password) but only if user is authenticated. To implement profile form use: ``` php -$app->add([Form::class])->setModel($app->auth->user); +Form::addTo($app)->setModel($app->auth->user); ``` Demos open profile form in a pop-up window, if you wish to do it, you can use this code: ``` php -$app->add([Button::class, 'Profile', 'primary'])->on('click', $app->add([Modal::class])->set(function($p) { - $p->add([Form::class])->setModel($p->app->auth->user); +Button::addTo($app, ['Profile', 'primary'])->on('click', Modal::addTo($app)->set(function($p) { + Form::addTo($p)->setModel($p->app->auth->user); })->show()); ``` @@ -185,7 +184,7 @@ Although a basic User model is supplied, you can either extend it or use your ow We include a slightly extended "Admin" interface which includes page to see user details and change their password. To create admin page use: ``` php -$app->add(new \Atk4\Login\UserAdmin()) +\Atk4\Login\UserAdmin::addTo($app) ->setModel(new \Atk4\Login\Model\User($app->db)); ``` diff --git a/behat.yml.dist b/behat.yml.dist index 2b4c2157..10c40d4d 100644 --- a/behat.yml.dist +++ b/behat.yml.dist @@ -1,6 +1,6 @@ default: suites: - atk4_login: + main: paths: features: '%paths.base%/tests-behat' contexts: @@ -8,15 +8,18 @@ default: - Atk4\Ui\Behat\Context extensions: Behat\MinkExtension: - base_url: 'http://172.18.0.2:8888/demos' + base_url: 'http://127.0.0.1:8888/demos' sessions: default: selenium2: browser: chrome - wd_host: 'http://selenium-chrome:4444/wd/hub' + wd_host: 'http://127.0.0.1:4444/wd/hub' capabilities: extra_capabilities: chrome: args: + - '--no-sandbox' - '--headless' + - '--disable-dev-shm-usage' + - '--disable-gpu' - '--window-size=1920,1200' diff --git a/composer.json b/composer.json index bd1c5f2e..25a00eb4 100644 --- a/composer.json +++ b/composer.json @@ -31,11 +31,10 @@ "atk4/ui": "~3.0.0" }, "require-dev": { - "behat/behat": "^3.8.2 || dev-master#6f38d11", - "behat/gherkin": "^4.8.1 || dev-master#5fbf806", - "behat/mink": "^1.8.2 || dev-master#1ab79d6", + "behat/behat": "^3.9", + "behat/mink": "^1.9", "behat/mink-extension": "^2.3.1", - "behat/mink-selenium2-driver": "^1.4", + "behat/mink-selenium2-driver": "^1.5", "ergebnis/composer-normalize": "^2.13", "friendsofphp/php-cs-fixer": "^3.0", "instaclick/php-webdriver": "^1.4.7", @@ -43,7 +42,8 @@ "phpstan/phpstan": "^0.12.82", "phpunit/phpcov": "*", "phpunit/phpunit": "^9.5.5", - "symfony/contracts": ">=1.1" + "symfony/console": "^4.4.30 || ^5.3.7", + "symfony/css-selector": "^4.4.24 || ^5.2.9" }, "config": { "sort-packages": true diff --git a/demos/_demo-data/create-db.php b/demos/_demo-data/create-db.php index 72fd2745..b912fef3 100644 --- a/demos/_demo-data/create-db.php +++ b/demos/_demo-data/create-db.php @@ -18,6 +18,14 @@ /** @var \Atk4\Data\Persistence\Sql $db */ require_once __DIR__ . '/../init-db.php'; +$model = new Model($db, ['table' => 'login_role']); +$model->addField('name', ['type' => 'string']); +(new Migration($model))->create(); +$model->import([ + 1 => ['id' => 1, 'name' => 'User Role'], + 2 => ['id' => 2, 'name' => 'Admin Role'], +]); + $model = new Model($db, ['table' => 'login_user']); $model->addField('name', ['type' => 'string']); $model->addField('email', ['type' => 'string']); @@ -29,15 +37,7 @@ 2 => ['id' => 2, 'name' => 'Administrator', 'email' => 'admin', 'password' => '$2y$10$p34ciRcg9GZyxukkLIaEnenGBao79fTFa4tFSrl7FvqrxnmEGlD4O', 'role_id' => 2], // admin/admin ]); -$model = new Model($db, ['table' => 'login_role']); -$model->addField('name', ['type' => 'string']); -(new Migration($model))->create(); -$model->import([ - 1 => ['id' => 1, 'name' => 'User Role'], - 2 => ['id' => 2, 'name' => 'Admin Role'], -]); - -$model = new Model($db, ['table' => 'login_access_role']); +$model = new Model($db, ['table' => 'login_access_rule']); $model->addField('role_id', ['type' => 'integer']); $model->addField('model', ['type' => 'string']); $model->addField('all_visible', ['type' => 'boolean']); @@ -50,9 +50,17 @@ (new Migration($model))->create(); $model->import([ - 1 => ['id' => 1, 'role_id' => 1, 'model' => '\\Atk4\Login\\Model\\User', 'all_visible' => 1, 'visible_fields' => null, 'all_editable' => 0, 'editable_fields' => null, 'all_actions' => 1, 'actions' => null, 'conditions' => null], - 2 => ['id' => 2, 'role_id' => 2, 'model' => '\\Atk4\Login\\Model\\User', 'all_visible' => 1, 'visible_fields' => null, 'all_editable' => 1, 'editable_fields' => null, 'all_actions' => 1, 'actions' => null, 'conditions' => null], - 3 => ['id' => 3, 'role_id' => 2, 'model' => '\\Atk4\Login\\Model\\Role', 'all_visible' => 1, 'visible_fields' => null, 'all_editable' => 1, 'editable_fields' => null, 'all_actions' => 1, 'actions' => null, 'conditions' => null], + 1 => ['id' => 1, 'role_id' => 1, 'model' => \Atk4\Login\Model\User::class, 'all_visible' => 1, 'visible_fields' => null, 'all_editable' => 0, 'editable_fields' => null, 'all_actions' => 1, 'actions' => null, 'conditions' => null], + 2 => ['id' => 2, 'role_id' => 2, 'model' => \Atk4\Login\Model\User::class, 'all_visible' => 1, 'visible_fields' => null, 'all_editable' => 1, 'editable_fields' => null, 'all_actions' => 1, 'actions' => null, 'conditions' => null], + 3 => ['id' => 3, 'role_id' => 2, 'model' => \Atk4\Login\Model\Role::class, 'all_visible' => 1, 'visible_fields' => null, 'all_editable' => 1, 'editable_fields' => null, 'all_actions' => 1, 'actions' => null, 'conditions' => null], ]); +$model = new Model($db, ['table' => 'demo_client']); +$model->addField('name', ['required' => true]); +$model->addField('vat_number'); +$model->addField('balance', ['type' => 'money']); +$model->addField('active', ['type' => 'boolean', 'default' => true]); + +(new Migration($model))->create(); + echo 'import complete!' . "\n\n"; diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 0b3c26ff..7ed11493 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -29,12 +29,6 @@ parameters: - '~^Unsafe usage of new static\(\)\.$~' # TODO these rules are generated, this ignores should be fixed in the code - # level 0 - - - message: "#^Instantiated class Atk4\\\\Data\\\\Model\\\\AccessRule not found\\.$#" - count: 1 - path: src/RoleAdmin.php - # level 1 - message: "#^Call to an undefined method Atk4\\\\Login\\\\Model\\\\AccessRule\\:\\:setUnique\\(\\)\\.$#" @@ -83,11 +77,11 @@ parameters: count: 1 path: src/RoleAdmin.php - - message: "#^Call to method addCondition\\(\\) on an unknown class Atk4\\\\Data\\\\Model\\\\AccessRule\\.$#" + message: "#^Call to an undefined method Atk4\\\\Ui\\\\Table\\\\Column\\:\\:addModal\\(\\)\\.$#" count: 1 - path: src/RoleAdmin.php + path: src/UserAdmin.php - - message: "#^Call to an undefined method Atk4\\\\Ui\\\\Table\\\\Column\\:\\:addModal\\(\\)\\.$#" + message: "#^Call to an undefined method Atk4\\\\Ui\\\\Form\\\\Control\\:\\:addAction\\(\\)\\.$#" count: 1 path: src/UserAdmin.php - diff --git a/src/Form/Control/Generic.php b/src/Form/Control/Generic.php index 4f846145..ac4fb85f 100644 --- a/src/Form/Control/Generic.php +++ b/src/Form/Control/Generic.php @@ -36,7 +36,7 @@ public function getModel() $model = new $class($this->form->model->persistence); if (!$model instanceof Model) { - throw new Exception('Class should be instance of Atk4\\Data\\Model'); + throw new Exception('Class should be instance of ' . Model::class); } return $model; diff --git a/src/RoleAdmin.php b/src/RoleAdmin.php index 52960b63..800031e9 100644 --- a/src/RoleAdmin.php +++ b/src/RoleAdmin.php @@ -6,6 +6,7 @@ use Atk4\Core\DebugTrait; use Atk4\Data\Model; +use Atk4\Login\Model\AccessRule; use Atk4\Ui\Crud; use Atk4\Ui\Header; use Atk4\Ui\Table\Column\ActionButtons; @@ -30,12 +31,12 @@ public function setModel(Model $role, $fields = null): Model $column = $this->table->addColumn(null, [ActionButtons::class, 'caption' => '']); $column->addModal(['icon' => 'cogs'], 'Role Permissions', function (View $v, $id) use ($role) { - $role->load($id); - $v->add([Header::class, $role->getTitle() . ' Permissions']); + $role = $role->load($id); + Header::addTo($v, [$role->getTitle() . ' Permissions']); $crud = Crud::addTo($v); //$crud->setModel($role->ref('AccessRules')); // this way it adds wrong table alias in field condition - ATK bug (withTitle + table_alias) - $crud->setModel((new Model\AccessRule($role->persistence))->addCondition('role_id', $id)); + $crud->setModel((new AccessRule($role->persistence))->addCondition('role_id', $id)); $crud->onFormAddEdit(function ($f) { // @todo - these lines below don't work. One reason is that there is no rule isNotChecked :) but still not sure it works diff --git a/src/UserAdmin.php b/src/UserAdmin.php index 0c3bdac4..5e2b6be6 100644 --- a/src/UserAdmin.php +++ b/src/UserAdmin.php @@ -51,51 +51,49 @@ public function setModel(Model $user) // Pop-up for resetting password. Will display button for generating random password $column->addModal(['icon' => 'key'], 'Change Password', function ($v, $id) { - $this->model->load($id); + $userEntity = $this->model->load($id); - $form = $v->add([Form::class]); + $form = Form::addTo($v); $f = $form->addControl('visible_password', null, ['required' => true]); //$form->addControl('email_user', null, ['type'=>'boolean', 'caption'=>'Email user their new password']); - $f->addAction(['icon' => 'random'])->on('click', function () use ($f) { - return $f->jsInput()->val($this->model->getField('password')->suggestPassword()); + $f->addAction(['icon' => 'random'])->on('click', function () use ($f, $userEntity) { + return $f->jsInput()->val($userEntity->getField('password')->suggestPassword()); }); - $form->onSubmit(function ($form) use ($v) { - $this->model->set('password', $form->model->get('visible_password')); - $this->model->save(); + $form->onSubmit(function ($form) use ($v, $userEntity) { + $userEntity->set('password', $form->model->get('visible_password')); + $userEntity->save(); return [ $v->getOwner()->hide(), new JsToast([ - 'message' => 'Password for ' . $this->model->get($this->model->title_field) . ' is changed!', + 'message' => 'Password for ' . $userEntity->get($userEntity->title_field) . ' is changed!', 'class' => 'success', ]), ]; - - //return 'Setting '.$form->model['visible_password'].' for '.$this->model['name']; }); }); /* - $column->addModal(['icon'=>'eye'], 'Details', function($v, $id) { - $this->model->load($id); + $column->addModal(['icon' => 'eye'], 'Details', function($v, $id, $userEntity) { + $userEntity = $this->model->load($id); - $c = $v->add(Columns::class); + $c = Columns::addTo($v); $left = $c->addColumn(); $right = $c->addColumn(); - $left->add([Header::class, 'Role "'.$this->model['role'].'" Access']); - $crud = $left->add([CRUD::class]); - $crud->setModel($this->model->ref('AccessRules')); + Header::addTo($left, ['Role "' . $userEntity['role'] . '" Access']); + $crud = Crud::addTo($left); + $crud->setModel($userEntity->ref('AccessRules')); $crud->table->onRowClick($right->jsReload(['rule'=>$crud->table->jsRow()->data('id')])); - $right->add([Header::class, 'Role Details']); + Header::addTo($right, ['Role Details']); $rule = $right->stickyGet('rule'); if (!$rule) { - $right->add([Message::class, 'Select role on the left', 'yellow']); + Message::addTo($right, ['Select role on the left', 'yellow']); } else { - $right->add([CRUD::class])->setModel($this->model->ref('AccessRules')->load($rule)); + Crud::addTo($right)->setModel($userEntity->ref('AccessRules')->load($rule)); } })->setAttr('title', 'User Details'); */ diff --git a/tests/Generic.php b/tests/Generic.php index 3b7133e8..56421f65 100644 --- a/tests/Generic.php +++ b/tests/Generic.php @@ -22,9 +22,9 @@ protected function setupDefaultDb() 2 => ['id' => 2, 'name' => 'Admin Role'], ], 'login_access_rule' => [ - 1 => ['id' => 1, 'role_id' => 1, 'model' => '\\Atk4\Login\\Model\\User', 'all_visible' => 1, 'visible_fields' => null, 'all_editable' => 0, 'editable_fields' => null, 'all_actions' => 1, 'actions' => null, 'conditions' => null], - 2 => ['id' => 2, 'role_id' => 2, 'model' => '\\Atk4\Login\\Model\\User', 'all_visible' => 1, 'visible_fields' => null, 'all_editable' => 1, 'editable_fields' => null, 'all_actions' => 1, 'actions' => null, 'conditions' => null], - 3 => ['id' => 3, 'role_id' => 2, 'model' => '\\Atk4\Login\\Model\\Role', 'all_visible' => 1, 'visible_fields' => null, 'all_editable' => 1, 'editable_fields' => null, 'all_actions' => 1, 'actions' => null, 'conditions' => null], + 1 => ['id' => 1, 'role_id' => 1, 'model' => \Atk4\Login\Model\User::class, 'all_visible' => 1, 'visible_fields' => null, 'all_editable' => 0, 'editable_fields' => null, 'all_actions' => 1, 'actions' => null, 'conditions' => null], + 2 => ['id' => 2, 'role_id' => 2, 'model' => \Atk4\Login\Model\User::class, 'all_visible' => 1, 'visible_fields' => null, 'all_editable' => 1, 'editable_fields' => null, 'all_actions' => 1, 'actions' => null, 'conditions' => null], + 3 => ['id' => 3, 'role_id' => 2, 'model' => \Atk4\Login\Model\Role::class, 'all_visible' => 1, 'visible_fields' => null, 'all_editable' => 1, 'editable_fields' => null, 'all_actions' => 1, 'actions' => null, 'conditions' => null], ], ]); }