Construire votre démarrage contrôle d'accès, relations d'enregistrement actives et slugs

Ce que vous allez créer

Ceci est la cinquième partie de la série Construire son démarrage avec PHP sur Tuts +. Dans cette série, je vous guide dans le lancement d'une startup du concept à la réalité en utilisant mon application Meeting Planner comme exemple concret. À chaque étape, nous publierons le code de Meeting Planner sous forme d’exemples open source à partir desquels vous pourrez apprendre. Nous aborderons également les problèmes commerciaux liés au démarrage à mesure qu'ils surviennent..

Tout le code de Meeting Planner est écrit dans le framework Yii2 pour PHP. Si vous souhaitez en savoir plus sur Yii2, consultez notre série parallèle Programmer avec Yii2 chez Tuts+.

Dans ce didacticiel, je vais vous expliquer plus de fonctionnalités du framework Yii qui rendront notre code de gestion des espaces existant plus robuste. Nous allons implémenter les contrôles d'accès simples de Yii pour garantir que seuls les utilisateurs connectés ajoutent des lieux. Nous allons utiliser les relations Active Record afin que seuls les espaces ajoutés par l'utilisateur apparaissent dans leur affichage de la page d'index des espaces. Nous allons également utiliser les comportements sluggables de Yii pour implémenter des slugs conviviaux pour les URL afin d'afficher ces lieux. Et nous allons faire un peu plus de nettoyage et de polissage autour des fonctionnalités Place.

Juste un rappel, je participe aux commentaires ci-dessous. Je suis particulièrement intéressé si vous avez des approches différentes ou des idées supplémentaires, ou si vous souhaitez suggérer des sujets pour de futurs tutoriels. J'accepte également les demandes de fonctionnalités pour Meeting Planner.

Contrôle d'accès

Le code que nous avons écrit jusqu'à présent permet à quiconque de créer des espaces, même s'ils ne se sont pas connectés. Nous pouvons utiliser les fonctionnalités de contrôle d'accès simples de Yii2 pour garantir que les utilisateurs s'enregistrent et se connectent avant d'ajouter et d'afficher des espaces.. 

Yii2 propose également un contrôle d'accès basé sur le rôle (RBAC) plus avancé (et complexe) que nous ne mettrons pas en œuvre pour le moment..

Si les utilisateurs ne sont pas connectés lorsqu'ils visitent les pages de lieu, Yii les redirigera vers la page de connexion..

Le contrôle d'accès intégré de Yii2 ne prend en charge que deux rôles par défaut: invité (non connecté), représenté par '?', Et authentifié, représenté par '@'. Si cela vous intéresse, Code Ninja a donné un bon exemple d'extension permettant de prendre en charge les modérateurs et les administrateurs (autorisation simplifiée basée sur les rôles dans Yii 2.0) sans avoir à recourir à RBAC..

Le cadre facilite la mise en œuvre de ces contrôles. Nous ajoutons simplement des comportements au PlaceController.php qui définissent les règles d’accès pour chaque action, par exemple. indexez, créez, affichez, etc. Nous allons examiner ici le comportement d'accès, mais si cela vous intéresse, les filtres de verbe de Yii vous permettent de restreindre les opérations http en fonction de l'action de votre contrôleur.

fonction publique comportements () return ['verbs' => ['class' => VerbFilter :: className (), 'actions' => ['delete' => ['post'],],], 'accès' => ['class' => \ yii \ filters \ AccessControl :: className (), 'only' => ['index', 'create', 'create_geo', 'create_place_google', 'update', 'view', 'slug'], 'rules' => [// autorise les utilisateurs authentifiés ['allow' => true, 'roles' => ['@'],], // tout le reste est refusé],],]; 

Une fois ajouté, si vous cliquez sur le Des endroits menu, vous serez redirigé vers la page de connexion:

Yii gère également la redirection vers la page d'index une fois la connexion établie..

Lorsque les utilisateurs accèdent aux pages de lieu, nous pouvons trouver l'utilisateur actuel avec ce code:

Yii :: $ app-> user-> getId ();

Donc, juste avant d’enregistrer de nouveaux lieux, nous pouvons mettre à jour le créé par champ à l'utilisateur actuellement connecté. Et nous pouvons faire confiance aux contrôles d'accès pour garantir que la méthode create n'est accessible qu'aux utilisateurs authentifiés:

 fonction publique actionCreate () $ model = new Place (); if ($ model-> load (Yii :: $ app-> request-> post ())) $ form = Yii :: $ app-> request-> post (); $ model-> created_by = Yii :: $ app-> user-> getId (); $ model-> save ();

Relations d'enregistrement actives

Dans Meeting Planner, je souhaite connaître le premier utilisateur à créer un lieu. Ceci est stocké dans le créé par champ. Nous souhaitons également suivre les lieux suggérés et utilisés par les utilisateurs pour leurs réunions, la fréquence à laquelle ils utilisent les lieux et leurs favoris. Nous utilisons la table UserPlace pour cela.

Maintenant que nous savons quel utilisateur est connecté lorsque nous créons un espace, nous souhaitons également renseigner une ligne relationnelle dans la table UserPlace..

Tout d'abord, nous devons utiliser le générateur de code de Yii, Gii (http: // localhost: 8888 / mp / index.php / gii / model), pour créer un modèle pour UserPlace:

Ensuite, lorsqu'un lieu est ajouté avec succès, nous souhaitons créer un enregistrement dans la table UserPlace pour l'utilisateur actif. Nous pouvons prolonger Yii ActiveRecord avec le après la sauvegarde méthode. Dans le modèle Place, nous ajoutons:

fonction publique afterSave ($ insert, $ modifiedAttributes) parent :: afterSave ($ insert, $ modifiedAttributes); if ($ insert) $ up = new UserPlace; $ up-> add ($ this-> created_by, $ this-> id); 

Dans le modèle UserPlace, nous ajoutons la fonction:

// ajouter un lieu à la fonction publique de la liste des lieux utilisateurs add ($ user_id, $ place_id) // vérifie si elle existe si (! UserPlace :: find () -> where (['user_id' => $ user_id, 'place_id' => $ place_id]) -> existe ()) // sinon, ajoutez-le $ up = new UserPlace; $ up-> user_id = $ user_id; $ up-> place_id = $ place_id; $ up-> save (); 

Lorsque l'utilisateur visite la page d'index des lieux, nous voulons afficher uniquement les lieux qu'il a utilisés, uniquement ceux de la table UserPlace pour cet utilisateur.. 

Je vais vous expliquer deux manières différentes d'y parvenir. Comme je gagne toujours en expérience avec Yii2.x, les détails de la meilleure approche étaient nouveaux pour moi. Je tiens à remercier Alex Makarov, un développeur de Yii qui gère également YiiFeed.com, pour son aide. Lui et le fondateur de Yii, Qiang Xue, ont été très utiles pour répondre aux questions et soutenir mes efforts avec ces tutoriels sur Yii..

L'approche la plus simple

Le moyen le plus simple est de joindre la table Place à la table UserPlace sur le UserPlace.place_id filtrage de propriété sur UserPlace.user_id avec l'utilisateur actuellement authentifié.

Voici la méthode du contrôleur d'index par défaut:

fonction publique actionIndex () $ searchModel = new PlaceSearch (); $ dataProvider = $ searchModel-> search (Yii :: $ app-> request-> queryParams); return $ this-> render ('index', ['searchModel' => $ searchModel, 'dataProvider' => $ dataProvider,]);  

Nous allons créer une nouvelle méthode de contrôleur appelée Yours:

 fonction publique actionYours () $ query = Place :: find () -> joinWith ('userPlaces') -> where (['user_id' => Yii :: $ app-> user-> getId ()]); $ searchModel = new PlaceSearch (); $ dataProvider = new ActiveDataProvider (['query' => $ query, 'pagination' => ['pageSize' => 10],]); return $ this-> render ('yours', ['dataProvider' => $ dataProvider, 'searchModel' => $ searchModel,]);  

Remarquerez que joinWith ('userPlaces') utilise la requête relationnelle générée par Gii dans Place.php. Cela peut être un peu déroutant si vous omettez le préfixe "get":

 / ** * @return \ yii \ db \ ActiveQuery * / fonction publique getUserPlaces () return $ this-> hasMany (UserPlace :: className (), ['place_id' => 'id']);  

Nous devons également modifier le PlaceSearch classe:

// ajoute une règle requise pour la fonction publique created_by rules () return [[['' created_by '],' required '], // ajoute la jointure dans la requête de recherche fonction publique search ($ params) $ query = Place: : find () -> joinWith ('user_place') -> where (['user_id' => Yii :: $ app-> user-> getId ()]); $ dataProvider = new ActiveDataProvider (['query' => $ query,]);

Une approche alternative

Un autre moyen d'implémenter cela consiste à utiliser un contrôleur UserPlace. Après tout, nous visionnons "les lieux de l'utilisateur". Dans ce cas, nous pouvons apporter une légère modification à la méthode d’indexation du contrôleur générée par Gii:

fonction publique actionIndex () $ searchModel = new UserPlaceSearch (); $ searchModel-> user_id = Yii :: $ app-> user-> getId (); $ dataProvider = $ searchModel-> search (Yii :: $ app-> request-> queryParams); return $ this-> render ('index', ['searchModel' => $ searchModel, 'dataProvider' => $ dataProvider,]); 

Puis dans /views/user-place/index.php, nous devons modifier les chemins de liaison générés et modéliser l'accès aux données, par exemple. / place / create_place_google:

'Place',]), ['/ place / create'], ['class' => 'btn btn-success'])?> 'btn btn-success'])?> 'Place']), ['/ place / create_place_google'], ['class' => 'btn btn-success'])?>

Dans le widget Grid View, nous utilisons la relation UserPlace avec la table Place pour accéder aux propriétés de ce dernier modèle, par exemple.. $ modèle-> lieu-> limace:

 $ dataProvider, // 'filterModel' => $ searchModel, 'columns' => [// ['class' => 'yii \ grid \ SerialColumn'], ['attribut' => 'nom_emplacement', 'format' = > 'raw', 'value' => function ($ model) return '
'. $ modèle-> lieu-> nom.'
'; ,], ['attribut' => 'type_emplacement', 'format' => 'raw', 'value' => function ($ model) return '
'. $ model-> place-> getPlaceType ($ model-> place-> place_type).'
'; ,], ['class' => 'yii \ grid \ ActionColumn', 'template' => 'view update', 'buttons' => ['view' => function ($ url, $ model ) return Html :: a ('', Yii :: getAlias ​​(' @ web ').' / Place /'.$ modèle-> lieu-> slug, ['titre' => Yii :: t ('yii', 'Voir'),]) ; , 'update' => function ($ url, $ model) return Html :: a ('', Yii :: getAlias ​​(' @ web ').' / Place / update /'.$ model-> place_id, ['title' => Yii :: t ('yii', 'Update'),]); ],],],]); ?>

L'approche UserPlace nécessite quelques modifications aux liens autour de la chapelure, des liens d'action de widget et des boutons de commande, mais reste assez simple..

Si vous examinez le code dans cette version, vous pouvez voir les espaces de l'utilisateur à la fois: http: // localhost: 8888 / mp / place / yours et http: // localhost: 8888 / mp / user-place. Il est intéressant de voir comment vous pouvez réaliser cette fonctionnalité avec deux approches différentes au sein de Yii..

Des limaces

Une fois que vous avez activé de jolies URL dans le framework Yii, la page d'affichage d'un objet modèle ressemble à quelque chose comme http://meetingplanner.com/place/692, où 692 représente l'ID de l'objet à afficher. En plus d'être indescriptible pour l'utilisateur, il est également moins efficace avec les moteurs de recherche. Il est préférable d'utiliser des chaînes conviviales pour les URL telles que http://meetingplanner.com/place/caffe-seattle. La chaîne est parfois appelée limace. Yii2 fournit un support intégré pour les slugs, sous la forme de comportements Sluggable. Les comportements font partie de la prise en charge de Yii Active Record et peuvent être appliqués automatiquement aux modèles d'objet de données..

Dans notre modèle de lieu, nous avons ajouté une propriété slug. Voici comment nous implémentons le comportement Sluggable dans le modèle d'emplacement:

fonction publique comportements () return [['class' => SluggableBehavior :: className (), 'attribut' => 'nom', 'immuable' => true, 'EnsureUnique' => true,],

Yii assurera pendant le enregistrer() opération que le champ slug est rempli avec une version conviviale du champ de nom. En d'autres termes, si le nom du lieu est Oddfellows Cafe, la limace sera oddfellows-cafe.

La propriété immuable garantit que le slug ne change jamais, même si le nom convivial est modifié. Ceci est utile pour conserver des liens vers des lieux ainsi que des références de moteurs de recherche..

La propriété EnsureUnique génère un slug unique en ajoutant automatiquement un index de suffixe.. 

Le générateur de code automatisé de Yii, Gii, relie généralement les objets par des identifiants numériques. Nous voulons changer ces liens pour utiliser la limace. Ce code existe à deux endroits.

Le premier est sur la page d'index de lieu dans les colonnes d'action de la grille. Vous pouvez personnaliser ces liens comme ceci dans /frontend/views/places/index.php:

 $ dataProvider, 'filterModel' => $ searchModel, 'columns' => [['class' => 'yii \ grid \ SerialColumn'], 'name', ['attribut' => 'type_emplacement', 'format' = > 'raw', 'value' => function ($ model) return '
'. $ model-> getPlaceType ($ model-> lieu_type).'
'; ,], ['class' => 'yii \ grid \ ActionColumn', 'template' => 'view update', 'buttons' => ['view' => function ($ url, $ model ) return Html :: a ('',' place /'.$ model-> slug, ['title' => Yii :: t ('yii', 'View'),]); ],],],]); ?>

L'autre endroit est dans la chapelure:

Par exemple, dans /frontend/views/place/update.php, Nous avons besoin de changer ça:

title = Yii :: t ('frontend', 'Update modelClass:', ['modelClass' => 'Place',]). ". $ modèle-> nom; $ ceci-> params [" chapelure "] [] = ['label' => Yii :: t ('frontend', 'Places'), 'url' => ['index']]; $ this-> params ['breadcrumbs'] [] = [' label '=> $ model-> name,' url '=> [' view ',' id '=> $ model-> id]]; $ this-> params [' breadcrumbs '] [] = Yii :: t ('frontend', 'Update');?> 

Remplacement du code d'identification de la vue pour utiliser le slug:

… $ This-> params ['breadcrumbs'] [] = ['label' => Yii :: t ('frontend', 'Places'), 'url' => ['index']]; $ this-> params ['breadcrumbs'] [] = ['label' => $ modèle-> nom, 'url' => ['slug', 'slug' => $ modèle-> slug]]; $ this-> params ['breadcrumbs'] [] = Yii :: t ('frontend', 'Update');… 

Nettoyage et polonais

Au fur et à mesure de la construction de Meeting Planner, des sprints de code seront créés pour créer de nouvelles fonctionnalités et périodes nécessaires au nettoyage et au polissage. Ceci est susceptible d'être un cycle répétitif.

Je vais passer en revue quelques scénarios que je souhaite aborder à ce stade. Bien sûr, il restera des zones de nettoyage nécessaires tant que nous construirons du code, en tenant compte des commentaires des utilisateurs et en améliorant le produit..

Extension de la barre de navigation

Yii2 est bien intégré à Bootstrap, pour que vos applications aient fière allure et s'exécutent de manière réactive tout de suite. Si vous souhaitez créer une barre de navigation avec des menus déroulants, il est utile de consulter la documentation de Bootstrap et de comprendre l'utilisation par Yii2 de la notation à tableaux courts en PHP. La documentation du widget Yii2 Navbar ne fournit pas beaucoup d'exemples pour le moment..

J'ai décidé de commencer à mettre à jour la navigation de Meeting Planner avec certains menus déroulants basés sur l'état de l'utilisateur, par exemple. invité ou authentifié.

Voici le code qui implémente ce qui précède - je suis sûr que je continuerai à le modifier au fur et à mesure:

NavBar :: begin (['brandLabel' => Yii :: t ('frontend', 'MeetingPlanner.io'), // 'brandUrl' => Yii :: $ app-> homeUrl, 'options' => [' class '=>' navbar-inverse navbar-fixed-top ',],]); if (Yii :: $ app-> user-> isGuest) $ menuItems [] = ['label' => Yii :: t ('frontend', 'Signup'), 'url' => ['/ site / s'inscrire']]; $ menuItems [] = ['label' => Yii :: t ('interface', 'Login'), 'url' => ['/ site / login']];  else $ menuItems = [['label' => Yii :: t ('frontend', 'Places'), 'url' => ['/ place / yours']],];  $ menuItems [] = ['label' => Yii :: t ('frontend', 'About'), 'items' => [['' label '=> Yii :: t (' frontend ',' En savoir plus '),' url '=> [' / site / sur ']], [' label '=> Yii :: t (' frontend ',' Contact '),' url '=> [' / site / contact ' ]],],]; if (! Yii :: $ app-> user-> isGuest) $ menuItems [] = ['label' => 'Compte', 'items' => [['label' => Yii :: t ('frontend ',' Coordonnées '),' url '=> [' / utilisateur-contact '],], [' label '=> Yii :: t (' interface ',' Déconnexion ').' ('. Yii :: $ app-> utilisateur-> identité-> nom d'utilisateur.') ',' Url '=> [' / site / logout '],' linkOptions '=> [' data-method '=>' post ']],],];  echo Nav :: widget (['options' => ['class' => 'navbar-nav navbar-right'], 'items' => $ menuItems,]); NavBar :: end ();

Valider les modèles correctement avant de les enregistrer

J'ai réécrit les actions de création dans PlaceController pour valider les modèles avant d'essayer d'ajouter des relations UserPlace. Dans certains cas, des données non valides pourraient être soumises, ce qui aurait pour effet de faire échec aux tentatives d’ajout de relations. Cela garantit également que les utilisateurs seront renvoyés au formulaire avec des messages d'erreur conviviaux en cas d'échec des validations..

fonction publique actionCreate () $ model = new Place (); if ($ model-> load (Yii :: $ app-> request-> post ())) $ form = Yii :: $ app-> request-> post (); if (! is_numeric ($ model-> lieu_type)) $ model-> lieu_type = Place :: TYPE_OTHER;  $ model-> created_by = Yii :: $ app-> user-> getId (); // valide le formulaire par rapport aux règles du modèle if ($ model-> validate ()) // toutes les entrées sont valides $ model-> save (); // recherche emplacement GPS à partir de l'adresse $ model-> addLocationFromAddress ($ model, $ form ['Place'] ['full_address']); return $ this-> redirect (['view', 'id' => $ model-> id]);  else // échec de la validation return $ this-> render ('create', ['model' = = $ model,]);  else return $ this-> render ('create', ['model' => $ model,]); 

Prévenir les doublons

Dans notre code de création de lieu initial, nous voulons nous protéger contre les doublons. Les lieux peuvent avoir des noms identiques, par exemple il y a beaucoup de cafés Starbucks (beaucoup trop), mais nous voulons éviter que le lieu exact ne soit créé deux fois, par exemple. Starbucks Coffee avec le même identifiant Google Place ou un nom et une adresse identiques.

Yii offre aux validateurs de modèles qui s’intègrent avec ActiveForms une grande partie de ce travail pour nous. Voici les règles que nous définirons dans le modèle Place:

/ ** * @inheritdoc * / règles de fonction publique () return [['' nom ',' slug '],' obligatoire '], [[' type_emplacement ',' statut ',' created_by ',' created_at ', 'updated_at'], 'entier'], [['nom', 'google_place_id', 'slug', 'site web', 'adresse complète', 'voisinage'], 'chaîne', 'max' => 255], [ ['site web'], 'url'], [['slug'], 'unique'], [['searchbox'], 'unique', 'targetAttribute' => 'google_place_id'], [['nom', 'full_address'], 'unique', 'targetAttribute' => ['name', 'full_address']],]; 

Les validateurs peuvent appliquer les champs obligatoires ainsi que les types et la longueur de champs. Ils peuvent également valider des URL telles que le champ de site Web ci-dessus, des adresses électroniques, etc. Vous pouvez également écrire des validateurs personnalisés.

Il existe plusieurs validateurs intégrés spécifiques à l'unicité. Par exemple, ils peuvent valider l'unicité d'un champ comme nous l'avons fait avec le champ slug. Mais il existe aussi des validateurs d'unicité plus complexes.

Lorsque l'utilisateur ajoute un lieu avec la saisie semi-automatique de Google, nous voulons imposer l'unicité des résultats obtenus. google_place_id dans le champ masqué, mais nous voulons que le message d'erreur apparaisse sur le Barre de recherche champ. Cette définition l'accomplit:

[['' searchbox '],' unique ',' targetAttribute '=>' google_place_id '], 

Nous voulons également nous assurer que le nom et l'adresse sont uniques ensemble. En d'autres termes, plusieurs lieux peuvent avoir le même nom ou la même adresse, mais aucun lieu ne peut être identique dans les deux champs. Cette définition fait que:

[['name', 'full_address'], 'unique', 'targetAttribute' => ['name', 'full_address']],

Bien entendu, de nombreux utilisateurs peuvent ajouter le même endroit à leur liste de UserPlaces..

Éliminer la suppression

Je souhaite également protéger contre la suppression de lieux. Le générateur de code automatisé de Yii, Gii, comporte généralement des liens pour supprimer des opérations de la grille d'index et mettre à jour des pages. Nous voulons supprimer ceux-ci. Et nous voulons restreindre l'accès à l'action de suppression à partir du contrôleur.

Voici un exemple de page d'index d'emplacement avec des icônes de suppression:

Lorsque nous avons personnalisé les liens slug ci-dessus, nous avons éliminé l'utilisation par défaut de la commande delete.

En passant, j’apprécie vraiment l’utilisation de Bootstrap et des glyphiques par Yii. Ils travaillent magnifiquement.

Voici la page d'affichage avec son bouton de suppression:

Pour le moment, commentons le code du bouton Supprimer dans /frontend/views/place/view.php. Nous voudrons peut-être le rajouter aux administrateurs à un moment donné.

  $ model-> id], ['class' => 'btn btn-danger', 'data' => ['confirm' => Yii :: t ('frontend', 'Êtes-vous sûr de vouloir supprimer cet élément? ? '),' method '=>' post ',],]) * /?>

Empêcher la saisie semi-automatique de saisir la clé

Alors que je construisais une partie du code de saisie semi-automatique HTML5 et Google Places, quelques erreurs se sont produites, certaines liées à JavaScript..

Par exemple, si vous cliquez sur la touche Entrée après avoir saisi le champ de saisie semi-automatique, Google envoie le formulaire. Nous devons passer outre cette.

Dans notre create_place.js, nous ajoutons un gestionnaire de clés pour empêcher le formulaire de soumettre:

function setupListeners () //… var place_input = document.getElementById ('place-searchbox'); google.maps.event.addDomListener (place_input, 'keydown', fonction (e) if (e.keyCode == 13) e.preventDefault (););  

Maintenant, quand vous frappez Entrer, vous verrez la carte sur la page et pourrez modifier le reste du formulaire avant de le soumettre.

Examen des avantages de l'utilisation d'un cadre

Beaucoup de gens pensent que PHP est une plate-forme moins sérieuse ou moins performante. Pour moi, le succès de Facebook avec PHP leur a toujours prouvé le contraire. 

Beaucoup de gens n'ont pas entendu parler du framework Yii ou ont rejeté la valeur des frameworks. 

Peu de temps après, dans cette série de startups, nous avons déjà bénéficié d'un grand nombre de fonctionnalités de développement du framework de Yii: développement rapide, livraison d'une architecture propre et code de qualité:

  • Architecture MVC
  • migrations de bases de données
  • Relations et validation du modèle Active Record
  • génération automatique de code
  • Intégration Bootstrap et Glyphicons
  • filtres de contrôle d'accès
  • comportements de limaces
  • internationalisation

C'est pourquoi je suis un ardent défenseur du cadre Yii. Cela fait de moi un développeur beaucoup plus efficace, capable de fournir des solutions beaucoup plus rapidement qu'avec PHP vanilla..

Et après?

Dans le prochain tutoriel Construire votre démarrage avec PHP, nous allons créer un support pour les paramètres utilisateur, les contacts et les images de profil..

N'hésitez pas à ajouter vos questions et commentaires ci-dessous; Je participe généralement aux discussions. Vous pouvez également me joindre sur Twitter @reifman ou m'envoyer un email directement.

Liens connexes

  • Programmation avec Yii2: Mise en route
  • Introduction au framework Yii