gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
Our job: fill in the 5 methods from UserInterface
. But check this out - I'm
going rogue - I'm only going to implement 2 of them: getUsername()
and getRoles()
.
First getUsername()
... is super-unimportant. Just return any unique user string
you want - a username, an email, a uuid, a funny, but unique joke - whatever. This
is only used to show you who is logged in when you're debugging.
In our app, our users won't have a username - but they will have an email. Add a
private $email
property then just use that in getUsername()
: return $this->email
:
... lines 1 - 7 | |
class User implements UserInterface | |
{ | |
private $email; | |
// needed by the security system | |
public function getUsername() | |
{ | |
return $this->email; | |
} | |
... lines 17 - 46 | |
} |
And add a setter for that method:
... lines 1 - 7 | |
class User implements UserInterface | |
{ | |
... lines 10 - 42 | |
public function setEmail($email) | |
{ | |
$this->email = $email; | |
} | |
} |
We'll eventually need that to create users.
Cool: half-way done! Now: getRoles()
. We'll talk roles later with authorization,
but these are basically permissions we want to give the user. For now, give every
user the same, one role: ROLE_USER
:
... lines 1 - 7 | |
class User implements UserInterface | |
{ | |
... lines 10 - 16 | |
public function getRoles() | |
{ | |
return ['ROLE_USER']; | |
} | |
... lines 21 - 40 | |
} |
So what about getPassword()
, getSalt()
and eraseCredentials()
? Keep them blank:
... lines 1 - 7 | |
class User implements UserInterface | |
{ | |
... lines 10 - 21 | |
public function getPassword() | |
{ | |
// leaving blank - I don't need/have a password! | |
} | |
public function getSalt() | |
{ | |
// leaving blank - I don't need/have a password! | |
} | |
public function eraseCredentials() | |
{ | |
// leaving blank - I don't need/have a password! | |
} | |
... lines 36 - 40 | |
} |
Whaat? It turns out, you only need these if your app is personally responsible for storing and checking user passwords. In our app - to start - we're not going to have passwords: we're just going to let anyone login with a single, central, hardcoded password. But there are also real-world situations where your app isn't responsible for managing and checking passwords.
If you have one of these, feel ok leaving these blank.
So in our application, we want to store users in the database. So let's set this
class up with Doctrine. Copy the use
statement from SubFamily
and paste it here:
... lines 1 - 6 | |
use Doctrine\ORM\Mapping as ORM; | |
... lines 8 - 63 |
Next, I'll put my cursor inside the class, press Command
+N
- or use the
"Code"->"Generate" menu - and select "ORM class":
... lines 1 - 8 | |
/** | |
* @ORM\Entity | |
* @ORM\Table(name="user") | |
*/ | |
class User implements UserInterface | |
... lines 14 - 63 |
Next, add a private $id
property and press Command
+N
one more time. This time,
choose "ORM annotation" and highlight both fields:
... lines 1 - 12 | |
class User implements UserInterface | |
{ | |
/** | |
* @ORM\Id | |
* @ORM\GeneratedValue(strategy="AUTO") | |
* @ORM\Column(type="integer") | |
*/ | |
private $id; | |
/** | |
* @ORM\Column(type="string", unique=true) | |
*/ | |
private $email; | |
... lines 26 - 61 | |
} |
Oh, and while we're here, let's add unique=true
for the email field:
... lines 1 - 12 | |
class User implements UserInterface | |
{ | |
... lines 15 - 21 | |
/** | |
* @ORM\Column(type="string", unique=true) | |
*/ | |
private $email; | |
... lines 26 - 61 | |
} |
Perfect: we have a fully functional User
class. Sure, it only has an id
and
email
, but that's enough!
Since we just added a new entity, let's generate a migration:
./bin/console doctrine:migrations:diff
I'll copy the class and open it up quickly, just to make sure it looks right:
... lines 1 - 10 | |
class Version20160524105319 extends AbstractMigration | |
{ | |
... lines 13 - 15 | |
public function up(Schema $schema) | |
{ | |
// this up() migration is auto-generated, please modify it to your needs | |
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.'); | |
$this->addSql('CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_8D93D649E7927C74 (email), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB'); | |
} | |
... lines 23 - 26 | |
public function down(Schema $schema) | |
{ | |
// this down() migration is auto-generated, please modify it to your needs | |
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.'); | |
$this->addSql('DROP TABLE user'); | |
} | |
} |
Looks great!
Finally, let's add some users to the database. Open our trusty fixtures.yml
.
I'll copy the SubFamily
section, change the class to User
and give these keys
user_1..10
:
... lines 1 - 22 | |
AppBundle\Entity\User: | |
user_{1..10}: | |
... lines 25 - 26 |
Then, the only field is email. Set it to weaverryan+<current()>@gmail.com
:
... lines 1 - 22 | |
AppBundle\Entity\User: | |
user_{1..10}: | |
email: weaverryan+<current()>@gmail.com |
The current()
function will return 1 through 10 as Alice loops through our set.
That'll give us weaverryan+1@gmail.com
up to weaverryan+10@gmail.com
. And if you
didn't know, Gmail ignores everything after a +
sign, so these will all be delivered
to weaverryan@gmail.com
. There's your Internet hack for the day.
Ok, let's roll! Don't forget to run the migration first:
./bin/console doctrine:migrations:migrate
And then load the fixtures:
./bin/console doctrine:fixtures:load
Hey hey: you've got users in the database. Let's let 'em login.
Hey Zoé!
Hmm, yea - some of the code blocks on this page were out-of-date! Thanks for leaving this comment! In the video, we *only* create an email field (not a username field) and return the email field in getUsername(). I just updated the incorrect code blocks to reflect this. But of course, you can do whatever you want - and your setup is equally valid :).
Cheers!
Hi! Im trying to redirect to a profile.html.twig file when I login with success, but for some reason, in mi new file a dont have any css style.
I have do ir without login in the Index.html.twig file and it works! Suggests?
( I added {% extends 'base.html.twig' %} in all the files)
Yo Matias Rouaux!
Hmm.... Ok, let's check 2 things:
1) If you view the HTML source, do you see your CSS link
tags? Or are they missing?
2) If the CSS link tags are there, are you able to open that URL in your browser?
It's definitely weird that it works for index.html.twig, but not here! For sure, the first thing I would have thought of is a missing extends tag... but you already thought of that :).
Cheers!
Oh, and if both of those things look good, could you post your templates - `profile.html.twig` and `base.html.twig`?
Last question.
I need to create a file manager to upload an image or many images to a gallery. I have been looking the "How to Upload Files" Documentation, but i dont know if it's the right service to implement. Do you have any to recommend for that task?
Hey Matias,
Hm, it depends on how you see it, but yes, you're looking in the right direction. You can also take a look at popular bundle which helps with files uploading: https://github.com/dustin10... - it will save you some time on initial stage and allow you to start faster. But probably implementing the way from "How to Upload Files" Documentation will give you more flexible solution, however will take more time for its implementation. So if you're enough VichUploaderBundle functionality - just use the bundle.
Cheers!
Oh.... i have inspected the code like you said and the links where there.. but i forgot to use the "asset" sintax.
I had this before: <link href="css/font-awesome.min.css" rel="stylesheet">
And now I change it to: <link href="{{ asset('css/font-awesome.min.css') }}" rel="stylesheet">
href="{{ asset('css/normalize.css') }}"
It worked!
Thanks!!! You are definitely amazing :D
Cheers!!
I'm following along but for Symfony 4. When I run my migration I get an error:
An exception occurred while executing 'ALTER TABLE user CHANGE email email VARCHAR(255) NOT NULL':
SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes
so to fix this I manually added a length like this:
/**
* @ORM\Column(type="string", length=191, unique=true)
*/
private $email;
And it works fine, BUT only after I remove the original migrations version. 2 things I don't understand.
1 - Why (and what) is the length requirement about? Is this new? My DB? Where do I find these specs?
2 - When you create bad migrations like this, what is the recommended way to handle them? They have not been run correctly, so are not added to the log table, so I have been doing it manually. Just wondering if there's a better way to remove them.
Just thoughts, I know this isn't a Symfony 4 tut, but most things line up.
Hey gstanto
> Why (and what) is the length requirement about? Is this new? My DB? Where do I find these specs?
That's a size limit constraint for "VARCHAR" field types in your DB (I'm assuming that you are using MySQL", by default those fields cannot be larger than 255 characters, but look's like, for some reason, your DB is using a smaller limit, as you already discovered. Why? Well, the Database needs to know how much memory a field requires, so it can assign it.
> When you create bad migrations like this, what is the recommended way to handle them?
You should definitely fix the SQL part that is wrong in the migration before adding it to the project's repository
Cheers!
hm... in the script, you used bin/console doctrine:fixtures:load to update the fixtures. But I thought it won't work, at least not directly? As I understand it, fixtures:load always try to purge the old datas from the tables and then create new. I got some constraint violation with subfamily and genus entities. I guess its because the subFamily in the genus entity is not set to "discard on delete/update"? So I had to drop the database and recreate this, then run migrate and fixutres:load. This works then.
Hey Yang!
Yes, you definitely understand things correctly :). But, doctrine:fixtures:load is pretty smart: it attempts to order the DELETE queries to avoid constraint violations (e.g it would delete the Genus table before trying to delete the SubFamily table). This doesn't always work (sometimes you have circular references), but it does most of the time. But you're also right that if you set the "on delete" behavior, then that would make deleting easier. For example:
// in Genus.php
/**
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\SubFamily")
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
private $subFamily;
With this setup, if a SubFamily were deleted, then all related Genus would also be deleted. As you know, this is done by setting the foreign key relationship in the database (i.e. the delete is done by the database itself). I usually don't set onDelete behavior until/unless I need it... because bad things can happen :).
Cheers!
On the bottom of page 2, i'm trying to load the fixtures but it returns this error:
[Doctrine\DBAL\Exception\UniqueConstraintViolationException]
An exception occurred while executing 'INSERT INTO user (email) VALUES (?)' with params ["weaverryan+1@gmail.com"]:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'weaverryan+1@gmail.com' for key 'UNIQ_8D93D6
49E7927C74'
[Doctrine\DBAL\Driver\PDOException]
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'weaverryan+1@gmail.com' for key 'UNIQ_8D93D6
49E7927C74'
[PDOException]
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'weaverryan+1@gmail.com' for key 'UNIQ_8D93D6
49E7927C74'
The user table remains empty...
Changing fixtures.yml to email: <email()> gives the same result (3 errors)
Manually inserting directly into the table causes no problems.
Que pasa?
Hey Lampje!
Bah, that is strange! When you use <email()> (good idea btw), what exact error do you get? Does it say "Duplicate entry 'weaverryan+1@gmail.com'"? Or does it say "Duplicate entry" but with a different email address? I'm sure this is something small, I would check a few things:
A) Make sure you don't have any other AppBundle\Entity\User entries in your fixtures file somewhere
B) Make sure don't have another email key under your users in the fixtures file
C) Double-check that your setEmail() method in User doesn't have anything funny
I've never had a problem with the randomization with Alice... which is why I'm looking for a simple answer :).
Cheers!
Yes, i get `'SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'weaverryan+1@gmail.com' for key
'UNIQ_8D93D649E7927C75''` 3 times.
I'm a bit further in the chapter and this still persists.. I've copied the .yml from the 'finished' dir, just to be sure that this doesn't have to do with tab's or something silly like that.
I've checked A, no other entries than:
AppBundle\Entity\Genus:
AppBundle\Entity\GenusNote:
AppBundle\Entity\SubFamily:
AppBundle\Entity\User:
No luck on B neither:
AppBundle\Entity\User:
user_{1..10}:
email: weaverryan+<current()>@gmail.com
C:
/**
* @param string $email
*/
public function setEmail( $email ) {
$this->email = $email;
}
Don't know if it matters, i'm using: 10.1.9-MariaDB as database.
Lampje Hmm, yea, I agree - it all looks good to me. If you use <email()>
instead, do you get the same error? I mean, duplicate constraint "weaverryan+1@gmail.com"? Or is a duplicate constraint error with a different email (I'm trying to see if weaverryan+1@gmail.com is sneaking into the code in some other way). Or, what happens if you change weaverryan+ to something else in the fixtures file? Basically, if you play around a bit, do you see any other strange behavior?
It was a good idea to copy the code from the finished dir: those files are created programmatically from the real code that worked for the screencast, so it's definitely correct/good files to work from.
Let me know if you find out anything else - this is very strange!
Just a brief note:
Loading the fixtures tends to break when using a PostgreSQL database. As user is a reserved keyword in PostgreSQL and can only be used with double quotes in all sql queries. Doctrine unfortunately does not do this when running the fixture insert queries. See the following stackoverflow thread:
Easiest fix is to set the table name to something else in your User entity like so:
/**
* @ORM\Entity
* @ORM\Table(name="users")
*/
class User implements UserInterface
Then create a migration, run the migration, and your fixtures will load successfully.
Hey Ben,
Yes, really, changing the table name is the easiest solution. Thanks for sharing this with others!
Cheers!
Hi,
I having a bit of trouble with the migration. In the first instance I got an error that said the tables already exisit etc. So I dropped them and created them again and then did the migration. Im still getting the same error.
Migration 20160818095005 failed during Execution. Error An exception occurred while executing 'CREATE TABLE genus (id INT AUTO_INCREMENT NOT NULL, sub_family_id INT NOT NULL, name VARCHAR(255) NOT NULL, species_count INT NOT NULL, fun_fact VARCHAR(255) DEFAULT NULL, is_published TINYINT(1) NOT NULL, first_discovered_at DATE NOT NULL, INDEX IDX_38C5106ED15310D4 (sub_family_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB':
SQLSTATE[42S01]: Base table or view already exists: 1050 Table 'genus' already exists.
The database was working before adding the User Table and I am not too sure where I have gone wrong here.
Thank you,
Claire
Hey @claire,
You did it right to drop this table, but you didn't have to create it. Migration will create this table for you. This's exactly what migration trying to do based on the error message you got: "Base table or view already exists: 1050 Table 'genus' already exists". So the right solution is: Drop `genus` table and don't create it, just run a migration. If you will still get similar errors, try to drop database at all and run all migrations from scratch. And don't forget to load fixtures after it.
Cheers!
Hello Victor
I have the same case.
here i don't understand why Migration have to create genus table that already exists and the last migration file generated via doctrine:migrations:diff don't include any database action related to this table. I mean if i was in production i risque to loose my data to recreate this table?
Hi Zied,
First of all, *you* should always double check the queries inside any new migration, you can't just rely on Doctrine here. So if you see something that could break your production, i.e. potential data loss - then you need to correct the migration queries.
At second - you should understand well how migrations works and how to use it. If you decide to manage your DB with migrations - then you better need to forget about "bin/console doctrine:schema:update --force", because if you tweak your DB schema with this command - I bet your migrations failed on the next migrate.
Now let's consider the example: you create a new entity. It's a new entity, so it's a new table, which you have not had on prod yet, i.e. in your DB on prod one missing table for now. When you run "bin/console doctrine:migration:diff" - it will create a migration with query which create this table. Then you will need to run this migration on production server which creates this new table for you. So in this case no any data loss, everything is fine.
P.S. you probably won't lose your data on prod when migration recreate a table - this migration just will fail with error "Table already exist".
I hope I understand you right and it'll clarify something for you. Let me know if it makes sense for you.
Cheers!
Thank for your explanation.
1- What i doesn't understand is the following:
When i create a new entity and then i run doctrine:migrations:diff, it will generate a migration to create only the table corresponding to this entity. So why when i run doctrine:migrations:migrate it attempt to execute the older migrations not only the latest.
2- I figure out the situation by running doctrine:migrations:execute 20161124132920 (the last migration) is that correct?
Hey Zied!
Ah, here is the secret! In your database, the migrations library creates and manages a table called migration_versions. Whenever you execute a migration, it adds a new row in this table to record that the migration has finished. In fact, each time that you run doctrine:migrations:migrate
, it looks at each file in your app/DoctrineMigrations
directory, and checks the migration_versions
table for each file to see if it has already been executed. If it has, it does not execute it. If it has not, then it executes it and records a new row in the table.
In our project (and this is the way I recommend doing it), we have created a migration for every database change (i.e. a migration to create every table). My guess is that you may have somehow created the genus
table via some method other than running your migrations (e.g. by running doctrine:schema:update --force
or doctrine:schema:create
). Basically, you should never do this :), because it means that you have a genus
table, but your migration_versions
table is still empty. So when you try to execute your migrations, it runs all of the migrations, including those that try to build the genus
table.
This whole system that uses the migration_versions
table is designed so that things are very predictable when you deploy to production: when you run doctrine:migrations:migrate
, Doctrine will look at the migration_versions
table in your production database to determine exactly which migration files have already been executed and which have not been executed yet.
If you find yourself in a spot where your system is "messed up" and you've accidentally created some tables in a method other than by running your migrations, then I recommend (locally on your development machine) to restart from scratch:
bin/console doctrine:database:drop --force
bin/console doctrine:database:create
bin/console doctrine:migrations:migrate
This will drop the database, which is important because it will drop your migration_versions
table. Then, it will run all of your existing migrations. NOW, you can create a new entity and run doctrine:migrations:diff
. It will correctly generate, and when you run doctrine:migrations:migrate
, Doctrine will know that you only have one new migration to execute.
Let me know if this helps! Cheers!
Hi Ryan, very clear now , it works for me.
I knew migrations from rails and when I got stack I thought that it is not the same idea, but your answer clarified everything.
Thanks.
Hey Victor,
aaa I see thank you for explaining that. I dropped the table and I ran migration but now getting this error:
[Doctrine\DBAL\Exception\ConnectionException]
An exception occured in driver: SQLSTATE[HY000] [1049] Unknown database 'claire_test'
I used ./bin/console doctrine:migrations:migrate
Is that the command to use to run all migrations ?
Thank you!
Ah, usually database creation is up to you. Migrations could create tables as you can see in the code of migrations for this course, but you have to create a DB manually, i.e. I mean you could create it easily by running the command:
$ bin/console doctrine:database:create
And when your DB exists - then start running migrations!
Cheers!
I added these lines to my src/AppBundle/ORM/fixtures.yml script (from previous lessons)
AppBundle\Entity\User:
user_{1..10}:
email: weaverryan+<current()>@gmail.com
and then from the command line I run:
./bin/console doctrine:fixtures:load
It purges the database and loads in new data as expected, However, my user table is empty. All other tables have (new) data. Any hints why this would not be working?
Hey Ernest H.!
Wow, that IS weird... we definitely know the fixtures are working... but strange that only the User table is not being loaded! Let's do some detective work: find the setEmail
method in User
and add this code:
public function setEmail($email)
{
throw new \Exception('setEmail called with '.$email);
$this->email = $email;
}
When you load your fixtures, do you now see this error? And does it show the correct email address (i.e. weaverryan+1@gmail.com)? I want to make sure that (1) the User fixtures are being loaded and (2) that there is not some other fixtures code for the User class somewhere else in the fixtures file that might be overriding this code.
Let me know what you find out! I've definitely never seen this problem before - so I bet it's something minor!
Cheers!
That error does not show.
I did confirm that if I manually load data into the users table (i.e., add a few email addresses), those entries do get wiped. That may be irrelevant depending on how the db gets purged. I don't see anything else in the User class that would affect this. I tried to paste the src/AppBundle/Entity/User.php file here but the comments in the code mess up the post here.
OK, I found the problem. Your hints helped figure it out. I somehow had a src/AppBundle/ORM/fixtures.yml file, but that is the wrong directory. I corrected it to src/AppBundle/DataFixtures/ORM/fixtures.yml and all is working now!!
Thanks very much.
Woohoo! Nice job finding the problem! The toughest bugs are when it's some small, tiny detail like this :D. Now, keep going!!
I'm working on a separate project, but I've been using the security stuff I've learned here in my projects since. I am, however, currently running into a problem. I want to be able to promote (add ROLE_ADMIN) and demote (remove ROLE_ADMIN). This in and of itself isn't difficult;
/**
* @Route("/demote_user/{id}", name="demote_user")
*/
public function demoteUserAction($id){
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('AppBundle:User')->find($id);
//with corresponding method in the user entity
$user->removeOneRole('ROLE_ADMIN');
$em->persist($user);
$em->flush();
return $this->redirectToRoute('list_users');
}
The trouble is that the user password is getting overwritten for reasons I don't entirely understand. Though you can see from dump;die
1 => User {#527 ▼
-id: 8
-email: "test@gmail.com"
-password: "$2y$13$oy2nRiXznzp1I9jrfHRSR.OGtvgR38S.sO3pdaoWWFP1WZhUlMeFq"
-plainPassword: null
-roles: array:1 [▶]
}
that plainPassword is getting set to null and therefore getting pushed onto the db overwriting the existing password. I know the problem, just not how to fix it...
Hey somecallmetim ,
Good detective work! How do you encode your password and where? I suppose you do it in the event listener where encode plain password and store the encoded value in User::$password (as we do in our screencasts). Then in this event listener you have to add one more extra check: if user's plain password is null - then skip encoding it, because user doesn't set a new password.
Cheers!
// composer.json
{
"require": {
"php": ">=5.5.9",
"symfony/symfony": "3.1.*", // v3.1.4
"doctrine/orm": "^2.5", // v2.7.2
"doctrine/doctrine-bundle": "^1.6", // 1.6.4
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
"symfony/swiftmailer-bundle": "^2.3", // v2.3.11
"symfony/monolog-bundle": "^2.8", // 2.11.1
"symfony/polyfill-apcu": "^1.0", // v1.2.0
"sensio/distribution-bundle": "^5.0", // v5.0.22
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
"doctrine/doctrine-migrations-bundle": "^1.1" // 1.1.1
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.0.7
"symfony/phpunit-bridge": "^3.0", // v3.1.3
"nelmio/alice": "^2.1", // 2.1.4
"doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
}
}
In the class \AppBundle\Entity\User, the property is $username, not $name. So I have updated my fixtures.yml to match with this :
AppBundle\Entity\User:
user_{1..10}:
username: <lastname()>
email: weaverryan+<current()>@gmail.com
The opposite operation is possible too : change the property to $name and there will be no need to update the fixtures.