log database Insert update Delete pouze v administraci

dehtak
Člen | 113
+
0
-

Zdravim,
chtel bych v administraci udelat takovej log database pouze u prikazu INSERT UPDATE a DELETE.
Pred smazanim exportovat prvne data a nekam je ulozit a pak provest smazani.
Jde mi o to abych mel pak prehled o tom kdo co smazal kdo co vytvoril.
Popripade smazane aby bylo mozne obnovit.
Mate napad jak na to ? Ale nechci u kazdy db tabulky mit sloupce update_date, update_from, insert_date, insert_from, trash

Skousel jsem vytvorit Tridu extends Nette\database\connections
a v metode query to tam zachytavat.
jenze me to furt rve protoze to posila data pak do jinch trid v nette\database a ty chtej jenom nette database connection a nic jinyho.
Napadlo me udelat novou tridu posilat to do ni ta si to tam prebere a pak by to posilala do connections.

Jen me napadlo jestli tohle uz nekdo neresil a nema naky elegantnejsi reseni.

Jeste jedna otazka asi pitoma ale nikde sem nevycet co je to to orm. Porad pisou ze
naky entity a doctriny tyhle slova me to vubec nevysvetli.
To je tezky kdyz nevim co slovo entita znamena. A doctrina.
Neni nekde vysvetleno normalne normalni reci co to je jak to funguje k cemu to slouzi.

Editoval dehtak (10. 2. 2021 9:31)

Kamil Valenta
Člen | 752
+
0
-

Podědit

class Context extends \Nette\Database\Context

normálně jde.

Druhá možnost je řešit to pomocí triggerů, čímž se budou uchovávat změny vykonané i mimo Nette app. Např. v nějakém SQL toolu.

Ozzrel
Generous Backer | 51
+
0
-

Ahoj, pro logování používám dvě metody.
Jedna je že si to prostě na hulváta zaloguju pomocí Nette kam si pošlu IP,uživatele a pole hodnot co provedl. (Ano, je to moc krásný CEF log pro odborníky) Pak dohledám kdo co proved.
Druhá je když potřebuju s daty dál pracovat. To použiju stejnou tabulku jen jí dám prefix např. archiv a do té pak přesouvám/mažu položky. To má za následek zrychlení dotazů u živých položek a následně mám i pomalejší roky stará data když je třeba.

ORM je zjednodušeně řečeno framework pro SQL.
Protože jednu chytrou paní napadlo proč si s počítačem nepovídat jako s dítětem. No a výsledkem je SQL s kterým se plácáme do dnes. Proto chytří muži vymýšlejí nástavby aby se s počítačem mluvilo tak jak se má. Tj. krátce a v povelech. A jako vedlejší efekt toho je že nemusíš řešit drobné rozdíly mezi DB MSSQL/MySQL/atd. Příkaz v ORM/Nette Explorer/Doctrine většinou zůstane stejný. Nevýhodou je drobné snížení rychlosti a tvorba velesložitých dotazů.

dehtak
Člen | 113
+
0
-

Ozzrel: Aha dik za vysvetleni orm , ja tak na to koukal na webu a rikal si proc to psat tak slozite kdyz staci napsat normalni SQL dotaz. Proc to delat jednoduse kdyz to jde slozite.

Kamil Valenta: v Context nic nezachytim jelikoz je to jen smeska trid Connection, selection, conventions

Kamil Valenta
Člen | 752
+
0
-

To není pravda, v Context si můžeš přepsat table(), query(), queryArgs()…

dehtak
Člen | 113
+
0
-

aha tak to ale nevim jak

dehtak
Člen | 113
+
0
-

no skousel jsem to podle tohodle "":https://github.com/…ionPanel.php#L55
,jenze pak nemuzu pouzit db prikaz pro uulozeni logu do db to bych se dostal do nekonecne smycky. A taky tim nezachytim delete jeste predtim nez se prikaz provede

Editoval dehtak (10. 2. 2021 18:31)

Kamil Valenta
Člen | 752
+
+1
-

dehtak napsal(a):

aha tak to ale nevim jak

Žádnou magii v tom nehledej…

class Context extends \Nette\Database\Context
{
    public function query($sql, ...$params)
    {
		// napred provedu vlastni kod s dotazem
		// pak ho necham vykonat
        return parent::query($sql, ...$params);
    }
}

Editoval Kamil Valenta (10. 2. 2021 19:10)

dehtak
Člen | 113
+
0
-

vyzkousim ale myslim ze to bude fungovat jen v pripade ze se napise

$db->query('....')

nikoliv v pripade

$db->table('table')->update(), $db->table('table')->insert()

Vyzkouseno, mel jsem pravdu.

Editoval dehtak (10. 2. 2021 19:33)

dehtak
Člen | 113
+
0
-

ja sem to vyresil tak ze sem napsal tridu prez kterou to jde pak do Connections, Takze to zachytavam v ni.
Budu muset ale pouzivat jen connection
Myslel jsem ze to jde nejak vyresit at se pouzije context nebo connection.

Kamil Valenta
Člen | 752
+
0
-

dehtak napsal(a):

$db->table('table')->update(), $db->table('table')->insert()

Vyzkouseno, mel jsem pravdu.

Blahopřeji. Samozřejmě, pokud si přepíšeš jen query(), bude table() nezměněna. Jak jsem psal, můžeš si přepsat i table() a queryArgs(). Nebo to celé řešit triggerem, což je nejjistější.

dehtak
Člen | 113
+
0
-

Nemate pravdu zavolanim table metoda table vrati tridu selections nic vic takze prikazy jako insert() delete update uz zachytavat nebude.

Kamil Valenta
Člen | 752
+
0
-

Ne, přepsaná metoda table() bude vracet co budeš potřebovat. Třeba nějakou App\Selection, ve které si zase přepíšeš co bude potřeba tam. Doporučuji nějaký článek o OOP…

Marek Bartoš
Nette Blogger | 1146
+
0
-

Nebo se můžeš navěsit na onQuery jak jsi měl v plánu, protože při přetěžování bys měl úplně stejný problém s rekurzí při logování do databáze a hlavně je to úplně zbytečné.
Problém s rekurzí můžeš vyřešit tak, že v onQuery z Nette\Database\ResultSet získáš původní sql dotaz a jestliže to bude dotaz ukládající log, tak ho nezaloguješ.

Kompletní implementace skrze extension by mohla vypadat nějak takto:

namespace Example;

use Nette\Database\Connection;
use Nette\Database\DriverException;
use Nette\Database\ResultSet;
use Nette\DI\CompilerExtension;
use Nette\DI\Definitions\ServiceDefinition;
use Nette\Utils\Strings;
use Psr\Log\LoggerInterface;

final class ExampleExtension extends CompilerExtension
{

	public function beforeCompile(): void
	{
		parent::beforeCompile();

		$builder = $this->getContainerBuilder();

		$connectionDefinition = $builder->getDefinitionByType(Connection::class);
		assert($connectionDefinition instanceof ServiceDefinition);
		$connectionDefinition->addSetup(
			[self::class, 'attachLogger'],
			[$connectionDefinition, $builder->getDefinitionByType(LoggerInterface::class)]
		);
	}

	public static function attachLogger(Connection $connection, LoggerInterface $logger): void
	{
		/**
		 * @param ResultSet|DriverException $result
		 */
		$connection->onQuery[] = static function (Connection $connection, $result) use ($logger): void {
			if (!$result instanceof ResultSet) {
				return;
			}

			$queryString = $result->getQueryString();
			if (Strings::startsWith(strtolower($queryString), 'insert into logs')) {
				return;
			}

			$logger->info("Query: {$queryString}");
		};
	}

}

Editoval Mabar (10. 2. 2021 23:00)

Kamil Valenta
Člen | 752
+
0
-

Jaký problém s rekurzí by měl mít?
Přetěžovat IMHO nic nechce.
Za „úplně zbytečné“ bych to neoznačoval – např. ve chvíli, kdy bude chtít ke každé větě evidovat i id v logu. Ano, v onQuery může následně dělat updaty, ale zvýší se mu tím počet dotazů na dvojnásobek.

Já například, pokud se věta nezměnila, ji vůbec neukládám. Tohle v onQuery neuděláš vůbec.

Každopádně toto si má db řešit sama, vyčleňovat to do PHP povede jen k tomu, že dřív nebo později log nebude kompletní…

Editoval Kamil Valenta (11. 2. 2021 9:15)

dehtak
Člen | 113
+
0
-

Skusim oboji jak ty triggery tak ty onQuery a dam pak vedet.
---------
U tech trigeru ale nezadam id prihlaseneho uzivatele provadejici ukon.
Nejlepsi bude asi pouzit to onQuery,
To ze bude mit vic sql dotazu nevadi, jelikoz to bude pouze v administraci a tam je prihlasenej max 1–3 uzivatele.
Je to jen proto abych s nekym pak nedohadoval kdo co smazal nebo upravil.

Editoval dehtak (11. 2. 2021 10:11)

Kamil Valenta
Člen | 752
+
0
-

dehtak napsal(a):

U tech trigeru ale nezadam id prihlaseneho uzivatele provadejici ukon.

Já si id uživatele tahám v proměnné. Sice to do některých dotazů může vnášet side effect (typicky do pohledů), ale přínos je IMHO větší.

dehtak
Člen | 113
+
0
-

To teda vubec netusim jak dostat promenou do triggeru Mohl bys uvest maly priklad ?

Kamil Valenta
Člen | 752
+
0
-

Např. u before insertu:

BEGIN
	NEW.user_id = @user_id
END

Na začátku aplikační vrstvy pak mám:

$this->database->queryArgs('SET @user_id=?', [$this->user->isLoggedIn() ? $this->user->id : 0]);

Když je id = 0, je uživatel nepřihlášen.
Když je id = null, jde o přístup mimo app přímo v nějakém sql toolu.

dehtak
Člen | 113
+
0
-

ja nechapu jak udelat ten sql
takhle vypada ten trigger

DELIMITER $$

	 CREATE TRIGGER `Insert` AFTER INSERT ON `seiten` FOR EACH ROW INSERT INTO _insert VALUES ('seiten', NEW.id,'promennaUserID',NOW())
$$
DELIMITER ;

skousel jsem misto promennaUserID dat @user_id
ale to hlasi chybu ze je neznama systemova promenna
Ale kam narvu ?

BEGIN
	NEW.user_id = @user_id
END

Sorry s timhle nemam moc zkusenosti jeste sem to nikdy nepouzival

Editoval dehtak (13. 2. 2021 13:11)

dehtak
Člen | 113
+
0
-

aha uz sem na to prisel
sql by melo vypadat takhle

DELIMITER $$

	 CREATE TRIGGER `Insert` AFTER INSERT ON `seiten` FOR EACH ROW INSERT INTO _insert VALUES ('seiten', NEW.id,@user_id,NOW())
$$
DELIMITER ;

a pak dat jen

$this->database->queryArgs('SET @user_id=?', [$this->user->isLoggedIn() ? $this->user->id : 0]);

Editoval dehtak (13. 2. 2021 13:32)