log database Insert update Delete pouze v administraci
- dehtak
- Člen | 113
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 | 815
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 | 54
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
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
- dehtak
- Člen | 113
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 | 815
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)
- Kamil Valenta
- Člen | 815
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ší.
- Kamil Valenta
- Člen | 815
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 | 1264
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 | 815
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
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 | 815
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ší.
- Kamil Valenta
- Člen | 815
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
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
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)