O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.

Doctrine For Beginners

Come to this talk prepared to learn about the Doctrine PHP open source project. The Doctrine project has been around for over a decade and has evolved from database abstraction software that dates back to the PEAR days. The packages provided by the Doctrine project have been downloaded almost 500 million times from packagist. In this talk we will take you through how to get started with Doctrine and how to take advantage of some of the more advanced features.

  • Entre para ver os comentários

  • Seja a primeira pessoa a gostar disto

Doctrine For Beginners

  1. 1. Doctrine for Beginners
  2. 2. Who am I? - CTO of MoreCommerce.com - Using PHP for 15 years - Involved with Doctrine and Symfony development for 10 years
  3. 3. What is MoreCommerce? - E-commerce seller tools and consumer marketplaces. - Wholly owned subsidiary of Alibaba Group - 100,000 Sellers - 80M Active Shoppers - Billions in GMV runs through MoreCommerce platforms
  4. 4. ● Open source PHP project started in 2006 ● Initially only an Object Relational Mapper ● Evolved to become a collection of high quality PHP packages focused on databases and persistence related functionality What is Doctrine?
  5. 5. +
  6. 6. $ composer create-project symfony/skeleton:4.1.* doctrine-for-beginners $ cd doctrine-for-beginners Create New Symfony Project
  7. 7. $ composer require symfony/orm-pack Install Doctrine
  8. 8. $ composer require symfony/maker-bundle --dev Install MakerBundle
  9. 9. DATABASE_URL=mysql://username:password@127.0.0 .1:3306/doctrine-for-beginners Configure Database URL Customize the DATABASE_URL environment variable in the .env file in the root of your project. In this example we are connecting to MySQL.
  10. 10. Supported Databases ● MySQL ● MariaDB ● SQLite ● PostgreSQL ● SQL Server ● Oracle ● SQL Anywhere ● DB2
  11. 11. DATABASE_URL=sqlite:///%kernel.project_dir%/va r/data.db Using SQLite Change the DATABASE_URL in the .env file to look like this if you want to use SQLite.
  12. 12. $ php bin/console doctrine:database:create Created database `doctrine-for-beginners` for connection named default Create Your Database
  13. 13. Database Migrations
  14. 14. $ php bin/console doctrine:migrations:generate A database migration is an incremental, reversible change to a relational database schema. You define the change to your database schema in a PHP class. Use the doctrine:migrations:generate command to generate a new blank migration. What is a Database Migration?
  15. 15. Generated Database Migration final class Version20181012002437 extends AbstractMigration { public function up(Schema $schema) : void { // this up() migration is auto-generated, please modify it to your needs } public function down(Schema $schema) : void { // this down() migration is auto-generated, please modify it to your needs } }
  16. 16. Write Your Migration public function up(Schema $schema) : void { $usersTable = $schema->createTable('user'); $usersTable->addColumn('id', 'integer', ['autoincrement' => true, 'notnull' => true]); $usersTable->addColumn('username', 'string', ['length' => 255]); $usersTable->setPrimaryKey(['id']); } public function down(Schema $schema) : void { $schema->dropTable('user'); }
  17. 17. Write Your Migration public function up(Schema $schema) : void { $this->addSql('CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, username VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); } public function down(Schema $schema) : void { $this->addSql('DROP TABLE user'); }
  18. 18. $ php bin/console doctrine:migrations:migrate Application Migrations WARNING! You are about to execute a database migration that could result in schema changes and data loss. Are you sure you wish to continue? (y/n)y Migrating up to 20181012002437 from 0 ++ migrating 20181012002437 -> CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, username VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB ++ migrated (0.04s) ------------------------ ++ finished in 0.04s ++ 1 migrations executed ++ 1 sql queries Run Your Migration
  19. 19. $ php bin/console doctrine:migrations:migrate prev Application Migrations WARNING! You are about to execute a database migration that could result in schema changes and data loss. Are you sure you wish to continue? (y/n)y Migrating down to 0 from 20181012002437 -- reverting 20181012002437 -> DROP TABLE user -- reverted (0.05s) ------------------------ ++ finished in 0.05s ++ 1 migrations executed ++ 1 sql queries Revert Your Migration
  20. 20. $ php bin/console make:controller UsersController Generate Controller We need a place to play around so let’s use the MakerBundle to generate a UsersController
  21. 21. AppControllerUsersController class UsersController extends AbstractController { /** * @Route("/users", name="users") */ public function index() { return $this->json([ 'message' => 'Welcome to your new controller!', 'path' => 'src/Controller/UsersController.php', ]); } }
  22. 22. Create Users class UsersController extends AbstractController { /** * @Route("/users/create", name="users_create") */ public function create(Connection $connection, Request $request) { $connection->insert('user', ['username' => $request->request->get('username')]); return $this->json([ 'success' => true, 'message' => sprintf('Created %s successfully!', $request->request->get('username')), ]); } }
  23. 23. $ curl --data "username=jon" http://localhost/users/create { "success":true, "message":"Created jon successfully!" } $ curl --data "username=ryan" http://localhost/users/create { "success":true, "message":"Created ryan successfully!" } Create Users
  24. 24. Read Users class UsersController extends AbstractController { /** * @Route("/users", name="users") */ public function users(Connection $connection) { $users = $connection->fetchAll('SELECT * FROM user'); return $this->json($users); } }
  25. 25. $ curl http://localhost/users [ { "id":"1", "username":"jon" }, { "id":"2", "username":"ryan" } ] Read Users
  26. 26. Read Single User class UsersController extends AbstractController { /** * @Route("/users/{username}", name="user") */ public function user(Connection $connection, string $username) { $users = $connection->fetchAll( 'SELECT * FROM user WHERE username = "' . $username . '"' ); return $this->json($users); } }
  27. 27. STOP! SQL Injection class UsersController extends AbstractController { /** * @Route("/users/{username}", name="user") */ public function user(Connection $connection, string $username) { $users = $connection->fetchAll( 'SELECT * FROM user WHERE username = "' . $username . '"' ); return $this->json($users); } } SQL Injection
  28. 28. Use Parameter Placeholders class UsersController extends AbstractController { /** * @Route("/users/{username}", name="user") */ public function user(Connection $connection, string $username) { $users = $connection->fetchAll( 'SELECT * FROM user WHERE username = :username', ['username' => $username] ); return $this->json($users); } }
  29. 29. ORM: Entities
  30. 30. $ php bin/console make:entity User Generating a User Entity Generate a User entity with property named username that is a string and has a max length of 255 characters. Generates the following files/classes: - src/Entity/User.php (AppEntityUser) - src/Repository/UserRepository.php (AppRepositoryUserRepository)
  31. 31. AppEntityUser /** * @ORMEntity(repositoryClass="AppRepositoryUserRepository") */ class User { /** * @ORMId() * @ORMGeneratedValue() * @ORMColumn(type="integer") */ private $id; /** * @ORMColumn(type="string", length=255) */ private $username; // getters and setters }
  32. 32. ORM: Entity Manager
  33. 33. Inject EntityManager /** * @Route("/users/create/{username}", name="users_create") */ public function create(EntityManagerInterface $em, Request $request) { $user = new User(); $user->setUsername($request->request->get('username')); $em->persist($user); $em->flush(); // ... }
  34. 34. persist() and flush() - persist() - schedules an object to be tracked by Doctrine - flush() - calculates changes made to objects and commits the changes to the database with SQL INSERT, UPDATE and DELETE queries.
  35. 35. How does flush() work? $trackedObjects = getTrackedObjects(); $changesets = []; foreach ($trackedObjects as $trackedObject) { $oldValues = getOldValues($trackedObject); $newValues = getNewValues($trackedObject); $changesets[] = calculateChangeset($oldValues, $newValues); } commitChangesets($changesets);
  36. 36. ORM: Entity Repositories
  37. 37. AppRepositoryUserRepository class UserRepository extends ServiceEntityRepository { public function __construct(RegistryInterface $registry) { parent::__construct($registry, User::class); } /* public function findOneBySomeField($value): ?User { return $this->createQueryBuilder('u') ->andWhere('u.exampleField = :val') ->setParameter('val', $value) ->getQuery() ->getOneOrNullResult() ; } */ }
  38. 38. Inject the UserRepository /** * @Route("/users", name="users") */ public function users(UserRepository $userRepository) { $users = $userRepository->findAll(); return $this->json(array_map(function(User $user) { return [ 'username' => $user->getUsername(), ]; }, $users)); }
  39. 39. Entity Repository: Magic Methods $user = $userRepository->findOneByUsername('jwage'); $user = $userRepository->findByFieldName('value'); Magic methods are implemented using __call(). When a method that does not exist is called, the method name is parsed and a query is generated, executed, and the results are returned.
  40. 40. Entity Repository: Find By $users = $userRepository->findBy(['active' => 1]);
  41. 41. Entity Repository: Find One By $users = $userRepository->findOneBy(['active' => 1]);
  42. 42. Entity Repository: Custom Methods class UserRepository extends ServiceEntityRepository { /** * @return User[] */ public function findActiveUsers() : array { return $this->createQueryBuilder('u') ->andWhere('u.active = 1') ->getQuery() ->execute(); ; } }
  43. 43. $ php bin/console make:entity User Modify Your Entity Modify your User entity and add a property named twitter that is a string and has a max length of 255 characters.
  44. 44. Modify Your Entity class User { // ... /** * @ORMColumn(type="string", length=255, nullable=true) */ private $twitter; // ... }
  45. 45. $ php bin/console make:migration Generate Migration Now that we’ve modified our User class and mapped a new twitter property, when we run make:migration, a migration will be generated with the SQL necessary to add the twitter column to the user table. public function up(Schema $schema) : void { $this->addSql('ALTER TABLE user ADD twitter VARCHAR(255) DEFAULT NULL'); }
  46. 46. $ php bin/console doctrine:migrations:migrate Application Migrations WARNING! You are about to execute a database migration that could result in schema changes and data loss. Are you sure you wish to continue? (y/n)y Migrating up to 20181012041720 from 20181012002437 ++ migrating 20181012041720 -> ALTER TABLE user ADD twitter VARCHAR(255) DEFAULT NULL ++ migrated (0.07s) ------------------------ ++ finished in 0.07s ++ 1 migrations executed ++ 1 sql queries Run Your Migration
  47. 47. Update Entities /** * @Route("/users/update/{username}", name="users_update") */ public function update(UserRepository $userRepository, EntityManagerInterface $em, Request $request, string $username) { $user = $userRepository->findOneByUsername($username); $user->setUsername($request->request->get('username')); $user->setTwitter($request->request->get('twitter')); $em->flush(); return $this->json([ 'success' => true, 'message' => sprintf('Updated %s successfully!', $username), ]); }
  48. 48. $ curl --data "username=ryan&twitter=weaverryan" http://localhost/users/update/ryan { "Success":true,"message": "Updated ryan successfully!" } Update Entities
  49. 49. Use Twitter Property /** * @Route("/users", name="users") */ public function users(UserRepository $userRepository) { $users = $userRepository->findAll(); return $this->json(array_map(function(User $user) { return [ 'username' => $user->getUsername(), 'twitter' => $user->getTwitter(), ]; }, $users)); }
  50. 50. $ curl http://localhost/users [ { "username":"jon", "twitter":null }, { "username":"ryan", "twitter":"weaverryan" } ] Use Twitter Property
  51. 51. Read Single User /** * @Route("/users/{username}", name="user") */ public function user(UserRepository $userRepository, string $username) { $user = $userRepository->findOneByUsername($username); // magic method return $this->json([ 'username' => $user->getUsername(), 'twitter' => $user->getTwitter(), ]); }
  52. 52. $ curl http://localhost/users/ryan { "username":"ryan", "twitter":"weaverryan" } Read Single User
  53. 53. Programmatic Schema Inspection
  54. 54. Inspect your Database Schema /** * @Route("/schema", name="schema") */ public function schema(Connection $connection) { $schemaManager = $connection->getSchemaManager(); $tables = $schemaManager->listTables(); $data = []; foreach ($tables as $table) { $data[$table->getName()] = [ 'columns' => [], ]; $columns = $schemaManager->listTableColumns($table->getName()); foreach ($columns as $column) { $data[$table->getName()]['columns'][] = $column->getName(); } } return $this->json($data); }
  55. 55. $ curl http://localhost/schema { "migration_versions":{ "columns":["version"] }, "user":{ "columns":["id","username","twitter"] } } Inspect your Database Schema
  56. 56. Transactions
  57. 57. When To Use Transactions When you have a unit of work that needs to be ALL OR NOTHING. Meaning, if one part of a larger process fails, all changes made to the database are rolled back.
  58. 58. $connection->beginTransaction(); try { $connection->executeQuery('UPDATE users SET twitter = :twitter WHERE username = :username', [ 'twitter' => 'jwage', 'username' => 'jwage' ]); // execute other updates // do something that throws an Exception and both updates will be rolled back $connection->commit(); } catch (Exception $e) { $connection->rollBack(); throw $e; } Example Transaction
  59. 59. $connection->transactional(function(Connection $connection) { $connection->executeQuery('...'); // do some stuff // do something that throws an exception }); Example Transaction
  60. 60. DQL: Doctrine Query Language
  61. 61. DQL: Doctrine Query Language Query language similar to SQL except in DQL you think in terms of your mapped entities and class properties instead of tables and columns. The DQL language is parsed and transformed to platform specific SQL queries. What is it?
  62. 62. DQL: Query Builder $qb = $entityManager->createQueryBuilder() ->select('u') ->from(User::class, 'u') ->where('u.username = :username') ->setParameter('username', 'jwage'); /** @var User[] $users */ $users = $qb->getQuery()->execute();
  63. 63. DQL -> SQL SELECT u FROM AppEntitiesUser u WHERE u.status = :status SELECT u0_.id AS id_0, u0_.username AS username_1, u0_.twitter AS twitter_2 FROM user u0_ WHERE u0_.username = ? DQL SQL
  64. 64. Writing DQL Manually $query = $entityManager->createQuery( 'SELECT u FROM AppEntityUser u WHERE u.username = :username' ); /** @var User[] $users */ $users = $query->execute([ 'username' => 'jon', ]);
  65. 65. DQL: Advanced
  66. 66. Advanced DQL Examples SELECT u FROM User u WHERE u.phonenumbers IS EMPTY SELECT u FROM User u WHERE SIZE(u.phonenumbers) > 1 SELECT u.id FROM User u WHERE :groupId MEMBER OF u.groups SELECT u.id FROM User u WHERE EXISTS (SELECT p.phonenumber FROM Phonenumber p WHERE p.user = u.id)
  67. 67. DQL: Data Transfer Objects class CustomerDTO { public function __construct($name, $email, $city, $value = null) { // Bind values to the object properties. } } $query = $em->createQuery('SELECT NEW CustomerDTO(c.name, e.email, a.city) FROM Customer c JOIN c.email e JOIN c.address a'); /** @var CustomerDTO[] $users */ $users = $query->getResult();
  68. 68. Native Raw SQL use DoctrineORMEntityManagerInterface; $rsm = new ResultSetMapping(); $rsm->addEntityResult(User::class, 'u'); $rsm->addFieldResult('u', 'id', 'id'); $rsm->addFieldResult('u', 'username', 'username'); $rsm->addFieldResult('u', 'twitter', 'twitter'); $query = $em->createNativeQuery( 'SELECT id, username, twitter FROM user WHERE username = :username', $rsm ); $query->setParameter('username', 'ryan'); /** @var User[] $user */ $users = $query->getResult();
  69. 69. Questions? Connect with me https://twitter.com/jwage https://github.com/jwage https://jwage.com

×