PHP todo app database setup question
I have a some far a table in mind for username and passwords but not sure how to link their "tasks" to my database in order to save them for when they logout and later re login. Any tips?
316 Replies
by id
create a table with the user_id
you can set it up to automatically update and/or delete all tasks when the user is deleted
I'd do something like this
users
tasks
by using a foreign relationship
good point
man im rusty
tasks should be a separate table
hi rusty, i though you were md
yes, because it is an n:n relationship
how you link two different sqlite files?
you don't
it's just two tables in a single sqlite file
an sqlite file isn't a single table
it's an entire database
wild
that's why we told you to use sqlite
do you have the php create the tables separately or write the code in the file?
you can do both
you can probably use phynx to do the migrations for you
I'd personally use an external SQLITE tool to set up the database
i think it supports sqlite
much easier than doing it with code
then you just open the file and the tables already exist
migrations are a thing to worry about later
there's another easy way: when the file doesn't exist, create it from an sql file
ive done with it code before on mysql but that was like near 18 years ago
so, to upgrade the database, just delete the sqlite3 file
what tools do you suggest?
vscode
there are extensions for it, to read sqlite files
you have vscode
I usually use the cli to create the file I think
i make it in php
i use the method i said
i added the cli tool for sqllite but gotta wait for the system to do a rebuild to add it
i did make a database folder in my project structure with a tododb.db file but I guess I should delete it and start over
don't call it .db
use .sqlite3
it's easier for you to know what it is
should i have the cli create the whole file?
or create it before hand then connect to it in the cli and add the tables?
i think that that's too much work
i just use the method i said
don't create it before
make the tool create it
this is what I do. Just create the file, open it with the cli, run the CREATE TABLE commands, and close it again
for me, migrations are for databases it would take me more than 30 seconds to recreate from scratch
this should work because migrations by hand can be tedious
yes, but for this, since he's going to do lots of changes, just having an sql file with all the create statements, then delete the file and make php create it automatically is easier
you want to add a column? edit the sql file, delete the database ... profit!
i think with PDO it's just
new PDO ("sqlite://database/tododb.sqlite3");
right?yes
what you think jochem about epic input ?
i used that for a project, for very quick prototyping
which is what you're doing now
you can add data to the sql file as well, by the way
explain how you do your method epic with detail if you can
since php can handle it
It's what I'd do for something bigger than a todo app.
true im still gonna learn that way for other projects but this seems a bit much
like, you can just use ALTER statements to make one or two tweaks to the database file. There's nothing wrong with the concept though, and it can be handy to keep the sql file anyway just for reference if you ever do delete the database file
dude, it's literally this:
this
also remember db files don't go in git in general, so it can be valuable to have a .sql version of the structure so you don't have to recreate the structure from your code later
exactly!
and, you can take the sql file, run the code anywhere and the database is created automatically
this is awful pseudocode, by the way
change my sqlite3 into a sql file for github?
nonono
sqlite3 = the database file
sql = the file with the sql code to re-create the database file
oh!
if you need to change anything, you change in the sql file
then just delete the database file
you would, at least definitely in a larger project, keep an SQL file with SQL statements that recreates your database from scratch. Basically a structure backup, plus inserts for any rows that are required for your app to function, so that if you rebuild it from scratch six months later, you can just run the file and not have to bother to recreate from scratch. It's not a huge deal for a one-off project with two tables and less than ten columns, but it's useful to have
sweet the update finished
i got the CLI tool
this what I got so far
also, if he ruins the database, he can re-create it
true
we're in total agreement about the usefulness, I'm just arguing that for two tables with 3 columns each it's potentially a little bit overkill to write migrations
oh, im not talking about migrations, but quick destroy and recreate scenarios
ok so userid is int how big? username and password text?
it's sqlite
you have int or int
big int, small int ... it's just int
but i would use bigint unsigned
im thinking user_id BIG INT UNSIGNED, username VARCHAR(255), password TEXT, task_id BIG INT UNSIGNED, description TEXT, done BOOLEAN?
do I need to do anything with foreign key for user_id?
primary key index
wait, no
I don't think bcrypt produces more than 255 characters, does it? So your password can just be a varchar.
actually, you are using sqlite
it has a rowid field for you
just use that
and yes, varchar(255) is enough for a bcrypt password
and it is too for an argon2 password
ah hey, actually, I think sqlite doesn't have a varchar type internally. It'll accept it, but it just translates it to TEXT
sqlite accepts the varchar declaration
also, you can drop the user_id
this is enough
sqlite automatically creates a rowid field
that I did not know
You could also primary key on the username, technically. It's unique anyway
but I personally prefer to be explicit and have it named the way I like
yes, this would be a good idea for mysql
a unique index for it
Row id
rowid
https://www.sqlite.org/rowidtable.html
using the rowid is way better than using your own primary key
you can still set an index on the username, but i doubt you will have more than 6 rows
Would email be a better login?
Or username
I usually prefer email logins personally
one less thing to remember
same
convert username to email?
nah
this what I got so far
no no no no no
don't create the rowid column
but wont tasks and user not match on row ids?
this is why I prefer to be explicit with the ID columns
NEVER create a rowid column
just create the user_id column INTEGER PRIMARY KEY AUTOINCREMENT on the users table, then reference it with a user_id column on tasks
unless you know what you're doing
ill need to start over
i dont like jochem's suggestion, but it's the best option if the rowid confuses you
so, just do what he said
what Epic is suggesting is having a user_id column on the tasks table which then references the rowid on users
^
you'd join with
SELECT * FROM users u LEFT JOIN tasks t ON t.user_id = u.rowid
I prefer being explicit, so you can do SELECT * FROM users u LEFT JOIN tasks t USING(user_id)
(does that even work in sqlite?)both tables are created 🙂
how do we save the file from CLI?
it's close enough
don't think you have to, it just writes as you go
just
.exit
weird
file just shows up as "todoDB" no extension
that's because you didn't add an extension?
would userid in tasks table need an AUTOINCREMENT?
no
you can't autoincrement 2 columns
make it NOT NULL?
yes
ok done!
what's the create sql for the tables?
you forgot the unsigned
also, you didn't set up the constraint
:/
what am i missing?
all integers must be insigned
and you should add a constraint to the tasks table
dont know how to do that on sqlite
everything gets casted to INTEGER according to SQLite
yes, but not unsigned
just add
UNSIGNED
INTEGER UNSIGNED
before I press enterno, the other way around
also, use bigint
yup, sounds good
Parse error: AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY
looks like you cantinteger then
they changed things
🤔
i mean, when you don't use the rowid column, and try to create your own, weird stuff gets weird
but, you're right, it's just integer
yup
how we add the constraint?
by the way, don't use a "done"
use an enum for the status
because the task can be in many states
so what data type fits then?
it can be paused, done, pending, queued, running, cancelled, error ...
this project is going to get more complicated
status TEXT CHECK( status IN ( ... ) )
idk how to do that
I just went by boolean because Jochem used it
why not just... Done or Not Done?
you do exactly as i wrote it
this isnt meant to be a big project
just a simple todo app so i can move on to a bigger project
i know
oh, then just "done" is enough
im debating on a blog next project
one that I actively update
with news like a personal
ok how we add the constraint?
ive never done it in sqlite 🤣
https://www.sqlite.org/foreignkeys.html
yup
there should be something that lets you delete everything from the tasks when you delete an user
and before you think it is overkill: data consistency is never overkill
got it
now im going to eat dinner
good luck
back
👋
so for my models i got this
why public?
I think these will talk to those fields?
username should be public
then add a readonly
also, you can shortcircuit those assignments in the constructor
yup
I forget how, but you don't need to do that anymore
make password
protected
or private
none: get it from the database and throw it away after use
oh ok so just take password completely away from the user model?
yeah, i wouldn't save any passwords
keep it in the database only
yeah, you can just fetch the pw when you need to check it
^
i also added this request for trimming
what?
trimming paths down for the browser url
why don't you use
$_SERVER['PATH_INFO']
?this lets you do
/index.php/same/path/here
nice
that's what i told you
make it readonly
achieves same thing?
no, i just explained the difference
here
i changed
REQUEST_URI
to PATH_INFO
you can use both
$_SERVER['PATH_INFO'] ?? $_SERVER['REQUEST_URI'] ?? '/'
that's not what i wrote
https://www.php.net/manual/en/function.parse-url.php
that will also throw an exception
close, yes
updated
thats better
very ternary
are the models ok for the database?
null coalescense
i think so
great!
hey trying to load this class variable in for my database
it's not liking it
here's Config
just use dependency injection
directory structure
your ./database is refering to the path relative to Config, but the string is used in database/conn.php
Undefined type 'database\Config'
is namespace
operate like Go does with thing where it goes by the folder name?try
\Config
?
(though tbh I'm past the Balmer point and just guessing)you're not following proper psr paths
you need to use
\database\Config
also, the path is wrong
it should be ./...sqlite3
doubt that, config isn't part of the database folder or namespace
since you want it in the same directory
it's either App\Config or \Config
so namespace functions kinda like Golang
everything is in "app" folder
namespaces function like paths
no, you put them wherever you want
but there's standards
so, follow them
you can save so many headaches just by using composer
if not, just have all the classes inside a folder, like "classes"
and register a class autoload that takes the namespace path and includes the file
this is what I used to do, yeah
never bothered with namespaces
namespaces are important
https://www.php.net/manual/en/function.spl-autoload-register.php <-- use that
wait where do I change the path?
in the "Config::" or require
config
ok here's new
yikes! no!
todoDB.sqlite3
that's it
config isn;t in database folder though?
is that ok?
where's the path going to be used?
PDO for connection
and where is that file?
inside database folder
both sqlite3 file and conn.php inside database
if it is inside the same folder, then you can just skip the directory
because it's relative to where you use it
otherwise, it's just a normal string
that's weird
PDO errored without the ""
forward slash
use \PDO;
at the top
i deleted the Config.php
that's a bad idea, but i get why you did it
what do you recommend?
i dont want "bad" code
that means 0 job
that's not bad
just sub-optimal
what's optimal?
composer
oh im not using composer
for this project
just vanilla php
why?
you want to show you can use the tools, right?
others suggested it
composer and a psr-2 would be the optimal for you, in my opinion
for this small scale things like composer and laravel are not needed
psr
whoever said that is bsing you
composer isn't about size
it's about reusable code
how do I use composer for this project?
i dont want to use laravel
dude, nobody is talking about laravel
composer is composer
laravel is laravel
ok what we do with composer here?
install composer
i have composer already on my system
alright
im in the PHPTodoAPP directory
where whole project sits
give me a second to pick a good loader
env/dotenv - Packagist
A simple, tiny and lightweight PHP .env loader
check this one
it just parses the .env file and stores it in a variable
but, you can save it into the environment variables, if you wish
that's it
now, just do
require './vendor/autoload.php';
do it in your index.php filecreated index.php inside app folder and added
require "vendor/autoload.php";
yes, but do
./vendor/autoload.php
you then create a .env file that's parseable by the php_ini_parse
function
https://www.php.net/manual/en/function.parse-ini-file.phpsomething like this?
that's what the file does
but has a little bit of nuance behind
https://github.com/sabroan/php-dotenv/blob/main/src/Dotenv.php
this is the code
i see
so what I need in .env file just the path to sqlite ffile?
besides, showing that you know how to use composer is important too
outside of localhost address
something like this:
you can actually do something cool af
then, you can just use
ENV['PDO_DSN']
do i need to move my
.env
inside app folder?
right now it's outsideno, leave in the same directory as the index.php file
index.php inside app
the index.php where everything is loaded from
technically i made a goof
since index.php is in
app
folder then it should be <?php
require "../vendor/autoload.php";
since ./
puts it inside app
still
the php intellisense isn't very great on vscodemove the vendor folder to the same folder where the index.php file is
actually
no, you can leave it like that
you can also do
../.env
, if you want
but that's very confusingwhat i would do is to have
index.php
, vendor
and .env
in the same folderthis is current structure
your structure is a huge mess
do this
it will save you lots of headaches
yeah everything is in app
never was a directory expert
they're all in
app
nowgood
good, good
now, you can add a loader to your composer,json file
which should be with the rest of the files
same as the composer.lock
yeah im still learning all these little things
im just used to php without anything
web is just way different now
by the way, you can use
require __DIR__ . '/vendor/autoload.php';
to make sure you load it from the right place, without using relative paths
well, it is the same
but you were making a mess, and im helping you to clean it up for youyup
awesome
now, to add a loader
this is php ive done in case you're interested
GitHub
GitHub - MD-2016/Anywhere-Bank-Web-Application: A web based banking...
A web based banking application that allows users to make an account and keep track of their money. - GitHub - MD-2016/Anywhere-Bank-Web-Application: A web based banking application that allows use...
move all your classes into a single folder
that's a monolithic mess
do this, and have that folder be in the same folder as the index.php, .env and so on
that will mess things up
model has two classes in it for those respective models
dude, just do it
every file that has the word
class
in itopen your composer.json file, and add this - replace Acme with a name that you don't mind repeating:
are you sure you want to use the acme namespace?
idk what im even doing
so i put what you did
what that does is "when you see the namespace starting with 'Acme', it loads the file inside the
class/
directory"my folder is called classes
where they all are currently
then you have to change the name of it
i mean, in the composer file
so this means all those files need their namespace changed?
no
yes
if i were you, i would change "Acme" to "MD"
or something like that
renamed
classes
to class
folderthat works
so
database
namespace needs changed to MD/database
or just MD/this
BUT!!!!!!
the folder HAS TO BE CALLED
database
SAME CAPITALIZATION
and the file name MUST BE THE SAME AS THE CLASS NAMEwell everything been moved to one folder and it's name is class
this is gonna be a mess
now do these changes to every single file
trust me, it's not
conn.php
you have to change the name of the file to
SQLConnection.php
ok what about the namespace?
change it as well
this is what happens
to SQLConnection?
no
same as you put in there
yup
namespace MD\class;
this is what happens:
MD\database\SQLConnection
will load the file class\database\SQLConnection.php
you're mapping the namespaces to a folderMD\Class is ok for namespace?
that will load in
class\class\
so, no, it's awfulMD?
MD\<foldername>\ <-- namespace
well all those files got moved out of database, helpers, and other folders
so im lost now
models will be in class\models
the namespace is MD\models
model User.php
that's it
ok
just feels a little unorganized
now that models is empty
and such
why is it empty?
i moved them from models to class
🤦♂️
no!
you keep the same directory structure
so keep same directory structure but they're all inside of class folder?
you're literally saying "hey, php, the namespace MD is inside the folder
class
"class/models/User.php
yes
instead of being 5-6 folders inside
app
those 5-6 are inside class
and class
is inside app
since I put SQLConnection inside database folder with the sqlite3 database previously
move that folder inside class?
everything that has classes goes inside that folder
ill just move the file then
don't do that
keep the same directory structure
or you will be making a huge salad
ill make a different directory for DB
move SQLConnection inside class
as long as it is inside the class directory, do what you want
pretend that the
class
directory is the app
directory
but only classes go theremoved SQLConnection inside a folder called
dbhelper
inside class
folderupdate the namespace
basically we're doing the same thing Go lang does
except changing folder names to match
in go you name the folder based on the namespace
you are doing the same now
then go can find the files easier
that's the idea
the folders are the namespaces
the files are the classes
also, NO MORE INCLUDES AND REQUIRES
if you have any, delete them all
except the one inside the index.php
that one's required to load composer
I think this is a good stopping point
that's how it is done
then, you can do
use MD\dbhelper\SQLConnection;
and the file is included imediatelly for you
and you can just use SQLConnection
right thereNeat
So autoloader
that's what the autoloader does
it automatically loads the classes for you
but uses composer to do it for you
I know ill almost need to make a
routes.php
thats up to how you organize things
you can use klein, for example
it's a very old, but really decent router
Nice
you can handle routes super easily with it
https://packagist.org/ <-- you can search for the packages in this website
Packagist
The PHP Package Repository
https://packagist.org/packages/klein/klein <-- and this is klein
klein/klein - Packagist
A lightning fast router for PHP