├── modules ├── cat_api │ ├── config │ │ ├── install │ │ │ └── cat_api.settings.yml │ │ └── schema │ │ │ └── cat_api.schema.yml │ ├── cat_api.info.yml │ ├── src │ │ ├── CredentialsInterface.php │ │ ├── Service │ │ │ ├── CatApiClientInterface.php │ │ │ ├── CatApiClient.php │ │ │ ├── CatBreedFactory.php │ │ │ └── CatApiClientFactory.php │ │ ├── Model │ │ │ ├── CatBreedInterface.php │ │ │ └── CatBreed.php │ │ ├── Form │ │ │ ├── SettingsForm.php │ │ │ └── SearchCatsForm.php │ │ ├── Plugin │ │ │ └── Block │ │ │ │ └── CatApiRandomCat.php │ │ └── Controller │ │ │ └── BrowseCatsPage.php │ ├── cat_api.services.yml │ ├── cat_api.routing.yml │ └── README.md ├── custom_entities │ ├── simple │ │ ├── simple.module │ │ ├── simple.permissions.yml │ │ ├── simple.info.yml │ │ ├── config │ │ │ └── simple.schema.yml │ │ ├── simple.links.action.yml │ │ ├── simple.links.menu.yml │ │ ├── simple.links.task.yml │ │ └── src │ │ │ ├── SimpleTypeListBuilder.php │ │ │ ├── SimpleListBuilder.php │ │ │ ├── Form │ │ │ ├── SimpleEntityForm.php │ │ │ └── SimpleTypeEntityForm.php │ │ │ └── Entity │ │ │ ├── SimpleEntity.php │ │ │ └── SimpleTypeEntity.php │ ├── practical │ │ ├── practical.module │ │ ├── practical.permissions.yml │ │ ├── practical.info.yml │ │ ├── config │ │ │ └── practical.schema.yml │ │ ├── practical.links.action.yml │ │ ├── src │ │ │ ├── Entity │ │ │ │ ├── PracticalTypeEntityInterface.php │ │ │ │ ├── PracticalEntityInterface.php │ │ │ │ ├── PracticalTypeEntity.php │ │ │ │ └── PracticalEntity.php │ │ │ ├── PracticalTypeListBuilder.php │ │ │ ├── Form │ │ │ │ ├── PracticalEntityForm.php │ │ │ │ └── PracticalTypeEntityForm.php │ │ │ ├── PracticalEntityAccessControlHandler.php │ │ │ ├── PracticalPermissionsGenerator.php │ │ │ └── PracticalListBuilder.php │ │ ├── practical.links.menu.yml │ │ └── practical.links.task.yml │ └── most_simple │ │ ├── most_simple.module │ │ ├── most_simple.permissions.yml │ │ ├── most_simple.info.yml │ │ ├── config │ │ └── most_simple.schema.yml │ │ └── src │ │ ├── Entity │ │ ├── MostSimpleEntity.php │ │ └── MostSimpleTypeEntity.php │ │ └── Form │ │ └── MostSimpleTypeEntityForm.php ├── blank_module │ └── blank_module.info.yml ├── cat_facts │ ├── README.md │ ├── cat_facts.services.yml │ ├── cat_facts.info.yml │ └── src │ │ ├── CatFactsClient.php │ │ └── Plugin │ │ └── Block │ │ └── CatFacts.php ├── custom_events │ ├── custom_events.info.yml │ ├── custom_events.module │ ├── src │ │ ├── Event │ │ │ └── UserLoginEvent.php │ │ └── EventSubscriber │ │ │ ├── ConfigEventsSubscriberWithDI.php │ │ │ ├── AnotherConfigEventsSubscriber.php │ │ │ └── UserLoginSubscriberWithDI.php │ └── custom_events.services.yml ├── calculator │ ├── calculator.info.yml │ ├── calculator.routing.yml │ ├── calculator.services.yml │ └── src │ │ ├── Service │ │ ├── CalculatorFactoryInterface.php │ │ ├── CalculatorFactory.php │ │ ├── CalculatorInterface.php │ │ └── Calculator.php │ │ ├── Model │ │ ├── CalculatedTotalInterface.php │ │ └── CalculatedTotal.php │ │ ├── Plugin │ │ └── Block │ │ │ └── MyGlobalCalculatorBlock.php │ │ └── Controller │ │ └── CalculatorSandbox.php ├── cookie_services │ ├── cookie_services.info.yml │ ├── cookie_services.routing.yml │ ├── src │ │ ├── Plugin │ │ │ └── Block │ │ │ │ └── BlockUsingCustomCacheContext.php │ │ ├── CookieServiceInterface.php │ │ ├── Cache │ │ │ └── Context │ │ │ │ └── CookieServiceComplexCacheContext.php │ │ ├── CookieServiceComplexData.php │ │ ├── CookieServiceSimple.php │ │ └── Form │ │ │ └── CookieServiceSandbox.php │ └── cookie_services.services.yml ├── services_examples │ ├── services_examples.info.yml │ ├── services_examples.routing.yml │ ├── src │ │ ├── PasswordGeneratorInterface.php │ │ ├── PasswordGeneratorCryptoSecure.php │ │ ├── PasswordGeneratorSimple.php │ │ ├── PasswordGeneratorUnambiguous.php │ │ ├── PasswordGeneratorUnambiguousDecoration.php │ │ ├── Controller │ │ │ └── PasswordGeneratorSandbox.php │ │ └── PasswordGeneratorAbstractBase.php │ └── services_examples.services.yml └── dependency_injection_examples │ ├── dependency_injection_examples.info.yml │ ├── dependency_injection_examples.routing.yml │ ├── src │ ├── AwesomeMarkupCreator.php │ ├── ServiceWithArguments.php │ ├── Form │ │ └── FormWithDependencyInjection.php │ ├── Plugin │ │ └── Block │ │ │ └── BlockWithDependencyInjection.php │ ├── Controller │ │ └── ControllerWithDependencyInjection.php │ ├── SetterInjectionExample.php │ └── ServiceWithWiredServices.php │ └── dependency_injection_examples.services.yml ├── themes └── blank_theme │ └── blank_theme.info.yml └── README.md /modules/cat_api/config/install/cat_api.settings.yml: -------------------------------------------------------------------------------- 1 | base_uri: 'https://api.thecatapi.com/v1/' 2 | api_key: '' 3 | -------------------------------------------------------------------------------- /modules/custom_entities/simple/simple.module: -------------------------------------------------------------------------------- 1 | dispatch($event, UserLoginEvent::EVENT_NAME); 20 | } 21 | -------------------------------------------------------------------------------- /modules/cat_api/cat_api.services.yml: -------------------------------------------------------------------------------- 1 | services: 2 | # Client factory service. 3 | cat_api.client_factory: 4 | class: \Drupal\cat_api\Service\CatApiClientFactory 5 | arguments: 6 | - '@config.factory' 7 | - '@http_client_factory' 8 | - '@serialization.json' 9 | 10 | # Create a service from another Service Factory. 11 | cat_api.client: 12 | class: \Drupal\cat_api\Service\CatApiClient 13 | factory: ['@cat_api.client_factory', 'create'] 14 | 15 | # Cat Breed model factory. 16 | cat_api.breed_factory: 17 | class: \Drupal\cat_api\Service\CatBreedFactory 18 | -------------------------------------------------------------------------------- /modules/dependency_injection_examples/dependency_injection_examples.routing.yml: -------------------------------------------------------------------------------- 1 | controller_with_dependency_injection: 2 | path: 'dependency-injection-controller' 3 | defaults: 4 | _controller: \Drupal\dependency_injection_examples\Controller\ControllerWithDependencyInjection::page 5 | _title: 'Dependency Injection into Controller' 6 | requirements: 7 | _permission: 'access content' 8 | 9 | form_with_dependency_injection: 10 | path: 'dependency-injection-form' 11 | defaults: 12 | _form: \Drupal\dependency_injection_examples\Form\FormWithDependencyInjection 13 | _title: 'Dependency Injection into Form' 14 | requirements: 15 | _permission: 'access content' 16 | -------------------------------------------------------------------------------- /modules/dependency_injection_examples/src/AwesomeMarkupCreator.php: -------------------------------------------------------------------------------- 1 | {$value}"); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /modules/calculator/src/Model/CalculatedTotalInterface.php: -------------------------------------------------------------------------------- 1 | arguments = func_get_args(); 24 | } 25 | 26 | /** 27 | * @return array 28 | * All arguments passed into the constructor. 29 | */ 30 | public function getArguments(): array { 31 | return $this->arguments; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /modules/services_examples/src/PasswordGeneratorCryptoSecure.php: -------------------------------------------------------------------------------- 1 | logger = $logger_channel_factory->get('pass_crypto'); 19 | } 20 | 21 | /** 22 | * {@inheritDoc} 23 | */ 24 | public function getRandomNumber(int $min = NULL, int $max = NULL) { 25 | return random_int($min, $max); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /modules/custom_events/src/Event/UserLoginEvent.php: -------------------------------------------------------------------------------- 1 | account = $account; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /modules/services_examples/src/PasswordGeneratorSimple.php: -------------------------------------------------------------------------------- 1 | t('Label'); 18 | $header['id'] = $this->t('Machine name'); 19 | 20 | return $header + parent::buildHeader(); 21 | } 22 | 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public function buildRow(EntityInterface $entity) { 27 | $row['label'] = $entity->label(); 28 | $row['id'] = $entity->id(); 29 | 30 | return $row + parent::buildRow($entity); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /modules/cat_api/README.md: -------------------------------------------------------------------------------- 1 | # Cat API module 2 | 3 | Custom Drupal module that integrates with [TheCatApi](https://thecatapi.com/). 4 | To use this module, you'll need to get an api key (free). 5 | 6 | This module shows most of the concepts and best practices discussed in my Drupal Camp Florida 2021 Training. 7 | 8 | * [Training Videos Playlist](https://youtube.com/playlist?list=PL8xTPkdstN3FOsuEJ_WibrF4xMelXRG4K) 9 | * [Training Slides](https://www.daggerhartlab.com/wp-content/uploads/Intermediate-OOP-in-Drupal-Jonathan-Daggerhart.pdf) 10 | 11 | ## Examples 12 | 13 | * [Object Design - Model](src/Model/CatBreed.php) 14 | * [Object Design - Service](src/Service/CatApiClient.php) 15 | * [Pattern - Simple Factory](src/Service/CatBreedFactory.php) 16 | * [Pattern - Method Factory](src/Controller/BrowseCatsPage.php) - Other method factories appear in the custom Form and Block plugin 17 | 18 | -------------------------------------------------------------------------------- /modules/services_examples/src/PasswordGeneratorUnambiguous.php: -------------------------------------------------------------------------------- 1 | logger = $logger_channel_factory->get('pass_unambig'); 19 | } 20 | 21 | /** 22 | * {@inheritDoc} 23 | */ 24 | public function getCharacterSets() { 25 | return [ 26 | 'numbers' => '23479', 27 | 'lower' => 'abcdefghjkmnpqrstuvwxyz', 28 | 'upper' => 'ACDEFHJKMNPQRTUVWXYZ', 29 | 'symbols' => '@#$%^&*(){}[]', 30 | ]; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /modules/cat_api/src/Model/CatBreedInterface.php: -------------------------------------------------------------------------------- 1 | t('Label'); 18 | $header['description'] = $this->t('Description'); 19 | $header['id'] = $this->t('Machine name'); 20 | 21 | return $header + parent::buildHeader(); 22 | } 23 | 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function buildRow(EntityInterface $entity) { 28 | /** @var \Drupal\practical\Entity\PracticalTypeEntityInterface $entity */ 29 | $row['label'] = $entity->label(); 30 | $row['description'] = $entity->getDescription(); 31 | $row['id'] = $entity->id(); 32 | 33 | return $row + parent::buildRow($entity); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /modules/calculator/src/Model/CalculatedTotal.php: -------------------------------------------------------------------------------- 1 | update($initial_value); 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function value() { 33 | $last = array_key_last($this->history); 34 | return $this->history[$last]; 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function update($next_value) { 41 | $this->history[] = $next_value; 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function history(): array { 48 | return $this->history; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /modules/cat_facts/src/CatFactsClient.php: -------------------------------------------------------------------------------- 1 | client = $http_client_factory->fromOptions([ 26 | 'base_uri' => 'https://cat-fact.herokuapp.com/', 27 | ]); 28 | } 29 | 30 | /** 31 | * Get some random cat facts. 32 | * 33 | * @param int $amount 34 | * 35 | * @return array 36 | */ 37 | public function random($amount = 1) { 38 | $response = $this->client->get('facts/random', [ 39 | 'query' => [ 40 | 'amount' => $amount 41 | ] 42 | ]); 43 | 44 | $data = Json::decode($response->getBody()); 45 | 46 | if ($amount == 1) { 47 | $data = [$data]; 48 | } 49 | 50 | return $data; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /modules/custom_events/custom_events.services.yml: -------------------------------------------------------------------------------- 1 | services: 2 | # Subscriber to the config events, with dependencies injected. 3 | # Name of this service. 4 | my_config_events_subscriber_with_di: 5 | # Event subscriber class that will listen for the events. 6 | class: '\Drupal\custom_events\EventSubscriber\ConfigEventsSubscriberWithDI' 7 | # Inject services as "arguments" 8 | arguments: 9 | - '@messenger' 10 | # Tagged as an event_subscriber to register this subscriber with the event_dispatch service. 11 | tags: 12 | - { name: 'event_subscriber' } 13 | 14 | # Some other event subscriber with dependency injection. 15 | another_config_events_subscriber: 16 | class: '\Drupal\custom_events\EventSubscriber\AnotherConfigEventsSubscriber' 17 | arguments: 18 | - '@messenger' 19 | tags: 20 | - { name: 'event_subscriber' } 21 | 22 | # Subscriber to the event we dispatch in hook_user_login, with dependencies injected. 23 | custom_events_user_login_with_di: 24 | class: '\Drupal\custom_events\EventSubscriber\UserLoginSubscriberWithDI' 25 | arguments: 26 | - '@database' 27 | - '@date.formatter' 28 | - '@messenger' 29 | tags: 30 | - { name: 'event_subscriber' } 31 | -------------------------------------------------------------------------------- /modules/custom_entities/simple/src/SimpleListBuilder.php: -------------------------------------------------------------------------------- 1 | t('Linked Entity Id'); 18 | $header['content_entity_label'] = $this->t('Content Entity Label'); 19 | $header['content_entity_id'] = $this->t('Content Entity Id'); 20 | $header['bundle_label'] = $this->t('Config Entity (Bundle) Label'); 21 | $header['bundle_id'] = $this->t('Config Entity (Bundle) Id'); 22 | 23 | return $header + parent::buildHeader(); 24 | } 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function buildRow(EntityInterface $entity) { 30 | $row['id'] = $entity->toLink($entity->id()); 31 | $row['content_entity_label'] = $entity->getEntityType()->getLabel()->render(); 32 | $row['content_entity_id'] = $entity->getEntityType()->id(); 33 | $row['bundle_label'] = $entity->bundle->entity->label(); 34 | $row['bundle_id'] = $entity->bundle(); 35 | 36 | return $row + parent::buildRow($entity); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /modules/cookie_services/src/Plugin/Block/BlockUsingCustomCacheContext.php: -------------------------------------------------------------------------------- 1 | addStatus('Block is not cached - ' . __CLASS__); 23 | 24 | return [ 25 | '#cache' => [ 26 | 'contexts' => [ 27 | 'another_cookie_service_complex_city' 28 | ], 29 | ], 30 | '#markup' => Markup::create(' 31 |
Note: Caching must be enabled to test this properly.
32 |
34 | Example custom cache context using cookie data.
35 | If this block not cached you should see a message indicating it is not cached.
36 | If it is cached, you should not see any message mentioning this blocks cached state.
37 |
' . print_r($this->globalCalculator->getTotal()->history(),1) . ''), 87 | ] 88 | ]; 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /modules/dependency_injection_examples/src/SetterInjectionExample.php: -------------------------------------------------------------------------------- 1 | serviceWithArguments = $service_with_arguments; 45 | } 46 | 47 | /** 48 | * Set the service's awesome markup creator. 49 | * 50 | * @param \Drupal\dependency_injection_examples\AwesomeMarkupCreator $awesome_markup_creator 51 | */ 52 | public function setAwesomeMarkupCreator(AwesomeMarkupCreator $awesome_markup_creator) { 53 | $this->awesomeMarkupCreator = $awesome_markup_creator; 54 | } 55 | 56 | /** 57 | * Set the service's logger. 58 | * 59 | * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_channel_factory 60 | */ 61 | public function setLogger(LoggerChannelFactoryInterface $logger_channel_factory) { 62 | $this->logger = $logger_channel_factory->get('di_injected'); 63 | } 64 | 65 | /** 66 | * Set the service's messenger. 67 | * 68 | * @param \Drupal\Core\Messenger\MessengerInterface $messenger 69 | */ 70 | public function setMessenger(MessengerInterface $messenger) { 71 | $this->messenger = $messenger; 72 | } 73 | 74 | /** 75 | * Use the messenger service to output the arguments from our service. 76 | */ 77 | public function messageServiceArgumentsAsMarkup() { 78 | $this->messenger->addStatus('Message and arguments using our injected services:'); 79 | $arguments = $this->serviceWithArguments->getArguments(); 80 | $message = $this->awesomeMarkupCreator->makeMarkup($arguments); 81 | $this->messenger->addStatus($message); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Custom Drupal 8 Entities w/ Bundle Examples 2 | 3 | 1. [Most Simple](modules/custom_entities/most_simple) - The Most Simple entity with bundles possible. Easy to create (very little code), hard to use (no links). 4 | 1. [Simple](modules/custom_entities/simple) - Simple Entity with bundles that is much more usable than the Most Simple Entity. Has links, useful ListBuilders, and informative messages. 5 | 1. [Practical](modules/custom_entities/practical) - More practical entity with bundles, generated bundle permissions, custom access control, entity fields, bundle descriptions, and much better ListBuilders with useful information. Also has interfaces for the Entity classes as best practice. 6 | 7 | [Related Blog Post](http://www.daggerhart.com/drupal-8-custom-entities-bundles/) 8 | 9 | # Learning about Drupal 8 Events 10 | 11 | 1. [Module](modules/custom_events) 12 | 1. [Registering services](modules/custom_events/custom_events.services.yml) - Registering services 13 | 1. [Config CRUD Event Subscriber](modules/custom_events/src/EventSubscriber/ConfigEventsSubscriberWithDI.php) - Example event subscriber that listens for Config object events. 14 | 1. [Custom Event - UserLoginEvent](modules/custom_events/src/Event/UserLoginEvent.php) - Custom Event that will be dispatched on `hook_user_login()`. 15 | 1. [Custom Event - Dispatching Events](modules/custom_events/custom_events.module) - Dispatching an event during `hook_user_login()`. 16 | 1. [Custom Event - Subscribing to Custom Event](modules/custom_events/src/EventSubscriber/ConfigEventsSubscriberWithDI.php) - Event Subscriber that listens for our custom UserLoginEvent. 17 | 18 | [Related Blog Post](https://www.daggerhart.com/drupal-8-hooks-events-event-subscribers/) 19 | 20 | # Services & Dependency Injection 21 | 22 | 1. [Dependency Injection Examples](modules/dependency_injection_examples) 23 | 1. [Services Examples](modules/services_examples) 24 | 1. [Calculator](modules/calculator) - More examples of services and dependency injection. 25 | 26 | # The most bare-minimum possible Drupal 8 module 27 | 28 | 1. [Module](modules/blank_module) 29 | 30 | # The most bare-minimum possible Drupal 8 theme 31 | 32 | 1. [Theme](themes/blank_theme) - This can be enabled from the modules folder. 33 | 34 | # Cat API custom modules 35 | 36 | These modules integrate with various free cat related APIs available online. 37 | 38 | 1. [Cat Facts](modules/cat_facts) 39 | 1. [Original Blog Post](https://www.hook42.com/blog/consuming-json-apis-drupal-8) 40 | 1. [Related Blog Post](https://www.daggerhart.com/guzzle-requests-json-in-drupal-8/) 41 | 1. [Cat API](modules/cat_api) 42 | * Services 43 | * Blocks 44 | * Dependency Injection 45 | -------------------------------------------------------------------------------- /modules/dependency_injection_examples/src/ServiceWithWiredServices.php: -------------------------------------------------------------------------------- 1 | awesomeMarkupCreator = $awesome_markup_creator; 59 | $this->serviceWithArguments = $service_with_arguments; 60 | $this->messenger = $messenger; 61 | $this->logger = $logger_channel_factory->get('di_wired'); 62 | } 63 | 64 | /** 65 | * Get the arguments from our custom service with arguments as markup. 66 | * 67 | * @return \Drupal\Component\Render\MarkupInterface|string 68 | */ 69 | public function getServiceArgumentsAsMarkup() { 70 | $arguments = $this->serviceWithArguments->getArguments(); 71 | return $this->awesomeMarkupCreator->makeMarkup($arguments); 72 | } 73 | 74 | /** 75 | * Use the logger to create a log of the arguments from our service. 76 | */ 77 | public function logServiceArgumentsAsMarkup() { 78 | $this->logger->info('Arguments from our service: %arguments_markup', [ 79 | '%arguments_markup' => $this->getServiceArgumentsAsMarkup(), 80 | ]); 81 | } 82 | 83 | /** 84 | * Use the messenger service to output the arguments from our service. 85 | */ 86 | public function messageServiceArgumentsAsMarkup() { 87 | $this->messenger->addStatus('Arguments from our service:'); 88 | $this->messenger->addStatus( 89 | $this->getServiceArgumentsAsMarkup() 90 | ); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /modules/custom_entities/practical/src/PracticalListBuilder.php: -------------------------------------------------------------------------------- 1 | dateFormatter = $date_formatter; 48 | $this->renderer = $renderer; 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { 55 | return new static( 56 | $entity_type, 57 | $container->get('entity_type.manager')->getStorage($entity_type->id()), 58 | $container->get('date.formatter'), 59 | $container->get('renderer') 60 | ); 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function buildHeader(){ 67 | $header['id'] = $this->t('Linked Entity Label'); 68 | $header['bundle_id'] = $this->t('Bundle'); 69 | $header['owner'] = $this->t('Owner'); 70 | $header['created'] = $this->t('Created'); 71 | $header['changed'] = $this->t('Changed'); 72 | 73 | return $header + parent::buildHeader(); 74 | } 75 | 76 | /** 77 | * {@inheritdoc} 78 | */ 79 | public function buildRow(EntityInterface $entity) { 80 | /** @var \Drupal\practical\Entity\PracticalEntityInterface $entity */ 81 | $row['id'] = $entity->toLink($entity->label()); 82 | $row['bundle_id'] = $entity->bundle(); 83 | $row['owner'] = $entity->getOwner()->toLink($entity->getOwner()->label()); 84 | $row['created'] = $this->dateFormatter->format($entity->getCreatedTime(), 'short'); 85 | $row['changed'] = $this->dateFormatter->format($entity->getChangedTime(), 'short'); 86 | 87 | return $row + parent::buildRow($entity); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /modules/custom_entities/simple/src/Form/SimpleTypeEntityForm.php: -------------------------------------------------------------------------------- 1 | entity; 18 | $content_entity_id = $entity_type->getEntityType()->getBundleOf(); 19 | 20 | $form['label'] = [ 21 | '#type' => 'textfield', 22 | '#title' => $this->t('Label'), 23 | '#maxlength' => 255, 24 | '#default_value' => $entity_type->label(), 25 | '#description' => $this->t("Label for the %content_entity_id entity type (bundle).", ['%content_entity_id' => $content_entity_id]), 26 | '#required' => TRUE, 27 | ]; 28 | 29 | $form['id'] = [ 30 | '#type' => 'machine_name', 31 | '#default_value' => $entity_type->id(), 32 | '#machine_name' => [ 33 | 'exists' => '\Drupal\simple\Entity\SimpleTypeEntity::load', 34 | ], 35 | '#disabled' => !$entity_type->isNew(), 36 | ]; 37 | 38 | return $this->protectBundleIdElement($form); 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | protected function actions(array $form, FormStateInterface $form_state) { 45 | $actions = parent::actions($form, $form_state); 46 | 47 | if (\Drupal::moduleHandler()->moduleExists('field_ui') && $this->getEntity()->isNew()) { 48 | $actions['save_continue'] = $actions['submit']; 49 | $actions['save_continue']['#value'] = $this->t('Save and manage fields'); 50 | $actions['save_continue']['#submit'][] = [$this, 'redirectToFieldUi']; 51 | } 52 | 53 | return $actions; 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | */ 59 | public function save(array $form, FormStateInterface $form_state) { 60 | $entity_type = $this->entity; 61 | $status = $entity_type->save(); 62 | $message_params = [ 63 | '%label' => $entity_type->label(), 64 | '%content_entity_id' => $entity_type->getEntityType()->getBundleOf(), 65 | ]; 66 | 67 | switch ($status) { 68 | case SAVED_NEW: 69 | $this->messenger()->addStatus($this->t('Created the %label %content_entity_id entity type.', $message_params)); 70 | break; 71 | 72 | default: 73 | $this->messenger()->addStatus($this->t('Saved the %label %content_entity_id entity type.', $message_params)); 74 | } 75 | 76 | $form_state->setRedirectUrl($entity_type->toUrl('collection')); 77 | } 78 | 79 | /** 80 | * Form submission handler to redirect to Manage fields page of Field UI. 81 | * 82 | * @param array $form 83 | * @param FormStateInterface $form_state 84 | */ 85 | public function redirectToFieldUi(array $form, FormStateInterface $form_state) { 86 | $route_info = FieldUI::getOverviewRouteInfo($this->entity->getEntityType()->getBundleOf(), $this->entity->id()); 87 | 88 | if ($form_state->getTriggeringElement()['#parents'][0] === 'save_continue' && $route_info) { 89 | $form_state->setRedirectUrl($route_info); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /modules/services_examples/src/Controller/PasswordGeneratorSandbox.php: -------------------------------------------------------------------------------- 1 | get('password_generator_simple'), 39 | $container->get('password_generator_crypto_secure'), 40 | $container->get('password_generator_unambiguous') 41 | ); 42 | } 43 | 44 | /** 45 | * PasswordGeneratorSandbox constructor. 46 | * 47 | * @param \Drupal\services_examples\PasswordGeneratorSimple $password_generator_simple 48 | * Simple password generator service. 49 | * @param \Drupal\services_examples\PasswordGeneratorInterface $password_generator_crypto_secure 50 | * Crypto secure password generator service. 51 | * @param \Drupal\services_examples\PasswordGeneratorInterface $password_generator_unambiguous_crypto_secure 52 | * Unambiguous password generator service (decorated) 53 | */ 54 | public function __construct( 55 | PasswordGeneratorSimple $password_generator_simple, 56 | PasswordGeneratorInterface $password_generator_crypto_secure, 57 | PasswordGeneratorInterface $password_generator_unambiguous_crypto_secure 58 | ) { 59 | $this->simple = $password_generator_simple; 60 | $this->crypto = $password_generator_crypto_secure; 61 | $this->unambiguous = $password_generator_unambiguous_crypto_secure; 62 | } 63 | 64 | /** 65 | * Page output. 66 | * 67 | * @return array[] 68 | * Render array. 69 | */ 70 | public function page() { 71 | return [ 72 | 'password' => [ 73 | '#markup' => Markup::create(" 74 |
{$this->simple->generatePassword()}
76 | "),
77 | ],
78 | 'crypto' => [
79 | '#markup' => Markup::create("
80 | {$this->crypto->generatePassword()}
82 | "),
83 | ],
84 | 'unambiguous' => [
85 | '#markup' => Markup::create("
86 | {$this->unambiguous->generatePassword()}
88 | "),
89 | ],
90 | 'unambiguous_limited' => [
91 | '#markup' => Markup::create("
92 | {$this->unambiguous->generatePassword(32, ['numbers', 'lower', 'upper'])}
94 | "),
95 | ],
96 | ];
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/modules/services_examples/src/PasswordGeneratorAbstractBase.php:
--------------------------------------------------------------------------------
1 | messenger = $messenger;
40 | }
41 |
42 | /**
43 | * Set the logger channel instance on this service.
44 | *
45 | * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_channel_factory
46 | * Logger factory.
47 | */
48 | abstract public function setLogger(LoggerChannelFactoryInterface $logger_channel_factory);
49 |
50 | /**
51 | * Get a random number.
52 | *
53 | * @param int|null $min
54 | * Number floor.
55 | * @param int|null $max
56 | * Number ceiling.
57 | *
58 | * @return int
59 | */
60 | abstract public function getRandomNumber(int $min = 0, int $max = NULL);
61 |
62 | /**
63 | * Return an associative array where each value is a list of characters that
64 | * can be used for password generation.
65 | *
66 | * @return array
67 | */
68 | public function getCharacterSets() {
69 | return [
70 | 'numbers' => '0123456789',
71 | 'lower' => 'abcdefghijklmnopqrstuvwxyz',
72 | 'upper' => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
73 | 'symbols' => '@#$%^&*(){}[]',
74 | ];
75 | }
76 |
77 | /**
78 | * {@inheritDoc}
79 | */
80 | public function generatePassword($length = 16, $allowed_sets = []) {
81 | $sets = $this->getCharacterSets();
82 |
83 | if (!empty($allowed_sets)) {
84 | $sets = array_filter($sets, function($key) use ($allowed_sets) {
85 | return in_array($key, $allowed_sets);
86 | }, ARRAY_FILTER_USE_KEY);
87 | }
88 |
89 | $total_sets = count($sets);
90 | $password = '';
91 | // Ensure each set is used at least once.
92 | foreach ($sets as $characters) {
93 | $characters_length = strlen($characters);
94 | $password .= $characters[$this->getRandomNumber(0, $characters_length - 1)];
95 | $length -= 1;
96 | }
97 |
98 | for ($i = 0; $i < $length; $i++) {
99 | // Get a random character set.
100 | $characters = array_values($sets)[$this->getRandomNumber(0, $total_sets -1)];
101 | $characters_length = strlen($characters);
102 |
103 | // Get a random character from the set.
104 | $password .= $characters[$this->getRandomNumber(0, $characters_length - 1)];
105 | }
106 |
107 | $this->messenger->addStatus($this->t('Password Generated: %password', [
108 | '%password' => $password,
109 | ]));
110 |
111 | $this->logger->info('Password Generated. Length: %length, Sets: %sets', [
112 | '%length' => $length + $total_sets,
113 | '%sets' => implode(', ', array_keys($sets)),
114 | ]);
115 |
116 | return $password;
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/modules/custom_entities/practical/src/Form/PracticalTypeEntityForm.php:
--------------------------------------------------------------------------------
1 | entity;
19 | $content_entity_id = $entity_type->getEntityType()->getBundleOf();
20 |
21 | $form['label'] = [
22 | '#type' => 'textfield',
23 | '#title' => $this->t('Label'),
24 | '#maxlength' => 255,
25 | '#default_value' => $entity_type->label(),
26 | '#description' => $this->t("Label for the %content_entity_id entity type (bundle).", ['%content_entity_id' => $content_entity_id]),
27 | '#required' => TRUE,
28 | ];
29 |
30 | $form['id'] = [
31 | '#type' => 'machine_name',
32 | '#default_value' => $entity_type->id(),
33 | '#machine_name' => [
34 | 'exists' => '\Drupal\practical\Entity\PracticalTypeEntity::load',
35 | ],
36 | '#disabled' => !$entity_type->isNew(),
37 | ];
38 |
39 | $form['description'] = [
40 | '#title' => $this->t('Description'),
41 | '#type' => 'textarea',
42 | '#default_value' => $entity_type->getDescription(),
43 | '#description' => $this->t('This text will be displayed on the "Add %content_entity_id" page.', ['%content_entity_id' => $content_entity_id]),
44 | ];
45 |
46 | return $this->protectBundleIdElement($form);
47 | }
48 |
49 | /**
50 | * {@inheritdoc}
51 | */
52 | protected function actions(array $form, FormStateInterface $form_state) {
53 | $actions = parent::actions($form, $form_state);
54 |
55 | if (\Drupal::moduleHandler()->moduleExists('field_ui') && $this->getEntity()->isNew()) {
56 | $actions['save_continue'] = $actions['submit'];
57 | $actions['save_continue']['#value'] = $this->t('Save and manage fields');
58 | $actions['save_continue']['#submit'][] = [$this, 'redirectToFieldUi'];
59 | }
60 |
61 | return $actions;
62 | }
63 |
64 | /**
65 | * {@inheritdoc}
66 | */
67 | public function save(array $form, FormStateInterface $form_state) {
68 | $entity_type = $this->entity;
69 | $status = $entity_type->save();
70 | $message_params = [
71 | '%label' => $entity_type->label(),
72 | '%content_entity_id' => $entity_type->getEntityType()->getBundleOf(),
73 | ];
74 |
75 | switch ($status) {
76 | case SAVED_NEW:
77 | $this->messenger()->addStatus($this->t('Created the %label %content_entity_id entity type.', $message_params));
78 | break;
79 |
80 | default:
81 | $this->messenger()->addStatus($this->t('Saved the %label %content_entity_id entity type.', $message_params));
82 | }
83 |
84 | $form_state->setRedirectUrl($entity_type->toUrl('collection'));
85 | }
86 |
87 | /**
88 | * Form submission handler to redirect to Manage fields page of Field UI.
89 | *
90 | * @param array $form
91 | * @param FormStateInterface $form_state
92 | */
93 | public function redirectToFieldUi(array $form, FormStateInterface $form_state) {
94 | $route_info = FieldUI::getOverviewRouteInfo($this->entity->getEntityType()->getBundleOf(), $this->entity->id());
95 |
96 | if ($form_state->getTriggeringElement()['#parents'][0] === 'save_continue' && $route_info) {
97 | $form_state->setRedirectUrl($route_info);
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/modules/cat_api/src/Controller/BrowseCatsPage.php:
--------------------------------------------------------------------------------
1 | catApiClient = $cat_api_client;
52 | $this->catBreedFactory = $cat_breed_factory;
53 | $this->request = $request_stack->getCurrentRequest();
54 | }
55 |
56 | /**
57 | * {@inheritDoc}
58 | */
59 | public static function create(ContainerInterface $container) {
60 | return new static(
61 | $container->get('cat_api.client'),
62 | $container->get('cat_api.breed_factory'),
63 | $container->get('request_stack')
64 | );
65 | }
66 |
67 | /**
68 | * Handle the cat browser page route.
69 | *
70 | * @param null $breed_id
71 | *
72 | * @return array
73 | */
74 | public function page($breed_id = NULL) {
75 | if (empty($breed_id)) {
76 | return $this->getBreedLinks();
77 | }
78 |
79 | return [
80 | 'back' => [
81 | '#markup' => Link::createFromRoute('Back', 'cat_api.browse_cats_page')->toString(),
82 | ],
83 | 'details' => $this->getBreedDetails($breed_id),
84 | ];
85 | }
86 |
87 | /**
88 | * Get a list of links for all breeds.
89 | *
90 | * @return array
91 | */
92 | private function getBreedLinks() {
93 | $breeds = $this->catApiClient->get('breeds');
94 | $links = [];
95 | foreach ($breeds as $breed) {
96 | $links[] = Link::createFromRoute($breed['name'], 'cat_api.browse_cats_page', ['breed_id' => $breed['id']]);
97 | }
98 | return [
99 | '#theme' => 'item_list',
100 | '#items' => $links,
101 | ];
102 | }
103 |
104 | /**
105 | * Get details about a specific cat breed.
106 | *
107 | * @param string $breed_id
108 | *
109 | * @return array[]
110 | */
111 | private function getBreedDetails(string $breed_id) {
112 | $results = $this->catApiClient->get('images/search', [
113 | 'breed_ids' => $breed_id,
114 | ]);
115 |
116 | $cat_breed = $this->catBreedFactory->createFromSearchResult($results[0]);
117 |
118 | return [
119 | 'name' => [
120 | '#type' => 'html_tag',
121 | '#tag' => 'h2',
122 | '#value' => $cat_breed->getName(),
123 | ],
124 | 'details' => [
125 | '#theme' => 'item_list',
126 | '#items' => [
127 | Markup::create("Temperament: {$cat_breed->getTemperament()}"),
128 | Markup::create("Description: {$cat_breed->getDescription()}"),
129 | ],
130 | ],
131 | 'image' => [
132 | '#markup' => $cat_breed->getPicture(),
133 | ],
134 | ];
135 | }
136 |
137 | }
138 |
--------------------------------------------------------------------------------
/modules/cat_api/src/Form/SearchCatsForm.php:
--------------------------------------------------------------------------------
1 | catApiClient = $cat_api_client;
43 | $this->catBreedFactory = $cat_breed_factory;
44 | }
45 |
46 | /**
47 | * {@inheritdoc}
48 | */
49 | public static function create(ContainerInterface $container) {
50 | return new static(
51 | $container->get('cat_api.client'),
52 | $container->get('cat_api.breed_factory')
53 | );
54 | }
55 |
56 | /**
57 | * {@inheritdoc}
58 | */
59 | public function getFormId() {
60 | return 'cat_api.search_cats';
61 | }
62 |
63 | /**
64 | * {@inheritdoc}
65 | */
66 | public function buildForm(array $form, FormStateInterface $form_state, string $breed_id = NULL, int $limit = 3) {
67 | $form['search'] = [
68 | '#type' => 'fieldset',
69 | '#title' => $this->t('Search'),
70 | 'breed_id' => [
71 | '#type' => 'select',
72 | '#title' => $this->t('Breed'),
73 | '#options' => array_merge([
74 | '' => $this->t('Select Breed')
75 | ], $this->getBreedOptions()),
76 | '#default_value' => $breed_id ? $breed_id : '',
77 | '#required' => TRUE,
78 | ],
79 | 'limit' => [
80 | '#type' => 'select',
81 | '#title' => $this->t('Limit'),
82 | '#options' => array_combine(range(1, 5), range(1, 5)),
83 | '#default_value' => $limit,
84 | ],
85 | 'actions' => [
86 | '#type' => 'actions',
87 | 'submit' => [
88 | '#type' => 'submit',
89 | '#value' => $this->t('Search'),
90 | ]
91 | ],
92 | ];
93 |
94 | if ($breed_id) {
95 | $form['results'] = [
96 | '#type' => 'fieldset',
97 | '#title' => $this->t('Results'),
98 | 'images' => $this->getImages($breed_id, $limit),
99 | ];
100 | }
101 |
102 | return $form;
103 | }
104 |
105 | /**
106 | * {@inheritdoc}
107 | */
108 | public function submitForm(array &$form, FormStateInterface $form_state) {
109 | $form_state->setRedirect('cat_api.search_cats_page', [
110 | 'breed_id' => $form_state->getValue('breed_id'),
111 | 'limit' => $form_state->getValue('limit'),
112 | ]);
113 | }
114 |
115 | /**
116 | * Get breeds as an options array.
117 | *
118 | * @return array
119 | */
120 | private function getBreedOptions() {
121 | $breeds = $this->catApiClient->get('breeds');
122 | $options = [];
123 | foreach ($breeds as $breed) {
124 | $cat_breed = $this->catBreedFactory->createFromBreedResult($breed);
125 | $options[$cat_breed->getId()] = $cat_breed->getName();
126 | }
127 | return $options;
128 | }
129 |
130 | /**
131 | * Get images for the given breed.
132 | *
133 | * @param string $breed_id
134 | * @param int $limit
135 | *
136 | * @return array
137 | */
138 | private function getImages($breed_id, $limit) {
139 | $results = $this->catApiClient->get('images/search', [
140 | 'breed_ids' => $breed_id,
141 | 'limit' => $limit,
142 | ]);
143 |
144 | $items = [];
145 | foreach ($results as $result) {
146 | $cat_breed = $this->catBreedFactory->createFromSearchResult($result);
147 | $items[] = Markup::create("{$complex_data}
111 | "),
112 | ],
113 | 'actions' => [
114 | '#type' => 'actions',
115 | 'set_complex_cookie_value' => [
116 | '#type' => 'submit',
117 | '#value' => $this->t("Set New Complex Cookie Value - {$this->cookieServiceComplex->getCookieName()}"),
118 | ],
119 | 'delete_complex_cookie' => [
120 | '#type' => 'submit',
121 | '#value' => $this->t("Delete Complex Cookie - {$this->cookieServiceComplex->getCookieName()}"),
122 | ],
123 | ],
124 | ];
125 |
126 | $another_complex_data = print_r($this->anotherCookieServiceComplex->getCookieValue(), 1);
127 | $form['another_complex'] = [
128 | '#type' => 'fieldset',
129 | '#title' => $this->t("Complex Cookie - {$this->anotherCookieServiceComplex->getCookieName()} - Random Data"),
130 | 'current_value' => [
131 | '#markup' => Markup::create("
132 | Current Value:{$another_complex_data}
134 | "),
135 | ],
136 | 'actions' => [
137 | '#type' => 'actions',
138 | 'set_another_complex_cookie_value' => [
139 | '#type' => 'submit',
140 | '#value' => $this->t("Set New Complex Cookie Value - {$this->anotherCookieServiceComplex->getCookieName()}"),
141 | ],
142 | 'delete_another_complex_cookie' => [
143 | '#type' => 'submit',
144 | '#value' => $this->t("Delete Complex Cookie - {$this->anotherCookieServiceComplex->getCookieName()}"),
145 | ],
146 | ],
147 | ];
148 |
149 | return $form;
150 | }
151 |
152 | /**
153 | * {@inheritDoc}
154 | */
155 | public function submitForm(array &$form, FormStateInterface $form_state) {
156 | $trigger = $form_state->getTriggeringElement();
157 | $trigger_button = NULL;
158 | if (isset($trigger['#parents'], $trigger['#parents'][0])) {
159 | $trigger_button = $trigger['#parents'][0];
160 | }
161 |
162 | switch ($trigger_button) {
163 | case 'set_simple_cookie_value':
164 | $this->messenger()->addStatus('Setting new simple cookie value.');
165 | $new_value = $form_state->getValue('new_value') ?? 0;
166 | $this->cookieServiceSimple->setCookieValue($new_value);
167 | break;
168 |
169 | case 'delete_simple_cookie':
170 | $this->messenger()->addStatus('Deleting simple cookie.');
171 | $this->cookieServiceSimple->setShouldDeleteCookie(TRUE);
172 | break;
173 |
174 | case 'set_complex_cookie_value':
175 | $this->messenger()->addStatus("Setting new complex cookie value for {$this->cookieServiceComplex->getCookieName()}.");
176 | $value = [
177 | 'some_value' => random_int(0, random_int(100, 1000000)),
178 | 'another_value' => random_int(0, random_int(100, 1000000)),
179 | 'again' => random_int(0, random_int(100, 1000000)),
180 | 'more' => random_int(0, random_int(100, 1000000)),
181 | ];
182 | $this->cookieServiceComplex->setCookieValue($value);
183 | break;
184 |
185 | case 'delete_complex_cookie':
186 | $this->messenger()->addStatus("Deleting complex cookie {$this->cookieServiceComplex->getCookieName()}.");
187 | $this->cookieServiceComplex->setShouldDeleteCookie(TRUE);
188 | break;
189 |
190 | case 'set_another_complex_cookie_value':
191 | $this->messenger()->addStatus("Setting new complex cookie value for {$this->anotherCookieServiceComplex->getCookieName()}.");
192 | $value = [
193 | 'first' => random_int(0, random_int(100, 1000000)),
194 | 'last' => random_int(0, random_int(100, 1000000)),
195 | 'city' => random_int(0, random_int(100, 1000000)),
196 | 'state' => random_int(0, random_int(100, 1000000)),
197 | ];
198 | $this->anotherCookieServiceComplex->setCookieValue($value);
199 | break;
200 |
201 | case 'delete_another_complex_cookie':
202 | $this->messenger()->addStatus("Deleting complex cookie {$this->anotherCookieServiceComplex->getCookieName()}.");
203 | $this->anotherCookieServiceComplex->setShouldDeleteCookie(TRUE);
204 | break;
205 | }
206 | }
207 |
208 | }
209 |
--------------------------------------------------------------------------------